初始提交:项目迁移,前端(管理端)

This commit is contained in:
jdc
2025-11-13 09:50:47 +08:00
commit 13f78a3086
695 changed files with 89296 additions and 0 deletions

View File

@@ -0,0 +1,305 @@
---
description: cl-adv-search 组件示例
globs: *.tsx, *.ts, *.vue
---
## 起步 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>base</el-tag>
<span>起步</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['adv-search/base.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="起步" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!--【很重要】高级搜索组件按钮 -->
<cl-adv-btn />
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
<!--【很重要】高级搜索组件 -->
<cl-adv-search ref="AdvSearch" />
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useAdvSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
}
]
});
// cl-adv-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const AdvSearch = useAdvSearch({
// 配置如 cl-form 一样
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input',
props: {
clearable: true
}
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input',
props: {
clearable: true
}
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
]
});
function refresh(params?: any) {
Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```
## 自定义 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>custom</el-tag>
<span>自定义</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['adv-search/custom.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="自定义" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!--【很重要】高级搜索组件按钮 -->
<cl-adv-btn>更多搜索</cl-adv-btn>
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
<!--【很重要】高级搜索组件 -->
<cl-adv-search ref="AdvSearch">
<!-- 自定义按钮 -->
<template #slot-btn>
<el-button @click="toSearch">自定义</el-button>
</template>
</cl-adv-search>
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useAdvSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
}
]
});
// cl-adv-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const AdvSearch = useAdvSearch({
// 配置如 cl-form 一样
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input',
props: {
clearable: true
}
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input',
props: {
clearable: true
}
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
],
title: '更多搜索',
size: '50%',
op: ['close', 'search', 'slot-btn']
});
function refresh(params?: any) {
Crud.value?.refresh(params);
}
// 自定义搜索
function toSearch() {
refresh({ page: 1 });
}
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,464 @@
---
description: module | plugins 模块、插件
globs:
---
# 模块/插件开发
## 目录结构
在 `src/modules` 或 `src/plugins` 下添加一个目录 `demo`
```js
demo
├──pages // 页面路由
├──views // 视图路由
├──hooks // 常用函数
├──components // 常用组件
├──directives // 指令
├──static // 静态文件目录
├──store // 状态管理
├──... // 其他自定义文件
├──config.ts // 配置文件
└──index.ts // 入口文件
```
::: warning
约定的目录名称不可修改,但可自行添加或者删除。
:::
## pages、views
1. 页面参与权限控制,所以不主动注册目录下的路由,通过 `菜单列表` 中配置注册。或者在 `config.ts` 中手动配置:
```js
import { type ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
views: [
{
path: "/demo",
meta: {
label: "测试",
},
component: () => import("./views/demo.vue"),
},
],
pages: [
{
path: "/demo2",
meta: {
label: "测试",
},
component: () => import("./pages/demo.vue"),
},
],
};
};
```
2. 使页面参与路由缓存,配置 `name` 参数
:::warning
`path` 与 `name` 的匹配规则:
- /demo/t1 = demo-t1
- /demo/t1-det = demo-t1-det
:::
方式 1
```html
<script lang="ts" setup>
defineOptions({
name: "demo",
});
</script>
```
方式 2
```html
<script lang="ts">
export default defineComponent({
name: "demo",
});
</script>
```
## components
目录下的组件,全局注册配置方法如下:
```js
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
components: [
import("./components/demo.vue"),
import("./components/demo1.vue"),
],
};
};
```
## directives
`directives` 会以目录下的文件名分别注册指令
```ts
// demo/directives/test.ts
export default {
created(el, binding) {},
mounted() {},
...
};
```
使用
```html
<div v-test></div>
```
## store
使用 `pinia` 的推荐写法:
```ts
import { defineStore } from "pinia";
import { ref } from "vue";
export const useTestStore = defineStore("test", function () {
const count = ref(0);
function add() {
count.value += 1;
}
return {
count,
add,
};
});
```
使用
```ts
import { useTestStore } from "/$/demo/store";
const test = useTestStore();
test.add();
console.log(test.count); // 1
```
::: tip
参考 `base` 模块下 `store` 的导出方式
:::
## config.ts
模块的配置,程序运行时会读取该文件。
- 全局组件、路由的导入
- 事件钩子
输入 `module-config` 关键字,`vscode` 中会自动生成:
```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";
export default (): ModuleConfig => {
return {
// 是否启用
enable: true,
// 插件名称
label: "插件名称",
// 插件描述
description: "插件描述",
// 作者
author: "作者",
version: "1.0.0",
updateTime: "2024-02-02",
logo: "",
// 忽略
ignore: {
// 忽略进度条的请求
NProgress: [
"/base/open/eps",
"/base/comm/person",
"/base/comm/permmenu",
"/base/comm/upload",
"/base/comm/uploadMode",
],
// 忽略 token 的路由
token: ["/login", "/401", "/403", "/404", "/500", "/502"],
},
// 排序
order: 0,
// 配置参数
options: {
name: "神仙",
},
// 示例页面
demo: [
{
name: "基础用法",
component: () => import("..."),
},
],
// 注册全局组件
components: [],
// 视图路由
views: [],
// 页面路由
pages: [],
// 顶部工具栏
toolbar: {
order: 1,
pc: true, // 是否在 pc 端显示
h5: true, // 是否在 h5 端显示
component: import("./components/index.vue"),
},
// 注入全局组件
index: {
component: import("./components/index.vue"),
},
// 安装时触发
install(app: Vue) {},
// 加载时触发
onLoad(events) {},
};
};
```
- order 模块加载顺序,值越大越先
- options 提供给外部使用的参数配置:
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
options: {
// 尺寸
size: 120,
// 显示文案
text: "选择文件",
// 限制
limit: {
// 上传最大数量
upload: 9,
// 文件空间选择数
select: 9,
// 上传大小限制
size: 100,
},
},
};
};
```
获取方式:
```ts
import { module } from "/@/cool";
const config = module.config("模块名");
```
- components 提供全局的组件:
```ts
import type { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
components: [import("./components/test.vue")],
};
};
```
批量导入可以使用 [import.meta.glob](mdc:https:/vitejs.dev/guide/features.html#glob-import) 方法:
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
components: Object.values(import.meta.glob("./components/**/*")),
};
};
```
- views 全局注册的视图路由,存放在 `/` 中的子路由 `children`
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
views: [
{
path: "/test",
meta: {
label: "测试中心",
},
component: () => import("./views/test.vue"),
},
],
};
};
```
- pages 全局注册的页面路由:
```ts
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
pages: [
{
path: "/test",
meta: {
label: "测试中心",
},
component: () => import("./views/test.vue"),
},
],
};
};
```
- install 模块安装时触发。用于预先处理:
```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";
export default (): ModuleConfig => {
return {
install(app: Vue) {
// 注册组件
app.component("test", Test);
// 注册指令
app.directive("focus", {
created(el, bind) {},
});
},
};
};
```
- onLoad 模块安装时触发,预先加载数据,如菜单配置、用户信息:
1. 使用 `await` 等待加载完成后往下执行
2. 可往下模块导出某个方法和变量,如 `hasToken` 验证是否有登陆
```ts
import { ModuleConfig } from "/@/cool";
import { Vue } from "vue";
export default (): ModuleConfig => {
return {
async onLoad() {
const { user, menu } = useStore();
if (user.token) {
// 获取用户信息
user.get();
// 获取菜单权限
await menu.get();
}
return {
async hasToken(cb: () => Promise<any> | void) {
if (user.token) {
if (cb) await cb();
}
},
};
},
};
};
```
其他模块中接收 `hasToken` 方法:
```ts
import { ModuleConfig } from "/@/cool";
import { useDict } from "./index";
export default (): ModuleConfig => {
return {
onLoad({ hasToken }) {
const { dict } = useDict();
hasToken(() => {
dict.refresh();
});
},
};
};
```
## index.ts
该模块需要对外开放的变量及方法,方便于别人直接使用:
```ts
// modules/test/index.ts
import { useStore } from "./store";
export function useTest() {
return {
// 导出 pinia
...useStore(),
// 自定义方法
test() {},
// 自定义变量
data: {
description: "数据描述",
},
};
}
```
导出命名规则 `useBase` `useDemo` `useDict` use + 模块名
使用:
```ts
import { useTest } from "/$/test";
const { data, test } = useTest();
```

