feat(frontend): 绩效台账与月度结算两页接入(新组绩效工资)

- 绩效台账页(order17):月/版本筛选 + 行展开计算快照 + 重算(原因
  对话框,管理员/财务);核算入口在回款管理页(每笔回款行内触发)
- 月结页(order18):生成快照(月份输入)+ 状态筛选 + 提交审批 +
  详情抽屉(按人明细三口径/试用期说明/审批单号关联)
- 均复用 usePagedList/AppPagination/--ys-* 惯例;金额 str 直出
- type-check 零错误;build 通过

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-17 00:54:51 +08:00
1 parent e7e47d88bc
commit a1217f4482
5 files changed
+609

No files matched your search

+175
View File
@@ -0,0 +1,175 @@
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import * as calcApi from '@/api/calc'
import type { LedgerRow } from '@/api/calc'
import AppPagination from '@/components/table/AppPagination.vue'
import { usePagedList } from '@/composables/usePagedList'
import { handledError } from '@/utils/error'
/**
* 绩效核算台账(M3 ④):核算事实的固化点。
*
* - 核算=把回款固化为台账行(每笔回款仅一次;同业务重复回款仅登记进度)
* - 重算=作废旧版本、按当前参数落新版本(管理员/财务授权,原因留痕)
* - 行展开看计算快照(所用参数版本与行级折算明细)
*/
const filters = reactive({
settlement_month: '',
is_active: 1 as number | undefined,
})
const { records, total, pageNum, pageSize, loading, load, search } = usePagedList<
LedgerRow,
{ settlement_month?: string; is_active?: number }
>(calcApi.listLedger, () => ({
settlement_month: filters.settlement_month.trim() || undefined,
is_active: filters.is_active,
}))
function resetFilters(): void {
filters.settlement_month = ''
filters.is_active = 1
search()
}
const recalcVisible = ref(false)
const recalcTarget = ref<LedgerRow | null>(null)
const recalcReason = ref('')
function openRecalc(row: LedgerRow): void {
recalcTarget.value = row
recalcReason.value = ''
recalcVisible.value = true
}
async function submitRecalc(): Promise<void> {
if (!recalcTarget.value) return
if (!recalcReason.value.trim()) {
ElMessage.warning('重算必须填写原因')
return
}
try {
await calcApi.recalcReceipt(recalcTarget.value.id, recalcReason.value.trim())
ElMessage.success('重算完成(新版本已生效)')
recalcVisible.value = false
await load()
} catch (error) {
handledError(error)
}
}
</script>
<template>
<div class="page">
<el-form inline class="filters" @submit.prevent>
<el-form-item label="归属月">
<el-input
v-model="filters.settlement_month"
placeholder="YYYY-MM"
clearable
style="width: 140px"
@keyup.enter="search"
/>
</el-form-item>
<el-form-item label="版本">
<el-select v-model="filters.is_active" style="width: 130px">
<el-option label="仅当前生效" :value="1" />
<el-option label="含历史版本" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="resetFilters">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="records" stripe>
<el-table-column type="expand">
<template #default="{ row }">
<pre class="snapshot">{{ JSON.stringify(row.snapshot_json ?? {}, null, 2) }}</pre>
</template>
</el-table-column>
<el-table-column prop="settlement_month" label="归属月" width="90" />
<el-table-column prop="business_no" label="业务编号" min-width="150" />
<el-table-column prop="personnel_name" label="归属人员" width="100" />
<el-table-column prop="role_code" label="岗位" width="110" />
<el-table-column prop="receipt_date" label="回款日" width="100" />
<el-table-column prop="payment_days" label="天数" width="70" />
<el-table-column prop="rate_text" label="计提比例" min-width="90" />
<el-table-column prop="converted_profit" label="折算毛利" min-width="100" />
<el-table-column prop="performance_amount" label="绩效金额" min-width="100" />
<el-table-column prop="payable_amount" label="应付绩效" min-width="100" />
<el-table-column label="版本" width="80">
<template #default="{ row }">
v{{ row.calc_version }}
<el-tag v-if="row.is_active === 0" size="small" type="info" disable-transitions>
已替代
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="90" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.is_active === 1 && row.calc_type === 1"
link
type="primary"
@click="openRecalc(row)"
>
重算
</el-button>
</template>
</el-table-column>
</el-table>
<AppPagination
v-model:page-num="pageNum"
v-model:page-size="pageSize"
:total="total"
@change="load"
/>
<el-dialog v-model="recalcVisible" title="重算确认" width="460px">
<p class="recalc-warn">
重算会作废当前版本(v{{ recalcTarget?.calc_version }})并按当前参数落新版本,
历史版本可追溯。请填写重算原因。
</p>
<el-input
v-model="recalcReason"
type="textarea"
:rows="2"
maxlength="255"
placeholder="重算原因(必填,留痕)"
/>
<template #footer>
<el-button @click="recalcVisible = false">取消</el-button>
<el-button type="primary" @click="submitRecalc">确认重算</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.filters {
margin-bottom: 4px;
}
.snapshot {
margin: 0;
padding: 10px;
font-size: 12px;
line-height: 1.6;
background: #f4f5f8;
border-radius: 6px;
white-space: pre-wrap;
word-break: break-all;
}
.recalc-warn {
margin: 0 0 10px;
font-size: 12px;
color: var(--ys-ink-3);
}
</style>