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

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

{ {
"editor.formatOnType": true, "editor.formatOnType": true,
"editor.formatOnSave": true, "editor.formatOnSave": false,
"[vue]": { "[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
...@@ -61,4 +61,5 @@ ...@@ -61,4 +61,5 @@
"v-ripple" "v-ripple"
], ],
"vscodeCustomCodeColor.highlightValueColor": "#b392f0", "vscodeCustomCodeColor.highlightValueColor": "#b392f0",
"Codegeex.RepoIndex": true,
} }
\ No newline at end of file
...@@ -9,22 +9,22 @@ type Result = { ...@@ -9,22 +9,22 @@ type Result = {
}; };
}; };
/** 获取{{pascalCase name}}列表 */ /** 获取{{ pageTitle}}列表 */
export const get{{pascalCase name}}List = (data?: object) => { export const get{{pascalCase name}}List = (data?: object) => {
return http.request<Result>("post", "/get-{{kebabCase name}}-list", { data }); return http.request<Result>("post", "/get-{{kebabCase name}}-list", { data });
}; };
/** 创建{{pascalCase name}} */ /** 创建{{ pageTitle}} */
export const create{{pascalCase name}} = (data?: object) => { export const create{{pascalCase name}} = (data?: object) => {
return http.request<Result>("post", "/create-{{kebabCase name}}", { data }); return http.request<Result>("post", "/create-{{kebabCase name}}", { data });
}; };
/** 更新{{pascalCase name}} */ /** 更新{{ pageTitle}} */
export const update{{pascalCase name}} = (data?: object) => { export const update{{pascalCase name}} = (data?: object) => {
return http.request<Result>("post", "/update-{{kebabCase name}}", { data }); return http.request<Result>("post", "/update-{{kebabCase name}}", { data });
}; };
/** 删除{{pascalCase name}} */ /** 删除{{ pageTitle}} */
export const delete{{pascalCase name}} = (data?: { id: string }) => { export const delete{{pascalCase name}} = (data?: { id: string }) => {
return http.request<Result>("post", "/delete-{{kebabCase name}}", { data }); return http.request<Result>("post", "/delete-{{kebabCase name}}", { data });
}; };
...@@ -4,6 +4,17 @@ module.exports = function (plop) { ...@@ -4,6 +4,17 @@ module.exports = function (plop) {
plop.setGenerator("page", { plop.setGenerator("page", {
description: "生成 Vue 页面、路由和 API", description: "生成 Vue 页面、路由和 API",
prompts: [ prompts: [
// 配置页面选择项,可选择简单页面或复杂页面
{
type: "list",
name: "choicesPageType",
message: "请选择要生成的文件类型:",
choices: [
{ name: "简单页面", value: "simplePage" },
{ name: "复杂页面", value: "complexPage" }
],
default: "Complex"
},
{ {
type: "input", type: "input",
name: "name", name: "name",
...@@ -15,58 +26,112 @@ module.exports = function (plop) { ...@@ -15,58 +26,112 @@ module.exports = function (plop) {
return true; return true;
} }
}, },
{
type: "confirm",
name: "hasRoute",
message: "是否生成路由配置?",
default: true
},
{
type: "confirm",
name: "hasAsyncRoute",
message: "是否生成动态路由配置?",
default: true
},
{ {
type: "input", type: "input",
name: "title", name: "pageTitle",
message: "请输入路由title名称:", message: "请输入页面title:"
when: answers => answers.hasAsyncRoute && answers.hasRoute, // when: answers => answers.hasAsyncRoute && answers.hasRoute,
validate: title => { // validate: title => {
if (!title) { // if (!title) {
return "路由title名称不能为空"; // return "路由title名称不能为空";
} // }
return true; // 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", type: "confirm",
name: "hasApi", name: "hasApi",
message: "是否生成 API 调用?", message: "是否生成 API 调用?",
default: true default: true
} }
// {
// type: "input",
// name: "apiName",
// message: "请输入API名称:",
// when: answers => answers.hasApi,
// validate: apiName => {
// if (!apiName) {
// return "apiTitle不能为空";
// }
// return true;
// }
// }
], ],
actions: data => { actions: data => {
const actions = []; const actions = [];
const { choicesPageType } = data;
// 生成页面 // 生成简单或复杂页面
actions.push({ if (choicesPageType === "simplePage") {
type: "add", actions.push({
path: `../src/views/{{lowerCase name}}/index.vue`, type: "add",
templateFile: "./view.hbs" 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) { if (data.hasRoute) {
...@@ -93,7 +158,7 @@ module.exports = function (plop) { ...@@ -93,7 +158,7 @@ module.exports = function (plop) {
}); });
} }
// 生成 API 调用 // 生成 API 文件
if (data.hasApi) { if (data.hasApi) {
actions.push({ actions.push({
type: "add", type: "add",
...@@ -105,4 +170,8 @@ module.exports = function (plop) { ...@@ -105,4 +170,8 @@ module.exports = function (plop) {
return actions; return actions;
} }
}); });
// plopfile.js
plop.setHelper("raw", function (options) {
return options.fn(this);
});
}; };
# Plop 工具使用详解:Prompts 和 Actions 的完整指南
Plop 是一个强大的代码生成工具,它通过命令行交互自动生成项目文件,特别适合 Vue、React 等项目中的重复性文件创建任务。下面我将详细介绍 Plop 的核心概念 prompts 和 actions 的使用方法。
## 1. Plop 简介与安装
Plop 是一个基于 Node.js 的命令行代码生成工具,它使用 Handlebars 作为模板引擎,可以大幅提升开发效率,特别是在需要创建大量相似文件结构的项目中。
安装 Plop:
```bash
npm i plop -D # 作为开发依赖安装
```
## 2. Prompts 配置详解
Prompts 是 Plop 与用户交互的部分,用于收集生成模板所需的输入信息。
### 基本 prompts 配置
```javascript
module.exports = function (plop) {
plop.setGenerator("component", {
description: "创建一个Vue组件",
prompts: [
{
type: "input", // 输入类型
name: "name", // 变量名,用于模板中引用
message: "请输入组件名称", // 提示信息
default: "MyComponent" // 默认值(可选)
},
{
type: "confirm", // 确认类型
name: "hasStyle",
message: "是否需要样式文件?"
},
{
type: "list", // 列表选择
name: "cssType",
message: "选择CSS预处理器类型",
choices: ["css", "scss", "less", "stylus"],
when: answers => answers.hasStyle // 条件显示
}
],
actions: function (data) {
// actions配置将在下一部分介绍
}
});
};
```
### Prompts 类型
Plop 支持多种 prompt 类型,源自 Inquirer.js:
1. **input** - 文本输入
2. **confirm** - 是/否确认
3. **list** - 单选列表
4. **checkbox** - 多选列表
5. **password** - 密码输入(隐藏输入)
6. **editor** - 打开编辑器输入多行文本
### 高级 prompts 技巧
- **条件 prompts**:使用 `when` 属性可以根据之前的回答决定是否显示当前 prompt
- **验证输入**:使用 `validate` 函数验证用户输入
- **过滤输入**:使用 `filter` 函数处理用户输入后再使用
- **转换输入**:使用 `transformer` 函数在用户输入时实时显示转换结果(不影响实际值)
```javascript
{
type: 'input',
name: 'componentName',
message: '输入组件名称',
validate: value => {
if (!value) return '组件名不能为空';
if (!/^[A-Z]/.test(value)) return '组件名必须以大写字母开头';
return true;
},
filter: value => value.trim(),
transformer: value => value.toUpperCase()
}
```
## 3. Actions 配置详解
Actions 定义了根据 prompts 收集的数据执行的实际操作,主要是文件生成和修改。
### 基本 actions 配置
```javascript
actions: function(data) {
const actions = [
{
type: 'add', // 添加文件
path: `src/components/${data.name}/${data.name}.vue`,
templateFile: 'plop-templates/component.hbs',
data: {
cssType: data.cssType
}
}
];
if (data.hasStyle) {
actions.push({
type: 'add',
path: `src/components/${data.name}/${data.name}.${data.cssType}`,
templateFile: 'plop-templates/component-style.hbs'
});
}
return actions;
}
```
### Action 类型
1. **add** - 添加新文件
- `path`: 文件生成路径
- `templateFile`: 模板文件路径
- `data`: 传递给模板的额外数据
- `skipIfExists`: 如果文件已存在则跳过(默认false)
2. **modify** - 修改现有文件
- `path`: 要修改的文件路径
- `pattern`: 正则表达式,匹配要修改的位置
- `template`: 替换模板(字符串或文件路径)
- `data`: 传递给模板的数据
3. **append** - 在文件末尾追加内容
- `path`: 文件路径
- `template`: 要追加的内容
- `separator`: 分隔符(可选)
4. **custom** - 自定义操作
- 可以执行任何JavaScript代码
### 高级 actions 技巧
- **动态路径**:路径可以使用模板字符串动态生成
- **条件 action**:根据 prompts 回答决定是否添加某些 action
- **多文件生成**:一个 generator 可以生成多个相关文件
- **文件修改**:使用 modify 或 append 修改现有文件(如路由配置)
```javascript
// 修改路由文件的示例
{
type: 'modify',
path: 'src/router/index.js',
pattern: /(\/\/ PLOP_ROUTE_IMPORT)/,
template: "import {{properCase name}} from '@/views/{{name}}';\n$1"
},
{
type: 'modify',
path: 'src/router/index.js',
pattern: /(\/\/ PLOP_ROUTE_DEFINE)/,
template: "{ path: '/{{dashCase name}}', name: '{{properCase name}}', component: {{properCase name}} },\n $1"
}
```
## 4. 模板文件编写
Plop 使用 Handlebars 模板引擎,模板文件通常放在 `plop-templates` 目录中。
### 基本模板示例 (`component.hbs`)
```handlebars
<template>
<div class="{{dashCase name}}-container">
<!-- 组件内容 -->
</div>
</template>
<script>
export default { name: '{{properCase name}}', props: {}, data() { return {} },
methods: {} }
</script>
{{#if cssType}}
<style lang="{{cssType}}" scoped>
.{{dashCase name}}-container {
/* 样式内容 */
}
</style>
{{/if}}
```
### Handlebars 帮助函数
Plop 内置了一些有用的帮助函数:
1. **case 转换函数**
- `camelCase`: myComponentName
- `properCase/PascalCase`: MyComponentName
- `dashCase/kebabCase`: my-component-name
- `snakeCase`: my_component_name
- `lowerCase`: my component name
- `titleCase`: My Component Name
2. **条件语句**
```handlebars
{{#if hasStyle}}
<style>...</style>
{{/if}}
```
3. **循环**:
```handlebars
{{#each imports}}
import
{{this}}
from '...';
{{/each}}
```
### 自定义帮助函数
你可以在 `plopfile.js` 中注册自定义帮助函数:
```javascript
module.exports = function (plop) {
// 添加自定义帮助函数
plop.setHelper("upperCase", text => text.toUpperCase());
// 或者从外部文件加载
plop.load("./plop-helpers.js");
};
```
## 5. 完整示例:Vue 组件生成器
### `plopfile.js` 配置
```javascript
module.exports = function (plop) {
// 设置帮助函数
plop.setHelper("formatDate", () => new Date().toISOString());
// 组件生成器
plop.setGenerator("component", {
description: "创建一个Vue组件",
prompts: [
{
type: "input",
name: "name",
message: "组件名称(大驼峰式,如UserCard)",
validate: value => /^[A-Z]/.test(value) || "组件名必须以大写字母开头"
},
{
type: "confirm",
name: "hasStyle",
message: "是否需要样式文件?",
default: true
},
{
type: "list",
name: "cssType",
message: "选择CSS预处理器",
choices: ["css", "scss", "less"],
default: "scss",
when: answers => answers.hasStyle
},
{
type: "confirm",
name: "hasTest",
message: "是否需要测试文件?",
default: false
}
],
actions: function (data) {
const actions = [
{
type: "add",
path: "src/components/{{properCase name}}/{{properCase name}}.vue",
templateFile: "plop-templates/component.hbs",
data: {
cssType: data.cssType,
createdDate: new Date().toISOString()
}
}
];
if (data.hasStyle) {
actions.push({
type: "add",
path: "src/components/{{properCase name}}/{{properCase name}}.{{cssType}}",
templateFile: "plop-templates/component-style.hbs"
});
}
if (data.hasTest) {
actions.push({
type: "add",
path: "src/components/{{properCase name}}/{{properCase name}}.spec.js",
templateFile: "plop-templates/component-test.hbs"
});
}
// 在全局组件文件中注册
actions.push({
type: "modify",
path: "src/components/index.js",
pattern: /(\/\/ PLOP_COMPONENT_IMPORT)/,
template:
"import {{properCase name}} from './{{properCase name}}/{{properCase name}}.vue';\n$1"
});
actions.push({
type: "modify",
path: "src/components/index.js",
pattern: /(\/\/ PLOP_COMPONENT_EXPORT)/,
template: " {{properCase name}},\n$1"
});
return actions;
}
});
};
```
### 模板文件示例
#### `component.hbs`
```handlebars
<template>
<div class="{{dashCase name}}-container">
<h2>{{properCase name}} Component</h2>
<!-- 组件内容 -->
</div>
</template>
<script>
export default { name: '{{properCase name}}', props: {}, data() { return {} },
created() { console.log('{{properCase name}}
created'); }, methods: {} }
</script>
{{#if cssType}}
<style lang="{{cssType}}" scoped>
.{{dashCase name}}-container {
/* 样式内容 */
}
</style>
{{/if}}
```
#### `component-style.hbs`
```handlebars
/*
{{properCase name}}
component styles */ .{{dashCase name}}-container { margin: 0; padding: 1rem; }
```
#### `component-test.hbs`
```handlebars
import { mount } from '@vue/test-utils'; import
{{properCase name}}
from './{{properCase name}}.vue'; describe('{{properCase name}}', () => {
test('is a Vue instance', () => { const wrapper = mount({{properCase name}});
expect(wrapper.vm).toBeTruthy(); }); });
```
## 6. 运行与使用
1. 在 `package.json` 中添加脚本:
```json
{
"scripts": {
"generate": "plop"
}
}
```
2. 运行命令:
```bash
npm run generate
# 或
yarn generate
```
3. 选择生成器并回答 prompts 问题
4. Plop 会根据配置生成文件
## 7. 最佳实践
1. **模板组织**
- 将模板文件放在 `plop-templates` 目录中
- 按功能或模块组织子目录
- 使用有意义的模板文件名
2. **生成器设计**
- 每个生成器专注于一个特定任务
- 提供清晰的描述和提示信息
- 考虑用户可能的错误输入并添加验证
3. **代码质量**
- 模板中保持一致的代码风格
- 添加必要的注释
- 考虑添加生成文件的头部注释(如创建日期、作者等)
4. **项目集成**
- 将 Plop 集成到项目开发流程中
- 考虑添加生成器来创建测试、文档等配套文件
- 与项目的 lint 和格式化工具兼容
## 8. 实际应用场景
1. **Vue 项目**
- 生成组件、页面、混入(mixin)、指令等
- 自动注册全局组件
- 更新路由配置
2. **React 项目**
- 生成函数组件或类组件
- 创建高阶组件
- 生成 Redux 相关的 action/reducer
3. **Node.js 项目**
- 生成控制器、服务、模型等
- 创建 CLI 命令
- 生成中间件
4. **文档生成**
- 自动生成 API 文档模板
- 创建示例代码文件
- 生成变更日志条目
## 总结
Plop 是一个强大的代码生成工具,通过合理配置 prompts 和 actions,可以大幅提升开发效率,减少重复性工作。关键在于:
1. 设计良好的 prompts 收集必要信息
2. 编写灵活的模板文件
3. 配置适当的 actions 来生成和修改文件
4. 遵循最佳实践保持生成代码的质量
通过 Plop,团队可以保持代码结构的一致性,快速生成符合项目规范的代码文件,让开发者更专注于业务逻辑的实现而非重复的文件创建和配置工作。
<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) => { ...@@ -167,7 +167,9 @@ router.beforeEach((to: ToRouteType, _from, next) => {
if (route && route.meta?.title) { if (route && route.meta?.title) {
if (isAllEmpty(route.parentId) && route.meta?.backstage) { 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", { useMultiTagsStoreHook().handleTags("push", {
path, path,
name, 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 @@ ...@@ -92,7 +92,7 @@
v-model="form.officalAddress" v-model="form.officalAddress"
placeholder="请输入官网地址" placeholder="请输入官网地址"
clearable clearable
class="w-[500px]!" style="width: 500px"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -129,6 +129,7 @@ ...@@ -129,6 +129,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12">111</el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论