View File

@@ -0,0 +1,743 @@
---
description: cl-search 组件示例
globs: *.tsx, *.ts, *.vue
---
## 起步 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>base</el-tag>
<span>起步</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['search/base.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="起步" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!--【很重要】搜索组件 -->
<cl-search ref="Search" />
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
}
]
});
// cl-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Search = useSearch({
// 配置如 cl-form 一样
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input',
props: {
clearable: true,
// 值改变的时候刷新列表
onChange(val: string) {
refresh({
name: val,
page: 1
});
}
}
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input',
props: {
clearable: true
}
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
],
onChange(data, prop) {
console.log(data, prop);
}
});
function refresh(params?: any) {
Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```
## 折叠 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>collapse</el-tag>
<span>折叠</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['search/collapse.vue']" />
<!-- 折叠表格组件 -->
<cl-dialog v-model="visible" title="折叠" width="80%">
<cl-crud ref="Crud">
<!--【collapse】折叠参数【inline】是否行内 -->
<cl-search ref="Search" reset-btn collapse :inline="false" />
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-12-26</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { range } from 'lodash-es';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
}
]
});
// cl-search 配置
const Search = useSearch({
items: [
...range(20).map(i => {
return {
label: '输入框',
prop: `T${i + 1}`,
component: {
name: 'el-input'
}
};
})
]
});
function refresh(params?: any) {
Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
x
```
## 自定义 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>custom</el-tag>
<span>自定义</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['search/custom.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="自定义" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!--【很重要】搜索组件 -->
<cl-search
ref="Search"
:reset-btn="true"
:on-load="onLoad"
:on-search="onSearch"
>
<!-- 自定义按钮 -->
<template #buttons="scope">
<el-button @click="toSearch(scope)">自定义按钮</el-button>
</template>
</cl-search>
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { ElMessage } from 'element-plus';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
}
]
});
// cl-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Search = useSearch({
// 配置如 cl-form 一样
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input',
props: {
clearable: true,
// 值改变的时候刷新列表
onChange(val: string) {
refresh({
name: val,
page: 1
});
}
}
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input',
props: {
clearable: true
}
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
]
});
function refresh(params?: any) {
Crud.value?.refresh(params);
}
// cl-search 初始化
function onLoad(data: any) {
data.name = '白小纯';
}
// cl-search 配置 onSearch 后,必须使用 next 方法继续请求
function onSearch(data: any, { next }: { next: (data: any) => void }) {
ElMessage.info('开始搜索');
// 这边可以处理其他事务
next(data);
}
// 自定义搜索data 为表单数据
function toSearch(data: any) {
ElMessage.info('自定义搜索');
refresh({
page: 1,
...data
});
}
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```
## 布局 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>layout</el-tag>
<span>布局</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['search/layout.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="布局" width="80%">
<cl-crud ref="Crud">
<!--【很重要】搜索组件 -->
<cl-search ref="Search" :reset-btn="true" />
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
}
]
});
// cl-search 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Search = useSearch({
// 取消行内表单模式
inline: false,
// 表单参数
props: {
labelPosition: 'top'
},
// 配置如 cl-form 一样
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input',
props: {
clearable: true,
// 值改变的时候刷新列表
onChange(val: string) {
refresh({
name: val,
page: 1
});
}
}
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input',
props: {
clearable: true
}
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
]
});
function refresh(params?: any) {
Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```
## 使用插件 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>plugin</el-tag>
<span>使用插件</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['search/layout.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="使用插件" width="80%">
<cl-crud ref="Crud">
<cl-row>
<cl-flex1 />
<!--【很重要】搜索组件 -->
<cl-search ref="Search" />
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useSearch, useTable } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { Plugins } from '/#/crud';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
}
]
});
// cl-search 配置
const Search = useSearch({
// 【很重要】自动读取 service 下的 search 数据
plugins: [
Plugins.Search.setAuto({
customComponent(field) {
if (field.propertyName == 'name') {
return {
name: 'cl-select',
props: {
options: [
{
label: '张三',
value: '1'
},
{
label: '李四',
value: '2'
}
]
}
};
}
// null 则不操作,按系统默认操作
return null;
}
})
]
});
function refresh(params?: any) {
Crud.value?.refresh(params);
}
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,716 @@
---
description: cl-upsert 组件示例
globs: *.tsx, *.ts, *.vue
---
## 起步 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>base</el-tag>
<span>起步</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['upsert/base.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="起步" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!-- 打开新增表单的按钮 -->
<cl-add-btn />
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
<!--【很重要】新增、编辑的表单组件 -->
<cl-upsert ref="Upsert" />
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
},
{
type: 'op',
// edit 打开编辑表单
buttons: ['edit', 'delete']
}
]
});
// cl-upsert 配置
//【很重要】该组件基于 cl-form 故很多示例都可复用
const Upsert = useUpsert({
// 配置如 cl-form 一样
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input'
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input'
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
]
});
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```
## 打开、关闭、提交等事件 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>event</el-tag>
<span>打开、关闭、提交等事件</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['upsert/event.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="事件" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!-- 打开新增表单的按钮 -->
<cl-add-btn />
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
<!--【很重要】新增、编辑的表单组件 -->
<cl-upsert ref="Upsert" />
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { useCool } from '/@/cool';
const { service } = useCool();
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
},
{
type: 'op',
// edit 打开编辑表单
buttons: ['edit', 'delete']
}
]
});
// cl-upsert 配置
const Upsert = useUpsert({
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input'
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input'
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
],
// 以下事件按顺序触发
// 弹窗打开的事件,这个时候还未有表单数据
onOpen() {
console.log('onOpen');
},
// 获取详情,编辑的时候会触发
async onInfo(data, { next, done }) {
// 不配置 onInfo 的时候默认执行 next(data),调用 service 的 info 接口获取详情
// next(data);
// 自定义,需要对请求数据进行处理或者返回处理后的数据
const res = await next({
id: data.id
});
done({
...res,
name: `[${res.name}]`
});
},
// 弹窗打开后,已经得到了表单数据
onOpened(data) {
// 判定是否编辑模式
if (Upsert.value?.mode == 'update') {
// 对数据处理
data.phone += '000';
}
},
// 提交事件的钩子
// data 表单提交数据
// next 继续往下执行
// done 关闭加载
// close 关闭弹窗
async onSubmit(data, { next, done, close }) {
// 不配置 onSubmit 的时候默认执行 next(data),提交后会去请求 service 的 update/add 接口
// next(data);
// 自定义如下
// 场景1提交时对参数额外的处理
// next({
// ...data,
// status: 1,
// createTime: dayjs().format("YYYY-MM-DD")
// });
// 场景2提交前、后的操作
// 之前,模拟获取 userId
const userId = await service.base.sys.user.info({ id: 1 });
// 返回值
const res = await next({
userId,
data
});
// 之后
// console.log(res);
},
// 关闭时触发
onClose(action, done) {
// action 关闭的类型
console.log('action', action);
// 使用 done 关闭窗口
done();
},
// 关闭后触发
onClosed() {
console.log('onClosed');
}
});
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```
## Hook的使用 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>hook</el-tag>
<span>Hook的使用</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['upsert/hook/index.vue', 'upsert/hook/reg-pca2.ts']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="Hook的使用" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!-- 打开新增表单的按钮 -->
<cl-add-btn />
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
<!--【很重要】新增、编辑的表单组件 -->
<cl-upsert ref="Upsert" />
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '省市区',
prop: 'pca',
formatter(row) {
return row.province ? row.province + '-' + row.city + '-' + row.district : '-';
},
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
},
{
type: 'op',
buttons: ['edit', 'delete']
}
]
});
// cl-upsert 配置
const Upsert = useUpsert({
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input'
}
},
{
label: '手机号',
prop: 'phone',
component: {
name: 'el-input'
}
},
{
label: '省市区',
prop: 'pca2',
//【很重要】hook 参数配置
hook: {
bind(value, { form }) {
// 将3个参数合并成一个数组带入级联选择器
return [form.province, form.city, form.district];
},
submit(value, { form, prop }) {
// 提交的时候将数组拆分成3个字段提交
const [province, city, district] = value || [];
form.province = province;
form.city = city;
form.district = district;
// 删除 prop 绑定值
form[prop] = undefined;
}
},
// 注册到全局后可直接使用,注册代码看 ./reg-pca2.ts
// hook: "pca2",
component: {
name: 'cl-distpicker'
}
},
{
label: '标签',
prop: 'labels',
//【很重要】使用内置方法,避免一些辣鸡后端要你这么传给他
hook: {
// labels 的数据为 1,2,3
// 绑定的时候将 labels 按 , 分割成数组
bind: ['split', 'number'],
// 提交的时候将 labels 拼接成字符串
submit: ['join']
},
component: {
name: 'el-select',
props: {
multiple: true
},
options: [
{
label: '帅气',
value: 1
},
{
label: '多金',
value: 2
},
{
label: '有才华',
value: 3
}
]
}
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
]
});
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```
## 新增、编辑、详情模式 示例
```vue
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark" disable-transitions>mode</el-tag>
<span>新增、编辑、详情模式</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['upsert/mode.vue']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="不同模式" width="80%">
<cl-crud ref="Crud">
<cl-row>
<!-- 打开新增表单的按钮 -->
<cl-add-btn />
</cl-row>
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
<!--【很重要】新增、编辑的表单组件 -->
<cl-upsert ref="Upsert" />
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { ElMessage } from 'element-plus';
const { dict } = useDict();
// cl-crud 配置
const Crud = useCrud(
{
service: 'test'
},
app => {
app.refresh();
}
);
// cl-table 配置
const Table = useTable({
autoHeight: false,
contextMenu: ['refresh'],
columns: [
{
label: '姓名',
prop: 'name',
minWidth: 140
},
{
label: '手机号',
prop: 'phone',
minWidth: 140
},
{
label: '工作',
prop: 'occupation',
dict: dict.get('occupation'),
minWidth: 140
},
{
label: '创建时间',
prop: 'createTime',
minWidth: 170,
sortable: 'desc'
},
{
type: 'op',
width: 240,
buttons: ['info', 'edit', 'delete']
}
]
});
// cl-upsert 配置
const Upsert = useUpsert({
items: [
{
label: '姓名',
prop: 'name',
component: {
name: 'el-input'
}
},
//【很重要】只有返回方法的时候才能使用 Upsert
() => {
return {
label: '手机号',
prop: 'phone',
// 新增的时候隐藏
// hidden: Upsert.value?.mode == "add",
component: {
name: 'el-input',
props: {
// 编辑的时候禁用
disabled: Upsert.value?.mode == 'update'
}
}
};
},
{
label: '工作',
prop: 'occupation',
component: {
name: 'cl-select',
props: {
tree: true,
checkStrictly: true,
options: dict.get('occupation')
}
}
}
],
onOpen() {
ElMessage.info(`当前模式:` + Upsert.value?.mode);
}
});
const visible = ref(false);
function open() {
visible.value = true;
}
</script>
```