|
@@ -34,25 +34,37 @@
|
|
|
<EyeOutlined class="mr-1" />
|
|
<EyeOutlined class="mr-1" />
|
|
|
查看明细
|
|
查看明细
|
|
|
</a-button>
|
|
</a-button>
|
|
|
- <a-button
|
|
|
|
|
|
|
+ <a-popconfirm
|
|
|
v-if="record.status === 'pending'"
|
|
v-if="record.status === 'pending'"
|
|
|
- size="small"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- @click="lockSalary(record.period)"
|
|
|
|
|
- class="hover:shadow-sm transition-shadow"
|
|
|
|
|
|
|
+ title="锁定后该月将无法继续计件和退回记录,确定锁定?"
|
|
|
|
|
+ ok-text="确定锁定"
|
|
|
|
|
+ cancel-text="取消"
|
|
|
|
|
+ @confirm="lockSalary(record.period)"
|
|
|
>
|
|
>
|
|
|
- <LockOutlined class="mr-1" />
|
|
|
|
|
- 锁定
|
|
|
|
|
- </a-button>
|
|
|
|
|
- <a-button
|
|
|
|
|
|
|
+ <a-button
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ class="hover:shadow-sm transition-shadow"
|
|
|
|
|
+ >
|
|
|
|
|
+ <LockOutlined class="mr-1" />
|
|
|
|
|
+ 锁定
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </a-popconfirm>
|
|
|
|
|
+ <a-popconfirm
|
|
|
v-else
|
|
v-else
|
|
|
- size="small"
|
|
|
|
|
- @click="unlockSalary(record.period)"
|
|
|
|
|
- class="hover:shadow-sm transition-shadow"
|
|
|
|
|
|
|
+ title="解锁后该月可继续计件和修改记录,确定解锁?"
|
|
|
|
|
+ ok-text="确定解锁"
|
|
|
|
|
+ cancel-text="取消"
|
|
|
|
|
+ @confirm="unlockSalary(record.period)"
|
|
|
>
|
|
>
|
|
|
- <UnlockOutlined class="mr-1" />
|
|
|
|
|
- 解锁
|
|
|
|
|
- </a-button>
|
|
|
|
|
|
|
+ <a-button
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ class="hover:shadow-sm transition-shadow"
|
|
|
|
|
+ >
|
|
|
|
|
+ <UnlockOutlined class="mr-1" />
|
|
|
|
|
+ 解锁
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </a-popconfirm>
|
|
|
<a-popconfirm
|
|
<a-popconfirm
|
|
|
title="确定删除此工资周期吗?"
|
|
title="确定删除此工资周期吗?"
|
|
|
@confirm="deleteSalary(record.period)"
|
|
@confirm="deleteSalary(record.period)"
|
|
@@ -91,23 +103,30 @@
|
|
|
<EyeOutlined class="mr-1" />
|
|
<EyeOutlined class="mr-1" />
|
|
|
明细
|
|
明细
|
|
|
</a-button>
|
|
</a-button>
|
|
|
- <a-button
|
|
|
|
|
|
|
+ <a-popconfirm
|
|
|
v-if="item.status === 'pending'"
|
|
v-if="item.status === 'pending'"
|
|
|
- size="small"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- @click="lockSalary(item.period)"
|
|
|
|
|
|
|
+ title="锁定后该月将无法继续计件和退回记录,确定锁定?"
|
|
|
|
|
+ ok-text="确定锁定"
|
|
|
|
|
+ cancel-text="取消"
|
|
|
|
|
+ @confirm="lockSalary(item.period)"
|
|
|
>
|
|
>
|
|
|
- <LockOutlined class="mr-1" />
|
|
|
|
|
- 锁定
|
|
|
|
|
- </a-button>
|
|
|
|
|
- <a-button
|
|
|
|
|
|
|
+ <a-button size="small" type="primary">
|
|
|
|
|
+ <LockOutlined class="mr-1" />
|
|
|
|
|
+ 锁定
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </a-popconfirm>
|
|
|
|
|
+ <a-popconfirm
|
|
|
v-else
|
|
v-else
|
|
|
- size="small"
|
|
|
|
|
- @click="unlockSalary(item.period)"
|
|
|
|
|
|
|
+ title="解锁后该月可继续计件和修改记录,确定解锁?"
|
|
|
|
|
+ ok-text="确定解锁"
|
|
|
|
|
+ cancel-text="取消"
|
|
|
|
|
+ @confirm="unlockSalary(item.period)"
|
|
|
>
|
|
>
|
|
|
- <UnlockOutlined class="mr-1" />
|
|
|
|
|
- 解锁
|
|
|
|
|
- </a-button>
|
|
|
|
|
|
|
+ <a-button size="small">
|
|
|
|
|
+ <UnlockOutlined class="mr-1" />
|
|
|
|
|
+ 解锁
|
|
|
|
|
+ </a-button>
|
|
|
|
|
+ </a-popconfirm>
|
|
|
<a-popconfirm title="确定删除此工资周期吗?" @confirm="deleteSalary(item.period)">
|
|
<a-popconfirm title="确定删除此工资周期吗?" @confirm="deleteSalary(item.period)">
|
|
|
<a-button size="small" danger>
|
|
<a-button size="small" danger>
|
|
|
<DeleteOutlined class="mr-1" />
|
|
<DeleteOutlined class="mr-1" />
|
|
@@ -134,6 +153,7 @@
|
|
|
v-model:value="generatePeriod"
|
|
v-model:value="generatePeriod"
|
|
|
placeholder="请选择工资周期"
|
|
placeholder="请选择工资周期"
|
|
|
style="width: 100%"
|
|
style="width: 100%"
|
|
|
|
|
+ :disabled-date="disabledFutureMonth"
|
|
|
/>
|
|
/>
|
|
|
</a-form-item>
|
|
</a-form-item>
|
|
|
</a-form>
|
|
</a-form>
|
|
@@ -147,15 +167,22 @@
|
|
|
:footer="null"
|
|
:footer="null"
|
|
|
class="!rounded-xl"
|
|
class="!rounded-xl"
|
|
|
>
|
|
>
|
|
|
|
|
+ <div class="mb-3 text-lg font-bold text-blue-600">
|
|
|
|
|
+ 工资总额: ¥{{ formatMoney(salaryGrandTotal) }}
|
|
|
|
|
+ <a-tag :color="salaryStatus === 'locked' ? 'red' : 'green'" class="ml-3">
|
|
|
|
|
+ {{ salaryStatus === 'locked' ? '已锁定' : '待锁定' }}
|
|
|
|
|
+ </a-tag>
|
|
|
|
|
+ </div>
|
|
|
<a-table
|
|
<a-table
|
|
|
:columns="detailColumns"
|
|
:columns="detailColumns"
|
|
|
:dataSource="salaryDetails"
|
|
:dataSource="salaryDetails"
|
|
|
:loading="detailLoading"
|
|
:loading="detailLoading"
|
|
|
rowKey="user_id"
|
|
rowKey="user_id"
|
|
|
- :pagination="false"
|
|
|
|
|
|
|
+ :pagination="detailPagination"
|
|
|
|
|
+ @change="handleDetailPageChange"
|
|
|
>
|
|
>
|
|
|
<template #amount="{ record }">
|
|
<template #amount="{ record }">
|
|
|
- ¥{{ record.total_amount.toFixed(2) }}
|
|
|
|
|
|
|
+ ¥{{ formatMoney(record.total_amount) }}
|
|
|
</template>
|
|
</template>
|
|
|
</a-table>
|
|
</a-table>
|
|
|
</a-modal>
|
|
</a-modal>
|
|
@@ -163,12 +190,12 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, onMounted, computed } from 'vue';
|
|
|
|
|
|
|
+import { ref, reactive, onMounted, computed } from 'vue';
|
|
|
import { message } from 'ant-design-vue';
|
|
import { message } from 'ant-design-vue';
|
|
|
import { PlusOutlined, ReloadOutlined, EyeOutlined, LockOutlined, UnlockOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
|
import { PlusOutlined, ReloadOutlined, EyeOutlined, LockOutlined, UnlockOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
|
|
import dayjs from 'dayjs';
|
|
import dayjs from 'dayjs';
|
|
|
import { SalaryApi, createApiClient } from '@smartcut/api-client';
|
|
import { SalaryApi, createApiClient } from '@smartcut/api-client';
|
|
|
-import { STORAGE_KEYS } from '@smartcut/shared-utils';
|
|
|
|
|
|
|
+import { STORAGE_KEYS, formatMoney } from '@smartcut/shared-utils';
|
|
|
import { getApiBaseUrl } from '@/apiConfig';
|
|
import { getApiBaseUrl } from '@/apiConfig';
|
|
|
import { useLayoutStore } from '@/stores/layout';
|
|
import { useLayoutStore } from '@/stores/layout';
|
|
|
import type { SalaryPeriod, SalaryDetailItem } from '@smartcut/types';
|
|
import type { SalaryPeriod, SalaryDetailItem } from '@smartcut/types';
|
|
@@ -197,7 +224,7 @@ const columns = [
|
|
|
const detailColumns = [
|
|
const detailColumns = [
|
|
|
{ title: '员工', dataIndex: 'user_name', key: 'user_name', width: 150 },
|
|
{ title: '员工', dataIndex: 'user_name', key: 'user_name', width: 150 },
|
|
|
{ title: '总数量', dataIndex: 'total_quantity', key: 'total_quantity', width: 120 },
|
|
{ title: '总数量', dataIndex: 'total_quantity', key: 'total_quantity', width: 120 },
|
|
|
- { title: '记录数', dataIndex: 'records_count', key: 'records_count', width: 100 },
|
|
|
|
|
|
|
+ { title: '记录数', dataIndex: 'record_count', key: 'record_count', width: 100 },
|
|
|
{ title: '工资金额', key: 'amount', width: 150, slots: { customRender: 'amount' } }
|
|
{ title: '工资金额', key: 'amount', width: 150, slots: { customRender: 'amount' } }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
@@ -209,6 +236,13 @@ const detailModalVisible = ref(false);
|
|
|
const currentPeriod = ref('');
|
|
const currentPeriod = ref('');
|
|
|
const salaryDetails = ref<SalaryDetailItem[]>([]);
|
|
const salaryDetails = ref<SalaryDetailItem[]>([]);
|
|
|
const detailLoading = ref(false);
|
|
const detailLoading = ref(false);
|
|
|
|
|
+const salaryGrandTotal = ref(0);
|
|
|
|
|
+const salaryStatus = ref('');
|
|
|
|
|
+const detailPagination = reactive({
|
|
|
|
|
+ current: 1,
|
|
|
|
|
+ pageSize: 20,
|
|
|
|
|
+ total: 0
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
async function loadSalaryList() {
|
|
async function loadSalaryList() {
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
@@ -226,6 +260,10 @@ function showGenerateModal() {
|
|
|
generatePeriod.value = dayjs();
|
|
generatePeriod.value = dayjs();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function disabledFutureMonth(current: dayjs.Dayjs): boolean {
|
|
|
|
|
+ return current && current > dayjs().endOf('month');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
async function handleGenerate() {
|
|
async function handleGenerate() {
|
|
|
if (!generatePeriod.value) {
|
|
if (!generatePeriod.value) {
|
|
|
message.error('请选择工资周期');
|
|
message.error('请选择工资周期');
|
|
@@ -249,9 +287,21 @@ async function handleGenerate() {
|
|
|
async function showDetailModal(period: string) {
|
|
async function showDetailModal(period: string) {
|
|
|
currentPeriod.value = period;
|
|
currentPeriod.value = period;
|
|
|
detailModalVisible.value = true;
|
|
detailModalVisible.value = true;
|
|
|
|
|
+ detailPagination.current = 1;
|
|
|
|
|
+ await loadSalaryDetail();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadSalaryDetail() {
|
|
|
detailLoading.value = true;
|
|
detailLoading.value = true;
|
|
|
try {
|
|
try {
|
|
|
- salaryDetails.value = await salaryApi.getSalaryDetail(period);
|
|
|
|
|
|
|
+ const res = await salaryApi.getSalaryDetail(currentPeriod.value, {
|
|
|
|
|
+ page: detailPagination.current,
|
|
|
|
|
+ page_size: detailPagination.pageSize
|
|
|
|
|
+ });
|
|
|
|
|
+ salaryDetails.value = res.details;
|
|
|
|
|
+ salaryGrandTotal.value = res.grand_total;
|
|
|
|
|
+ salaryStatus.value = res.status;
|
|
|
|
|
+ detailPagination.total = res.total;
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
message.error('加载工资明细失败');
|
|
message.error('加载工资明细失败');
|
|
|
} finally {
|
|
} finally {
|
|
@@ -259,6 +309,12 @@ async function showDetailModal(period: string) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function handleDetailPageChange(pag: any) {
|
|
|
|
|
+ detailPagination.current = pag.current;
|
|
|
|
|
+ detailPagination.pageSize = pag.pageSize;
|
|
|
|
|
+ loadSalaryDetail();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
async function lockSalary(period: string) {
|
|
async function lockSalary(period: string) {
|
|
|
try {
|
|
try {
|
|
|
await salaryApi.lockSalary(period);
|
|
await salaryApi.lockSalary(period);
|