| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- <template>
- <div v-loading="pageLoading" class="app-container" :element-loading-text="loadingText">
- <div class="category-tabs" style="margin-bottom: 16px; padding: 10px 16px; background: #fff; border-radius: 8px;">
- <el-button
- v-for="tab in categoryTabs"
- :key="tab.key"
- :type="activeCategory === tab.key ? 'primary' : 'default'"
- @click="handleCategoryChange(tab.key)"
- >
- {{ tab.label }}
- </el-button>
- </div>
- <!-- 额度管理列表 -->
- <template v-if="activeCategory === 'quota'">
- <PageSearch
- ref="searchRef"
- :search-config="searchConfig"
- @query-click="handleQueryClick"
- @reset-click="handleResetClick"
- />
- <PageContent ref="contentRef" :content-config="contentConfig">
- <template #toolbar="{ toolbarRight, onToolbar, removeIds, cols }">
- <CrudToolbarLeft
- :remove-ids="removeIds"
- :perm-create="['module_payment:quota:create']"
- @add="handleOpenDialog('create')"
- />
- <div class="data-table__toolbar--right">
- <CrudToolbarRight :buttons="toolbarRight" :cols="cols" :on-toolbar="onToolbar" />
- </div>
- </template>
- <template #table="{ data, loading, tableRef, onSelectionChange }">
- <div class="data-table__content">
- <el-table
- :ref="tableRef as any"
- v-loading="loading"
- :data="data"
- height="100%"
- border
- @selection-change="onSelectionChange"
- >
- <template #empty>
- <el-empty :image-size="80" description="暂无数据" />
- </template>
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'selection')?.show"
- type="selection"
- min-width="55"
- align="center"
- />
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'quota_id')?.show"
- key="quota_id"
- label="额度ID"
- prop="quota_id"
- min-width="150"
- show-overflow-tooltip
- />
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'employee_id')?.show"
- key="employee_id"
- label="员工ID"
- prop="employee_id"
- min-width="150"
- show-overflow-tooltip
- />
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'institution_id')?.show"
- key="institution_id"
- label="制度ID"
- prop="institution_id"
- min-width="150"
- show-overflow-tooltip
- />
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'quota_type')?.show"
- key="quota_type"
- label="额度类型"
- prop="quota_type"
- min-width="100"
- >
- <template #default="scope">
- {{ formatQuotaType(scope.row.quota_type) }}
- </template>
- </el-table-column>
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'total_amount')?.show"
- key="total_amount"
- label="总金额"
- prop="total_amount"
- min-width="100"
- align="right"
- >
- <template #default="scope">
- {{ scope.row.total_amount ? `¥${scope.row.total_amount.toFixed(2)}` : "-" }}
- </template>
- </el-table-column>
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'available_amount')?.show"
- key="available_amount"
- label="可用金额"
- prop="available_amount"
- min-width="100"
- align="right"
- >
- <template #default="scope">
- {{ scope.row.available_amount ? `¥${scope.row.available_amount.toFixed(2)}` : "-" }}
- </template>
- </el-table-column>
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'status')?.show"
- key="status"
- label="状态"
- prop="status"
- min-width="100"
- >
- <template #default="scope">
- <el-tag :type="STATUS_TAG_TYPE[scope.row.status]">
- {{ STATUS_LABEL[scope.row.status] || scope.row.status }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'created_time')?.show"
- key="created_time"
- label="创建时间"
- prop="created_time"
- min-width="160"
- sortable
- />
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'operation')?.show"
- fixed="right"
- label="操作"
- align="center"
- min-width="160"
- >
- <template #default="scope">
- <el-button
- v-hasPerm="['module_payment:quota:detail']"
- type="info"
- size="small"
- link
- icon="View"
- @click="handleOpenDialog('detail', scope.row.quota_id)"
- >
- 详情
- </el-button>
- <el-button
- v-hasPerm="['module_payment:quota:update']"
- type="primary"
- size="small"
- link
- icon="edit"
- @click="handleOpenDialog('update', scope.row.quota_id)"
- >
- 编辑
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </template>
- </PageContent>
- <EnhancedDialog
- v-model="dialogVisible.visible"
- :title="dialogVisible.title"
- @close="handleCloseDialog"
- >
- <template v-if="dialogVisible.type === 'detail'">
- <QuotaDetail :quota-id="currentQuotaId" />
- </template>
- <template v-else>
- <QuotaForm
- ref="formRef"
- :type="dialogVisible.type"
- :quota-id="currentQuotaId"
- :employee-id="employeeIdFromUrl"
- :institution-id="institutionIdFromUrl"
- @success="handleFormSuccess"
- />
- </template>
- <template #footer>
- <div class="dialog-footer">
- <el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
- 确定
- </el-button>
- <el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
- <el-button @click="handleCloseDialog">取消</el-button>
- </div>
- </template>
- </EnhancedDialog>
- </template>
- <!-- 手工发放批次管理 -->
- <template v-if="activeCategory === 'batch'">
- <div class="batch-section">
- <div style="margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center;">
- <h3 style="margin: 0">手工发放批次</h3>
- <el-button type="primary" @click="handleCreateBatch">
- <el-icon><Plus /></el-icon> 新建发放
- </el-button>
- </div>
- <el-table :data="batchList" border max-height="calc(100vh - 300px)">
- <template #empty>
- <el-empty :image-size="80" description="暂无发放批次" />
- </template>
- <el-table-column type="index" label="序号" width="60" />
- <el-table-column prop="issue_batch_id" label="批次ID" min-width="180" show-overflow-tooltip />
- <el-table-column prop="batch_no" label="批次号" min-width="140" />
- <el-table-column prop="issue_name" label="发放名称" min-width="120" />
- <el-table-column prop="institution_id" label="制度ID" min-width="160" show-overflow-tooltip />
- <el-table-column prop="total_count" label="发放人数" width="80" align="center" />
- <el-table-column prop="total_amount" label="总金额" width="120" align="right">
- <template #default="scope">
- {{ scope.row.total_amount ? `¥${Number(scope.row.total_amount).toFixed(2)}` : "-" }}
- </template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="80" align="center">
- <template #default="scope">
- <el-tag :type="ISSUE_BATCH_STATUS_TAG[scope.row.status] || 'info'" size="small">
- {{ ISSUE_BATCH_STATUS_LABEL[scope.row.status] || scope.row.status }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="created_time" label="创建时间" width="160" />
- <el-table-column label="操作" width="180" align="center" fixed="right">
- <template #default="scope">
- <el-button type="primary" size="small" link @click="handleViewBatchRecords(scope.row)">
- 发放明细
- </el-button>
- <el-button
- v-if="scope.row.status === 'ACTIVE'"
- type="danger"
- size="small"
- link
- @click="handleCancelBatch(scope.row)"
- >
- 作废
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </template>
- <!-- 新建批次弹窗 -->
- <el-dialog v-model="batchDialogVisible" title="新建手工发放" width="800px" destroy-on-close @close="handleBatchDialogClose">
- <IssueBatchForm
- ref="batchFormRef"
- :enterprise-id="enterpriseIdFromUrl"
- @success="handleBatchCreateSuccess"
- />
- <template #footer>
- <el-button type="primary" @click="handleBatchSubmit">确认发放</el-button>
- <el-button @click="batchDialogVisible = false">取消</el-button>
- </template>
- </el-dialog>
- <!-- 发放记录弹窗 -->
- <el-dialog v-model="recordsDialogVisible" title="发放明细" width="900px" destroy-on-close>
- <IssueBatchDetail
- :issue-batch-id="currentBatchId"
- :institution-id="currentBatchInstitutionId"
- :batch-info="currentBatchInfo"
- />
- <template #footer>
- <el-button type="primary" @click="recordsDialogVisible = false">关闭</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- defineOptions({
- name: "Quota",
- inheritAttrs: false,
- });
- import { Plus } from "@element-plus/icons-vue";
- import QuotaAPI, {
- QuotaPageQuery,
- QUOTA_TYPE_OPTIONS,
- STATUS_TAG_TYPE,
- STATUS_LABEL,
- ISSUE_BATCH_STATUS_TAG,
- ISSUE_BATCH_STATUS_LABEL,
- } from "@/api/module_payment/quota";
- import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
- import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
- import PageSearch from "@/components/CURD/PageSearch.vue";
- import PageContent from "@/components/CURD/PageContent.vue";
- import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
- import QuotaForm from "./components/QuotaForm.vue";
- import QuotaDetail from "./components/QuotaDetail.vue";
- import IssueBatchForm from "./components/IssueBatchForm.vue";
- import IssueBatchDetail from "./components/IssueBatchDetail.vue";
- import type { ISearchConfig, IContentConfig } from "@/components/CURD/types";
- import { useCrudList } from "@/components/CURD/useCrudList";
- import { useLoadingAction } from "@/composables/useLoadingAction";
- import { useRoute } from "vue-router";
- import { ElMessage, ElMessageBox } from "element-plus";
- import { ref, reactive, computed, onMounted } from "vue";
- import { useEnterpriseStore } from "@/store/modules/enterprise.store";
- const route = useRoute();
- const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } =
- useCrudList();
- const formRef = ref();
- const { pageLoading, loadingText, execute: loadingExecute } = useLoadingAction();
- const employeeIdFromUrl = computed(() => route.query.employee_id as string | undefined);
- const institutionIdFromUrl = computed(() => route.query.institution_id as string | undefined);
- const enterpriseIdFromUrl = computed(() => route.query.enterprise_id as string | undefined);
- const categoryTabs = [
- { key: "quota", label: "额度管理" },
- { key: "batch", label: "手工发放" },
- ];
- const activeCategory = ref("quota");
- function handleCategoryChange(key: string) {
- activeCategory.value = key;
- if (key === "batch") loadBatchList();
- }
- // ===== 额度管理 =====
- const searchConfig = reactive<ISearchConfig>({
- permPrefix: "module_payment:quota",
- colon: true,
- isExpandable: true,
- showNumber: 3,
- form: { labelWidth: "auto" },
- formItems: [
- {
- prop: "employee_id",
- label: "员工ID",
- type: "input",
- attrs: { placeholder: "请输入员工ID", clearable: true },
- },
- {
- prop: "institution_id",
- label: "制度ID",
- type: "input",
- attrs: { placeholder: "请输入制度ID", clearable: true },
- },
- {
- prop: "status",
- label: "状态",
- type: "select",
- options: [
- { label: "全部", value: "" },
- { label: "正常", value: "QUOTA_ACTIVE" },
- { label: "冻结", value: "QUOTA_FROZEN" },
- { label: "已用完", value: "QUOTA_EXHAUSTED" },
- { label: "已过期", value: "QUOTA_EXPIRED" },
- ],
- attrs: { placeholder: "请选择状态", clearable: true, style: { width: "167.5px" } },
- },
- ],
- });
- const contentCols = reactive<
- Array<{
- prop?: string;
- label?: string;
- show?: boolean;
- }>
- >([
- { prop: "selection", label: "选择框", show: false },
- { prop: "index", label: "序号", show: true },
- { prop: "quota_id", label: "额度ID", show: true },
- { prop: "employee_id", label: "员工ID", show: true },
- { prop: "institution_id", label: "制度ID", show: true },
- { prop: "quota_type", label: "额度类型", show: true },
- { prop: "total_amount", label: "总金额", show: true },
- { prop: "available_amount", label: "可用金额", show: true },
- { prop: "status", label: "状态", show: true },
- { prop: "created_time", label: "创建时间", show: true },
- { prop: "operation", label: "操作", show: true },
- ]);
- const contentConfig = reactive<IContentConfig<QuotaPageQuery>>({
- permPrefix: "module_payment:quota",
- pk: "quota_id",
- cols: contentCols as IContentConfig["cols"],
- hideColumnFilter: false,
- toolbar: [],
- defaultToolbar: ["refresh", "filter"],
- pagination: true,
- initialParams: computed(() => ({
- employee_id: employeeIdFromUrl.value,
- institution_id: institutionIdFromUrl.value,
- })) as Record<string, unknown>,
- indexAction: async (params) => {
- const query: QuotaPageQuery = {
- page_no: params.page_no,
- page_size: params.page_size,
- };
- if (params.employee_id) query.employee_id = params.employee_id;
- if (params.institution_id) query.institution_id = params.institution_id;
- if (params.status) query.status = params.status;
- const res = await QuotaAPI.listQuota(query);
- return {
- list: res.data.data?.items || [],
- total: res.data.data?.total || 0,
- };
- },
- });
- const dialogVisible = reactive({
- title: "",
- visible: false,
- type: "create" as "create" | "update" | "detail",
- });
- const currentQuotaId = ref<string>();
- function handleOpenDialog(type: "create" | "update" | "detail", quotaId?: string) {
- dialogVisible.type = type;
- currentQuotaId.value = quotaId;
- if (type === "create") {
- dialogVisible.title = "发放额度";
- } else if (type === "update") {
- dialogVisible.title = "编辑额度";
- } else {
- dialogVisible.title = "额度详情";
- }
- dialogVisible.visible = true;
- }
- async function handleCloseDialog() {
- dialogVisible.visible = false;
- }
- function handleSubmit() {
- formRef.value?.submitForm();
- }
- function handleFormSuccess() {
- dialogVisible.visible = false;
- refreshList();
- }
- async function handleDelete(quotaId?: string) {
- if (!quotaId) return;
- await loadingExecute({
- confirmMessage: "确认删除该额度?",
- confirmTitle: "警告",
- confirmType: "warning",
- loadingText: "正在删除...",
- action: () => QuotaAPI.deleteQuota(quotaId),
- onSuccess: () => {
- ElMessage.success("删除成功");
- refreshList();
- },
- });
- }
- function formatQuotaType(type?: string) {
- if (!type) return "-";
- const option = QUOTA_TYPE_OPTIONS.find((item) => item.value === type);
- return option ? option.label : type;
- }
- // ===== 手工发放批次管理 =====
- const batchFormRef = ref();
- const batchDialogVisible = ref(false);
- const recordsDialogVisible = ref(false);
- const batchList = ref<any[]>([]);
- const currentBatchId = ref("");
- const currentBatchInstitutionId = ref("");
- const currentBatchInfo = ref<any>({});
- async function loadBatchList() {
- try {
- const store = useEnterpriseStore();
- const eid = store.getCurrentEnterprise?.enterprise_id;
- const res = await QuotaAPI.issueBatchList({ page_no: 1, page_size: 200, institution_id: institutionIdFromUrl.value });
- batchList.value = res?.data?.data?.items || [];
- } catch (e) {
- console.error("加载批次列表失败", e);
- }
- }
- function handleCreateBatch() {
- batchFormRef.value?.resetForm();
- batchDialogVisible.value = true;
- }
- async function handleBatchSubmit() {
- const formValid = await batchFormRef.value?.submitForm();
- if (!formValid) return;
- const formData = batchFormRef.value?.getFormData();
- if (!formData) return;
- await loadingExecute({
- loadingText: "正在发放...",
- action: () => QuotaAPI.issueBatchCreate(formData),
- onSuccess: (res: any) => {
- const data = res?.data?.data || res;
- ElMessage.success("发放成功");
- batchDialogVisible.value = false;
- if (data?.issue_quota_check_failed_list?.length > 0) {
- ElMessage.warning(`有 ${data.issue_quota_check_failed_list.length} 条校验失败`);
- }
- loadBatchList();
- },
- });
- }
- function handleBatchDialogClose() {
- batchDialogVisible.value = false;
- }
- function handleViewBatchRecords(row: any) {
- currentBatchId.value = row.issue_batch_id;
- currentBatchInstitutionId.value = row.institution_id;
- currentBatchInfo.value = {
- issue_batch_id: row.issue_batch_id,
- batch_no: row.batch_no,
- issue_name: row.issue_name,
- institution_id: row.institution_id,
- };
- recordsDialogVisible.value = true;
- }
- function handleCancelBatch(row: any) {
- ElMessageBox.confirm(`确认作废发放批次 "${row.issue_name}"?作废后已发放的额度将无法使用。`, "警告", {
- confirmButtonText: "确认作废",
- cancelButtonText: "取消",
- type: "warning",
- }).then(async () => {
- const store = useEnterpriseStore();
- const eid = store.getCurrentEnterprise?.enterprise_id;
- if (!eid) {
- ElMessage.error("企业ID不存在");
- return;
- }
- await loadingExecute({
- loadingText: "正在作废...",
- action: () =>
- QuotaAPI.issueBatchCancel({
- enterprise_id: eid,
- institution_id: row.institution_id,
- issue_batch_id: row.issue_batch_id,
- }),
- onSuccess: () => {
- ElMessage.success("作废成功");
- loadBatchList();
- },
- });
- }).catch(() => {});
- }
- </script>
|