feat: 项目初始化,未病局
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<el-dialog :model-value="visible" @update:model-value="handleUpdateVisible" title="添加药材" width="600px" destroy-on-close>
|
||||
<el-form :model="form" label-width="120px" ref="formRef" class="mt-2" :rules="formRules">
|
||||
<!-- 药材下拉 + 未找到按钮 -->
|
||||
<el-form-item v-if="!isAddingNew" label="药材名称" prop="medicinalTypeId" style="display: flex; align-items: center">
|
||||
<el-select v-model="form.medicinalTypeId" placeholder="请选择药材" filterable style="flex: 1; margin-right: 8px">
|
||||
<el-option v-for="item in medicinalList" :key="item.id" :label="item.dictLabel" :value="item.id" />
|
||||
</el-select>
|
||||
<el-button type="text" @click="handleNotFound">未找到</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 新药材输入框 -->
|
||||
<el-form-item label="新药材名称" v-if="isAddingNew" prop="medicinalName">
|
||||
<el-input v-model="form.medicinalName" placeholder="请输入新药材名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="添加量(g)" prop="addWeight">
|
||||
<el-input v-model.number="form.addWeight" type="number" min="0.01" step="0.01" placeholder="请输入添加量" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 改为总价输入 -->
|
||||
<el-form-item label="总价" prop="totalPrice">
|
||||
<el-input v-model.number="form.totalPrice" type="number" min="0" step="0.01" placeholder="请输入总价(元)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确认添加</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from "vue";
|
||||
import { medicinalApi } from "@/api/modules/biz/WeiBingJu/medicinal";
|
||||
import { ElMessage, ElForm, FormRules } from "element-plus";
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false }
|
||||
});
|
||||
const emit = defineEmits(["update:visible", "close", "refreshList"]);
|
||||
|
||||
const medicinalList = ref<any[]>([]);
|
||||
const isAddingNew = ref(false);
|
||||
|
||||
const formRef = ref<InstanceType<typeof ElForm>>();
|
||||
const form = reactive({
|
||||
medicinalTypeId: null as number | null,
|
||||
addWeight: 0,
|
||||
// 前端改为 totalPrice
|
||||
totalPrice: 0,
|
||||
medicinalName: ""
|
||||
});
|
||||
|
||||
// 动态表单校验(注意 totalPrice 字段)
|
||||
const formRules = computed<FormRules>(() => ({
|
||||
medicinalTypeId: [{ required: !isAddingNew.value, message: "请选择药材", trigger: "change" }],
|
||||
medicinalName: [{ required: isAddingNew.value, message: "请输入新药材名称", trigger: "blur" }],
|
||||
addWeight: [
|
||||
{ required: true, message: "请输入添加量", trigger: "blur" },
|
||||
{ type: "number", min: 0.01, message: "添加量不能小于0.01", trigger: "blur" }
|
||||
],
|
||||
totalPrice: [
|
||||
{ required: true, message: "请输入总价", trigger: "blur" },
|
||||
{ type: "number", min: 0, message: "总价不能为负", trigger: "blur" }
|
||||
]
|
||||
}));
|
||||
|
||||
const fetchMedicinalList = async () => {
|
||||
try {
|
||||
const res = await medicinalApi.getDictList();
|
||||
medicinalList.value = res.data || [];
|
||||
} catch (err) {
|
||||
ElMessage.error("加载药材列表失败");
|
||||
medicinalList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async newVal => {
|
||||
if (newVal) {
|
||||
handleResetForm();
|
||||
await fetchMedicinalList();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleUpdateVisible = (newVisible: boolean) => {
|
||||
emit("update:visible", newVisible);
|
||||
if (!newVisible) {
|
||||
handleResetForm();
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
const handleCancel = () => emit("update:visible", false);
|
||||
const handleResetForm = () => {
|
||||
form.medicinalTypeId = null;
|
||||
form.addWeight = 0;
|
||||
form.totalPrice = 0;
|
||||
form.medicinalName = "";
|
||||
isAddingNew.value = false;
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
const handleNotFound = () => {
|
||||
isAddingNew.value = true;
|
||||
form.medicinalTypeId = null;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算单价,并校验 addWeight > 0
|
||||
if (!form.addWeight || form.addWeight <= 0) {
|
||||
ElMessage.warning("添加量需大于 0");
|
||||
return;
|
||||
}
|
||||
|
||||
// unitPrice = totalPrice / addWeight,保留两位
|
||||
const unitPrice = parseFloat((form.totalPrice / form.addWeight).toFixed(2));
|
||||
|
||||
// 构造后端期望的 payload(注意把 totalPrice 改为 unitPrice)
|
||||
const payload: any = {
|
||||
medicinalTypeId: form.medicinalTypeId,
|
||||
addWeight: form.addWeight,
|
||||
unitPrice: unitPrice,
|
||||
medicinalName: form.medicinalName
|
||||
};
|
||||
|
||||
try {
|
||||
await medicinalApi.addMedicinal(payload);
|
||||
ElMessage.success("添加成功");
|
||||
emit("update:visible", false);
|
||||
emit("refreshList");
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err.message || "添加失败");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="props.visible"
|
||||
:title="`${props.medicinalName} - 添加记录`"
|
||||
width="1200px"
|
||||
@close="handleDialogClose"
|
||||
@update:model-value="handleUpdateVisible"
|
||||
append-to-body
|
||||
>
|
||||
<ProTable ref="proTableRef" title="添加记录" :columns="columns" :request-api="getAdditionRecordsApi" :row-key="rowKey" :show-selection="false">
|
||||
<!-- 行内列 slot:addWeight -->
|
||||
<template #addWeight="{ row }">
|
||||
<template v-if="editingId === row.id">
|
||||
<el-input-number v-model.number="editModel.addWeight" :min="0.01" :step="0.01" controls-position="right" size="small" />
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ row.addWeight }}
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- unitPrice 列:只展示单价,不编辑 -->
|
||||
<template #unitPrice="{ row }">
|
||||
{{ (row.unitPrice ?? 0).toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<!-- totalPrice 列:查看时显示合计,编辑时可以修改 -->
|
||||
<template #totalPrice="{ row }">
|
||||
<template v-if="editingId === row.id">
|
||||
<el-input-number
|
||||
v-model.number="editModel.totalPrice"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
controls-position="right"
|
||||
size="small"
|
||||
placeholder="输入总价(元)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ ((row.addWeight ?? 0) * (row.unitPrice ?? 0)).toFixed(2) }}
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- addTime 格式化 -->
|
||||
<template #addTime="{ row }">
|
||||
{{ row.addTime ? formatDate(row.addTime) : "-" }}
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #operation="{ row }">
|
||||
<template v-if="editingId === row.id">
|
||||
<el-button size="small" type="primary" @click="saveRowEdit(row)" :loading="saving"> 保存 </el-button>
|
||||
<el-button size="small" @click="cancelRowEdit">取消</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="text" size="small" @click="startRowEdit(row)"> 编辑 </el-button>
|
||||
<el-button type="text" size="small" text-color="#ff4d4f" @click="handleDeleteRecord(row.id)"> 删除 </el-button>
|
||||
</template>
|
||||
</template>
|
||||
</ProTable>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 单行行内编辑版本(总价 totalPrice 输入):
|
||||
* - 点击「编辑」:将该行复制到 editModel(editModel.addWeight & editModel.totalPrice),记录 editingId(只允许单行编辑)
|
||||
* - 保存:计算 unitPrice = totalPrice / addWeight,发送 unitPrice 到后端
|
||||
* - 取消:不调用后端,直接清除 editingId(恢复显示)
|
||||
*/
|
||||
|
||||
import { defineProps, defineEmits, ref, reactive, watch, nextTick } from "vue";
|
||||
import ProTable from "@/components/ProTable/index.vue";
|
||||
import { medicinalApi } from "@/api";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
medicinalId: { type: Number, required: true },
|
||||
medicinalName: { type: String, default: "药材" }
|
||||
});
|
||||
const emit = defineEmits(["update:visible", "close", "refreshList"]);
|
||||
|
||||
// proTable 引用(用于 refresh)
|
||||
const proTableRef = ref<any | null>(null);
|
||||
|
||||
// 当前正在编辑的行 id(单行)
|
||||
const editingId = ref<number | null>(null);
|
||||
|
||||
// 编辑模型(只存一行):注意 totalPrice 而不是 unitPrice
|
||||
const editModel = reactive({
|
||||
addWeight: 0,
|
||||
totalPrice: 0
|
||||
});
|
||||
|
||||
// 保存中标记,避免重复提交
|
||||
const saving = ref(false);
|
||||
|
||||
// 列定义,slotName 使用 prop 名称(与 ProTable slot 对应)
|
||||
const columns = [
|
||||
{ prop: "addWeight", label: "添加量(g)", minWidth: 120, slotName: "addWeight" },
|
||||
{ prop: "unitPrice", label: "单价(元/g)", minWidth: 120, slotName: "unitPrice" },
|
||||
{ prop: "totalPrice", label: "总价(元)", minWidth: 120, slotName: "totalPrice" },
|
||||
{ prop: "addTime", label: "添加时间", minWidth: 200, slotName: "addTime" },
|
||||
{ prop: "addUserName", label: "添加人员", minWidth: 150 },
|
||||
{ prop: "operation", label: "操作", width: 180, fixed: "right", slotName: "operation" }
|
||||
];
|
||||
|
||||
const rowKey = (row: any) => row.id;
|
||||
|
||||
// 打开弹窗时刷新表格
|
||||
watch(
|
||||
() => props.visible,
|
||||
async val => {
|
||||
if (val && props.medicinalId) {
|
||||
await nextTick();
|
||||
proTableRef.value?.refresh?.();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 将 ProTable 的请求透传到后端(ProTable 会带上 pageNum/pageSize 等)
|
||||
const getAdditionRecordsApi = (params: any) => {
|
||||
return medicinalApi.getAdditionRecords(props.medicinalId, params);
|
||||
};
|
||||
|
||||
// 开始编辑:把当前行复制到 editModel(addWeight 和 totalPrice)
|
||||
const startRowEdit = (row: any) => {
|
||||
if (editingId.value !== null && editingId.value !== row.id) {
|
||||
ElMessage.warning("请先保存或取消当前正在编辑的行");
|
||||
return;
|
||||
}
|
||||
editingId.value = row.id;
|
||||
editModel.addWeight = row.addWeight ?? 0;
|
||||
// 后端返回的是 unitPrice,前端要显示/编辑 totalPrice = unitPrice * addWeight
|
||||
editModel.totalPrice = Number(((row.unitPrice ?? 0) * (row.addWeight ?? 1)).toFixed(2));
|
||||
};
|
||||
|
||||
// 取消编辑:清空 editingId(恢复显示)
|
||||
const cancelRowEdit = () => {
|
||||
editingId.value = null;
|
||||
editModel.addWeight = 0;
|
||||
editModel.totalPrice = 0;
|
||||
};
|
||||
|
||||
// 保存编辑:计算 unitPrice = totalPrice / addWeight -> 发送到后端
|
||||
const saveRowEdit = async (row: any) => {
|
||||
// 基本校验
|
||||
if (!editModel.addWeight || editModel.addWeight <= 0) {
|
||||
ElMessage.warning("添加量需大于 0");
|
||||
return;
|
||||
}
|
||||
if (editModel.totalPrice < 0) {
|
||||
ElMessage.warning("总价不能为负");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
saving.value = true;
|
||||
// 计算 unitPrice(保留两位)
|
||||
const unitPrice = parseFloat((editModel.totalPrice / editModel.addWeight).toFixed(2));
|
||||
|
||||
await medicinalApi.updateAdditionRecord(row.id, {
|
||||
addWeight: editModel.addWeight,
|
||||
unitPrice: unitPrice
|
||||
});
|
||||
|
||||
ElMessage.success("保存成功");
|
||||
// 刷新表格并退出编辑
|
||||
await proTableRef.value?.refresh?.();
|
||||
emit("refreshList");
|
||||
cancelRowEdit();
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err?.message || "保存失败");
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 删除记录
|
||||
const handleDeleteRecord = async (id: number) => {
|
||||
ElMessageBox.confirm("确定要删除这条添加记录吗?删除后不可恢复!", "确认删除", {
|
||||
confirmButtonText: "确认",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await medicinalApi.deleteAdditionRecord(id);
|
||||
ElMessage.success("删除成功");
|
||||
await proTableRef.value?.refresh?.();
|
||||
emit("refreshList");
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err?.message || "删除失败");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* 用户取消删除 */
|
||||
});
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatDate = (dateStr: string) => {
|
||||
const d = new Date(String(dateStr).replace(" ", "T"));
|
||||
return isNaN(d.getTime()) ? dateStr : d.toLocaleString();
|
||||
};
|
||||
|
||||
// 处理弹窗关闭
|
||||
const handleDialogClose = () => {
|
||||
editingId.value = null;
|
||||
editModel.addWeight = 0;
|
||||
editModel.totalPrice = 0;
|
||||
emit("update:visible", false);
|
||||
emit("close");
|
||||
};
|
||||
const handleUpdateVisible = (newVal: boolean) => {
|
||||
if (!newVal) handleDialogClose();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<!-- 关键修改:v-model → :visible + @update:visible -->
|
||||
<el-dialog :visible="visible" @update:visible="handleUpdateVisible" :title="`${medicinalName} - 消耗记录`" width="800px">
|
||||
<el-table v-loading="loading" :data="consumptionRecords" border style="width: 100%" empty-text="暂无消耗记录">
|
||||
<el-table-column prop="amountG" label="使用量(g)" width="120"></el-table-column>
|
||||
<el-table-column label="使用人员" width="200">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.personName }}</span>
|
||||
<el-tag size="small" type="info" v-if="scope.row.isRelative" style="margin-left: 8px">
|
||||
家属({{ scope.row.employeeName || "未知员工" }})
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="useTime" label="使用时间" width="200">
|
||||
<template #default="scope">
|
||||
{{ scope.row.useTime ? new Date(scope.row.useTime).toLocaleString() : "-" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits, ref, watch } from "vue";
|
||||
import { getConsumptionRecords, Medicinal } from "@/api/modules/biz/WeiBingJu/medicinal";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
// 1. 接收父组件prop
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
medicinalId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
medicinalName: {
|
||||
type: String,
|
||||
default: "药材"
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 定义发射事件:update:visible + 关闭通知
|
||||
const emit = defineEmits(["update:visible", "close"]);
|
||||
|
||||
// 状态数据
|
||||
const loading = ref(false);
|
||||
const consumptionRecords = ref<Medicinal.ConsumptionRecord[]>([]);
|
||||
|
||||
// 3. 监听弹窗状态变化
|
||||
const handleUpdateVisible = (newVisible: boolean) => {
|
||||
emit("update:visible", newVisible);
|
||||
// 弹窗关闭时清空数据
|
||||
if (!newVisible) {
|
||||
consumptionRecords.value = [];
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
|
||||
// 监听visible变化:弹窗显示时加载消耗记录
|
||||
watch(
|
||||
() => props.visible,
|
||||
async newVal => {
|
||||
if (newVal && props.medicinalId) {
|
||||
await fetchConsumptionRecords();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 加载消耗记录
|
||||
const fetchConsumptionRecords = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getConsumptionRecords({ medicinalId: props.medicinalId });
|
||||
consumptionRecords.value = res;
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err.message || "加载消耗记录失败");
|
||||
consumptionRecords.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<el-card class="mt-4">
|
||||
<ProTable ref="proTable" title="药材列表" :columns="columns" :request-api="medicinalApi.page">
|
||||
<template #tableHeader>
|
||||
<!-- 新增按钮:保持框架一致的 s-button 写法 -->
|
||||
<s-button suffix="药材" @click="onOpenAdd" />
|
||||
</template>
|
||||
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewConsumption(scope.row)"> 查看消耗记录 </s-button>
|
||||
<s-button link :opt="FormOptEnum.VIEW" size="small" class="ml-2" @click="handleViewAddition(scope.row)"> 查看添加记录 </s-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
</el-card>
|
||||
|
||||
<!-- 消耗记录弹窗 -->
|
||||
<consumption-record-dialog v-model:visible="showConsumptionDialog" :medicinal-id="currentMedicinalId" :medicinal-name="currentMedicinalName" />
|
||||
|
||||
<!-- 添加记录弹窗 -->
|
||||
<addition-record-dialog
|
||||
:visible="showAdditionDialog"
|
||||
:medicinal-id="currentMedicinalId"
|
||||
:medicinal-name="currentMedicinalName"
|
||||
@update:visible="val => (showAdditionDialog = val)"
|
||||
@refresh-list="handleRefresh"
|
||||
/>
|
||||
|
||||
<!-- 新增药材弹窗 -->
|
||||
<add-medicinal-dialog v-model:visible="showAddMedicinalDialog" @refresh-list="handleRefresh" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="sysModule">
|
||||
import { medicinalApi, MedicinalInfo } from "@/api";
|
||||
import { ref } from "vue";
|
||||
import ProTable from "@/components/ProTable/index.vue";
|
||||
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
|
||||
import { FormOptEnum } from "@/enums";
|
||||
|
||||
// 子组件
|
||||
//import ConsumptionRecordDialog from "./components/ConsumptionRecordDialog.vue";
|
||||
import AdditionRecordDialog from "./components/AdditionRecordDialog.vue";
|
||||
import AddMedicinalDialog from "./components/AddMedicinalDialog.vue";
|
||||
|
||||
const proTable = ref<ProTableInstance | null>(null);
|
||||
|
||||
const showConsumptionDialog = ref(false);
|
||||
const showAdditionDialog = ref(false);
|
||||
const showAddMedicinalDialog = ref(false);
|
||||
|
||||
const currentMedicinalId = ref<number | null>(null);
|
||||
const currentMedicinalName = ref<string>("");
|
||||
|
||||
const columns: ColumnProps<MedicinalInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "medicinalName", label: "药材名称", search: { el: "input" } },
|
||||
{ prop: "stock", label: "药材库存(g)" },
|
||||
{ prop: "usedCost", label: "使用成本" },
|
||||
{ prop: "remainingValue", label: "剩余价值" },
|
||||
{ prop: "operation", label: "操作", width: 240, fixed: "right" }
|
||||
];
|
||||
|
||||
// 打开新增药材弹窗
|
||||
function onOpenAdd() {
|
||||
showAddMedicinalDialog.value = true;
|
||||
}
|
||||
|
||||
// 查看消耗记录
|
||||
function handleViewConsumption(row: MedicinalInfo) {
|
||||
currentMedicinalId.value = row.medicinalId ?? (row as any).id ?? null;
|
||||
currentMedicinalName.value = row.medicinalName ?? "";
|
||||
showConsumptionDialog.value = true;
|
||||
}
|
||||
|
||||
// 查看添加记录
|
||||
function handleViewAddition(row: MedicinalInfo) {
|
||||
console.log("查看添加记录", row);
|
||||
// 直接设置正确的ID和名称,然后显示弹窗
|
||||
currentMedicinalId.value = row.medicinalId ?? (row as any).id ?? null;
|
||||
currentMedicinalName.value = row.medicinalName ?? "";
|
||||
showAdditionDialog.value = true;
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
function handleRefresh() {
|
||||
proTable.value?.refresh?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.table-box {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,714 @@
|
||||
<template>
|
||||
<el-dialog :model-value="visible" @update:model-value="handleUpdateVisible" :title="dialogTitle" width="900px" destroy-on-close>
|
||||
<!-- 人员信息 -->
|
||||
<el-card shadow="hover" class="mb-4">
|
||||
<template #header><span>人员信息</span></template>
|
||||
<el-form :model="personForm" label-width="120px" :disabled="!isEdit" ref="personFormRef" :rules="personFormRules">
|
||||
<!-- 1. 人员类型 -->
|
||||
<el-form-item label="人员类型" required prop="isEmployee">
|
||||
<el-radio-group v-model="personForm.isEmployee" @change="handlePersonTypeChange">
|
||||
<el-radio :label="true">公司员工</el-radio>
|
||||
<el-radio :label="false">员工家属</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 2. 员工场景:员工选择/新增 -->
|
||||
<template v-if="personForm.isEmployee">
|
||||
<!-- 员工下拉选择(默认显示) -->
|
||||
<el-form-item v-if="showEmployeeSelect" label="员工姓名" required prop="employeeId">
|
||||
<el-select
|
||||
v-model="personForm.employeeId"
|
||||
placeholder="搜索或选择员工"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleEmployeeSearch"
|
||||
@change="handleEmployeeChange"
|
||||
clearable
|
||||
>
|
||||
<!-- 正常员工选项 -->
|
||||
<el-option v-for="emp in employeeOptions" :key="emp.id" :label="emp.name" :value="emp.id" />
|
||||
<!-- 未找到选项(仅搜索后无结果时显示) -->
|
||||
<el-option v-if="employeeOptions.length === 0 && employeeKeyword" :key="0" :label="`未找到,新增员工:${employeeKeyword}`" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 员工新增文本框(点击"未找到"后显示) -->
|
||||
<el-form-item v-if="!showEmployeeSelect" label="员工姓名" required prop="newEmployeeName">
|
||||
<el-input
|
||||
v-model="personForm.newEmployeeName"
|
||||
placeholder="输入新员工姓名"
|
||||
@blur="() => (personForm.name = personForm.newEmployeeName)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 3. 家属场景:关联员工选择 + 家属选择/新增 -->
|
||||
<template v-else>
|
||||
<!-- 3.1 关联员工选择(第一步) -->
|
||||
<el-form-item v-if="showRelatedEmployeeSelect" label="关联员工" required prop="relatedEmployeeId">
|
||||
<el-select
|
||||
v-model="personForm.relatedEmployeeId"
|
||||
placeholder="搜索或选择关联员工"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleRelatedEmployeeSearch"
|
||||
@change="handleRelatedEmployeeChange"
|
||||
clearable
|
||||
>
|
||||
<!-- 正常关联员工选项 -->
|
||||
<el-option v-for="emp in relatedEmployeeOptions" :key="emp.id" :label="emp.name" :value="emp.id" />
|
||||
<!-- 未找到选项 -->
|
||||
<el-option
|
||||
v-if="relatedEmployeeOptions.length === 0 && relatedEmployeeKeyword"
|
||||
:key="0"
|
||||
:label="`未找到,新增关联员工:${relatedEmployeeKeyword}`"
|
||||
:value="0"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 3.2 关联员工新增(点击"未找到"后显示) -->
|
||||
<el-form-item v-if="!showRelatedEmployeeSelect" label="关联员工姓名" required prop="newRelatedEmployeeName">
|
||||
<el-input v-model="personForm.newRelatedEmployeeName" placeholder="输入新关联员工姓名" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 3.3 家属选择(选择关联员工后显示) -->
|
||||
<el-form-item v-if="showRelativeSelect && personForm.relatedEmployeeId" label="家属姓名" required prop="relativeId">
|
||||
<el-select
|
||||
v-model="personForm.relativeId"
|
||||
placeholder="搜索或选择家属"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleRelativeSearch"
|
||||
@change="handleRelativeChange"
|
||||
clearable
|
||||
>
|
||||
<!-- 正常家属选项 -->
|
||||
<el-option v-for="rel in relativeOptions" :key="rel.id" :label="rel.name" :value="rel.id" />
|
||||
<!-- 未找到选项 -->
|
||||
<el-option v-if="relativeOptions.length === 0 && relativeKeyword" :key="0" :label="`未找到,新增家属:${relativeKeyword}`" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 3.4 家属新增(点击"未找到"后显示 / 关联员工新增时同步显示) -->
|
||||
<el-form-item v-if="!showRelativeSelect || !showRelatedEmployeeSelect" label="家属姓名" required prop="newRelativeName">
|
||||
<el-input
|
||||
v-model="personForm.newRelativeName"
|
||||
placeholder="输入新家属姓名"
|
||||
@blur="() => (personForm.name = personForm.newRelativeName)"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 3.5 亲属关系(家属场景必选) -->
|
||||
<el-form-item label="亲属关系" required prop="relationShip" v-if="personForm.relatedEmployeeId || !showRelatedEmployeeSelect">
|
||||
<el-select v-model="personForm.relationShip" clearable>
|
||||
<el-option v-for="item in relationTypes" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 4. 公共信息:年龄/体重/身高(复用原有逻辑) -->
|
||||
<el-form-item label="年龄" prop="age">
|
||||
<el-input v-model.number="personForm.age" placeholder="输入年龄" />
|
||||
</el-form-item>
|
||||
<el-form-item label="体重(kg)" prop="weight">
|
||||
<el-input v-model.number="personForm.weight" placeholder="输入体重" />
|
||||
</el-form-item>
|
||||
<el-form-item label="身高(cm)" prop="height">
|
||||
<el-input v-model.number="personForm.height" placeholder="输入身高" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 药材消耗、诊断建议等原有模块(保持不变) -->
|
||||
<el-card shadow="hover" class="mb-4">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span>药材消耗</span>
|
||||
<el-button v-if="isEdit" type="primary" size="small" @click="handleAddHerb"> 添加药材 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="herbList" border :disabled="!isEdit" empty-text="暂无药材消耗记录">
|
||||
<el-table-column label="药材名称" required>
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.medicinalId"
|
||||
placeholder="搜索或选择药材"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="kw => handleHerbSearch(kw, scope.$index)"
|
||||
@change="val => handleHerbChange(val, scope.$index)"
|
||||
:disabled="!isEdit"
|
||||
clearable
|
||||
>
|
||||
<el-option v-for="item in scope.row.medicinalOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
<el-option
|
||||
v-if="scope.row.medicinalOptions.length === 0 && scope.row.keyword"
|
||||
:key="0"
|
||||
:label="`新增药材:${scope.row.keyword}`"
|
||||
:value="0"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input v-if="scope.row.showNewInput" v-model="scope.row.newName" placeholder="输入新药材名称" class="mt-2" :disabled="!isEdit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="消耗(g)" required>
|
||||
<template #default="scope">
|
||||
<el-input v-model.number="scope.row.amountG" type="number" min="0.1" placeholder="输入消耗克数" :disabled="!isEdit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.note" placeholder="输入备注" :disabled="!isEdit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" v-if="isEdit">
|
||||
<template #default="scope">
|
||||
<el-button type="text" text-color="#ff4d4f" @click="handleDeleteHerb(scope.$index)"> 删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<div class="flex gap-4 mb-4">
|
||||
<el-card shadow="hover" class="flex-1">
|
||||
<template #header><span>诊断结果</span></template>
|
||||
<el-input v-model="prescriptionForm.diagnosisResult" type="textarea" rows="5" placeholder="输入诊断结果" :disabled="!isEdit" />
|
||||
</el-card>
|
||||
<el-card shadow="hover" class="flex-1">
|
||||
<template #header><span>用药建议</span></template>
|
||||
<el-input v-model="prescriptionForm.userMedicinaAdvice" type="textarea" rows="5" placeholder="输入用药建议" :disabled="!isEdit" />
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover" class="mb-4">
|
||||
<el-form-item label="就诊时间" required>
|
||||
<el-date-picker
|
||||
v-model="prescriptionForm.visitDate"
|
||||
type="datetime"
|
||||
placeholder="选择就诊时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="!isEdit"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave" v-if="isEdit">
|
||||
{{ prescriptionId ? "保存修改" : "新增处方" }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed, nextTick } from "vue";
|
||||
import { ElMessage, ElMessageBox, ElForm, FormItemRule } from "element-plus";
|
||||
import { prescriptionApi } from "@/api/modules/biz/WeiBingJu/prescription";
|
||||
|
||||
// 1. props/emit定义
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false },
|
||||
prescriptionId: { type: Number, default: 0 },
|
||||
isEdit: { type: Boolean, default: false }
|
||||
});
|
||||
const emit = defineEmits(["update:visible", "close", "refreshList"]);
|
||||
|
||||
// 2. 基础变量
|
||||
const dialogTitle = computed(() => {
|
||||
if (props.prescriptionId === 0) return "新增就诊处方";
|
||||
return props.isEdit ? "编辑处方" : "处方详情";
|
||||
});
|
||||
const loading = ref(false);
|
||||
const personFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
const relationTypes = ref<any[]>([]); // 亲属关系列表
|
||||
const herbList = ref<any[]>([]); // 药材列表(原有逻辑)
|
||||
|
||||
// 3. 处方表单(原有逻辑)
|
||||
const prescriptionForm = reactive({
|
||||
diagnosisResult: "",
|
||||
userMedicinaAdvice: "",
|
||||
visitDate: new Date().toISOString().slice(0, 16) // 默认当前时间
|
||||
});
|
||||
|
||||
// 4. 人员表单(核心重构:区分员工/家属场景)
|
||||
const personForm = reactive({
|
||||
// 基础字段
|
||||
id: undefined, // 人员ID(编辑时用)
|
||||
name: "", // 人员姓名(最终提交用)
|
||||
isEmployee: true, // 是否员工(true=员工,false=家属)
|
||||
|
||||
// 员工场景字段
|
||||
employeeId: undefined, // 已选员工ID
|
||||
newEmployeeName: "", // 新增员工姓名
|
||||
|
||||
// 家属场景字段
|
||||
relatedEmployeeId: undefined, // 关联员工ID
|
||||
newRelatedEmployeeName: "", // 新增关联员工姓名
|
||||
relativeId: undefined, // 已选家属ID
|
||||
newRelativeName: "", // 新增家属姓名
|
||||
relationShip: undefined, // 亲属关系ID
|
||||
|
||||
// 公共字段
|
||||
age: undefined,
|
||||
weight: undefined,
|
||||
height: undefined
|
||||
});
|
||||
|
||||
// 5. 下拉/新增状态控制(核心:控制显示隐藏)
|
||||
const state = reactive({
|
||||
// 员工场景
|
||||
showEmployeeSelect: true, // 是否显示员工下拉(默认显示)
|
||||
employeeOptions: [] as any[], // 员工下拉选项
|
||||
employeeKeyword: "", // 员工搜索关键词
|
||||
|
||||
// 家属场景
|
||||
showRelatedEmployeeSelect: true, // 是否显示关联员工下拉(默认显示)
|
||||
relatedEmployeeOptions: [] as any[], // 关联员工下拉选项
|
||||
relatedEmployeeKeyword: "", // 关联员工搜索关键词
|
||||
|
||||
showRelativeSelect: true, // 是否显示家属下拉(默认显示)
|
||||
relativeOptions: [] as any[], // 家属下拉选项
|
||||
relativeKeyword: "" // 家属搜索关键词
|
||||
});
|
||||
|
||||
// 6. 表单验证规则
|
||||
const personFormRules = reactive<Record<string, FormItemRule[]>>({
|
||||
isEmployee: [{ required: true, message: "请选择人员类型", trigger: "change" }],
|
||||
employeeId: [{ required: true, message: "请选择员工", trigger: "change" }],
|
||||
newEmployeeName: [{ required: true, message: "请输入员工姓名", trigger: "blur" }],
|
||||
relatedEmployeeId: [{ required: true, message: "请选择关联员工", trigger: "change" }],
|
||||
newRelatedEmployeeName: [{ required: true, message: "请输入关联员工姓名", trigger: "blur" }],
|
||||
relativeId: [{ required: true, message: "请选择家属", trigger: "change" }],
|
||||
newRelativeName: [{ required: true, message: "请输入家属姓名", trigger: "blur" }],
|
||||
relationShip: [{ required: true, message: "请选择亲属关系", trigger: "change" }],
|
||||
age: [{ type: "number", message: "请输入合法年龄", trigger: "blur" }],
|
||||
weight: [{ type: "number", message: "请输入合法体重", trigger: "blur" }],
|
||||
height: [{ type: "number", message: "请输入合法身高", trigger: "blur" }]
|
||||
});
|
||||
|
||||
// 7. 初始化逻辑(监听弹窗显示/隐藏)
|
||||
watch(
|
||||
() => props.visible,
|
||||
async val => {
|
||||
if (val) {
|
||||
await fetchRelationTypes(); // 加载亲属关系
|
||||
if (props.prescriptionId) {
|
||||
await fetchPrescriptionDetail(); // 编辑/详情:加载已有数据
|
||||
} else {
|
||||
resetForm(); // 新增:重置表单
|
||||
}
|
||||
} else {
|
||||
resetForm(); // 弹窗关闭:重置表单
|
||||
emit("close");
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 8. 基础接口请求(原有逻辑优化)
|
||||
// 加载亲属关系
|
||||
const fetchRelationTypes = async () => {
|
||||
try {
|
||||
const res = await prescriptionApi.getRelationTypes();
|
||||
relationTypes.value = res || [];
|
||||
} catch (err) {
|
||||
relationTypes.value = [];
|
||||
ElMessage.error("加载亲属关系失败");
|
||||
}
|
||||
};
|
||||
|
||||
// 加载处方详情(编辑/详情场景)
|
||||
const fetchPrescriptionDetail = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await prescriptionApi.getDetail({ id: props.prescriptionId });
|
||||
if (!res) return;
|
||||
|
||||
// 填充处方信息
|
||||
prescriptionForm.diagnosisResult = res.diagnosisResult || "";
|
||||
prescriptionForm.userMedicinaAdvice = res.userMedicinaAdvice || "";
|
||||
prescriptionForm.visitDate = res.visitDate || new Date().toISOString().slice(0, 16);
|
||||
|
||||
// 填充人员信息
|
||||
const { person } = res;
|
||||
if (person) {
|
||||
personForm.id = person.id;
|
||||
personForm.name = person.name;
|
||||
personForm.isEmployee = person.EmployeeId === undefined; // 员工:EmployeeId为空
|
||||
personForm.age = person.age;
|
||||
personForm.weight = person.weight;
|
||||
personForm.height = person.height;
|
||||
personForm.relationShip = person.RelationShip;
|
||||
|
||||
// 员工场景:赋值员工ID
|
||||
if (personForm.isEmployee) {
|
||||
personForm.employeeId = person.id;
|
||||
state.employeeOptions = [{ id: person.id, name: person.name }];
|
||||
}
|
||||
// 家属场景:赋值关联员工ID和家属ID
|
||||
else {
|
||||
personForm.relatedEmployeeId = person.EmployeeId;
|
||||
personForm.relativeId = person.id;
|
||||
// 加载关联员工选项
|
||||
state.relatedEmployeeOptions = [{ id: person.EmployeeId, name: res.employeeName || "" }];
|
||||
// 加载家属选项
|
||||
state.relativeOptions = [{ id: person.id, name: person.name }];
|
||||
}
|
||||
}
|
||||
|
||||
// 填充药材信息(原有逻辑)
|
||||
herbList.value = (res.herbs || []).map((h: any) => ({
|
||||
medicinalId: h.medicinalId,
|
||||
medicinalOptions: [{ id: h.medicinalId, name: h.medicinalName }],
|
||||
keyword: h.medicinalName,
|
||||
showNewInput: false,
|
||||
newName: "",
|
||||
amountG: h.amountG,
|
||||
note: h.note || ""
|
||||
}));
|
||||
} catch (err) {
|
||||
ElMessage.error("加载处方详情失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 9. 员工场景交互逻辑
|
||||
// 员工下拉搜索
|
||||
const handleEmployeeSearch = async (keyword: string) => {
|
||||
state.employeeKeyword = keyword.trim();
|
||||
if (!keyword) {
|
||||
state.employeeOptions = [];
|
||||
return;
|
||||
}
|
||||
// 调用接口查询员工(personType=1表示员工)
|
||||
const res = await prescriptionApi.getPersonList({ personType: 1, keyword });
|
||||
state.employeeOptions = res || [];
|
||||
};
|
||||
|
||||
// 员工下拉选择(含"未找到"逻辑)
|
||||
const handleEmployeeChange = async (val: number) => {
|
||||
if (val === 0) {
|
||||
// 点击"未找到":隐藏下拉,显示新增文本框
|
||||
state.showEmployeeSelect = false;
|
||||
personForm.employeeId = undefined; // 清空下拉值
|
||||
personForm.newEmployeeName = state.employeeKeyword; // 预填搜索关键词
|
||||
} else {
|
||||
// 选择已有员工:显示下拉,清空新增文本框
|
||||
state.showEmployeeSelect = true;
|
||||
personForm.newEmployeeName = "";
|
||||
// 赋值人员姓名
|
||||
const selectedEmp = state.employeeOptions.find(emp => emp.id === val);
|
||||
if (selectedEmp) personForm.name = selectedEmp.name;
|
||||
}
|
||||
};
|
||||
|
||||
// 10. 家属场景交互逻辑
|
||||
// 关联员工下拉搜索
|
||||
const handleRelatedEmployeeSearch = async (keyword: string) => {
|
||||
state.relatedEmployeeKeyword = keyword.trim();
|
||||
if (!keyword) {
|
||||
state.relatedEmployeeOptions = [];
|
||||
return;
|
||||
}
|
||||
// 调用接口查询员工(personType=1表示员工)
|
||||
const res = await prescriptionApi.getPersonList({ personType: 1, keyword });
|
||||
state.relatedEmployeeOptions = res || [];
|
||||
};
|
||||
|
||||
// 关联员工下拉选择(含"未找到"逻辑)
|
||||
const handleRelatedEmployeeChange = async (val: number) => {
|
||||
if (val === 0) {
|
||||
// 点击"未找到":隐藏关联员工下拉,显示新增文本框+家属新增文本框
|
||||
state.showRelatedEmployeeSelect = false;
|
||||
state.showRelativeSelect = false; // 隐藏家属下拉
|
||||
personForm.relatedEmployeeId = undefined;
|
||||
personForm.newRelatedEmployeeName = state.relatedEmployeeKeyword;
|
||||
personForm.relativeId = undefined;
|
||||
} else {
|
||||
// 选择已有关联员工:显示关联员工下拉,加载家属列表
|
||||
state.showRelatedEmployeeSelect = true;
|
||||
state.showRelativeSelect = true; // 显示家属下拉
|
||||
personForm.newRelatedEmployeeName = "";
|
||||
personForm.newRelativeName = "";
|
||||
// 加载该员工的家属(personType=2表示家属,传employeeId)
|
||||
await handleLoadRelatives(val);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载关联员工的家属列表
|
||||
const handleLoadRelatives = async (employeeId: number) => {
|
||||
try {
|
||||
const res = await prescriptionApi.getPersonList({
|
||||
personType: 2, // 假设personType=2表示家属
|
||||
employeeId
|
||||
});
|
||||
state.relativeOptions = res || [];
|
||||
} catch (err) {
|
||||
state.relativeOptions = [];
|
||||
ElMessage.error("加载家属列表失败");
|
||||
}
|
||||
};
|
||||
|
||||
// 家属下拉搜索
|
||||
const handleRelativeSearch = async (keyword: string) => {
|
||||
state.relativeKeyword = keyword.trim();
|
||||
if (!keyword || !personForm.relatedEmployeeId) {
|
||||
state.relativeOptions = [];
|
||||
return;
|
||||
}
|
||||
// 调用接口查询家属(传关联员工ID)
|
||||
const res = await prescriptionApi.getPersonList({
|
||||
personType: 2,
|
||||
employeeId: personForm.relatedEmployeeId,
|
||||
keyword
|
||||
});
|
||||
state.relativeOptions = res || [];
|
||||
};
|
||||
|
||||
// 家属下拉选择(含"未找到"逻辑)
|
||||
const handleRelativeChange = async (val: number) => {
|
||||
if (val === 0) {
|
||||
// 点击"未找到":隐藏家属下拉,显示家属新增文本框
|
||||
state.showRelativeSelect = false;
|
||||
personForm.relativeId = undefined;
|
||||
personForm.newRelativeName = state.relativeKeyword;
|
||||
} else {
|
||||
// 选择已有家属:显示家属下拉,清空新增文本框
|
||||
state.showRelativeSelect = true;
|
||||
personForm.newRelativeName = "";
|
||||
// 赋值家属姓名
|
||||
const selectedRel = state.relativeOptions.find(rel => rel.id === val);
|
||||
if (selectedRel) personForm.name = selectedRel.name;
|
||||
}
|
||||
};
|
||||
|
||||
// 11. 人员类型切换逻辑(重置状态)
|
||||
const handlePersonTypeChange = () => {
|
||||
// 重置所有状态
|
||||
state.showEmployeeSelect = true;
|
||||
state.showRelatedEmployeeSelect = true;
|
||||
state.showRelativeSelect = true;
|
||||
state.employeeOptions = [];
|
||||
state.relatedEmployeeOptions = [];
|
||||
state.relativeOptions = [];
|
||||
state.employeeKeyword = "";
|
||||
state.relatedEmployeeKeyword = "";
|
||||
state.relativeKeyword = "";
|
||||
|
||||
// 重置表单字段
|
||||
personForm.employeeId = undefined;
|
||||
personForm.newEmployeeName = "";
|
||||
personForm.relatedEmployeeId = undefined;
|
||||
personForm.newRelatedEmployeeName = "";
|
||||
personForm.relativeId = undefined;
|
||||
personForm.newRelativeName = "";
|
||||
personForm.relationShip = undefined;
|
||||
personForm.name = "";
|
||||
|
||||
// 重置表单验证
|
||||
nextTick(() => {
|
||||
personFormRef.value?.clearValidate();
|
||||
});
|
||||
};
|
||||
|
||||
// 12. 药材相关逻辑(保持原有)
|
||||
const handleAddHerb = () => {
|
||||
herbList.value.push({
|
||||
medicinalId: undefined,
|
||||
medicinalOptions: [],
|
||||
keyword: "",
|
||||
showNewInput: false,
|
||||
newName: "",
|
||||
amountG: undefined,
|
||||
note: ""
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteHerb = (index: number) => {
|
||||
herbList.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const handleHerbSearch = async (keyword: string, index: number) => {
|
||||
const res = await prescriptionApi.searchMedicinals({ keyword });
|
||||
herbList.value[index].keyword = keyword;
|
||||
herbList.value[index].medicinalOptions = res || [];
|
||||
};
|
||||
|
||||
const handleHerbChange = (val: number | 0, index: number) => {
|
||||
if (val === 0) {
|
||||
herbList.value[index].showNewInput = true;
|
||||
herbList.value[index].newName = herbList.value[index].keyword;
|
||||
} else {
|
||||
herbList.value[index].showNewInput = false;
|
||||
herbList.value[index].newName = "";
|
||||
}
|
||||
};
|
||||
|
||||
// 13. 表单提交/取消逻辑(优化参数组装)
|
||||
const handleSave = async () => {
|
||||
// 表单验证
|
||||
if (!personFormRef.value?.validate()) return;
|
||||
// 药材验证
|
||||
if (herbList.value.length === 0) {
|
||||
return ElMessage.warning("请至少添加一条药材消耗记录");
|
||||
}
|
||||
// 诊断结果验证
|
||||
if (!prescriptionForm.diagnosisResult.trim()) {
|
||||
return ElMessage.warning("请输入诊断结果");
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
// 处理新增员工/家属的情况
|
||||
let newPersonId: number | undefined = personForm.id;
|
||||
if (!newPersonId) {
|
||||
if (personForm.isEmployee) {
|
||||
// 新增员工
|
||||
if (state.showEmployeeSelect) {
|
||||
newPersonId = personForm.employeeId; // 选择已有员工
|
||||
} else {
|
||||
// 新增员工:调用addPerson接口
|
||||
newPersonId = await prescriptionApi.addPerson({
|
||||
name: personForm.newEmployeeName.trim(),
|
||||
isEmployee: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 新增家属:先处理关联员工(如有新增)
|
||||
let relatedEmpId = personForm.relatedEmployeeId;
|
||||
if (!state.showRelatedEmployeeSelect) {
|
||||
// 新增关联员工
|
||||
relatedEmpId = await prescriptionApi.addPerson({
|
||||
name: personForm.newRelatedEmployeeName.trim(),
|
||||
isEmployee: true
|
||||
});
|
||||
}
|
||||
|
||||
// 新增家属
|
||||
if (state.showRelativeSelect) {
|
||||
newPersonId = personForm.relativeId; // 选择已有家属
|
||||
} else {
|
||||
// 新增家属:调用addPerson接口
|
||||
newPersonId = await prescriptionApi.addPerson({
|
||||
name: personForm.newRelativeName.trim(),
|
||||
isEmployee: false,
|
||||
EmployeeId: relatedEmpId,
|
||||
RelationShip: personForm.relationShip
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 组装药材参数
|
||||
const herbParams = herbList.value.map(h => ({
|
||||
medicinalId: h.medicinalId,
|
||||
amountG: h.amountG,
|
||||
note: h.note?.trim()
|
||||
}));
|
||||
|
||||
// 最终提交参数
|
||||
const submitParams = {
|
||||
id: props.prescriptionId || undefined,
|
||||
PersonId: newPersonId, // 人员ID(新增/编辑后的值)
|
||||
diagnosisResult: prescriptionForm.diagnosisResult.trim(),
|
||||
userMedicinaAdvice: prescriptionForm.userMedicinaAdvice.trim(),
|
||||
visitDate: prescriptionForm.visitDate,
|
||||
herbs: herbParams
|
||||
};
|
||||
|
||||
// 提交保存
|
||||
await prescriptionApi.save(submitParams);
|
||||
ElMessage.success(props.prescriptionId ? "修改成功" : "新增成功");
|
||||
emit("update:visible", false);
|
||||
emit("refreshList");
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err.message || "保存失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
if (props.isEdit) {
|
||||
ElMessageBox.confirm("是否放弃未保存的修改?", "确认取消", { type: "warning" })
|
||||
.then(() => emit("update:visible", false))
|
||||
.catch(); // 无逻辑时可省略函数体
|
||||
} else {
|
||||
emit("update:visible", false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateVisible = (newVisible: boolean) => {
|
||||
emit("update:visible", newVisible);
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
// 重置处方表单
|
||||
prescriptionForm.diagnosisResult = "";
|
||||
prescriptionForm.userMedicinaAdvice = "";
|
||||
prescriptionForm.visitDate = new Date().toISOString().slice(0, 16);
|
||||
|
||||
// 重置人员表单
|
||||
Object.assign(personForm, {
|
||||
id: undefined,
|
||||
name: "",
|
||||
isEmployee: true,
|
||||
employeeId: undefined,
|
||||
newEmployeeName: "",
|
||||
relatedEmployeeId: undefined,
|
||||
newRelatedEmployeeName: "",
|
||||
relativeId: undefined,
|
||||
newRelativeName: "",
|
||||
relationShip: undefined,
|
||||
age: undefined,
|
||||
weight: undefined,
|
||||
height: undefined
|
||||
});
|
||||
|
||||
// 重置状态
|
||||
Object.assign(state, {
|
||||
showEmployeeSelect: true,
|
||||
employeeOptions: [],
|
||||
employeeKeyword: "",
|
||||
showRelatedEmployeeSelect: true,
|
||||
relatedEmployeeOptions: [],
|
||||
relatedEmployeeKeyword: "",
|
||||
showRelativeSelect: true,
|
||||
relativeOptions: [],
|
||||
relativeKeyword: ""
|
||||
});
|
||||
|
||||
// 重置药材列表
|
||||
herbList.value = [];
|
||||
|
||||
// 重置表单验证
|
||||
nextTick(() => {
|
||||
personFormRef.value?.clearValidate();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.gap-4 {
|
||||
gap: 16px;
|
||||
}
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<el-card class="mt-4">
|
||||
<ProTable ref="proTable" title="处方列表" :columns="columns" :request-api="getPrescriptionListApi" :row-key="rowKey">
|
||||
<template #tableHeader>
|
||||
<s-button suffix="处方" type="success" @click="handleAddPrescription" />
|
||||
</template>
|
||||
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewDetail(scope.row.id)"> 查看详情 </s-button>
|
||||
<s-button link :opt="FormOptEnum.EDIT" size="small" class="ml-2" @click="handleEdit(scope.row.id)" v-if="scope.row.canEdit">
|
||||
编辑
|
||||
</s-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
</el-card>
|
||||
|
||||
<!-- 弹窗:查看/编辑处方 -->
|
||||
<prescription-detail-dialog
|
||||
:visible="showDetailDialog"
|
||||
:prescription-id="currentPrescriptionId"
|
||||
:is-edit="isEdit"
|
||||
@update:visible="val => (showDetailDialog = val)"
|
||||
@refresh-list="handleRefresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="prescriptionModule">
|
||||
import { ref } from "vue";
|
||||
import ProTable from "@/components/ProTable/index.vue";
|
||||
import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
||||
import { prescriptionApi } from "@/api";
|
||||
import PrescriptionDetailDialog from "./components/PrescriptionDetailDialog.vue";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
|
||||
// ProTable ref
|
||||
const proTable = ref<ProTableInstance | null>(null);
|
||||
|
||||
// 弹窗状态
|
||||
const showDetailDialog = ref(false);
|
||||
const currentPrescriptionId = ref<number | null>(null);
|
||||
const isEdit = ref(false);
|
||||
|
||||
// 表格列配置
|
||||
const columns: ColumnProps<any>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "personName", label: "就诊人", search: { el: "input" } },
|
||||
{
|
||||
prop: "personType",
|
||||
label: "就诊人类型",
|
||||
formatter: (row: any) => (row.personType === 1 ? "公司员工" : `员工家属(${row.employeeName || "未知"})`)
|
||||
},
|
||||
{ prop: "visitDate", label: "就诊时间" },
|
||||
{ prop: "operation", label: "操作", width: 180, fixed: "right" }
|
||||
];
|
||||
|
||||
// rowKey定义
|
||||
const rowKey = (row: any) => row.id;
|
||||
|
||||
// 请求接口
|
||||
const getPrescriptionListApi = (params: any) => {
|
||||
return prescriptionApi.getList(params);
|
||||
};
|
||||
|
||||
// 新增处方
|
||||
const handleAddPrescription = () => {
|
||||
currentPrescriptionId.value = null;
|
||||
isEdit.value = true;
|
||||
showDetailDialog.value = true;
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (id: number) => {
|
||||
currentPrescriptionId.value = id;
|
||||
isEdit.value = false;
|
||||
showDetailDialog.value = true;
|
||||
};
|
||||
|
||||
// 编辑
|
||||
const handleEdit = (id: number) => {
|
||||
currentPrescriptionId.value = id;
|
||||
isEdit.value = true;
|
||||
showDetailDialog.value = true;
|
||||
};
|
||||
|
||||
// 刷新表格
|
||||
const handleRefresh = () => {
|
||||
proTable.value?.refresh?.();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.table-box {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
* @Description: 柱状图
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:12
|
||||
!-->
|
||||
<template>
|
||||
<div id="columnChat" class="h-150px"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="columnChart">
|
||||
import { OpLog, opLogApi } from "@/api";
|
||||
import { Column } from "@antv/g2plot";
|
||||
|
||||
onMounted(() => {
|
||||
opLogApi.columnChart().then(res => {
|
||||
createColumnChat(res.data);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建折线图,具体文档参考https://g2plot.antv.antgroup.com/api/plots/column
|
||||
* @param data 数据
|
||||
*/
|
||||
function createColumnChat(data: OpLog.ColumnChart[]) {
|
||||
const column = new Column("columnChat", {
|
||||
data,
|
||||
isGroup: true,
|
||||
xField: "date",
|
||||
yField: "count",
|
||||
seriesField: "name",
|
||||
/** 设置颜色 */
|
||||
color: ["#409EFF", "#F5222D"],
|
||||
/** 设置间距 */
|
||||
// marginRatio: 0.1,
|
||||
label: {
|
||||
// 可手动配置 label 数据标签位置
|
||||
position: "middle", // 'top', 'middle', 'bottom'
|
||||
// 可配置附加的布局方法
|
||||
layout: [
|
||||
// 柱形图数据标签位置自动调整
|
||||
{ type: "interval-adjust-position" },
|
||||
// 数据标签防遮挡
|
||||
{ type: "interval-hide-overlap" },
|
||||
// 数据标签文颜色自动调整
|
||||
{ type: "adjust-color" }
|
||||
]
|
||||
}
|
||||
});
|
||||
column.render(); //渲染
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<!--
|
||||
* @Description: 详情
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:16
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" title="日志详情" form-size="650px">
|
||||
<el-descriptions :column="1" border class="mb-2">
|
||||
<el-descriptions-item label="名称">{{ logInfo.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="IP地址">{{ logInfo.opIp }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">{{ logInfo.opAddress }}</el-descriptions-item>
|
||||
<el-descriptions-item label="请求地址">{{ logInfo.reqUrl }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作类">{{ logInfo.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作方法">{{ logInfo.methodName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="浏览器">{{ logInfo.opBrowser }}</el-descriptions-item>
|
||||
<el-descriptions-item label="设备">{{ logInfo.opOs }}</el-descriptions-item>
|
||||
<el-descriptions-item label="时间">{{ logInfo.opTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户">{{ logInfo.opUser }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账号">{{ logInfo.opAccount }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-space direction="vertical" class="mb-2 w-full">
|
||||
<span>请求参数:</span>
|
||||
<code-high-light language="JSON" class="w-600px" :code="paramJson" />
|
||||
</el-space>
|
||||
<el-space direction="vertical" class="mb-2" style="width: 100%">
|
||||
返回结果:
|
||||
<code-high-light autodetect class="w-600px" :code="resultJson"></code-high-light>
|
||||
</el-space>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="onClose"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="detail">
|
||||
import { OpLog, opLogApi } from "@/api";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 表单参数
|
||||
const logInfo = ref<OpLog.OpLogInfo>({
|
||||
name: "",
|
||||
opIp: "",
|
||||
opAddress: "",
|
||||
opBrowser: "",
|
||||
opOs: "",
|
||||
opTime: "",
|
||||
opUser: "",
|
||||
opAccount: "",
|
||||
id: 0,
|
||||
category: "",
|
||||
exeStatus: "",
|
||||
createTime: "",
|
||||
exeMessage: null,
|
||||
className: "",
|
||||
methodName: "",
|
||||
reqMethod: "",
|
||||
reqUrl: "",
|
||||
paramJson: "",
|
||||
resultJson: ""
|
||||
});
|
||||
|
||||
const paramJson = ref<string>();
|
||||
const resultJson = ref<string>();
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(record: OpLog.OpLogInfo) {
|
||||
logInfo.value = record;
|
||||
visible.value = true; //显示表单
|
||||
opLogApi.detail({ id: record.id }).then(res => {
|
||||
logInfo.value = res.data;
|
||||
if (res.data.paramJson) paramJson.value = JSON.stringify(JSON.parse(res.data.paramJson), null, 2);
|
||||
if (res.data.exeMessage) {
|
||||
resultJson.value = res.data.exeMessage;
|
||||
} else {
|
||||
if (res.data.resultJson) resultJson.value = JSON.stringify(JSON.parse(res.data.resultJson), null, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-space__item) {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!--
|
||||
* @Description: 饼状图
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:22
|
||||
!-->
|
||||
<template>
|
||||
<div id="pieChat" class="h-150px"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="pieChart">
|
||||
import { OpLog, opLogApi } from "@/api";
|
||||
import { Pie } from "@antv/g2plot";
|
||||
|
||||
onMounted(() => {
|
||||
opLogApi.pieChart().then(res => {
|
||||
createPieChat(res.data);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建折线图,具体文档参考https://g2plot.antv.antgroup.com/api/plots/pie
|
||||
* @param data 数据
|
||||
*/
|
||||
function createPieChat(data: OpLog.PineChart[]) {
|
||||
const piePlot = new Pie("pieChat", {
|
||||
appendPadding: 10,
|
||||
data,
|
||||
angleField: "value",
|
||||
colorField: "type",
|
||||
radius: 0.9,
|
||||
color: ["#409EFF", "#F5222D"],
|
||||
label: {
|
||||
type: "inner",
|
||||
offset: "-30%",
|
||||
content: ({ percent }) => `${(percent * 100).toFixed(0)}%`,
|
||||
style: {
|
||||
fontSize: 14,
|
||||
textAlign: "center"
|
||||
}
|
||||
},
|
||||
interactions: [{ type: "element-active" }]
|
||||
});
|
||||
piePlot.render(); //渲染
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<!--
|
||||
* @Description: 操作日志
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:27
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="10" class="mb-2">
|
||||
<el-col :span="16">
|
||||
<el-card header="周统计" shadow="never">
|
||||
<column-chat />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card header="总比例" shadow="never">
|
||||
<pie-chat />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="table-box min-h-400px">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
title="系统字典"
|
||||
class="table"
|
||||
:tool-button="false"
|
||||
:columns="columns"
|
||||
:request-api="opLogApi.page"
|
||||
:init-param="initParam"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
<el-space>
|
||||
<el-radio-group v-model="initParam.category" class="mb-15px">
|
||||
<el-radio-button v-for="(item, index) in visLogTypeOptions" :key="index" :label="item.value">{{ item.label }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-input v-model="initParam.searchKey" placeholder="请输入字典名称" class="mb-15px">
|
||||
<template #append>
|
||||
<el-button :icon="Search" class="el-input-button" />
|
||||
</template>
|
||||
</el-input>
|
||||
<s-button type="danger" plain :opt="FormOptEnum.DELETE" @click="onDelete('清空当前日志')">清空</s-button>
|
||||
</el-space>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.VIEW" @click="onOpen(scope.row)"> 详情 </s-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 日志详情 -->
|
||||
<Detail ref="detailRef" />
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="sysOpLog">
|
||||
import ColumnChat from "./components/columnChat.vue";
|
||||
import PieChat from "./components/pieChat.vue";
|
||||
import Detail from "./components/detail.vue";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import { OpLog, opLogApi } from "@/api";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
|
||||
// 左侧表格初始化条件
|
||||
interface InitParam {
|
||||
category?: string; //字典分类
|
||||
searchKey?: string; //关键字
|
||||
}
|
||||
// 访问日志类型
|
||||
let visLogTypeOptions = [
|
||||
{
|
||||
label: "操作日志",
|
||||
value: "OPERATE"
|
||||
},
|
||||
{
|
||||
label: "异常日志",
|
||||
value: "EXCEPTION"
|
||||
}
|
||||
];
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive<InitParam>({ category: visLogTypeOptions[0].value }); //主表格初始化参数
|
||||
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<OpLog.OpLogInfo>[] = [
|
||||
{ prop: "name", label: "名称" },
|
||||
{ prop: "opIp", label: "IP地址" },
|
||||
{ prop: "opAddress", label: "地址" },
|
||||
{ prop: "className", label: "类名称" },
|
||||
{ prop: "methodName", label: "方法名称" },
|
||||
{ prop: "opUser", label: "用户" },
|
||||
{ prop: "opAccount", label: "账号" },
|
||||
{ prop: "opTime", label: "操作时间" },
|
||||
{ prop: "operation", label: "操作", fixed: "right", width: 100 }
|
||||
];
|
||||
|
||||
const detailRef = ref<InstanceType<typeof Detail> | null>(null); //详情引用
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(record: OpLog.OpLogInfo) {
|
||||
detailRef.value?.onOpen(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
async function onDelete(msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(opLogApi.delete, initParam.category, msg);
|
||||
proTable.value?.refresh(); //刷新主表格
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-card__header) {
|
||||
padding: 9px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!--
|
||||
* @Description: 详情
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:38
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" title="日志详情" form-size="600px">
|
||||
<el-descriptions :column="1" border class="mb-2">
|
||||
<el-descriptions-item label="名称">{{ logInfo.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="IP地址">{{ logInfo.opIp }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地址">{{ logInfo.opAddress }}</el-descriptions-item>
|
||||
<el-descriptions-item label="浏览器">{{ logInfo.opBrowser }}</el-descriptions-item>
|
||||
<el-descriptions-item label="设备">{{ logInfo.opOs }}</el-descriptions-item>
|
||||
<el-descriptions-item label="时间">{{ logInfo.opTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户">{{ logInfo.opUser }}</el-descriptions-item>
|
||||
<el-descriptions-item label="账号">{{ logInfo.opAccount }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="onClose"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="detail">
|
||||
import { VisLog } from "@/api";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 表单参数
|
||||
const logInfo = ref<VisLog.VisLogInfo>({
|
||||
name: "",
|
||||
opIp: "",
|
||||
opAddress: "",
|
||||
opBrowser: "",
|
||||
opOs: "",
|
||||
opTime: "",
|
||||
opUser: "",
|
||||
opAccount: "",
|
||||
id: 0,
|
||||
category: "",
|
||||
exeStatus: "",
|
||||
createTime: ""
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(record: VisLog.VisLogInfo) {
|
||||
logInfo.value = record;
|
||||
visible.value = true; //显示表单
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!--
|
||||
* @Description: 折线图
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:42
|
||||
!-->
|
||||
<template>
|
||||
<div id="lineChat" class="h-150px"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="lineChart">
|
||||
import { VisLog, visLogApi } from "@/api";
|
||||
import { Line } from "@antv/g2plot";
|
||||
|
||||
const seriesField = "series"; //分组字段
|
||||
const yField = "value"; //图形在 y 方向对应的数据字段名,一般是纵向的坐标轴对应的字段
|
||||
|
||||
type alias = "用户登录" | "用户登出"; //别名
|
||||
|
||||
// lineMeta 用于处理数据,将数据中的字段名转换为别名
|
||||
interface LineMeta {
|
||||
[key: string]: {
|
||||
alias: alias;
|
||||
};
|
||||
}
|
||||
const lineMeta: LineMeta = {
|
||||
loginCount: {
|
||||
alias: "用户登录"
|
||||
},
|
||||
logoutCount: {
|
||||
alias: "用户登出"
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
visLogApi.lineChart().then(res => {
|
||||
createLineChat(res.data);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建折线图,具体文档参考https://g2plot.antv.antgroup.com/api/plots/line
|
||||
* @param data 数据
|
||||
*/
|
||||
function createLineChat(data: VisLog.LineChart[]) {
|
||||
const line = new Line("lineChat", {
|
||||
data: processData(data, lineMeta),
|
||||
padding: "auto",
|
||||
xField: "date",
|
||||
yField: yField,
|
||||
seriesField: seriesField,
|
||||
color: ["#409EFF", "rgb(188, 189, 190)"],
|
||||
appendPadding: [0, 8, 0, 0]
|
||||
});
|
||||
line.render(); //渲染
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据,将数据中的字段名转换为别名
|
||||
* @param data 数据
|
||||
* @param yFields y轴字段
|
||||
* @param meta 别名
|
||||
*/
|
||||
function processData(data: VisLog.LineChart[], meta: LineMeta) {
|
||||
const result: any = [];
|
||||
//将数据中的字段名转换为别名
|
||||
data.forEach((item: VisLog.LineChart) => {
|
||||
result.push({ ...item, [seriesField]: meta.loginCount?.alias, [yField]: item.loginCount });
|
||||
result.push({ ...item, [seriesField]: meta.logoutCount?.alias, [yField]: item.logoutCount });
|
||||
});
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!--
|
||||
* @Description: 饼状图
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:47
|
||||
!-->
|
||||
<template>
|
||||
<div id="pieChat" class="h-150px"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="pieChart">
|
||||
import { VisLog, visLogApi } from "@/api";
|
||||
import { Pie } from "@antv/g2plot";
|
||||
|
||||
onMounted(() => {
|
||||
visLogApi.pieChart().then(res => {
|
||||
createPieChat(res.data);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建折线图,具体文档参考https://g2plot.antv.antgroup.com/api/plots/pie
|
||||
* @param data 数据
|
||||
*/
|
||||
function createPieChat(data: VisLog.PineChart[]) {
|
||||
const piePlot = new Pie("pieChat", {
|
||||
appendPadding: 10,
|
||||
data,
|
||||
angleField: "value",
|
||||
colorField: "type",
|
||||
radius: 0.9,
|
||||
color: ["#409EFF", "rgb(188, 189, 190)"],
|
||||
label: {
|
||||
type: "inner",
|
||||
offset: "-30%",
|
||||
content: ({ percent }) => `${(percent * 100).toFixed(0)}%`,
|
||||
style: {
|
||||
fontSize: 14,
|
||||
textAlign: "center"
|
||||
}
|
||||
},
|
||||
interactions: [{ type: "element-active" }]
|
||||
});
|
||||
piePlot.render(); //渲染
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<!--
|
||||
* @Description: 访问日志
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:33
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="10" class="mb-2">
|
||||
<el-col :span="16">
|
||||
<el-card header="周统计" shadow="never">
|
||||
<line-chat />
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card header="总比例" shadow="never">
|
||||
<pie-chat />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="table-box min-h-400px">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
title="系统字典"
|
||||
class="table"
|
||||
:tool-button="false"
|
||||
:columns="columns"
|
||||
:request-api="visLogApi.page"
|
||||
:init-param="initParam"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
<el-space>
|
||||
<el-radio-group v-model="initParam.category" class="mb-15px">
|
||||
<el-radio-button v-for="(item, index) in visLogTypeOptions" :key="index" :label="item.value">{{ item.label }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-input v-model="initParam.searchKey" placeholder="请输入字典名称" class="mb-15px">
|
||||
<template #append>
|
||||
<el-button :icon="Search" class="el-input-button" />
|
||||
</template>
|
||||
</el-input>
|
||||
<s-button type="danger" plain :opt="FormOptEnum.DELETE" @click="onDelete('清空当前日志')">清空</s-button>
|
||||
</el-space>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.VIEW" @click="onOpen(scope.row)"> 详情 </s-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 日志详情 -->
|
||||
<Detail ref="detailRef" />
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="sysVisLog">
|
||||
import LineChat from "./components/lineChat.vue";
|
||||
import PieChat from "./components/pieChat.vue";
|
||||
import Detail from "./components/detail.vue";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import { VisLog, visLogApi } from "@/api";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
|
||||
// 左侧表格初始化条件
|
||||
interface InitParam {
|
||||
category?: string; //字典分类
|
||||
searchKey?: string; //关键字
|
||||
}
|
||||
// 访问日志类型
|
||||
let visLogTypeOptions = [
|
||||
{
|
||||
label: "登录日志",
|
||||
value: "LOGIN"
|
||||
},
|
||||
{
|
||||
label: "登出日志",
|
||||
value: "LOGOUT"
|
||||
}
|
||||
];
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive<InitParam>({ category: visLogTypeOptions[0].value }); //主表格初始化参数
|
||||
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<VisLog.VisLogInfo>[] = [
|
||||
{ prop: "name", label: "名称" },
|
||||
{ prop: "opIp", label: "IP地址" },
|
||||
{ prop: "opAddress", label: "地址" },
|
||||
{ prop: "opBrowser", label: "浏览器" },
|
||||
{ prop: "opOs", label: "操作系统" },
|
||||
{ prop: "opUser", label: "用户" },
|
||||
{ prop: "opAccount", label: "账号" },
|
||||
{ prop: "opTime", label: "操作时间" },
|
||||
{ prop: "operation", label: "操作", fixed: "right", width: 100 }
|
||||
];
|
||||
|
||||
const detailRef = ref<InstanceType<typeof Detail> | null>(null); //详情引用
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(record: VisLog.VisLogInfo) {
|
||||
detailRef.value?.onOpen(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
async function onDelete(msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(visLogApi.delete, initParam.category, msg);
|
||||
proTable.value?.refresh(); //刷新主表格
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-card__header) {
|
||||
padding: 9px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,538 @@
|
||||
<!--
|
||||
* @Description: 基础信息
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-01-08 16:05:34
|
||||
!-->
|
||||
<template>
|
||||
<el-form ref="basicFormRef" :rules="rules" :model="basicProps.record" label-width="auto" label-suffix=" :" label-position="top">
|
||||
<el-card :bordered="true" header="基本设置">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="选择主表" prop="dbTable">
|
||||
<s-select v-model="basicProps.record.dbTable" :options="basicOptions.tableOptions" filterable @change="formFieldAssign" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="表前缀移除" prop="tablePrefix">
|
||||
<s-radio-group v-model="basicProps.record.tablePrefix" :options="genConst.tablePrefixOptions" @change="tablePrefixChange">
|
||||
</s-radio-group>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="生成方式" prop="generateType">
|
||||
<s-radio-group v-model="basicProps.record.generateType" :options="genConst.generateTypeOptions"> </s-radio-group>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="选择生成模板" prop="moduleType" :popover="{ title: '生成模板说明', placement: 'top-start', width: '500' }">
|
||||
<template #popover>
|
||||
<p>单表:生成普通单表的增删改查</p>
|
||||
<p>树表-列表型:生成树型结构的表的增删改查,参考组织管理</p>
|
||||
<p>树表-树型:生成树型结构的表的增删改查,参考菜单管理</p>
|
||||
<p>主子表:生成主表关联子表的增删改查,删除主表数据会清除相应的子表数据</p>
|
||||
</template>
|
||||
<s-select v-model="basicProps.record.moduleType" :options="genConst.moduleTypeOptions" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="数据权限" prop="dataPermission">
|
||||
<s-radio-group v-model="basicProps.record.dataPermission" :options="genConst.dataPermissionOptions"></s-radio-group>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<s-form-item label="功能名" prop="functionName">
|
||||
<s-input v-model="basicProps.record.functionName" allow-clear />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<s-form-item label="后缀" prop="functionNameSuffix">
|
||||
<s-input v-model="basicProps.record.functionNameSuffix" allow-clear />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="功能列表" prop="funcList">
|
||||
<s-select v-model="basicProps.record.funcList" multiple :options="genConst.funcOptions" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="作者" prop="authorName">
|
||||
<s-input v-model="basicProps.record.authorName" allow-clear />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="basicProps.record.sortCode" :max="100" style="width: 100%" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider v-if="showTree" content-position="left">树表配置</el-divider>
|
||||
<el-row v-if="showTree" :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="树ID字段" prop="treeId">
|
||||
<s-select v-model="basicProps.record.treeId" :options="basicOptions.columnOptions" filterable />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="树父ID字段" prop="treePid">
|
||||
<s-select v-model="basicProps.record.treeId" :options="basicOptions.columnOptions" filterable />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="树名称字段" prop="treeName">
|
||||
<s-select v-model="basicProps.record.treeId" :options="basicOptions.columnOptions" filterable />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider v-if="showMaster" content-position="left">主子表配置</el-divider>
|
||||
<el-row v-if="showMaster" :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="关联子表的表名" prop="childTable">
|
||||
<s-select v-model="basicProps.record.childTable" :options="basicOptions.childTableList" filterable @change="selectChildTable" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<s-form-item label="树ID字段" prop="childFk">
|
||||
<s-select v-model="basicProps.record.childFk" :options="basicOptions.childColumnOptions" filterable />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card :bordered="true" header="前端设置" class="mt-2">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="前端文件夹路径" prop="frontedPath">
|
||||
<s-input v-model="basicProps.record.frontedPath" allow-clear />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="所属模块" prop="module">
|
||||
<s-select
|
||||
v-model="basicProps.record.module"
|
||||
:options="basicOptions.moduleOptions"
|
||||
filterable
|
||||
@change="moduleChange(basicProps.record.module as number, false)"
|
||||
/>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="上级目录" prop="menuPid">
|
||||
<MenuSelector
|
||||
v-model:menu-value="basicProps.record.menuPid"
|
||||
:check-strictly="true"
|
||||
:menu-tree-data="basicOptions.menuTreeData"
|
||||
:show-all="true"
|
||||
:only-catalog="true"
|
||||
@change="changeMenu"
|
||||
/>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="表单布局" prop="formLayout">
|
||||
<s-radio-group v-model="basicProps.record.formLayout" :options="genConst.formLayoutOptions"></s-radio-group>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="使用栅格" prop="gridWhether">
|
||||
<s-radio-group v-model="basicProps.record.gridWhether" :options="genConst.gridWhetherOptions"></s-radio-group>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="basicProps.record.moduleType === genConst.moduleTypeOptions[0].value">
|
||||
<s-form-item label="左侧树" prop="leftTree">
|
||||
<s-select v-model="basicProps.record.leftTree" :options="genConst.treeOptions" filterable />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="路由名(最终路由名是:路由名+业务名)" prop="routeName">
|
||||
<s-input v-model="basicProps.record.routeName" allow-clear placeholder="根据上级目录自动生成,顶级需手动填写" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="图标" prop="icon">
|
||||
<SelectIconPlus v-if="showIcon" v-model:icon-value="basicProps.record.icon" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card :bordered="true" header="后端设置" class="mt-2">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="业务名(最好和文件夹路径对应)" prop="busName">
|
||||
<s-input v-model="basicProps.record.busName" allow-clear />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="类名" prop="className">
|
||||
<s-input v-model="basicProps.record.className" allow-clear />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="服务层代码位置" prop="servicePosition">
|
||||
<el-input style="max-width: 600px" v-model="basicProps.record.serviceDictionary" placeholder="请输入文件夹">
|
||||
<template #prepend>
|
||||
<s-select style="width: 250px" v-model="basicProps.record.servicePosition" :options="basicOptions.servicePositions" filterable />
|
||||
</template>
|
||||
</el-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="接口层代码位置" prop="controllerPosition">
|
||||
<el-input style="max-width: 600px" v-model="basicProps.record.controllerDictionary" placeholder="请输入文件夹">
|
||||
<template #prepend>
|
||||
<s-select style="width: 250px" v-model="basicProps.record.controllerPosition" :options="basicOptions.servicePositions" filterable />
|
||||
</template>
|
||||
</el-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GenCode, genBasicApi, userCenterApi } from "@/api";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { useUserStore } from "@/stores/modules";
|
||||
import * as genConst from "../genConst";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { MenuCategoryEnum } from "@/enums";
|
||||
const userStore = useUserStore();
|
||||
const basicFormRef = ref<FormInstance>();
|
||||
const showIcon = ref(false);
|
||||
|
||||
// 选项接口
|
||||
interface BasicOptions {
|
||||
/*模块选项 */
|
||||
moduleOptions: { label: string; value: string | number }[];
|
||||
/*服务列表 */
|
||||
servicePositions: { label: string; value: string | number }[];
|
||||
/*控制器选项 */
|
||||
controllerPositions: { label: string; value: string | number }[];
|
||||
/*数据表列表 */
|
||||
tableOptions: GenCode.TableList[];
|
||||
/*字段选项 */
|
||||
columnOptions: { label: string; value: string }[];
|
||||
/*子表字段 */
|
||||
childColumnOptions: { label: string; value: string }[];
|
||||
/*子表列表 */
|
||||
childTableList: GenCode.TableList[];
|
||||
/*菜单选择器数据 */
|
||||
menuTreeData: any[];
|
||||
}
|
||||
|
||||
// 表单参数
|
||||
const basicProps = reactive<FormProps.Base<GenCode.GenBasic>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {}
|
||||
});
|
||||
|
||||
//显示树表配置
|
||||
const showTree = computed(() => {
|
||||
return basicProps.record.moduleType && basicProps.record.moduleType.includes("tree");
|
||||
});
|
||||
|
||||
//显示主子表配置
|
||||
const showMaster = computed(() => {
|
||||
return basicProps.record.moduleType && basicProps.record.moduleType.includes("master");
|
||||
});
|
||||
|
||||
//下拉用的数据
|
||||
const basicOptions: BasicOptions = reactive({
|
||||
tableOptions: [],
|
||||
moduleOptions: [],
|
||||
servicePositions: [],
|
||||
controllerPositions: [],
|
||||
columnOptions: [],
|
||||
childColumnOptions: [],
|
||||
childTableList: [],
|
||||
menuTreeData: []
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
tablePrefix: [required("请选择是否移除表前缀")],
|
||||
dbTable: [required("请选择主表")],
|
||||
dbTableKey: [required("请选择主表主键")],
|
||||
generateType: [required("请选择生成方式")],
|
||||
module: [required("请选择所属模块")],
|
||||
menuPid: [required("请选择上级目录")],
|
||||
routeName: [required("请输入路由名")],
|
||||
icon: [required("请选择图标")],
|
||||
functionName: [required("请输入功能名")],
|
||||
busName: [required("请输入业务名")],
|
||||
className: [required("请输入类名")],
|
||||
frontedPath: [required("请输入前端路径")],
|
||||
servicePosition: [required("请输入服务层代码项目")],
|
||||
serviceDictionary: [required("请输入服务层代码文件夹")],
|
||||
controllerPosition: [required("请输入接口层代码位置")],
|
||||
sortCode: [required("请选择排序")],
|
||||
formLayout: [required("请选择表单布局")],
|
||||
gridWhether: [required("请选择是否使用栅格")],
|
||||
authorName: [required("请输入作者名")],
|
||||
funcList: [required("请选择功能列表")],
|
||||
moduleType: [required("请输入模版类型")],
|
||||
dataPermission: [required("请选择数据权限")],
|
||||
treeId: [required("请选择树Id字段")],
|
||||
treePid: [required("请选择树父Id字段")],
|
||||
treeName: [required("请选择树名称字段")],
|
||||
childTable: [required("请选择关联子表的表名")],
|
||||
childFk: [required("请选择关联子表的外键")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param record 表单数据
|
||||
*/
|
||||
function onOpen(record: GenCode.GenBasic) {
|
||||
//加载默认的模块
|
||||
basicOptions.moduleOptions = userStore.moduleListGet.map(item => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.id
|
||||
};
|
||||
});
|
||||
|
||||
//获取程序集
|
||||
genBasicApi.assemblies().then(res => {
|
||||
const data = res.data;
|
||||
basicOptions.servicePositions = data.map(item => {
|
||||
return {
|
||||
value: item,
|
||||
label: item
|
||||
};
|
||||
});
|
||||
basicOptions.controllerPositions = data.map(item => {
|
||||
return {
|
||||
value: item,
|
||||
label: item
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// 获取数据库中的所有表
|
||||
genBasicApi.tables({}).then(res => {
|
||||
const data = res.data;
|
||||
basicOptions.tableOptions = data.map(item => {
|
||||
return {
|
||||
value: item.tableName,
|
||||
tableName: item.tableName,
|
||||
configId: item.configId,
|
||||
label: `${item.tableDescription}-${item.configId}-${item.entityName}`,
|
||||
entityName: item.entityName,
|
||||
tableDescription: item.tableDescription || item.entityName,
|
||||
tableColumns: item.columns
|
||||
};
|
||||
});
|
||||
if (record && Object.keys(record).length > 0) {
|
||||
basicProps.record = record;
|
||||
autoModuleType(record); //自动填写模块类型
|
||||
moduleChange(record.module, true); //自动填写上级目录
|
||||
showIcon.value = true;
|
||||
} else {
|
||||
basicProps.record = {
|
||||
sortCode: 99,
|
||||
authorName: "superAdmin",
|
||||
tablePrefix: "Y",
|
||||
servicePosition: "",
|
||||
serviceDictionary: "Services",
|
||||
controllerPosition: "",
|
||||
controllerDictionary: "Controllers/Application",
|
||||
generateType: "PRO",
|
||||
functionName: "",
|
||||
functionNameSuffix: "管理",
|
||||
formLayout: "vertical",
|
||||
gridWhether: "N",
|
||||
leftTree: "null",
|
||||
moduleType: "single",
|
||||
dataPermission: "N",
|
||||
funcList: ["curd"],
|
||||
icon: ""
|
||||
};
|
||||
showIcon.value = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模版字段填充
|
||||
* @param record 表单数据
|
||||
*/
|
||||
function autoModuleType(record) {
|
||||
//获取表信息
|
||||
const table = basicOptions.tableOptions.find(item => {
|
||||
return item.tableName === record.dbTable && item.configId == record.configId;
|
||||
});
|
||||
if (table) {
|
||||
//树ID列表
|
||||
basicOptions.columnOptions = table.tableColumns.map(item => {
|
||||
return {
|
||||
value: item.columnName,
|
||||
label: `${item.columnName}:${item.columnDescription}`
|
||||
};
|
||||
});
|
||||
//获取子表列表
|
||||
basicOptions.childTableList = basicOptions.tableOptions.filter(item => item.configId == table.configId);
|
||||
//获取子表信息
|
||||
let subTable = basicOptions.childTableList.find(item => {
|
||||
return item.tableName === record.childTable && item.configId == record.configId;
|
||||
});
|
||||
if (subTable) {
|
||||
//获取子表字段
|
||||
basicOptions.childColumnOptions = subTable.tableColumns.map(item => {
|
||||
return {
|
||||
value: item.columnName,
|
||||
label: `${item.columnName}:${item.columnDescription}`
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表前缀移除
|
||||
*
|
||||
*/
|
||||
function tablePrefixChange() {
|
||||
const tableName = basicProps.record.dbTable;
|
||||
if (tableName) {
|
||||
const tableNameHump = getTableNameToHump(tableName);
|
||||
basicProps.record.busName = tableNameHump.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表名转驼峰
|
||||
* @param tableName 表名
|
||||
*/
|
||||
function getTableNameToHump(tableName: string) {
|
||||
if (tableName) {
|
||||
const arr = tableName.toLowerCase().split("_");
|
||||
if (basicProps.record.tablePrefix === "Y") {
|
||||
arr.splice(0, 1);
|
||||
}
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
// charAt()方法得到第一个字母,slice()得到第二个字母以后的字符串
|
||||
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
|
||||
}
|
||||
return arr.join("");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表名转驼峰
|
||||
* @param tableName 表名
|
||||
*/
|
||||
function getClassName(tableName) {
|
||||
if (tableName) {
|
||||
const arr = tableName.toLowerCase().split("_");
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
// charAt()方法得到第一个字母,slice()得到第二个字母以后的字符串
|
||||
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1);
|
||||
}
|
||||
return arr.join("");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单内设置默认的值
|
||||
* @param value
|
||||
* @param options
|
||||
*/
|
||||
function formFieldAssign(value: string) {
|
||||
const option = basicOptions.tableOptions.find(item => item.value === value);
|
||||
if (option) {
|
||||
const tableNameHump = getTableNameToHump(option.tableName);
|
||||
basicProps.record.busName = tableNameHump.toLowerCase();
|
||||
basicProps.record.className = getClassName(option.tableName);
|
||||
basicProps.record.entityName = option.entityName;
|
||||
basicProps.record.configId = option.configId;
|
||||
basicOptions.columnOptions = option.tableColumns.map(item => {
|
||||
return {
|
||||
value: item.columnName,
|
||||
label: `${item.columnName}:${item.columnDescription}`
|
||||
};
|
||||
});
|
||||
basicOptions.childTableList = basicOptions.tableOptions.filter(item => {
|
||||
return item.configId == option.configId;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择子表
|
||||
* @param value
|
||||
*/
|
||||
function selectChildTable(value: string) {
|
||||
const option = basicOptions.childTableList.find(item => item.value === value);
|
||||
if (option) {
|
||||
basicProps.record.childTable = option.entityName; //子表名实体赋值
|
||||
basicOptions.childColumnOptions = option.tableColumns.map(item => {
|
||||
return {
|
||||
value: item.columnName,
|
||||
label: `${item["columnName"]}-${item.columnDescription}`
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 选择模板的回调
|
||||
function moduleChange(value: number, assign: boolean) {
|
||||
if (!assign) {
|
||||
// 先去掉值
|
||||
basicProps.record.menuPid = undefined;
|
||||
}
|
||||
userCenterApi.getAuthMenuList({ id: value }).then(res => {
|
||||
console.log("res", res.data);
|
||||
//去掉单页
|
||||
const menuTreeData = res.data.filter(item => item.category != MenuCategoryEnum.SPA);
|
||||
basicOptions.menuTreeData = menuTreeData;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并提交数据
|
||||
*/
|
||||
function onSubmit(): Promise<GenCode.GenBasic> {
|
||||
console.log("basic submit");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
basicFormRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
genBasicApi.submitForm(basicProps.record, basicProps.record.id != undefined).then(res => {
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 选中菜单事件 */
|
||||
function changeMenu(value: string) {
|
||||
basicProps.record.routeName = value;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen,
|
||||
onSubmit
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,295 @@
|
||||
<!--
|
||||
* @Description: 配置步骤
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-01-08 16:05:19
|
||||
!-->
|
||||
<template>
|
||||
<el-card :bordered="true">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
:request-auto="false"
|
||||
:columns="columns"
|
||||
:tool-button="false"
|
||||
:pagination="false"
|
||||
:request-api="genConfigApi.list"
|
||||
:init-param="initParam"
|
||||
:data-callback="dataCallback"
|
||||
>
|
||||
<template #fieldRemark="scope">
|
||||
<s-input v-model="scope.row.fieldRemark" />
|
||||
</template>
|
||||
<template #width="scope">
|
||||
<s-input v-model="scope.row.width" />
|
||||
</template>
|
||||
<template #fieldNetType="scope">
|
||||
<s-select
|
||||
v-model="scope.row.fieldNetType"
|
||||
:disabled="toCommonFieldEstimate(scope.row)"
|
||||
:options="genConst.fieldNetTypeOptions"
|
||||
filterable
|
||||
@change="fieldNetTypeChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
<template #effectType="scope">
|
||||
<s-select
|
||||
v-model="scope.row.effectType"
|
||||
:disabled="toCommonFieldEstimate(scope.row) || toFieldSelectEstimate(scope.row)"
|
||||
:options="genConst.effectTypeOptions"
|
||||
filterable
|
||||
@change="effectTypeChange(scope.row)"
|
||||
:class="{ effectType: scope.row.effectType === 'fk' }"
|
||||
/>
|
||||
<el-button
|
||||
@click="fkFormRef?.onOpen(scope.row)"
|
||||
type="primary"
|
||||
link
|
||||
v-if="scope.row.effectType === 'fk'"
|
||||
class="pl-2 ml-2"
|
||||
:icon="View"
|
||||
></el-button>
|
||||
</template>
|
||||
<template #dictTypeCode="scope">
|
||||
<s-select
|
||||
v-if="scope.row.effectType === 'radio' || scope.row.effectType === 'select' || scope.row.effectType === 'checkbox'"
|
||||
v-model="scope.row.dictTypeCode"
|
||||
:options="dictTypeCodeOptions"
|
||||
filterable
|
||||
/>
|
||||
</template>
|
||||
<template #whetherTable="scope">
|
||||
<el-checkbox v-model="scope.row.whetherTable" :value="scope.row.whetherTable" />
|
||||
</template>
|
||||
<template #whetherRetract="scope">
|
||||
<el-checkbox v-model="scope.row.whetherRetract" :value="scope.row.whetherRetract" />
|
||||
</template>
|
||||
<template #whetherResizable="scope">
|
||||
<el-checkbox v-model="scope.row.whetherResizable" :value="scope.row.whetherResizable" />
|
||||
</template>
|
||||
<template #whetherAddUpdate="scope">
|
||||
<el-checkbox v-model="scope.row.whetherAddUpdate" :disabled="toFieldEstimate(scope.row)" :value="scope.row.whetherAddUpdate" />
|
||||
</template>
|
||||
<template #whetherImportExport="scope">
|
||||
<el-checkbox v-model="scope.row.whetherImportExport" :disabled="toFieldEstimate(scope.row)" :value="scope.row.whetherAddUpdate" />
|
||||
</template>
|
||||
<template #whetherRequired="scope">
|
||||
<el-checkbox
|
||||
v-model="scope.row.whetherRequired"
|
||||
:disabled="toFieldEstimate(scope.row) || !scope.row.whetherAddUpdate"
|
||||
:value="scope.row.whetherRequired"
|
||||
/>
|
||||
</template>
|
||||
<template #queryWhether="scope">
|
||||
<el-switch v-model="scope.row.queryWhether" />
|
||||
</template>
|
||||
<template #queryType="scope">
|
||||
<s-select
|
||||
v-if="scope.row.queryWhether === true && scope.row.effectType !== 'datepicker'"
|
||||
v-model="scope.row.queryType"
|
||||
:options="genConst.queryTypeOptions"
|
||||
filterable
|
||||
/>
|
||||
<span v-else-if="scope.row.effectType === 'datepicker' && scope.row.queryWhether === true">时间段</span>
|
||||
<span v-else>无</span>
|
||||
</template>
|
||||
</ProTable>
|
||||
</el-card>
|
||||
<FkForm ref="fkFormRef" @successful="proTable?.refresh()" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { GenCode, genConfigApi } from "@/api";
|
||||
import * as genConst from "../genConst";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import FkForm from "./fkForm.vue";
|
||||
import { View } from "@element-plus/icons-vue";
|
||||
const dictStore = useDictStore();
|
||||
// 初始化参数
|
||||
const initParam = reactive<{ basicId: string | number }>({ basicId: 0 });
|
||||
const dictTypeCodeOptions = dictStore.getDictAll();
|
||||
const fkFormRef = ref<InstanceType<typeof FkForm> | null>(null);
|
||||
|
||||
// 获取 ProTable 元素
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<GenCode.GenConfig>[] = [
|
||||
{ type: "sort", label: "排序", width: 60 },
|
||||
{ prop: "fieldName", label: "字段" },
|
||||
{ prop: "fieldRemark", label: "注释" },
|
||||
{ prop: "fieldType", label: "类型", width: 100 },
|
||||
{ prop: "fieldNetType", label: "实体类型", width: 150 },
|
||||
{ prop: "effectType", label: "作用类型" },
|
||||
{ prop: "dictTypeCode", label: "字典", width: 140 },
|
||||
{ prop: "width", label: "列宽", width: 80 },
|
||||
{ prop: "whetherTable", label: "列表", width: 60 },
|
||||
{ prop: "whetherRetract", label: "列省略", width: 60 },
|
||||
{ prop: "whetherResizable", label: "可伸缩列", width: 60 },
|
||||
{ prop: "whetherAddUpdate", label: "增改", width: 60 },
|
||||
{ prop: "whetherImportExport", label: "导入导出", width: 60 },
|
||||
{ prop: "whetherRequired", label: "必填", width: 60 },
|
||||
{ prop: "queryWhether", label: "查询", width: 80 },
|
||||
{ prop: "queryType", label: "查询方式", width: 150, fixed: "right" }
|
||||
];
|
||||
|
||||
/**
|
||||
* 打开配置步骤
|
||||
* @param id 基础信息id
|
||||
*/
|
||||
function onOpen(id: number | string) {
|
||||
initParam.basicId = id;
|
||||
}
|
||||
|
||||
// dataCallback 是对于返回的表格数据做处理
|
||||
function dataCallback(data: any) {
|
||||
data.forEach(item => {
|
||||
for (const key in item) {
|
||||
if (item[key] === "Y") {
|
||||
item[key] = true;
|
||||
}
|
||||
if (item[key] === "N") {
|
||||
item[key] = false;
|
||||
}
|
||||
}
|
||||
// 去掉删除标识
|
||||
// 如果是主键,我们不提供主键的配置,也用不到
|
||||
if (
|
||||
item.isPrimaryKey ||
|
||||
item.fieldName.toLowerCase().includes("isdelete") ||
|
||||
item.fieldName.toLowerCase().includes("createuserid") ||
|
||||
item.fieldName.toLowerCase().includes("updateuserid")
|
||||
) {
|
||||
data = data.filter(table => {
|
||||
return table.fieldName != item.fieldName;
|
||||
});
|
||||
}
|
||||
// 让默认的变成设置的
|
||||
fieldNetTypeChange(item);
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
*通用字段是否可选
|
||||
* @param record
|
||||
*/
|
||||
function toCommonFieldEstimate(record: GenCode.GenConfig) {
|
||||
if (record.fieldName.toLowerCase().includes("createuser") || record.fieldName.toLowerCase().includes("updateuser")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param record 通用字段是否可选
|
||||
*/
|
||||
function toFieldSelectEstimate(record) {
|
||||
if (record.fieldNetType === "DateTime" && record.effectType === "datepicker") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param record 改变作用类型
|
||||
* @param record
|
||||
*/
|
||||
function effectTypeChange(record: GenCode.GenConfig) {
|
||||
console.log("value", record);
|
||||
if (record.effectType === "fk") {
|
||||
console.log("value", record);
|
||||
|
||||
fkFormRef.value?.onOpen(record);
|
||||
} else {
|
||||
record.fkEntityName = "";
|
||||
record.fkColumnName = "";
|
||||
record.fkColumnId = "";
|
||||
}
|
||||
console.log(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体类型选择触发
|
||||
*/
|
||||
function fieldNetTypeChange(record: GenCode.GenConfig) {
|
||||
if (record.fieldNetType === "DateTime") {
|
||||
record.effectType = "datepicker";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用字段是否可选
|
||||
* @param data
|
||||
*/
|
||||
function toFieldEstimate(data: GenCode.GenConfig) {
|
||||
if (
|
||||
data.fieldName.toLowerCase().includes("createuser") ||
|
||||
data.fieldName.toLowerCase().includes("createtime") ||
|
||||
data.fieldName.toLowerCase().includes("updateuser") ||
|
||||
data.fieldName.toLowerCase().includes("updatetime") ||
|
||||
data.fieldName.toLowerCase().includes("isdelete") ||
|
||||
data.fieldName.toLowerCase().includes("createuserid") ||
|
||||
data.fieldName.toLowerCase().includes("updateuserid") ||
|
||||
data.isPrimaryKey === "true"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并提交数据
|
||||
*/
|
||||
function onSubmit(): Promise<any> {
|
||||
let data = proTable.value?.tableData as GenCode.GenConfig[];
|
||||
let error = false;
|
||||
data.forEach(item => {
|
||||
// 必填那一项转换
|
||||
for (const key in item) {
|
||||
//不转isdeleste
|
||||
if (key != "isDelete") {
|
||||
if (item[key] === true) {
|
||||
item[key] = "Y";
|
||||
}
|
||||
if (item[key] === false) {
|
||||
item[key] = "N";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.queryWhether === "Y" && !item.queryType) {
|
||||
// 排除掉时间选择
|
||||
if (item.fieldNetType !== "DateTime" && item.effectType !== "checkbox" && item.fieldNetType !== "Date") {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
if ((item.effectType === "select" || item.effectType === "radio" || item.effectType === "checkbox") && !item.dictTypeCode) {
|
||||
error = true;
|
||||
}
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
if (error) {
|
||||
reject("校验失败,请选择对应的下拉框选项");
|
||||
return;
|
||||
}
|
||||
genConfigApi
|
||||
.editBatch(data)
|
||||
.then(res => {
|
||||
resolve(res.data);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({ onOpen, onSubmit });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.effectType {
|
||||
width: 75%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
<!--
|
||||
* @Description: 外键关系配置
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-02-05 11:02:14
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" title="外键关系配置" form-size="600px">
|
||||
<el-form ref="fkFormRef" :model="fkProps.fkInfo" :rules="rules" label-width="auto" label-suffix=" :">
|
||||
<s-form-item label="选择外键表" prop="fkEntityName">
|
||||
<s-select v-model="fkProps.fkInfo.fkEntityName" :options="fkProps.tableOptions" filterable @change="formFieldAssign" />
|
||||
</s-form-item>
|
||||
<s-form-item label="外键ID" prop="fkColumnId">
|
||||
<s-select v-model="fkProps.fkInfo.fkColumnId" :options="fkProps.columnOptions" filterable />
|
||||
</s-form-item>
|
||||
<s-form-item label="显示字段" prop="fkColumnName">
|
||||
<s-select v-model="fkProps.fkInfo.fkColumnName" :options="fkProps.columnOptions" filterable />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button type="primary" @click="onSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GenCode, genBasicApi } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus";
|
||||
const fkFormRef = ref<FormInstance>();
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
// 选项接口
|
||||
interface FkProps {
|
||||
/*数据表列表 */
|
||||
tableOptions: GenCode.TableList[];
|
||||
/*字段选项 */
|
||||
columnOptions: { label: string; value: string }[];
|
||||
/*当前行数据 */
|
||||
fkInfo: GenCode.GenConfig;
|
||||
}
|
||||
|
||||
// 表单参数
|
||||
const fkProps: FkProps = reactive({
|
||||
tableOptions: [],
|
||||
columnOptions: [],
|
||||
fkInfo: {} as GenCode.GenConfig
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
fkEntityName: [required("请选择外键表")],
|
||||
fkColumnId: [required("请选择外键Id")],
|
||||
fkColumnName: [required("请选择外键字段")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param genConfig 表单参数
|
||||
*/
|
||||
function onOpen(genConfig: GenCode.GenConfig) {
|
||||
visible.value = true; //显示抽屉
|
||||
console.log("genConfig", genConfig);
|
||||
fkProps.fkInfo = genConfig;
|
||||
// 获取数据库中的所有表
|
||||
genBasicApi.tables({ isAll: true }).then(res => {
|
||||
const data = res.data;
|
||||
fkProps.tableOptions = data.map(item => {
|
||||
return {
|
||||
value: item.tableName,
|
||||
tableName: item.tableName,
|
||||
configId: item.configId,
|
||||
label: `${item.tableDescription}-${item.configId}-${item.entityName}`,
|
||||
entityName: item.entityName,
|
||||
tableDescription: item.tableDescription || item.entityName,
|
||||
tableColumns: item.columns
|
||||
};
|
||||
});
|
||||
const option = fkProps.tableOptions.find(item => item.value === genConfig.fkEntityName);
|
||||
if (option) {
|
||||
fkProps.columnOptions = option.tableColumns.map(item => {
|
||||
return {
|
||||
value: item.columnName,
|
||||
label: `${item.columnName}:${item.columnDescription}`
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单内设置默认的值
|
||||
* @param value
|
||||
* @param options
|
||||
*/
|
||||
function formFieldAssign(value: string) {
|
||||
fkProps.fkInfo.fkColumnName = "";
|
||||
fkProps.fkInfo.fkColumnId = "";
|
||||
const option = fkProps.tableOptions.find(item => item.value === value);
|
||||
if (option) {
|
||||
fkProps.columnOptions = option.tableColumns.map(item => {
|
||||
return {
|
||||
value: item.columnName,
|
||||
label: `${item.columnName}:${item.columnDescription}`
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
/** 提交表单*/
|
||||
function onSubmit() {
|
||||
fkFormRef.value?.validate(valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
onClose(); //关闭表单
|
||||
});
|
||||
}
|
||||
defineExpose({ onOpen });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!--
|
||||
* @Description: 预览
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-02-10 10:52:26
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" title="代码生成预览" form-size="1200px" destroy-on-close>
|
||||
<el-tabs v-model="previewProps.codeTypeActiveKey" class="-mt-4" @tab-change="onTabChange">
|
||||
<el-tab-pane
|
||||
v-for="codeType in previewProps.codeTypeArray"
|
||||
:key="codeType.codeTypeKey"
|
||||
:name="codeType.codeTypeKey"
|
||||
:label="codeType.codeTypeTitle"
|
||||
>
|
||||
<div class="tabs-wrapper">
|
||||
<el-tabs v-model="previewProps.codeListActiveKey" tab-position="left" type="card" style="height: 100%">
|
||||
<el-tab-pane v-for="pan in codeType.codeTypeList" :key="pan.codeFileName" :name="pan.codeFileName" :label="pan.codeFileName">
|
||||
<div style="height: calc(100vh - 160px); overflow: auto">
|
||||
<el-input v-model="pan.codeFileContent" type="textarea" autosize />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GenCode, genBasicApi } from "@/api";
|
||||
import { TabPaneName } from "element-plus";
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 选项接口
|
||||
interface PreviewProps {
|
||||
/*代码类型选项 */
|
||||
codeTypeActiveKey: string;
|
||||
/*代码文件选项 */
|
||||
codeListActiveKey: string;
|
||||
/*代码预览数组 */
|
||||
codeTypeArray: { codeTypeKey: string; codeTypeTitle: string; codeTypeList: GenCode.GenBaseCodeResult[] }[];
|
||||
}
|
||||
|
||||
// 表单参数
|
||||
const previewProps: PreviewProps = reactive({
|
||||
codeTypeArray: [],
|
||||
codeTypeActiveKey: "",
|
||||
codeListActiveKey: ""
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param genConfig 表单参数
|
||||
*/
|
||||
function onOpen(basic: GenCode.GenBasic) {
|
||||
visible.value = true; //显示抽屉
|
||||
console.log("basic", basic);
|
||||
|
||||
genBasicApi.previewGen({ id: basic.id }).then(res => {
|
||||
if (res.data) {
|
||||
previewProps.codeTypeArray = [
|
||||
{
|
||||
codeTypeKey: "frontend",
|
||||
codeTypeTitle: "前端代码",
|
||||
codeTypeList: res.data.codeFrontendResults
|
||||
},
|
||||
{
|
||||
codeTypeKey: "backend",
|
||||
codeTypeTitle: "后端代码",
|
||||
codeTypeList: res.data.codeBackendResults
|
||||
},
|
||||
{
|
||||
codeTypeKey: "sqlend",
|
||||
codeTypeTitle: "SQL文件",
|
||||
codeTypeList: res.data.sqlResults
|
||||
}
|
||||
];
|
||||
previewProps.codeTypeActiveKey = previewProps.codeTypeArray[0].codeTypeKey; //默认选中第一个
|
||||
previewProps.codeListActiveKey = previewProps.codeTypeArray[0].codeTypeList[0].codeFileName; //默认选中第一个
|
||||
} else {
|
||||
ElMessage.warning("预览失败:请检查问题");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* tab切换
|
||||
* @param key 选中的key
|
||||
*/
|
||||
function onTabChange(key: TabPaneName) {
|
||||
previewProps.codeListActiveKey = previewProps.codeTypeArray.find(f => f.codeTypeKey === key)?.codeTypeList[0]?.codeFileName || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭表单
|
||||
*/
|
||||
function onClose() {
|
||||
visible.value = false; //关闭抽屉
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tabs-wrapper {
|
||||
height: calc(100vh - 260px);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,133 @@
|
||||
<!--
|
||||
* @Description: 代码生成步骤
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-01-08 15:00:51
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="steps-card" :bordered="false">
|
||||
<el-row class="xn-row">
|
||||
<el-col :span="6"></el-col>
|
||||
<el-col :span="12">
|
||||
<el-steps align-center finish-status="success" :active="active">
|
||||
<el-step v-for="item in steps" :key="item.title" :title="item.title" />
|
||||
</el-steps>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div style="float: right">
|
||||
<el-button :disabled="active === 0" style="margin-right: 8px" @click="prev"> 上一步 </el-button>
|
||||
<el-button :disabled="active === 2" type="primary" style="margin-left: 8px" @click="next"> 继续 </el-button>
|
||||
<el-button type="primary" danger ghost style="margin-left: 8px" @click="$emit('closed')"> 关闭 </el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<div v-if="active === 0">
|
||||
<Basic ref="basicRef" />
|
||||
</div>
|
||||
<div v-if="active === 1">
|
||||
<Config ref="configRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GenCode } from "@/api";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import Basic from "./basic.vue";
|
||||
import Config from "./config.vue";
|
||||
|
||||
const active = ref(0);
|
||||
const emit = defineEmits(["closed"]);
|
||||
const recordData = ref({}); //记录数据
|
||||
// 分布步骤数据
|
||||
const steps = [
|
||||
{
|
||||
title: "基础信息",
|
||||
content: "基础信息"
|
||||
},
|
||||
{
|
||||
title: "详细配置",
|
||||
content: "详细配置"
|
||||
}
|
||||
];
|
||||
|
||||
// 表单参数
|
||||
reactive<FormProps.Base<GenCode.GenBasic>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 获取组件实例
|
||||
const basicRef = ref<InstanceType<typeof Basic> | null>(null);
|
||||
const configRef = ref<InstanceType<typeof Config> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<GenCode.GenBasic>) {
|
||||
// console.log("record", props.record);
|
||||
// Object.assign(genBasicProps, props); //合并参数
|
||||
basicRef.value?.onOpen(props.record as GenCode.GenBasic);
|
||||
}
|
||||
|
||||
/**下一步**/
|
||||
function next() {
|
||||
// active.value++;
|
||||
console.log("active.value", active.value);
|
||||
// 判断是哪一步
|
||||
if (active.value === 0) {
|
||||
basicRef.value?.onSubmit().then(data => {
|
||||
recordData.value = data;
|
||||
active.value++;
|
||||
nextTick(() => {
|
||||
configRef.value?.onOpen(data.id);
|
||||
});
|
||||
});
|
||||
// active.value--;
|
||||
}
|
||||
if (active.value === 1) {
|
||||
configRef.value?.onSubmit().then(() => {
|
||||
active.value++;
|
||||
nextTick(() => {
|
||||
// emit("closed");
|
||||
//关闭页面
|
||||
emit("closed");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**上一步**/
|
||||
function prev() {
|
||||
active.value--;
|
||||
if (active.value === 0) {
|
||||
nextTick(() => {
|
||||
basicRef.value!.onOpen(recordData.value as GenCode.GenBasic);
|
||||
});
|
||||
}
|
||||
if (active.value === 1) {
|
||||
nextTick(() => {
|
||||
// configRef.value.onOpen(recordData.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.steps-card {
|
||||
padding-top: -10px;
|
||||
margin: -12px -12px 10px;
|
||||
}
|
||||
.gen-row {
|
||||
margin-top: -10px;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,262 @@
|
||||
//生成选项
|
||||
export const execTypeOptions = [
|
||||
{
|
||||
label: "全部",
|
||||
value: "ALL"
|
||||
},
|
||||
{
|
||||
label: "前端",
|
||||
value: "FRONTEND"
|
||||
},
|
||||
{
|
||||
label: "后端",
|
||||
value: "BACKEND"
|
||||
}
|
||||
];
|
||||
|
||||
//生成方式
|
||||
export const generateTypeOptions = [
|
||||
{
|
||||
label: "项目内",
|
||||
value: "PRO"
|
||||
},
|
||||
{
|
||||
label: "压缩包",
|
||||
value: "ZIP"
|
||||
}
|
||||
];
|
||||
//表前缀
|
||||
export const tablePrefixOptions = [
|
||||
{
|
||||
label: "移除",
|
||||
value: "Y"
|
||||
},
|
||||
{
|
||||
label: "不移除",
|
||||
value: "N"
|
||||
}
|
||||
];
|
||||
//表单布局
|
||||
export const formLayoutOptions = [
|
||||
{
|
||||
label: "垂直",
|
||||
value: "vertical"
|
||||
},
|
||||
{
|
||||
label: "水平",
|
||||
value: "horizontal"
|
||||
}
|
||||
];
|
||||
//使用格栅
|
||||
export const gridWhetherOptions = [
|
||||
{
|
||||
label: "栅格布局",
|
||||
value: "Y"
|
||||
},
|
||||
{
|
||||
label: "不使用",
|
||||
value: "N"
|
||||
}
|
||||
];
|
||||
|
||||
//数据权限
|
||||
export const dataPermissionOptions = [
|
||||
{
|
||||
label: "有",
|
||||
value: "Y"
|
||||
},
|
||||
{
|
||||
label: "无",
|
||||
value: "N"
|
||||
}
|
||||
];
|
||||
//生成模版
|
||||
export const moduleTypeOptions = [
|
||||
{
|
||||
label: "单表",
|
||||
value: "single"
|
||||
},
|
||||
{
|
||||
label: "树表-列表型",
|
||||
value: "treeList"
|
||||
},
|
||||
{
|
||||
label: "树表-树型",
|
||||
value: "treeTree"
|
||||
},
|
||||
{
|
||||
label: "主子表",
|
||||
value: "masterChild"
|
||||
}
|
||||
];
|
||||
//左侧树
|
||||
export const treeOptions = [
|
||||
{
|
||||
label: "不使用",
|
||||
value: "null"
|
||||
},
|
||||
{
|
||||
label: "组织树",
|
||||
value: "org"
|
||||
}
|
||||
];
|
||||
|
||||
//功能列表
|
||||
export const funcOptions = [
|
||||
{
|
||||
label: "增删改查",
|
||||
value: "curd"
|
||||
}
|
||||
// {
|
||||
// label: "批量删除",
|
||||
// value: "batchDelete"
|
||||
// },
|
||||
// {
|
||||
// label: "导入",
|
||||
// value: "import"
|
||||
// },
|
||||
// {
|
||||
// label: "导出",
|
||||
// value: "export"
|
||||
// },
|
||||
// {
|
||||
// label: "批量编辑",
|
||||
// value: "batchEdit"
|
||||
// }
|
||||
];
|
||||
|
||||
//作用类型
|
||||
export const effectTypeOptions = [
|
||||
{
|
||||
label: "输入框",
|
||||
value: "input"
|
||||
},
|
||||
{
|
||||
label: "文本框",
|
||||
value: "textarea"
|
||||
},
|
||||
{
|
||||
label: "下拉框",
|
||||
value: "select"
|
||||
},
|
||||
{
|
||||
label: "单选框",
|
||||
value: "radio"
|
||||
},
|
||||
{
|
||||
label: "复选框",
|
||||
value: "checkbox"
|
||||
},
|
||||
{
|
||||
label: "日期选择器",
|
||||
value: "datepicker"
|
||||
},
|
||||
{
|
||||
label: "时间选择器",
|
||||
value: "timepicker"
|
||||
},
|
||||
{
|
||||
label: "数字输入框",
|
||||
value: "inputNumber"
|
||||
},
|
||||
{
|
||||
label: "滑动数字条",
|
||||
value: "slider"
|
||||
},
|
||||
{
|
||||
label: "图片",
|
||||
value: "image"
|
||||
},
|
||||
{
|
||||
label: "外键",
|
||||
value: "fk"
|
||||
}
|
||||
];
|
||||
|
||||
// 实体类型
|
||||
export const fieldNetTypeOptions = [
|
||||
{
|
||||
label: "Byte",
|
||||
value: "byte"
|
||||
},
|
||||
{
|
||||
label: "Int",
|
||||
value: "int"
|
||||
},
|
||||
{
|
||||
label: "Long",
|
||||
value: "long"
|
||||
},
|
||||
{
|
||||
label: "String",
|
||||
value: "string"
|
||||
},
|
||||
{
|
||||
label: "Bool",
|
||||
value: "bool"
|
||||
},
|
||||
{
|
||||
label: "Decimal",
|
||||
value: "decimal"
|
||||
},
|
||||
{
|
||||
label: "Double",
|
||||
value: "double"
|
||||
},
|
||||
{
|
||||
label: "Date",
|
||||
value: "Date"
|
||||
},
|
||||
{
|
||||
label: "DateTime",
|
||||
value: "DateTime"
|
||||
},
|
||||
{
|
||||
label: "Single",
|
||||
value: "Single"
|
||||
},
|
||||
{
|
||||
label: "Byte[]",
|
||||
value: "byte[]"
|
||||
},
|
||||
{
|
||||
label: "Guid",
|
||||
value: "Guid"
|
||||
}
|
||||
];
|
||||
|
||||
// 查询方式
|
||||
export const queryTypeOptions = [
|
||||
{
|
||||
label: "模糊包含",
|
||||
value: "like"
|
||||
},
|
||||
{
|
||||
label: "模糊不包含",
|
||||
value: "notLike"
|
||||
},
|
||||
{
|
||||
label: "等于",
|
||||
value: "eq"
|
||||
},
|
||||
{
|
||||
label: "不等于",
|
||||
value: "ne"
|
||||
},
|
||||
{
|
||||
label: "大于",
|
||||
value: "gt"
|
||||
},
|
||||
{
|
||||
label: "大于等于",
|
||||
value: "ge"
|
||||
},
|
||||
{
|
||||
label: "小于",
|
||||
value: "lt"
|
||||
},
|
||||
{
|
||||
label: "小于等于",
|
||||
value: "le"
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,195 @@
|
||||
<!--
|
||||
* @Description: 代码生成器
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-01-07 16:24:17
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable v-if="indexShow" ref="proTable" title="代码生成器列表" :columns="columns" :request-api="genBasicApi.page">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选代码生成')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表格 状态 -->
|
||||
<template #status="scope">
|
||||
<el-space wrap>
|
||||
<el-tag v-if="scope.row.status === MessageStatusDictEnum.ALREADY" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_STATUS, MessageStatusDictEnum.ALREADY)
|
||||
}}</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === MessageStatusDictEnum.READY" type="info">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_STATUS, MessageStatusDictEnum.READY)
|
||||
}}</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 表格 类型 -->
|
||||
<template #category="scope">
|
||||
<el-space wrap>
|
||||
<el-tag v-if="scope.row.category === MessageTypeDictEnum.INFORM" type="danger">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_CATEGORY, MessageTypeDictEnum.INFORM)
|
||||
}}</el-tag>
|
||||
<el-tag v-else-if="scope.row.category === MessageTypeDictEnum.MESSAGE" type="primary">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_CATEGORY, MessageTypeDictEnum.MESSAGE)
|
||||
}}</el-tag>
|
||||
<el-tag v-else-if="scope.row.category === MessageTypeDictEnum.NOTICE" type="warning">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_CATEGORY, MessageTypeDictEnum.NOTICE)
|
||||
}}</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.VIEW" @click="previewRef?.onOpen(scope.row)">预览</s-button>
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)">配置</s-button>
|
||||
<s-button link :icon="VideoPlay" @click="choseExecGen(scope.row)">生成</s-button>
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.dbTable}】信息`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<Steps v-else ref="stepsRef" @closed="closeStep" />
|
||||
<Preview ref="previewRef" />
|
||||
<el-dialog v-model="dialogVisible" title="生成选项" width="400">
|
||||
<s-radio-group v-model="execType" :options="genConst.execTypeOptions" size="large"></s-radio-group>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="execGen">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="genCode">
|
||||
import { VideoPlay } from "@element-plus/icons-vue";
|
||||
import { genBasicApi, GenCode } from "@/api";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { FormOptEnum, SysDictEnum, MessageStatusDictEnum, MessageTypeDictEnum } from "@/enums";
|
||||
import * as genConst from "./genConst";
|
||||
import Steps from "./components/steps.vue";
|
||||
import Preview from "./components/preview.vue";
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
const dictStore = useDictStore();
|
||||
const indexShow = ref(true);
|
||||
const previewRef = ref<InstanceType<typeof Preview> | null>(null); // 预览表单引用
|
||||
const dialogVisible = ref(false);
|
||||
const execType = ref(genConst.execTypeOptions[0].value);
|
||||
const execRecord = ref(); //要生成的id
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<GenCode.GenBasic>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "configId", label: "库名" },
|
||||
{ prop: "dbTable", label: "表名" },
|
||||
{ prop: "busName", label: "业务名" },
|
||||
{ prop: "functionName", label: "功能名" },
|
||||
{ prop: "className", label: "类名" },
|
||||
{ prop: "generateType", label: "生成方式", enum: genConst.generateTypeOptions },
|
||||
{ prop: "moduleType", label: "生成模版", enum: genConst.moduleTypeOptions },
|
||||
{ prop: "authorName", label: "作者" },
|
||||
{ prop: "operation", label: "操作", width: 280, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const stepsRef = ref<InstanceType<typeof Steps> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | GenCode.GenBasic = {}) {
|
||||
indexShow.value = false;
|
||||
nextTick(() => {
|
||||
stepsRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭步骤
|
||||
*/
|
||||
function closeStep() {
|
||||
indexShow.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(genBasicApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
|
||||
//生成方式
|
||||
// function generateTypeFilter(text: string) {
|
||||
// const array = genConst.generateTypeOptions;
|
||||
// const foundItem = array.find(f => f.value === text);
|
||||
// if (foundItem) {
|
||||
// return foundItem.label;
|
||||
// } else {
|
||||
// // 返回一个默认值或者抛出一个错误
|
||||
// return "未知";
|
||||
// }
|
||||
// }
|
||||
//选择生成前端还是后端
|
||||
function choseExecGen(record: GenCode.GenBasic) {
|
||||
dialogVisible.value = true;
|
||||
execRecord.value = record;
|
||||
}
|
||||
|
||||
/**执行代码生成 */
|
||||
function execGen() {
|
||||
console.log("execType", execType);
|
||||
const param = {
|
||||
id: execRecord.value.id,
|
||||
execType: execType.value
|
||||
};
|
||||
if (execRecord.value.generateType === "PRO") {
|
||||
genBasicApi.execGenPro(param).then(() => {
|
||||
RefreshTable();
|
||||
});
|
||||
} else {
|
||||
// 下载压缩包
|
||||
genBasicApi.execGenZip(param).then(res => {
|
||||
console.log("[ res ] >", res);
|
||||
let data = res as any;
|
||||
const blob = new Blob([data.data], { type: "application/octet-stream;charset=UTF-8" });
|
||||
const contentDisposition = data.headers["content-disposition"];
|
||||
const patt = new RegExp("filename=([^;]+\\.[^\\.;]+);*");
|
||||
const $link = document.createElement("a");
|
||||
$link.href = URL.createObjectURL(blob);
|
||||
const match = patt.exec(contentDisposition);
|
||||
if (match) {
|
||||
$link.download = decodeURIComponent(match[1]);
|
||||
}
|
||||
$link.click();
|
||||
document.body.appendChild($link);
|
||||
document.body.removeChild($link); // 下载完成移除元素
|
||||
window.URL.revokeObjectURL($link.href); // 释放掉blob对象
|
||||
});
|
||||
}
|
||||
execCancel();
|
||||
}
|
||||
|
||||
//取消生成
|
||||
function execCancel() {
|
||||
dialogVisible.value = false;
|
||||
execType.value = genConst.execTypeOptions[0].value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<!--
|
||||
* @Description: 站内信详情
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-11-11 09:22:49
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" title="站内信详情" form-size="700px">
|
||||
<h4 class="title">基本信息</h4>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="主题" label-align="left">
|
||||
{{ messageProps.record.subject }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="消息类型" label-align="left">
|
||||
{{ dictStore.dictTranslation(SysDictEnum.MESSAGE_CATEGORY, messageProps.record.category!) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发送方式" label-align="left">
|
||||
{{ dictStore.dictTranslation(SysDictEnum.MESSAGE_WAY, messageProps.record.sendWay!) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" label-align="left">
|
||||
{{ messageProps.record.createTime }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发送时间" label-align="left">
|
||||
{{ messageProps.record.sendTime }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态" label-align="left">
|
||||
{{ dictStore.dictTranslation(SysDictEnum.MESSAGE_STATUS, messageProps.record.status!) }}
|
||||
</el-descriptions-item>
|
||||
<!-- <el-descriptions-item label="正文" class-name="my-class" label-align="left" label-class-name="my-label">
|
||||
{{ messageProps.record.content }}
|
||||
</el-descriptions-item> -->
|
||||
</el-descriptions>
|
||||
<h4 class="title">正文:</h4>
|
||||
<div v-html="messageProps.record.content"></div>
|
||||
<h4 class="title">接收详情:</h4>
|
||||
<ProTable
|
||||
v-if="getReadData"
|
||||
ref="proTable"
|
||||
title="接收列表"
|
||||
class="table"
|
||||
:columns="columns"
|
||||
:pagination="true"
|
||||
:data="messageProps.record.receiverDetail"
|
||||
:tool-button="false"
|
||||
>
|
||||
<!-- 表格 类型 -->
|
||||
<template #read="scope">
|
||||
<el-space wrap>
|
||||
<el-tag v-if="scope.row.read === true" type="info">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.read === false" type="danger">否</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
</ProTable>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="onClose"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysMessage, messageApi } from "@/api";
|
||||
import { FormOptEnum, SysDictEnum } from "@/enums";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore();
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 是否
|
||||
const yesNoOptions = dictStore.getDictList(SysDictEnum.YES_NO, true);
|
||||
|
||||
const getReadData = ref<boolean>(false); //是否获取了接收详情数据
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<any>[] = [
|
||||
{ prop: "name", label: "姓名" },
|
||||
{ prop: "read", label: "是否已读", enum: yesNoOptions }
|
||||
];
|
||||
|
||||
// 表单参数
|
||||
const messageProps = reactive<FormProps.Base<SysMessage.SysMessageInfo>>({
|
||||
opt: FormOptEnum.VIEW,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<SysMessage.SysMessageInfo>) {
|
||||
console.log("[ messageProps.record.id ] >", messageProps.record.id);
|
||||
Object.assign(messageProps, props); //合并参数
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
messageApi.detail({ id: props.record.id, showReceiveInfo: true }).then(res => {
|
||||
messageProps.record = res.data;
|
||||
getReadData.value = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按钮分页数据
|
||||
* 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
* 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
|
||||
*/
|
||||
// function getPage(params: any) {
|
||||
// let newParams = JSON.parse(JSON.stringify(params)); //转换成json字符串再转换成json对象
|
||||
// newParams.id = messageProps.record.id; //按钮父Id
|
||||
// console.log("[ initParam ] >", initParam.value);
|
||||
// return messageApi.readDetail(newParams);
|
||||
// }
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,242 @@
|
||||
<!--
|
||||
* @Description: 单页管理表单页面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:59
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${messageProps.opt}站内信`" form-size="700px">
|
||||
<el-form
|
||||
ref="messageFormRef"
|
||||
:rules="rules"
|
||||
:disabled="messageProps.disabled"
|
||||
:model="messageProps.record"
|
||||
:hide-required-asterisk="messageProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="消息主题" prop="subject">
|
||||
<s-input v-model="messageProps.record.subject"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="消息类型" prop="category">
|
||||
<s-select v-model="messageProps.record.category" :options="messageTypeOptions" />
|
||||
</s-form-item>
|
||||
<s-form-item label="正文内容" prop="content">
|
||||
<s-input v-model="messageProps.record.content" type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" />
|
||||
</s-form-item>
|
||||
<s-form-item label="发送方式" prop="sendWay">
|
||||
<s-radio-group v-model="messageProps.record.sendWay" :options="sendWayOptions" button @change="handleSendWayChange" />
|
||||
</s-form-item>
|
||||
<s-form-item v-if="messageProps.record.sendWay === MessageSendWayDictEnum.DELAY" label="延迟时间" prop="delayTime">
|
||||
<el-input v-model="messageProps.record.delayTime">
|
||||
<template #append>秒</template>
|
||||
</el-input>
|
||||
</s-form-item>
|
||||
<s-form-item v-if="messageProps.record.sendWay === MessageSendWayDictEnum.SCHEDULE" label="指定时间" prop="sendTime">
|
||||
<date-picker v-model="messageProps.record.sendTime" :show-shortcuts="false" :disabled-date-before="true" type="datetime" />
|
||||
</s-form-item>
|
||||
<s-form-item label="接收人" prop="receiverType">
|
||||
<!-- 全部和指定 -->
|
||||
<s-radio-group v-model="messageProps.record.receiverType" :options="receiverTypeOptions" button @change="handleReceiverTypeChange" />
|
||||
</s-form-item>
|
||||
<s-form-item v-if="messageProps.record.receiverType !== MessageReceiverTypeDictEnum.ALL" :label="receiverType" prop="receiverInfo">
|
||||
<el-button link type="primary" @click="showSelector">选择</el-button>
|
||||
<el-tag v-for="item in messageProps.record.receiverInfo" :key="item.id" class="ml-3px" closable @close="removeTag(item)">{{
|
||||
item.name
|
||||
}}</el-tag>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!messageProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
<!-- 用户选择器 -->
|
||||
<user-selector
|
||||
ref="userSelectorRef"
|
||||
multiple
|
||||
:org-tree-api="sysOrgApi.tree"
|
||||
:position-tree-api="sysPositionApi.tree"
|
||||
:role-tree-api="sysRoleApi.tree"
|
||||
:user-selector-api="sysUserApi.selector"
|
||||
@successful="handleChooseUser"
|
||||
/>
|
||||
<!-- 角色选择器 -->
|
||||
<role-selector
|
||||
ref="roleSelectorRef"
|
||||
multiple
|
||||
:org-tree-api="sysOrgApi.tree"
|
||||
:role-selector-api="sysRoleApi.roleSelector"
|
||||
@successful="handleChooseRole"
|
||||
></role-selector>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysMessage, messageApi, sysOrgApi, sysPositionApi, sysRoleApi, sysUserApi, SysUser, SysRole } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum, MessageTypeDictEnum, SysDictEnum, MessageReceiverTypeDictEnum, MessageSendWayDictEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";
|
||||
import { RoleSelectorInstance } from "@/components/Selectors/RoleSelector/interface";
|
||||
|
||||
const dictStore = useDictStore();
|
||||
const visible = ref(false); //是否显示表单
|
||||
// 消息类型选项
|
||||
const messageTypeOptions = dictStore.getDictList(SysDictEnum.MESSAGE_CATEGORY);
|
||||
|
||||
// 消息接受者类型选项
|
||||
const receiverTypeOptions = dictStore.getDictList(SysDictEnum.RECEIVER_TYPE);
|
||||
|
||||
// 发送方式选项
|
||||
const sendWayOptions = dictStore.getDictList(SysDictEnum.MESSAGE_WAY);
|
||||
|
||||
//根据接收人类型,计算出显示角色还是用户
|
||||
const receiverType = computed(() => {
|
||||
return messageProps.record.receiverType == MessageReceiverTypeDictEnum.ROLE ? "角色" : "用户";
|
||||
});
|
||||
|
||||
// 表单参数
|
||||
const messageProps = reactive<FormProps.Base<SysMessage.SysMessageInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
subject: [required("请输入消息主题")],
|
||||
category: [required("请输入消息类型")],
|
||||
content: [required("请输入正文内容")],
|
||||
receiverType: [required("请选择接收人")],
|
||||
sendWay: [required("请选择发送方式")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<SysMessage.SysMessageInfo>) {
|
||||
Object.assign(messageProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
messageProps.record.category = MessageTypeDictEnum.INFORM;
|
||||
messageProps.record.receiverType = MessageReceiverTypeDictEnum.ALL;
|
||||
messageProps.record.receiverInfo = [] as SysUser.SysUserInfo[] | SysRole.SysRoleInfo[];
|
||||
messageProps.record.sendWay = MessageSendWayDictEnum.NOW;
|
||||
messageProps.record.read = false;
|
||||
}
|
||||
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
messageApi.detail({ id: props.record.id }).then(res => {
|
||||
messageProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const messageFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
messageFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//如果接收人类型是指定,但是没有选择接收人,提示用户选择接收人
|
||||
if (messageProps.record.receiverType != MessageReceiverTypeDictEnum.ALL && messageProps.record.receiverInfo!.length == 0) {
|
||||
ElMessage("请选择接收人");
|
||||
return;
|
||||
}
|
||||
//如果发送方式是延迟发送,但是没有填写延迟时间,提示用户填写延迟时间
|
||||
if (messageProps.record.sendWay == MessageSendWayDictEnum.DELAY && messageProps.record.delayTime == null) {
|
||||
ElMessage("请填写延迟时间");
|
||||
return;
|
||||
}
|
||||
//如果发送方式是定时发送,但是没有填写发送时间,提示用户填写发送时间
|
||||
if (messageProps.record.sendWay == MessageSendWayDictEnum.SCHEDULE && messageProps.record.sendTime == null) {
|
||||
ElMessage("请填写发送时间");
|
||||
return;
|
||||
}
|
||||
|
||||
//提交表单
|
||||
await messageApi
|
||||
.submitForm(messageProps.record, messageProps.record.id != undefined)
|
||||
.then(() => {
|
||||
messageProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用
|
||||
const roleSelectorRef = ref<RoleSelectorInstance>(); //角色选择器引用
|
||||
|
||||
/** 显示用户选择器 */
|
||||
function showSelector() {
|
||||
//根据是指定角色还是用户,显示不同的选择器
|
||||
if (messageProps.record.receiverType == MessageReceiverTypeDictEnum.ROLE) {
|
||||
roleSelectorRef.value?.showSelector();
|
||||
} else if (messageProps.record.receiverType == MessageReceiverTypeDictEnum.APPOINT) {
|
||||
userSelectorRef.value?.showSelector();
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择用户 */
|
||||
function handleChooseUser(data: SysUser.SysUserInfo[]) {
|
||||
// 选择用户后,将用户id赋值给orgProps.record.directorId
|
||||
if (data.length > 0) {
|
||||
console.log("[ data ] >", data);
|
||||
//赋值messageProps.record.receiverInfo
|
||||
messageProps.record.receiverInfo = data;
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择角色 */
|
||||
function handleChooseRole(data: SysRole.SysRoleInfo[]) {
|
||||
// 选择角色后,将角色id赋值给orgProps.record.directorId
|
||||
if (data.length > 0) {
|
||||
messageProps.record.receiverInfo = data;
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除选择的角色或者用户 */
|
||||
function removeTag(data: SysUser.SysUserInfo | SysRole.SysRoleInfo) {
|
||||
//移除messageProps.record.receiverInfo中对应Id的数据
|
||||
if (messageProps.record.receiverInfo) {
|
||||
const newData = messageProps.record.receiverInfo!.filter(item => item.id != data.id) as SysUser.SysUserInfo[] | SysRole.SysRoleInfo[];
|
||||
messageProps.record.receiverInfo = newData;
|
||||
}
|
||||
}
|
||||
|
||||
/** 改变接收人类型 */
|
||||
function handleReceiverTypeChange() {
|
||||
//接收人类型改变时,清空接收人信息
|
||||
messageProps.record.receiverInfo = [];
|
||||
}
|
||||
|
||||
/** 改变发送方式 */
|
||||
function handleSendWayChange(value: any) {
|
||||
//发送方式改变时,清空发送方式信息
|
||||
// messageProps.record.sendType = [];
|
||||
messageProps.record.sendTime = undefined;
|
||||
messageProps.record.delayTime = undefined;
|
||||
messageProps.record.sendWay = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.s-input-group__prepend) {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,143 @@
|
||||
<!--
|
||||
* @Description:站内消息
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-11-08 16:16:52
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="消息列表" :columns="columns" :request-api="messageApi.page">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="站内信" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="站内信"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选信息')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表格 状态 -->
|
||||
<template #status="scope">
|
||||
<el-space wrap>
|
||||
<el-tag v-if="scope.row.status === MessageStatusDictEnum.ALREADY" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_STATUS, MessageStatusDictEnum.ALREADY)
|
||||
}}</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === MessageStatusDictEnum.READY" type="info">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_STATUS, MessageStatusDictEnum.READY)
|
||||
}}</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 表格 类型 -->
|
||||
<template #category="scope">
|
||||
<el-space wrap>
|
||||
<el-tag v-if="scope.row.category === MessageTypeDictEnum.INFORM" type="danger">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_CATEGORY, MessageTypeDictEnum.INFORM)
|
||||
}}</el-tag>
|
||||
<el-tag v-else-if="scope.row.category === MessageTypeDictEnum.MESSAGE" type="primary">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_CATEGORY, MessageTypeDictEnum.MESSAGE)
|
||||
}}</el-tag>
|
||||
<el-tag v-else-if="scope.row.category === MessageTypeDictEnum.NOTICE" type="warning">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MESSAGE_CATEGORY, MessageTypeDictEnum.NOTICE)
|
||||
}}</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button v-if="scope.row.status === MessageStatusDictEnum.READY" link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button v-if="scope.row.status === MessageStatusDictEnum.ALREADY" link :opt="FormOptEnum.VIEW" @click="onOpenDetail(scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.title}】信息`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 详情表单 -->
|
||||
<Detail ref="detailRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="sysSpa">
|
||||
import { messageApi } from "@/api";
|
||||
import { SysMessage } from "@/api/interface";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { FormOptEnum, SysDictEnum, MessageStatusDictEnum, MessageTypeDictEnum } from "@/enums";
|
||||
import Form from "./components/form.vue";
|
||||
import Detail from "./components/detail.vue";
|
||||
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
const dictStore = useDictStore();
|
||||
|
||||
// 单页类型选项
|
||||
const messageTypeOptions = dictStore.getDictList(SysDictEnum.MESSAGE_CATEGORY);
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.MESSAGE_STATUS);
|
||||
// 发送方式选项
|
||||
const sendWayOptions = dictStore.getDictList(SysDictEnum.MESSAGE_WAY);
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysMessage.SysMessageInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "searchKey", label: "主题关键字", search: { el: "input" }, isShow: false },
|
||||
{ prop: "subject", label: "主题" },
|
||||
{
|
||||
prop: "category",
|
||||
label: "消息类型",
|
||||
enum: messageTypeOptions,
|
||||
search: { el: "select" }
|
||||
},
|
||||
{ prop: "content", label: "正文" },
|
||||
{ prop: "sendWay", label: "发送方式", enum: sendWayOptions },
|
||||
{ prop: "createTime", label: "创建时间" },
|
||||
{ prop: "sendTime", label: "发送时间" },
|
||||
{ prop: "status", label: "状态", enum: statusOptions, search: { el: "select" } },
|
||||
{ prop: "operation", label: "操作", width: 200, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | SysMessage.SysMessageInfo = {}) {
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(messageApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const detailRef = ref<InstanceType<typeof Detail> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开详情
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpenDetail(record: SysMessage.SysMessageInfo) {
|
||||
detailRef.value?.onOpen({ opt: FormOptEnum.VIEW, record: record, successful: RefreshTable });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<!--
|
||||
* @Description: 权限按钮批量表单页面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:42:29
|
||||
!-->
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="批量新增按钮" width="500px">
|
||||
<el-form ref="buttonFormRef" :rules="rules" :model="buttonProps" label-width="auto">
|
||||
<s-form-item label="权限简称" prop="title">
|
||||
<s-input v-model="buttonProps.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="编码前缀" prop="code">
|
||||
<s-input v-model="buttonProps.code"></s-input>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button primary @click="handleSubmit"> 确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { buttonApi, Button } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus/es/components/form";
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 表单参数接口
|
||||
interface ButtonProps extends Button.Batch {
|
||||
/** 成功之后的方法 */
|
||||
successful?: () => void;
|
||||
}
|
||||
|
||||
// 表单参数
|
||||
const buttonProps = reactive<ButtonProps>({
|
||||
parentId: 0,
|
||||
title: "",
|
||||
code: ""
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入权限简称")],
|
||||
code: [required("请输入编码前缀")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: ButtonProps) {
|
||||
Object.assign(buttonProps, props); //合并参数
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const buttonFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
buttonFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await buttonApi
|
||||
.batch(buttonProps)
|
||||
.then(() => {
|
||||
buttonProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<!--
|
||||
* @Description: 权限按钮表单页面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:42:36
|
||||
!-->
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="`${buttonProps.opt}按钮`" width="500px">
|
||||
<el-form
|
||||
ref="buttonFormRef"
|
||||
:rules="rules"
|
||||
:disabled="buttonProps.disabled"
|
||||
:model="buttonProps.record"
|
||||
:hide-required-asterisk="buttonProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="按钮名称" prop="title">
|
||||
<s-input v-model="buttonProps.record.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="按钮编码" prop="code">
|
||||
<s-input v-model="buttonProps.record.code"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="buttonProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
<s-form-item label="说明" prop="description">
|
||||
<s-input v-model="buttonProps.record.description"></s-input>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!buttonProps.disabled" primary @click="handleSubmit"> 确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { buttonApi, Button } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus/es/components/form";
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 表单参数
|
||||
const buttonProps = reactive<FormProps.Base<Button.ButtonInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入单页名称")],
|
||||
code: [required("请输入按钮编码")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<Button.ButtonInfo>) {
|
||||
Object.assign(buttonProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
buttonProps.record.sortCode = 99;
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
buttonApi.detail({ id: props.record.id }).then(res => {
|
||||
buttonProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const buttonFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
buttonFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await buttonApi
|
||||
.submitForm(buttonProps.record, buttonProps.record.id != undefined)
|
||||
.then(() => {
|
||||
buttonProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<!--
|
||||
* @Description: 权限按钮管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:42:52
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<form-container v-model="visible" title="权限按钮" form-size="60%">
|
||||
<div class="table-box min-h-300px">
|
||||
<ProTable ref="proTable" title="按钮列表" class="table" :columns="columns" :request-api="getPage">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="按钮" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button @click="onOpenBatch()"> 批量新增 </s-button>
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="按钮"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选按钮')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.title}】按钮`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="onClose"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 批量新增 -->
|
||||
<Batch ref="batchRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { buttonApi, Button } from "@/api";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import Form from "./components/form.vue";
|
||||
import Batch from "./components/batch.vue";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const parentId = ref<number | string>(0); //按钮父Id
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<Button.ButtonInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "title", label: "名称" },
|
||||
{ prop: "code", label: "编码" },
|
||||
{ prop: "sortCode", label: "排序" },
|
||||
{ prop: "description", label: "说明" },
|
||||
{ prop: "operation", label: "操作", width: 150, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | Button.ButtonInfo = { parentId: parentId.value }) {
|
||||
formRef.value?.onOpen({
|
||||
opt: opt,
|
||||
record: record,
|
||||
successful: () => RefreshTable()
|
||||
});
|
||||
}
|
||||
|
||||
// 批量新增引用
|
||||
const batchRef = ref<InstanceType<typeof Batch> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开批量新增表单
|
||||
*/
|
||||
function onOpenBatch() {
|
||||
batchRef.value?.onOpen({
|
||||
parentId: parentId.value,
|
||||
title: "",
|
||||
code: "",
|
||||
successful: RefreshTable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(buttonApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpenTable(menuId: number | string) {
|
||||
visible.value = true;
|
||||
parentId.value = menuId;
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按钮分页数据
|
||||
* 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
* 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
|
||||
*/
|
||||
function getPage(params: any) {
|
||||
let newParams = JSON.parse(JSON.stringify(params)); //转换成json字符串再转换成json对象
|
||||
newParams.parentId = parentId.value; //按钮父Id
|
||||
return buttonApi.page(newParams);
|
||||
}
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpenTable
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!--
|
||||
* @Description: 更改菜单模块
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:42:57
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="'更改模块->' + changeModuleProps.title" form-size="450px">
|
||||
<el-form ref="changeModuleFormRef" :rules="rules" :model="changeModuleProps" class="mt-20px">
|
||||
<s-form-item label="所属模块" prop="module">
|
||||
<s-select v-model="changeModuleProps.module" :options="changeModuleProps.moduleOptions"></s-select>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { menuApi } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus/es/components/form";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
/** 表单参数 */
|
||||
interface ChangeModuleProps {
|
||||
/** 菜单id */
|
||||
id: number | string;
|
||||
/** 模块id */
|
||||
module: number | string;
|
||||
/** 菜单名称 */
|
||||
title: string;
|
||||
/** 模块列表 */
|
||||
moduleOptions: { label: string; value: number | string }[];
|
||||
/** 表单实例 */
|
||||
successful?: () => void;
|
||||
}
|
||||
/** 表单参数 */
|
||||
const changeModuleProps = reactive<ChangeModuleProps>({
|
||||
id: 0,
|
||||
module: 0,
|
||||
title: "",
|
||||
moduleOptions: []
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
module: [required("请选择所属模块")]
|
||||
});
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const changeModuleFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
changeModuleFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await menuApi
|
||||
.changeModule({
|
||||
id: changeModuleProps.id,
|
||||
module: changeModuleProps.module
|
||||
})
|
||||
.then(() => {
|
||||
//提交成功
|
||||
changeModuleProps.successful?.();
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 打开表单
|
||||
* @param title 菜单名称
|
||||
* @param module 模块id
|
||||
*/
|
||||
function onOpen(moduleProps: ChangeModuleProps) {
|
||||
Object.assign(changeModuleProps, moduleProps); //合并参数
|
||||
visible.value = true; //显示表单
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,219 @@
|
||||
<!--
|
||||
* @Description: 菜单管理表菜单面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:02
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${menuProps.opt}菜单`" form-size="600px">
|
||||
<el-form
|
||||
ref="menuFormRef"
|
||||
:rules="rules"
|
||||
:disabled="menuProps.disabled"
|
||||
:model="menuProps.record"
|
||||
:hide-required-asterisk="menuProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<!-- 基本设置 -->
|
||||
<el-divider content-position="left">基本设置</el-divider>
|
||||
<s-form-item label="菜单名称" prop="title">
|
||||
<s-input v-model="menuProps.record.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="菜单类型" prop="menuType">
|
||||
<s-radio-group v-model="menuProps.record.menuType" :options="menuTypeOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="图标" prop="icon">
|
||||
<SelectIconPlus v-model:icon-value="menuProps.record.icon!" />
|
||||
</s-form-item>
|
||||
<s-form-item label="上级菜单" prop="parentId">
|
||||
<MenuSelector
|
||||
v-model:menu-value="menuProps.record.parentId"
|
||||
show-all
|
||||
@change="changeMenu"
|
||||
:menu-tree-api="() => menuApi.menuTreeSelector({ module: module })"
|
||||
/>
|
||||
</s-form-item>
|
||||
<s-form-item label="状态" prop="status">
|
||||
<s-radio-group v-model="menuProps.record.status" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="路由地址" prop="path">
|
||||
<s-input v-model="menuProps.record.path" placeholder="请填写路由地址,例:/home/index"></s-input>
|
||||
</s-form-item>
|
||||
<div v-if="isMenu || isSubSet || isLink">
|
||||
<s-form-item label="组件名称" prop="name">
|
||||
<s-input v-model="menuProps.record.name">
|
||||
<template #prepend>setup name=</template>
|
||||
</s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="组件地址" prop="component">
|
||||
<s-input v-model="menuProps.record.component">
|
||||
<template #prepend>src/views/</template>
|
||||
</s-input>
|
||||
</s-form-item>
|
||||
</div>
|
||||
<div v-if="isLink">
|
||||
<s-form-item label="链接地址:" prop="isLink">
|
||||
<s-input v-model="menuProps.record.isLink" placeholder="请填写链接地址,例:http://www.baidu.com"></s-input>
|
||||
</s-form-item>
|
||||
</div>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="menuProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
<s-form-item label="说明" prop="description">
|
||||
<s-input v-model="menuProps.record.description"></s-input>
|
||||
</s-form-item>
|
||||
<!-- 功能设置 -->
|
||||
<div v-if="isMenu || isLink">
|
||||
<el-divider content-position="left">功能设置</el-divider>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="设置主页" prop="isHome">
|
||||
<s-radio-group v-model="menuProps.record.isHome" :disabled="isLink || isSubSet" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="隐藏菜单" prop="isHide">
|
||||
<s-radio-group v-model="menuProps.record.isHide" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="页面全屏" prop="isFull">
|
||||
<s-radio-group v-model="menuProps.record.isFull" :disabled="isLink" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="固定标签页" prop="isAffix">
|
||||
<s-radio-group v-model="menuProps.record.isAffix" :disabled="isLink" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="路由缓存" prop="isKeepAlive">
|
||||
<s-radio-group v-model="menuProps.record.isKeepAlive" :disabled="isLink" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!menuProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { menuApi, Menu } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus/es/components/form";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 菜单类型选项
|
||||
const menuTypeOptions = dictStore
|
||||
.getDictList(SysDictEnum.MENU_TYPE)
|
||||
.filter((item: { value: MenuTypeDictEnum }) => item.value !== MenuTypeDictEnum.BUTTON);
|
||||
// 是否选项
|
||||
const yesOptions = dictStore.getDictList(SysDictEnum.YES_NO, true);
|
||||
|
||||
// 通用状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
// 模块id
|
||||
const module = ref<number | string>(0);
|
||||
|
||||
// 表单参数
|
||||
const menuProps = reactive<FormProps.Base<Menu.MenuInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入菜单名称")],
|
||||
parentId: [required("请选择上级菜单")],
|
||||
menuType: [required("请选择菜单类型")],
|
||||
path: [required("请输入路由地址")],
|
||||
name: [required("请输入组件名称")],
|
||||
component: [required("请输入组件地址")],
|
||||
sortCode: [required("请输入排序")],
|
||||
icon: [required("请选择图标")],
|
||||
status: [required("请选择状态")],
|
||||
isLink: [required("请输入链接地址")]
|
||||
});
|
||||
|
||||
// const isCatalog = computed(() => menuProps.record.menuType === MenuTypeDictEnum.CATALOG); //是否是目录
|
||||
const isMenu = computed(() => menuProps.record.menuType === MenuTypeDictEnum.MENU); //是否是菜单
|
||||
const isLink = computed(() => menuProps.record.menuType === MenuTypeDictEnum.LINK); //是否
|
||||
const isSubSet = computed(() => menuProps.record.menuType === MenuTypeDictEnum.SUBSET); //是否是子集
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<Menu.MenuInfo>, moduleId: number | string) {
|
||||
Object.assign(menuProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
menuProps.record.sortCode = 99;
|
||||
menuProps.record.status = statusOptions[0].value;
|
||||
menuProps.record.menuType = MenuTypeDictEnum.MENU;
|
||||
menuProps.record.sortCode = 99;
|
||||
menuProps.record.isHome = false;
|
||||
menuProps.record.isHide = false;
|
||||
menuProps.record.isFull = false;
|
||||
menuProps.record.isAffix = false;
|
||||
menuProps.record.isKeepAlive = true;
|
||||
menuProps.record.module = moduleId;
|
||||
}
|
||||
module.value = moduleId; //设置模块id
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
menuApi.detail({ id: props.record.id }).then(res => {
|
||||
menuProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const menuFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
menuFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await menuApi
|
||||
.submitForm(menuProps.record, menuProps.record.id != undefined)
|
||||
.then(() => {
|
||||
menuProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
/** 菜单选择事件
|
||||
* @param path 路由地址
|
||||
*/
|
||||
function changeMenu(path: string) {
|
||||
if (isSubSet.value) {
|
||||
path = path + "/:id";
|
||||
menuProps.record.activeMenu = path;
|
||||
}
|
||||
menuProps.record.path = path;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,220 @@
|
||||
<!--
|
||||
* @Description: 菜单管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:08
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="菜单列表" :indent="20" :columns="columns" :request-api="menuApi.tree" :pagination="false">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="菜单" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
suffix="菜单"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
plain
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选菜单')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表格 菜单类型 按钮 -->
|
||||
<template #menuType="scope">
|
||||
<el-space>
|
||||
<el-tag>{{ dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType) }}</el-tag>
|
||||
<el-tag v-if="scope.row.isHome === true" type="warning">首页</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<el-space>
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.title}】菜单`)" />
|
||||
<el-dropdown @command="handleCommand">
|
||||
<el-link type="primary" :underline="false" :icon="ArrowDown"> 更多 </el-link>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="scope.row.parentId === 0" :command="command(scope.row, cmdEnum.ChangeModule)">{{
|
||||
cmdEnum.ChangeModule
|
||||
}}</el-dropdown-item>
|
||||
<el-dropdown-item v-if="isMenu(scope.row.menuType)" :command="command(scope.row, cmdEnum.Button)">{{
|
||||
cmdEnum.Button
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-space>
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 更改模块 -->
|
||||
<ChangeModule ref="changeModuleFormRef" />
|
||||
<!-- 权限按钮 -->
|
||||
<Button ref="buttonFormRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="sysMenu">
|
||||
import { menuApi, Menu } from "@/api";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { ArrowDown } from "@element-plus/icons-vue";
|
||||
import { useUserStore, useDictStore } from "@/stores/modules";
|
||||
import { SysDictEnum, FormOptEnum, MenuTypeDictEnum, CommonStatusEnum } from "@/enums";
|
||||
import Form from "./components/form.vue";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import ChangeModule from "./components/changeModule.vue";
|
||||
import Button from "../button/index.vue";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const dictStore = useDictStore();
|
||||
//遍历模块列表,生成下拉选项
|
||||
const moduleOptions = userStore.moduleList.map(item => {
|
||||
return { label: item.title, value: item.id };
|
||||
});
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<Menu.MenuInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "id", label: "Id", isShow: false },
|
||||
{
|
||||
prop: "module",
|
||||
label: "应用模块",
|
||||
enum: moduleOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: {
|
||||
clearable: false,
|
||||
placeholder: "请选择应用模块"
|
||||
},
|
||||
defaultValue: moduleOptions[0].value
|
||||
},
|
||||
isShow: false
|
||||
},
|
||||
{ prop: "title", label: "菜单名称", width: 150, align: "left", search: { el: "input" } },
|
||||
{
|
||||
prop: "icon",
|
||||
label: "菜单图标",
|
||||
render: scope => {
|
||||
return <svg-icon icon={scope.row.icon} class="h-6 w-6" />;
|
||||
}
|
||||
},
|
||||
{ prop: "menuType", label: "菜单类型" },
|
||||
{ prop: "name", label: "组件名称" },
|
||||
{ prop: "path", label: "路由地址", width: 200, search: { el: "input" } },
|
||||
{ prop: "component", label: "组件路径", width: 250 },
|
||||
{ prop: "sortCode", label: "排序", width: 80 },
|
||||
{ prop: "status", label: "状态" },
|
||||
{ prop: "description", label: "说明" },
|
||||
|
||||
{ prop: "operation", label: "操作", width: 230, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | Menu.MenuInfo = {}) {
|
||||
formRef.value?.onOpen(
|
||||
{
|
||||
opt: opt,
|
||||
record: record,
|
||||
successful: () => RefreshTable()
|
||||
},
|
||||
proTable.value?.searchParam.module
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(menuApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
|
||||
/**判断是否是菜单 */
|
||||
const isMenu = computed(() => (menuType: string) => {
|
||||
return menuType === MenuTypeDictEnum.MENU;
|
||||
}); //是否是菜单
|
||||
|
||||
/** 更多下拉菜单命令枚举 */
|
||||
enum cmdEnum {
|
||||
ChangeModule = "更改模块",
|
||||
Button = "权限按钮"
|
||||
}
|
||||
|
||||
/** 下拉菜单参数接口 */
|
||||
interface Command {
|
||||
row: Menu.MenuInfo;
|
||||
command: cmdEnum;
|
||||
}
|
||||
|
||||
/**配置command的参数 */
|
||||
function command(row: Menu.MenuInfo, command: cmdEnum): Command {
|
||||
return {
|
||||
row: row,
|
||||
command: command
|
||||
};
|
||||
}
|
||||
|
||||
// 修改模块表单引用
|
||||
const changeModuleFormRef = ref<InstanceType<typeof ChangeModule> | null>(null);
|
||||
// 权限按钮表单引用
|
||||
const buttonFormRef = ref<InstanceType<typeof Button> | null>(null);
|
||||
|
||||
/**
|
||||
* 更多下拉菜单点击事件
|
||||
* @param command
|
||||
*/
|
||||
function handleCommand(command: Command) {
|
||||
console.log("[ 12312 ] >", 12312);
|
||||
switch (command.command) {
|
||||
case cmdEnum.ChangeModule:
|
||||
//更改模块
|
||||
changeModuleFormRef.value?.onOpen({
|
||||
moduleOptions,
|
||||
id: command.row.id,
|
||||
title: command.row.title,
|
||||
module: command.row.module,
|
||||
successful: () => RefreshTable()
|
||||
});
|
||||
break;
|
||||
case cmdEnum.Button:
|
||||
//权限按钮
|
||||
buttonFormRef.value?.onOpenTable(command.row.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-dropdown-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
:deep(.el-link__inner) {
|
||||
padding-left: 6px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,122 @@
|
||||
<!--
|
||||
* @Description: 模块管理表单页面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:14
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${moduleProps.opt}模块`" form-size="600px">
|
||||
<el-form
|
||||
ref="moduleFormRef"
|
||||
:rules="rules"
|
||||
:disabled="moduleProps.disabled"
|
||||
:model="moduleProps.record"
|
||||
:hide-required-asterisk="moduleProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
class="mt-20px"
|
||||
>
|
||||
<s-form-item label="模块名称" prop="title">
|
||||
<s-input v-model="moduleProps.record.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="图标" prop="icon">
|
||||
<SelectIconPlus v-model:icon-value="moduleProps.record.icon!" />
|
||||
</s-form-item>
|
||||
<s-form-item label="状态" prop="status">
|
||||
<s-radio-group v-model="moduleProps.record.status" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="moduleProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
<s-form-item label="说明" prop="description">
|
||||
<s-input v-model="moduleProps.record.description"></s-input>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!moduleProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { moduleApi, Module } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum, SysDictEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
const visible = ref(false); //
|
||||
|
||||
// 通用状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
|
||||
// 表单参数
|
||||
const moduleProps = reactive<FormProps.Base<Module.ModuleInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入单页名称")],
|
||||
menuType: [required("请选择单页类型")],
|
||||
path: [required("请输入路由地址")],
|
||||
name: [required("请输入组件名称")],
|
||||
component: [required("请输入组件地址")],
|
||||
sortCode: [required("请输入排序")],
|
||||
icon: [required("请选择图标")],
|
||||
status: [required("请选择状态")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<Module.ModuleInfo>) {
|
||||
Object.assign(moduleProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
moduleProps.record.sortCode = 99;
|
||||
moduleProps.record.status = statusOptions[0].value;
|
||||
}
|
||||
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
moduleApi.detail({ id: props.record.id }).then(res => {
|
||||
moduleProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const moduleFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
moduleFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await moduleApi
|
||||
.submitForm(moduleProps.record, moduleProps.record.id != undefined)
|
||||
.then(() => {
|
||||
moduleProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<!--
|
||||
* @Description: 模块管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:19
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="模块列表" :columns="columns" :request-api="moduleApi.page">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="模块" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="模块"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选模块')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)"> 编辑 </s-button>
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.title}】页面`)"> 删除 </s-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="sysModule">
|
||||
import { moduleApi, Module } from "@/api";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { FormOptEnum, CommonStatusEnum, SysDictEnum } from "@/enums";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import Form from "./components/form.vue";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<Module.ModuleInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "title", label: "模块名称", search: { el: "input" } },
|
||||
{
|
||||
prop: "icon",
|
||||
label: "菜单图标",
|
||||
render: scope => {
|
||||
return <svg-icon icon={scope.row.icon} class="h-6 w-6" />;
|
||||
}
|
||||
},
|
||||
|
||||
{ prop: "sortCode", label: "排序" },
|
||||
{ prop: "description", label: "说明" },
|
||||
{ prop: "status", label: "状态" },
|
||||
{ prop: "createTime", label: "创建时间" },
|
||||
{ prop: "operation", label: "操作", width: 200, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | Module.ModuleInfo = {}) {
|
||||
formRef.value?.onOpen({
|
||||
opt: opt,
|
||||
record: record,
|
||||
successful: () => RefreshTable()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(moduleApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<!--
|
||||
* @Description: 数据范围选择器
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:27
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<el-space>
|
||||
<s-radio-group v-model="defaultLevel" :options="scopeOptions" value-key="level" label-key="title" @change="changeDataScope">
|
||||
<template #radio="{ item }">
|
||||
<el-badge
|
||||
v-if="
|
||||
dataScope.level === defineScope?.level && item.scopeCategory === SysRole.DataScopeEnum.SCOPE_ORG_DEFINE && item.scopeDefineOrgIdList
|
||||
"
|
||||
:value="dataScope.scopeDefineOrgIdList.length"
|
||||
type="success"
|
||||
>
|
||||
<span class="mb-5">{{ item.title }}</span>
|
||||
</el-badge>
|
||||
<span v-else>{{ item.title }}</span>
|
||||
</template>
|
||||
</s-radio-group>
|
||||
<div v-if="dataScope.level === defineScope?.level">
|
||||
<el-button link type="primary" class="ml-15px" @click="handleDefineOrg">选择组织</el-button>
|
||||
</div>
|
||||
</el-space>
|
||||
<el-dialog v-model="dialogVisible" append-to-body title="选择组织" width="400" destroy-on-close>
|
||||
<TreeFilter
|
||||
title="组织列表"
|
||||
multiple
|
||||
check-strictly
|
||||
class="treeFilter"
|
||||
label="name"
|
||||
:request-api="sysOrgApi.tree"
|
||||
:default-value="dataScope.scopeDefineOrgIdList"
|
||||
@change="changeTreeFilter"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button @click="dialogCancel">取消</el-button>
|
||||
<el-button type="primary" @click="chooseDefineOrg"> 确定 </el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysRole, sysOrgApi } from "@/api";
|
||||
|
||||
const dialogVisible = ref(false); //是否显示弹窗
|
||||
|
||||
// props
|
||||
interface DataScopeProps {
|
||||
modelValue: SysRole.DefaultDataScope | undefined;
|
||||
}
|
||||
const emit = defineEmits(["update:modelValue"]); //定义emit
|
||||
const props = defineProps<DataScopeProps>(); //定义props
|
||||
|
||||
// 用户信息
|
||||
const dataScope = computed({
|
||||
get: () => props.modelValue ?? scopeOptions[0], // 判断是否有值,没有值则取第一个
|
||||
set: val => emit("update:modelValue", val)
|
||||
});
|
||||
|
||||
// 数据范围列表
|
||||
const scopeOptions = SysRole.dataScopeOptions;
|
||||
|
||||
// 默认数据范围
|
||||
const defaultLevel = ref(dataScope.value.level);
|
||||
|
||||
// 自定义数据范围
|
||||
const defineScope = SysRole.dataScopeOptions.find(item => item.scopeCategory === SysRole.DataScopeEnum.SCOPE_ORG_DEFINE);
|
||||
|
||||
// 自定义组织id列表
|
||||
const defineOrgIdList = ref<number[] | string[]>(dataScope.value.scopeDefineOrgIdList);
|
||||
|
||||
/** 修改数据权限 */
|
||||
function changeDataScope(value: any) {
|
||||
let scope = SysRole.dataScopeOptions.find(item => item.level === value);
|
||||
if (scope) {
|
||||
dataScope.value = scope;
|
||||
}
|
||||
}
|
||||
|
||||
// /** 处理自定义 */
|
||||
function handleDefineOrg() {
|
||||
dialogVisible.value = true;
|
||||
console.log("[ dataScope.value ] >", dataScope.value);
|
||||
}
|
||||
|
||||
/** 组织树数据变化 */
|
||||
function changeTreeFilter(val: any[]) {
|
||||
defineOrgIdList.value = val;
|
||||
}
|
||||
|
||||
/** 取消选择组织 */
|
||||
function dialogCancel() {
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false;
|
||||
}
|
||||
|
||||
/** 选择机构 */
|
||||
function chooseDefineOrg() {
|
||||
// 设置默认数据权限中的机构ID列表
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false;
|
||||
dataScope.value.scopeDefineOrgIdList = defineOrgIdList.value;
|
||||
console.log("[ dataScope.value ] >", dataScope.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.treeFilter {
|
||||
width: 100% !important;
|
||||
}
|
||||
:deep(.el-badge__content.is-fixed) {
|
||||
transform: translateY(-30%) translateX(100%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,125 @@
|
||||
<!--
|
||||
* @Description: 表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:34
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${sysRoleProps.opt}角色`" form-size="800px" @close="onClose">
|
||||
<el-form
|
||||
ref="sysRoleFormRef"
|
||||
:rules="rules"
|
||||
:disabled="sysRoleProps.disabled"
|
||||
:model="sysRoleProps.record"
|
||||
:hide-required-asterisk="sysRoleProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="角色名称" prop="name">
|
||||
<s-input v-model="sysRoleProps.record.name"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="角色分类" prop="category">
|
||||
<s-radio-group v-model="sysRoleProps.record.category" :options="roleCategoryOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item v-if="sysRoleProps.record.category === OrgCategoryEnum.ORG" label="所属组织" prop="orgId">
|
||||
<org-selector v-model:org-value="sysRoleProps.record.orgId!" />
|
||||
</s-form-item>
|
||||
<s-form-item label="角色编码" prop="code">
|
||||
<s-input v-model="sysRoleProps.record.code" placeholder="请填写角色编码,不填则为随机值"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="默认数据范围" prop="code">
|
||||
<DataScopeSelector v-model="sysRoleProps.record.defaultDataScope"></DataScopeSelector>
|
||||
</s-form-item>
|
||||
<s-form-item label="状态" prop="status">
|
||||
<s-radio-group v-model="sysRoleProps.record.status" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="sysRoleProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!sysRoleProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysRole, sysRoleApi } from "@/api";
|
||||
import { FormOptEnum, SysDictEnum, OrgCategoryEnum } from "@/enums";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import DataScopeSelector from "./dataScopeSelector.vue";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
const roleCategoryOptions = dictStore.getDictList(SysDictEnum.ROLE_CATEGORY); // 角色类型选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS); // 通用状态选项
|
||||
|
||||
// 表单参数
|
||||
const sysRoleProps = reactive<FormProps.Base<SysRole.SysRoleInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [required("请输入角色名称")],
|
||||
orgId: [required("请选择所属组织")],
|
||||
category: [required("请选择角色类型")],
|
||||
status: [required("请选择状态")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<SysRole.SysRoleInfo>) {
|
||||
Object.assign(sysRoleProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
sysRoleProps.record.sortCode = 99;
|
||||
sysRoleProps.record.category = roleCategoryOptions[0].value;
|
||||
sysRoleProps.record.status = statusOptions[0].value;
|
||||
sysRoleProps.record.defaultDataScope = SysRole.dataScopeOptions[0];
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
sysRoleApi.detail({ id: props.record.id }).then(res => {
|
||||
sysRoleProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const sysRoleFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
sysRoleFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await sysRoleApi
|
||||
.submitForm(sysRoleProps.record, sysRoleProps.record.id != undefined)
|
||||
.then(() => {
|
||||
sysRoleProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,458 @@
|
||||
<!--
|
||||
* @Description: 授权移动端资源表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-08-18 15:43:49
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<form-container v-model="visible" :title="`授权移动端资源->${grantProps.name}`" form-size="1200px" @close="onClose">
|
||||
<el-alert title="移动端资源授权:配置角色可访问的移动端菜单和功能" type="info" show-icon style="margin-bottom: 10px" />
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
:columns="columns"
|
||||
:tool-button="false"
|
||||
:cell-style="{ padding: '0px' }"
|
||||
:row-style="{ height: '0' }"
|
||||
:data="grantProps.tableData"
|
||||
:pagination="false"
|
||||
:span-method="objectSpanMethod"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
<s-radio-group
|
||||
v-model="grantProps.moduleId"
|
||||
:options="grantProps.echoDatalist"
|
||||
value-key="id"
|
||||
label-key="title"
|
||||
button
|
||||
class="mb-2"
|
||||
@change="moduleClick"
|
||||
>
|
||||
<template #radio="{ item }">
|
||||
<el-space>
|
||||
<svg-icon :icon="item.icon" class="h-4 w-4"></svg-icon>
|
||||
{{ item.title }}
|
||||
</el-space>
|
||||
</template>
|
||||
</s-radio-group>
|
||||
</template>
|
||||
<!-- 一级目录 -->
|
||||
<template #parentName="scope">
|
||||
<el-checkbox v-model="scope.row.parentCheck" @change="val => changeParent(scope.row, val)">
|
||||
{{ scope.row.parentName }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<!-- 菜单 -->
|
||||
<template #title="scope">
|
||||
<el-checkbox v-model="scope.row.nameCheck" @change="val => changeSub(scope.row, val)">
|
||||
{{ scope.row.title }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<!-- 按钮授权 -->
|
||||
<template #button="scope">
|
||||
<template v-if="scope.row.button.length > 0">
|
||||
<template v-for="(item, index) in scope.row.button" :key="item.id">
|
||||
<el-checkbox v-model="item.check" style="margin-right: 5px" @change="val => changeChildCheckBox(scope.row, val)">
|
||||
{{ item.title }}
|
||||
</el-checkbox>
|
||||
<br v-if="(index + 1) % 5 === 0" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ProTable>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
<el-dialog v-model="dialogVisible" title="请选择数据范围" width="30%" destroy-on-close>
|
||||
<DataScopeSelector v-model="grantProps.defaultDataScope"></DataScopeSelector>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleUserSubmit"> 确定 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysRole, SysUser, mobileMenuApi, MobileMenu } from "@/api";
|
||||
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
||||
import type { TableColumnCtx } from "element-plus";
|
||||
import DataScopeSelector from "./dataScopeSelector.vue";
|
||||
|
||||
const visible = ref(false); //授权移动端资源表单是否显示
|
||||
const dialogVisible = ref(false); // 数据范围弹窗显示
|
||||
|
||||
/** 授权移动端资源表单参数接口 */
|
||||
interface GrantMobileResourceFormProps {
|
||||
/** id */
|
||||
id: number | string;
|
||||
/** 名称 */
|
||||
name: string;
|
||||
/** 模块id */
|
||||
moduleId: number | string;
|
||||
/** 移动端资源树 */
|
||||
mobileResTree: MobileMenu.MobileResTreeSelector[];
|
||||
/** 已拥有的移动端资源 */
|
||||
ownMobileResource: MobileMenu.RoleOwnMobileResource | null;
|
||||
/** 一级菜单索引 */
|
||||
firstShowMap: Record<string, number[]>;
|
||||
/** 回显数据列表 */
|
||||
echoDatalist: MobileMenu.MobileResTreeSelector[];
|
||||
/** 表格数据 */
|
||||
tableData: MobileMenu.RoleGrantMobileResourceMenu[];
|
||||
//默认数据范围,用户才有
|
||||
defaultDataScope: SysRole.DefaultDataScope;
|
||||
}
|
||||
|
||||
/** 授权移动端资源表单参数 */
|
||||
const grantProps = reactive<GrantMobileResourceFormProps>({
|
||||
id: 0,
|
||||
name: "",
|
||||
moduleId: 0,
|
||||
mobileResTree: [],
|
||||
ownMobileResource: null,
|
||||
firstShowMap: {},
|
||||
echoDatalist: [],
|
||||
tableData: [],
|
||||
defaultDataScope: SysRole.dataScopeOptions[0]
|
||||
});
|
||||
|
||||
/** props */
|
||||
const props = defineProps<{
|
||||
/** 是否是用户 */
|
||||
user?: boolean;
|
||||
}>();
|
||||
|
||||
// 获取 ProTable DOM
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<MobileMenu.RoleGrantMobileResourceMenu>[] = [
|
||||
{ prop: "parentName", label: "一级目录", width: 150, align: "left" },
|
||||
{ prop: "title", label: "菜单", width: 180, align: "left" },
|
||||
{ prop: "button", label: "按钮授权", align: "left" }
|
||||
];
|
||||
|
||||
/** 列合并 */
|
||||
interface SpanMethodProps {
|
||||
// 行
|
||||
row: MobileMenu.RoleGrantMobileResourceMenu;
|
||||
// 列
|
||||
column: TableColumnCtx<MobileMenu.RoleGrantMobileResourceMenu>;
|
||||
// 行索引
|
||||
rowIndex: number;
|
||||
// 列索引
|
||||
columnIndex: number;
|
||||
}
|
||||
|
||||
/** 列合并*/
|
||||
const objectSpanMethod = ({ row, rowIndex, columnIndex }: SpanMethodProps) => {
|
||||
// 如果列索引为0
|
||||
if (columnIndex == 0) {
|
||||
// 获取父名称
|
||||
const parentName = row.parentName;
|
||||
// 获取父名称的索引数组
|
||||
const indexArr = grantProps.firstShowMap[parentName];
|
||||
// 如果行索引等于索引数组的第一个
|
||||
if (rowIndex === indexArr[0]) {
|
||||
return { rowspan: indexArr.length, colspan: 1 };
|
||||
} else {
|
||||
return { rowspan: 0, colspan: 0 };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
async function onOpen(props: FormProps.Base<SysRole.SysRoleInfo | SysUser.SysUserInfo>) {
|
||||
grantProps.id = props.record.id || 0;
|
||||
grantProps.name = props.record.name || "";
|
||||
await loadData();
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
/** 加载数据 */
|
||||
async function loadData() {
|
||||
if (grantProps.echoDatalist.length > 0) {
|
||||
let data = grantProps.echoDatalist.find(f => f.id === grantProps.moduleId)!.menu;
|
||||
grantProps.tableData = data;
|
||||
} else {
|
||||
// 获取移动端资源授权树
|
||||
await mobileMenuApi.resourceTreeSelector().then(res => {
|
||||
grantProps.mobileResTree = res.data;
|
||||
grantProps.moduleId = res.data[0].id;
|
||||
});
|
||||
await getMobileResources(); // 获取移动端资源
|
||||
|
||||
// 初始化数据结构
|
||||
grantProps.mobileResTree.forEach(module => {
|
||||
if (module.menu) {
|
||||
module.menu.forEach(menu => {
|
||||
// 如果 parentId 等于自己的 id,说明是顶级菜单,设置 parentName 为模块名称
|
||||
if (menu.parentId === menu.id) {
|
||||
menu.parentName = module.title; // 使用模块标题作为父级名称
|
||||
}
|
||||
// 初始化checkbox状态
|
||||
menu.parentCheck = false;
|
||||
menu.nameCheck = false;
|
||||
menu.button.forEach(btn => {
|
||||
btn.check = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 如果有已拥有的移动端资源
|
||||
if (grantProps.ownMobileResource) {
|
||||
// 回显模块数据
|
||||
// 获取echoDatalist
|
||||
grantProps.echoDatalist = echoModuleData(grantProps.mobileResTree, grantProps.ownMobileResource);
|
||||
} else {
|
||||
// 没有已拥有资源时,直接使用资源树
|
||||
grantProps.echoDatalist = grantProps.mobileResTree;
|
||||
}
|
||||
|
||||
// 获取模块id
|
||||
grantProps.moduleId = grantProps.mobileResTree[0].id;
|
||||
// 获取表格数据
|
||||
grantProps.tableData = grantProps.echoDatalist[0].menu;
|
||||
|
||||
// 构建索引映射
|
||||
grantProps.firstShowMap = {};
|
||||
grantProps.tableData.forEach((item: MobileMenu.RoleGrantMobileResourceMenu, index) => {
|
||||
if (grantProps.firstShowMap[item.parentName]) {
|
||||
grantProps.firstShowMap[item.parentName].push(index);
|
||||
} else {
|
||||
grantProps.firstShowMap[item.parentName] = [index];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取移动端资源 */
|
||||
async function getMobileResources() {
|
||||
if (props.user) {
|
||||
// 获取用户拥有移动端资源(这里需要用户API也支持移动端资源)
|
||||
// 暂时使用mobileMenuApi,实际应该在用户API中添加对应接口
|
||||
await mobileMenuApi.roleOwnMobileMenu({ id: grantProps.id }).then(res => {
|
||||
grantProps.ownMobileResource = res.data;
|
||||
});
|
||||
} else {
|
||||
// 获取角色拥有移动端资源
|
||||
await mobileMenuApi.roleOwnMobileMenu({ id: grantProps.id }).then(res => {
|
||||
grantProps.ownMobileResource = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回显模块数据
|
||||
* @param mobileResTree 移动端资源树
|
||||
* @param ownMobileResource 已拥有的移动端资源
|
||||
*/
|
||||
function echoModuleData(mobileResTree: MobileMenu.MobileResTreeSelector[], ownMobileResource: MobileMenu.RoleOwnMobileResource) {
|
||||
// 通过应用循环
|
||||
mobileResTree.forEach(module => {
|
||||
//如果有菜单
|
||||
if (module.menu) {
|
||||
module.menu.forEach(menu => {
|
||||
// 如果 parentId 等于自己的 id,说明是顶级菜单,设置 parentName 为模块名称
|
||||
if (menu.parentId === menu.id) {
|
||||
menu.parentName = module.title; // 使用模块标题作为父级名称
|
||||
}
|
||||
|
||||
const menuCheck = ref(0);
|
||||
// 如果已授权资源信息列表有数据
|
||||
if (ownMobileResource.grantInfoList.length > 0) {
|
||||
// 通过已授权资源信息列表循环
|
||||
ownMobileResource.grantInfoList.forEach(grantInfo => {
|
||||
// 如果已授权资源信息列表中的菜单id等于当前菜单id
|
||||
if (menu.id == grantInfo.menuId) {
|
||||
menuCheck.value++;
|
||||
// 处理按钮
|
||||
if (grantInfo.buttonInfo.length > 0) {
|
||||
// 通过已授权资源信息列表中的按钮循环
|
||||
grantInfo.buttonInfo.forEach(button => {
|
||||
menu.button.forEach(itemButton => {
|
||||
// 如果已授权资源信息列表中的按钮id等于当前按钮id
|
||||
if (button === itemButton.id) {
|
||||
itemButton.check = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 回显前面的2个
|
||||
if (menuCheck.value > 0) {
|
||||
menu.parentCheck = true;
|
||||
menu.nameCheck = true;
|
||||
}
|
||||
});
|
||||
// 缓存加入索引
|
||||
module.menu.forEach((item: MobileMenu.RoleGrantMobileResourceMenu, index) => {
|
||||
// 下面就是用来知道不同的一级菜单里面有几个二级菜单,以及他们所在的索引
|
||||
if (grantProps.firstShowMap[item.parentName]) {
|
||||
grantProps.firstShowMap[item.parentName].push(index);
|
||||
} else {
|
||||
grantProps.firstShowMap[item.parentName] = [index];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return mobileResTree;
|
||||
}
|
||||
|
||||
/** 选择模块 */
|
||||
function moduleClick() {
|
||||
// 切换模块时,更新表格数据和索引映射
|
||||
let data = grantProps.echoDatalist.find(f => f.id === grantProps.moduleId)!.menu;
|
||||
grantProps.tableData = data;
|
||||
|
||||
// 重新构建索引映射
|
||||
grantProps.firstShowMap = {};
|
||||
grantProps.tableData.forEach((item: MobileMenu.RoleGrantMobileResourceMenu, index) => {
|
||||
if (grantProps.firstShowMap[item.parentName]) {
|
||||
grantProps.firstShowMap[item.parentName].push(index);
|
||||
} else {
|
||||
grantProps.firstShowMap[item.parentName] = [index];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 一级目录点击 */
|
||||
function changeParent(row: MobileMenu.RoleGrantMobileResourceMenu, val: any) {
|
||||
row.parentCheck = val;
|
||||
// 通过这个应用id,找到应用下的所有菜单
|
||||
const moduleMenu = grantProps.echoDatalist.find(f => row.module === f.id);
|
||||
const parentName = row.parentName;
|
||||
// 获取同一级菜单的所有索引
|
||||
const indexArr = grantProps.firstShowMap[parentName];
|
||||
indexArr.forEach(indexItem => {
|
||||
// 获取同一级菜单的所有行
|
||||
const row = moduleMenu?.menu[indexItem];
|
||||
|
||||
if (row)
|
||||
// 给这些菜单的索引去勾选
|
||||
changeSub(row, val);
|
||||
});
|
||||
}
|
||||
|
||||
/** 二级菜单的点击 */
|
||||
function changeSub(row: MobileMenu.RoleGrantMobileResourceMenu, val: any) {
|
||||
// 选中二级菜单
|
||||
row.nameCheck = val;
|
||||
row.button.forEach(item => {
|
||||
// 选中按钮
|
||||
item.check = val;
|
||||
});
|
||||
}
|
||||
|
||||
/** 权限按钮点击 */
|
||||
function changeChildCheckBox(row: MobileMenu.RoleGrantMobileResourceMenu, val: any) {
|
||||
// 如果val为false,并且row的子菜单没有选中,则取消所有按钮的选中
|
||||
if (!val && checkAllChildNotChecked(row)) {
|
||||
// 这里注释掉勾选去掉所有按钮,联动去掉菜单
|
||||
/*record.nameCheck = false
|
||||
record.parentCheck = false*/
|
||||
} else if (val) {
|
||||
// 如果val为true,则将row的nameCheck和parentCheck属性设置为true
|
||||
row.nameCheck = val;
|
||||
row.parentCheck = val;
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查所有子级是否都没有勾选 */
|
||||
function checkAllChildNotChecked(record: MobileMenu.RoleGrantMobileResourceMenu) {
|
||||
// 获取记录的子节点
|
||||
const child = record.button;
|
||||
// 判断子节点是否被选中,如果有一个子节点未被选中,则返回false
|
||||
return child.every(field => !field.check);
|
||||
}
|
||||
|
||||
/** 提交之前转换数据 */
|
||||
function convertData() {
|
||||
const params: MobileMenu.GrantMobileResourceReq = {
|
||||
id: grantProps.id,
|
||||
GrantInfoList: []
|
||||
};
|
||||
// 遍历echoDatalist,获取每一项
|
||||
grantProps.echoDatalist.forEach(data => {
|
||||
// 如果data有menu属性
|
||||
if (data.menu) {
|
||||
// 遍历data.menu,获取每一项
|
||||
data.menu.forEach(item => {
|
||||
// 初始化grantInfo
|
||||
const grantInfo: MobileMenu.RelationRoleMobileResource = {
|
||||
menuId: 0,
|
||||
buttonInfo: []
|
||||
};
|
||||
// 如果被选中了
|
||||
if (item.nameCheck) {
|
||||
// 获取item的id
|
||||
grantInfo.menuId = item.id;
|
||||
// 遍历item.button,获取每一项
|
||||
item.button.forEach(button => {
|
||||
// 如果button被选中了
|
||||
if (button.check) {
|
||||
// 将button的id添加到grantInfo.buttonInfo中
|
||||
grantInfo.buttonInfo.push(button.id);
|
||||
}
|
||||
});
|
||||
// 将grantInfo添加到params.GrantInfoList中
|
||||
params.GrantInfoList.push(grantInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// 返回params
|
||||
return params;
|
||||
}
|
||||
|
||||
/** 提交 */
|
||||
function handleSubmit() {
|
||||
if (props.user) {
|
||||
dialogVisible.value = true;
|
||||
} else {
|
||||
let params = convertData();
|
||||
grantMobileResource(params);
|
||||
}
|
||||
}
|
||||
|
||||
/** 用户授权移动端资源提交 */
|
||||
const handleUserSubmit = () => {
|
||||
const param = convertData();
|
||||
grantMobileResource(param);
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
/** 授权移动端资源 */
|
||||
function grantMobileResource(params: MobileMenu.GrantMobileResourceReq) {
|
||||
// 使用移动端菜单API进行授权
|
||||
mobileMenuApi.grantRoleMobileResource(params).then(() => {
|
||||
onClose();
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单 */
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
grantProps.echoDatalist = [];
|
||||
grantProps.moduleId = 0;
|
||||
grantProps.firstShowMap = {};
|
||||
grantProps.tableData = [];
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,239 @@
|
||||
<!--
|
||||
* @Description: 授权权限表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:40
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`授权权限->${grantProps.name}`" form-size="1200px" @close="onClose">
|
||||
<el-alert
|
||||
title="注:此功能界面需要与代码查询条件配合使用,并非所有接口都需设置数据范围,多用于业务模块!"
|
||||
type="warning"
|
||||
show-icon
|
||||
style="margin-bottom: 10px"
|
||||
/>
|
||||
<ProTable ref="proTable" :columns="columns" :tool-button="false" :data="grantProps.tableData" row-key="api">
|
||||
<!-- 自定义api表头 -->
|
||||
<template #apiHeader>
|
||||
<el-row class="row-bg" justify="space-between">
|
||||
<el-col :span="12"><el-checkbox @change="onCheckAllChange"> 接口 </el-checkbox></el-col>
|
||||
<el-col :span="12"> <el-input v-model="searchValue" @input="searchApi" placeholder="请输入接口名称" /></el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<!-- 接口 -->
|
||||
<template #api="scope">
|
||||
<el-checkbox v-model="scope.row.check" @change="val => changeApi(scope.row, val)">
|
||||
{{ scope.row.api }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<!-- 数据范围 -->
|
||||
<template #dataScope="scope">
|
||||
<DataScopeSelector v-if="scope.row.check" v-model="scope.row.dataScope"></DataScopeSelector>
|
||||
</template>
|
||||
</ProTable>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysRole, sysRoleApi, sysUserApi, SysUser } from "@/api";
|
||||
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
||||
import DataScopeSelector from "./dataScopeSelector.vue";
|
||||
|
||||
const visible = ref(false); //授权权限表单是否显示
|
||||
const searchValue = ref(""); // 搜索的值
|
||||
// 数据范围列表
|
||||
const scopeOptions = SysRole.dataScopeOptions;
|
||||
|
||||
/** 授权权限表单参数 */
|
||||
interface GrantPermissionFormProps {
|
||||
/** id */
|
||||
id: number | string;
|
||||
/** 名称 */
|
||||
name: string;
|
||||
/** 表格数据 */
|
||||
tableData: SysRole.RoleGrantPermission[];
|
||||
/** 权限数据 */
|
||||
permissionData: SysRole.RoleGrantPermission[];
|
||||
}
|
||||
|
||||
//表单参数
|
||||
const grantProps = reactive<GrantPermissionFormProps>({
|
||||
id: 0,
|
||||
name: "",
|
||||
tableData: [],
|
||||
permissionData: []
|
||||
});
|
||||
|
||||
/** props */
|
||||
const props = defineProps<{
|
||||
/** 是否是用户 */
|
||||
user?: boolean;
|
||||
}>();
|
||||
|
||||
// 获取 ProTable DOM
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysRole.ResTreeSelector>[] = [
|
||||
// { type: "selection", width: 60 },
|
||||
{ prop: "api", label: "接口", width: 480, align: "left" },
|
||||
{ prop: "dataScope", label: "数据范围", align: "left" }
|
||||
];
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
async function onOpen(props: FormProps.Base<SysRole.SysRoleInfo | SysUser.SysUserInfo>) {
|
||||
grantProps.id = props.record.id || 0;
|
||||
grantProps.name = props.record.name || "";
|
||||
await loadData();
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
/** 加载数据 */
|
||||
async function loadData() {
|
||||
const params = { id: grantProps.id };
|
||||
// 获取权限树
|
||||
const tree = props.user ? await sysUserApi.permissionTreeSelector(params) : await sysRoleApi.permissionTreeSelector(params);
|
||||
// 获取当前角色拥有的权限
|
||||
const ownPer = props.user ? await sysUserApi.ownPermission(params) : await sysRoleApi.ownPermission(params);
|
||||
// 调用echoModuleData方法,将权限树和当前角色拥有的权限进行合并
|
||||
echoModuleData(tree.data, ownPer.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回显模块数据
|
||||
* @param apiList 权限列表
|
||||
* @param ownPermission 已拥有的权限
|
||||
*/
|
||||
function echoModuleData(apiList: string[], ownPermission: SysRole.RoleOwnPermission) {
|
||||
//遍历api列表
|
||||
apiList.forEach(item => {
|
||||
let data: SysRole.RoleGrantPermission = {
|
||||
api: item,
|
||||
dataScope: SysRole.dataScopeOptions[0],
|
||||
check: false
|
||||
};
|
||||
let api = subStrApi(item); // 截取api串中的中文及括号
|
||||
//找到对应的Api权限
|
||||
let grantInfo = ownPermission.grantInfoList.find(grantInfo => grantInfo.apiUrl === api);
|
||||
if (grantInfo) {
|
||||
data.check = true;
|
||||
let dataScope = scopeOptions.find(scope => scope.scopeCategory === grantInfo?.scopeCategory);
|
||||
// 找到对应的数据范围
|
||||
data.dataScope = { ...dataScope! }; //合并参数
|
||||
// 如果scopeCategory为自定义数据范围则赋值数据范围
|
||||
if (grantInfo.scopeCategory == SysRole.DataScopeEnum.SCOPE_ORG_DEFINE) {
|
||||
data.dataScope.scopeDefineOrgIdList = grantInfo.scopeDefineOrgIdList;
|
||||
}
|
||||
}
|
||||
grantProps.tableData.push(data);
|
||||
});
|
||||
grantProps.permissionData = grantProps.tableData; //保存权限数据
|
||||
}
|
||||
|
||||
/** 搜索api
|
||||
* @param value 搜索值
|
||||
*/
|
||||
function searchApi(value: string) {
|
||||
console.log("[ value ] >", value);
|
||||
//如果搜索值为空,就显示全部
|
||||
if (!value) {
|
||||
grantProps.tableData = grantProps.permissionData;
|
||||
return;
|
||||
}
|
||||
//过滤api
|
||||
grantProps.tableData = grantProps.permissionData.filter(item => {
|
||||
return item.api.indexOf(value) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
/** 提交之前转换数据 */
|
||||
function convertData() {
|
||||
//将tableData的数据合并到permissionData的中
|
||||
grantProps.permissionData.forEach(item => {
|
||||
let data = grantProps.tableData.find(data => data.api === item.api);
|
||||
if (data) {
|
||||
item.check = data.check;
|
||||
item.dataScope = data.dataScope;
|
||||
}
|
||||
});
|
||||
//定义参数
|
||||
const params: SysRole.GrantPermissionReq = {
|
||||
id: grantProps.id,
|
||||
grantInfoList: []
|
||||
};
|
||||
// 遍历表格数据赋值给参数列表
|
||||
grantProps.permissionData.forEach(item => {
|
||||
if (item.check) {
|
||||
let grantInfo: SysRole.RelationRolePermission = {
|
||||
apiUrl: subStrApi(item.api),
|
||||
scopeCategory: item.dataScope.scopeCategory,
|
||||
scopeDefineOrgIdList: item.dataScope.scopeDefineOrgIdList
|
||||
};
|
||||
params.grantInfoList.push(grantInfo);
|
||||
}
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
/** 截取api串中的中文及括号 */
|
||||
function subStrApi(api: string) {
|
||||
return api.substring(0, api.indexOf("[")); // 截取api串中的中文及括号
|
||||
}
|
||||
|
||||
/** 选中api */
|
||||
function changeApi(row: SysRole.RoleGrantPermission, val: any) {
|
||||
row.check = val; //设置选中状态
|
||||
//如果选中了
|
||||
if (val) {
|
||||
row.dataScope.check = true;
|
||||
} else {
|
||||
//去掉已选中的
|
||||
row.dataScope.check = false;
|
||||
if (row.dataScope.scopeCategory == SysRole.DataScopeEnum.SCOPE_ORG_DEFINE) {
|
||||
row.dataScope.scopeDefineOrgIdList = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 全选/取消 */
|
||||
function onCheckAllChange(val: any) {
|
||||
grantProps.tableData.forEach(item => {
|
||||
changeApi(item, val);
|
||||
});
|
||||
}
|
||||
|
||||
/** 提交 */
|
||||
function handleSubmit() {
|
||||
let params = convertData();
|
||||
//判断是否是用户
|
||||
if (props.user) {
|
||||
sysUserApi.grantPermission(params).then(() => {
|
||||
onClose();
|
||||
});
|
||||
} else {
|
||||
sysRoleApi.grantPermission(params).then(() => {
|
||||
onClose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭表单 */
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
grantProps.tableData = [];
|
||||
grantProps.permissionData = [];
|
||||
searchValue.value = "";
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,414 @@
|
||||
<!--
|
||||
* @Description: 授权资源表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:49
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<form-container v-model="visible" :title="`授权资源->${grantProps.name}`" form-size="1200px" @close="onClose">
|
||||
<el-alert title="非超管角色不可被授权系统模块菜单资源" type="warning" show-icon style="margin-bottom: 10px" />
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
:columns="columns"
|
||||
:tool-button="false"
|
||||
:cell-style="{ padding: '0px' }"
|
||||
:row-style="{ height: '0' }"
|
||||
:data="grantProps.tableData"
|
||||
:pagination="false"
|
||||
:span-method="objectSpanMethod"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
<s-radio-group
|
||||
v-model="grantProps.moduleId"
|
||||
:options="grantProps.echoDatalist"
|
||||
value-key="id"
|
||||
label-key="title"
|
||||
button
|
||||
class="mb-2"
|
||||
@change="moduleClick"
|
||||
>
|
||||
<template #radio="{ item }">
|
||||
<el-space>
|
||||
<svg-icon :icon="item.icon" class="h-4 w-4"></svg-icon>
|
||||
{{ item.title }}
|
||||
</el-space>
|
||||
</template>
|
||||
</s-radio-group>
|
||||
</template>
|
||||
<!-- 一级目录 -->
|
||||
<template #parentName="scope">
|
||||
<el-checkbox v-model="scope.row.parentCheck" @change="val => changeParent(scope.row, val)">
|
||||
{{ scope.row.parentName }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<!-- 菜单 -->
|
||||
<template #title="scope">
|
||||
<el-checkbox v-model="scope.row.nameCheck" @change="val => changeSub(scope.row, val)">
|
||||
{{ scope.row.title }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<!-- 按钮授权 -->
|
||||
<template #button="scope">
|
||||
<template v-if="scope.row.button.length > 0">
|
||||
<template v-for="(item, index) in scope.row.button" :key="item.id">
|
||||
<el-checkbox v-model="item.check" style="margin-right: 5px" @change="val => changeChildCheckBox(scope.row, val)">
|
||||
{{ item.title }}
|
||||
</el-checkbox>
|
||||
<br v-if="(index + 1) % 5 === 0" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ProTable>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
<el-dialog v-model="dialogVisible" title="请选择数据范围" width="30%" destroy-on-close>
|
||||
<DataScopeSelector v-model="grantProps.defaultDataScope"></DataScopeSelector>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleUserSubmit"> 确定 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysRole, SysUser, sysRoleApi, sysUserApi } from "@/api";
|
||||
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
||||
import type { TableColumnCtx } from "element-plus";
|
||||
import DataScopeSelector from "./dataScopeSelector.vue";
|
||||
const visible = ref(false); //授权资源表单是否显示
|
||||
const dialogVisible = ref(false); // 数据范围弹窗显示
|
||||
|
||||
/** 授权资源表单参数接口 */
|
||||
interface GrantResourceFormProps {
|
||||
/** id */
|
||||
id: number | string;
|
||||
/** 名称 */
|
||||
name: string;
|
||||
/** 模块id */
|
||||
moduleId: number | string;
|
||||
/** 资源树 */
|
||||
resTree: SysRole.ResTreeSelector[];
|
||||
/** 已拥有的资源 */
|
||||
ownResource: SysRole.RoleOwnResource | null;
|
||||
/** 一级菜单索引 */
|
||||
firstShowMap: Record<string, number[]>;
|
||||
/** 回显数据列表 */
|
||||
echoDatalist: SysRole.ResTreeSelector[];
|
||||
/** 表格数据 */
|
||||
tableData: SysRole.RoleGrantResourceMenu[];
|
||||
//默认数据范围,用户才有
|
||||
defaultDataScope: SysRole.DefaultDataScope;
|
||||
}
|
||||
|
||||
/** 授权资源表单参数 */
|
||||
const grantProps = reactive<GrantResourceFormProps>({
|
||||
id: 0,
|
||||
name: "",
|
||||
moduleId: 0,
|
||||
resTree: [],
|
||||
ownResource: null,
|
||||
firstShowMap: {},
|
||||
echoDatalist: [],
|
||||
tableData: [],
|
||||
defaultDataScope: SysRole.dataScopeOptions[0]
|
||||
});
|
||||
|
||||
/** props */
|
||||
const props = defineProps<{
|
||||
/** 是否是用户 */
|
||||
user?: boolean;
|
||||
}>();
|
||||
|
||||
// 获取 ProTable DOM
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysRole.ResTreeSelector>[] = [
|
||||
// { type: "selection", width: 80 },
|
||||
{ prop: "parentName", label: "一级目录", width: 150, align: "left" },
|
||||
{ prop: "title", label: "菜单", width: 180, align: "left" },
|
||||
{ prop: "button", label: "按钮授权", align: "left" }
|
||||
];
|
||||
|
||||
/** 列合并 */
|
||||
interface SpanMethodProps {
|
||||
// 行
|
||||
row: SysRole.RoleGrantResourceMenu;
|
||||
// 列
|
||||
column: TableColumnCtx<SysRole.RoleGrantResourceMenu>;
|
||||
// 行索引
|
||||
rowIndex: number;
|
||||
// 列索引
|
||||
columnIndex: number;
|
||||
}
|
||||
|
||||
/** 列合并*/
|
||||
const objectSpanMethod = ({ row, rowIndex, columnIndex }: SpanMethodProps) => {
|
||||
// 如果列索引为0
|
||||
if (columnIndex == 0) {
|
||||
// 获取父名称
|
||||
const parentName = row.parentName;
|
||||
// 获取父名称的索引数组
|
||||
const indexArr = grantProps.firstShowMap[parentName];
|
||||
// 如果行索引等于索引数组的第一个
|
||||
if (rowIndex === indexArr[0]) {
|
||||
return { rowspan: indexArr.length, colspan: 1 };
|
||||
} else {
|
||||
return { rowspan: 0, colspan: 0 };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
async function onOpen(props: FormProps.Base<SysRole.SysRoleInfo | SysUser.SysUserInfo>) {
|
||||
grantProps.id = props.record.id || 0;
|
||||
grantProps.name = props.record.name || "";
|
||||
await loadData();
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
/** 加载数据 */
|
||||
async function loadData() {
|
||||
if (grantProps.echoDatalist.length > 0) {
|
||||
let data = grantProps.echoDatalist.find(f => f.id === grantProps.moduleId)!.menu;
|
||||
grantProps.tableData = data;
|
||||
} else {
|
||||
// 获取角色资源树
|
||||
await sysRoleApi.resourceTreeSelector().then(res => {
|
||||
grantProps.resTree = res.data;
|
||||
grantProps.moduleId = res.data[0].id;
|
||||
});
|
||||
await getResources(); // 获取资源
|
||||
// 如果有已拥有的资源
|
||||
if (grantProps.ownResource) {
|
||||
// 回显模块数据
|
||||
// 获取echoDatalist
|
||||
grantProps.echoDatalist = echoModuleData(grantProps.resTree, grantProps.ownResource);
|
||||
// 获取模块id
|
||||
grantProps.moduleId = grantProps.resTree[0].id;
|
||||
// 获取表格数据
|
||||
grantProps.tableData = grantProps.echoDatalist[0].menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取资源 */
|
||||
async function getResources() {
|
||||
if (props.user) {
|
||||
// 获取用户拥有资源
|
||||
await sysUserApi.ownResource({ id: grantProps.id }).then(res => {
|
||||
grantProps.ownResource = res.data;
|
||||
});
|
||||
} else {
|
||||
// 获取角色拥有资源
|
||||
await sysRoleApi.ownResource({ id: grantProps.id }).then(res => {
|
||||
grantProps.ownResource = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回显模块数据
|
||||
* @param resTree 资源树
|
||||
* @param ownResource 已拥有的资源
|
||||
*/
|
||||
function echoModuleData(resTree: SysRole.ResTreeSelector[], ownResource: SysRole.RoleOwnResource) {
|
||||
// 通过应用循环
|
||||
resTree.forEach(module => {
|
||||
//如果有菜单
|
||||
if (module.menu) {
|
||||
module.menu.forEach(menu => {
|
||||
const menuCheck = ref(0);
|
||||
// 如果已授权资源信息列表有数据
|
||||
if (ownResource.grantInfoList.length > 0) {
|
||||
// 通过已授权资源信息列表循环
|
||||
ownResource.grantInfoList.forEach(grantInfo => {
|
||||
// 如果已授权资源信息列表中的菜单id等于当前菜单id
|
||||
if (menu.id == grantInfo.menuId) {
|
||||
menuCheck.value++;
|
||||
// 处理按钮
|
||||
if (grantInfo.buttonInfo.length > 0) {
|
||||
// 通过已授权资源信息列表中的按钮循环
|
||||
grantInfo.buttonInfo.forEach(button => {
|
||||
menu.button.forEach(itemButton => {
|
||||
// 如果已授权资源信息列表中的按钮id等于当前按钮id
|
||||
if (button === itemButton.id) {
|
||||
itemButton.check = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 回显前面的2个
|
||||
if (menuCheck.value > 0) {
|
||||
menu.parentCheck = true;
|
||||
menu.nameCheck = true;
|
||||
}
|
||||
});
|
||||
// 缓存加入索引
|
||||
module.menu.forEach((item: SysRole.RoleGrantResourceMenu, index) => {
|
||||
// 下面就是用来知道不同的一级菜单里面有几个二级菜单,以及他们所在的索引
|
||||
if (grantProps.firstShowMap[item.parentName]) {
|
||||
grantProps.firstShowMap[item.parentName].push(index);
|
||||
} else {
|
||||
grantProps.firstShowMap[item.parentName] = [index];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return resTree;
|
||||
}
|
||||
|
||||
/** 选择模块 */
|
||||
function moduleClick() {
|
||||
loadData();
|
||||
}
|
||||
|
||||
/** 一级目录点击 */
|
||||
function changeParent(row: SysRole.RoleGrantResourceMenu, val: any) {
|
||||
row.parentCheck = val;
|
||||
// 通过这个应用id,找到应用下的所有菜单
|
||||
const moduleMenu = grantProps.echoDatalist.find(f => row.module === f.id);
|
||||
const parentName = row.parentName;
|
||||
// 获取同一级菜单的所有索引
|
||||
const indexArr = grantProps.firstShowMap[parentName];
|
||||
indexArr.forEach(indexItem => {
|
||||
// 获取同一级菜单的所有行
|
||||
const row = moduleMenu?.menu[indexItem];
|
||||
|
||||
if (row)
|
||||
// 给这些菜单的索引去勾选
|
||||
changeSub(row, val);
|
||||
});
|
||||
}
|
||||
|
||||
/** 二级菜单的点击 */
|
||||
function changeSub(row: SysRole.RoleGrantResourceMenu, val: any) {
|
||||
// 选中二级菜单
|
||||
row.nameCheck = val;
|
||||
row.button.forEach(item => {
|
||||
// 选中按钮
|
||||
item.check = val;
|
||||
});
|
||||
}
|
||||
|
||||
/** 权限按钮点击 */
|
||||
function changeChildCheckBox(row: SysRole.RoleGrantResourceMenu, val: any) {
|
||||
// 如果val为false,并且row的子菜单没有选中,则取消所有按钮的选中
|
||||
if (!val && checkAllChildNotChecked(row)) {
|
||||
// 这里注释掉勾选去掉所有按钮,联动去掉菜单
|
||||
/*record.nameCheck = false
|
||||
record.parentCheck = false*/
|
||||
} else if (val) {
|
||||
// 如果val为true,则将row的nameCheck和parentCheck属性设置为true
|
||||
row.nameCheck = val;
|
||||
row.parentCheck = val;
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查所有子级是否都没有勾选 */
|
||||
function checkAllChildNotChecked(record: SysRole.RoleGrantResourceMenu) {
|
||||
// 获取记录的子节点
|
||||
const child = record.button;
|
||||
// 判断子节点是否被选中,如果有一个子节点未被选中,则返回false
|
||||
return child.every(field => !field.check);
|
||||
}
|
||||
|
||||
/** 提交之前转换数据 */
|
||||
function convertData() {
|
||||
const params: SysRole.GrantResourceReq = {
|
||||
id: grantProps.id,
|
||||
grantInfoList: []
|
||||
};
|
||||
// 遍历echoDatalist,获取每一项
|
||||
grantProps.echoDatalist.forEach(data => {
|
||||
// 如果data有menu属性
|
||||
if (data.menu) {
|
||||
// 遍历data.menu,获取每一项
|
||||
data.menu.forEach(item => {
|
||||
// 初始化grantInfo
|
||||
const grantInfo: SysRole.RelationRoleResource = {
|
||||
menuId: 0,
|
||||
buttonInfo: []
|
||||
};
|
||||
// 如果被选中了
|
||||
if (item.nameCheck) {
|
||||
// 获取item的id
|
||||
grantInfo.menuId = item.id;
|
||||
// 遍历item.button,获取每一项
|
||||
item.button.forEach(button => {
|
||||
// 如果button被选中了
|
||||
if (button.check) {
|
||||
// 将button的id添加到grantInfo.buttonInfo中
|
||||
grantInfo.buttonInfo.push(button.id as never);
|
||||
}
|
||||
});
|
||||
// 将grantInfo添加到params.grantInfoList中
|
||||
params.grantInfoList.push(grantInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// 返回params
|
||||
return params;
|
||||
}
|
||||
|
||||
/** 提交 */
|
||||
function handleSubmit() {
|
||||
if (props.user) {
|
||||
dialogVisible.value = true;
|
||||
} else {
|
||||
let params = convertData();
|
||||
grantResource(params);
|
||||
}
|
||||
}
|
||||
|
||||
/** 用户授权资源提交 */
|
||||
const handleUserSubmit = () => {
|
||||
const param = convertData();
|
||||
param.defaultDataScope = grantProps.defaultDataScope;
|
||||
grantResource(param);
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
/** 授权资源 */
|
||||
function grantResource(params: SysRole.GrantResourceReq) {
|
||||
// 根据用户判断走哪个方法
|
||||
if (props.user) {
|
||||
sysUserApi.grantResource(params).then(() => {
|
||||
onClose();
|
||||
});
|
||||
} else {
|
||||
sysRoleApi.grantResource(params).then(() => {
|
||||
onClose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 关闭表单 */
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
grantProps.echoDatalist = [];
|
||||
grantProps.moduleId = 0;
|
||||
grantProps.firstShowMap = {};
|
||||
grantProps.tableData = [];
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,242 @@
|
||||
<!--
|
||||
* @Description: 角色管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:53
|
||||
!-->
|
||||
<template>
|
||||
<div class="main-box">
|
||||
<TreeFilter
|
||||
ref="treeFilter"
|
||||
label="name"
|
||||
title="组织列表"
|
||||
:request-api="sysOrgApi.tree"
|
||||
:default-value="initParam.orgId"
|
||||
@change="changeTreeFilter"
|
||||
/>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" :columns="columns" :request-api="sysRoleApi.page" :init-param="initParam">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="角色" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="角色"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选角色')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<el-space>
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.name}】角色`)" />
|
||||
<el-dropdown @command="handleCommand">
|
||||
<el-link type="primary" :underline="false" :icon="ArrowDown"> 授权 </el-link>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="(item, index) in cmdEnum" :key="index" :command="command(scope.row, item)">{{ item }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-space>
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 授权资源组件 -->
|
||||
<GrantResource ref="grantResourceRef" />
|
||||
<!-- 授权移动端资源组件 -->
|
||||
<GrantMobileResource ref="grantMobileResourceRef" />
|
||||
<!-- 授权权限组件 -->
|
||||
<GrantPermission ref="grantPermissionRef" />
|
||||
<!-- 用户选择器 -->
|
||||
<user-selector
|
||||
ref="userSelectorRef"
|
||||
:org-tree-api="sysOrgApi.tree"
|
||||
:position-tree-api="sysPositionApi.tree"
|
||||
:role-tree-api="sysRoleApi.tree"
|
||||
:user-selector-api="sysUserApi.selector"
|
||||
multiple
|
||||
@successful="handleChooseUser"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="sysRole">
|
||||
import { sysOrgApi, sysRoleApi, SysRole, SysUser, sysUserApi, sysPositionApi } from "@/api";
|
||||
import { ArrowDown } from "@element-plus/icons-vue";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import TreeFilter from "@/components/TreeFilter/index.vue";
|
||||
import Form from "./components/form.vue";
|
||||
import GrantResource from "./components/grantResource.vue";
|
||||
import GrantMobileResource from "./components/grantMobileResource.vue";
|
||||
import GrantPermission from "./components/grantPermission.vue";
|
||||
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 角色类型选项
|
||||
const roleCategoryOptions = dictStore.getDictList(SysDictEnum.ROLE_CATEGORY);
|
||||
// 状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
// 组织列表组件
|
||||
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
|
||||
|
||||
interface InitParam {
|
||||
orgId: number | string;
|
||||
}
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive<InitParam>({ orgId: 0 });
|
||||
// 获取 ProTable 元素
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysRole.SysRoleInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "name", label: "角色名称", search: { el: "input" } },
|
||||
{
|
||||
prop: "category",
|
||||
label: "角色分类",
|
||||
enum: roleCategoryOptions,
|
||||
search: { el: "select" }
|
||||
},
|
||||
{ prop: "code", label: "角色编码", search: { el: "input" } },
|
||||
{ prop: "status", label: "状态", enum: statusOptions, search: { el: "select" } },
|
||||
{ prop: "sortCode", label: "排序" },
|
||||
{ prop: "createTime", label: "创建时间" },
|
||||
{ prop: "operation", label: "操作", width: 230, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | SysRole.SysRoleInfo = {}) {
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(sysRoleApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh(); //刷新表格
|
||||
treeFilter.value?.refresh(); //刷新树形筛选器
|
||||
}
|
||||
|
||||
/** 部门切换 */
|
||||
function changeTreeFilter(val: number | string) {
|
||||
proTable.value!.pageable.pageNum = 1;
|
||||
if (val != "") {
|
||||
// 如果传入的val不为空,则将val赋值给initParam.parentId
|
||||
initParam.orgId = val;
|
||||
} else {
|
||||
// 否则将initParam.parentId赋值为0
|
||||
initParam.orgId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 更多下拉菜单命令枚举 */
|
||||
enum cmdEnum {
|
||||
GrantResource = "授权资源",
|
||||
// GrantMobileResource = "授权移动端资源",
|
||||
GrantPermission = "授权权限",
|
||||
GrantUser = "授权用户"
|
||||
}
|
||||
|
||||
/** 下拉菜单参数接口 */
|
||||
interface Command {
|
||||
row: SysRole.SysRoleInfo;
|
||||
command: cmdEnum;
|
||||
}
|
||||
|
||||
/**配置command的参数 */
|
||||
function command(row: SysRole.SysRoleInfo, command: cmdEnum): Command {
|
||||
return {
|
||||
row: row,
|
||||
command: command
|
||||
};
|
||||
}
|
||||
|
||||
const grantResourceRef = ref<InstanceType<typeof GrantResource> | null>(null); //授权资源组件引用
|
||||
const grantMobileResourceRef = ref<InstanceType<typeof GrantMobileResource> | null>(null); //授权移动端资源组件引用
|
||||
const grantPermissionRef = ref<InstanceType<typeof GrantPermission> | null>(null); //授权权限组件引用
|
||||
const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用
|
||||
const roleId = ref<number | string>(0); //角色id
|
||||
|
||||
/**
|
||||
* 更多下拉菜单点击事件
|
||||
* @param command
|
||||
*/
|
||||
function handleCommand(command: Command) {
|
||||
switch (command.command) {
|
||||
case cmdEnum.GrantUser:
|
||||
roleId.value = command.row.id; //获取角色id
|
||||
sysRoleApi.ownUser({ id: command.row.id }).then(res => {
|
||||
userSelectorRef.value?.showSelector(res.data); //显示用户选择器
|
||||
});
|
||||
break;
|
||||
case cmdEnum.GrantResource:
|
||||
// 打开授权资源组件
|
||||
grantResourceRef.value?.onOpen({
|
||||
opt: FormOptEnum.EDIT,
|
||||
record: command.row,
|
||||
successful: RefreshTable
|
||||
});
|
||||
break;
|
||||
case cmdEnum.GrantMobileResource:
|
||||
// 打开授权移动端资源组件
|
||||
grantMobileResourceRef.value?.onOpen({
|
||||
opt: FormOptEnum.EDIT,
|
||||
record: command.row,
|
||||
successful: RefreshTable
|
||||
});
|
||||
break;
|
||||
case cmdEnum.GrantPermission:
|
||||
// 打开授权权限组件
|
||||
grantPermissionRef.value?.onOpen({
|
||||
opt: FormOptEnum.EDIT,
|
||||
record: command.row,
|
||||
successful: RefreshTable
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择用户 */
|
||||
function handleChooseUser(data: SysUser.SysUserInfo[]) {
|
||||
//组装参数
|
||||
const grantUser: SysRole.GrantUserReq = {
|
||||
id: roleId.value,
|
||||
grantInfoList: data.map(item => item.id) as number[] | string[]
|
||||
};
|
||||
sysRoleApi.grantUser(grantUser);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<!--
|
||||
* @Description: 单页管理表单页面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:43:59
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${spaProps.opt}单页`" form-size="600px">
|
||||
<el-form
|
||||
ref="spaFormRef"
|
||||
:rules="rules"
|
||||
:disabled="spaProps.disabled"
|
||||
:model="spaProps.record"
|
||||
:hide-required-asterisk="spaProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<!-- 基本设置 -->
|
||||
<el-divider content-position="left">基本设置</el-divider>
|
||||
<s-form-item label="单页名称" prop="title">
|
||||
<s-input v-model="spaProps.record.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="单页类型" prop="menuType">
|
||||
<s-radio-group v-model="spaProps.record.menuType" :options="spaTypeOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="图标" prop="icon">
|
||||
<SelectIconPlus v-model:icon-value="spaProps.record.icon!" />
|
||||
</s-form-item>
|
||||
<div v-if="spaProps.record.menuType === MenuTypeDictEnum.MENU">
|
||||
<s-form-item label="路由地址" prop="path">
|
||||
<s-input v-model="spaProps.record.path" placeholder="请填写路由地址,例:/home/index"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="组件名称" prop="name">
|
||||
<s-input v-model="spaProps.record.name">
|
||||
<template #prepend>setup name=</template>
|
||||
</s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="组件地址" prop="component">
|
||||
<s-input v-model="spaProps.record.component">
|
||||
<template #prepend>src/views/</template>
|
||||
</s-input>
|
||||
</s-form-item>
|
||||
</div>
|
||||
<div v-else>
|
||||
<s-form-item label="链接地址:" prop="path">
|
||||
<s-input v-model="spaProps.record.path" placeholder="请填写链接地址,例:http://www.baidu.com"></s-input>
|
||||
</s-form-item>
|
||||
</div>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="spaProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
<s-form-item label="说明" prop="description">
|
||||
<s-input v-model="spaProps.record.description"></s-input>
|
||||
</s-form-item>
|
||||
<!-- 功能设置 -->
|
||||
<el-divider content-position="left">功能设置</el-divider>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="设置主页" prop="isHome">
|
||||
<s-radio-group v-model="spaProps.record.isHome" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="隐藏页面" prop="isHide">
|
||||
<s-radio-group v-model="spaProps.record.isHide" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="页面全屏" prop="isFull">
|
||||
<s-radio-group v-model="spaProps.record.isFull" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="固定标签页" prop="isAffix">
|
||||
<s-radio-group v-model="spaProps.record.isAffix" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="路由缓存" prop="isKeepAlive">
|
||||
<s-radio-group v-model="spaProps.record.isKeepAlive" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!spaProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Spa, spaApi } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
|
||||
// 单页类型选项
|
||||
const spaTypeOptions = dictStore
|
||||
.getDictList(SysDictEnum.MENU_TYPE)
|
||||
.filter((item: { value: MenuTypeDictEnum }) => item.value == MenuTypeDictEnum.MENU || item.value == MenuTypeDictEnum.LINK);
|
||||
// 是否选项
|
||||
const yesOptions = dictStore.getDictList(SysDictEnum.YES_NO, true);
|
||||
|
||||
// 表单参数
|
||||
const spaProps = reactive<FormProps.Base<Spa.SpaInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入单页名称")],
|
||||
menuType: [required("请选择单页类型")],
|
||||
path: [required("请输入路由地址")],
|
||||
name: [required("请输入组件名称")],
|
||||
component: [required("请输入组件地址")],
|
||||
sortCode: [required("请输入排序")],
|
||||
icon: [required("请选择图标")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<Spa.SpaInfo>) {
|
||||
Object.assign(spaProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
spaProps.record.menuType = MenuTypeDictEnum.MENU;
|
||||
spaProps.record.sortCode = 99;
|
||||
spaProps.record.isHome = true;
|
||||
spaProps.record.isHide = true;
|
||||
spaProps.record.isFull = true;
|
||||
spaProps.record.isAffix = true;
|
||||
spaProps.record.isKeepAlive = true;
|
||||
}
|
||||
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
spaApi.detail({ id: props.record.id }).then(res => {
|
||||
spaProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const spaFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
spaFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await spaApi
|
||||
.submitForm(spaProps.record, spaProps.record.id != undefined)
|
||||
.then(() => {
|
||||
spaProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.s-input-group__prepend) {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<!--
|
||||
* @Description: 单页管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:44:05
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="单页列表" :columns="columns" :request-api="spaApi.page">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="单页" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="单页"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选页面')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表格 菜单类型 按钮 -->
|
||||
<template #menuType="scope">
|
||||
<el-space wrap>
|
||||
<el-tag v-if="scope.row.menuType === MenuTypeDictEnum.MENU" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.MENU)
|
||||
}}</el-tag>
|
||||
<el-tag v-else-if="scope.row.menuType === MenuTypeDictEnum.LINK" type="warning">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, MenuTypeDictEnum.LINK)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="info">{{ dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType) }}</el-tag>
|
||||
<el-tag v-if="scope.row.isHome === true" type="danger">首页</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.title}】页面`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="sysSpa">
|
||||
import { spaApi } from "@/api";
|
||||
import { Spa } from "@/api/interface";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { FormOptEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
|
||||
import Form from "./components/form.vue";
|
||||
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
const dictStore = useDictStore();
|
||||
|
||||
// 单页类型选项
|
||||
const spaTypeOptions = dictStore
|
||||
.getDictList(SysDictEnum.MENU_TYPE)
|
||||
.filter((item: { value: MenuTypeDictEnum }) => item.value == MenuTypeDictEnum.MENU || item.value == MenuTypeDictEnum.LINK);
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<Spa.SpaInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "searchKey", label: "关键字", search: { el: "input" }, isShow: false },
|
||||
{ prop: "title", label: "单页名称" },
|
||||
{
|
||||
prop: "icon",
|
||||
label: "菜单图标",
|
||||
render: scope => {
|
||||
return <svg-icon icon={scope.row.icon} class="h-6 w-6" />;
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: "menuType",
|
||||
label: "单页类型",
|
||||
enum: spaTypeOptions,
|
||||
search: { el: "select" }
|
||||
},
|
||||
{ prop: "path", label: "路由地址" },
|
||||
{ prop: "component", label: "组件路径" },
|
||||
{ prop: "sortCode", label: "排序" },
|
||||
{ prop: "description", label: "说明" },
|
||||
{ prop: "createTime", label: "创建时间" },
|
||||
{ prop: "operation", label: "操作", width: 200, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | Spa.SpaInfo = {}) {
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(spaApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<!--
|
||||
* @Description: 权限按钮批量表单页面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:42:29
|
||||
!-->
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="批量新增按钮" width="500px">
|
||||
<el-form ref="buttonFormRef" :rules="rules" :model="buttonProps" label-width="auto">
|
||||
<s-form-item label="权限简称" prop="title">
|
||||
<s-input v-model="buttonProps.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="编码前缀" prop="code">
|
||||
<s-input v-model="buttonProps.code"></s-input>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button primary @click="handleSubmit"> 确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mobileButtonApi, Button } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus/es/components/form";
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 表单参数接口
|
||||
interface ButtonProps extends Button.Batch {
|
||||
/** 成功之后的方法 */
|
||||
successful?: () => void;
|
||||
}
|
||||
|
||||
// 表单参数
|
||||
const buttonProps = reactive<ButtonProps>({
|
||||
parentId: 0,
|
||||
title: "",
|
||||
code: ""
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入权限简称")],
|
||||
code: [required("请输入编码前缀")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: ButtonProps) {
|
||||
Object.assign(buttonProps, props); //合并参数
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const buttonFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
buttonFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await mobileButtonApi
|
||||
.batch(buttonProps)
|
||||
.then(() => {
|
||||
buttonProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<!--
|
||||
* @Description: 权限按钮表单页面
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:42:36
|
||||
!-->
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="`${buttonProps.opt}按钮`" width="500px">
|
||||
<el-form
|
||||
ref="buttonFormRef"
|
||||
:rules="rules"
|
||||
:disabled="buttonProps.disabled"
|
||||
:model="buttonProps.record"
|
||||
:hide-required-asterisk="buttonProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="按钮名称" prop="title">
|
||||
<s-input v-model="buttonProps.record.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="按钮编码" prop="code">
|
||||
<s-input v-model="buttonProps.record.code"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="buttonProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
<s-form-item label="说明" prop="description">
|
||||
<s-input v-model="buttonProps.record.description"></s-input>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!buttonProps.disabled" primary @click="handleSubmit"> 确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mobileButtonApi, Button } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus/es/components/form";
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 表单参数
|
||||
const buttonProps = reactive<FormProps.Base<Button.ButtonInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入单页名称")],
|
||||
code: [required("请输入按钮编码")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<Button.ButtonInfo>) {
|
||||
Object.assign(buttonProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
buttonProps.record.sortCode = 99;
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
mobileButtonApi.detail({ id: props.record.id }).then(res => {
|
||||
buttonProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const buttonFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
buttonFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await mobileButtonApi
|
||||
.submitForm(buttonProps.record, buttonProps.record.id != undefined)
|
||||
.then(() => {
|
||||
buttonProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<!--
|
||||
* @Description: 权限按钮管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:42:52
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<form-container v-model="visible" title="权限按钮" form-size="60%">
|
||||
<div class="table-box min-h-300px">
|
||||
<ProTable ref="proTable" title="按钮列表" class="table" :columns="columns" :request-api="getPage">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="按钮" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button @click="onOpenBatch()"> 批量新增 </s-button>
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="按钮"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选按钮')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.title}】按钮`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="onClose"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 批量新增 -->
|
||||
<Batch ref="batchRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mobileButtonApi, Button } from "@/api";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import Form from "./components/form.vue";
|
||||
import Batch from "./components/batch.vue";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const parentId = ref<number | string>(0); //按钮父Id
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<Button.ButtonInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "title", label: "名称" },
|
||||
{ prop: "code", label: "编码" },
|
||||
{ prop: "sortCode", label: "排序" },
|
||||
{ prop: "description", label: "说明" },
|
||||
{ prop: "operation", label: "操作", width: 150, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | Button.ButtonInfo = { parentId: parentId.value }) {
|
||||
formRef.value?.onOpen({
|
||||
opt: opt,
|
||||
record: record,
|
||||
successful: () => RefreshTable()
|
||||
});
|
||||
}
|
||||
|
||||
// 批量新增引用
|
||||
const batchRef = ref<InstanceType<typeof Batch> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开批量新增表单
|
||||
*/
|
||||
function onOpenBatch() {
|
||||
batchRef.value?.onOpen({
|
||||
parentId: parentId.value,
|
||||
title: "",
|
||||
code: "",
|
||||
successful: RefreshTable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(mobileButtonApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpenTable(menuId: number | string) {
|
||||
visible.value = true;
|
||||
parentId.value = menuId;
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按钮分页数据
|
||||
* 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
* 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
|
||||
*/
|
||||
function getPage(params: any) {
|
||||
let newParams = JSON.parse(JSON.stringify(params)); //转换成json字符串再转换成json对象
|
||||
newParams.parentId = parentId.value; //按钮父Id
|
||||
return mobileButtonApi.page(newParams);
|
||||
}
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpenTable
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!--
|
||||
* @Description: 更改菜单模块
|
||||
* @Author: huguodong
|
||||
* @Date: 2025-07-02 13:52:09
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="'更改模块->' + changeModuleProps.title" form-size="450px">
|
||||
<el-form ref="changeModuleFormRef" :rules="rules" :model="changeModuleProps" class="mt-20px">
|
||||
<s-form-item label="所属模块" prop="module">
|
||||
<s-select v-model="changeModuleProps.module" :options="changeModuleProps.moduleOptions"></s-select>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mobileMenuApi } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus/es/components/form";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
/** 表单参数 */
|
||||
interface ChangeModuleProps {
|
||||
/** 菜单id */
|
||||
id: number | string;
|
||||
/** 模块id */
|
||||
module: number | string;
|
||||
/** 菜单名称 */
|
||||
title: string;
|
||||
/** 模块列表 */
|
||||
moduleOptions: { label: string; value: number | string }[];
|
||||
/** 表单实例 */
|
||||
successful?: () => void;
|
||||
}
|
||||
/** 表单参数 */
|
||||
const changeModuleProps = reactive<ChangeModuleProps>({
|
||||
id: 0,
|
||||
module: 0,
|
||||
title: "",
|
||||
moduleOptions: []
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
module: [required("请选择所属模块")]
|
||||
});
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const changeModuleFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
changeModuleFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await mobileMenuApi
|
||||
.changeModule({
|
||||
id: changeModuleProps.id,
|
||||
module: changeModuleProps.module
|
||||
})
|
||||
.then(() => {
|
||||
//提交成功
|
||||
changeModuleProps.successful?.();
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 打开表单
|
||||
* @param title 菜单名称
|
||||
* @param module 模块id
|
||||
*/
|
||||
function onOpen(moduleProps: ChangeModuleProps) {
|
||||
Object.assign(changeModuleProps, moduleProps); //合并参数
|
||||
visible.value = true; //显示表单
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<!--
|
||||
* @Description: 菜单管理表单页面
|
||||
* @Author: superAdmin
|
||||
* @Date: 2025-06-26 16:41:59
|
||||
!-->
|
||||
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${menuProps.opt}菜单`" form-size="600px">
|
||||
<el-form
|
||||
ref="menuFormRef"
|
||||
:rules="rules"
|
||||
:disabled="menuProps.disabled"
|
||||
:model="menuProps.record"
|
||||
:hide-required-asterisk="menuProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="菜单名称" prop="title">
|
||||
<s-input v-model="menuProps.record.title"></s-input>
|
||||
</s-form-item>
|
||||
|
||||
<s-form-item label="菜单类型" prop="menuType">
|
||||
<s-radio-group v-model="menuProps.record.menuType" :options="menuTypeOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="上级菜单" prop="parentId">
|
||||
<MenuSelector
|
||||
v-model:menu-value="menuProps.record.parentId"
|
||||
show-all
|
||||
@change="changeMenu"
|
||||
:menu-tree-api="() => mobileMenuApi.menuTreeSelector({ module: module })"
|
||||
/>
|
||||
</s-form-item>
|
||||
<s-form-item label="路径" prop="path" v-if="isMenu">
|
||||
<s-input v-model="menuProps.record.path"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="图标" prop="icon">
|
||||
<SelectIconPlus v-model:icon-value="menuProps.record.icon!" />
|
||||
</s-form-item>
|
||||
<s-form-item label="颜色" prop="color">
|
||||
<color-picker v-model:value="menuProps.record.color" />
|
||||
</s-form-item>
|
||||
<s-form-item label="排序码" prop="sortCode">
|
||||
<el-slider v-model="menuProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
<s-form-item label="正规则" prop="regType">
|
||||
<s-radio-group v-model="menuProps.record.regType" :options="regTypeOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="状态" prop="status">
|
||||
<s-radio-group v-model="menuProps.record.status" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="描述" prop="description">
|
||||
<s-input v-model="menuProps.record.description"></s-input>
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!menuProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mobileMenuApi, MobileModule } from "@/api";
|
||||
import { FormOptEnum, MenuTypeDictEnum } from "@/enums";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore();
|
||||
const menuTypeOptions = dictStore
|
||||
.getDictList("MENU_TYPE")
|
||||
.filter((item: { value: MenuTypeDictEnum }) => item.value == MenuTypeDictEnum.MENU || item.value == MenuTypeDictEnum.CATALOG); //菜单类型选项
|
||||
const regTypeOptions = dictStore.getDictList("YES_NO"); //正规则选项
|
||||
const statusOptions = dictStore.getDictList("COMMON_STATUS"); //状态选项
|
||||
const isMenu = computed(() => menuProps.record.menuType === MenuTypeDictEnum.MENU); //是否是菜单
|
||||
// 模块id
|
||||
const module = ref<number | string>(0);
|
||||
|
||||
// 表单参数
|
||||
const menuProps = reactive<FormProps.Base<MobileModule.MobileModuleInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入菜单名称")],
|
||||
parentId: [required("请选择上级菜单")],
|
||||
menuType: [required("请选择菜单类型")],
|
||||
path: [required("请输入路径")],
|
||||
regType: [required("请选择正规则")],
|
||||
color: [required("请选择颜色")],
|
||||
sortCode: [required("请输入排序")],
|
||||
icon: [required("请选择图标")],
|
||||
status: [required("请选择状态")]
|
||||
});
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<MobileModule.MobileModuleInfo>, moduleId: number | string) {
|
||||
Object.assign(menuProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
menuProps.record.menuType = menuTypeOptions[0].value;
|
||||
//如果是新增,设置默认值
|
||||
//如果是新增,设置默认值
|
||||
menuProps.record.sortCode = 99;
|
||||
menuProps.record.status = statusOptions[0].value;
|
||||
menuProps.record.menuType = MenuTypeDictEnum.MENU;
|
||||
menuProps.record.sortCode = 99;
|
||||
menuProps.record.module = moduleId;
|
||||
menuProps.record.regType = regTypeOptions[0].value;
|
||||
menuProps.record.color = "#1890ff"; //默认颜色
|
||||
}
|
||||
module.value = moduleId; //设置模块id
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
mobileMenuApi.detail({ id: props.record.id }).then(res => {
|
||||
menuProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const menuFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
menuFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await mobileMenuApi
|
||||
.submitForm(menuProps.record, menuProps.record.id != undefined)
|
||||
.then(() => {
|
||||
menuProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
/** 菜单选择事件
|
||||
* @param path 路由地址
|
||||
*/
|
||||
function changeMenu(path: string) {
|
||||
menuProps.record.path = path;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,228 @@
|
||||
<!--
|
||||
* @Description: 菜单管理
|
||||
* @Author: superAdmin
|
||||
* @Date: 2025-06-26 16:41:59
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="菜单列表" :columns="columns" :request-api="getTableList" :request-auto="false" :pagination="false">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="菜单" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="菜单"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选菜单')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表格 菜单类型 按钮 -->
|
||||
<template #menuType="scope">
|
||||
<el-space>
|
||||
<!-- <el-tag>{{ dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType) }}</el-tag> -->
|
||||
<el-tag v-if="scope.row.menuType === MenuTypeDictEnum.MENU" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="info">{{ dictStore.dictTranslation(SysDictEnum.MENU_TYPE, scope.row.menuType) }}</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 图标 -->
|
||||
<template #icon="scope">
|
||||
<svg-icon :icon="scope.row.icon" :color="scope.row.color" class="h-6 w-8" />
|
||||
</template>
|
||||
<!-- 颜色 -->
|
||||
<template #color="scope">
|
||||
<el-tag :color="scope.row.color" effect="dark" :style="{ borderColor: scope.row.color }" size="large">{{ scope.row.color }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<el-space>
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.title}】菜单`)" />
|
||||
<el-dropdown @command="handleCommand">
|
||||
<el-link type="primary" :underline="false" :icon="ArrowDown"> 更多 </el-link>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="scope.row.parentId === 0" :command="command(scope.row, cmdEnum.ChangeModule)">{{
|
||||
cmdEnum.ChangeModule
|
||||
}}</el-dropdown-item>
|
||||
<el-dropdown-item v-if="isMenu(scope.row.menuType)" :command="command(scope.row, cmdEnum.Button)">{{
|
||||
cmdEnum.Button
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-space>
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 更改模块 -->
|
||||
<ChangeModule ref="changeModuleFormRef" />
|
||||
<!-- 权限按钮 -->
|
||||
<Button ref="buttonFormRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="mobileMenu">
|
||||
import { MobileMenu, mobileMenuApi } from "@/api";
|
||||
import { ArrowDown } from "@element-plus/icons-vue";
|
||||
import { FormOptEnum, CommonStatusEnum, SysDictEnum, MenuTypeDictEnum } from "@/enums";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import Form from "./components/form.vue";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import ChangeModule from "./components/changeModule.vue";
|
||||
import Button from "../button/index.vue";
|
||||
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
const dictStore = useDictStore();
|
||||
const menuTypeOptions = dictStore.getDictList("MENU_TYPE"); //菜单类型选项
|
||||
const regTypeOptions = dictStore.getDictList("YES_NO"); //正规则选项
|
||||
const statusOptions = dictStore.getDictList("COMMON_STATUS"); //状态选项
|
||||
const moduleOptions = ref<{ label: string; value: string | number }[]>([]);
|
||||
const columns: ColumnProps<MobileMenu.MobileMenuInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "id", label: "Id", isShow: false },
|
||||
{
|
||||
prop: "module",
|
||||
label: "应用模块",
|
||||
enum: moduleOptions,
|
||||
search: {
|
||||
el: "select",
|
||||
props: {
|
||||
clearable: false,
|
||||
placeholder: "请选择应用模块"
|
||||
}
|
||||
},
|
||||
isShow: false
|
||||
},
|
||||
{ prop: "title", label: "菜单名称", width: 150, align: "left", search: { el: "input" } },
|
||||
{ prop: "menuType", label: "菜单类型", enum: menuTypeOptions, search: { el: "select" } },
|
||||
{ prop: "path", label: "路径" },
|
||||
{ prop: "icon", label: "图标" },
|
||||
{ prop: "color", label: "颜色" },
|
||||
{ prop: "sortCode", label: "排序码" },
|
||||
{ prop: "regType", label: "正规则", enum: regTypeOptions },
|
||||
{ prop: "status", label: "状态", enum: statusOptions },
|
||||
{ prop: "description", label: "描述" },
|
||||
{ prop: "operation", label: "操作", width: 230, fixed: "right" }
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
// 获取应用模块下拉选项
|
||||
mobileMenuApi.moduleSelector().then(res => {
|
||||
moduleOptions.value = res.data.map(item => ({ label: item.title, value: item.id }));
|
||||
// 设置默认值(取第一个模块)
|
||||
if (moduleOptions.value.length > 0) {
|
||||
// 1. 赋值初始化参数
|
||||
proTable.value!.searchInitParam["module"] = moduleOptions.value[0].value;
|
||||
// 2. 重置参数(使新参数生效)
|
||||
proTable.value?.resetParam();
|
||||
proTable.value?.getTableList();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
// 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList"
|
||||
const getTableList = (params: any) => {
|
||||
let newParams = JSON.parse(JSON.stringify(params));
|
||||
return mobileMenuApi.tree(newParams);
|
||||
};
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | MobileMenu.MobileMenuInfo = {}) {
|
||||
console.log("[ proTable.value?.searchParam.module ] >", proTable.value?.searchParam.module);
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable }, proTable.value?.searchParam.module);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(mobileMenuApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
|
||||
/**判断是否是菜单 */
|
||||
const isMenu = computed(() => (menuType: string) => {
|
||||
return menuType === MenuTypeDictEnum.MENU;
|
||||
});
|
||||
|
||||
/** 更多下拉菜单命令枚举 */
|
||||
enum cmdEnum {
|
||||
ChangeModule = "更改模块",
|
||||
Button = "权限按钮"
|
||||
}
|
||||
|
||||
/** 下拉菜单参数接口 */
|
||||
interface Command {
|
||||
row: MobileMenu.MobileMenuInfo;
|
||||
command: cmdEnum;
|
||||
}
|
||||
|
||||
/**配置command的参数 */
|
||||
function command(row: MobileMenu.MobileMenuInfo, command: cmdEnum): Command {
|
||||
return {
|
||||
row: row,
|
||||
command: command
|
||||
};
|
||||
}
|
||||
|
||||
// 修改模块表单引用
|
||||
const changeModuleFormRef = ref<InstanceType<typeof ChangeModule> | null>(null);
|
||||
// 权限按钮表单引用
|
||||
const buttonFormRef = ref<InstanceType<typeof Button> | null>(null);
|
||||
|
||||
/**
|
||||
* 更多下拉菜单点击事件
|
||||
* @param command
|
||||
*/
|
||||
function handleCommand(command: Command) {
|
||||
switch (command.command) {
|
||||
case cmdEnum.ChangeModule:
|
||||
//更改模块
|
||||
changeModuleFormRef.value?.onOpen({
|
||||
moduleOptions: moduleOptions.value,
|
||||
id: command.row.id,
|
||||
title: command.row.title,
|
||||
module: command.row.module,
|
||||
successful: () => RefreshTable()
|
||||
});
|
||||
break;
|
||||
case cmdEnum.Button:
|
||||
//权限按钮
|
||||
buttonFormRef.value?.onOpenTable(command.row.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<!--
|
||||
* @Description: 模块管理表单页面
|
||||
* @Author: superAdmin
|
||||
* @Date: 2025-06-25 14:37:52
|
||||
!-->
|
||||
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${moduleProps.opt}模块`" form-size="600px">
|
||||
<el-form
|
||||
ref="moduleFormRef"
|
||||
:rules="rules"
|
||||
:disabled="moduleProps.disabled"
|
||||
:model="moduleProps.record"
|
||||
:hide-required-asterisk="moduleProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="名称" prop="title">
|
||||
<s-input v-model="moduleProps.record.title"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="图标" prop="icon">
|
||||
<SelectIconPlus v-model:icon-value="moduleProps.record.icon!" />
|
||||
</s-form-item>
|
||||
<s-form-item label="颜色" prop="color">
|
||||
<color-picker v-model:value="moduleProps.record.color" />
|
||||
</s-form-item>
|
||||
<s-form-item label="描述" prop="description">
|
||||
<s-input v-model="moduleProps.record.description"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="排序码" prop="sortCode">
|
||||
<el-slider v-model="moduleProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
<s-form-item label="状态" prop="status">
|
||||
<s-radio-group v-model="moduleProps.record.status" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!moduleProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { mobileModuleApi, MobileModule } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum, SysDictEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
const visible = ref(false); //
|
||||
|
||||
// 通用状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
|
||||
// 表单参数
|
||||
const moduleProps = reactive<FormProps.Base<MobileModule.MobileModuleInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [required("请输入显示名称")],
|
||||
sortCode: [required("请输入排序")],
|
||||
icon: [required("请选择图标")],
|
||||
status: [required("请选择状态")]
|
||||
});
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<MobileModule.MobileModuleInfo>) {
|
||||
Object.assign(moduleProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
moduleProps.record.sortCode = 99;
|
||||
moduleProps.record.status = statusOptions[0].value;
|
||||
moduleProps.record.color = "#1890ff"; //默认颜色
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
mobileModuleApi.detail({ id: props.record.id }).then(res => {
|
||||
moduleProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const moduleFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
moduleFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await mobileModuleApi
|
||||
.submitForm(moduleProps.record, moduleProps.record.id != undefined)
|
||||
.then(() => {
|
||||
moduleProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,106 @@
|
||||
<!--
|
||||
* @Description: 模块管理
|
||||
* @Author: superAdmin
|
||||
* @Date: 2025-06-25 14:37:52
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="模块列表" :columns="columns" :request-api="mobileModuleApi.page">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="模块" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="模块"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选模块')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 图标 -->
|
||||
<template #icon="scope">
|
||||
<svg-icon :icon="scope.row.icon" :color="scope.row.color" class="h-6 w-8" />
|
||||
</template>
|
||||
<!-- 颜色 -->
|
||||
<template #color="scope">
|
||||
<el-tag :color="scope.row.color" effect="dark" :style="{ borderColor: scope.row.color }" size="large">{{ scope.row.color }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除所选模块`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="mobileModule">
|
||||
import { mobileModuleApi, MobileModule } from "@/api";
|
||||
import { FormOptEnum, CommonStatusEnum, SysDictEnum } from "@/enums";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import Form from "./components/form.vue";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
const columns: ColumnProps<MobileModule.MobileModuleInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 80 },
|
||||
{ prop: "title", label: "显示名称", search: { el: "input" } },
|
||||
{ prop: "description", label: "描述" },
|
||||
{
|
||||
prop: "icon",
|
||||
label: "菜单图标"
|
||||
},
|
||||
{
|
||||
prop: "color",
|
||||
label: "颜色"
|
||||
},
|
||||
{ prop: "sortCode", label: "排序码" },
|
||||
{ prop: "status", label: "状态" },
|
||||
{ prop: "operation", label: "操作", width: 230, fixed: "right" }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | MobileModule.MobileModuleInfo = {}) {
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(mobileModuleApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<!--
|
||||
* @Description:连接配置
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-11-07 13:55:56
|
||||
!-->
|
||||
<template>
|
||||
<el-form ref="baseFormRef" :rules="rules" :model="baseFormProps" label-width="auto" label-suffix=" :" label-position="top">
|
||||
<s-form-item label="MQTT服务端地址" prop="MQTT_PARAM_URL">
|
||||
<s-input v-model="baseFormProps.MQTT_PARAM_URL"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="MQTT用户名" prop="MQTT_PARAM_USERNAME">
|
||||
<s-input v-model="baseFormProps.MQTT_PARAM_USERNAME"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="MQTT密码" prop="MQTT_PARAM_PASSWORD">
|
||||
<s-input v-model="baseFormProps.MQTT_PARAM_PASSWORD"></s-input>
|
||||
</s-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
|
||||
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysConfig, sysConfigApi } from "@/api";
|
||||
import { FormInstance } from "element-plus";
|
||||
const submitLoading = ref(false); //提交按钮loading
|
||||
const baseFormRef = ref<FormInstance>();
|
||||
import { SysConfigTypeEnum } from "@/enums";
|
||||
|
||||
/**
|
||||
* @description: 连接配置参数
|
||||
*/
|
||||
const baseFormProps = reactive<SysConfig.MqttPolicyConfig>({
|
||||
MQTT_PARAM_URL: "",
|
||||
MQTT_PARAM_USERNAME: "",
|
||||
MQTT_PARAM_PASSWORD: ""
|
||||
});
|
||||
|
||||
//props定义
|
||||
const props = defineProps({
|
||||
baseConfig: {
|
||||
type: Array as PropType<SysConfig.ConfigInfo[]>,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
//监听loginPolicy变化
|
||||
watch(
|
||||
() => props.baseConfig,
|
||||
(newVal: SysConfig.ConfigInfo[]) => {
|
||||
//重新赋值
|
||||
newVal.forEach((item: SysConfig.ConfigInfo) => {
|
||||
(baseFormProps[item.configKey as keyof SysConfig.MqttPolicyConfig] as string) = item.configValue;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
MQTT_PARAM_URL: [{ required: true, message: "请输入MQTT服务端地址", trigger: "blur" }],
|
||||
MQTT_PARAM_USERNAME: [{ required: true, message: "请输入MQTT用户名", trigger: "blur" }],
|
||||
MQTT_PARAM_PASSWORD: [{ required: true, message: "请输入MQTT密码", trigger: "blur" }]
|
||||
});
|
||||
|
||||
/** 提交表单 */
|
||||
function onSubmit() {
|
||||
baseFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
submitLoading.value = true;
|
||||
//组装参数
|
||||
const param: SysConfig.ConfigInfo[] = Object.entries(baseFormProps).map(item => {
|
||||
return {
|
||||
id: 0,
|
||||
category: SysConfigTypeEnum.MQTT_POLICY,
|
||||
configKey: item[0],
|
||||
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
|
||||
};
|
||||
});
|
||||
//提交数据
|
||||
sysConfigApi.configEditForm(param).finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
baseFormRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--
|
||||
* @Description: 安全配置
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-01-31 15:33:50
|
||||
!-->
|
||||
<template>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" tab-position="left">
|
||||
<el-tab-pane label="连接配置" name="base" class="pl-5"><BaseForm :base-config="baseConfig" /></el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysConfig } from "@/api";
|
||||
import BaseForm from "./baseForm.vue";
|
||||
import { SysConfigTypeEnum } from "@/enums";
|
||||
|
||||
const activeName = ref("base");
|
||||
const baseConfig = ref<SysConfig.ConfigInfo[]>([]);
|
||||
|
||||
//props定义
|
||||
const props = defineProps({
|
||||
sysConfigs: {
|
||||
type: Array as PropType<SysConfig.ConfigInfo[]>,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
//监听sysConfigs变化
|
||||
watch(
|
||||
() => props.sysConfigs,
|
||||
(newVal: SysConfig.ConfigInfo[]) => {
|
||||
// 配置赋值
|
||||
baseConfig.value = newVal.filter(item => item.category == SysConfigTypeEnum.MQTT_POLICY);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-size: 132px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<!--
|
||||
* @Description: 配置表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-02-02 10:49:28
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${configProps.opt}配置`" form-size="600px">
|
||||
<el-form
|
||||
ref="configFormRef"
|
||||
:rules="rules"
|
||||
:disabled="configProps.disabled"
|
||||
:model="configProps.record"
|
||||
:hide-required-asterisk="configProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="配置键" prop="configKey">
|
||||
<s-input v-model="configProps.record.configKey"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="配置值" prop="configValue">
|
||||
<s-input v-model="configProps.record.configValue"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="备注" prop="remark">
|
||||
<s-input v-model="configProps.record.remark"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="configProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!configProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysConfig, sysConfigApi } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
|
||||
// 表单参数接口
|
||||
interface ConfigProps extends FormProps.Base<SysConfig.ConfigInfo> {}
|
||||
|
||||
// 表单参数
|
||||
const configProps = reactive<ConfigProps>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
configKey: [required("请输入配置键")],
|
||||
configValue: [required("请输入配置值")],
|
||||
sortCode: [required("请输入排序")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: ConfigProps) {
|
||||
Object.assign(configProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
configProps.record.sortCode = 99;
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const configFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
configFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await sysConfigApi
|
||||
.submitForm(configProps.record, configProps.record.id != undefined)
|
||||
.then(() => {
|
||||
configProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<!--
|
||||
* @Description: 其他配置
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-02-02 08:43:44
|
||||
!-->
|
||||
<template>
|
||||
<div class="table-box min-h-300px">
|
||||
<ProTable ref="proTable" class="table" :columns="columns" :request-api="sysConfigApi.page">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader>
|
||||
<s-button :suffix="title" @click="onOpen(FormOptEnum.ADD)" />
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete(scope.row.id, `删除【${scope.row.configKey}】配置`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { sysConfigApi, SysConfig } from "@/api";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { FormOptEnum } from "@/enums";
|
||||
import Form from "./form.vue";
|
||||
|
||||
const title = "配置";
|
||||
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysConfig.ConfigInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "configKey", label: "配置键" },
|
||||
{ prop: "configValue", label: "配置值" },
|
||||
{ prop: "remark", label: "备注" },
|
||||
{ prop: "sortCode", label: "排序", width: 80 },
|
||||
{ prop: "operation", label: "操作", fixed: "right", width: 150 }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | SysConfig.ConfigInfo = {}) {
|
||||
formRef.value?.onOpen({
|
||||
opt: opt,
|
||||
record: record,
|
||||
successful: RefreshTable //刷新子表格
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(id: number | string, msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(sysConfigApi.delete, { id }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh(); //刷新主表格
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
* @Description: 安全配置
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-01-31 15:33:50
|
||||
!-->
|
||||
<template>
|
||||
<el-tabs v-model="activeName" class="demo-tabs" tab-position="left">
|
||||
<el-tab-pane label="登录策略" name="login" class="pl-5"><LoginForm :login-policy="loginPolicy" /></el-tab-pane>
|
||||
<el-tab-pane label="密码策略" name="pwd" class="pl-5"><PwdForm :pwd-policy="pwdPolicy" /></el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysConfig } from "@/api";
|
||||
import LoginForm from "./loginForm.vue";
|
||||
import PwdForm from "./pwdForm.vue";
|
||||
import { SysConfigTypeEnum } from "@/enums";
|
||||
|
||||
const activeName = ref("login");
|
||||
const loginPolicy = ref<SysConfig.ConfigInfo[]>([]);
|
||||
const pwdPolicy = ref<SysConfig.ConfigInfo[]>([]);
|
||||
|
||||
//props定义
|
||||
const props = defineProps({
|
||||
sysConfigs: {
|
||||
type: Array as PropType<SysConfig.ConfigInfo[]>,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
//监听sysConfigs变化
|
||||
watch(
|
||||
() => props.sysConfigs,
|
||||
(newVal: SysConfig.ConfigInfo[]) => {
|
||||
// 配置赋值
|
||||
loginPolicy.value = newVal.filter(item => item.category == SysConfigTypeEnum.LOGIN_POLICY);
|
||||
pwdPolicy.value = newVal.filter(item => item.category == SysConfigTypeEnum.PWD_POLICY);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
font-size: 132px;
|
||||
font-weight: 600;
|
||||
color: #6b778c;
|
||||
}
|
||||
:deep(.el-tabs__nav-scroll) {
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<!--
|
||||
* @Description: 登录策略
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-01-31 16:25:48
|
||||
!-->
|
||||
<template>
|
||||
<el-form ref="loginFormRef" :rules="rules" :model="loginFormProps" label-width="auto" label-suffix=" :" label-position="top">
|
||||
<s-form-item label="单用户登录开关" prop="LOGIN_SINGLE_OPEN">
|
||||
<s-radio-group v-model="loginFormProps.LOGIN_SINGLE_OPEN" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
<s-form-item label="允许登录错误次数" prop="LOGIN_ERROR_COUNT">
|
||||
<el-input-number v-model.number="loginFormProps.LOGIN_ERROR_COUNT" :min="1" :max="999" />
|
||||
</s-form-item>
|
||||
<s-form-item label="登录错误重置时间(分)" prop="LOGIN_ERROR_RESET_TIME">
|
||||
<el-input-number v-model.number="loginFormProps.LOGIN_ERROR_RESET_TIME" :min="1" :max="999" />
|
||||
</s-form-item>
|
||||
<s-form-item label="登录错误次数过多账号锁定时长:(分)" prop="LOGIN_ERROR_LOCK">
|
||||
<el-input-number v-model.number="loginFormProps.LOGIN_ERROR_LOCK" :min="1" :max="999" />
|
||||
</s-form-item>
|
||||
<s-form-item label="登录验证码开关" prop="LOGIN_CAPTCHA_OPEN">
|
||||
<s-radio-group v-model="loginFormProps.LOGIN_CAPTCHA_OPEN" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
<s-form-item label="登录验证码类型" prop="LOGIN_CAPTCHA_TYPE">
|
||||
<s-radio-group v-model="loginFormProps.LOGIN_CAPTCHA_TYPE" :options="captchaOptions" />
|
||||
</s-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
|
||||
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysConfig, sysConfigApi } from "@/api";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { SysConfigTypeEnum, SysDictEnum } from "@/enums";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
|
||||
// 字典类型选项
|
||||
const yesNoOptions = dictStore.getDictList(SysDictEnum.YES_NO);
|
||||
const captchaOptions = dictStore.getDictList(SysDictEnum.CAPTCHA_TYPE);
|
||||
const submitLoading = ref(false); //提交按钮loading
|
||||
const loginFormRef = ref<FormInstance>();
|
||||
|
||||
/** 登录策略参数 */
|
||||
const loginFormProps = reactive<SysConfig.LoginPolicyConfig>({
|
||||
LOGIN_SINGLE_OPEN: yesNoOptions[0].value,
|
||||
LOGIN_CAPTCHA_OPEN: yesNoOptions[0].value,
|
||||
LOGIN_CAPTCHA_TYPE: captchaOptions[0].value,
|
||||
LOGIN_ERROR_COUNT: 5,
|
||||
LOGIN_ERROR_RESET_TIME: 5,
|
||||
LOGIN_ERROR_LOCK: 30
|
||||
});
|
||||
|
||||
//props定义
|
||||
const props = defineProps({
|
||||
loginPolicy: {
|
||||
type: Array as PropType<SysConfig.ConfigInfo[]>,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
//监听loginPolicy变化
|
||||
watch(
|
||||
() => props.loginPolicy,
|
||||
(newVal: SysConfig.ConfigInfo[]) => {
|
||||
//重新赋值
|
||||
newVal.forEach((item: SysConfig.ConfigInfo) => {
|
||||
let prop = loginFormProps[item.configKey as keyof SysConfig.LoginPolicyConfig];
|
||||
//如果是number类型就转为number
|
||||
if (typeof prop === "number") {
|
||||
(loginFormProps[item.configKey as keyof SysConfig.LoginPolicyConfig] as number) = Number(item.configValue);
|
||||
} else {
|
||||
(loginFormProps[item.configKey as keyof SysConfig.LoginPolicyConfig] as string) = item.configValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
LOGIN_ERROR_COUNT: [{ required: true, message: "请输入允许登录错误次数", trigger: "blur" }],
|
||||
LOGIN_ERROR_RESET_TIME: [{ required: true, message: "请输入登录错误重置时间", trigger: "blur" }],
|
||||
LOGIN_ERROR_LOCK: [{ required: true, message: "请输入登录错误次数过多账号锁定时长", trigger: "blur" }],
|
||||
LOGIN_CAPTCHA_TYPE: [{ required: true, message: "请选择登录验证码类型", trigger: "blur" }],
|
||||
LOGIN_SINGLE_OPEN: [{ required: true, message: "请选择单用户登录开关", trigger: "blur" }],
|
||||
LOGIN_CAPTCHA_OPEN: [{ required: true, message: "请选择登录验证码开关", trigger: "blur" }]
|
||||
});
|
||||
|
||||
/** 提交表单 */
|
||||
function onSubmit() {
|
||||
loginFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
submitLoading.value = true;
|
||||
//组装参数
|
||||
const param: SysConfig.ConfigInfo[] = Object.entries(loginFormProps).map(item => {
|
||||
return {
|
||||
id: 0,
|
||||
category: SysConfigTypeEnum.LOGIN_POLICY,
|
||||
configKey: item[0],
|
||||
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
|
||||
};
|
||||
});
|
||||
//提交数据
|
||||
sysConfigApi.configEditForm(param).finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
loginFormRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,143 @@
|
||||
<!--
|
||||
* @Description: 密码策略
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-01-31 16:25:33
|
||||
!-->
|
||||
<template>
|
||||
<el-form ref="pwdFormRef" :rules="rules" :model="pwdFormProps" label-width="auto" label-suffix=" :" label-position="top">
|
||||
<s-form-item label="系统默认密码" prop="PWD_DEFAULT_PASSWORD" class="w-50">
|
||||
<s-input v-model="pwdFormProps.PWD_DEFAULT_PASSWORD"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="密码定期提醒更新" prop="PWD_REMIND">
|
||||
<s-radio-group v-model="pwdFormProps.PWD_REMIND" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
<s-form-item label="密码定期提醒更新时间(天)" prop="LOGIN_ERROR_LOCK">
|
||||
<el-input-number v-model.number="pwdFormProps.PWD_REMIND_DAY" :min="1" :max="999" />
|
||||
</s-form-item>
|
||||
<s-form-item label="修改初始密码提醒" prop="PWD_UPDATE_DEFAULT">
|
||||
<s-radio-group v-model="pwdFormProps.PWD_UPDATE_DEFAULT" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
<s-form-item label="密码最小长度" prop="PWD_MIN_LENGTH">
|
||||
<el-input-number v-model.number="pwdFormProps.PWD_MIN_LENGTH" :min="6" :max="20" />
|
||||
</s-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
<s-form-item label="是否含数字" prop="PWD_CONTAIN_NUM">
|
||||
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_NUM" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<s-form-item label="是否包含小写字母" prop="PWD_CONTAIN_LOWER">
|
||||
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_LOWER" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<s-form-item label="是否包含大写字母" prop="PWD_CONTAIN_UPPER">
|
||||
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_UPPER" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<s-form-item label="是否包含特殊字符" prop="PWD_CONTAIN_CHARACTER">
|
||||
<s-radio-group v-model="pwdFormProps.PWD_CONTAIN_CHARACTER" :options="yesNoOptions" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
|
||||
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysConfig, sysConfigApi } from "@/api";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { SysConfigTypeEnum, SysDictEnum } from "@/enums";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
|
||||
// 字典类型选项
|
||||
const yesNoOptions = dictStore.getDictList(SysDictEnum.YES_NO);
|
||||
const submitLoading = ref(false); //提交按钮loading
|
||||
const pwdFormRef = ref<FormInstance>();
|
||||
|
||||
/** 登录策略参数 */
|
||||
const pwdFormProps = reactive<SysConfig.PwdPolicyConfig>({
|
||||
PWD_DEFAULT_PASSWORD: "",
|
||||
PWD_REMIND: yesNoOptions[0].value,
|
||||
PWD_REMIND_DAY: 30,
|
||||
PWD_UPDATE_DEFAULT: yesNoOptions[0].value,
|
||||
PWD_MIN_LENGTH: 6,
|
||||
PWD_CONTAIN_NUM: yesNoOptions[0].value,
|
||||
PWD_CONTAIN_LOWER: yesNoOptions[0].value,
|
||||
PWD_CONTAIN_UPPER: yesNoOptions[0].value,
|
||||
PWD_CONTAIN_CHARACTER: yesNoOptions[0].value
|
||||
});
|
||||
|
||||
//props定义
|
||||
const props = defineProps({
|
||||
pwdPolicy: {
|
||||
type: Array as PropType<SysConfig.ConfigInfo[]>,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
//监听pwdPolicy变化
|
||||
watch(
|
||||
() => props.pwdPolicy,
|
||||
(newVal: SysConfig.ConfigInfo[]) => {
|
||||
//重新赋值
|
||||
newVal.forEach((item: SysConfig.ConfigInfo) => {
|
||||
let prop = pwdFormProps[item.configKey as keyof SysConfig.PwdPolicyConfig];
|
||||
//如果是number类型就转为number
|
||||
if (typeof prop === "number") {
|
||||
(pwdFormProps[item.configKey as keyof SysConfig.PwdPolicyConfig] as number) = Number(item.configValue);
|
||||
} else {
|
||||
(pwdFormProps[item.configKey as keyof SysConfig.PwdPolicyConfig] as string) = item.configValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
PWD_DEFAULT_PASSWORD: [{ required: true, message: "请输入系统默认密码", trigger: "blur" }],
|
||||
PWD_REMIND: [{ required: true, message: "请选择密码定期提醒更新", trigger: "blur" }],
|
||||
PWD_REMIND_DAY: [{ required: true, message: "请输入密码定期提醒更新时间", trigger: "blur" }],
|
||||
PWD_UPDATE_DEFAULT: [{ required: true, message: "请选择修改初始密码提醒", trigger: "blur" }],
|
||||
PWD_MIN_LENGTH: [{ required: true, message: "请输入密码最小长度", trigger: "blur" }],
|
||||
PWD_CONTAIN_NUM: [{ required: true, message: "请选择是否含数字", trigger: "blur" }],
|
||||
PWD_CONTAIN_LOWER: [{ required: true, message: "请选择是否包含小写字母", trigger: "blur" }],
|
||||
PWD_CONTAIN_UPPER: [{ required: true, message: "请选择是否包含大写字母", trigger: "blur" }],
|
||||
PWD_CONTAIN_CHARACTER: [{ required: true, message: "请选择是否包含特殊字符", trigger: "blur" }]
|
||||
});
|
||||
|
||||
/** 提交表单 */
|
||||
function onSubmit() {
|
||||
pwdFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
submitLoading.value = true;
|
||||
//组装参数
|
||||
const param: SysConfig.ConfigInfo[] = Object.entries(pwdFormProps).map(item => {
|
||||
return {
|
||||
id: 0,
|
||||
category: SysConfigTypeEnum.LOGIN_POLICY,
|
||||
configKey: item[0],
|
||||
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
|
||||
};
|
||||
});
|
||||
//提交数据
|
||||
sysConfigApi.configEditForm(param).finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
pwdFormRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,307 @@
|
||||
<!--
|
||||
* @Description: 系统配置
|
||||
* @Author: huguodong
|
||||
* @Date: 2024-01-22 16:44:07
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<el-form ref="sysConfigFormRef" :rules="rules" :model="sysBaseProps" label-width="auto" label-suffix=" :" label-position="top">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="系统LOGO" prop="SYS_LOGO">
|
||||
<UploadImg v-model:image-url="sysBaseProps.SYS_LOGO" width="135px" height="135px" :file-size="3" :auto-upload="false">
|
||||
<template #empty>
|
||||
<el-icon><Avatar /></el-icon>
|
||||
<span>请上传LOGO</span>
|
||||
</template>
|
||||
</UploadImg>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="系统ico文件" prop="SYS_ICO">
|
||||
<UploadImg
|
||||
v-model:image-url="sysBaseProps.SYS_ICO!"
|
||||
width="135px"
|
||||
height="135px"
|
||||
:file-size="1"
|
||||
:file-type="icoType"
|
||||
:auto-upload="false"
|
||||
>
|
||||
<template #empty>
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>请上传.ICO文件</span>
|
||||
</template>
|
||||
</UploadImg>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="系统名称" prop="SYS_NAME">
|
||||
<s-input v-model="sysBaseProps.SYS_NAME"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="系统版本" prop="SYS_VERSION">
|
||||
<s-input v-model="sysBaseProps.SYS_VERSION"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="默认快捷方式" prop="SYS_DEFAULT_WORKBENCH_DATA">
|
||||
<MenuSelector
|
||||
v-model:menu-value="sysBaseProps.SYS_DEFAULT_WORKBENCH_DATA!.shortcut"
|
||||
multiple
|
||||
:check-strictly="false"
|
||||
:menu-tree-api="menuApi.menuTreeSelector"
|
||||
/>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="多租户配置" prop="SYS_TENANT_OPTIONS">
|
||||
<s-radio-group v-model="sysBaseProps.SYS_TENANT_OPTIONS" :options="tenantOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="版权信息" prop="SYS_COPYRIGHT">
|
||||
<s-input v-model="sysBaseProps.SYS_COPYRIGHT"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="版权链接URL" prop="SYS_COPYRIGHT_URL">
|
||||
<s-input v-model="sysBaseProps.SYS_COPYRIGHT_URL"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<s-form-item label="底部超链接" prop="SYS_FOOTER_LINKS">
|
||||
<el-tag
|
||||
v-for="tag in sysBaseProps.SYS_FOOTER_LINKS"
|
||||
:key="tag.name"
|
||||
class="mx-1"
|
||||
closable
|
||||
:disable-transitions="false"
|
||||
@close="handleClose(tag.name)"
|
||||
@click="linkDetail(tag)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</el-tag>
|
||||
|
||||
<el-button class="ml-1" size="small" @click="showLinkModel()">添加</el-button>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="网站开启访问(仅业务)" prop="SYS_WEB_STATUS">
|
||||
<s-radio-group v-model="sysBaseProps.SYS_WEB_STATUS" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<s-form-item label="网站关闭提示" prop="SYS_WEB_CLOSE_PROMPT">
|
||||
<s-input v-model="sysBaseProps.SYS_WEB_CLOSE_PROMPT" :rows="3" type="textarea"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="submitLoading" @click="onSubmit()">保存</el-button>
|
||||
<el-button style="margin-left: 10px" @click="resetForm">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<!-- 超链接弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" title="超链接信息" width="30%" show-close destroy-on-close>
|
||||
<el-form ref="linkFormRef" :model="linkForm" :rules="linkRules" label-width="auto" label-suffix=" :" label-position="top">
|
||||
<s-form-item label="超链接名称" prop="name">
|
||||
<s-input v-model="linkForm.name"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="超链接地址" prop="url">
|
||||
<s-input v-model="linkForm.url"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="linkForm.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveLink">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysConfig, menuApi, sysConfigApi } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { SysDictEnum, SysConfigTypeEnum, SysBaseEnum, TenantEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 字典类型选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
//多租户选项
|
||||
const tenantOptions = dictStore.getDictList(SysDictEnum.TENANT_OPTIONS);
|
||||
|
||||
const icoType: File.ImageMimeType[] = ["image/x-icon"]; //ico文件类型
|
||||
const submitLoading = ref(false); //提交按钮loading
|
||||
const sysConfigFormRef = ref<FormInstance>(); //表单实例
|
||||
/** 系统配置 */
|
||||
const sysBaseProps = reactive<SysConfig.SysBaseConfig>({
|
||||
SYS_NAME: "",
|
||||
SYS_LOGO: "",
|
||||
SYS_ICO: "",
|
||||
SYS_VERSION: "",
|
||||
SYS_COPYRIGHT: "",
|
||||
SYS_COPYRIGHT_URL: "",
|
||||
SYS_DEFAULT_WORKBENCH_DATA: { shortcut: [] },
|
||||
SYS_WEB_STATUS: statusOptions[0].value,
|
||||
SYS_WEB_CLOSE_PROMPT: "",
|
||||
SYS_TENANT_OPTIONS: TenantEnum.CLOSE,
|
||||
SYS_FOOTER_LINKS: []
|
||||
});
|
||||
|
||||
//定义props
|
||||
const props = defineProps({
|
||||
sysConfigs: {
|
||||
type: Array as PropType<SysConfig.ConfigInfo[]>,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
//监听sysConfigs变化
|
||||
watch(
|
||||
() => props.sysConfigs,
|
||||
(newVal: SysConfig.ConfigInfo[]) => {
|
||||
//重新赋值
|
||||
newVal.forEach((item: SysConfig.ConfigInfo) => {
|
||||
//如果是对象类型的属性就转成对象
|
||||
if (item.configKey == SysBaseEnum.SYS_DEFAULT_WORKBENCH_DATA) {
|
||||
const workBenchData: SysConfig.WorkBenchData = JSON.parse(item.configValue);
|
||||
sysBaseProps.SYS_DEFAULT_WORKBENCH_DATA = workBenchData;
|
||||
} else if (item.configKey == SysBaseEnum.SYS_FOOTER_LINKS) {
|
||||
const footerLinks: SysConfig.FooterLinkProps[] = JSON.parse(item.configValue);
|
||||
sysBaseProps.SYS_FOOTER_LINKS = footerLinks;
|
||||
} else {
|
||||
// 其他属性直接赋值
|
||||
(sysBaseProps[item.configKey as keyof SysConfig.SysBaseConfig] as string) = item.configValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
SYS_NAME: [required("请输入系统名称")],
|
||||
SYS_LOGO: [required("请上传系统LOGO")],
|
||||
SYS_ICO: [required("请上传系统ICO")],
|
||||
SYS_VERSION: [required("请输入系统版本")],
|
||||
SYS_COPYRIGHT: [required("请输入系统版权")],
|
||||
SYS_COPYRIGHT_URL: [required("请输入系统版权链接")],
|
||||
SYS_DEFAULT_WORKBENCH_DATA: [required("请输入系统默认工作台数据")],
|
||||
SYS_WEB_STATUS: [required("请选择网站开启访问状态")],
|
||||
SYS_WEB_CLOSE_PROMPT: [required("请输入网站关闭提示")]
|
||||
});
|
||||
|
||||
const dialogVisible = ref(false); // 是否显示弹窗
|
||||
// 超链接临时对象
|
||||
const linkTmp = ref<SysConfig.FooterLinkProps>({
|
||||
name: "",
|
||||
url: "",
|
||||
sortCode: 99
|
||||
});
|
||||
// 是否是添加超链接
|
||||
const linkFormRef = ref<FormInstance>(); // 超链接表单实例
|
||||
// 超链接表单参数
|
||||
const linkForm = reactive<SysConfig.FooterLinkProps>({
|
||||
name: "",
|
||||
url: "",
|
||||
sortCode: 99
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const linkRules = reactive({
|
||||
name: [required("请输入超链接名称")],
|
||||
url: [required("请输入超链接地址")],
|
||||
sortCode: [required("请输入排序")]
|
||||
});
|
||||
|
||||
/** 删除超链接 */
|
||||
function handleClose(tag: string) {
|
||||
sysBaseProps.SYS_FOOTER_LINKS = sysBaseProps.SYS_FOOTER_LINKS.filter(item => item.name != tag);
|
||||
}
|
||||
|
||||
/** 打开超链接详情 */
|
||||
function linkDetail(value: SysConfig.FooterLinkProps) {
|
||||
linkTmp.value = value;
|
||||
linkForm.name = value.name;
|
||||
linkForm.url = value.url;
|
||||
linkForm.sortCode = value.sortCode;
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
/** 显示超链接表单 */
|
||||
function showLinkModel() {
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
/** 保存超链接 */
|
||||
function saveLink() {
|
||||
linkFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//判断是编辑还是添加
|
||||
if (linkTmp.value.name) {
|
||||
//删除旧的值,添加新的值
|
||||
sysBaseProps.SYS_FOOTER_LINKS = sysBaseProps.SYS_FOOTER_LINKS.filter(item => item.name != linkTmp.value.name);
|
||||
}
|
||||
sysBaseProps.SYS_FOOTER_LINKS.push({ ...linkForm });
|
||||
//根据sortCode排序,sortCode越小越靠前
|
||||
sysBaseProps.SYS_FOOTER_LINKS.sort((a, b) => a.sortCode - b.sortCode);
|
||||
//关闭弹窗,恢复默认值
|
||||
dialogVisible.value = false;
|
||||
linkTmp.value.name = "";
|
||||
linkTmp.value.url = "";
|
||||
linkTmp.value.sortCode = 99;
|
||||
linkFormRef.value?.resetFields();
|
||||
});
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
function onSubmit() {
|
||||
sysConfigFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
submitLoading.value = true;
|
||||
//组装参数
|
||||
const param: SysConfig.ConfigInfo[] = Object.entries(sysBaseProps).map(item => {
|
||||
return {
|
||||
id: 0,
|
||||
category: SysConfigTypeEnum.SYS_BASE,
|
||||
configKey: item[0],
|
||||
//configValue 如果是对象就转成json字符串
|
||||
configValue: typeof item[1] === "object" ? JSON.stringify(item[1]) : String(item[1])
|
||||
};
|
||||
});
|
||||
//提交数据
|
||||
sysConfigApi.configEditForm(param).finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
sysConfigFormRef.value?.resetFields();
|
||||
linkFormRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
* @Description:
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-22 16:59:28
|
||||
!-->
|
||||
<template>
|
||||
<div class="card">
|
||||
<el-tabs v-model="activeName" class="-mt-4">
|
||||
<el-tab-pane label="系统配置" name="sysConfig"><SysBaseConfig :sys-configs="sysBase" /></el-tab-pane>
|
||||
<el-tab-pane label="安全配置" name="safety"><SafetyConfig :sys-configs="safetyConfig" /></el-tab-pane>
|
||||
<el-tab-pane label="MQTT配置" name="mqtt"><MqttConfig :sys-configs="mqttConfig" /></el-tab-pane>
|
||||
<el-tab-pane label="文件配置" name="file">开发中......</el-tab-pane>
|
||||
<el-tab-pane label="其他配置" name="other"><OtherConfig v-if="activeName === 'other'" /></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SysBaseConfig from "./components/sysBaseConfig.vue";
|
||||
import SafetyConfig from "./components/safetyConfig/index.vue";
|
||||
import OtherConfig from "./components/otherConfig/index.vue";
|
||||
import MqttConfig from "./components/mqttConfig/index.vue";
|
||||
import { SysConfig, sysConfigApi } from "@/api";
|
||||
import { SysConfigTypeEnum } from "@/enums";
|
||||
|
||||
const activeName = ref("sysConfig");
|
||||
|
||||
const sysBase = ref<SysConfig.ConfigInfo[]>([]);
|
||||
const safetyConfig = ref<SysConfig.ConfigInfo[]>([]);
|
||||
const mqttConfig = ref<SysConfig.ConfigInfo[]>([]);
|
||||
onMounted(async () => {
|
||||
// 获取系统配置
|
||||
await sysConfigApi.list().then(res => {
|
||||
if (res.data) {
|
||||
//sysBase赋值
|
||||
sysBase.value = res.data.filter(item => item.category == SysConfigTypeEnum.SYS_BASE);
|
||||
//safetyBase赋值
|
||||
safetyConfig.value = res.data.filter(item => item.category == SysConfigTypeEnum.LOGIN_POLICY || item.category == SysConfigTypeEnum.PWD_POLICY);
|
||||
//mqttConfig赋值
|
||||
mqttConfig.value = res.data.filter(item => item.category == SysConfigTypeEnum.MQTT_POLICY);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-tabs__item) {
|
||||
font-size: large;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<!--
|
||||
* @Description: 字典表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:05
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" :title="`${dictProps.opt}字典`" form-size="600px">
|
||||
<el-form
|
||||
ref="dictFormRef"
|
||||
:rules="rules"
|
||||
:disabled="dictProps.disabled"
|
||||
:model="dictProps.record"
|
||||
:hide-required-asterisk="dictProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="字典名称" prop="dictLabel">
|
||||
<s-input v-model="dictProps.record.dictLabel"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="字典值" prop="dictValue">
|
||||
<s-input v-model="dictProps.record.dictValue"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="字典状态" prop="status">
|
||||
<s-radio-group v-model="dictProps.record.status" :options="statusOptions" />
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="dictProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!dictProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysDict, sysDictApi } from "@/api";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { FormOptEnum, CommonStatusEnum, SysDictEnum, DictCategoryEnum } from "@/enums";
|
||||
import { FormInstance } from "element-plus";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
|
||||
// 字典类型选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
|
||||
// 表单参数接口
|
||||
interface DictProps extends FormProps.Base<SysDict.DictInfo> {
|
||||
category: DictCategoryEnum; //字典分类
|
||||
parentId?: number | string; //父Id
|
||||
}
|
||||
|
||||
// 表单参数
|
||||
const dictProps = reactive<DictProps>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false,
|
||||
category: DictCategoryEnum.FRM
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
dictLabel: [required("请输入字典名称")],
|
||||
dictValue: [required("请输入字典值")],
|
||||
status: [required("请选择状态")],
|
||||
sortCode: [required("请输入排序")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: DictProps) {
|
||||
Object.assign(dictProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
dictProps.record.status = CommonStatusEnum.ENABLE;
|
||||
dictProps.record.sortCode = 99;
|
||||
dictProps.record.category = props.category;
|
||||
dictProps.record.parentId = props.parentId;
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const dictFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
dictFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await sysDictApi
|
||||
.submitForm(dictProps.record, dictProps.record.id != undefined)
|
||||
.then(() => {
|
||||
dictProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,216 @@
|
||||
<!--
|
||||
* @Description: 系统字典
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:10
|
||||
!-->
|
||||
<template>
|
||||
<div class="main-box gap-10px">
|
||||
<div class="w-7/12">
|
||||
<ProTable
|
||||
ref="proTable"
|
||||
:columns="columns"
|
||||
:tool-button="false"
|
||||
:request-api="getDictPage"
|
||||
:init-param="initParam"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<el-space>
|
||||
<el-radio-group v-model="initParam.category" class="mb-15px">
|
||||
<el-radio-button v-for="(item, index) in dctTypeOptions" :key="index" :label="item.value">{{ item.label }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-input v-model="initParam.searchKey" placeholder="请输入字典名称" class="mb-15px">
|
||||
<template #append>
|
||||
<el-button :icon="Search" class="el-input-button" @click="RefreshTable" />
|
||||
</template>
|
||||
</el-input>
|
||||
<s-button :suffix="title" @click="onOpen(FormOptEnum.ADD, initParam.category as DictCategoryEnum)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
:suffix="title"
|
||||
:disabled="!scope.isSelected"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选字典')"
|
||||
/>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button
|
||||
link
|
||||
:opt="FormOptEnum.EDIT"
|
||||
@click="onOpen(FormOptEnum.EDIT, initParam.category as DictCategoryEnum, scope.row.parentId, scope.row)"
|
||||
/>
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.dictLabel}】字典`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
</div>
|
||||
<ProTable
|
||||
ref="proTableChild"
|
||||
:columns="columns"
|
||||
:tool-button="false"
|
||||
:request-api="sysDictApi.page"
|
||||
:request-auto="false"
|
||||
:init-param="childInitParam"
|
||||
>
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<el-space>
|
||||
<el-input v-model="childInitParam.searchKey" placeholder="请输入字典名称" class="mb-15px">
|
||||
<template #append>
|
||||
<el-button :icon="Search" class="el-input-button" />
|
||||
</template>
|
||||
</el-input>
|
||||
<s-button :suffix="title" @click="onOpen(FormOptEnum.ADD, childInitParam.category as DictCategoryEnum, childInitParam.parentId)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
:suffix="title"
|
||||
plain
|
||||
:disabled="!scope.isSelected"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选字典')"
|
||||
/>
|
||||
</el-space>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button
|
||||
link
|
||||
:opt="FormOptEnum.EDIT"
|
||||
@click="onOpen(FormOptEnum.EDIT, childInitParam.category as DictCategoryEnum, scope.row.parentId, scope.row)"
|
||||
/>
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.dictLabel}字典`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="sysDict">
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import { SysDict, sysDictApi } from "@/api";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { SysDictEnum, FormOptEnum, CommonStatusEnum, DictCategoryEnum } from "@/enums";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import Form from "./components/form.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const title = "字典"; //功能名称
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 左侧表格初始化条件
|
||||
interface InitParam {
|
||||
category?: string; //字典分类
|
||||
searchKey?: string; //关键字
|
||||
}
|
||||
//右侧表格列初始化条件
|
||||
interface ChildInitParam extends InitParam {
|
||||
parentId: number | string; //父Id
|
||||
}
|
||||
const defaultParentId: string | number = -1; //默认父Id
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive<InitParam>({ category: DictCategoryEnum.FRM }); //主表格初始化参数
|
||||
const childInitParam = reactive<ChildInitParam>({ parentId: defaultParentId }); //子表格初始化参数
|
||||
|
||||
// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
|
||||
// 默认不做操作就直接在 ProTable 组件上绑定 :request="getUserList"
|
||||
const getDictPage = (params: any) => {
|
||||
childInitParam.parentId = defaultParentId;
|
||||
return sysDictApi.page(params);
|
||||
};
|
||||
|
||||
// 字典类型选项
|
||||
const dctTypeOptions = dictStore.getDictList(SysDictEnum.DICT_CATEGORY);
|
||||
|
||||
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
|
||||
const proTable = ref<ProTableInstance>();
|
||||
const proTableChild = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysDict.DictInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "dictLabel", label: "字典名称" },
|
||||
{ prop: "dictValue", label: "字典值" },
|
||||
{ prop: "status", label: "状态", width: 100 },
|
||||
{ prop: "sortCode", label: "排序", width: 80 },
|
||||
{ prop: "operation", label: "操作", fixed: "right", width: 150 }
|
||||
];
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, category: DictCategoryEnum, parentId: number | string = 0, record: {} | SysDict.DictInfo = {}) {
|
||||
//如果parentId是-1代表没有选择任何字典,此时不打开表单
|
||||
if (parentId === defaultParentId) {
|
||||
ElMessage("请先选择字典");
|
||||
} else {
|
||||
formRef.value?.onOpen({
|
||||
opt: opt,
|
||||
record: record,
|
||||
category: category,
|
||||
parentId: parentId,
|
||||
successful: () => proTableChild.value?.refresh() //刷新子表格
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(sysDictApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh(); //刷新主表格
|
||||
proTableChild.value?.refresh(); //刷新子表格
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格当前行变化
|
||||
* @param val 选中的值
|
||||
*/
|
||||
function handleCurrentChange(val: SysDict.DictInfo | undefined) {
|
||||
// 如果val存在,则将parentId和category赋值给childInitParam
|
||||
if (val) {
|
||||
childInitParam.parentId = val.id;
|
||||
childInitParam.category = val.category;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 表格按钮样式
|
||||
.el-space__item > .el-button {
|
||||
margin-top: -13px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<!--
|
||||
* @Description: 复制
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:25
|
||||
!-->
|
||||
<template>
|
||||
<form-container v-model="visible" title="复制组织" form-size="600px">
|
||||
<el-form ref="orgFormRef" :rules="rules" :model="copyProps" label-width="auto">
|
||||
<s-form-item label="目标组织" prop="targetId">
|
||||
<org-selector v-model:org-value="copyProps.targetId" />
|
||||
</s-form-item>
|
||||
<s-form-item label="包含子集" prop="containsChild" :tooltip="{ content: '将组织下的子机构也复制一份到目标组织' }">
|
||||
<s-radio-group v-model="copyProps.containsChild" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="包含职位" prop="containsPosition" :tooltip="{ content: '将组织下的子机构也复制一份到目标组织' }">
|
||||
<s-radio-group v-model="copyProps.containsPosition" :options="yesOptions" button />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { sysOrgApi } from "@/api";
|
||||
import { SysDictEnum } from "@/enums";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 是否选项
|
||||
const yesOptions = dictStore.getDictList(SysDictEnum.YES_NO);
|
||||
|
||||
// 表单参数
|
||||
interface copyProps {
|
||||
/** 目标组织id */
|
||||
targetId: string | number;
|
||||
/** 选择的组织id */
|
||||
ids: string[] | number[];
|
||||
/** 是否包含子集 */
|
||||
containsChild: boolean;
|
||||
/** 是否包含职位 */
|
||||
containsPosition: boolean;
|
||||
/** 成功后的回调 */
|
||||
successful?: () => void;
|
||||
}
|
||||
|
||||
// 表单参数
|
||||
const copyProps = reactive<copyProps>({
|
||||
targetId: 0,
|
||||
ids: [],
|
||||
containsChild: true,
|
||||
containsPosition: true
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
targetId: [required("请选择目标组织")],
|
||||
containsChild: [required("请选择是否包含子集")],
|
||||
containsPosition: [required("请选择是否包含职位")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(ids: string[] | number[], successful: () => void) {
|
||||
visible.value = true; //显示表单
|
||||
copyProps.ids = ids;
|
||||
copyProps.successful = successful;
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const orgFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
orgFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await sysOrgApi
|
||||
.copy(copyProps)
|
||||
.then(() => {
|
||||
copyProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<!--
|
||||
* @Description: 表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:28
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<form-container v-model="visible" :title="`${orgProps.opt}组织`" form-size="600px">
|
||||
<el-form
|
||||
ref="orgFormRef"
|
||||
:rules="rules"
|
||||
:disabled="orgProps.disabled"
|
||||
:model="orgProps.record"
|
||||
:hide-required-asterisk="orgProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="上级组织" prop="parentId">
|
||||
<org-selector v-model:org-value="orgProps.record.parentId!" />
|
||||
</s-form-item>
|
||||
<s-form-item label="组织名称" prop="name">
|
||||
<s-input v-model="orgProps.record.name"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="组织编码" prop="code">
|
||||
<s-input v-model="orgProps.record.code" placeholder="请填写组织编码,不填则为随机值" clearable></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="组织分类" prop="category">
|
||||
<s-radio-group v-model="orgProps.record.category" :options="orgCategoryOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="状态" prop="status">
|
||||
<s-radio-group v-model="orgProps.record.status" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="指定主管" prop="directorId">
|
||||
<el-button link type="primary" @click="showSelector">选择</el-button>
|
||||
<el-tag v-if="orgProps.record.directorId" class="ml-3px" type="warning" closable @close="removeDirector">{{
|
||||
orgProps.record.directorInfo?.name
|
||||
}}</el-tag>
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="orgProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!orgProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
<user-selector
|
||||
ref="userSelectorRef"
|
||||
:org-tree-api="sysOrgApi.tree"
|
||||
:position-tree-api="sysPositionApi.tree"
|
||||
:role-tree-api="sysRoleApi.tree"
|
||||
:user-selector-api="sysUserApi.selector"
|
||||
@successful="handleChooseUser"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysOrg, SysUser, sysOrgApi, sysPositionApi, sysRoleApi, sysUserApi } from "@/api";
|
||||
import { FormOptEnum, SysDictEnum } from "@/enums";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 组织类型选项
|
||||
const orgCategoryOptions = dictStore.getDictList(SysDictEnum.ORG_CATEGORY);
|
||||
// 通用状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
|
||||
// 表单参数
|
||||
const orgProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [required("请输入组织名称")],
|
||||
parentId: [required("请选择上级组织")],
|
||||
category: [required("请选择组织类型")],
|
||||
status: [required("请选择状态")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<SysOrg.SysOrgInfo>) {
|
||||
Object.assign(orgProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
orgProps.record.sortCode = 99;
|
||||
orgProps.record.category = orgCategoryOptions[0].value;
|
||||
orgProps.record.status = statusOptions[0].value;
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
sysOrgApi.detail({ id: props.record.id }).then(res => {
|
||||
orgProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const orgFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
orgFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await sysOrgApi
|
||||
.submitForm(orgProps.record, orgProps.record.id != undefined)
|
||||
.then(() => {
|
||||
orgProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用
|
||||
/** 显示用户选择器 */
|
||||
function showSelector() {
|
||||
//将orgProps.record.directorInfo转为 SysUser.SysUserInfo[]类型
|
||||
const directorInfo = orgProps.record.directorInfo ? [orgProps.record.directorInfo] : [];
|
||||
userSelectorRef.value?.showSelector(directorInfo);
|
||||
}
|
||||
|
||||
/** 选择用户 */
|
||||
function handleChooseUser(data: SysUser.SysUserInfo[]) {
|
||||
// 选择用户后,将用户id赋值给orgProps.record.directorId
|
||||
if (data.length > 0) {
|
||||
orgProps.record.directorId = data[0].id;
|
||||
orgProps.record.directorInfo = data[0];
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除主管 */
|
||||
function removeDirector() {
|
||||
orgProps.record.directorId = null;
|
||||
orgProps.record.directorInfo = null;
|
||||
}
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,152 @@
|
||||
<!--
|
||||
* @Description: 组织管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:32
|
||||
!-->
|
||||
<template>
|
||||
<div class="main-box">
|
||||
<TreeFilter
|
||||
ref="treeFilter"
|
||||
label="name"
|
||||
title="组织列表"
|
||||
:request-api="sysOrgApi.tree"
|
||||
:default-value="initParam.parentId"
|
||||
@change="changeTreeFilter"
|
||||
/>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="组织列表" :columns="columns" :request-api="sysOrgApi.page" :init-param="initParam">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="组织" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button :disabled="!scope.isSelected" :icon="CopyDocument" @click="onOpenCopy(scope.selectedListIds)">复制组织</s-button>
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="组织"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选组织')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.name}】组织`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 复制表单 -->
|
||||
<Copy ref="copyRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="sysOrg">
|
||||
import { sysOrgApi, SysOrg } from "@/api";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { CopyDocument } from "@element-plus/icons-vue";
|
||||
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import TreeFilter from "@/components/TreeFilter/index.vue";
|
||||
import Form from "./components/form.vue";
|
||||
import Copy from "./components/copy.vue";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 组织类型选项
|
||||
const orgCategoryOptions = dictStore.getDictList(SysDictEnum.ORG_CATEGORY);
|
||||
// 状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
|
||||
|
||||
interface InitParam {
|
||||
parentId: number | string;
|
||||
}
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive<InitParam>({ parentId: 0 });
|
||||
// 获取 ProTable 元素
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysOrg.SysOrgInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "name", label: "组织名称", search: { el: "input" } },
|
||||
{ prop: "names", label: "组织全称" },
|
||||
{
|
||||
prop: "category",
|
||||
label: "分类",
|
||||
enum: orgCategoryOptions,
|
||||
search: { el: "select" }
|
||||
},
|
||||
{ prop: "code", label: "编码", search: { el: "input" } },
|
||||
{ prop: "status", label: "状态", enum: statusOptions, search: { el: "select" } },
|
||||
{ prop: "sortCode", label: "排序" },
|
||||
{ prop: "createTime", label: "创建时间" },
|
||||
|
||||
{ prop: "operation", label: "操作", width: 150, fixed: "right" }
|
||||
];
|
||||
|
||||
/** 部门切换 */
|
||||
function changeTreeFilter(val: number | string) {
|
||||
proTable.value!.pageable.pageNum = 1;
|
||||
if (val != "") {
|
||||
// 如果传入的val不为空,则将val赋值给initParam.parentId
|
||||
initParam.parentId = val;
|
||||
} else {
|
||||
// 否则将initParam.parentId赋值为0
|
||||
initParam.parentId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const copyRef = ref<InstanceType<typeof Copy> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开复制表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpenCopy(ids: string[] | number[]) {
|
||||
copyRef.value?.onOpen(ids, RefreshTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(sysOrgApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh(); //刷新表格
|
||||
treeFilter.value?.refresh(); //刷新树形筛选器
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,125 @@
|
||||
<!--
|
||||
* @Description: 表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:37
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<form-container v-model="visible" :title="`${positionProps.opt}职位`" form-size="600px">
|
||||
<el-form
|
||||
ref="positionFormRef"
|
||||
:rules="rules"
|
||||
:disabled="positionProps.disabled"
|
||||
:model="positionProps.record"
|
||||
:hide-required-asterisk="positionProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<s-form-item label="所属组织" prop="orgId">
|
||||
<org-selector v-model:org-value="positionProps.record.orgId!" :show-all="false" />
|
||||
</s-form-item>
|
||||
<s-form-item label="职位名称" prop="name">
|
||||
<s-input v-model="positionProps.record.name"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="职位编码" prop="code">
|
||||
<s-input v-model="positionProps.record.code" placeholder="请填写职位编码,不填则为随机值"></s-input>
|
||||
</s-form-item>
|
||||
<s-form-item label="职位分类" prop="category">
|
||||
<s-radio-group v-model="positionProps.record.category" :options="posCategoryOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="状态" prop="status">
|
||||
<s-radio-group v-model="positionProps.record.status" :options="statusOptions" button />
|
||||
</s-form-item>
|
||||
<s-form-item label="排序" prop="sortCode">
|
||||
<el-slider v-model="positionProps.record.sortCode" show-input :min="1" />
|
||||
</s-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!positionProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysPosition, sysPositionApi } from "@/api";
|
||||
import { FormOptEnum, SysDictEnum } from "@/enums";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
|
||||
const visible = ref(false); //是否显示表单
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 职位类型选项
|
||||
const posCategoryOptions = dictStore.getDictList(SysDictEnum.POSITION_CATEGORY);
|
||||
// 通用状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
|
||||
// 表单参数
|
||||
const positionProps = reactive<FormProps.Base<SysPosition.SysPositionInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
parentId: [required("请选择所属组织")],
|
||||
name: [required("请输入职位名称")],
|
||||
orgId: [required("请选择所属职位")],
|
||||
category: [required("请选择职位类型")],
|
||||
status: [required("请选择状态")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<SysPosition.SysPositionInfo>) {
|
||||
Object.assign(positionProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
positionProps.record.sortCode = 99;
|
||||
positionProps.record.category = posCategoryOptions[0].value;
|
||||
positionProps.record.status = statusOptions[0].value;
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
sysPositionApi.detail({ id: props.record.id }).then(res => {
|
||||
positionProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const positionFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
positionFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await sysPositionApi
|
||||
.submitForm(positionProps.record, positionProps.record.id != undefined)
|
||||
.then(() => {
|
||||
positionProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<!--
|
||||
* @Description: 职位管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:43
|
||||
!-->
|
||||
<template>
|
||||
<div class="main-box">
|
||||
<TreeFilter
|
||||
ref="treeFilter"
|
||||
label="name"
|
||||
title="组织列表"
|
||||
:request-api="sysOrgApi.tree"
|
||||
:default-value="initParam.orgId"
|
||||
@change="changeTreeFilter"
|
||||
/>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="职位列表" :columns="columns" :request-api="sysPositionApi.page" :init-param="initParam">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="职位" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="职位"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选职位')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
|
||||
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
|
||||
}}</el-tag>
|
||||
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.name}】职位`)" />
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="sysPosition">
|
||||
import { sysOrgApi, sysPositionApi, SysPosition } from "@/api";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import TreeFilter from "@/components/TreeFilter/index.vue";
|
||||
import Form from "./components/form.vue";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// 职位类型选项
|
||||
const posCategoryOptions = dictStore.getDictList(SysDictEnum.POSITION_CATEGORY);
|
||||
// 状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
|
||||
|
||||
interface InitParam {
|
||||
orgId: number | string;
|
||||
}
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive<InitParam>({ orgId: 0 });
|
||||
// 获取 ProTable 元素
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysPosition.SysPositionInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "name", label: "职位名称", search: { el: "input" } },
|
||||
{
|
||||
prop: "category",
|
||||
label: "职位分类",
|
||||
enum: posCategoryOptions,
|
||||
search: { el: "select" }
|
||||
},
|
||||
{ prop: "code", label: "职位编码", search: { el: "input" } },
|
||||
{ prop: "status", label: "状态", enum: statusOptions, search: { el: "select" } },
|
||||
{ prop: "sortCode", label: "排序" },
|
||||
{ prop: "createTime", label: "创建时间" },
|
||||
|
||||
{ prop: "operation", label: "操作", width: 150, fixed: "right" }
|
||||
];
|
||||
|
||||
/** 部门切换 */
|
||||
function changeTreeFilter(val: number | string) {
|
||||
proTable.value!.pageable.pageNum = 1;
|
||||
if (val != "") {
|
||||
// 如果传入的val不为空,则将val赋值给initParam.parentId
|
||||
initParam.orgId = val;
|
||||
} else {
|
||||
// 否则将initParam.parentId赋值为0
|
||||
initParam.orgId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | SysPosition.SysPositionInfo = {}) {
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(sysPositionApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh(); //刷新表格
|
||||
treeFilter.value?.refresh(); //刷新树形筛选器
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,167 @@
|
||||
<!--
|
||||
* @Description: 基本信息
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:50
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<el-tab-pane label="基础信息" name="basic">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="账号" prop="account">
|
||||
<s-input v-model="userInfo.account"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="姓名" prop="name">
|
||||
<s-input v-model="userInfo.name"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="性别" prop="gender">
|
||||
<s-radio-group v-model="userInfo.gender" :options="genderOptions" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="昵称" prop="nickname">
|
||||
<s-input v-model="userInfo.nickname"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="手机号" prop="phone">
|
||||
<s-input v-model="userInfo.phone"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="邮箱" prop="email">
|
||||
<s-input v-model="userInfo.email"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="出生日期" prop="birthday">
|
||||
<date-picker v-model="userInfo.birthday"></date-picker>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="入职日期" prop="entryDate">
|
||||
<date-picker v-model="userInfo.entryDate"></date-picker>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="员工编号" prop="empNo">
|
||||
<s-input v-model="userInfo.empNo"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="职级" prop="positionLevel">
|
||||
<s-input v-model="userInfo.positionLevel" clearable></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="部门和职位" prop="positionId">
|
||||
<position-selector v-model="userInfo.orgAndPosIdList" @change="handleChange" />
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="主管" prop="directorId">
|
||||
<el-button link type="primary" @click="showSelector">选择</el-button>
|
||||
<el-tag v-if="userInfo.directorId" class="ml-3px" type="warning" closable @close="removeDirector">{{
|
||||
userInfo.directorInfo?.name
|
||||
}}</el-tag>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<user-selector
|
||||
ref="userSelectorRef"
|
||||
:org-tree-api="sysOrgApi.tree"
|
||||
:position-tree-api="sysPositionApi.tree"
|
||||
:role-tree-api="sysRoleApi.tree"
|
||||
:user-selector-api="sysUserApi.selector"
|
||||
@successful="handleChooseUser"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysUser, sysOrgApi, sysPositionApi, sysRoleApi, sysUserApi } from "@/api";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { SysDictEnum } from "@/enums";
|
||||
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// props
|
||||
interface FormProps {
|
||||
modelValue: Partial<SysUser.SysUserInfo>;
|
||||
}
|
||||
const emit = defineEmits(["update:modelValue"]); //定义emit
|
||||
const props = defineProps<FormProps>(); //定义props
|
||||
// 用户信息
|
||||
const userInfo = computed({
|
||||
get: () => props.modelValue,
|
||||
set: val => emit("update:modelValue", val)
|
||||
});
|
||||
|
||||
const genderOptions = dictStore.getDictList(SysDictEnum.GENDER); //性别选项
|
||||
onMounted(() => {
|
||||
// 初始化
|
||||
userInfo.value.gender = userInfo.value.gender ? userInfo.value.gender : genderOptions[0].value;
|
||||
});
|
||||
|
||||
/** 用于处理职位选择器改变值的情况 */
|
||||
function handleChange(value: any) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
// 获取改变后的值中最后一位的值,即positionId
|
||||
let positionId = value[value.length - 1];
|
||||
// 获取改变后的值中最后两位的值,即orgId
|
||||
let orgId = value[value.length - 2];
|
||||
// 将orgId赋值给userInfo的value的orgId
|
||||
userInfo.value.orgId = orgId;
|
||||
// 将positionId赋值给userInfo的value的positionId
|
||||
userInfo.value.positionId = positionId;
|
||||
}
|
||||
|
||||
const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器ref
|
||||
/** 显示用户选择器 */
|
||||
function showSelector() {
|
||||
//将userInfo.value.directorInfo转为 SysUser.SysUserInfo[]类型
|
||||
const directorInfo = userInfo.value.directorInfo ? [userInfo.value.directorInfo] : [];
|
||||
userSelectorRef.value?.showSelector(directorInfo);
|
||||
}
|
||||
|
||||
/** 选择用户 */
|
||||
function handleChooseUser(data: SysUser.SysUserInfo[]) {
|
||||
// 选择用户后,将用户id赋值给userInfo.value.directorId
|
||||
if (data.length > 0) {
|
||||
userInfo.value.directorId = data[0].id;
|
||||
userInfo.value.directorInfo = data[0];
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除主管 */
|
||||
function removeDirector() {
|
||||
userInfo.value.directorId = null;
|
||||
userInfo.value.directorInfo = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-input__wrapper) {
|
||||
width: 100% !important;
|
||||
}
|
||||
:deep(.el-date-editor.el-input) {
|
||||
width: 92% !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,145 @@
|
||||
<!--
|
||||
* @Description: 更多信息
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:55
|
||||
!-->
|
||||
<template>
|
||||
<el-tab-pane label="更多信息" name="more">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="民族" prop="nation">
|
||||
<s-select v-model="userInfo.nation" :options="nationOptions"></s-select>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="籍贯" prop="nativePlace">
|
||||
<s-input v-model="userInfo.nativePlace"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="家庭住址" prop="homeAddress">
|
||||
<s-input v-model="userInfo.homeAddress" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="通信地址" prop="mailingAddress">
|
||||
<s-input v-model="userInfo.mailingAddress" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="证件类型" prop="nation">
|
||||
<s-select v-model="userInfo.idCardType" :options="cardTypeOptions"></s-select>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="证件号码" prop="idCardNumber">
|
||||
<s-input v-model="userInfo.idCardNumber"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="文化程度" prop="cultureLevel">
|
||||
<s-select v-model="userInfo.cultureLevel" :options="cultureLevelOptions"></s-select>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="政治面貌" prop="politicalOutlook">
|
||||
<s-input v-model="userInfo.politicalOutlook"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="毕业学校" prop="college">
|
||||
<s-input v-model="userInfo.college"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="学历" prop="education">
|
||||
<s-input v-model="userInfo.education"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="学制" prop="eduLength">
|
||||
<s-input v-model="userInfo.eduLength"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="学位" prop="degree">
|
||||
<s-input v-model="userInfo.degree"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="家庭电话" prop="homeTel">
|
||||
<s-input v-model="userInfo.homeTel"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="办公电话" prop="officeTel">
|
||||
<s-input v-model="userInfo.officeTel"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="紧急联系人" prop="emergencyContact">
|
||||
<s-input v-model="userInfo.emergencyContact"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<s-form-item label="紧急联系电话" prop="emergencyPhone">
|
||||
<s-input v-model="userInfo.emergencyPhone"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<s-form-item label="紧急联系人地址" prop="emergencyAddress">
|
||||
<s-input v-model="userInfo.emergencyAddress"></s-input>
|
||||
</s-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SysUser } from "@/api";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import { SysDictEnum } from "@/enums";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
// props
|
||||
interface FormProps {
|
||||
modelValue: Partial<SysUser.SysUserInfo>;
|
||||
}
|
||||
const emit = defineEmits(["update:modelValue"]); //定义emit
|
||||
const props = defineProps<FormProps>(); //定义props
|
||||
// 用户信息
|
||||
const userInfo = computed({
|
||||
get: () => props.modelValue,
|
||||
set: val => emit("update:modelValue", val)
|
||||
});
|
||||
|
||||
// 通用状态选项
|
||||
const nationOptions = dictStore.getDictList(SysDictEnum.NATION);
|
||||
// 证件类型选项
|
||||
const cardTypeOptions = dictStore.getDictList(SysDictEnum.IDCARD_TYPE);
|
||||
//文化程度选项
|
||||
const cultureLevelOptions = dictStore.getDictList(SysDictEnum.CULTURE_LEVEL);
|
||||
onMounted(() => {
|
||||
// 初始化默认值
|
||||
// userInfo.value.nation = userInfo.value.nation ? userInfo.value.nation : nationOptions[0].value;
|
||||
userInfo.value.idCardType = userInfo.value.idCardType ? userInfo.value.idCardType : cardTypeOptions[0].value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,111 @@
|
||||
<!--
|
||||
* @Description: 表单
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:45:59
|
||||
!-->
|
||||
<template>
|
||||
<div>
|
||||
<form-container v-model="visible" :title="`${sysUserProps.opt}用户`" form-size="800px" @close="onClose">
|
||||
<el-form
|
||||
ref="sysUserFormRef"
|
||||
:rules="rules"
|
||||
:disabled="sysUserProps.disabled"
|
||||
:model="sysUserProps.record"
|
||||
:hide-required-asterisk="sysUserProps.disabled"
|
||||
label-width="auto"
|
||||
label-suffix=" :"
|
||||
>
|
||||
<el-tabs v-model="activeName">
|
||||
<Basic v-model="sysUserProps.record"></Basic>
|
||||
<More v-model="sysUserProps.record"></More>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onClose"> 取消 </el-button>
|
||||
<el-button v-show="!sysUserProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
|
||||
</template>
|
||||
</form-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="sysUserForm">
|
||||
import { SysUser, sysUserApi } from "@/api";
|
||||
import { FormOptEnum, SysDictEnum } from "@/enums";
|
||||
import { required } from "@/utils/formRules";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import Basic from "./form_basic.vue";
|
||||
import More from "./form_more.vue";
|
||||
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
const visible = ref(false); //是否显示表单
|
||||
const activeName = ref("basic");
|
||||
// 通用状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
|
||||
// 表单参数
|
||||
const sysUserProps = reactive<FormProps.Base<SysUser.SysUserInfo>>({
|
||||
opt: FormOptEnum.ADD,
|
||||
record: {},
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
account: [required("请输入账号")],
|
||||
name: [required("请输入姓名")],
|
||||
gender: [required("请选择性别")],
|
||||
positionId: [required("请选择部门和职位")]
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param props 表单参数
|
||||
*/
|
||||
function onOpen(props: FormProps.Base<SysUser.SysUserInfo>) {
|
||||
Object.assign(sysUserProps, props); //合并参数
|
||||
if (props.opt == FormOptEnum.ADD) {
|
||||
//如果是新增,设置默认值
|
||||
sysUserProps.record.sortCode = 99;
|
||||
sysUserProps.record.status = statusOptions[0].value;
|
||||
}
|
||||
visible.value = true; //显示表单
|
||||
if (props.record.id) {
|
||||
//如果传了id,就去请求api获取record
|
||||
sysUserApi.detail({ id: props.record.id }).then(res => {
|
||||
sysUserProps.record = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据(新增/编辑)
|
||||
const sysUserFormRef = ref<FormInstance>();
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
sysUserFormRef.value?.validate(async valid => {
|
||||
if (!valid) return; //表单验证失败
|
||||
//提交表单
|
||||
await sysUserApi
|
||||
.submitForm(sysUserProps.record, sysUserProps.record.id != undefined)
|
||||
.then(() => {
|
||||
sysUserProps.successful!(); //调用父组件的successful方法
|
||||
})
|
||||
.finally(() => {
|
||||
onClose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 关闭表单*/
|
||||
function onClose() {
|
||||
visible.value = false;
|
||||
activeName.value = "basic";
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
onOpen
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,251 @@
|
||||
<!--
|
||||
* @Description: 用户管理
|
||||
* @Author: huguodong
|
||||
* @Date: 2023-12-15 15:46:03
|
||||
!-->
|
||||
<template>
|
||||
<div class="main-box">
|
||||
<TreeFilter
|
||||
ref="treeFilter"
|
||||
label="name"
|
||||
title="组织列表"
|
||||
:request-api="sysOrgApi.tree"
|
||||
:default-value="initParam.orgId"
|
||||
@change="changeTreeFilter"
|
||||
/>
|
||||
<div class="table-box">
|
||||
<ProTable ref="proTable" title="用户列表" :columns="columns" :request-api="sysUserApi.page" :init-param="initParam">
|
||||
<!-- 表格 header 按钮 -->
|
||||
<template #tableHeader="scope">
|
||||
<s-button suffix="用户" @click="onOpen(FormOptEnum.ADD)" />
|
||||
<s-button
|
||||
type="danger"
|
||||
plain
|
||||
suffix="用户"
|
||||
:opt="FormOptEnum.DELETE"
|
||||
:disabled="!scope.isSelected"
|
||||
@click="onDelete(scope.selectedListIds, '删除所选用户')"
|
||||
/>
|
||||
</template>
|
||||
<!-- 头像 -->
|
||||
<template #avatar="scope">
|
||||
<el-avatar :src="scope.row.avatar" :size="30" />
|
||||
<!-- <span>{{ scope.row.avatar }}}</span> -->
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="scope">
|
||||
<el-switch :model-value="scope.row.status === CommonStatusEnum.ENABLE" :loading="switchLoading" @change="editStatus(scope.row)" />
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #operation="scope">
|
||||
<el-space>
|
||||
<s-button link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
|
||||
<s-button link :opt="FormOptEnum.DELETE" @click="onDelete([scope.row.id], `删除【${scope.row.account}】用户`)" />
|
||||
<el-dropdown @command="handleCommand">
|
||||
<el-link type="primary" :underline="false" :icon="ArrowDown"> 更多 </el-link>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="(item, index) in cmdEnum" :key="index" :command="command(scope.row, item)">{{ item }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-space>
|
||||
</template>
|
||||
</ProTable>
|
||||
<!-- 新增/编辑表单 -->
|
||||
<Form ref="formRef" />
|
||||
<!-- 角色选择器组件 -->
|
||||
<role-selector
|
||||
ref="roleSelectorRef"
|
||||
multiple
|
||||
:org-tree-api="sysOrgApi.tree"
|
||||
:role-selector-api="sysRoleApi.roleSelector"
|
||||
@successful="handleChooseRole"
|
||||
></role-selector>
|
||||
<!-- 授权资源组件 -->
|
||||
<GrantResource user ref="grantResourceRef" />
|
||||
<!-- 授权权限组件 -->
|
||||
<GrantPermission user ref="grantPermissionRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SysUser">
|
||||
import { sysOrgApi, sysUserApi, SysUser, SysRole, sysRoleApi } from "@/api";
|
||||
import { ArrowDown } from "@element-plus/icons-vue";
|
||||
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
|
||||
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
|
||||
import { useHandleData } from "@/hooks/useHandleData";
|
||||
import { useDictStore } from "@/stores/modules";
|
||||
import TreeFilter from "@/components/TreeFilter/index.vue";
|
||||
import Form from "./components/form/index.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { RoleSelectorInstance } from "@/components/Selectors/RoleSelector/interface";
|
||||
import GrantResource from "../../limit/role/components/grantResource.vue";
|
||||
import GrantPermission from "../../limit/role/components/grantPermission.vue";
|
||||
const dictStore = useDictStore(); //字典仓库
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
|
||||
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
|
||||
//状态开关loading
|
||||
const switchLoading = ref(false);
|
||||
|
||||
interface InitParam {
|
||||
orgId: number | string;
|
||||
}
|
||||
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
|
||||
const initParam = reactive<InitParam>({ orgId: 0 });
|
||||
// 获取 ProTable 元素
|
||||
const proTable = ref<ProTableInstance>();
|
||||
|
||||
// 表格配置项
|
||||
const columns: ColumnProps<SysUser.SysUserInfo>[] = [
|
||||
{ type: "selection", fixed: "left", width: 50 },
|
||||
{ prop: "searchKey", label: "姓名或账号", search: { el: "input" }, isShow: false },
|
||||
{ prop: "avatar", label: "头像", width: 80 },
|
||||
{ prop: "account", label: "账号" },
|
||||
{ prop: "name", label: "姓名" },
|
||||
{ prop: "gender", label: "性别", width: 80 },
|
||||
{ prop: "phone", label: "手机号" },
|
||||
{ prop: "orgName", label: "组织" },
|
||||
{ prop: "positionName", label: "职位" },
|
||||
{
|
||||
prop: "status",
|
||||
label: "状态",
|
||||
width: 90,
|
||||
enum: statusOptions,
|
||||
search: { el: "select" }
|
||||
},
|
||||
{ prop: "createTime", label: "创建时间" },
|
||||
{ prop: "operation", label: "操作", width: 230, fixed: "right" }
|
||||
];
|
||||
|
||||
/** 部门切换 */
|
||||
function changeTreeFilter(val: number | string) {
|
||||
proTable.value!.pageable.pageNum = 1;
|
||||
if (val != "") {
|
||||
// 如果传入的val不为空,则将val赋值给initParam.parentId
|
||||
initParam.orgId = val;
|
||||
} else {
|
||||
// 否则将initParam.parentId赋值为0
|
||||
initParam.orgId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null);
|
||||
|
||||
/**
|
||||
* 打开表单
|
||||
* @param opt 操作类型
|
||||
* @param record 记录
|
||||
*/
|
||||
function onOpen(opt: FormOptEnum, record: {} | SysUser.SysUserInfo = {}) {
|
||||
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ids id数组
|
||||
*/
|
||||
async function onDelete(ids: string[], msg: string) {
|
||||
// 二次确认 => 请求api => 刷新表格
|
||||
await useHandleData(sysUserApi.delete, { ids }, msg);
|
||||
RefreshTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新表格
|
||||
*/
|
||||
function RefreshTable() {
|
||||
proTable.value?.refresh(); //刷新表格
|
||||
treeFilter.value?.refresh(); //刷新树形筛选器
|
||||
}
|
||||
|
||||
/** 更多下拉菜单命令枚举 */
|
||||
enum cmdEnum {
|
||||
ResetPassword = "重置密码",
|
||||
GrantRole = "授权角色",
|
||||
GrantResource = "授权资源",
|
||||
GrantPermission = "授权权限"
|
||||
}
|
||||
|
||||
/** 下拉菜单参数接口 */
|
||||
interface Command {
|
||||
row: SysUser.SysUserInfo;
|
||||
command: cmdEnum;
|
||||
}
|
||||
|
||||
/**配置command的参数 */
|
||||
function command(row: SysUser.SysUserInfo, command: cmdEnum): Command {
|
||||
return {
|
||||
row: row,
|
||||
command: command
|
||||
};
|
||||
}
|
||||
|
||||
const roleSelectorRef = ref<RoleSelectorInstance>(); //角色选择器引用
|
||||
const userId = ref<number | string>(0); //用户id
|
||||
const grantResourceRef = ref<InstanceType<typeof GrantResource> | null>(null); //授权资源组件引用
|
||||
const grantPermissionRef = ref<InstanceType<typeof GrantPermission> | null>(null); //授权权限组件引用
|
||||
/**
|
||||
* 更多下拉菜单点击事件
|
||||
* @param command
|
||||
*/
|
||||
function handleCommand(command: Command) {
|
||||
switch (command.command) {
|
||||
case cmdEnum.ResetPassword:
|
||||
useHandleData(sysUserApi.resetPassword, { id: command.row.id }, cmdEnum.ResetPassword);
|
||||
break;
|
||||
case cmdEnum.GrantRole:
|
||||
userId.value = command.row.id; //获取用户id
|
||||
sysUserApi.ownRole({ id: command.row.id }).then(res => {
|
||||
roleSelectorRef.value?.showSelector(res.data); //显示用户选择器
|
||||
});
|
||||
break;
|
||||
case cmdEnum.GrantResource:
|
||||
// 打开授权资源组件
|
||||
grantResourceRef.value?.onOpen({
|
||||
opt: FormOptEnum.EDIT,
|
||||
record: command.row,
|
||||
successful: RefreshTable
|
||||
});
|
||||
break;
|
||||
case cmdEnum.GrantPermission:
|
||||
// 打开授权权限组件
|
||||
grantPermissionRef.value?.onOpen({
|
||||
opt: FormOptEnum.EDIT,
|
||||
record: command.row,
|
||||
successful: RefreshTable
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** 选择角色 */
|
||||
function handleChooseRole(data: SysRole.SysRoleInfo[]) {
|
||||
//组装参数
|
||||
const grantUser: SysUser.GrantRoleReq = {
|
||||
id: userId.value,
|
||||
roleIdList: data.map(item => item.id) as number[] | string[]
|
||||
};
|
||||
sysUserApi.grantRole(grantUser); //授权角色
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改状态
|
||||
* @param row 当前行数据
|
||||
*/
|
||||
function editStatus(row: SysUser.SysUserInfo) {
|
||||
switchLoading.value = true;
|
||||
const status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE; // 状态取反
|
||||
sysUserApi.updateStatus({ id: row.id }, status).then(() => {
|
||||
switchLoading.value = false;
|
||||
ElMessage.success("修改成功");
|
||||
row.status = status;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
Reference in New Issue
Block a user