| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- <template>
- <div v-loading="pageLoading" class="app-container" :element-loading-text="loadingText">
- <!-- 顶部分类标签栏 -->
- <div class="category-tabs">
- <div class="category-tabs__nav">
- <el-button
- v-for="tab in categoryTabs"
- :key="tab.key"
- :type="activeCategory === tab.key ? 'primary' : 'default'"
- :icon="tab.icon"
- @click="handleCategoryChange(tab.key)"
- >
- {{ tab.label }}
- </el-button>
- </div>
- </div>
- <!-- 搜索栏和场景选择 -->
- <div class="search-bar">
- <div class="search-bar__scene">
- <span class="search-bar__label">场景:</span>
- <div class="scene-card-group">
- <el-tag
- v-for="scene in currentScenes"
- :key="scene.value"
- :class="{ 'scene-card--active': sceneValue === scene.value }"
- class="scene-card"
- @click="handleSceneChange(scene.value)"
- >
- {{ scene.label }}
- </el-tag>
- </div>
- </div>
- <div class="search-bar__right">
- <el-input
- v-model="searchName"
- placeholder="制度名称搜索"
- class="search-bar__input"
- @keyup.enter="handleSearch"
- />
- <el-button type="primary" @click="handleSearch">搜索</el-button>
- <el-button type="success" @click="handleOpenDialog('create')">新增制度</el-button>
- </div>
- </div>
- <PageContent ref="contentRef" :content-config="contentConfig">
- <template #toolbar="{ toolbarRight, onToolbar, removeIds, cols }">
- <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 === 'institution_name')?.show"
- key="institution_name"
- label="制度名称"
- prop="institution_name"
- min-width="150"
- show-overflow-tooltip
- />
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'valid_period')?.show"
- key="valid_period"
- label="制度有效期"
- min-width="150"
- show-overflow-tooltip
- >
- <template #default="scope">
- {{ (scope.row.effective_start_date || '').substring(0, 10) || '-' }}
- ~
- {{ (scope.row.effective_end_date || '').substring(0, 10) || '长期有效' }}
- </template>
- </el-table-column>
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'expense_type')?.show"
- key="expense_type"
- label="费用类型"
- prop="expense_type"
- min-width="100"
- >
- <template #default="scope">
- {{ EXPENSE_TYPE_LABEL[scope.row.expense_type] || scope.row.expense_type }}
- </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 === 'updated_time')?.show"
- key="updated_time"
- label="修改时间"
- prop="updated_time"
- min-width="160"
- sortable
- />
- <el-table-column
- v-if="contentCols.find((col) => col.prop === 'operation')?.show"
- fixed="right"
- label="操作"
- align="center"
- min-width="240"
- >
- <template #default="scope">
- <el-button
- v-hasPerm="['module_payment:expense:institution:detail']"
- type="text"
- size="small"
- @click="handleOpenDialog('detail', scope.row.institution_id)"
- >
- 详情
- </el-button>
- <el-button
- v-hasPerm="['module_payment:expense:institution:modify']"
- type="text"
- size="small"
- @click="handleOpenDialog('update', scope.row.institution_id)"
- >
- 编辑
- </el-button>
- <el-button
- v-hasPerm="['module_payment:expense:institution:scope:modify']"
- type="text"
- size="small"
- @click="handleToggleEffective(scope.row)"
- >
- {{ scope.row.effective === "1" ? "停用" : "启用" }}
- </el-button>
- <el-button
- v-hasPerm="['module_payment:expense:institution:scope:modify']"
- type="text"
- size="small"
- @click="handleOpenScopeDialog(scope.row.institution_id)"
- >
- 成员管理
- </el-button>
- <el-button
- v-hasPerm="['module_payment:expense:institution:delete']"
- type="text"
- size="small"
- @click="handleDelete(scope.row)"
- >
- 删除
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </template>
- </PageContent>
- <EnhancedDialog
- v-model="dialogVisible.visible"
- :title="dialogVisible.title"
- width="1000px"
- @close="handleCloseDialog"
- >
- <template v-if="dialogVisible.type === 'detail'">
- <InstitutionDetail :institution-id="currentInstitutionId" />
- </template>
- <template v-else>
- <InstitutionForm
- ref="formRef"
- :key="formKey"
- :type="dialogVisible.type"
- :institution-id="currentInstitutionId"
- :enterprise-id="currentEnterpriseId"
- @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>
- <ScopeDialog
- v-model="scopeDialogVisible"
- :institution-id="currentInstitutionId"
- :enterprise-id="currentEnterpriseId"
- />
- </div>
- </template>
- <script setup lang="ts">
- defineOptions({
- name: "Institution",
- inheritAttrs: false,
- });
- import InstitutionAPI, {
- InstitutionPageQuery,
- STATUS_TAG_TYPE,
- STATUS_LABEL,
- EXPENSE_TYPE_LABEL,
- } from "@/api/module_payment/institution";
- import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
- import PageContent from "@/components/CURD/PageContent.vue";
- import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
- import InstitutionForm from "./components/InstitutionForm.vue";
- import InstitutionDetail from "./components/InstitutionDetail.vue";
- import ScopeDialog from "./components/ScopeDialog.vue";
- import type { IContentConfig } from "@/components/CURD/types";
- import { useCrudList } from "@/components/CURD/useCrudList";
- import { useLoadingAction } from "@/composables/useLoadingAction";
- import { useRoute } from "vue-router";
- import { ElMessage } from "element-plus";
- import { ref, reactive, computed } from "vue";
- import { useEnterpriseStore } from "@/store/modules/enterprise.store";
- const route = useRoute();
- const enterpriseStore = useEnterpriseStore();
- // 分类标签与对应场景映射
- const categoryTabs = [
- // { key: "meal", label: "餐饮", icon: "UtensilsCrossed", scenes: [{ label: "差旅餐饮", value: "business_meal" }, { label: "员工餐补", value: "staff_meal" }, { label: "团建聚餐", value: "team_dinner" }] },
- // { key: "hotel", label: "酒店", icon: "Building2", scenes: [{ label: "商务出差", value: "business_trip" }, { label: "会议住宿", value: "meeting_hotel" }, { label: "培训住宿", value: "training_hotel" }] },
- // { key: "flight", label: "机票", icon: "Plane", scenes: [{ label: "国内出差", value: "domestic_flight" }, { label: "国际出差", value: "international_flight" }, { label: "紧急出差", value: "urgent_flight" }] },
- // { key: "train", label: "火车票", icon: "Train", scenes: [{ label: "省内出差", value: "provincial_train" }, { label: "跨省出差", value: "interprovincial_train" }, { label: "通勤", value: "commute_train" }] },
- // { key: "bus", label: "公交", icon: "Bus", scenes: [{ label: "市内通勤", value: "city_bus" }, { label: "郊区出行", value: "suburb_bus" }] },
- // { key: "subway", label: "地铁", icon: "Metro", scenes: [{ label: "日常通勤", value: "daily_subway" }, { label: "加班补贴", value: "overtime_subway" }] },
- // { key: "car", label: "用车", icon: "Car", scenes: [{ label: "公务用车", value: "official_car" }, { label: "网约车", value: "ride_hailing" }, { label: "自驾补贴", value: "self_drive" }] },
- // { key: "service", label: "服务", icon: "Headphones", scenes: [{ label: "咨询服务", value: "consult_service" }, { label: "外包服务", value: "outsourcing" }] },
- // { key: "shopping", label: "商城", icon: "ShoppingCart", scenes: [{ label: "办公用品", value: "office_supplies" }, { label: "劳保用品", value: "labor_protection" }, { label: "员工福利", value: "employee_welfare" }] },
- // { key: "express", label: "快递", icon: "Truck", scenes: [{ label: "日常快递", value: "daily_express" }, { label: "大件物流", value: "bulk_logistics" }] },
- // { key: "gas", label: "加油", icon: "Fuel", scenes: [{ label: "公务车加油", value: "official_gas" }, { label: "私家车补贴", value: "private_gas" }] },
- // { key: "medical", label: "医疗", icon: "Stethoscope", scenes: [{ label: "体检", value: "medical_checkup" }, { label: "门诊报销", value: "outpatient" }, { label: "住院报销", value: "hospitalization" }] },
- // { key: "advertising", label: "电商广告充值", icon: "Monitor", scenes: [{ label: "平台推广", value: "platform_promo" }, { label: "品牌广告", value: "brand_ad" }, { label: "促销活动", value: "promotion" }] },
- { key: "DEFAULT", label: "默认", icon: "Box", scenes: [{ label: "通用", value: "common" }] },
- ];
- // 当前激活的分类
- const activeCategory = ref("DEFAULT");
- // 当前分类对应的场景列表
- const currentScenes = computed(() => {
- const category = categoryTabs.find(tab => tab.key === activeCategory.value);
- return category?.scenes || [{ label: "通用", value: "common" }];
- });
- // 场景值 - 默认选中第一个
- const sceneValue = ref(currentScenes.value[0]?.value || "");
- // 搜索名称
- const searchName = ref("");
- const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
- const formRef = ref();
- const { pageLoading, loadingText, execute: loadingExecute } = useLoadingAction();
- const currentEnterpriseId = computed(() => enterpriseStore.getCurrentEnterprise?.enterprise_id);
- const contentCols = reactive<
- Array<{
- prop?: string;
- label?: string;
- show?: boolean;
- }>
- >([
- { prop: "selection", label: "选择框", show: false },
- { prop: "index", label: "序号", show: true },
- { prop: "institution_name", label: "制度名称", show: true },
- { prop: "valid_period", label: "制度有效期", show: true },
- { prop: "expense_type", label: "费用类型", show: true },
- { prop: "status", label: "制度状态", show: true },
- { prop: "updated_time", label: "修改时间", show: true },
- { prop: "operation", label: "操作", show: true },
- ]);
- const contentConfig = reactive<IContentConfig<InstitutionPageQuery>>({
- permPrefix: "module_payment:institution",
- pk: "institution_id",
- cols: contentCols as IContentConfig["cols"],
- hideColumnFilter: false,
- toolbar: [],
- defaultToolbar: ["refresh", "filter"],
- pagination: true,
- initialParams: computed(() => ({
- enterprise_id: currentEnterpriseId.value,
- })) as Record<string, unknown>,
- indexAction: async (params) => {
- const query: InstitutionPageQuery = {
- page_no: params.page_no,
- page_size: params.page_size,
- };
- if (params.enterprise_id) query.enterprise_id = params.enterprise_id;
- const res = await InstitutionAPI.listInstitution(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 currentInstitutionId = ref<string>();
- const formKey = ref(0);
- const scopeDialogVisible = ref(false);
- function handleOpenDialog(type: "create" | "update" | "detail", institutionId?: string) {
- dialogVisible.type = type;
- currentInstitutionId.value = institutionId;
- if (type === "create") {
- dialogVisible.title = "创建费控制度";
- } else if (type === "update") {
- dialogVisible.title = "编辑费控制度";
- } else {
- dialogVisible.title = "费控制度详情";
- }
- if (type === "update") formKey.value++;
- dialogVisible.visible = true;
- }
- function handleOpenScopeDialog(institutionId?: string) {
- if (!institutionId) {
- ElMessage.warning("制度ID不存在");
- return;
- }
- currentInstitutionId.value = institutionId;
- scopeDialogVisible.value = true;
- }
- async function handleCloseDialog() {
- dialogVisible.visible = false;
- }
- function handleSubmit() {
- formRef.value?.submitForm();
- }
- function handleFormSuccess() {
- dialogVisible.visible = false;
- refreshList();
- }
- async function handleDelete(row: any) {
- const institutionId = row.institution_id;
- const enterpriseId = row.enterprise_id || currentEnterpriseId.value;
- if (!institutionId) return;
- if (!enterpriseId) {
- ElMessage.warning("企业ID不存在,无法删除");
- return;
- }
- await loadingExecute({
- confirmMessage: "确认删除该费控制度?",
- confirmTitle: "警告",
- confirmType: "warning",
- loadingText: "正在删除...",
- action: () => InstitutionAPI.deleteInstitution(institutionId, enterpriseId),
- onSuccess: () => {
- ElMessage.success("删除成功");
- refreshList();
- },
- });
- }
- async function handleToggleEffective(row: any) {
- const institutionId = row.institution_id;
- const enterpriseId = row.enterprise_id || currentEnterpriseId.value;
- if (!institutionId || !enterpriseId) {
- ElMessage.warning("制度ID或企业ID不存在");
- return;
- }
- const newEffective = row.effective === "1" ? "0" : "1";
- const actionText = newEffective === "1" ? "启用" : "停用";
- await loadingExecute({
- confirmMessage: `确认${actionText}该制度?`,
- confirmTitle: "提示",
- confirmType: "warning",
- loadingText: `正在${actionText}...`,
- action: () => InstitutionAPI.modifyEffective(institutionId, enterpriseId, newEffective),
- onSuccess: () => {
- ElMessage.success(`${actionText}成功`);
- refreshList();
- },
- });
- }
- function handleCategoryChange(categoryKey: string) {
- activeCategory.value = categoryKey;
- // 切换分类后,重置场景为当前分类的第一个场景
- const category = categoryTabs.find(tab => tab.key === categoryKey);
- sceneValue.value = category?.scenes?.[0]?.value || "";
- refreshList();
- }
- function handleSceneChange(sceneValueStr: string) {
- sceneValue.value = sceneValueStr;
- refreshList();
- }
- function handleSearch() {
- refreshList();
- }
- </script>
- <style scoped>
- .category-tabs {
- margin-bottom: 16px;
- padding: 16px;
- background: #fff;
- border-radius: 8px;
- }
- .category-tabs__nav {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- }
- .search-bar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- padding: 16px;
- background: #fff;
- border-radius: 8px;
- }
- .search-bar__scene {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .search-bar__label {
- font-weight: 500;
- }
- .search-bar__right {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .search-bar__input {
- width: 200px;
- }
- .scene-card-group {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- }
- .scene-card {
- padding: 6px 16px;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.3s ease;
- border: 1px solid #d9d9d9;
- background: #fff;
- color: #666;
- }
- .scene-card:hover {
- border-color: #409eff;
- color: #409eff;
- }
- .scene-card--active {
- background: #409eff;
- border-color: #409eff;
- color: #fff;
- }
- .scene-card--active:hover {
- background: #66b1ff;
- border-color: #66b1ff;
- color: #fff;
- }
- </style>
|