|
@@ -0,0 +1,264 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="table-box position-management">
|
|
|
|
|
+ <div class="filter-bar">
|
|
|
|
|
+ <div class="filter-row">
|
|
|
|
|
+ <span class="filter-label">职位</span>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="filterPositionName"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ placeholder="请输入"
|
|
|
|
|
+ style="width: 220px"
|
|
|
|
|
+ maxlength="50"
|
|
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-button type="primary" @click="handleSearch"> 搜索 </el-button>
|
|
|
|
|
+ <el-button @click="handleReset"> 重置 </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <ProTable
|
|
|
|
|
+ ref="proTable"
|
|
|
|
|
+ :columns="columns"
|
|
|
|
|
+ :request-api="getTableList"
|
|
|
|
|
+ :init-param="initParam"
|
|
|
|
|
+ :data-callback="dataCallback"
|
|
|
|
|
+ :tool-button="false"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #tableHeader>
|
|
|
|
|
+ <div class="table-header">
|
|
|
|
|
+ <el-button type="primary" @click="openCreate"> 新增职位 </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template #operation="scope">
|
|
|
|
|
+ <el-button link type="primary" @click="openEdit(scope.row)"> 编辑 </el-button>
|
|
|
|
|
+ <el-button link type="danger" @click="onDeleteClick(scope.row)"> 删除 </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ProTable>
|
|
|
|
|
+
|
|
|
|
|
+ <p v-if="deleteHint" class="delete-hint">
|
|
|
|
|
+ {{ deleteHint }}
|
|
|
|
|
+ </p>
|
|
|
|
|
+
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="dialogVisible"
|
|
|
|
|
+ :title="editId == null ? '新增职位' : '编辑职位'"
|
|
|
|
|
+ width="420px"
|
|
|
|
|
+ append-to-body
|
|
|
|
|
+ destroy-on-close
|
|
|
|
|
+ @closed="onDialogClosed"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form ref="formRef" :model="form" :rules="formRules" label-width="72px" @submit.prevent>
|
|
|
|
|
+ <el-form-item label="职位" prop="positionName">
|
|
|
|
|
+ <el-input v-model="form.positionName" placeholder="请输入" maxlength="50" clearable show-word-limit />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="dialogVisible = false"> 取消 </el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="submitLoading" @click="submitForm"> 确定 </el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts" name="positionManagement">
|
|
|
|
|
+import { ref, reactive } from "vue";
|
|
|
|
|
+import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
|
|
+import type { FormInstance, FormRules } from "element-plus";
|
|
|
|
|
+import ProTable from "@/components/ProTable/index.vue";
|
|
|
|
|
+import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
|
|
|
|
+import { localGet } from "@/utils";
|
|
|
|
|
+import { queryStaffTitle, createTitle, updateTitle, deleteTitle } from "@/api/modules/storeDecoration";
|
|
|
|
|
+
|
|
|
|
|
+const proTable = ref<ProTableInstance>();
|
|
|
|
|
+const formRef = ref<FormInstance>();
|
|
|
|
|
+const filterPositionName = ref("");
|
|
|
|
|
+const listPositionName = ref("");
|
|
|
|
|
+const deleteHint = ref("");
|
|
|
|
|
+const dialogVisible = ref(false);
|
|
|
|
|
+const submitLoading = ref(false);
|
|
|
|
|
+const editId = ref<string | number | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+const initParam = reactive({
|
|
|
|
|
+ storeId: localGet("geeker-user")?.userInfo?.storeId || localGet("createdId")
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const form = reactive({
|
|
|
|
|
+ positionName: ""
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const formRules: FormRules = {
|
|
|
|
|
+ positionName: [{ required: true, message: "请输入职位名称", trigger: "blur" }]
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const columns = reactive<ColumnProps[]>([
|
|
|
|
|
+ { type: "index", label: "序号", width: 80 },
|
|
|
|
|
+ { prop: "positionName", label: "职位", minWidth: 200 },
|
|
|
|
|
+ { prop: "operation", label: "操作", fixed: "right", width: 160 }
|
|
|
|
|
+]);
|
|
|
|
|
+
|
|
|
|
|
+const dataCallback = (data: any) => {
|
|
|
|
|
+ const raw = data?.records ?? data?.list ?? (Array.isArray(data) ? data : []);
|
|
|
|
|
+ const list = (Array.isArray(raw) ? raw : []).map((item: any) => ({
|
|
|
|
|
+ id: item.id ?? item.titleId,
|
|
|
|
|
+ positionName: item.titleName ?? item.positionName ?? item.name ?? item.position ?? "",
|
|
|
|
|
+ staffCount: Number(item.staffCount ?? item.bindStaffCount ?? item.personCount ?? 0)
|
|
|
|
|
+ }));
|
|
|
|
|
+ const total = data?.total ?? data?.totalCount ?? list.length;
|
|
|
|
|
+ return { list, total: Number(total) || 0 };
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const getTableList = async (params: any) => {
|
|
|
|
|
+ const storeId = params.storeId ?? initParam.storeId;
|
|
|
|
|
+ if (!storeId) {
|
|
|
|
|
+ return Promise.resolve({ data: { list: [], total: 0 } });
|
|
|
|
|
+ }
|
|
|
|
|
+ const res: any = await queryStaffTitle({ storeId });
|
|
|
|
|
+ const inner = res?.data;
|
|
|
|
|
+ const raw = Array.isArray(inner) ? inner : (inner?.records ?? inner?.list ?? []);
|
|
|
|
|
+ const mapped = (Array.isArray(raw) ? raw : []).map((item: any) => ({
|
|
|
|
|
+ id: item.id ?? item.titleId,
|
|
|
|
|
+ positionName: item.titleName ?? item.positionName ?? item.name ?? item.position ?? "",
|
|
|
|
|
+ staffCount: Number(item.staffCount ?? item.bindStaffCount ?? item.personCount ?? 0)
|
|
|
|
|
+ }));
|
|
|
|
|
+ const kw = listPositionName.value?.trim().toLowerCase();
|
|
|
|
|
+ const filtered = kw
|
|
|
|
|
+ ? mapped.filter((row: { positionName: string }) => (row.positionName || "").toLowerCase().includes(kw))
|
|
|
|
|
+ : mapped;
|
|
|
|
|
+ const pageNum = params.pageNum ?? 1;
|
|
|
|
|
+ const pageSize = params.pageSize ?? 10;
|
|
|
|
|
+ const total = filtered.length;
|
|
|
|
|
+ const start = (pageNum - 1) * pageSize;
|
|
|
|
|
+ const list = filtered.slice(start, start + pageSize);
|
|
|
|
|
+ return { data: { list, total } };
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleSearch = () => {
|
|
|
|
|
+ listPositionName.value = filterPositionName.value?.trim() ?? "";
|
|
|
|
|
+ proTable.value?.getTableList();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleReset = () => {
|
|
|
|
|
+ filterPositionName.value = "";
|
|
|
|
|
+ listPositionName.value = "";
|
|
|
|
|
+ proTable.value?.getTableList();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const clearDeleteHint = () => {
|
|
|
|
|
+ deleteHint.value = "";
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const onDeleteBlocked = () => {
|
|
|
|
|
+ deleteHint.value = "此职位下有所属人员,不可删除";
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const onDeleteClick = (row: { id: string | number; positionName: string; staffCount?: number }) => {
|
|
|
|
|
+ if (Number(row.staffCount) > 0) onDeleteBlocked();
|
|
|
|
|
+ else handleDelete(row);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const openCreate = () => {
|
|
|
|
|
+ clearDeleteHint();
|
|
|
|
|
+ editId.value = null;
|
|
|
|
|
+ form.positionName = "";
|
|
|
|
|
+ dialogVisible.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const openEdit = (row: { id: string | number; positionName: string }) => {
|
|
|
|
|
+ clearDeleteHint();
|
|
|
|
|
+ editId.value = row.id;
|
|
|
|
|
+ form.positionName = row.positionName || "";
|
|
|
|
|
+ dialogVisible.value = true;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const onDialogClosed = () => {
|
|
|
|
|
+ editId.value = null;
|
|
|
|
|
+ form.positionName = "";
|
|
|
|
|
+ formRef.value?.resetFields();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const handleDelete = (row: { id: string | number; positionName: string }) => {
|
|
|
|
|
+ clearDeleteHint();
|
|
|
|
|
+ const storeId = initParam.storeId;
|
|
|
|
|
+ if (!storeId) {
|
|
|
|
|
+ ElMessage.warning("缺少门店信息");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ ElMessageBox.confirm(`确定删除职位「${row.positionName || ""}」吗?`, "提示", {
|
|
|
|
|
+ confirmButtonText: "确定",
|
|
|
|
|
+ cancelButtonText: "取消",
|
|
|
|
|
+ type: "warning"
|
|
|
|
|
+ })
|
|
|
|
|
+ .then(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await deleteTitle({ id: row.id });
|
|
|
|
|
+ ElMessage.success("删除成功");
|
|
|
|
|
+ proTable.value?.getTableList();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ /* 错误提示由请求拦截器统一处理 */
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(() => {});
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const submitForm = async () => {
|
|
|
|
|
+ if (!formRef.value) return;
|
|
|
|
|
+ try {
|
|
|
|
|
+ await formRef.value.validate();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const storeId = initParam.storeId;
|
|
|
|
|
+ if (!storeId) {
|
|
|
|
|
+ ElMessage.warning("缺少门店信息");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ submitLoading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const titleName = form.positionName.trim();
|
|
|
|
|
+ if (editId.value != null) {
|
|
|
|
|
+ await updateTitle({ storeId, id: editId.value, titleName });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await createTitle({ storeId, titleName });
|
|
|
|
|
+ }
|
|
|
|
|
+ ElMessage.success(editId.value == null ? "新增成功" : "保存成功");
|
|
|
|
|
+ dialogVisible.value = false;
|
|
|
|
|
+ proTable.value?.getTableList();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ /* 错误提示由请求拦截器统一处理 */
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.position-management {
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.filter-bar {
|
|
|
|
|
+ padding: 0 4px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.filter-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.filter-label {
|
|
|
|
|
+ min-width: 36px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+}
|
|
|
|
|
+.table-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 12px 0;
|
|
|
|
|
+}
|
|
|
|
|
+.delete-hint {
|
|
|
|
|
+ margin: 8px 4px 0;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: var(--el-color-danger);
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|