提交 f3f001b7 authored 作者: 詹银鑫's avatar 詹银鑫

Merge branch 'feature-20250411' of https://t.clxkj.cn/hejie/EMS into zyxdev

{
"editor.formatOnType": true,
"editor.formatOnSave": true,
"editor.formatOnSave": false,
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
......@@ -61,4 +61,5 @@
"v-ripple"
],
"vscodeCustomCodeColor.highlightValueColor": "#b392f0",
"Codegeex.RepoIndex": true,
}
\ No newline at end of file
......@@ -9,22 +9,22 @@ type Result = {
};
};
/** 获取{{pascalCase name}}列表 */
/** 获取{{ pageTitle}}列表 */
export const get{{pascalCase name}}List = (data?: object) => {
return http.request<Result>("post", "/get-{{kebabCase name}}-list", { data });
};
/** 创建{{pascalCase name}} */
/** 创建{{ pageTitle}} */
export const create{{pascalCase name}} = (data?: object) => {
return http.request<Result>("post", "/create-{{kebabCase name}}", { data });
};
/** 更新{{pascalCase name}} */
/** 更新{{ pageTitle}} */
export const update{{pascalCase name}} = (data?: object) => {
return http.request<Result>("post", "/update-{{kebabCase name}}", { data });
};
/** 删除{{pascalCase name}} */
/** 删除{{ pageTitle}} */
export const delete{{pascalCase name}} = (data?: { id: string }) => {
return http.request<Result>("post", "/delete-{{kebabCase name}}", { data });
};
......@@ -4,6 +4,17 @@ module.exports = function (plop) {
plop.setGenerator("page", {
description: "生成 Vue 页面、路由和 API",
prompts: [
// 配置页面选择项,可选择简单页面或复杂页面
{
type: "list",
name: "choicesPageType",
message: "请选择要生成的文件类型:",
choices: [
{ name: "简单页面", value: "simplePage" },
{ name: "复杂页面", value: "complexPage" }
],
default: "Complex"
},
{
type: "input",
name: "name",
......@@ -15,58 +26,112 @@ module.exports = function (plop) {
return true;
}
},
{
type: "confirm",
name: "hasRoute",
message: "是否生成路由配置?",
default: true
},
{
type: "confirm",
name: "hasAsyncRoute",
message: "是否生成动态路由配置?",
default: true
},
{
type: "input",
name: "title",
message: "请输入路由title名称:",
when: answers => answers.hasAsyncRoute && answers.hasRoute,
validate: title => {
if (!title) {
return "路由title名称不能为空";
}
return true;
}
name: "pageTitle",
message: "请输入页面title:"
// when: answers => answers.hasAsyncRoute && answers.hasRoute,
// validate: title => {
// if (!title) {
// return "路由title名称不能为空";
// }
// return true;
// }
},
// 是否为页面添加components组件
// {
// type: "confirm",
// name: "hasComp",
// message: "是否生成页面的components文件夹",
// default: false
// },
// {
// type: "confirm",
// name: "hasRoute",
// message: "是否生成路由配置?",
// default: true
// },
// {
// type: "confirm",
// name: "hasAsyncRoute",
// message: "是否生成动态路由配置?",
// default: true
// },
// {
// type: "input",
// name: "title",
// message: "请输入路由title名称:",
// when: answers => answers.hasAsyncRoute && answers.hasRoute,
// validate: title => {
// if (!title) {
// return "路由title名称不能为空";
// }
// return true;
// }
// },
{
type: "confirm",
name: "hasApi",
message: "是否生成 API 调用?",
default: true
}
// {
// type: "input",
// name: "apiName",
// message: "请输入API名称:",
// when: answers => answers.hasApi,
// validate: apiName => {
// if (!apiName) {
// return "apiTitle不能为空";
// }
// return true;
// }
// }
],
actions: data => {
const actions = [];
// 生成页面
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}/index.vue`,
templateFile: "./view.hbs"
});
const { choicesPageType } = data;
// 生成简单或复杂页面
if (choicesPageType === "simplePage") {
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}/index.vue`,
templateFile: "./view.hbs",
data: {
title: data.pageTitle
}
});
} else {
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}/index.vue`,
templateFile: "./view/view.hbs",
data: {
title: data.pageTitle
}
});
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}//hooks.ts`,
templateFile: "./view/hooks.hbs"
});
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}/form.vue`,
templateFile: "./view/form.hbs"
});
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}/utils//hook.tsx`,
templateFile: "./view/utils/hook.hbs"
});
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}/utils//rule.ts`,
templateFile: "./view/utils/rule.hbs"
});
actions.push({
type: "add",
path: `../src/views/{{lowerCase name}}/utils//types.ts`,
templateFile: "./view/utils/types.hbs"
});
}
// 生成components文件夹
// if (data.hasComp) {
// actions.push({
// type: "add",
// path: `../src/views/{{lowerCase name}}/components/demo.vue`,
// templateFile: "./view/view2.hbs"
// });
// }
// 生成路由配置
if (data.hasRoute) {
......@@ -93,7 +158,7 @@ module.exports = function (plop) {
});
}
// 生成 API 调用
// 生成 API 文件
if (data.hasApi) {
actions.push({
type: "add",
......@@ -105,4 +170,8 @@ module.exports = function (plop) {
return actions;
}
});
// plopfile.js
plop.setHelper("raw", function (options) {
return options.fn(this);
});
};
差异被折叠。
<script setup lang="ts">
// 从 vue 导入 ref 函数,用于创建响应式引用
import { ref } from "vue";
// 导入自定义组件 ReCol
import ReCol from "@/components/ReCol";
// 从当前目录的 utils/rule 文件导入表单验证规则
import { formRules } from "./utils/rule";
// 从当前目录的 utils/types 文件导入表单属性类型
import { FormProps } from "./utils/types";
// 从当前目录的 hooks 文件导入公共 Hook
import { usePublicHooks } from "./hooks";
/**
* 定义组件的 props,使用 withDefaults 设置默认值
* @property {FormItemProps} formInline - 表单内联数据,包含部门相关信息
*/
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
higherDeptOptions: [], // 上级部门选项列表,默认为空数组
parentId: 0, // 上级部门 ID,默认为 0
name: "", // 部门名称,默认为空字符串
principal: "", // 部门负责人,默认为空字符串
phone: "", // 联系电话,默认为空字符串
email: "", // 邮箱地址,默认为空字符串
sort: 0, // 排序值,默认为 0
status: 1, // 部门状态,默认为 1(启用)
remark: "" // 备注信息,默认为空字符串
})
});
// 创建一个 ref 引用,用于获取表单实例
const ruleFormRef = ref();
// 调用公共 Hook 获取 switch 样式
const { switchStyle } = usePublicHooks();
// 创建一个 ref 引用,用于存储表单数据
const newFormInline = ref(props.formInline);
/**
* 获取表单实例的引用
* @returns {unknown} 表单实例的引用
*/
function getRef() {
return ruleFormRef.value;
}
// 暴露 getRef 方法,供父组件调用
defineExpose({ getRef });
</script>
<template>
<!-- 定义一个 Element Plus 表单,绑定表单引用、表单数据和验证规则 -->
<el-form
ref="ruleFormRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<!-- 定义一个行布局,设置列间距为 30px -->
<el-row :gutter="30">
<!-- 上级部门表单项 -->
<re-col>
<el-form-item label="上级部门">
<!-- 级联选择器,用于选择上级部门 -->
<el-cascader
v-model="newFormInline.parentId"
class="w-full"
:options="newFormInline.higherDeptOptions"
:props="{
value: 'id', // 选项的值字段
label: 'name', // 选项的显示文本字段
emitPath: false, // 不返回选中项的完整路径
checkStrictly: true // 可单独选择任意一级选项
}"
clearable
filterable
placeholder="请选择上级部门"
>
<!-- 自定义选项显示内容 -->
<template #default="{ node, data }">
<span>{{ data.name }}</span>
<!-- 如果不是叶子节点,显示子节点数量 -->
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
</re-col>
<!-- 部门名称表单项,添加了验证规则 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="部门名称" prop="name">
<!-- 输入框,用于输入部门名称 -->
<el-input
v-model="newFormInline.name"
clearable
placeholder="请输入部门名称"
/>
</el-form-item>
</re-col>
<!-- 部门负责人表单项 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="部门负责人">
<!-- 输入框,用于输入部门负责人姓名 -->
<el-input
v-model="newFormInline.principal"
clearable
placeholder="请输入部门负责人"
/>
</el-form-item>
</re-col>
<!-- 手机号表单项,添加了验证规则 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="手机号" prop="phone">
<!-- 输入框,用于输入手机号 -->
<el-input
v-model="newFormInline.phone"
clearable
placeholder="请输入手机号"
/>
</el-form-item>
</re-col>
<!-- 邮箱表单项,添加了验证规则 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="邮箱" prop="email">
<!-- 输入框,用于输入邮箱地址 -->
<el-input
v-model="newFormInline.email"
clearable
placeholder="请输入邮箱"
/>
</el-form-item>
</re-col>
<!-- 排序表单项 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="排序">
<!-- 数字输入框,用于输入排序值 -->
<el-input-number
v-model="newFormInline.sort"
class="w-full!"
:min="0"
:max="9999"
controls-position="right"
/>
</el-form-item>
</re-col>
<!-- 部门状态表单项 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="部门状态">
<!-- 开关组件,用于切换部门状态 -->
<el-switch
v-model="newFormInline.status"
inline-prompt
:active-value="1"
:inactive-value="0"
active-text="启用"
inactive-text="停用"
:style="switchStyle"
/>
</el-form-item>
</re-col>
<!-- 备注表单项 -->
<re-col>
<el-form-item label="备注">
<!-- 文本域输入框,用于输入备注信息 -->
<el-input
v-model="newFormInline.remark"
placeholder="请输入备注信息"
type="textarea"
/>
</el-form-item>
</re-col>
</el-row>
</el-form>
</template>
// 抽离可公用的工具函数等用于系统管理页面逻辑
// 从 vue 库导入 computed 函数,用于创建计算属性
import { computed } from "vue";
// 从 @pureadmin/utils 库导入 useDark 函数,用于获取当前是否为深色模式
import { useDark } from "@pureadmin/utils";
/**
* 用于系统管理页面的公共 Hook
* 该 Hook 主要用于处理深色模式相关的样式计算
* @returns 包含深色模式状态、开关样式和标签样式的对象
*/
export function usePublicHooks() {
// 调用 useDark 函数获取当前是否为深色模式的状态
const { isDark } = useDark();
/**
* 计算 `el-switch` 组件的样式
* 根据深色模式状态设置开关开启和关闭时的颜色
*/
const switchStyle = computed(() => {
return {
"--el-switch-on-color": "#6abe39", // 开关开启时的颜色
"--el-switch-off-color": "#e84749" // 开关关闭时的颜色
};
});
/**
* 计算 `el-tag` 组件的样式
* 根据传入的状态和深色模式状态设置标签的文本颜色、背景颜色和边框颜色
*/
const tagStyle = computed(() => {
return (status: number) => {
return status === 1
? {
// 状态为 1 时的标签样式
"--el-tag-text-color": isDark.value ? "#6abe39" : "#389e0d",
"--el-tag-bg-color": isDark.value ? "#172412" : "#f6ffed",
"--el-tag-border-color": isDark.value ? "#274a17" : "#b7eb8f"
}
: {
// 状态不为 1 时的标签样式
"--el-tag-text-color": isDark.value ? "#e84749" : "#cf1322",
"--el-tag-bg-color": isDark.value ? "#2b1316" : "#fff1f0",
"--el-tag-border-color": isDark.value ? "#58191c" : "#ffa39e"
};
};
});
return {
/** 当前网页是否为`dark`模式 */
isDark,
/** 表现更鲜明的`el-switch`组件 */
switchStyle,
/** 表现更鲜明的`el-tag`组件 */
tagStyle
};
}
import dayjs from "dayjs";
import editForm from "../form.vue";
import { handleTree } from "@/utils/tree";
import { message } from "@/utils/message";
import { getDeptList, addDept, deleteDept, updateDept } from "@/api/systems";
import { usePublicHooks } from "../../{{name}}/hooks";
import { addDialog } from "@/components/ReDialog";
import { reactive, ref, onMounted, h } from "vue";
import type { FormItemProps } from "./types";
import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils";
export function useDept() {
const form = reactive({
name: "",
status: null
});
const formRef = ref();
const dataList = ref([]);
const loading = ref(true);
const { tagStyle } = usePublicHooks();
const columns: TableColumnList = [
{
label: "部门名称",
prop: "name",
width: 180,
align: "left"
},
{
label: "排序",
prop: "sort",
minWidth: 70
},
{
label: "状态",
prop: "status",
minWidth: 100,
align: "center",
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} style={tagStyle.value(row.status)}>
{row.status === 1 ? "启用" : "停用"}
</el-tag>
)
},
{
label: "创建时间",
minWidth: 200,
prop: "createTime",
formatter: ({ createTime }) =>
dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 210,
slot: "operation"
}
];
function handleSelectionChange(val) {
console.log("handleSelectionChange", val);
}
function resetForm(formEl) {
if (!formEl) return;
formEl.resetFields();
onSearch();
}
async function onSearch() {
loading.value = true;
const params = {
pageNum: 1,
pageSize: 200
};
const { data } = await getDeptList(params);
let newData = (data as any).records;
if (!isAllEmpty(form.name)) {
newData = newData.filter((item) => item.name.includes(form.name));
}
if (!isAllEmpty(form.status)) {
newData = newData.filter((item) => item.status === form.status);
}
dataList.value = handleTree(newData);
setTimeout(() => {
loading.value = false;
}, 500);
}
function formatHigherDeptOptions(treeList) {
if (!treeList || !treeList.length) return;
const newTreeList = [];
for (let i = 0; i < treeList.length; i++) {
treeList[i].disabled = treeList[i].status === 0;
formatHigherDeptOptions(treeList[i].children);
newTreeList.push(treeList[i]);
}
return newTreeList;
}
function openDialog(title? = "新增", row?) {
addDialog({
title: `${title}部门`,
props: {
formInline: {
higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList.value)),
parentId: row?.parentId ?? 0,
name: row?.name ?? "",
principal: row?.principal ?? "",
phone: row?.phone ?? "",
email: row?.email ?? "",
sort: row?.sort ?? 0,
status: row?.status ?? 1,
remark: row?.remark ?? ""
}
},
width: "40%",
draggable: true,
fullscreen: deviceDetection(),
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(editForm, { ref: formRef, formInline: null }),
beforeSure: (done, { options }) => {
const FormRef = formRef.value.getRef();
const curData = options.props.formInline as FormItemProps;
function chores() {
message(`您${title}了部门名称为${curData.name}的这条数据`, {
type: "success"
});
done();
onSearch();
}
FormRef.validate(valid => {
if (valid) {
if (title === "新增") {
addDept(curData).then(res => {
if ((res as any).code === "0") {
chores();
} else {
message((res as any).msg, { type: "error" });
}
});
} else {
if (!row?.id) {
message("id不能为空", { type: "error" });
return;
}
curData.id = row.id;
updateDept(curData).then(res => {
if ((res as any).code === "0") {
chores();
} else {
message((res as any).msg, { type: "error" });
}
});
}
}
});
}
});
}
function handleDelete(row) {
console.log("handleDelete", row.id);
deleteDept({ id: row.id }).then(res => {
if ((res as any).code === "0") {
message(`您删除了部门名称为${row.name}的这条数据`, { type: "success" });
onSearch();
}
});
}
onMounted(() => {
onSearch();
});
return {
form,
loading,
columns,
dataList,
onSearch,
resetForm,
openDialog,
handleDelete,
handleSelectionChange
};
}
\ No newline at end of file
// 从 vue 库导入 reactive 函数,用于创建响应式对象
import { reactive } from "vue";
// 从 element-plus 库导入 FormRules 类型,用于定义表单验证规则
import type { FormRules } from "element-plus";
// 从 @pureadmin/utils 库导入 isPhone 和 isEmail 函数,用于验证手机号和邮箱格式
import { isPhone, isEmail } from "@pureadmin/utils";
/**
* 自定义表单规则校验
* 此对象包含了部门表单中各个字段的验证规则,使用 reactive 函数将其转换为响应式对象。
*/
export const formRules = reactive(<FormRules>{
// 部门名称字段的验证规则
name: [
// 验证部门名称是否为必填项,若为空则提示 "部门名称为必填项",在失去焦点时触发验证
{ required: true, message: "部门名称为必填项", trigger: "blur" }
],
// 联系电话字段的验证规则
phone: [
{
/**
* 自定义验证函数,用于验证联系电话格式
* @param rule - 当前验证规则对象
* @param value - 当前字段的值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator: (rule, value, callback) => {
// 若联系电话为空,则直接通过验证
if (value === "") {
callback();
}
// 若联系电话格式不正确,则返回错误信息
else if (!isPhone(value)) {
callback(new Error("请输入正确的手机号码格式"));
}
// 若联系电话格式正确,则通过验证
else {
callback();
}
},
// 在失去焦点时触发验证
trigger: "blur"
// 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
// trigger: "click"
}
],
// 邮箱字段的验证规则
email: [
{
/**
* 自定义验证函数,用于验证邮箱格式
* @param rule - 当前验证规则对象
* @param value - 当前字段的值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator: (rule, value, callback) => {
// 若邮箱为空,则直接通过验证
if (value === "") {
callback();
}
// 若邮箱格式不正确,则返回错误信息
else if (!isEmail(value)) {
callback(new Error("请输入正确的邮箱格式"));
}
// 若邮箱格式正确,则通过验证
else {
callback();
}
},
trigger: "blur"
}
]
});
/**
* 部门表单单项属性接口
* 定义了部门表单中每个字段的类型和属性,用于规范部门相关表单数据的结构。
*/
interface FormItemProps {
/**
* 上级部门选项列表
* 每个选项是一个键值对对象,键为字符串类型,值为任意类型。
*/
higherDeptOptions: Record<string, unknown>[];
/**
* 上级部门 ID
* 用于标识当前部门所属的上级部门,为数字类型。
*/
parentId: number;
/**
* 部门名称
* 用于存储部门的具体名称,为字符串类型。
*/
name: string;
/**
* 部门负责人
* 记录部门负责人的姓名,为字符串类型。
*/
principal: string;
/**
* 联系电话
* 可以是字符串或数字类型,用于存储部门的联系电话。
*/
phone: string | number;
/**
* 邮箱地址
* 用于存储部门的联系邮箱,为字符串类型。
*/
email: string;
/**
* 排序值
* 用于对部门进行排序,为数字类型。
*/
sort: number;
/**
* 部门状态
* 用数字表示部门的状态,如启用、停用等。
*/
status: number;
/**
* 备注信息
* 用于存储关于部门的额外说明信息,为字符串类型。
*/
remark: string;
/**
* 部门 ID
* 可选属性,用于唯一标识一个部门,为数字类型。
*/
id?: number;
}
/**
* 部门表单属性接口
* 包含一个部门表单单项属性对象,用于规范整个部门表单的数据结构。
*/
interface FormProps {
/**
* 表单内联数据
* 包含部门表单各项属性的对象,类型为 FormItemProps。
*/
formInline: FormItemProps;
}
/**
* 导出部门表单单项属性和表单属性类型
* 方便在其他文件中引用这些类型,确保数据类型的一致性。
*/
export type { FormItemProps, FormProps };
<script setup lang="ts">
import { ref } from 'vue';
import { useDept } from './utils/hook';
import { PureTableBar } from '@/components/RePureTableBar';
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
// 导入不同的图标组件
import Delete from "~icons/ep/delete";
import EditPen from "~icons/ep/edit-pen";
import Refresh from "~icons/ep/refresh";
import AddFill from "~icons/ri/add-circle-line";
import { Column } from "element-plus";
defineOptions({ name: '{{name}}' });
const formRef = ref();
const tableRef = ref();
const { form, loading, columns, dataList, onSearch, resetForm, openDialog, handleDelete, handleSelectionChange } = useDept();
function onFullscreen() { tableRef.value.setAdaptive(); }
</script>
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="form"
class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto">
<el-form-item label="{{pageTitle}}名称:" prop="name">
<el-input v-model="form.name" placeholder="请输入{{pageTitle}}名称" clearable class="w-[180px]!" />
</el-form-item>
<el-form-item label="状态:" prop="status">
<el-select v-model="form.status" placeholder="请选择状态" clearable class="w-[180px]!">
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon('ri/search-line')" :loading="loading" @click="onSearch">
搜索框
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
</el-form-item>
</el-form>
<PureTableBar :title="'{{title}}(仅演示,操作后不生效)'" :columns="columns" :tableRef="tableRef?.getTableRef()"
@refresh="onSearch" @fullscreen="onFullscreen">
<template #buttons>
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="openDialog()">
新增{{pageTitle}}
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table ref="tableRef" adaptive :adaptiveConfig="{ offsetBottom: 45 }" align-whole="center" row-key="id"
showOverflowTooltip table-layout="auto" default-expand-all :loading="loading" :size="size" :data="dataList"
:columns="dynamicColumns" :header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}" @selection-change="handleSelectionChange">
<template #operation="{ row }">
<el-button class="reset-margin" link type="primary" :size="size" :icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)">
修改
</el-button>
<el-button class="reset-margin" link type="primary" :size="size" :icon="useRenderIcon(AddFill)"
@click="openDialog('新增', { parentId: row.id } as any)">
新增
</el-button>
<el-popconfirm :title="`是否确认删除{{pageTitle}}名称为${row.name}的这条数据`" @confirm="handleDelete(row)">
<template #reference>
<el-button class="reset-margin" link type="primary" :size="size" :icon="useRenderIcon(Delete)">
删除
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<style lang="scss" scoped>
/* 移除表格内滚动条底部的线 */
:deep(.el-table__inner-wrapper::before) {
height: 0;
}
/* 主内容区域样式 */
:deep(.main-content) {
margin: 24px 24px 0 !important;
}
/* 搜索按钮样式 */
:deep(.search-form) {
.el-form-item {
margin-bottom: 12px;
}
}
</style>
\ No newline at end of file
import { http } from "@/utils/http";
type Result = {
success: boolean;
data?: {
/** 列表数据 */
list?: Array<any>;
[key: string]: any;
};
};
/** 获取设备列表 */
export const getDeviceList = (data?: object) => {
return http.request<Result>("post", "/get-device-list", { data });
};
/** 创建设备 */
export const createDevice = (data?: object) => {
return http.request<Result>("post", "/create-device", { data });
};
/** 更新设备 */
export const updateDevice = (data?: object) => {
return http.request<Result>("post", "/update-device", { data });
};
/** 删除设备 */
export const deleteDevice = (data?: { id: string }) => {
return http.request<Result>("post", "/delete-device", { data });
};
......@@ -167,7 +167,9 @@ router.beforeEach((to: ToRouteType, _from, next) => {
if (route && route.meta?.title) {
if (isAllEmpty(route.parentId) && route.meta?.backstage) {
// 此处为动态顶级路由(目录)
const { path, name, meta } = route.children[0];
const { path, name, meta } = !route.children
? route
: route.children[0];
useMultiTagsStoreHook().handleTags("push", {
path,
name,
......
<script setup lang="ts">
// 从 vue 导入 ref 函数,用于创建响应式引用
import { ref } from "vue";
// 导入自定义组件 ReCol
import ReCol from "@/components/ReCol";
// 从当前目录的 utils/rule 文件导入表单验证规则
import { formRules } from "./utils/rule";
// 从当前目录的 utils/types 文件导入表单属性类型
import { FormProps } from "./utils/types";
// 从当前目录的 hooks 文件导入公共 Hook
import { usePublicHooks } from "./hooks";
/**
* 定义组件的 props,使用 withDefaults 设置默认值
* @property {FormItemProps} formInline - 表单内联数据,包含部门相关信息
*/
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
higherDeptOptions: [], // 上级部门选项列表,默认为空数组
parentId: 0, // 上级部门 ID,默认为 0
name: "", // 部门名称,默认为空字符串
principal: "", // 部门负责人,默认为空字符串
phone: "", // 联系电话,默认为空字符串
email: "", // 邮箱地址,默认为空字符串
sort: 0, // 排序值,默认为 0
status: 1, // 部门状态,默认为 1(启用)
remark: "" // 备注信息,默认为空字符串
})
});
// 创建一个 ref 引用,用于获取表单实例
const ruleFormRef = ref();
// 调用公共 Hook 获取 switch 样式
const { switchStyle } = usePublicHooks();
// 创建一个 ref 引用,用于存储表单数据
const newFormInline = ref(props.formInline);
/**
* 获取表单实例的引用
* @returns {unknown} 表单实例的引用
*/
function getRef() {
return ruleFormRef.value;
}
// 暴露 getRef 方法,供父组件调用
defineExpose({ getRef });
</script>
<template>
<!-- 定义一个 Element Plus 表单,绑定表单引用、表单数据和验证规则 -->
<el-form
ref="ruleFormRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<!-- 定义一个行布局,设置列间距为 30px -->
<el-row :gutter="30">
<!-- 上级部门表单项 -->
<re-col>
<el-form-item label="上级部门">
<!-- 级联选择器,用于选择上级部门 -->
<el-cascader
v-model="newFormInline.parentId"
class="w-full"
:options="newFormInline.higherDeptOptions"
:props="{
value: 'id', // 选项的值字段
label: 'name', // 选项的显示文本字段
emitPath: false, // 不返回选中项的完整路径
checkStrictly: true // 可单独选择任意一级选项
}"
clearable
filterable
placeholder="请选择上级部门"
>
<!-- 自定义选项显示内容 -->
<template #default="{ node }">
<span />
<!-- 如果不是叶子节点,显示子节点数量 -->
<span v-if="!node.isLeaf"> () </span>
</template>
</el-cascader>
</el-form-item>
</re-col>
<!-- 部门名称表单项,添加了验证规则 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="部门名称" prop="name">
<!-- 输入框,用于输入部门名称 -->
<el-input
v-model="newFormInline.name"
clearable
placeholder="请输入部门名称"
/>
</el-form-item>
</re-col>
<!-- 部门负责人表单项 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="部门负责人">
<!-- 输入框,用于输入部门负责人姓名 -->
<el-input
v-model="newFormInline.principal"
clearable
placeholder="请输入部门负责人"
/>
</el-form-item>
</re-col>
<!-- 手机号表单项,添加了验证规则 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="手机号" prop="phone">
<!-- 输入框,用于输入手机号 -->
<el-input
v-model="newFormInline.phone"
clearable
placeholder="请输入手机号"
/>
</el-form-item>
</re-col>
<!-- 邮箱表单项,添加了验证规则 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="邮箱" prop="email">
<!-- 输入框,用于输入邮箱地址 -->
<el-input
v-model="newFormInline.email"
clearable
placeholder="请输入邮箱"
/>
</el-form-item>
</re-col>
<!-- 排序表单项 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="排序">
<!-- 数字输入框,用于输入排序值 -->
<el-input-number
v-model="newFormInline.sort"
class="w-full!"
:min="0"
:max="9999"
controls-position="right"
/>
</el-form-item>
</re-col>
<!-- 部门状态表单项 -->
<re-col :value="12" :xs="24" :sm="24">
<el-form-item label="部门状态">
<!-- 开关组件,用于切换部门状态 -->
<el-switch
v-model="newFormInline.status"
inline-prompt
:active-value="1"
:inactive-value="0"
active-text="启用"
inactive-text="停用"
:style="switchStyle"
/>
</el-form-item>
</re-col>
<!-- 备注表单项 -->
<re-col>
<el-form-item label="备注">
<!-- 文本域输入框,用于输入备注信息 -->
<el-input
v-model="newFormInline.remark"
placeholder="请输入备注信息"
type="textarea"
/>
</el-form-item>
</re-col>
</el-row>
</el-form>
</template>
// 抽离可公用的工具函数等用于系统管理页面逻辑
// 从 vue 库导入 computed 函数,用于创建计算属性
import { computed } from "vue";
// 从 @pureadmin/utils 库导入 useDark 函数,用于获取当前是否为深色模式
import { useDark } from "@pureadmin/utils";
/**
* 用于系统管理页面的公共 Hook
* 该 Hook 主要用于处理深色模式相关的样式计算
* @returns 包含深色模式状态、开关样式和标签样式的对象
*/
export function usePublicHooks() {
// 调用 useDark 函数获取当前是否为深色模式的状态
const { isDark } = useDark();
/**
* 计算 `el-switch` 组件的样式
* 根据深色模式状态设置开关开启和关闭时的颜色
*/
const switchStyle = computed(() => {
return {
"--el-switch-on-color": "#6abe39", // 开关开启时的颜色
"--el-switch-off-color": "#e84749" // 开关关闭时的颜色
};
});
/**
* 计算 `el-tag` 组件的样式
* 根据传入的状态和深色模式状态设置标签的文本颜色、背景颜色和边框颜色
*/
const tagStyle = computed(() => {
return (status: number) => {
return status === 1
? {
// 状态为 1 时的标签样式
"--el-tag-text-color": isDark.value ? "#6abe39" : "#389e0d",
"--el-tag-bg-color": isDark.value ? "#172412" : "#f6ffed",
"--el-tag-border-color": isDark.value ? "#274a17" : "#b7eb8f"
}
: {
// 状态不为 1 时的标签样式
"--el-tag-text-color": isDark.value ? "#e84749" : "#cf1322",
"--el-tag-bg-color": isDark.value ? "#2b1316" : "#fff1f0",
"--el-tag-border-color": isDark.value ? "#58191c" : "#ffa39e"
};
};
});
return {
/** 当前网页是否为`dark`模式 */
isDark,
/** 表现更鲜明的`el-switch`组件 */
switchStyle,
/** 表现更鲜明的`el-tag`组件 */
tagStyle
};
}
<script setup lang="ts">
import { ref } from "vue";
import { useDept } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
// 导入不同的图标组件
import Delete from "~icons/ep/delete";
import EditPen from "~icons/ep/edit-pen";
import Refresh from "~icons/ep/refresh";
import AddFill from "~icons/ri/add-circle-line";
import { Column } from "element-plus";
defineOptions({ name: "device" });
const formRef = ref();
const tableRef = ref();
const {
form,
loading,
columns,
dataList,
onSearch,
resetForm,
openDialog,
handleDelete,
handleSelectionChange
} = useDept();
function onFullscreen() {
tableRef.value.setAdaptive();
}
</script>
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto"
>
<el-form-item label="设备名称:" prop="name">
<el-input
v-model="form.name"
placeholder="请输入设备名称"
clearable
class="w-[180px]!"
/>
</el-form-item>
<el-form-item label="状态:" prop="status">
<el-select
v-model="form.status"
placeholder="请选择状态"
clearable
class="w-[180px]!"
>
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri/search-line')"
:loading="loading"
@click="onSearch"
>
搜索框
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
</el-form-item>
</el-form>
<PureTableBar
:title="'设备(仅演示,操作后不生效)'"
:columns="columns"
:tableRef="tableRef?.getTableRef()"
@refresh="onSearch"
@fullscreen="onFullscreen"
>
<template #buttons>
<el-button
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
>
新增设备
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
ref="tableRef"
adaptive
:adaptiveConfig="{ offsetBottom: 45 }"
align-whole="center"
row-key="id"
showOverflowTooltip
table-layout="auto"
default-expand-all
:loading="loading"
:size="size"
:data="dataList"
:columns="dynamicColumns"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
@selection-change="handleSelectionChange"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
修改
</el-button>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增', { parentId: row.id } as any)"
>
新增
</el-button>
<el-popconfirm
:title="`是否确认删除设备名称为${row.name}的这条数据`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Delete)"
>
删除
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<style lang="scss" scoped>
/* 移除表格内滚动条底部的线 */
:deep(.el-table__inner-wrapper::before) {
height: 0;
}
/* 主内容区域样式 */
:deep(.main-content) {
margin: 24px 24px 0 !important;
}
/* 搜索按钮样式 */
:deep(.search-form) {
.el-form-item {
margin-bottom: 12px;
}
}
</style>
import dayjs from "dayjs";
import editForm from "../form.vue";
import { handleTree } from "@/utils/tree";
import { message } from "@/utils/message";
import { getDeptList, addDept, deleteDept, updateDept } from "@/api/systems";
import { usePublicHooks } from "../../device/hooks";
import { addDialog } from "@/components/ReDialog";
import { reactive, ref, onMounted, h } from "vue";
import type { FormItemProps } from "./types";
import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils";
export function useDept() {
const form = reactive({
name: "",
status: null
});
const formRef = ref();
const dataList = ref([]);
const loading = ref(true);
const { tagStyle } = usePublicHooks();
const columns: TableColumnList = [
{
label: "部门名称",
prop: "name",
width: 180,
align: "left"
},
{
label: "排序",
prop: "sort",
minWidth: 70
},
{
label: "状态",
prop: "status",
minWidth: 100,
align: "center",
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} style={tagStyle.value(row.status)}>
{row.status === 1 ? "启用" : "停用"}
</el-tag>
)
},
{
label: "创建时间",
minWidth: 200,
prop: "createTime",
formatter: ({ createTime }) =>
dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 210,
slot: "operation"
}
];
function handleSelectionChange(val) {
console.log("handleSelectionChange", val);
}
function resetForm(formEl) {
if (!formEl) return;
formEl.resetFields();
onSearch();
}
async function onSearch() {
loading.value = true;
const params = {
pageNum: 1,
pageSize: 200
};
const { data } = await getDeptList(params);
let newData = (data as any).records;
if (!isAllEmpty(form.name)) {
newData = newData.filter(item => item.name.includes(form.name));
}
if (!isAllEmpty(form.status)) {
newData = newData.filter(item => item.status === form.status);
}
dataList.value = handleTree(newData);
setTimeout(() => {
loading.value = false;
}, 500);
}
function formatHigherDeptOptions(treeList) {
if (!treeList || !treeList.length) return;
const newTreeList = [];
for (let i = 0; i < treeList.length; i++) {
treeList[i].disabled = treeList[i].status === 0;
formatHigherDeptOptions(treeList[i].children);
newTreeList.push(treeList[i]);
}
return newTreeList;
}
function openDialog(title? = "新增", row?) {
addDialog({
title: `${title}部门`,
props: {
formInline: {
higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList.value)),
parentId: row?.parentId ?? 0,
name: row?.name ?? "",
principal: row?.principal ?? "",
phone: row?.phone ?? "",
email: row?.email ?? "",
sort: row?.sort ?? 0,
status: row?.status ?? 1,
remark: row?.remark ?? ""
}
},
width: "40%",
draggable: true,
fullscreen: deviceDetection(),
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(editForm, { ref: formRef, formInline: null }),
beforeSure: (done, { options }) => {
const FormRef = formRef.value.getRef();
const curData = options.props.formInline as FormItemProps;
function chores() {
message(`您${title}了部门名称为${curData.name}的这条数据`, {
type: "success"
});
done();
onSearch();
}
FormRef.validate(valid => {
if (valid) {
if (title === "新增") {
addDept(curData).then(res => {
if ((res as any).code === "0") {
chores();
} else {
message((res as any).msg, { type: "error" });
}
});
} else {
if (!row?.id) {
message("id不能为空", { type: "error" });
return;
}
curData.id = row.id;
updateDept(curData).then(res => {
if ((res as any).code === "0") {
chores();
} else {
message((res as any).msg, { type: "error" });
}
});
}
}
});
}
});
}
function handleDelete(row) {
console.log("handleDelete", row.id);
deleteDept({ id: row.id }).then(res => {
if ((res as any).code === "0") {
message(`您删除了部门名称为${row.name}的这条数据`, { type: "success" });
onSearch();
}
});
}
onMounted(() => {
onSearch();
});
return {
form,
loading,
columns,
dataList,
onSearch,
resetForm,
openDialog,
handleDelete,
handleSelectionChange
};
}
// 从 vue 库导入 reactive 函数,用于创建响应式对象
import { reactive } from "vue";
// 从 element-plus 库导入 FormRules 类型,用于定义表单验证规则
import type { FormRules } from "element-plus";
// 从 @pureadmin/utils 库导入 isPhone 和 isEmail 函数,用于验证手机号和邮箱格式
import { isPhone, isEmail } from "@pureadmin/utils";
/**
* 自定义表单规则校验
* 此对象包含了部门表单中各个字段的验证规则,使用 reactive 函数将其转换为响应式对象。
*/
export const formRules = reactive(<FormRules>{
// 部门名称字段的验证规则
name: [
// 验证部门名称是否为必填项,若为空则提示 "部门名称为必填项",在失去焦点时触发验证
{ required: true, message: "部门名称为必填项", trigger: "blur" }
],
// 联系电话字段的验证规则
phone: [
{
/**
* 自定义验证函数,用于验证联系电话格式
* @param rule - 当前验证规则对象
* @param value - 当前字段的值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator: (rule, value, callback) => {
// 若联系电话为空,则直接通过验证
if (value === "") {
callback();
}
// 若联系电话格式不正确,则返回错误信息
else if (!isPhone(value)) {
callback(new Error("请输入正确的手机号码格式"));
}
// 若联系电话格式正确,则通过验证
else {
callback();
}
},
// 在失去焦点时触发验证
trigger: "blur"
// 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
// trigger: "click"
}
],
// 邮箱字段的验证规则
email: [
{
/**
* 自定义验证函数,用于验证邮箱格式
* @param rule - 当前验证规则对象
* @param value - 当前字段的值
* @param callback - 验证回调函数,用于返回验证结果
*/
validator: (rule, value, callback) => {
// 若邮箱为空,则直接通过验证
if (value === "") {
callback();
}
// 若邮箱格式不正确,则返回错误信息
else if (!isEmail(value)) {
callback(new Error("请输入正确的邮箱格式"));
}
// 若邮箱格式正确,则通过验证
else {
callback();
}
},
trigger: "blur"
}
]
});
/**
* 部门表单单项属性接口
* 定义了部门表单中每个字段的类型和属性,用于规范部门相关表单数据的结构。
*/
interface FormItemProps {
/**
* 上级部门选项列表
* 每个选项是一个键值对对象,键为字符串类型,值为任意类型。
*/
higherDeptOptions: Record<string, unknown>[];
/**
* 上级部门 ID
* 用于标识当前部门所属的上级部门,为数字类型。
*/
parentId: number;
/**
* 部门名称
* 用于存储部门的具体名称,为字符串类型。
*/
name: string;
/**
* 部门负责人
* 记录部门负责人的姓名,为字符串类型。
*/
principal: string;
/**
* 联系电话
* 可以是字符串或数字类型,用于存储部门的联系电话。
*/
phone: string | number;
/**
* 邮箱地址
* 用于存储部门的联系邮箱,为字符串类型。
*/
email: string;
/**
* 排序值
* 用于对部门进行排序,为数字类型。
*/
sort: number;
/**
* 部门状态
* 用数字表示部门的状态,如启用、停用等。
*/
status: number;
/**
* 备注信息
* 用于存储关于部门的额外说明信息,为字符串类型。
*/
remark: string;
/**
* 部门 ID
* 可选属性,用于唯一标识一个部门,为数字类型。
*/
id?: number;
}
/**
* 部门表单属性接口
* 包含一个部门表单单项属性对象,用于规范整个部门表单的数据结构。
*/
interface FormProps {
/**
* 表单内联数据
* 包含部门表单各项属性的对象,类型为 FormItemProps。
*/
formInline: FormItemProps;
}
/**
* 导出部门表单单项属性和表单属性类型
* 方便在其他文件中引用这些类型,确保数据类型的一致性。
*/
export type { FormItemProps, FormProps };
......@@ -92,7 +92,7 @@
v-model="form.officalAddress"
placeholder="请输入官网地址"
clearable
class="w-[500px]!"
style="width: 500px"
/>
</el-form-item>
</el-col>
......@@ -129,6 +129,7 @@
/>
</el-form-item>
</el-col>
<el-col :span="12">111</el-col>
</el-row>
<el-row>
<el-col :span="12">
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论