feat: add salary records page

This commit is contained in:
zhx 2026-07-09 15:50:08 +08:00
parent fee62b696d
commit e989b13e92
6 changed files with 547 additions and 3 deletions

View File

@ -212,6 +212,15 @@ export function listTeamBillTable(params) {
})
}
// BD/BD Leader/Admin 工资结算记录.
export function listSalarySettlementRecords(params) {
return request({
url: '/team/salary/settlement/list',
method: 'get',
params
})
}
// 团队账单-内部备注添加.
export function addBillInternalRemarks(data) {
return request({
@ -384,4 +393,3 @@ export function teamBindBd(data) {
data
})
}

View File

@ -121,6 +121,7 @@ export default {
'区域配置': 'Region Configuration',
'用户银行': 'User Bank',
'目标兑换工资记录': 'Target Salary Exchange Records',
'工资记录': 'Salary Records',
'用户银行流水': 'User Bank Transactions',
'用户银行账户': 'User Bank Accounts',
'美金兑换金币记录': 'USD-to-Gold Exchange Records',

View File

@ -7309,6 +7309,68 @@ export default {
memberAccount: 'Member Account'
}
},
salaryRecord: {
loading: 'Loading',
filter: {
system: 'System',
identity: 'Identity',
userId: 'User ID',
month: 'Bill Month',
billHalf: 'Bill Cycle',
status: 'Status',
bizNo: 'Biz No'
},
date: {
monthFormat: 'yyyy MM'
},
action: {
search: 'Search'
},
identity: {
all: 'All',
bd: 'BD',
bdLeader: 'BD Leader',
admin: 'Admin'
},
billHalf: {
first: 'First Half',
second: 'Second Half'
},
status: {
pending: 'Pending',
success: 'Success',
failed: 'Failed'
},
table: {
system: 'System',
identity: 'Identity',
user: 'User',
bill: 'Bill Cycle',
settlementSalary: 'Settlement Salary',
status: 'Status',
roleDetails: 'Role Data',
bizNo: 'Biz No',
failureReason: 'Failure Reason',
createTime: 'Created At',
updateTime: 'Updated At'
},
detail: {
agencyNumber: 'Agent Count',
bdNumber: 'BD Count',
teamSalaryAmount: 'Team Salary',
teamRechargeAmount: 'Team Recharge',
hitPolicyType: 'Hit Policy',
commissionRate: 'Commission Rate',
basicSalary: 'Basic Salary',
rewardAmount: 'Reward Amount',
newRechargeAgentCount: 'New Recharge Agents',
rechargeAgentSalary: 'Recharge Agent Salary',
bdLeaderSalary: 'BD Leader Salary',
bdPolicyFinalSalary: 'BD Policy Salary',
bdPolicyHitType: 'BD Hit Policy',
totalSalary: 'Total Salary'
}
},
userBankDiamondWithdrawGoldApply: {
filter: {
system: 'System',

View File

@ -7306,6 +7306,68 @@ export default {
memberAccount: '成员账户'
}
},
salaryRecord: {
loading: '加载中',
filter: {
system: '系统',
identity: '身份',
userId: '用户ID',
month: '账单月份',
billHalf: '账单周期',
status: '状态',
bizNo: '业务号'
},
date: {
monthFormat: 'yyyy 年 MM 月'
},
action: {
search: '搜索'
},
identity: {
all: '全部',
bd: 'BD',
bdLeader: 'BD Leader',
admin: 'Admin'
},
billHalf: {
first: '上半月',
second: '下半月'
},
status: {
pending: '待处理',
success: '成功',
failed: '失败'
},
table: {
system: '系统',
identity: '身份',
user: '用户',
bill: '账单周期',
settlementSalary: '结算工资',
status: '状态',
roleDetails: '身份数据',
bizNo: '业务号',
failureReason: '失败原因',
createTime: '创建时间',
updateTime: '更新时间'
},
detail: {
agencyNumber: 'Agent 数量',
bdNumber: 'BD 数量',
teamSalaryAmount: '团队工资',
teamRechargeAmount: '团队充值',
hitPolicyType: '命中政策',
commissionRate: '提成比例',
basicSalary: '基础工资',
rewardAmount: '奖励金额',
newRechargeAgentCount: '新增充值代理',
rechargeAgentSalary: '充值代理工资',
bdLeaderSalary: 'BD Leader 工资',
bdPolicyFinalSalary: 'BD 政策工资',
bdPolicyHitType: 'BD 命中政策',
totalSalary: '总工资'
}
},
userBankDiamondWithdrawGoldApply: {
filter: {
system: '系统',

View File

@ -91,6 +91,12 @@ export const mockRouters = [
name: 'BdLead',
component: () => import('@/views/team/bd-lead/index'),
meta: { title: 'BD Lead', icon: 'form' }
},
{
path: 'team/salary-record',
name: 'SalaryRecord',
component: () => import('@/views/team/salary-record/index'),
meta: { title: '工资记录', icon: 'form' }
}
]
},
@ -424,7 +430,7 @@ export const mockRouters = [
meta: { title: '用户银行卡提现现金申请', icon: 'form' }
},
{
path:'/withdraw-diamond-apply-page',
path: '/withdraw-diamond-apply-page',
name: 'UserBankWithdrawDiamondApply',
component: () =>
import('@/views/user/bank-balance/withdraw-diamond-apply/index'),
@ -438,7 +444,7 @@ export const mockRouters = [
meta: { title: '用户银行金币兑换申请', icon: 'form' }
},
{
path:'/user-bank-diamond-withdraw-gold-apply',
path: '/user-bank-diamond-withdraw-gold-apply',
name: 'UserBankDiamondWithdrawGoldApply',
component: () =>
import('@/views/user/bank-balance/diamond-withdraw-gold-apply/index'),

View File

@ -0,0 +1,405 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-select
v-model="listQuery.sysOrigin"
:placeholder="$t('pages.salaryRecord.filter.system')"
style="width: 120px"
class="filter-item"
@change="handleSearch"
>
<el-option
v-for="item in permissionsSysOriginPlatforms"
:key="item.value"
:label="item.label"
:value="item.value"
>
<span style="float: left;">
<sys-origin-icon :icon="item.value" :desc="item.value" />
</span>
<span style="float: left;margin-left:10px">{{ item.label }}</span>
</el-option>
</el-select>
<el-select
v-model="listQuery.identity"
:placeholder="$t('pages.salaryRecord.filter.identity')"
class="filter-item"
clearable
style="width: 140px"
@change="handleSearch"
>
<el-option
v-for="item in identityOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="filter-item">
<account-input
v-model="listQuery.userId"
:sys-origin="listQuery.sysOrigin"
:placeholder="$t('pages.salaryRecord.filter.userId')"
/>
</div>
<el-date-picker
v-model="billMonth"
type="month"
:placeholder="$t('pages.salaryRecord.filter.month')"
:format="$t('pages.salaryRecord.date.monthFormat')"
value-format="yyyyMM"
class="filter-item"
@change="handleBillChange"
/>
<el-select
v-model="billHalf"
:placeholder="$t('pages.salaryRecord.filter.billHalf')"
class="filter-item"
clearable
style="width: 130px"
@change="handleBillChange"
>
<el-option
v-for="item in billHalfOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-select
v-model="listQuery.status"
:placeholder="$t('pages.salaryRecord.filter.status')"
class="filter-item"
clearable
style="width: 120px"
@change="handleSearch"
>
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model.trim="listQuery.bizNo"
:placeholder="$t('pages.salaryRecord.filter.bizNo')"
class="filter-item filter-wide"
clearable
@keyup.enter.native="handleSearch"
/>
<el-button
:loading="searchLoading"
:disabled="searchDisabled"
class="filter-item"
type="primary"
icon="el-icon-search"
@click="handleSearch"
>
{{ $t('pages.salaryRecord.action.search') }}
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
:element-loading-text="$t('pages.salaryRecord.loading')"
fit
highlight-current-row
>
<el-table-column prop="sysOrigin" :label="$t('pages.salaryRecord.table.system')" align="center" width="80">
<template slot-scope="scope">
<sys-origin-icon :icon="scope.row.sysOrigin" :desc="scope.row.sysOrigin" />
</template>
</el-table-column>
<el-table-column :label="$t('pages.salaryRecord.table.identity')" align="center" width="110">
<template slot-scope="scope">
<el-tag :type="identityTagType(scope.row.identity)" size="mini">
{{ identityLabel(scope.row.identity) }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('pages.salaryRecord.table.user')" align="center" min-width="210">
<template slot-scope="scope">
<user-table-exhibit
v-if="scope.row.userProfile"
:user-profile="scope.row.userProfile"
:query-details="true"
/>
<span v-else>{{ scope.row.userId || '-' }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('pages.salaryRecord.table.bill')" align="center" min-width="150">
<template slot-scope="scope">
<div>{{ scope.row.billBelong || '-' }}</div>
<div class="muted-text">{{ scope.row.billTitle || '-' }}</div>
</template>
</el-table-column>
<el-table-column :label="$t('pages.salaryRecord.table.settlementSalary')" align="center" width="120">
<template slot-scope="scope">
{{ amountText(scope.row.settlementSalary) }}
</template>
</el-table-column>
<el-table-column :label="$t('pages.salaryRecord.table.status')" align="center" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.status" :type="statusTagType(scope.row.status)" size="mini">
{{ statusLabel(scope.row.status) }}
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="$t('pages.salaryRecord.table.roleDetails')" align="left" min-width="280">
<template slot-scope="scope">
<div class="detail-lines">
<div v-for="(line, index) in roleDetailLines(scope.row)" :key="index">
{{ line }}
</div>
</div>
</template>
</el-table-column>
<el-table-column
prop="bizNo"
:label="$t('pages.salaryRecord.table.bizNo')"
align="center"
min-width="180"
show-overflow-tooltip
/>
<el-table-column
prop="failureReason"
:label="$t('pages.salaryRecord.table.failureReason')"
align="center"
min-width="180"
show-overflow-tooltip
/>
<el-table-column prop="createTime" :label="$t('pages.salaryRecord.table.createTime')" align="center" width="160">
<template slot-scope="scope">
{{ scope.row.createTime | dateFormat }}
</template>
</el-table-column>
<el-table-column prop="updateTime" :label="$t('pages.salaryRecord.table.updateTime')" align="center" width="160">
<template slot-scope="scope">
{{ scope.row.updateTime | dateFormat }}
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="listQuery.cursor"
:limit.sync="listQuery.limit"
@pagination="renderData"
/>
</div>
</template>
<script>
import Pagination from '@/components/Pagination'
import { listSalarySettlementRecords } from '@/api/team'
import { mapGetters } from 'vuex'
export default {
name: 'SalaryRecord',
components: {
Pagination
},
data() {
return {
billMonth: '',
billHalf: '',
list: [],
total: 0,
listLoading: false,
searchLoading: false,
searchDisabled: false,
listQuery: {
cursor: 1,
limit: 20,
sysOrigin: '',
identity: '',
userId: '',
billBelong: '',
status: '',
bizNo: ''
}
}
},
computed: {
...mapGetters(['permissionsSysOriginPlatforms']),
identityOptions() {
return [
{ value: '', label: this.$t('pages.salaryRecord.identity.all') },
{ value: 'BD', label: this.$t('pages.salaryRecord.identity.bd') },
{ value: 'BD_LEADER', label: this.$t('pages.salaryRecord.identity.bdLeader') },
{ value: 'ADMIN', label: this.$t('pages.salaryRecord.identity.admin') }
]
},
billHalfOptions() {
return [
{ value: '01', label: this.$t('pages.salaryRecord.billHalf.first') },
{ value: '16', label: this.$t('pages.salaryRecord.billHalf.second') }
]
},
statusOptions() {
return [
{ value: 'PENDING', label: this.$t('pages.salaryRecord.status.pending') },
{ value: 'SUCCESS', label: this.$t('pages.salaryRecord.status.success') },
{ value: 'FAILED', label: this.$t('pages.salaryRecord.status.failed') }
]
}
},
created() {
const querySystem = this.permissionsSysOriginPlatforms[0]
if (!querySystem) {
return
}
this.listQuery.sysOrigin = querySystem.value
this.renderData(true)
},
methods: {
renderData(isReset) {
if (isReset === true) {
this.listQuery.cursor = 1
this.list = []
}
this.listLoading = true
this.searchDisabled = true
listSalarySettlementRecords(this.buildQuery()).then(res => {
const { body } = res
this.total = body.total || 0
this.list = body.records || []
this.listLoading = false
this.searchLoading = false
this.searchDisabled = false
}).catch(() => {
this.listLoading = false
this.searchLoading = false
this.searchDisabled = false
})
},
handleSearch() {
this.searchLoading = true
this.renderData(true)
},
handleBillChange() {
// yyyyMMdd
if (!this.billMonth && !this.billHalf) {
this.listQuery.billBelong = ''
this.handleSearch()
return
}
if (this.billMonth && this.billHalf) {
this.listQuery.billBelong = Number(`${this.billMonth}${this.billHalf}`)
this.handleSearch()
return
}
this.listQuery.billBelong = ''
},
buildQuery() {
const params = { ...this.listQuery }
Object.keys(params).forEach(key => {
if (params[key] === '' || params[key] === null || params[key] === undefined) {
delete params[key]
}
})
return params
},
identityLabel(identity) {
const option = this.identityOptions.find(item => item.value === identity)
return option ? option.label : identity || '-'
},
identityTagType(identity) {
if (identity === 'BD_LEADER') {
return 'warning'
}
if (identity === 'ADMIN') {
return 'danger'
}
return 'success'
},
statusLabel(status) {
const option = this.statusOptions.find(item => item.value === status)
return option ? option.label : status || '-'
},
statusTagType(status) {
if (status === 'SUCCESS') {
return 'success'
}
if (status === 'FAILED') {
return 'danger'
}
return 'warning'
},
amountText(value) {
return value === null || value === undefined || value === '' ? '-' : value
},
formatLine(labelKey, value) {
return `${this.$t(`pages.salaryRecord.detail.${labelKey}`)}: ${this.amountText(value)}`
},
roleDetailLines(row) {
if (row.identity === 'BD') {
return [
this.formatLine('agencyNumber', row.agencyNumber),
this.formatLine('teamSalaryAmount', row.teamSalaryAmount),
this.formatLine('teamRechargeAmount', row.teamRechargeAmount),
this.formatLine('hitPolicyType', row.hitPolicyType),
this.formatLine('commissionRate', row.commissionRate)
]
}
if (row.identity === 'BD_LEADER') {
return [
this.formatLine('bdNumber', row.bdNumber),
this.formatLine('teamSalaryAmount', row.teamSalaryAmount),
this.formatLine('basicSalary', row.basicSalary),
this.formatLine('rewardAmount', row.rewardAmount),
this.formatLine('newRechargeAgentCount', row.newRechargeAgentCount)
]
}
if (row.identity === 'ADMIN') {
return [
this.formatLine('rechargeAgentSalary', row.rechargeAgentSalary),
this.formatLine('bdLeaderSalary', row.bdLeaderSalary),
this.formatLine('bdPolicyFinalSalary', row.bdPolicyFinalSalary),
this.formatLine('bdPolicyHitType', row.bdPolicyHitType),
this.formatLine('totalSalary', row.totalSalary)
]
}
return ['-']
}
}
}
</script>
<style scoped lang="scss">
.filter-wide {
width: 220px;
}
.muted-text {
color: #909399;
font-size: 12px;
line-height: 20px;
}
.detail-lines {
line-height: 22px;
color: #606266;
}
</style>