33 changed files with 6017 additions and 1106 deletions
File diff suppressed because it is too large
@ -0,0 +1,156 @@ |
|||
import type { BaseResponse } from '@/utils/request'; |
|||
import { request } from '@/utils/request'; |
|||
|
|||
/** |
|||
* @description 查询类别列表 |
|||
* @param {SearchPageListParams} data |
|||
* @returns |
|||
*/ |
|||
export function fetchDictList() { |
|||
return request<BaseResponse<API.SearchPageListResult>>({ |
|||
url: `/fieldType/list`, |
|||
method: 'post', |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 新增单条 |
|||
* @param {DictType} data |
|||
* @returns |
|||
*/ |
|||
export function createDict(data: API.DictType) { |
|||
return request({ |
|||
url: `/fieldType/create`, |
|||
method: 'post', |
|||
data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 修改单条 |
|||
* @param {DictType} data |
|||
* @returns |
|||
*/ |
|||
export function updateDict(data: API.DictType) { |
|||
return request({ |
|||
url: `/fieldType/update`, |
|||
method: 'put', |
|||
data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 删除多条 |
|||
*/ |
|||
export function deleteBatchDictById(data: API.DeleteBatchDictParams) { |
|||
return request({ |
|||
url: `/fieldType/deleteBatch`, |
|||
method: 'delete', |
|||
data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 根据字典类型查询字典值列表 |
|||
* @param { fieldTypeId:string} data |
|||
* @returns |
|||
*/ |
|||
export function fetchDictValueListByType(data: { fieldTypeName: string }) { |
|||
return request({ |
|||
url: `/dict/query`, |
|||
method: 'post', |
|||
data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 根据字典类型查询字典值分页列表 |
|||
* @param {SearchPageListParams} data |
|||
* @returns |
|||
*/ |
|||
export function fetchDictValuePageList(data: API.SearchPageListParams) { |
|||
return request<BaseResponse<API.SearchPageListResult>>( |
|||
{ |
|||
url: `/dict/page`, |
|||
method: 'post', |
|||
data, |
|||
}, |
|||
{ |
|||
isGetDataDirectly: false, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* @description 新增单条 |
|||
* @param {DictValueType} data |
|||
* @returns |
|||
*/ |
|||
export function createDictValue(data: API.DictValueType) { |
|||
return request({ |
|||
url: `/dict/create`, |
|||
method: 'post', |
|||
data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 启用 |
|||
* @param {id: string } data |
|||
* @returns |
|||
*/ |
|||
export function enableDictValue(data: { id: string }) { |
|||
return request({ |
|||
url: `/dict/enable`, |
|||
method: 'post', |
|||
params: data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 禁用 |
|||
* @param {id: string } data |
|||
* @returns |
|||
*/ |
|||
export function disableDictValue(data: { id: string }) { |
|||
return request({ |
|||
url: `/dict/disable`, |
|||
method: 'post', |
|||
params: data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 修改单条 |
|||
* @param {DictValueType} data |
|||
* @returns |
|||
*/ |
|||
export function updateDictValue(data: API.DictValueType) { |
|||
return request({ |
|||
url: `/dict/update`, |
|||
method: 'put', |
|||
data, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 删除单条 |
|||
*/ |
|||
export function deleteDictValueById(params: API.DeleteDictValueParams) { |
|||
return request({ |
|||
url: `/dict/delete`, |
|||
method: 'delete', |
|||
params: params, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* @description 删除多条 |
|||
*/ |
|||
export function deleteBatchDictValueById(data: API.DeleteBatchDictValueParams) { |
|||
return request({ |
|||
url: `/dict/deleteBatch`, |
|||
method: 'delete', |
|||
data, |
|||
}); |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
declare namespace API { |
|||
type DictType = { |
|||
id?: string; |
|||
name: string; |
|||
}; |
|||
|
|||
type DeleteDictParams = { |
|||
id: string; |
|||
}; |
|||
|
|||
type DeleteBatchDictParams = string[]; |
|||
|
|||
type DictValueType = { |
|||
id?: string; |
|||
fieldTypeId: string; |
|||
fieldValue: string; |
|||
enable: string; |
|||
pendingStatus?: boolean; |
|||
}; |
|||
|
|||
type DeleteDictValueParams = { |
|||
id: string; |
|||
}; |
|||
|
|||
type DeleteBatchDictValueParams = string[]; |
|||
} |
|||
@ -0,0 +1,769 @@ |
|||
<!-- |
|||
* @Author: AaronWu 2463371514@qq.com |
|||
* @Date: 2025-04-01 10:28:36 |
|||
* @LastEditors: AaronWu 2463371514@qq.com |
|||
* @LastEditTime: 2025-04-01 16:51:20 |
|||
* @FilePath: /IssueSupportManage/src/assets/DTO.md |
|||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE |
|||
--> |
|||
|
|||
# 接口文档 |
|||
|
|||
<details open> |
|||
<summary><h2>用户认证模块</h2></summary> |
|||
|
|||
<details open> |
|||
<summary><h3>1. 用户登录</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:用户登录接口,用于验证用户身份并获取访问令牌 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/login |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### LoginParams |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| -------- | ------ | ---- | ------ | |
|||
| username | string | 是 | 用户名 | |
|||
| password | string | 是 | 密码 | |
|||
|
|||
#### 响应结果 |
|||
|
|||
##### LoginResult |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------------ | ------ | -------- | |
|||
| accessToken | string | 访问令牌 | |
|||
| expiresTime | string | 过期时间 | |
|||
| refreshToken | string | 刷新令牌 | |
|||
| userId | string | 用户 ID | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "登录成功", |
|||
"data": { |
|||
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", |
|||
"expiresTime": "2024-04-01 11:30:00", |
|||
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", |
|||
"userId": "12345" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 2. 用户登出 |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:用户登出接口,用于注销当前用户的登录状态 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/logout |
|||
|
|||
#### 请求参数 |
|||
|
|||
无 |
|||
|
|||
#### 响应结果 |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------- | ------ | -------- | |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "登出成功" |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h2>用户管理模块</h2></summary> |
|||
|
|||
<details open> |
|||
<summary><h3>1. 用户列表查询</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:查询用户列表数据,支持条件搜索 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/user/list |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### SearchListParams |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ------- | ------ | ---- | ---------------------------------------- | |
|||
| keyword | string | 否 | 搜索关键词(支持用户名、手机号模糊搜索) | |
|||
| state | number | 否 | 用户状态(0:禁用,1:启用) | |
|||
| sex | string | 否 | 性别 | |
|||
|
|||
#### 响应结果 |
|||
|
|||
##### SearchListResult |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------ | --------------- | ------------ | |
|||
| list | Array<UserInfo> | 用户列表数据 | |
|||
|
|||
##### UserInfo |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ---------- | ------- | ------------------------ | |
|||
| id | string | 用户 ID | |
|||
| account | string | 账号 | |
|||
| username | string | 用户名 | |
|||
| mobile | string | 手机号 | |
|||
| sex | string | 性别 | |
|||
| state | number | 状态(0:禁用,1:启用) | |
|||
| remark | string | 备注 | |
|||
| createTime | string | 创建时间 | |
|||
| isAdmin | boolean | 是否管理员 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "操作成功", |
|||
"data": { |
|||
"list": [ |
|||
{ |
|||
"id": "1", |
|||
"username": "zhangsan", |
|||
"mobile": "13800138000", |
|||
"sex": "男", |
|||
"state": 1, |
|||
"remark": "技术部员工", |
|||
"createTime": "2024-04-01 10:00:00", |
|||
"isAdmin": false |
|||
} |
|||
] |
|||
} |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>2. 用户分页列表</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:分页查询用户列表数据 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/user/page |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### SearchPageListParams |
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
|-------|------|------|------| |
|||
| name | string | 否 | 搜索关键词 | |
|||
| keyword | string | 否 | 搜索关键词(支持用户名、手机号模糊搜索) | |
|||
| state | number | 否 | 用户状态(0:禁用,1:启用) | |
|||
| sex | string | 否 | 性别 | |
|||
| current | number | 是 | 当前页码 | |
|||
| size | number | 是 | 每页条数 | |
|||
| sort | string | 否 | 排序字段 | |
|||
| order | string | 否 | 排序方式(asc:升序,desc:降序) | |
|||
|
|||
##### SearchListParams |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| --------- | ------ | ---- | ---------------------------------------- | |
|||
| name | string | 否 | 搜索关键词 | |
|||
| keyword | string | 否 | 搜索关键词(支持用户名、手机号模糊搜索) | |
|||
| state | number | 否 | 用户状态(0:禁用,1:启用) | |
|||
| sex | string | 否 | 性别 | |
|||
| pageSize | number | 是 | 每页条数 | |
|||
| pageNum | number | 是 | 当前页码 | |
|||
| sortField | string | 否 | 排序字段(默认:createTime) | |
|||
| sortOrder | string | 否 | 排序方式(asc:升序,desc:降序) | |
|||
|
|||
#### 响应结果 |
|||
|
|||
##### SearchPageListResult |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| -------- | --------------- | ------------ | |
|||
| list | Array<UserInfo> | 用户列表数据 | |
|||
| total | number | 总记录数 | |
|||
| pageSize | number | 每页条数 | |
|||
| pageNum | number | 当前页码 | |
|||
|
|||
##### UserInfo |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ---------- | ------- | ------------------------ | |
|||
| id | string | 用户 ID | |
|||
| account | string | 账号 | |
|||
| username | string | 用户名 | |
|||
| mobile | string | 手机号 | |
|||
| sex | string | 性别 | |
|||
| state | number | 状态(0:禁用,1:启用) | |
|||
| remark | string | 备注 | |
|||
| createTime | string | 创建时间 | |
|||
| isAdmin | boolean | 是否管理员 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "操作成功", |
|||
"data": { |
|||
"list": [ |
|||
{ |
|||
"id": "1", |
|||
"username": "zhangsan", |
|||
"mobile": "13800138000", |
|||
"sex": "男", |
|||
"state": 1, |
|||
"remark": "技术部员工", |
|||
"createTime": "2024-04-01 10:00:00", |
|||
"isAdmin": false |
|||
} |
|||
], |
|||
"total": 100, |
|||
"pageSize": 10, |
|||
"pageNum": 1 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>3. 新增用户</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:新增用户信息 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/user/create |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### UserInfoType |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| -------- | ------- | ---- | ------------------------ | |
|||
| account | string |是 | 账号 | |
|||
| username | string | 是 | 用户名 | |
|||
| mobile | string | 是 | 手机号 | |
|||
| sex | string | 是 | 性别 | |
|||
| state | number | 是 | 状态(0:禁用,1:启用) | |
|||
| remark | string | 是 | 备注 | |
|||
| isAdmin | boolean | 否 | 是否管理员 | |
|||
|
|||
#### 响应结果 |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------- | ------ | -------- | |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "创建成功" |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>4. 修改用户</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:修改用户信息 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/user/update |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### UserInfoType |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| -------- | ------- | ---- | ------------------------ | |
|||
| id | string | 是 | 用户 ID | |
|||
| account | string | 是 | 账号 | |
|||
| username | string | 是 | 用户名 | |
|||
| mobile | string | 是 | 手机号 | |
|||
| sex | string | 是 | 性别 | |
|||
| state | number | 是 | 状态(0:禁用,1:启用) | |
|||
| remark | string | 是 | 备注 | |
|||
| isAdmin | boolean | 否 | 是否管理员 | |
|||
|
|||
#### 响应结果 |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------- | ------ | -------- | |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "修改成功" |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>5. 查询用户详情</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:根据 ID 查询用户详细信息 |
|||
- 请求方式:GET |
|||
- 接口路径:/api/user/getById |
|||
|
|||
#### 请求参数 |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ------ | ------ | ---- | ------- | |
|||
| id | string | 是 | 用户 ID | |
|||
|
|||
#### 响应结果 |
|||
|
|||
##### UserInfo |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ---------- | ------- | ------------------------ | |
|||
| id | string | 用户 ID | |
|||
| account | string | 账号 | |
|||
| username | string | 用户名 | |
|||
| mobile | string | 手机号 | |
|||
| sex | string | 性别 | |
|||
| state | number | 状态(0:禁用,1:启用) | |
|||
| remark | string | 备注 | |
|||
| createTime | string | 创建时间 | |
|||
| isAdmin | boolean | 是否管理员 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "操作成功", |
|||
"data": { |
|||
"id": "1", |
|||
"username": "zhangsan", |
|||
"mobile": "13800138000", |
|||
"sex": "男", |
|||
"state": 1, |
|||
"remark": "技术部员工", |
|||
"createTime": "2024-04-01 10:00:00", |
|||
"isAdmin": false |
|||
} |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h2>字典管理模块</h2></summary> |
|||
|
|||
<details open> |
|||
<summary><h3>1. 字典类别列表查询</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:查询所有字典类别列表 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/fieldType/list |
|||
|
|||
#### 请求参数 |
|||
|
|||
无 |
|||
|
|||
#### 响应结果 |
|||
|
|||
##### SearchPageListResult |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------ | --------------- | ------------ | |
|||
| list | Array<DictType> | 字典类别列表 | |
|||
|
|||
##### DictType |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ------ | ------ | ---- | -------- | |
|||
| id | string | 否 | 类别 ID | |
|||
| name | string | 是 | 类别名称 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "操作成功", |
|||
"data": { |
|||
"list": [ |
|||
{ |
|||
"id": "1", |
|||
"name": "性别" |
|||
} |
|||
] |
|||
} |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>2. 新增字典类别</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:新增字典类别信息 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/fieldType/create |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### DictType |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ------ | ------ | ---- | -------- | |
|||
| id | string | 否 | 类别 ID | |
|||
| name | string | 是 | 类别名称 | |
|||
|
|||
#### 响应结果 |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------- | ------ | -------- | |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "创建成功" |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>3. 修改字典类别</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:修改字典类别信息 |
|||
- 请求方式:PUT |
|||
- 接口路径:/api/fieldType/update |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### DictType |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ------ | ------ | ---- | -------- | |
|||
| id | string | 是 | 类别 ID | |
|||
| name | string | 是 | 类别名称 | |
|||
|
|||
#### 响应结果 |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------- | ------ | -------- | |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "修改成功" |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>4. 批量删除字典类别</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:批量删除字典类别信息 |
|||
- 请求方式:DELETE |
|||
- 接口路径:/api/fieldType/deleteBatch |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### DeleteBatchDictParams |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ------ | -------- | ---- | ---------------- | |
|||
| - | string[] | 是 | 字典类别 ID 数组 | |
|||
|
|||
#### 请求示例 |
|||
|
|||
```json |
|||
["1", "2", "3"] |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>5. 根据字典类型查询字典值列表</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:根据字典类型名称查询对应的字典值列表 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/dict/query |
|||
|
|||
#### 请求参数 |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ------------- | ------ | ---- | ------------ | |
|||
| fieldTypeName | string | 是 | 字典类型名称 | |
|||
|
|||
#### 响应结果 |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------ | -------------------- | ---------- | |
|||
| list | Array<DictValueType> | 字典值列表 | |
|||
|
|||
##### DictValueType |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------------- | ------- | ----------- | |
|||
| id | string | 字典值 ID | |
|||
| fieldTypeId | string | 字典类型 ID | |
|||
| fieldValue | string | 字典值 | |
|||
| enable | string | 启用状态 | |
|||
| pendingStatus | boolean | 待处理状态 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "操作成功", |
|||
"data": { |
|||
"list": [ |
|||
{ |
|||
"id": "1", |
|||
"fieldTypeId": "type_1", |
|||
"fieldValue": "男", |
|||
"enable": "1", |
|||
"pendingStatus": false |
|||
}, |
|||
{ |
|||
"id": "2", |
|||
"fieldTypeId": "type_1", |
|||
"fieldValue": "女", |
|||
"enable": "1", |
|||
"pendingStatus": false |
|||
} |
|||
] |
|||
} |
|||
} |
|||
``` |
|||
|
|||
<details open> |
|||
<summary><h3>6. 字典值分页列表查询</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
|
|||
- 接口描述:分页查询字典值列表数据 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/dict/page |
|||
|
|||
#### 请求参数 |
|||
|
|||
##### SearchPageListParams |
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
|-------|------|------|------| |
|||
| fieldValue | string | 否 | 字典值 | |
|||
| current | number | 是 | 当前页码 | |
|||
| size | number | 是 | 每页条数 | |
|||
| sort | string | 否 | 排序字段 | |
|||
| order | string | 否 | 排序方式(asc:升序,desc:降序) | |
|||
|
|||
##### SearchListParams |
|||
|
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
| ---------- | ------ | ---- | --------------------------------- | |
|||
| fieldValue | string | 否 | 字典值 | |
|||
| sortField | string | 否 | 排序字段(默认:createTime) | |
|||
| sortOrder | string | 否 | 排序方式(asc:升序,desc:降序) | |
|||
|
|||
#### 响应结果 |
|||
|
|||
##### SearchPageListResult |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| -------- | -------------------- | ---------- | |
|||
| list | Array<DictValueType> | 字典值列表 | |
|||
| total | number | 总记录数 | |
|||
| pageSize | number | 每页条数 | |
|||
| pageNum | number | 当前页码 | |
|||
|
|||
##### DictValueType |
|||
|
|||
| 参数名 | 类型 | 说明 | |
|||
| ------------- | ------- | ----------- | |
|||
| id | string | 字典值 ID | |
|||
| fieldTypeId | string | 字典类型 ID | |
|||
| fieldValue | string | 字典值 | |
|||
| enable | string | 启用状态 | |
|||
| pendingStatus | boolean | 待处理状态 | |
|||
|
|||
#### 响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "操作成功", |
|||
"data": { |
|||
"list": [ |
|||
{ |
|||
"id": "1", |
|||
"fieldTypeId": "type_1", |
|||
"fieldValue": "男", |
|||
"enable": "1", |
|||
"pendingStatus": false |
|||
} |
|||
], |
|||
"total": 10, |
|||
"pageSize": 10, |
|||
"pageNum": 1 |
|||
} |
|||
} |
|||
``` |
|||
<details open> |
|||
<summary><h3>7. 新增字典值</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
- 接口描述:新增字典值信息 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/dict/create |
|||
|
|||
#### 请求参数 |
|||
##### DictValueType |
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
|-------|------|------|------| |
|||
| id | string | 否 | 字典值ID | |
|||
| fieldTypeId | string | 是 | 字典类型ID | |
|||
| fieldValue | string | 是 | 字典值 | |
|||
| enable | string | 是 | 启用状态 | |
|||
| pendingStatus | boolean | 否 | 待处理状态 | |
|||
|
|||
#### 响应结果 |
|||
| 参数名 | 类型 | 说明 | |
|||
|-------|------|------| |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "创建成功" |
|||
} |
|||
``` |
|||
<details open> |
|||
<summary><h3>8. 修改字典值</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
- 接口描述:修改字典值信息 |
|||
- 请求方式:PUT |
|||
- 接口路径:/api/dict/update |
|||
|
|||
#### 请求参数 |
|||
##### DictValueType |
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
|-------|------|------|------| |
|||
| id | string | 是 | 字典值ID | |
|||
| fieldTypeId | string | 是 | 字典类型ID | |
|||
| fieldValue | string | 是 | 字典值 | |
|||
| enable | string | 是 | 启用状态 | |
|||
| pendingStatus | boolean | 否 | 待处理状态 | |
|||
|
|||
#### 响应结果 |
|||
| 参数名 | 类型 | 说明 | |
|||
|-------|------|------| |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "修改成功" |
|||
} |
|||
``` |
|||
<details open> |
|||
<summary><h3>9. 启用字典值</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
- 接口描述:启用指定的字典值 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/dict/enable |
|||
|
|||
#### 请求参数 |
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
|-------|------|------|------| |
|||
| id | string | 是 | 字典值ID | |
|||
|
|||
#### 响应结果 |
|||
| 参数名 | 类型 | 说明 | |
|||
|-------|------|------| |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "启用成功" |
|||
} |
|||
``` |
|||
<details open> |
|||
<summary><h3>10. 禁用字典值</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
- 接口描述:禁用指定的字典值 |
|||
- 请求方式:POST |
|||
- 接口路径:/api/dict/disable |
|||
|
|||
#### 请求参数 |
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
|-------|------|------|------| |
|||
| id | string | 是 | 字典值ID | |
|||
|
|||
#### 响应结果 |
|||
| 参数名 | 类型 | 说明 | |
|||
|-------|------|------| |
|||
| code | number | 状态码 | |
|||
| message | string | 响应消息 | |
|||
|
|||
#### 响应示例 |
|||
```json |
|||
{ |
|||
"code": 200, |
|||
"message": "禁用成功" |
|||
} |
|||
``` |
|||
<details open> |
|||
<summary><h3>11. 批量删除字典值</h3></summary> |
|||
|
|||
#### 接口说明 |
|||
- 接口描述:批量删除字典值信息 |
|||
- 请求方式:DELETE |
|||
- 接口路径:/api/dict/deleteBatch |
|||
|
|||
#### 请求参数 |
|||
##### DeleteBatchDictValueParams |
|||
| 参数名 | 类型 | 必填 | 说明 | |
|||
|-------|------|------|------| |
|||
| - | string[] | 是 | 字典值ID数组 | |
|||
|
|||
#### 请求示例 |
|||
```json |
|||
["1", "2", "3"] |
|||
``` |
|||
@ -0,0 +1,7 @@ |
|||
import { withInstall } from '@/utils' |
|||
|
|||
import basicDrawer from './src/BasicDrawer.vue'; |
|||
|
|||
export const BasicDrawer = withInstall(basicDrawer); |
|||
export * from './src/typing'; |
|||
export { useDrawer, useDrawerInner } from './src/useDrawer'; |
|||
@ -0,0 +1,258 @@ |
|||
<template> |
|||
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues"> |
|||
<template #title v-if="!$slots.title"> |
|||
<DrawerHeader |
|||
:title="getMergeProps.title" |
|||
:isDetail="isDetail" |
|||
:showDetailBack="showDetailBack" |
|||
@close="onClose" |
|||
> |
|||
<template #titleToolbar> |
|||
<slot name="titleToolbar"></slot> |
|||
</template> |
|||
</DrawerHeader> |
|||
</template> |
|||
<template v-else #title> |
|||
<slot name="title"></slot> |
|||
</template> |
|||
|
|||
<ScrollContainer |
|||
:style="getScrollContentStyle" |
|||
:loading-tip="loadingText || t('common.loadingText')" |
|||
> |
|||
<!-- v-loading="getLoading" --> |
|||
|
|||
<slot></slot> |
|||
</ScrollContainer> |
|||
<DrawerFooter v-bind="getProps" @close="onClose" @ok="handleOk" :height="getFooterHeight"> |
|||
<template #[item]="data" v-for="item in Object.keys($slots)"> |
|||
<slot :name="item" v-bind="data || {}"></slot> |
|||
</template> |
|||
</DrawerFooter> |
|||
</Drawer> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { DrawerInstance, DrawerProps } from './typing'; |
|||
import type { CSSProperties } from 'vue'; |
|||
import { |
|||
defineComponent, |
|||
ref, |
|||
computed, |
|||
watch, |
|||
unref, |
|||
nextTick, |
|||
toRaw, |
|||
getCurrentInstance, |
|||
} from 'vue'; |
|||
import { Drawer } from 'ant-design-vue'; |
|||
import { useI18n } from '@/hooks/useI18n'; |
|||
import { isFunction, isNumber } from '@/utils/is'; |
|||
import { deepMerge } from '@/utils'; |
|||
import DrawerFooter from './components/DrawerFooter.vue'; |
|||
import DrawerHeader from './components/DrawerHeader.vue'; |
|||
import { ScrollContainer } from '@/components/core/Container'; |
|||
import { basicProps } from './props'; |
|||
// import { useDesign } from '/@/hooks/web/useDesign'; |
|||
import { useAttrs } from '@/hooks/core/useAttrs'; |
|||
|
|||
export default defineComponent({ |
|||
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader }, |
|||
inheritAttrs: false, |
|||
props: basicProps, |
|||
emits: ['visible-change', 'ok', 'close', 'register'], |
|||
setup(props, { emit }) { |
|||
const visibleRef = ref(false); |
|||
const attrs = useAttrs(); |
|||
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null); |
|||
|
|||
const { t } = useI18n(); |
|||
const prefixVar= 'basic-drawer', |
|||
prefixCls ='basic-drawer'; |
|||
|
|||
const drawerInstance: DrawerInstance = { |
|||
setDrawerProps: setDrawerProps, |
|||
emitVisible: undefined, |
|||
}; |
|||
|
|||
const instance = getCurrentInstance(); |
|||
|
|||
instance && emit('register', drawerInstance, instance.uid); |
|||
|
|||
const getMergeProps = computed((): DrawerProps => { |
|||
return deepMerge(toRaw(props), unref(propsRef)); |
|||
}); |
|||
|
|||
const getProps = computed((): DrawerProps => { |
|||
const opt = { |
|||
placement: 'right', |
|||
...unref(attrs), |
|||
...unref(getMergeProps), |
|||
visible: unref(visibleRef), |
|||
}; |
|||
opt.title = undefined; |
|||
const { isDetail, width, wrapClassName, getContainer } = opt; |
|||
if (isDetail) { |
|||
if (!width) { |
|||
opt.width = '100%'; |
|||
} |
|||
const detailCls = `${prefixCls}__detail`; |
|||
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls; |
|||
|
|||
if (!getContainer) { |
|||
// TODO type error? |
|||
opt.getContainer = `.${prefixVar}-layout-content` as any; |
|||
} |
|||
} |
|||
return opt as DrawerProps; |
|||
}); |
|||
|
|||
const getBindValues = computed((): DrawerProps => { |
|||
return { |
|||
...attrs, |
|||
...unref(getProps), |
|||
}; |
|||
}); |
|||
|
|||
// Custom implementation of the bottom button, |
|||
const getFooterHeight = computed(() => { |
|||
const { footerHeight, showFooter } = unref(getProps); |
|||
if (showFooter && footerHeight) { |
|||
return isNumber(footerHeight) |
|||
? `${footerHeight}px` |
|||
: `${footerHeight.replace('px', '')}px`; |
|||
} |
|||
return `0px`; |
|||
}); |
|||
|
|||
const getScrollContentStyle = computed((): CSSProperties => { |
|||
const footerHeight = unref(getFooterHeight); |
|||
return { |
|||
position: 'relative', |
|||
height: `calc(100% - ${footerHeight})`, |
|||
}; |
|||
}); |
|||
|
|||
const getLoading = computed(() => { |
|||
return !!unref(getProps)?.loading; |
|||
}); |
|||
|
|||
watch( |
|||
() => props.visible, |
|||
(newVal, oldVal) => { |
|||
if (newVal !== oldVal) visibleRef.value = newVal; |
|||
}, |
|||
{ deep: true }, |
|||
); |
|||
|
|||
watch( |
|||
() => visibleRef.value, |
|||
(visible) => { |
|||
nextTick(() => { |
|||
emit('visible-change', visible); |
|||
instance && drawerInstance.emitVisible?.(visible, instance.uid); |
|||
}); |
|||
}, |
|||
); |
|||
|
|||
// Cancel event |
|||
async function onClose(e: Recordable) { |
|||
const { closeFunc } = unref(getProps); |
|||
emit('close', e); |
|||
if (closeFunc && isFunction(closeFunc)) { |
|||
const res = await closeFunc(); |
|||
visibleRef.value = !res; |
|||
return; |
|||
} |
|||
visibleRef.value = false; |
|||
} |
|||
|
|||
function setDrawerProps(props: Partial<DrawerProps>): void { |
|||
// Keep the last setDrawerProps |
|||
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props); |
|||
|
|||
if (Reflect.has(props, 'visible')) { |
|||
visibleRef.value = !!props.visible; |
|||
} |
|||
} |
|||
|
|||
function handleOk() { |
|||
emit('ok'); |
|||
} |
|||
|
|||
return { |
|||
onClose, |
|||
t, |
|||
prefixCls, |
|||
getMergeProps: getMergeProps as any, |
|||
getScrollContentStyle, |
|||
getProps: getProps as any, |
|||
getLoading, |
|||
getBindValues, |
|||
getFooterHeight, |
|||
handleOk, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
<style lang="less"> |
|||
@header-height: 60px; |
|||
@detail-header-height: 40px; |
|||
@prefix-cls: ~'basic-drawer'; |
|||
@prefix-cls-detail: ~'basic-drawer__detail'; |
|||
|
|||
.@{prefix-cls} { |
|||
.ant-drawer-wrapper-body { |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.ant-drawer-close { |
|||
&:hover { |
|||
color: @error-color; |
|||
} |
|||
} |
|||
|
|||
.ant-drawer-body { |
|||
height: calc(100% - @header-height); |
|||
padding: 0; |
|||
background-color: @component-background; |
|||
|
|||
.scrollbar__wrap { |
|||
padding: 16px !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
> .scrollbar > .scrollbar__bar.is-horizontal { |
|||
display: none; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.@{prefix-cls-detail} { |
|||
position: absolute; |
|||
|
|||
.ant-drawer-header { |
|||
width: 100%; |
|||
height: @detail-header-height; |
|||
padding: 0; |
|||
border-top: 1px solid @border-color-base; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.ant-drawer-title { |
|||
height: 100%; |
|||
} |
|||
|
|||
.ant-drawer-close { |
|||
height: @detail-header-height; |
|||
line-height: @detail-header-height; |
|||
} |
|||
|
|||
.scrollbar__wrap { |
|||
padding: 0 !important; |
|||
} |
|||
|
|||
.ant-drawer-body { |
|||
height: calc(100% - @detail-header-height); |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,82 @@ |
|||
<template> |
|||
<div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer"> |
|||
<template v-if="!$slots.footer"> |
|||
<slot name="insertFooter"></slot> |
|||
<a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn"> |
|||
{{ cancelText }} |
|||
</a-button> |
|||
<slot name="centerFooter"></slot> |
|||
<a-button |
|||
:type="okType" |
|||
@click="handleOk" |
|||
v-bind="okButtonProps" |
|||
class="mr-2" |
|||
:loading="confirmLoading" |
|||
v-if="showOkBtn" |
|||
> |
|||
{{ okText }} |
|||
</a-button> |
|||
<slot name="appendFooter"></slot> |
|||
</template> |
|||
|
|||
<template v-else> |
|||
<slot name="footer"></slot> |
|||
</template> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import type { CSSProperties } from 'vue'; |
|||
import { defineComponent, computed } from 'vue'; |
|||
// import { useDesign } from '/@/hooks/web/useDesign'; |
|||
|
|||
import { footerProps } from '../props'; |
|||
export default defineComponent({ |
|||
name: 'BasicDrawerFooter', |
|||
props: { |
|||
...footerProps, |
|||
height: { |
|||
type: String, |
|||
default: '60px', |
|||
}, |
|||
}, |
|||
emits: ['ok', 'close'], |
|||
setup(props, { emit }) { |
|||
const prefixCls = 'basic-drawer-footer'; |
|||
|
|||
const getStyle = computed((): CSSProperties => { |
|||
const heightStr = `${props.height}`; |
|||
return { |
|||
height: heightStr, |
|||
lineHeight: `calc(${heightStr} - 1px)`, |
|||
}; |
|||
}); |
|||
|
|||
function handleOk() { |
|||
emit('ok'); |
|||
} |
|||
|
|||
function handleClose() { |
|||
emit('close'); |
|||
} |
|||
return { handleOk, prefixCls, handleClose, getStyle }; |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less"> |
|||
@prefix-cls: ~'basic-drawer-footer'; |
|||
@footer-height: 60px; |
|||
.@{prefix-cls} { |
|||
position: absolute; |
|||
bottom: 0; |
|||
width: 100%; |
|||
padding: 0 12px 0 20px; |
|||
text-align: right; |
|||
background-color: @component-background; |
|||
border-top: 1px solid @border-color-base; |
|||
|
|||
> * { |
|||
margin-right: 8px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,74 @@ |
|||
<template> |
|||
<BasicTitle v-if="!isDetail" :class="prefixCls"> |
|||
<slot name="title"></slot> |
|||
{{ !$slots.title ? title : '' }} |
|||
</BasicTitle> |
|||
|
|||
<div :class="[prefixCls, `${prefixCls}--detail`]" v-else> |
|||
<span :class="`${prefixCls}__twrap`"> |
|||
<span @click="handleClose" v-if="showDetailBack"> |
|||
<ArrowLeftOutlined :class="`${prefixCls}__back`" /> |
|||
</span> |
|||
<span v-if="title">{{ title }}</span> |
|||
</span> |
|||
|
|||
<span :class="`${prefixCls}__toolbar`"> |
|||
<slot name="titleToolbar"></slot> |
|||
</span> |
|||
</div> |
|||
</template> |
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
import BasicTitle from '@/components/basic/basic-title/index.vue'; |
|||
import { ArrowLeftOutlined } from '@ant-design/icons-vue'; |
|||
|
|||
// import { useDesign } from '/@/hooks/web/useDesign'; |
|||
|
|||
import { propTypes } from '@/utils/propTypes'; |
|||
export default defineComponent({ |
|||
name: 'BasicDrawerHeader', |
|||
components: { BasicTitle, ArrowLeftOutlined }, |
|||
props: { |
|||
isDetail: propTypes.bool, |
|||
showDetailBack: propTypes.bool, |
|||
title: propTypes.string, |
|||
}, |
|||
emits: ['close'], |
|||
setup(_, { emit }) { |
|||
const prefixCls = 'basic-drawer-header'; |
|||
|
|||
function handleClose() { |
|||
emit('close'); |
|||
} |
|||
|
|||
return { prefixCls, handleClose }; |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less"> |
|||
@prefix-cls: ~'basic-drawer-header'; |
|||
@footer-height: 60px; |
|||
.@{prefix-cls} { |
|||
display: flex; |
|||
height: 100%; |
|||
align-items: center; |
|||
|
|||
&__back { |
|||
padding: 0 12px; |
|||
cursor: pointer; |
|||
|
|||
&:hover { |
|||
color: @primary-color; |
|||
} |
|||
} |
|||
|
|||
&__twrap { |
|||
flex: 1; |
|||
} |
|||
|
|||
&__toolbar { |
|||
padding-right: 50px; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,44 @@ |
|||
import type { PropType } from 'vue'; |
|||
|
|||
import { useI18n } from '@/hooks/useI18n'; |
|||
const { t } = useI18n(); |
|||
|
|||
export const footerProps = { |
|||
confirmLoading: { type: Boolean }, |
|||
/** |
|||
* @description: Show close button |
|||
*/ |
|||
showCancelBtn: { type: Boolean, default: true }, |
|||
cancelButtonProps: Object as PropType<Recordable>, |
|||
cancelText: { type: String, default: t('common.cancelText') }, |
|||
/** |
|||
* @description: Show confirmation button |
|||
*/ |
|||
showOkBtn: { type: Boolean, default: true }, |
|||
okButtonProps: Object as PropType<Recordable>, |
|||
okText: { type: String, default: t('common.okText') }, |
|||
okType: { type: String, default: 'primary' }, |
|||
showFooter: { type: Boolean }, |
|||
footerHeight: { |
|||
type: [String, Number] as PropType<string | number>, |
|||
default: 60, |
|||
}, |
|||
}; |
|||
export const basicProps = { |
|||
isDetail: { type: Boolean }, |
|||
title: { type: String, default: '' }, |
|||
loadingText: { type: String }, |
|||
showDetailBack: { type: Boolean, default: true }, |
|||
visible: { type: Boolean }, |
|||
loading: { type: Boolean }, |
|||
maskClosable: { type: Boolean, default: true }, |
|||
getContainer: { |
|||
type: [Object, String] as PropType<any>, |
|||
}, |
|||
closeFunc: { |
|||
type: [Function, Object] as PropType<any>, |
|||
default: null, |
|||
}, |
|||
destroyOnClose: { type: Boolean }, |
|||
...footerProps, |
|||
}; |
|||
@ -0,0 +1,195 @@ |
|||
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'; |
|||
import type { ScrollContainerOptions } from '@/components/core/Container/index'; |
|||
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue'; |
|||
|
|||
export interface DrawerInstance { |
|||
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void; |
|||
emitVisible?: (visible: boolean, uid: number) => void; |
|||
} |
|||
|
|||
export interface ReturnMethods extends DrawerInstance { |
|||
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void; |
|||
closeDrawer: () => void; |
|||
getVisible?: ComputedRef<boolean>; |
|||
} |
|||
|
|||
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void; |
|||
|
|||
export interface ReturnInnerMethods extends DrawerInstance { |
|||
closeDrawer: () => void; |
|||
changeLoading: (loading: boolean) => void; |
|||
changeOkLoading: (loading: boolean) => void; |
|||
getVisible?: ComputedRef<boolean>; |
|||
} |
|||
|
|||
export type UseDrawerReturnType = [RegisterFn, ReturnMethods]; |
|||
|
|||
export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods]; |
|||
|
|||
|
|||
export interface DrawerActionType { |
|||
scrollBottom: () => void; |
|||
scrollTo: (to: number) => void; |
|||
getScrollWrap: () => Element | null; |
|||
} |
|||
|
|||
export interface DrawerFooterProps { |
|||
showOkBtn: boolean; |
|||
showCancelBtn: boolean; |
|||
/** |
|||
* Text of the Cancel button |
|||
* @default 'cancel' |
|||
* @type string |
|||
*/ |
|||
cancelText: string; |
|||
/** |
|||
* Text of the OK button |
|||
* @default 'OK' |
|||
* @type string |
|||
*/ |
|||
okText: string; |
|||
|
|||
/** |
|||
* Button type of the OK button |
|||
* @default 'primary' |
|||
* @type string |
|||
*/ |
|||
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default'; |
|||
/** |
|||
* The ok button props, follow jsx rules |
|||
* @type object |
|||
*/ |
|||
okButtonProps: { props: ButtonProps; on: {} }; |
|||
|
|||
/** |
|||
* The cancel button props, follow jsx rules |
|||
* @type object |
|||
*/ |
|||
cancelButtonProps: { props: ButtonProps; on: {} }; |
|||
/** |
|||
* Whether to apply loading visual effect for OK button or not |
|||
* @default false |
|||
* @type boolean |
|||
*/ |
|||
confirmLoading: boolean; |
|||
|
|||
showFooter: boolean; |
|||
footerHeight: string | number; |
|||
} |
|||
export interface DrawerProps extends DrawerFooterProps { |
|||
isDetail?: boolean; |
|||
loading?: boolean; |
|||
showDetailBack?: boolean; |
|||
visible?: boolean; |
|||
/** |
|||
* Built-in ScrollContainer component configuration |
|||
* @type ScrollContainerOptions |
|||
*/ |
|||
scrollOptions?: ScrollContainerOptions; |
|||
closeFunc?: () => Promise<any>; |
|||
triggerWindowResize?: boolean; |
|||
/** |
|||
* Whether a close (x) button is visible on top right of the Drawer dialog or not. |
|||
* @default true |
|||
* @type boolean |
|||
*/ |
|||
closable?: boolean; |
|||
|
|||
/** |
|||
* Whether to unmount child components on closing drawer or not. |
|||
* @default false |
|||
* @type boolean |
|||
*/ |
|||
destroyOnClose?: boolean; |
|||
|
|||
/** |
|||
* Return the mounted node for Drawer. |
|||
* @default 'body' |
|||
* @type any ( HTMLElement| () => HTMLElement | string) |
|||
*/ |
|||
getContainer?: () => HTMLElement | string; |
|||
|
|||
/** |
|||
* Whether to show mask or not. |
|||
* @default true |
|||
* @type boolean |
|||
*/ |
|||
mask?: boolean; |
|||
|
|||
/** |
|||
* Clicking on the mask (area outside the Drawer) to close the Drawer or not. |
|||
* @default true |
|||
* @type boolean |
|||
*/ |
|||
maskClosable?: boolean; |
|||
|
|||
/** |
|||
* Style for Drawer's mask element. |
|||
* @default {} |
|||
* @type object |
|||
*/ |
|||
maskStyle?: CSSProperties; |
|||
|
|||
/** |
|||
* The title for Drawer. |
|||
* @type any (string | slot) |
|||
*/ |
|||
title?: VNodeChild | JSX.Element; |
|||
/** |
|||
* The class name of the container of the Drawer dialog. |
|||
* @type string |
|||
*/ |
|||
wrapClassName?: string; |
|||
class?: string; |
|||
/** |
|||
* Style of wrapper element which **contains mask** compare to `drawerStyle` |
|||
* @type object |
|||
*/ |
|||
wrapStyle?: CSSProperties; |
|||
|
|||
/** |
|||
* Style of the popup layer element |
|||
* @type object |
|||
*/ |
|||
drawerStyle?: CSSProperties; |
|||
|
|||
/** |
|||
* Style of floating layer, typically used for adjusting its position. |
|||
* @type object |
|||
*/ |
|||
bodyStyle?: CSSProperties; |
|||
headerStyle?: CSSProperties; |
|||
|
|||
/** |
|||
* Width of the Drawer dialog. |
|||
* @default 256 |
|||
* @type string | number |
|||
*/ |
|||
width?: string | number; |
|||
|
|||
/** |
|||
* placement is top or bottom, height of the Drawer dialog. |
|||
* @type string | number |
|||
*/ |
|||
height?: string | number; |
|||
|
|||
/** |
|||
* The z-index of the Drawer. |
|||
* @default 1000 |
|||
* @type number |
|||
*/ |
|||
zIndex?: number; |
|||
|
|||
/** |
|||
* The placement of the Drawer. |
|||
* @default 'right' |
|||
* @type string |
|||
*/ |
|||
placement?: 'top' | 'right' | 'bottom' | 'left'; |
|||
afterVisibleChange?: (visible?: boolean) => void; |
|||
keyboard?: boolean; |
|||
/** |
|||
* Specify a callback that will be called when a user clicks mask, close button or Cancel button. |
|||
*/ |
|||
onClose?: (e?: Event) => void; |
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
import type { |
|||
UseDrawerReturnType, |
|||
DrawerInstance, |
|||
ReturnMethods, |
|||
DrawerProps, |
|||
UseDrawerInnerReturnType, |
|||
} from './typing'; |
|||
import { |
|||
ref, |
|||
getCurrentInstance, |
|||
unref, |
|||
reactive, |
|||
watchEffect, |
|||
nextTick, |
|||
toRaw, |
|||
computed, |
|||
} from 'vue'; |
|||
import { isProdMode } from '@/utils/env'; |
|||
import { isFunction } from '@/utils/is'; |
|||
import { tryOnUnmounted } from '@vueuse/core'; |
|||
import { isEqual } from 'lodash-es'; |
|||
import { error } from '@/utils/log'; |
|||
|
|||
const dataTransferRef = reactive<any>({}); |
|||
|
|||
const visibleData = reactive<{ [key: number]: boolean }>({}); |
|||
|
|||
/** |
|||
* @description: Applicable to separate drawer and call outside |
|||
*/ |
|||
export function useDrawer(): UseDrawerReturnType { |
|||
if (!getCurrentInstance()) { |
|||
throw new Error('useDrawer() can only be used inside setup() or functional components!'); |
|||
} |
|||
const drawer = ref<DrawerInstance | null>(null); |
|||
const loaded = ref<Nullable<boolean>>(false); |
|||
const uid = ref<string>(''); |
|||
|
|||
function register(drawerInstance: DrawerInstance, uuid: string) { |
|||
isProdMode() && |
|||
tryOnUnmounted(() => { |
|||
drawer.value = null; |
|||
loaded.value = null; |
|||
dataTransferRef[unref(uid)] = null; |
|||
}); |
|||
|
|||
if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) { |
|||
return; |
|||
} |
|||
uid.value = uuid; |
|||
drawer.value = drawerInstance; |
|||
loaded.value = true; |
|||
|
|||
drawerInstance.emitVisible = (visible: boolean, uid: number) => { |
|||
visibleData[uid] = visible; |
|||
}; |
|||
} |
|||
|
|||
const getInstance = () => { |
|||
const instance = unref(drawer); |
|||
if (!instance) { |
|||
error('useDrawer instance is undefined!'); |
|||
} |
|||
return instance; |
|||
}; |
|||
|
|||
const methods: ReturnMethods = { |
|||
setDrawerProps: (props: Partial<DrawerProps>): void => { |
|||
getInstance()?.setDrawerProps(props); |
|||
}, |
|||
|
|||
getVisible: computed((): boolean => { |
|||
return visibleData[~~unref(uid)]; |
|||
}), |
|||
|
|||
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { |
|||
getInstance()?.setDrawerProps({ |
|||
visible: visible, |
|||
}); |
|||
if (!data) return; |
|||
|
|||
if (openOnSet) { |
|||
dataTransferRef[unref(uid)] = null; |
|||
dataTransferRef[unref(uid)] = toRaw(data); |
|||
return; |
|||
} |
|||
const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data)); |
|||
if (!equal) { |
|||
dataTransferRef[unref(uid)] = toRaw(data); |
|||
} |
|||
}, |
|||
closeDrawer: () => { |
|||
getInstance()?.setDrawerProps({ visible: false }); |
|||
}, |
|||
}; |
|||
|
|||
return [register, methods]; |
|||
} |
|||
|
|||
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => { |
|||
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null); |
|||
const currentInstance = getCurrentInstance(); |
|||
const uidRef = ref<string>(''); |
|||
|
|||
if (!getCurrentInstance()) { |
|||
throw new Error('useDrawerInner() can only be used inside setup() or functional components!'); |
|||
} |
|||
|
|||
const getInstance = () => { |
|||
const instance = unref(drawerInstanceRef); |
|||
if (!instance) { |
|||
error('useDrawerInner instance is undefined!'); |
|||
return; |
|||
} |
|||
return instance; |
|||
}; |
|||
|
|||
const register = (modalInstance: DrawerInstance, uuid: string) => { |
|||
isProdMode() && |
|||
tryOnUnmounted(() => { |
|||
drawerInstanceRef.value = null; |
|||
}); |
|||
|
|||
uidRef.value = uuid; |
|||
drawerInstanceRef.value = modalInstance; |
|||
currentInstance?.emit('register', modalInstance, uuid); |
|||
}; |
|||
|
|||
watchEffect(() => { |
|||
const data = dataTransferRef[unref(uidRef)]; |
|||
if (!data) return; |
|||
if (!callbackFn || !isFunction(callbackFn)) return; |
|||
nextTick(() => { |
|||
callbackFn(data); |
|||
}); |
|||
}); |
|||
|
|||
return [ |
|||
register, |
|||
{ |
|||
changeLoading: (loading = true) => { |
|||
getInstance()?.setDrawerProps({ loading }); |
|||
}, |
|||
|
|||
changeOkLoading: (loading = true) => { |
|||
getInstance()?.setDrawerProps({ confirmLoading: loading }); |
|||
}, |
|||
getVisible: computed((): boolean => { |
|||
return visibleData[~~unref(uidRef)]; |
|||
}), |
|||
|
|||
closeDrawer: () => { |
|||
getInstance()?.setDrawerProps({ visible: false }); |
|||
}, |
|||
|
|||
setDrawerProps: (props: Partial<DrawerProps>) => { |
|||
getInstance()?.setDrawerProps(props); |
|||
}, |
|||
}, |
|||
]; |
|||
}; |
|||
@ -1,7 +1,21 @@ |
|||
/* |
|||
* @Author: AaronWu 2463371514@qq.com |
|||
* @Date: 2025-03-31 15:12:17 |
|||
* @LastEditors: AaronWu 2463371514@qq.com |
|||
* @LastEditTime: 2025-04-01 10:39:52 |
|||
* @FilePath: /IssueSupportManage/src/shims-vue.d.ts |
|||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|||
*/ |
|||
declare module '*.vue' { |
|||
import type { DefineComponent } from 'vue' |
|||
const component: DefineComponent<{}, {}, any> |
|||
export default component |
|||
import type { DefineComponent } from 'vue'; |
|||
const component: DefineComponent<{}, {}, any>; |
|||
export default component; |
|||
} |
|||
|
|||
declare module 'nprogress' |
|||
declare module '*.md' { |
|||
import type { ComponentOptions } from 'vue'; |
|||
const Component: ComponentOptions; |
|||
export default Component; |
|||
} |
|||
|
|||
declare module 'nprogress'; |
|||
|
|||
@ -0,0 +1,38 @@ |
|||
|
|||
|
|||
/** |
|||
* @description: Development mode |
|||
*/ |
|||
export const devMode = 'development'; |
|||
|
|||
/** |
|||
* @description: Production mode |
|||
*/ |
|||
export const prodMode = 'production'; |
|||
|
|||
/** |
|||
* @description: Get environment variables |
|||
* @returns: |
|||
* @example: |
|||
*/ |
|||
export function getEnv(): string { |
|||
return import.meta.env.MODE; |
|||
} |
|||
|
|||
/** |
|||
* @description: Is it a development mode |
|||
* @returns: |
|||
* @example: |
|||
*/ |
|||
export function isDevMode(): boolean { |
|||
return import.meta.env.DEV; |
|||
} |
|||
|
|||
/** |
|||
* @description: Is it a production mode |
|||
* @returns: |
|||
* @example: |
|||
*/ |
|||
export function isProdMode(): boolean { |
|||
return import.meta.env.PROD; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE; |
|||
|
|||
export function warn(message: string) { |
|||
console.warn(`[${projectName} warn]:${message}`); |
|||
} |
|||
|
|||
export function error(message: string) { |
|||
throw new Error(`[${projectName} error]:${message}`); |
|||
} |
|||
@ -1,85 +1,90 @@ |
|||
<!-- |
|||
功能:功能描述 |
|||
作者:Aaron.Wu |
|||
时间:2023年05月25日 17:00:26 |
|||
版本:v1.0 |
|||
修改记录: |
|||
修改内容: |
|||
修改人员: |
|||
修改时间: |
|||
* @Author: AaronWu 2463371514@qq.com |
|||
* @Date: 2025-03-31 15:12:17 |
|||
* @LastEditors: AaronWu 2463371514@qq.com |
|||
* @LastEditTime: 2025-04-01 10:40:23 |
|||
* @FilePath: /IssueSupportManage/src/views/dashboard/welcome/index.vue |
|||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE |
|||
--> |
|||
<template> |
|||
<div class="w-200px"> |
|||
<!-- <button @click="handleClick">222</button> |
|||
<BasicTree |
|||
ref="treeRef" |
|||
:clickRowToExpand="false" |
|||
:beforeRightClick="getRightMenuList" |
|||
:loading="treeLoading" |
|||
title="77777" |
|||
:treeData="treeData" |
|||
checkStrictly |
|||
checkable |
|||
highlight |
|||
search |
|||
toolbar |
|||
@select="handleSelect" |
|||
:checkedKeys="['0-0']" |
|||
> |
|||
</BasicTree> --> |
|||
<div class="welcome-container"> |
|||
<a-card class="doc-card"> |
|||
<template #title> |
|||
<div class="card-title"> |
|||
<span>接口文档</span> |
|||
<a-tag color="blue">DTO</a-tag> |
|||
</div> |
|||
</template> |
|||
<!-- <div class="markdown-body" v-html="mdContent"></div> --> |
|||
<DTO></DTO> |
|||
</a-card> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ContextMenuItem } from '@/components/basic/ContextMenu'; |
|||
import { BasicTree } from '@/components/core/Tree'; |
|||
import { PlusOutlined } from '@ant-design/icons-vue'; |
|||
import { Menu } from 'ant-design-vue'; |
|||
import { h, ref } from 'vue'; |
|||
import { ref, onMounted } from 'vue'; |
|||
import DTO from '@/assets/DTO.md'; |
|||
// console.log('DTO: ', DTO); |
|||
|
|||
const treeRef = ref<Nullable<any>>(null); |
|||
const treeData = ref<any[]>([ |
|||
{ |
|||
title: 'parent 1', |
|||
key: '0-0', |
|||
children: [ |
|||
{ |
|||
title: 'parent 1-0', |
|||
key: '0-0-0', |
|||
disabled: true, |
|||
children: [ |
|||
{ title: 'leaf', key: '0-0-0-0', disableCheckbox: true }, |
|||
{ title: 'leaf', key: '0-0-0-1' }, |
|||
], |
|||
}, |
|||
{ |
|||
title: 'parent 1-1', |
|||
key: '0-0-1', |
|||
children: [{ key: '0-0-1-0', title: 'sss' }], |
|||
}, |
|||
], |
|||
}, |
|||
]); |
|||
const treeLoading = ref<boolean>(false); |
|||
// const mdContent = ref(DTO); |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.welcome-container { |
|||
padding: 24px; |
|||
|
|||
const handleSelect = (key, row) => { |
|||
console.log('row: ', row); |
|||
console.log('key: ', key); |
|||
}; |
|||
.doc-card { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
|
|||
const handleClick = () => { |
|||
console.log(treeRef.value.checkAll(true)); |
|||
treeRef.value.setCheckedKeys(['0-0']) |
|||
}; |
|||
.card-title { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12px; |
|||
|
|||
// 右键菜单 |
|||
function getRightMenuList(node: any): ContextMenuItem[] { |
|||
return [ |
|||
{ |
|||
label: '新增', |
|||
handler: () => {}, |
|||
}, |
|||
]; |
|||
span { |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="less" scoped></style> |
|||
|
|||
:deep(.markdown-body) { |
|||
padding: 16px 0; |
|||
|
|||
table { |
|||
width: 100%; |
|||
border-collapse: collapse; |
|||
margin: 16px 0; |
|||
|
|||
th, |
|||
td { |
|||
padding: 12px; |
|||
border: 1px solid #e8e8e8; |
|||
} |
|||
|
|||
th { |
|||
background: #fafafa; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
tr:hover { |
|||
background: #fafafa; |
|||
} |
|||
} |
|||
|
|||
h2 { |
|||
font-size: 24px; |
|||
margin: 24px 0 16px; |
|||
padding-bottom: 8px; |
|||
border-bottom: 1px solid #eee; |
|||
} |
|||
|
|||
h3 { |
|||
font-size: 18px; |
|||
margin: 20px 0 12px; |
|||
color: #1a1a1a; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
|
|||
@ -1,14 +0,0 @@ |
|||
<!-- |
|||
功能:功能描述 |
|||
作者:Aaron |
|||
时间:2023年06月16日 14:53:40 |
|||
版本:v1.0 |
|||
修改记录: |
|||
修改内容: |
|||
修改人员: |
|||
修改时间: |
|||
--> |
|||
<template> </template> |
|||
|
|||
<script lang="tsx" setup></script> |
|||
<style lang="less" scoped></style> |
|||
@ -0,0 +1,330 @@ |
|||
<template> |
|||
<div class="bg-white m-4 mr-2 overflow-hidden h-full"> |
|||
<BasicTree |
|||
ref="treeRef" |
|||
:actionList="actionList" |
|||
:beforeRightClick="getRightMenuList" |
|||
:checkable="!query" |
|||
:clickRowToExpand="false" |
|||
:treeData="treeData" |
|||
checkStrictly |
|||
:defaultExpandAll="true" |
|||
search |
|||
toolbar |
|||
:highlight="true" |
|||
@select="handleSelect" |
|||
:toolbarStrictly="false" |
|||
> |
|||
<!-- <template #titleBefore="item"> </template> --> |
|||
</BasicTree> |
|||
<div v-if="query" class="m-4 flex justify-center"> |
|||
<a-button class="w-[80%]" type="primary" @click="handleReset()">重置</a-button> |
|||
<!-- <Checkbox v-model:checked="recursion" @change="handleQuery()">本级及子级</Checkbox> --> |
|||
</div> |
|||
<div v-else class="m-4 flex justify-center"> |
|||
<a-button |
|||
type="primary" |
|||
class="w-[80%]" |
|||
v-permission="{ action: RoleEnum.DICT_ADD, effect: 'disabled' }" |
|||
@click="openTypeModal({})" |
|||
> |
|||
{{ '新增类型' }} |
|||
</a-button> |
|||
<!-- <a-button |
|||
v-permission="{ action: RoleEnum.DICT_DELETE, effect: 'disabled' }" |
|||
class="mr-2" |
|||
@click="handleBatchDelete()" |
|||
> |
|||
{{ '删除' }} |
|||
</a-button> --> |
|||
</div> |
|||
<!-- <OrgRole @register="registerModal" @success="handleSuccess" /> --> |
|||
</div> |
|||
</template> |
|||
<script lang="tsx"> |
|||
import { defineComponent, h, onMounted, ref, unref } from 'vue'; |
|||
import { Checkbox, message, Tag } from 'ant-design-vue'; // antd组件 |
|||
import { useI18n } from '@/hooks/useI18n'; |
|||
import { |
|||
BasicTree, |
|||
ContextMenuItem, |
|||
TreeActionItem, |
|||
TreeActionType, |
|||
TreeItem, |
|||
} from '@/components/core/Tree'; |
|||
import { eachTree, findChildrenByParentId, findNodeByKey } from '@/utils/helper/treeHelper'; |
|||
import { RoleEnum } from '@/enums/roleEnum'; |
|||
import { OrgTypeEnum } from '@/enums/biz/base'; |
|||
import { useFormModal } from '@/hooks/useModal/index'; |
|||
|
|||
import { createDict, deleteBatchDictById, fetchDictList, updateDict } from '@/api/dict/index'; |
|||
// import { useModal } from '@/components/Modal'; |
|||
// import OrgRole from './orgRole/index.vue'; |
|||
import { useMessage } from '@/hooks/useMessage'; |
|||
import { mockTree } from './mockData'; |
|||
import { Nullable } from '@/utils/types'; |
|||
import { typeFormSchema } from './formSchemas'; |
|||
import { TableListItem } from './columns'; |
|||
import { omit } from 'lodash-es'; |
|||
import { isArray } from '@/utils/is'; |
|||
|
|||
export default defineComponent({ |
|||
name: 'BaseDictValueManagement', |
|||
components: { |
|||
BasicTree, |
|||
// OrgRole, |
|||
Checkbox, |
|||
Tag, |
|||
}, |
|||
props: { |
|||
query: { |
|||
type: Boolean, |
|||
default: false, |
|||
}, |
|||
}, |
|||
emits: ['select', 'add', 'edit', 'change', 'reset'], |
|||
setup(props, { emit }) { |
|||
const { t } = useI18n(); |
|||
const { createMessage, createConfirm } = useMessage(); |
|||
|
|||
const treeRef = ref<Nullable<TreeActionType>>(null); |
|||
const treeData = ref<TreeItem[]>([]); |
|||
const recursion = ref<boolean>(false); |
|||
// 绑定角色 |
|||
// const [registerModal, { openModal }] = useModal(); |
|||
|
|||
const [showModal] = useFormModal(); |
|||
|
|||
/** |
|||
* @description 打开弹窗 |
|||
*/ |
|||
const openTypeModal = async (record: Partial<TableListItem> = {}, isReadOnly = false) => { |
|||
const [formRef] = await showModal<any>({ |
|||
modalProps: { |
|||
title: `${isReadOnly ? '查看' : record.id ? '编辑' : '新增'}类型`, |
|||
width: 500, |
|||
onFinish: async (values) => { |
|||
values.id = record.id; |
|||
await (record.id ? updateDict : createDict)(values); |
|||
message.success(`${record.id ? '编辑' : '新增'}成功`); |
|||
fetch(); |
|||
}, |
|||
}, |
|||
formProps: { |
|||
labelWidth: 100, |
|||
schemas: typeFormSchema, |
|||
autoSubmitOnEnter: true, |
|||
}, |
|||
}); |
|||
formRef?.setFieldsValue(record); |
|||
}; |
|||
|
|||
function getTree() { |
|||
const tree = unref(treeRef); |
|||
if (!tree) { |
|||
throw new Error('树结构加载失败,请刷新页面'); |
|||
} |
|||
return tree; |
|||
} |
|||
|
|||
onMounted(() => { |
|||
fetch(); |
|||
}); |
|||
|
|||
// 加载数据 |
|||
async function fetch() { |
|||
// const types = (await fetchDictList()) as unknown as TreeItem[]; |
|||
const types = mockTree; |
|||
console.log('types: ', types); |
|||
treeData.value = types.map((e) => { |
|||
return { |
|||
...e, |
|||
name: e.name, |
|||
editable: true, |
|||
}; |
|||
}) as unknown as TreeItem[]; |
|||
eachTree(treeData.value, (item) => { |
|||
item.key = item.id; |
|||
item.title = item.name; |
|||
item.slots = { titleBefore: 'titleBefore' }; |
|||
return item; |
|||
}); |
|||
console.log('treeData.value: ', treeData.value); |
|||
console.log('[treeData.value[0].id]: ', [treeData.value[0].id]); |
|||
|
|||
if (isArray(treeData.value) && treeData.value.length > 0) { |
|||
getTree().setSelectedKeys([treeData.value[0].id]); |
|||
handleSelect([treeData.value[0].id]); |
|||
} |
|||
// setTimeout(() => { |
|||
// getTree().filterByLevel(2); |
|||
// }, 0); |
|||
} |
|||
|
|||
// 选择节点 |
|||
function handleSelect(keys: string[]) { |
|||
console.log('keys: ', keys); |
|||
if (keys[0]) { |
|||
const node = findNodeByKey(keys[0], treeData.value); |
|||
const parent = findNodeByKey(node?.parentId, treeData.value); |
|||
let childrenIds: string[] = []; |
|||
if (recursion.value) { |
|||
childrenIds = findChildrenByParentId(keys[0], treeData.value); |
|||
} else { |
|||
childrenIds = [node.id]; |
|||
} |
|||
emit('select', parent, node, childrenIds); |
|||
} else { |
|||
emit('select', {}, {}); |
|||
} |
|||
} |
|||
|
|||
let actionList: TreeActionItem[] = []; |
|||
let getRightMenuList = (_: any): ContextMenuItem[] => { |
|||
return []; |
|||
}; |
|||
if (!props.query) { |
|||
// 悬停图标 |
|||
actionList = [ |
|||
{ |
|||
render: (node) => { |
|||
if (node.editable) { |
|||
return h( |
|||
'a', |
|||
{ |
|||
class: 'ml-2', |
|||
onClick: (e: Event) => { |
|||
e?.stopPropagation(); |
|||
e?.preventDefault(); |
|||
const current = findNodeByKey(node?.id, treeData.value); |
|||
// const parent = findNodeByKey(node?.parentId, treeData.value); |
|||
// emit('edit', parent, current); |
|||
openTypeModal(current); |
|||
}, |
|||
}, |
|||
'编辑', |
|||
); |
|||
} |
|||
}, |
|||
}, |
|||
|
|||
{ |
|||
render: (node) => { |
|||
if (node.editable) { |
|||
return h( |
|||
'a', |
|||
{ |
|||
class: 'ml-2', |
|||
onClick: (e: Event) => { |
|||
e?.stopPropagation(); |
|||
e?.preventDefault(); |
|||
batchDelete([node.id]); |
|||
}, |
|||
}, |
|||
'删除', |
|||
); |
|||
} |
|||
}, |
|||
}, |
|||
]; |
|||
|
|||
// 右键菜单 |
|||
// getRightMenuList = (node: any): ContextMenuItem[] => { |
|||
// return [ |
|||
// { |
|||
// label: '编辑', |
|||
// auth: RoleEnum.DICT_EDIT, |
|||
// handler: () => { |
|||
// const current = findNodeByKey(unref(node)?.id, treeData.value); |
|||
// const parent = findNodeByKey(unref(node)?.parentId, treeData.value); |
|||
// emit('edit', parent, current); |
|||
// }, |
|||
// }, |
|||
// { |
|||
// label: '删除', |
|||
// auth: RoleEnum.DICT_DELETE, |
|||
// handler: () => { |
|||
// batchDelete([unref(node).id]); |
|||
// }, |
|||
// }, |
|||
// ]; |
|||
// }; |
|||
} |
|||
|
|||
// 执行批量删除 |
|||
async function batchDelete(ids: string[]) { |
|||
createConfirm({ |
|||
iconType: 'warning', |
|||
content: '选中节点及其子结点将被永久删除, 是否确定删除?', |
|||
onOk: async () => { |
|||
try { |
|||
await deleteBatchDictById(ids); |
|||
createMessage.success(t('common.tips.deleteSuccess')); |
|||
fetch(); |
|||
} catch (e) {} |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
// 点击树外面的 新增 |
|||
function handleAdd() { |
|||
emit('add', findNodeByKey('0', treeData.value)); |
|||
} |
|||
|
|||
// 点击树外面的 批量删除 |
|||
function handleBatchDelete() { |
|||
const { checked } = getTree().getCheckedKeys() as { |
|||
checked: string[]; |
|||
halfChecked: string[]; |
|||
}; |
|||
if (!checked || checked.length <= 0) { |
|||
createMessage.warning(t('common.tips.pleaseSelectTheData')); |
|||
return; |
|||
} |
|||
batchDelete(checked); |
|||
} |
|||
|
|||
// 切换显示方式 |
|||
function changeDisplay() { |
|||
emit('change', '2'); |
|||
} |
|||
|
|||
// 重置 |
|||
function handleReset() { |
|||
getTree().setSelectedKeys([]); |
|||
emit('reset'); |
|||
} |
|||
|
|||
// 选择 本级及子级 |
|||
function handleQuery() { |
|||
handleSelect(getTree().getSelectedKeys() as string[]); |
|||
} |
|||
|
|||
// 新增或编辑成功回调 |
|||
function handleSuccess() { |
|||
fetch(); |
|||
} |
|||
|
|||
return { |
|||
t, |
|||
treeRef, |
|||
treeData, |
|||
fetch, |
|||
handleAdd, |
|||
handleBatchDelete, |
|||
getRightMenuList, |
|||
actionList, |
|||
handleSelect, |
|||
changeDisplay, |
|||
handleReset, |
|||
handleQuery, |
|||
RoleEnum, |
|||
// registerModal, |
|||
handleSuccess, |
|||
recursion, |
|||
OrgTypeEnum, |
|||
openTypeModal, |
|||
}; |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -1,132 +1,80 @@ |
|||
/* |
|||
* @Author: AaronWu 2463371514@qq.com |
|||
* @Date: 2025-04-01 13:43:34 |
|||
* @LastEditors: AaronWu 2463371514@qq.com |
|||
* @LastEditTime: 2025-04-01 13:52:02 |
|||
* @FilePath: /IssueSupportManage/src/views/system/dictionary/columns.tsx |
|||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|||
*/ |
|||
import type { TableColumn } from '@/components/core/dynamic-table'; |
|||
import { formatToDateTime } from '@/utils/dateUtil'; |
|||
import { h } from 'vue'; |
|||
import { Switch } from 'ant-design-vue'; |
|||
import { disableDictValue, enableDictValue } from '@/api/dict'; |
|||
|
|||
export type TableListItem = API.DictionaryInfoType; |
|||
export type DictionaryTableListItem = API.DictionaryItemInfoType; |
|||
export type TableListItem = API.DictValueType; |
|||
export type TableColumnItem = TableColumn<TableListItem>; |
|||
export type DictionaryTableColumnItem = TableColumn<DictionaryTableListItem>; |
|||
|
|||
// 数据项类型
|
|||
// export type ListItemType = typeof tableData[number];
|
|||
// 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示
|
|||
export const baseColumns: TableColumnItem[] = [ |
|||
{ |
|||
title: '标识', |
|||
title: '字段值', |
|||
align: 'center', |
|||
dataIndex: 'dictKey', |
|||
width: 200, |
|||
dataIndex: 'fieldValue', |
|||
width: 150, |
|||
ellipsis: true, |
|||
resizable: true, |
|||
// defaultEditable: true,
|
|||
// editable: ({ index }) => {
|
|||
// // 第一行不允许被编辑
|
|||
// return index > 0;
|
|||
// },
|
|||
formItemProps: { |
|||
defaultValue: '', |
|||
required: false, |
|||
}, |
|||
}, |
|||
{ |
|||
title: '名称', |
|||
title: '启用状态', |
|||
align: 'center', |
|||
dataIndex: 'name', |
|||
width: 200, |
|||
dataIndex: 'enable', |
|||
width: 150, |
|||
ellipsis: true, |
|||
resizable: true, |
|||
formItemProps: { |
|||
defaultValue: '', |
|||
required: false, |
|||
component: 'Select', |
|||
defaultValue: '1', |
|||
componentProps: { |
|||
options: [ |
|||
{ |
|||
label: '启用', |
|||
value: '1', |
|||
}, |
|||
{ |
|||
label: '禁用', |
|||
value: '0', |
|||
}, |
|||
], |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
title: '状态', |
|||
align: 'center', |
|||
dataIndex: 'status', |
|||
hideInSearch: true, |
|||
width: 100, |
|||
customRender: ({ record }) => { |
|||
return ( |
|||
<a-tag color={record.status ? 'success' : 'error'}>{record.status ? '启用' : '禁用'}</a-tag> |
|||
); |
|||
}, |
|||
}, |
|||
const onChange = (checked: boolean) => { |
|||
record.pendingStatus = true; |
|||
const newState = checked ? '1' : '0'; |
|||
record.enable = newState; |
|||
|
|||
{ |
|||
title: '创建时间', |
|||
align: 'center', |
|||
dataIndex: 'createTime', |
|||
width: 200, |
|||
formItemProps: { |
|||
defaultValue: '', |
|||
required: false, |
|||
component: 'RangePicker', |
|||
}, |
|||
customRender: ({ record }) => `${formatToDateTime(record.createTime)}`, |
|||
}, |
|||
]; |
|||
(checked ? enableDictValue : disableDictValue)({ id: record.id! }) |
|||
.then(() => { |
|||
record.enable = newState; |
|||
}) |
|||
.catch(() => {}) |
|||
.finally(() => { |
|||
record.pendingStatus = false; |
|||
}); |
|||
}; |
|||
|
|||
export const dictItemColumns: DictionaryTableColumnItem[]=[ |
|||
{ |
|||
title: '标识', |
|||
align: 'center', |
|||
dataIndex: 'dictKey', |
|||
width: 200, |
|||
resizable: true, |
|||
defaultEditable: true, |
|||
// editable: ({ index }) => {
|
|||
// // 第一行不允许被编辑
|
|||
// return index > 0;
|
|||
// },
|
|||
formItemProps: { |
|||
defaultValue: '', |
|||
required: false, |
|||
}, |
|||
}, |
|||
{ |
|||
title: '名称', |
|||
align: 'center', |
|||
dataIndex: 'name', |
|||
width: 200, |
|||
resizable: true, |
|||
formItemProps: { |
|||
defaultValue: '', |
|||
required: false, |
|||
// 渲染函数写法
|
|||
return h(Switch, { |
|||
checked: record.enable === '1', |
|||
loading: record.pendingStatus, |
|||
onChange, |
|||
}); |
|||
}, |
|||
}, |
|||
{ |
|||
title: '状态', |
|||
align: 'center', |
|||
dataIndex: 'status', |
|||
hideInSearch: true, |
|||
width: 100, |
|||
customRender: ({ record }) => { |
|||
return ( |
|||
<a-tag color={record.status ? 'success' : 'error'}>{record.status ? '启用' : '禁用'}</a-tag> |
|||
); |
|||
}, |
|||
}, |
|||
{ |
|||
title: '排序', |
|||
align: 'center', |
|||
dataIndex: 'sortValue', |
|||
hideInSearch: true, |
|||
width: 200, |
|||
resizable: true, |
|||
formItemProps: { |
|||
defaultValue: '', |
|||
required: false, |
|||
}, |
|||
}, |
|||
{ |
|||
title: '创建时间', |
|||
align: 'center', |
|||
dataIndex: 'createTime', |
|||
hideInSearch: true, |
|||
width: 200, |
|||
formItemProps: { |
|||
defaultValue: '', |
|||
required: false, |
|||
component: 'RangePicker', |
|||
}, |
|||
customRender: ({ record }) => `${formatToDateTime(record.createTime)}`, |
|||
}, |
|||
] |
|||
]; |
|||
|
|||
@ -1,44 +1,62 @@ |
|||
import type { FormSchema } from '@/components/core/schema-form/'; |
|||
|
|||
export const schemas: FormSchema[] = [ |
|||
|
|||
// 列表编辑页字段
|
|||
export const editFormSchema: FormSchema[] = [ |
|||
{ |
|||
field: 'dictKey', |
|||
field: 'id', |
|||
label: 'ID', |
|||
component: 'Input', |
|||
label: '标识', |
|||
colProps: { |
|||
span: 12, |
|||
}, |
|||
rules: [{ required: true }], |
|||
vShow: false, |
|||
}, |
|||
{ |
|||
field: 'name', |
|||
label: '字段值', |
|||
field: 'fieldValue', |
|||
component: 'Input', |
|||
label: '名称', |
|||
colProps: { |
|||
span: 12, |
|||
span: 24, |
|||
}, |
|||
rules: [{ required: true }], |
|||
}, |
|||
{ |
|||
field: 'status', |
|||
component: 'Switch', |
|||
label: '状态', |
|||
label: '启用状态', |
|||
field: 'enable', |
|||
component: 'RadioGroup', |
|||
defaultValue: '1', |
|||
colProps: { |
|||
span: 12, |
|||
}, |
|||
componentProps: { |
|||
options: [ |
|||
{ |
|||
label: '启用', |
|||
value: '1', |
|||
}, |
|||
{ |
|||
label: '禁用', |
|||
value: '0', |
|||
}, |
|||
], |
|||
}, |
|||
rules: [{ required: true, type: 'string' }], |
|||
|
|||
}, |
|||
]; |
|||
|
|||
// 新增类别表单
|
|||
export const typeFormSchema: FormSchema[] = [ |
|||
{ |
|||
field: 'remark', |
|||
component: 'InputTextArea', |
|||
label: '备注', |
|||
field: 'id', |
|||
label: 'ID', |
|||
component: 'Input', |
|||
vShow: false, |
|||
}, |
|||
{ |
|||
label: '名称', |
|||
field: 'name', |
|||
component: 'Input', |
|||
colProps: { |
|||
span: 12, |
|||
span: 24, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'dictItems', |
|||
label: '字典条目', |
|||
slot: 'dictItems', |
|||
rules: [{ required: true }], |
|||
}, |
|||
]; |
|||
|
|||
@ -0,0 +1,14 @@ |
|||
/* |
|||
* @Author: AaronWu 2463371514@qq.com |
|||
* @Date: 2025-04-01 13:24:31 |
|||
* @LastEditors: AaronWu 2463371514@qq.com |
|||
* @LastEditTime: 2025-04-01 13:25:12 |
|||
* @FilePath: /IssueSupportManage/src/views/system/dictionary/mockData.ts |
|||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|||
*/ |
|||
export const mockTree: any[] = [ |
|||
{ |
|||
id: 1, |
|||
name: '字典类别', |
|||
}, |
|||
]; |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue