From 710fe5406dac368a4e5d8a8aa2595244b1969613 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 21 May 2026 13:44:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E8=B5=84=E5=8F=91=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/operate/manual-salary-payment.vue | 318 +++++++++++++++--- 1 file changed, 269 insertions(+), 49 deletions(-) diff --git a/apps/src/views/operate/manual-salary-payment.vue b/apps/src/views/operate/manual-salary-payment.vue index 281375f..c17d16b 100644 --- a/apps/src/views/operate/manual-salary-payment.vue +++ b/apps/src/views/operate/manual-salary-payment.vue @@ -15,13 +15,13 @@ import { pageManualSalary, } from '#/api/legacy/operate'; import AccountInput from '#/components/account-input.vue'; +import AdminConfigTable from '#/views/_shared/admin-config-table.vue'; import { getAllowedSysOrigins } from '#/views/system/shared'; import { Button, Card, Modal, - Pagination, Select, Space, Table, @@ -33,6 +33,12 @@ import UserProfileLink from './components/user-profile-link.vue'; defineOptions({ name: 'OperateManualSalaryPayment' }); +type SalarySortKey = + | 'currentSalaryBalance' + | 'payableSalary' + | 'totalIssuedSalary'; +type SalarySortOrder = '' | 'ascend' | 'descend'; + const accessStore = useAccessStore(); const sysOriginOptions = computed(() => { const options = getAllowedSysOrigins(accessStore.accessCodes || []); @@ -41,6 +47,7 @@ const sysOriginOptions = computed(() => { const loading = ref(false); const paying = ref(false); +const settlingUserId = ref(''); const total = ref(0); const list = ref>>([]); const countries = ref>>([]); @@ -48,11 +55,15 @@ const countryKeyword = ref(''); const resultOpen = ref(false); const resultRows = ref>>([]); const selectedUserIds = ref([]); +const salarySortKey = ref<'' | SalarySortKey>(''); +const salarySortOrder = ref(''); const query = reactive({ countryCode: '', cursor: 1, limit: 20, + sortField: '', + sortOrder: '' as SalarySortOrder, sysOrigin: '', userId: '', }); @@ -63,9 +74,11 @@ const columns = [ { dataIndex: 'lastSettlementTarget', key: 'lastSettlementTarget', title: '上次结算的积分', width: 150 }, { dataIndex: 'currentTarget', key: 'currentTarget', title: '本次积分', width: 130 }, { dataIndex: 'policyLevel', key: 'policyLevel', title: '本次积分档位', width: 130 }, - { dataIndex: 'payableSalary', key: 'payableSalary', title: '应发工资', width: 120 }, - { dataIndex: 'totalIssuedSalary', key: 'totalIssuedSalary', title: '总发放工资', width: 130 }, - { dataIndex: 'currentSalaryBalance', key: 'currentSalaryBalance', title: '当前工资余额', width: 140 }, + { dataIndex: 'expectedSalary', key: 'expectedSalary', title: '本月应发', width: 120 }, + { dataIndex: 'payableSalary', key: 'payableSalary', title: '应发(应发-实收)', width: 160 }, + { dataIndex: 'totalIssuedSalary', key: 'totalIssuedSalary', title: '总发工资', width: 130 }, + { dataIndex: 'currentSalaryBalance', key: 'currentSalaryBalance', title: '当前工资', width: 140 }, + { dataIndex: 'action', fixed: 'right', key: 'action', title: '操作', width: 110 }, ]; const resultColumns = [ @@ -77,6 +90,28 @@ const resultColumns = [ { dataIndex: 'currentTarget', key: 'currentTarget', title: '本次积分', width: 120 }, ]; +const salarySortableKeys = new Set([ + 'payableSalary', + 'totalIssuedSalary', + 'currentSalaryBalance', +]); + +const hasMore = computed(() => list.value.length < total.value); + +const tableRows = computed(() => + list.value.map((item) => ({ + ...item, + rowKey: getRowKey(item), + })), +); + +const summaryText = computed(() => { + if (total.value <= 0) { + return list.value.length > 0 ? `已加载 ${list.value.length} 条` : ''; + } + return `已加载 ${list.value.length}/${total.value} 条`; +}); + const selectedUserCount = computed(() => selectedUserIds.value.length); const confirmButtonText = computed(() => @@ -137,7 +172,7 @@ async function loadCountries() { countries.value = (await getCountryAlls()) || []; } -async function loadData(reset = false) { +async function loadData(reset = false, append = false) { if (!query.sysOrigin) { return; } @@ -147,22 +182,34 @@ async function loadData(reset = false) { } if (reset) { query.cursor = 1; + list.value = []; + total.value = 0; selectedUserIds.value = []; } loading.value = true; try { const result = await pageManualSalary({ ...query }); - list.value = result.records || []; + const records = result.records || []; + list.value = append ? [...list.value, ...records] : records; total.value = Number(result.total || 0); + sortLoadedRows(); } finally { loading.value = false; } } -function handlePageChange(page: number, pageSize: number) { - query.cursor = page; - query.limit = pageSize; - void loadData(); +async function loadNextPage() { + if (loading.value || !hasMore.value) { + return; + } + const previousCursor = query.cursor; + query.cursor = previousCursor + 1; + try { + await loadData(false, true); + } catch (error) { + query.cursor = previousCursor; + throw error; + } } function formatMoney(value?: number | string) { @@ -192,6 +239,74 @@ function getRowKey(record: Record) { return String(record.userId || record.id || ''); } +function getSalarySortValue( + record: Record, + key: SalarySortKey, +) { + const value = Number(record?.[key] || 0); + return Number.isFinite(value) ? value : 0; +} + +function isSalarySortableColumn(key?: number | string) { + return salarySortableKeys.has(String(key || '') as SalarySortKey); +} + +function salarySortIndicator(key?: number | string) { + if (salarySortKey.value !== String(key || '') || !salarySortOrder.value) { + return '排序'; + } + return salarySortOrder.value === 'descend' ? '降序' : '升序'; +} + +function sortLoadedRows() { + if (!salarySortKey.value || !salarySortOrder.value) { + return; + } + const sortKey = salarySortKey.value; + const direction = salarySortOrder.value === 'ascend' ? 1 : -1; + list.value = [...list.value].sort((left, right) => { + const diff = + getSalarySortValue(left, sortKey) - getSalarySortValue(right, sortKey); + if (diff !== 0) { + return diff * direction; + } + return getRowKey(left).localeCompare(getRowKey(right)); + }); +} + +function toggleSalarySort(key?: number | string) { + if (!isSalarySortableColumn(key)) { + return; + } + const nextKey = String(key) as SalarySortKey; + if (salarySortKey.value !== nextKey) { + salarySortKey.value = nextKey; + salarySortOrder.value = 'descend'; + } else if (salarySortOrder.value === 'descend') { + salarySortOrder.value = 'ascend'; + } else if (salarySortOrder.value === 'ascend') { + salarySortKey.value = ''; + salarySortOrder.value = ''; + } else { + salarySortOrder.value = 'descend'; + } + query.sortField = salarySortKey.value; + query.sortOrder = salarySortOrder.value; + void loadData(true); +} + +async function submitManualSalary(userIds?: string[]) { + const hasSelectedUsers = !!userIds?.length; + resultRows.value = await confirmManualSalary({ + countryCode: query.countryCode, + sysOrigin: query.sysOrigin, + ...(hasSelectedUsers ? { userIds } : {}), + }); + resultOpen.value = true; + selectedUserIds.value = []; + await loadData(true); +} + function confirmPay() { if (!query.countryCode) { message.warning('请选择国家'); @@ -211,15 +326,8 @@ function confirmPay() { async onOk() { paying.value = true; try { - resultRows.value = await confirmManualSalary({ - countryCode: query.countryCode, - sysOrigin: query.sysOrigin, - ...(hasSelectedUsers ? { userIds } : {}), - }); - resultOpen.value = true; - selectedUserIds.value = []; + await submitManualSalary(hasSelectedUsers ? userIds : undefined); message.success('发放成功'); - await loadData(); } finally { paying.value = false; } @@ -227,13 +335,45 @@ function confirmPay() { }); } +function settleUser(record: Record) { + if (!query.countryCode) { + message.warning('请选择国家'); + return; + } + const userId = String(record.userId || ''); + if (!userId) { + message.warning('用户ID为空'); + return; + } + const payableSalary = Number(record.payableSalary || 0); + Modal.confirm({ + content: payableSalary < 0 + ? '该用户应发小于实收,本次发放0并置为已结算,不会对用户扣款。' + : '本次只结算当前这一行用户的工资。', + title: `确认结算用户 ${record.userProfile?.account || userId} 的当前账期工资?`, + async onOk() { + settlingUserId.value = userId; + try { + await submitManualSalary([userId]); + message.success('结算成功'); + } finally { + settlingUserId.value = ''; + } + }, + }); +} + void loadCountries();