create-jnrs-vue 1.2.26 → 1.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/jnrs-vue/package.json +3 -3
- package/jnrs-vue/public/system/menu.json +18 -0
- package/jnrs-vue/src/api/demos/ai_agent_context.ts +159 -0
- package/jnrs-vue/src/api/demos/index.ts +12 -17
- package/jnrs-vue/src/api/request.ts +2 -2
- package/jnrs-vue/src/components/common/DictTag.vue +1 -1
- package/jnrs-vue/src/composables/useCrud.ts +228 -55
- package/jnrs-vue/src/utils/packages.ts +2 -2
- package/jnrs-vue/src/views/demos/compositionCrud/index.vue +350 -0
- package/jnrs-vue/src/views/demos/compositionTable/index.vue +85 -0
- package/jnrs-vue/src/views/demos/crud/index.vue +5 -5
- package/jnrs-vue/src/views/demos/unitTest/RequestPage.vue +2 -2
- package/package.json +1 -1
package/jnrs-vue/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jnrs-vue",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.27",
|
|
4
4
|
"description": "JNRS 信息化管理系统",
|
|
5
5
|
"author": "talia_tan",
|
|
6
6
|
"private": true,
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@element-plus/icons-vue": "^2.3.2",
|
|
22
22
|
"@jnrs/lingshu-smart": "2.2.7",
|
|
23
|
-
"@jnrs/shared": "1.1.
|
|
24
|
-
"@jnrs/vue-core": "1.2.
|
|
23
|
+
"@jnrs/shared": "1.1.23",
|
|
24
|
+
"@jnrs/vue-core": "1.2.15",
|
|
25
25
|
"@vueuse/core": "^14.1.0",
|
|
26
26
|
"element-plus": "^2.13.3",
|
|
27
27
|
"pinia": "^3.0.4",
|
|
@@ -27,6 +27,15 @@
|
|
|
27
27
|
},
|
|
28
28
|
"component": "/demos/simpleTable/index"
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"path": "/compositionTable",
|
|
32
|
+
"name": "compositionTable",
|
|
33
|
+
"meta": {
|
|
34
|
+
"title": "简单数据表格 - 组合式",
|
|
35
|
+
"todoCount": 0
|
|
36
|
+
},
|
|
37
|
+
"component": "/demos/compositionTable/index"
|
|
38
|
+
},
|
|
30
39
|
{
|
|
31
40
|
"path": "/crud",
|
|
32
41
|
"name": "Crud",
|
|
@@ -36,6 +45,15 @@
|
|
|
36
45
|
},
|
|
37
46
|
"component": "/demos/crud/index"
|
|
38
47
|
},
|
|
48
|
+
{
|
|
49
|
+
"path": "/compositionCrud",
|
|
50
|
+
"name": "compositionCrud",
|
|
51
|
+
"meta": {
|
|
52
|
+
"title": "完整增删改查 - 组合式",
|
|
53
|
+
"todoCount": 0
|
|
54
|
+
},
|
|
55
|
+
"component": "/demos/compositionCrud/index"
|
|
56
|
+
},
|
|
39
57
|
{
|
|
40
58
|
"path": "/testRedirect",
|
|
41
59
|
"name": "TestRedirect",
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author : TanRui
|
|
3
|
+
* @WeChat : Tan578853789
|
|
4
|
+
* @File : project/ai_agent_context.ts
|
|
5
|
+
* @Date : 2026/03/01
|
|
6
|
+
* @Desc. : 示例模块接口文档(对于标准业务功能可作为 AI Agent 上下文)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
// 列表数据及数据总数
|
|
11
|
+
IPageTableData,
|
|
12
|
+
// 分页
|
|
13
|
+
IPagination,
|
|
14
|
+
// 附件
|
|
15
|
+
IFile
|
|
16
|
+
} from '@/types'
|
|
17
|
+
import {
|
|
18
|
+
// axios 请求方法泛型拓展封装
|
|
19
|
+
axiosRequest
|
|
20
|
+
} from '../request'
|
|
21
|
+
import {
|
|
22
|
+
// 将对象转为 FormData
|
|
23
|
+
objectToFormData
|
|
24
|
+
} from '@/utils'
|
|
25
|
+
|
|
26
|
+
export interface Project extends IFile {
|
|
27
|
+
id: string
|
|
28
|
+
programCode: string
|
|
29
|
+
program: string
|
|
30
|
+
code: string
|
|
31
|
+
name: string
|
|
32
|
+
description: string
|
|
33
|
+
projectType?: '敏捷型' | '瀑布型' | '混合型'
|
|
34
|
+
manager: string
|
|
35
|
+
budget: number
|
|
36
|
+
plannedStartDate: string
|
|
37
|
+
plannedFinishDate: string
|
|
38
|
+
progress: string
|
|
39
|
+
status: '进行中' | '已完成' | '未开始' | '已暂停'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type ProjectReadOnly =
|
|
43
|
+
| 'programCode'
|
|
44
|
+
| 'program'
|
|
45
|
+
| 'code'
|
|
46
|
+
| 'manager'
|
|
47
|
+
| 'plannedFinishDate'
|
|
48
|
+
| 'progress'
|
|
49
|
+
| 'status'
|
|
50
|
+
| 'imageDocument'
|
|
51
|
+
| 'attachmentDocument'
|
|
52
|
+
|
|
53
|
+
export type EditProject = Omit<Project, ProjectReadOnly> & {
|
|
54
|
+
newAttachmentFile: []
|
|
55
|
+
newImageFiles: []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ProjectQuery extends IPagination {
|
|
59
|
+
code?: string
|
|
60
|
+
projectType?: '敏捷型' | '瀑布型' | '混合型'
|
|
61
|
+
manager?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 项目 - 列表数据
|
|
66
|
+
*/
|
|
67
|
+
export const ProjectListApi = (params?: ProjectQuery): Promise<IPageTableData<Project>> => {
|
|
68
|
+
return axiosRequest({
|
|
69
|
+
url: '/project/table',
|
|
70
|
+
method: 'get',
|
|
71
|
+
params
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 项目 - 详情数据
|
|
77
|
+
*/
|
|
78
|
+
export const DataApiTest = (params: { id: string }): Promise<Project> => {
|
|
79
|
+
return axiosRequest({
|
|
80
|
+
url: '/project/info',
|
|
81
|
+
method: 'get',
|
|
82
|
+
params
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 项目 - 新增
|
|
88
|
+
*/
|
|
89
|
+
export const CreateProjectApi = (data: Omit<Project, 'id'>) => {
|
|
90
|
+
return axiosRequest({
|
|
91
|
+
url: '/project/save',
|
|
92
|
+
method: 'post',
|
|
93
|
+
data
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 项目 - 编辑
|
|
99
|
+
*/
|
|
100
|
+
export const EditProjectApi = (data: EditProject) => {
|
|
101
|
+
return axiosRequest({
|
|
102
|
+
url: '/project/save',
|
|
103
|
+
method: 'post',
|
|
104
|
+
data: objectToFormData(data) // 请求体如果为 formData 类型,使用 objectToFormData 方法转换
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 项目 - 更新
|
|
110
|
+
*/
|
|
111
|
+
export const UpdateProjectApi = (data: Project) => {
|
|
112
|
+
return axiosRequest({
|
|
113
|
+
url: '/project/update',
|
|
114
|
+
method: 'put',
|
|
115
|
+
data
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 项目 - 删除
|
|
121
|
+
*/
|
|
122
|
+
export const DeleteProjectApi = (id: string) => {
|
|
123
|
+
return axiosRequest({
|
|
124
|
+
url: `/project/delete/${id}`,
|
|
125
|
+
method: 'delete'
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 项目 - 导入
|
|
131
|
+
*/
|
|
132
|
+
export const ImportDataApi = (data: FormData) => {
|
|
133
|
+
return axiosRequest({
|
|
134
|
+
url: '/project/import',
|
|
135
|
+
method: 'post',
|
|
136
|
+
data
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 项目 - 导出
|
|
142
|
+
*/
|
|
143
|
+
export const ExportApi = (data: Record<string, unknown>): Promise<Blob> => {
|
|
144
|
+
return axiosRequest({
|
|
145
|
+
url: '/project/export',
|
|
146
|
+
method: 'post',
|
|
147
|
+
data
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 项目 - 文件下载
|
|
153
|
+
*/
|
|
154
|
+
export const DownloadFileApi = (): Promise<Blob> => {
|
|
155
|
+
return axiosRequest({
|
|
156
|
+
url: '/project/files',
|
|
157
|
+
method: 'post'
|
|
158
|
+
})
|
|
159
|
+
}
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* @WeChat : Tan578853789
|
|
4
4
|
* @File : demos/index.ts
|
|
5
5
|
* @Date : 2026/03/01
|
|
6
|
-
* @Desc. :
|
|
6
|
+
* @Desc. : 示例模块接口文档(非标业务功能人工编码参考)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { IFullResponse } from '@jnrs/shared'
|
|
10
10
|
import type { IPagination, IPageTableData, IFile, IUser } from '@/types'
|
|
11
11
|
import { axiosRequest } from '../request'
|
|
12
12
|
import { objectToFormData } from '@/utils'
|
|
@@ -15,7 +15,7 @@ import { objectToFormData } from '@/utils'
|
|
|
15
15
|
* 项目 - 详情
|
|
16
16
|
*/
|
|
17
17
|
export interface Project extends IFile {
|
|
18
|
-
id:
|
|
18
|
+
id: number
|
|
19
19
|
programCode: string
|
|
20
20
|
program: string
|
|
21
21
|
code: string
|
|
@@ -31,10 +31,9 @@ export interface Project extends IFile {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* 创建时不能修改的属性
|
|
35
35
|
*/
|
|
36
|
-
type
|
|
37
|
-
| 'id'
|
|
36
|
+
type ProjectReadOnly =
|
|
38
37
|
| 'programCode'
|
|
39
38
|
| 'program'
|
|
40
39
|
| 'code'
|
|
@@ -48,8 +47,7 @@ type CreateProjectOmit =
|
|
|
48
47
|
/**
|
|
49
48
|
* 项目 - 创建
|
|
50
49
|
*/
|
|
51
|
-
export type EditProject = Omit<Project,
|
|
52
|
-
id?: string
|
|
50
|
+
export type EditProject = Omit<Project, ProjectReadOnly> & {
|
|
53
51
|
managerId?: Record<string, unknown> | number
|
|
54
52
|
newAttachmentFile: []
|
|
55
53
|
newImageFiles: []
|
|
@@ -62,27 +60,24 @@ export interface ProjectQuery extends IPagination {
|
|
|
62
60
|
code?: string
|
|
63
61
|
projectType?: '敏捷型' | '瀑布型' | '混合型'
|
|
64
62
|
manager?: string
|
|
63
|
+
plannedStartDate?: string
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
// 测试 获取数据
|
|
68
|
-
export const DataApiTest = (id: string): Promise<IUser> => {
|
|
67
|
+
export const DataApiTest = (params: { id: string }): Promise<IUser> => {
|
|
69
68
|
return axiosRequest({
|
|
70
69
|
url: '/auth/user-info',
|
|
71
70
|
method: 'get',
|
|
72
|
-
params
|
|
73
|
-
id
|
|
74
|
-
}
|
|
71
|
+
params
|
|
75
72
|
})
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
// 测试 获取全量数据
|
|
79
|
-
export const FullDataApiTest = (id: string): Promise<
|
|
76
|
+
export const FullDataApiTest = (params: { id: string }): Promise<IFullResponse<IUser>> => {
|
|
80
77
|
return axiosRequest({
|
|
81
78
|
url: '/auth/user-info',
|
|
82
79
|
method: 'get',
|
|
83
|
-
params
|
|
84
|
-
id
|
|
85
|
-
},
|
|
80
|
+
params,
|
|
86
81
|
returnFullResponse: true // 需要返回完整响应数据时使用该选项
|
|
87
82
|
})
|
|
88
83
|
}
|
|
@@ -154,7 +149,7 @@ export const UpdateProjectApi = (data: Project) => {
|
|
|
154
149
|
/**
|
|
155
150
|
* 删除数据
|
|
156
151
|
*/
|
|
157
|
-
export const DeleteProjectApi = (id:
|
|
152
|
+
export const DeleteProjectApi = (id: number) => {
|
|
158
153
|
return axiosRequest({
|
|
159
154
|
url: `/demos/delete/${id}`,
|
|
160
155
|
method: 'delete'
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @Desc. : axios 网络请求实例
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { IBusinessRequest,
|
|
9
|
+
import type { IBusinessRequest, IFullResponse } from '@jnrs/shared'
|
|
10
10
|
import { createAxiosInstance } from '@jnrs/shared/request'
|
|
11
11
|
import { useMockStore } from '@jnrs/vue-core/pinia'
|
|
12
12
|
import { useAuthStore } from '@/stores'
|
|
@@ -32,7 +32,7 @@ const axiosInstance = createAxiosInstance({
|
|
|
32
32
|
* @param options 请求配置项
|
|
33
33
|
* @returns Promise<T> 响应数据
|
|
34
34
|
*/
|
|
35
|
-
const axiosRequest = <T =
|
|
35
|
+
const axiosRequest = <T = IFullResponse>(options: IBusinessRequest): Promise<T> => {
|
|
36
36
|
if (!axiosInstance) {
|
|
37
37
|
throw new Error('请先调用 createRequest 初始化 axios 实例')
|
|
38
38
|
}
|
|
@@ -1,58 +1,177 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @Author : TanRui
|
|
3
|
+
* @WeChat : Tan578853789
|
|
4
|
+
* @File : useCrud.ts
|
|
5
|
+
* @Date : 2026/04/16
|
|
6
|
+
* @Desc. : 通用 CRUD 组合式 API - 适用于所有业务模块
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ref, nextTick } from 'vue'
|
|
10
|
+
import type { FormInstance, FormRules } from 'element-plus'
|
|
3
11
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
4
12
|
import { debounce } from '@jnrs/shared/lodash'
|
|
5
13
|
import { objectMatchAssign } from '@jnrs/shared'
|
|
14
|
+
import { useI18n } from '@/locales'
|
|
6
15
|
import type { IPagination, IPageTableData } from '@/types'
|
|
7
16
|
|
|
17
|
+
/**
|
|
18
|
+
* CRUD 配置选项
|
|
19
|
+
*/
|
|
8
20
|
interface CrudOptions<TItem, TForm, TQuery> {
|
|
9
|
-
//
|
|
10
|
-
|
|
21
|
+
// ========== 必需配置 ==========
|
|
22
|
+
/** 表单默认值工厂函数 */
|
|
23
|
+
defaultForm?: () => TForm
|
|
24
|
+
/** 列表查询 API */
|
|
25
|
+
listApi?: (params: Partial<IPagination> & TQuery) => Promise<IPageTableData<TItem> | TItem[]>
|
|
26
|
+
/** 保存 API(新增/编辑) */
|
|
27
|
+
saveApi?: (data: TForm) => Promise<unknown>
|
|
28
|
+
|
|
29
|
+
// ========== 可选配置 ==========
|
|
30
|
+
/** 查询表单默认值工厂函数 */
|
|
11
31
|
defaultQuery?: () => TQuery
|
|
12
|
-
|
|
13
|
-
listApi: (params: Partial<IPagination> & TQuery) => Promise<IPageTableData<TItem> | TItem[]>
|
|
14
|
-
saveApi: (data: TForm) => Promise<unknown>
|
|
32
|
+
/** 删除 API */
|
|
15
33
|
deleteApi?: (id: number) => Promise<unknown>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
34
|
+
/** 是否启用分页,默认 true */
|
|
35
|
+
usePagination?: boolean
|
|
36
|
+
/** 初始页码,默认 1 */
|
|
37
|
+
initialPageNo?: number
|
|
38
|
+
/** 初始每页条数,默认 20 */
|
|
39
|
+
initialPageSize?: number
|
|
40
|
+
/** 防抖延迟(毫秒),默认 300 */
|
|
41
|
+
debounceDelay?: number
|
|
42
|
+
|
|
43
|
+
// ========== 数据处理钩子 ==========
|
|
44
|
+
/** 列表项数据转换函数(用于处理附件等字段映射) */
|
|
19
45
|
transformItem?: (item: TItem) => TItem
|
|
46
|
+
/** 编辑前数据处理(用于 Select 组件等特殊字段回填) */
|
|
20
47
|
beforeEdit?: (row: TItem, form: TForm) => TForm
|
|
48
|
+
/** 提交前数据处理(用于 Select 组件等特殊字段转换) */
|
|
49
|
+
beforeSubmit?: (form: TForm) => TForm
|
|
50
|
+
|
|
51
|
+
// ========== UI 配置 ==========
|
|
52
|
+
/** 成功提示消息 */
|
|
53
|
+
successMessage?: {
|
|
54
|
+
save?: string
|
|
55
|
+
delete?: string
|
|
56
|
+
}
|
|
57
|
+
/** 删除确认配置 */
|
|
58
|
+
deleteConfirm?: {
|
|
59
|
+
title?: string
|
|
60
|
+
message?: string
|
|
61
|
+
confirmText?: string
|
|
62
|
+
cancelText?: string
|
|
63
|
+
}
|
|
21
64
|
}
|
|
22
65
|
|
|
23
|
-
|
|
66
|
+
/**
|
|
67
|
+
* 通用 CRUD 组合式 API
|
|
68
|
+
*
|
|
69
|
+
* 适用于所有具有增删改查功能的业务模块,提供统一的表格管理、表单编辑、删除确认等功能。
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // 基础用法
|
|
74
|
+
* const {
|
|
75
|
+
* loading, tableData, total, pagination, queryForm,
|
|
76
|
+
* dialogRef, formRef, form,
|
|
77
|
+
* getList, openCreate, openEdit, submitForm, handleDelete
|
|
78
|
+
* } = useCrud<TItem, TForm, TQuery>({
|
|
79
|
+
* defaultForm: () => ({ id: '', name: '' }),
|
|
80
|
+
* listApi: YourListApi,
|
|
81
|
+
* saveApi: YourSaveApi,
|
|
82
|
+
* deleteApi: YourDeleteApi
|
|
83
|
+
* })
|
|
84
|
+
*
|
|
85
|
+
* // 高级用法:带数据转换和钩子
|
|
86
|
+
* const { ... } = useCrud<TItem, TForm, TQuery>({
|
|
87
|
+
* defaultForm: () => ({ ... }),
|
|
88
|
+
* defaultQuery: () => ({ code: '', status: undefined }),
|
|
89
|
+
* listApi: DeviceListApi,
|
|
90
|
+
* saveApi: SaveDeviceApi,
|
|
91
|
+
* deleteApi: DeleteDeviceApi,
|
|
92
|
+
*
|
|
93
|
+
* // 数据转换:处理附件字段
|
|
94
|
+
* transformItem: (item) => ({
|
|
95
|
+
* ...item,
|
|
96
|
+
* images: item.imageDocument?.attachments
|
|
97
|
+
* }),
|
|
98
|
+
*
|
|
99
|
+
* // 编辑前:Select 组件回填
|
|
100
|
+
* beforeEdit: (row, form) => ({
|
|
101
|
+
* ...form,
|
|
102
|
+
* categoryId: { id: row.categoryId, name: row.categoryName }
|
|
103
|
+
* }),
|
|
104
|
+
*
|
|
105
|
+
* // 提交前:Select 组件转换
|
|
106
|
+
* beforeSubmit: (form) => ({
|
|
107
|
+
* ...form,
|
|
108
|
+
* categoryId: extractFieldId(form.categoryId, 'id')
|
|
109
|
+
* })
|
|
110
|
+
* })
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function useCrud<TItem, TForm, TQuery>(options: CrudOptions<TItem, TForm, TQuery>) {
|
|
114
|
+
const { t: $t } = useI18n()
|
|
115
|
+
|
|
24
116
|
const {
|
|
25
|
-
defaultForm,
|
|
26
|
-
defaultQuery,
|
|
117
|
+
defaultForm = () => ({}) as TForm,
|
|
118
|
+
defaultQuery = () => ({}) as TQuery,
|
|
27
119
|
listApi,
|
|
28
120
|
saveApi,
|
|
29
121
|
deleteApi,
|
|
30
|
-
|
|
122
|
+
usePagination = true,
|
|
123
|
+
initialPageNo = 1,
|
|
124
|
+
initialPageSize = 20,
|
|
125
|
+
debounceDelay = 300,
|
|
31
126
|
transformItem,
|
|
32
|
-
beforeEdit
|
|
127
|
+
beforeEdit,
|
|
128
|
+
beforeSubmit,
|
|
129
|
+
successMessage = {
|
|
130
|
+
save: '数据已保存',
|
|
131
|
+
delete: '删除成功'
|
|
132
|
+
},
|
|
133
|
+
deleteConfirm = {
|
|
134
|
+
title: '确定要删除吗?',
|
|
135
|
+
message: '<p style="color: #f30">请注意,您尚未保存的数据将会丢失。</p>',
|
|
136
|
+
confirmText: $t('global.action.confirm'),
|
|
137
|
+
cancelText: $t('global.action.cancel')
|
|
138
|
+
}
|
|
33
139
|
} = options
|
|
34
140
|
|
|
35
|
-
// 列表状态
|
|
141
|
+
// ==================== 列表状态 ====================
|
|
36
142
|
const loading = ref(false)
|
|
37
|
-
const tableData = ref<TItem[]>([])
|
|
143
|
+
const tableData = ref<TItem[]>([])
|
|
38
144
|
const total = ref(0)
|
|
39
|
-
const pagination = ref<IPagination>({
|
|
40
|
-
|
|
145
|
+
const pagination = ref<IPagination>({
|
|
146
|
+
pageNo: initialPageNo,
|
|
147
|
+
pageSize: initialPageSize
|
|
148
|
+
})
|
|
149
|
+
const queryForm = ref<TQuery>(defaultQuery?.())
|
|
41
150
|
|
|
42
|
-
// 表单状态
|
|
151
|
+
// ==================== 表单状态 ====================
|
|
43
152
|
const dialogRef = ref()
|
|
44
153
|
const formRef = ref<FormInstance>()
|
|
45
|
-
const form = ref(defaultForm())
|
|
154
|
+
const form = ref(defaultForm())
|
|
155
|
+
|
|
156
|
+
// ==================== 核心方法 ====================
|
|
46
157
|
|
|
47
|
-
|
|
158
|
+
/**
|
|
159
|
+
* 获取列表数据(带防抖)
|
|
160
|
+
*/
|
|
48
161
|
const getList = debounce(async () => {
|
|
162
|
+
if (!listApi) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
49
166
|
loading.value = true
|
|
50
167
|
try {
|
|
51
168
|
const params = {
|
|
52
|
-
...(
|
|
169
|
+
...(usePagination ? pagination.value : {}),
|
|
53
170
|
...queryForm.value
|
|
54
171
|
} as Partial<IPagination> & TQuery
|
|
172
|
+
|
|
55
173
|
const res = await listApi(params)
|
|
174
|
+
|
|
56
175
|
// 支持返回数组或 { list, count } 格式
|
|
57
176
|
if (Array.isArray(res)) {
|
|
58
177
|
tableData.value = transformItem ? res.map(transformItem) : res
|
|
@@ -61,78 +180,128 @@ export function useCrud<TItem, TForm, TQuery = object>(options: CrudOptions<TIte
|
|
|
61
180
|
tableData.value = transformItem ? res.list.map(transformItem) : res.list
|
|
62
181
|
total.value = res.count ?? res.list.length
|
|
63
182
|
}
|
|
64
|
-
} catch (
|
|
65
|
-
console.error(
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error('获取列表失败:', error)
|
|
66
185
|
} finally {
|
|
67
186
|
loading.value = false
|
|
68
187
|
}
|
|
69
|
-
},
|
|
188
|
+
}, debounceDelay)
|
|
70
189
|
|
|
71
|
-
|
|
190
|
+
/**
|
|
191
|
+
* 打开新增弹窗
|
|
192
|
+
*/
|
|
72
193
|
const openCreate = () => {
|
|
73
194
|
dialogRef.value?.open()
|
|
74
|
-
|
|
195
|
+
nextTick(() => {
|
|
75
196
|
formRef.value?.resetFields()
|
|
76
|
-
form.value = defaultForm()
|
|
197
|
+
form.value = defaultForm()
|
|
77
198
|
})
|
|
78
199
|
}
|
|
79
200
|
|
|
80
|
-
|
|
201
|
+
/**
|
|
202
|
+
* 打开编辑弹窗
|
|
203
|
+
* @param row 要编辑的行数据
|
|
204
|
+
*/
|
|
81
205
|
const openEdit = (row: TItem) => {
|
|
82
206
|
dialogRef.value?.open()
|
|
83
|
-
|
|
207
|
+
nextTick(() => {
|
|
84
208
|
formRef.value?.resetFields()
|
|
85
|
-
const matched = objectMatchAssign(
|
|
86
|
-
|
|
87
|
-
row as Record<string, unknown>
|
|
88
|
-
) as TForm
|
|
89
|
-
form.value = (beforeEdit ? beforeEdit(row, matched) : matched) as TForm
|
|
209
|
+
const matched = objectMatchAssign(defaultForm(), row)
|
|
210
|
+
form.value = beforeEdit ? beforeEdit(row, matched) : matched
|
|
90
211
|
})
|
|
91
212
|
}
|
|
92
213
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
214
|
+
/**
|
|
215
|
+
* 提交表单(新增或编辑)
|
|
216
|
+
* @param rules 表单验证规则(可选)
|
|
217
|
+
*/
|
|
218
|
+
const submitForm = async (rules?: FormRules) => {
|
|
219
|
+
// 如果有传入 rules,则进行表单验证
|
|
220
|
+
if (rules && formRef.value) {
|
|
221
|
+
const valid = await formRef.value.validate().catch(() => false)
|
|
222
|
+
if (!valid) return
|
|
223
|
+
}
|
|
97
224
|
|
|
98
225
|
loading.value = true
|
|
99
226
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
227
|
+
// 提交前数据处理
|
|
228
|
+
const submitData = beforeSubmit ? beforeSubmit(form.value) : form.value
|
|
229
|
+
|
|
230
|
+
await saveApi?.(submitData)
|
|
231
|
+
|
|
232
|
+
ElMessage({
|
|
233
|
+
message: successMessage.save,
|
|
234
|
+
grouping: true,
|
|
235
|
+
showClose: true,
|
|
236
|
+
type: 'success'
|
|
237
|
+
})
|
|
238
|
+
|
|
102
239
|
dialogRef.value?.close()
|
|
103
240
|
getList()
|
|
104
|
-
} catch (
|
|
105
|
-
console.error(
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error('保存失败:', error)
|
|
106
243
|
} finally {
|
|
107
244
|
loading.value = false
|
|
108
245
|
}
|
|
109
246
|
}
|
|
110
247
|
|
|
111
|
-
|
|
248
|
+
/**
|
|
249
|
+
* 删除数据
|
|
250
|
+
* @param id 要删除的数据 ID
|
|
251
|
+
*/
|
|
112
252
|
const handleDelete = async (id: number) => {
|
|
113
|
-
if (!deleteApi)
|
|
253
|
+
if (!deleteApi) {
|
|
254
|
+
console.warn('未配置 deleteApi')
|
|
255
|
+
return
|
|
256
|
+
}
|
|
114
257
|
|
|
115
258
|
try {
|
|
116
|
-
await ElMessageBox.confirm(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
259
|
+
await ElMessageBox.confirm(deleteConfirm.message, deleteConfirm.title, {
|
|
260
|
+
dangerouslyUseHTMLString: true,
|
|
261
|
+
confirmButtonText: deleteConfirm.confirmText,
|
|
262
|
+
cancelButtonText: deleteConfirm.cancelText,
|
|
263
|
+
confirmButtonType: 'danger'
|
|
120
264
|
})
|
|
121
265
|
|
|
122
266
|
loading.value = true
|
|
123
267
|
await deleteApi(id)
|
|
124
|
-
|
|
268
|
+
|
|
269
|
+
ElMessage({
|
|
270
|
+
message: successMessage.delete,
|
|
271
|
+
grouping: true,
|
|
272
|
+
showClose: true,
|
|
273
|
+
type: 'success'
|
|
274
|
+
})
|
|
275
|
+
|
|
125
276
|
getList()
|
|
126
|
-
} catch (
|
|
127
|
-
if (
|
|
128
|
-
console.error(
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (error !== 'cancel') {
|
|
279
|
+
console.error('删除失败:', error)
|
|
129
280
|
}
|
|
130
281
|
} finally {
|
|
131
282
|
loading.value = false
|
|
132
283
|
}
|
|
133
284
|
}
|
|
134
285
|
|
|
286
|
+
/**
|
|
287
|
+
* 处理表格选择变化
|
|
288
|
+
*/
|
|
289
|
+
const handleSelectionChange = (rows: TItem[]) => {
|
|
290
|
+
console.log('选中的行:', rows)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 重置查询条件
|
|
295
|
+
*/
|
|
296
|
+
const resetQuery = () => {
|
|
297
|
+
queryForm.value = defaultQuery?.()
|
|
298
|
+
pagination.value.pageNo = initialPageNo
|
|
299
|
+
getList()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ==================== 返回 ====================
|
|
135
303
|
return {
|
|
304
|
+
// 状态
|
|
136
305
|
loading,
|
|
137
306
|
tableData,
|
|
138
307
|
total,
|
|
@@ -141,10 +310,14 @@ export function useCrud<TItem, TForm, TQuery = object>(options: CrudOptions<TIte
|
|
|
141
310
|
dialogRef,
|
|
142
311
|
formRef,
|
|
143
312
|
form,
|
|
313
|
+
|
|
314
|
+
// 方法
|
|
144
315
|
getList,
|
|
145
316
|
openCreate,
|
|
146
317
|
openEdit,
|
|
147
318
|
submitForm,
|
|
148
|
-
handleDelete
|
|
319
|
+
handleDelete,
|
|
320
|
+
handleSelectionChange,
|
|
321
|
+
resetQuery
|
|
149
322
|
}
|
|
150
323
|
}
|
|
@@ -17,7 +17,7 @@ import { FileApi } from '@/api/common'
|
|
|
17
17
|
*/
|
|
18
18
|
export const objectToFormData = (obj: object): FormData => {
|
|
19
19
|
// 根据后端返回结果处理的映射关系
|
|
20
|
-
const
|
|
20
|
+
const transMap = {
|
|
21
21
|
// 图片
|
|
22
22
|
newImageFiles: { finallyKey: 'originImageNames', valueKey: 'uniqueFileName' },
|
|
23
23
|
// 通用附件
|
|
@@ -27,7 +27,7 @@ export const objectToFormData = (obj: object): FormData => {
|
|
|
27
27
|
// 数据库
|
|
28
28
|
databaseFile: { finallyKey: 'databaseFile', valueKey: 'uniqueFileName' }
|
|
29
29
|
}
|
|
30
|
-
return _objectToFormData(obj as Record<string, unknown>,
|
|
30
|
+
return _objectToFormData(obj as Record<string, unknown>, transMap)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { FormRules } from 'element-plus'
|
|
3
|
+
import { onActivated, ref } from 'vue'
|
|
4
|
+
import { Plus } from '@element-plus/icons-vue'
|
|
5
|
+
import { useRoute } from '@jnrs/vue-core/router'
|
|
6
|
+
import { dateFormatsToObject, verifyNumberGtZero } from '@jnrs/shared'
|
|
7
|
+
import { getDictList, downloadFile, extractFieldId } from '@/utils'
|
|
8
|
+
import {
|
|
9
|
+
ProjectListApi,
|
|
10
|
+
EditProjectApi,
|
|
11
|
+
DeleteProjectApi,
|
|
12
|
+
DownloadTemplateApi,
|
|
13
|
+
ImportDataApi,
|
|
14
|
+
ExportApi
|
|
15
|
+
} from '@/api/demos/index'
|
|
16
|
+
import type { Project, EditProject, ProjectQuery } from '@/api/demos/index'
|
|
17
|
+
import { JnDialog, JnDatetime, JnPagination, JnFileUpload, JnTable, JnImportAndExport } from '@jnrs/vue-core/components'
|
|
18
|
+
import ImageView from '@/components/common/ImageView.vue'
|
|
19
|
+
import PdfView from '@/components/common/PdfView.vue'
|
|
20
|
+
import DictTag from '@/components/common/DictTag.vue'
|
|
21
|
+
import SelectManager from '@/components/select/SelectManager.vue'
|
|
22
|
+
import { useCrud } from '@/composables/useCrud'
|
|
23
|
+
|
|
24
|
+
const route = useRoute()
|
|
25
|
+
|
|
26
|
+
// 使用通用 CRUD 组合式 API(让 TypeScript 自动推断类型)
|
|
27
|
+
const {
|
|
28
|
+
loading,
|
|
29
|
+
tableData,
|
|
30
|
+
total,
|
|
31
|
+
pagination,
|
|
32
|
+
queryForm,
|
|
33
|
+
formRef,
|
|
34
|
+
dialogRef,
|
|
35
|
+
form,
|
|
36
|
+
getList,
|
|
37
|
+
openCreate,
|
|
38
|
+
openEdit,
|
|
39
|
+
handleDelete: crudHandleDelete,
|
|
40
|
+
submitForm: crudSubmitForm,
|
|
41
|
+
handleSelectionChange
|
|
42
|
+
} = useCrud<Project, EditProject, ProjectQuery>({
|
|
43
|
+
// 表单默认值
|
|
44
|
+
defaultForm: () => ({
|
|
45
|
+
id: 0,
|
|
46
|
+
name: '',
|
|
47
|
+
projectType: undefined,
|
|
48
|
+
managerId: undefined,
|
|
49
|
+
budget: 0,
|
|
50
|
+
plannedStartDate: '',
|
|
51
|
+
description: '',
|
|
52
|
+
newImageFiles: [],
|
|
53
|
+
newAttachmentFile: []
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
// 查询表单默认值
|
|
57
|
+
defaultQuery: () => ({
|
|
58
|
+
pageNo: 1,
|
|
59
|
+
pageSize: 20,
|
|
60
|
+
code: '',
|
|
61
|
+
projectType: undefined,
|
|
62
|
+
manager: '',
|
|
63
|
+
plannedStartDate: ''
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
// API
|
|
67
|
+
listApi: ProjectListApi,
|
|
68
|
+
saveApi: EditProjectApi,
|
|
69
|
+
deleteApi: DeleteProjectApi,
|
|
70
|
+
|
|
71
|
+
// 数据转换:处理附件字段映射
|
|
72
|
+
transformItem: (item: Project) => ({
|
|
73
|
+
...item,
|
|
74
|
+
newImageFiles: item.imageDocument?.attachments,
|
|
75
|
+
newAttachmentFile: item.attachmentDocument?.attachments
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
// 编辑前处理:Select 组件数据回填
|
|
79
|
+
beforeEdit: (row: Project, form: EditProject) => {
|
|
80
|
+
if ('managerId' in row && 'manager' in row) {
|
|
81
|
+
return {
|
|
82
|
+
...form,
|
|
83
|
+
managerId: {
|
|
84
|
+
managerId: row.managerId,
|
|
85
|
+
manager: row.manager
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return form
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// 提交前处理:Select 组件值转换
|
|
93
|
+
beforeSubmit: (form: EditProject) => ({
|
|
94
|
+
...form,
|
|
95
|
+
managerId: extractFieldId(form.managerId, 'managerId') as number
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// 表单验证规则
|
|
100
|
+
const rules = ref<FormRules>({
|
|
101
|
+
name: [{ required: true, message: '请输入', trigger: 'change' }],
|
|
102
|
+
projectType: [{ required: true, message: '请选择', trigger: 'change' }],
|
|
103
|
+
plannedStartDate: [{ required: true, message: '请选择', trigger: 'change' }],
|
|
104
|
+
newImageFiles: [{ required: true, message: '请上传', trigger: 'change' }],
|
|
105
|
+
managerId: [{ required: true, message: '请选择', trigger: 'change' }],
|
|
106
|
+
budget: [
|
|
107
|
+
{ required: true, message: '请输入', trigger: 'change' },
|
|
108
|
+
{
|
|
109
|
+
validator: verifyNumberGtZero,
|
|
110
|
+
trigger: 'change'
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// 包装删除方法,传递正确的 ID 类型
|
|
116
|
+
const handleDelete = (row: Project) => {
|
|
117
|
+
crudHandleDelete(row.id)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 包装提交方法,传入验证规则
|
|
121
|
+
const submitForm = () => {
|
|
122
|
+
crudSubmitForm(rules.value)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
onActivated(() => {
|
|
126
|
+
console.log(route.meta) // 获取路由元信息
|
|
127
|
+
getList()
|
|
128
|
+
})
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
<template>
|
|
132
|
+
<!-- 编辑弹窗 -->
|
|
133
|
+
<JnDialog ref="dialogRef" title="项目管理" width="600px" :close-on-click-modal="false" :close-on-press-escape="true">
|
|
134
|
+
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto" v-loading="loading">
|
|
135
|
+
<el-form-item prop="id"></el-form-item>
|
|
136
|
+
<el-form-item label="项目名称" prop="name">
|
|
137
|
+
<el-input v-model.trim="form.name" maxlength="20" show-word-limit />
|
|
138
|
+
</el-form-item>
|
|
139
|
+
<el-form-item label="类型" prop="projectType">
|
|
140
|
+
<el-select v-model="form.projectType" filterable placeholder="">
|
|
141
|
+
<el-option
|
|
142
|
+
:label="item.label"
|
|
143
|
+
:value="item.value"
|
|
144
|
+
v-for="item in getDictList('projectType')"
|
|
145
|
+
:key="item.value"
|
|
146
|
+
/>
|
|
147
|
+
</el-select>
|
|
148
|
+
</el-form-item>
|
|
149
|
+
<el-form-item label="项目经理" prop="managerId">
|
|
150
|
+
<SelectManager
|
|
151
|
+
v-model="form.managerId"
|
|
152
|
+
:formRef="formRef"
|
|
153
|
+
validateFieldName="managerId"
|
|
154
|
+
:limit="1"
|
|
155
|
+
:simpleValue="false"
|
|
156
|
+
></SelectManager>
|
|
157
|
+
</el-form-item>
|
|
158
|
+
<el-form-item label="预算" prop="budget">
|
|
159
|
+
<el-input-number v-model="form.budget" :min="0" :precision="2" :step="10000" controls-position="right">
|
|
160
|
+
<template #prefix>
|
|
161
|
+
<span>¥</span>
|
|
162
|
+
</template>
|
|
163
|
+
</el-input-number>
|
|
164
|
+
</el-form-item>
|
|
165
|
+
<el-form-item label="计划开始时间" prop="plannedStartDate">
|
|
166
|
+
<el-date-picker
|
|
167
|
+
v-model="form.plannedStartDate"
|
|
168
|
+
type="datetime"
|
|
169
|
+
format="YYYY-MM-DD HH:mm:ss"
|
|
170
|
+
value-format="YYYY-MM-DD HH:mm:ss"
|
|
171
|
+
:disabled-date="
|
|
172
|
+
(time: Date) => {
|
|
173
|
+
return time.getTime() < Date.now() - 86400000
|
|
174
|
+
}
|
|
175
|
+
"
|
|
176
|
+
/>
|
|
177
|
+
</el-form-item>
|
|
178
|
+
<el-form-item label="描述" prop="description">
|
|
179
|
+
<el-input
|
|
180
|
+
v-model="form.description"
|
|
181
|
+
:autosize="{ minRows: 2, maxRows: 4 }"
|
|
182
|
+
type="textarea"
|
|
183
|
+
maxlength="200"
|
|
184
|
+
show-word-limit
|
|
185
|
+
/>
|
|
186
|
+
</el-form-item>
|
|
187
|
+
<el-form-item label="上传图片" prop="newImageFiles">
|
|
188
|
+
<JnFileUpload
|
|
189
|
+
v-model="form.newImageFiles"
|
|
190
|
+
:formRef="formRef"
|
|
191
|
+
validateFieldName="newImageFiles"
|
|
192
|
+
accept=".png,.jpg,.bmp,.gif"
|
|
193
|
+
:fileSizeMb="50"
|
|
194
|
+
:limit="3"
|
|
195
|
+
drag
|
|
196
|
+
:downloadFileFn="(file) => downloadFile(file.uniqueFileName, file.fileName)"
|
|
197
|
+
/>
|
|
198
|
+
</el-form-item>
|
|
199
|
+
<el-form-item label="上传文件" prop="newAttachmentFile">
|
|
200
|
+
<JnFileUpload v-model="form.newAttachmentFile" accept=".pdf" :limit="1" />
|
|
201
|
+
</el-form-item>
|
|
202
|
+
</el-form>
|
|
203
|
+
<template #footer>
|
|
204
|
+
<el-button type="success" icon="Select" :loading="loading" @click="submitForm()">提交</el-button>
|
|
205
|
+
</template>
|
|
206
|
+
</JnDialog>
|
|
207
|
+
|
|
208
|
+
<el-card v-loading="loading">
|
|
209
|
+
<template #header>
|
|
210
|
+
<div style="display: flex; justify-content: space-between; align-items: center">
|
|
211
|
+
<span>项目管理 - 组合式</span>
|
|
212
|
+
<div style="display: flex; justify-content: space-between; align-items: center">
|
|
213
|
+
<JnImportAndExport
|
|
214
|
+
:importTemplateApi="DownloadTemplateApi"
|
|
215
|
+
importBtnName="导入项目"
|
|
216
|
+
:importApi="ImportDataApi"
|
|
217
|
+
exportBtnName="导出项目"
|
|
218
|
+
:exportApi="ExportApi"
|
|
219
|
+
:exportParams="{
|
|
220
|
+
...dateFormatsToObject(queryForm.plannedStartDate || '')
|
|
221
|
+
}"
|
|
222
|
+
:exportDynamicParamsConfig="{
|
|
223
|
+
label: '项目编号',
|
|
224
|
+
prop: 'code'
|
|
225
|
+
}"
|
|
226
|
+
:exportDisabled="tableData.length === 0"
|
|
227
|
+
size="small"
|
|
228
|
+
@change="getList()"
|
|
229
|
+
/>
|
|
230
|
+
<el-button type="primary" size="small" :icon="Plus" @click="openCreate()" style="margin-left: 12px">
|
|
231
|
+
新增
|
|
232
|
+
</el-button>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
</template>
|
|
236
|
+
|
|
237
|
+
<!-- 查询条件 -->
|
|
238
|
+
<el-form :model="queryForm" size="small" inline v-loading="loading">
|
|
239
|
+
<el-form-item label="项目编号" prop="code">
|
|
240
|
+
<el-input v-model="queryForm.code" clearable style="width: 200px">
|
|
241
|
+
<template #append>
|
|
242
|
+
<el-button icon="Search" @click="getList()" />
|
|
243
|
+
</template>
|
|
244
|
+
</el-input>
|
|
245
|
+
</el-form-item>
|
|
246
|
+
<el-form-item label="项目名称" prop="projectType">
|
|
247
|
+
<el-select
|
|
248
|
+
v-model="queryForm.projectType"
|
|
249
|
+
filterable
|
|
250
|
+
placeholder=""
|
|
251
|
+
clearable
|
|
252
|
+
style="width: 200px"
|
|
253
|
+
@change="getList()"
|
|
254
|
+
>
|
|
255
|
+
<el-option
|
|
256
|
+
:label="item.label"
|
|
257
|
+
:value="item.value"
|
|
258
|
+
v-for="item in getDictList('projectType')"
|
|
259
|
+
:key="item.value"
|
|
260
|
+
/>
|
|
261
|
+
</el-select>
|
|
262
|
+
</el-form-item>
|
|
263
|
+
<el-form-item label="项目经理" prop="manager">
|
|
264
|
+
<SelectManager
|
|
265
|
+
v-model="queryForm.manager"
|
|
266
|
+
:initialParams="{ role: 1 }"
|
|
267
|
+
size="small"
|
|
268
|
+
width="200px"
|
|
269
|
+
@change="getList()"
|
|
270
|
+
></SelectManager>
|
|
271
|
+
</el-form-item>
|
|
272
|
+
<el-form-item label="计划开始时间" prop="plannedStartDate">
|
|
273
|
+
<el-date-picker
|
|
274
|
+
v-model="queryForm.plannedStartDate"
|
|
275
|
+
value-format="YYYY-MM-DD"
|
|
276
|
+
size="small"
|
|
277
|
+
style="width: 200px"
|
|
278
|
+
@change="getList()"
|
|
279
|
+
/>
|
|
280
|
+
</el-form-item>
|
|
281
|
+
</el-form>
|
|
282
|
+
|
|
283
|
+
<!-- 数据列表 -->
|
|
284
|
+
<JnTable
|
|
285
|
+
:data="tableData"
|
|
286
|
+
:pagination="pagination"
|
|
287
|
+
:autoHeight="true"
|
|
288
|
+
:showScrollbar="true"
|
|
289
|
+
:showIndexColumn="true"
|
|
290
|
+
:showSelectionColumn="true"
|
|
291
|
+
:showMouseSelection="true"
|
|
292
|
+
@selection-change="handleSelectionChange"
|
|
293
|
+
>
|
|
294
|
+
<el-table-column prop="code" label="项目编号" min-width="200" sortable show-overflow-tooltip />
|
|
295
|
+
<el-table-column prop="name" label="项目名称" min-width="200" sortable show-overflow-tooltip />
|
|
296
|
+
<el-table-column prop="program" label="项目集名称" min-width="200" sortable show-overflow-tooltip />
|
|
297
|
+
<el-table-column prop="projectType" label="项目类型" width="100" align="center" sortable>
|
|
298
|
+
<template #default="{ row }">
|
|
299
|
+
<DictTag dictName="projectType" :value="row.projectType" />
|
|
300
|
+
</template>
|
|
301
|
+
</el-table-column>
|
|
302
|
+
<el-table-column prop="manager" label="项目经理" width="100" align="center" sortable />
|
|
303
|
+
<el-table-column prop="budget" label="预算(¥)" width="100" align="center" sortable>
|
|
304
|
+
<template #default="{ row }">
|
|
305
|
+
{{ row.budget }}
|
|
306
|
+
</template>
|
|
307
|
+
</el-table-column>
|
|
308
|
+
<el-table-column prop="plannedStartDate" label="计划开始时间" width="130" align="center" sortable>
|
|
309
|
+
<template #default="{ row }">
|
|
310
|
+
<JnDatetime :value="row.plannedStartDate" />
|
|
311
|
+
</template>
|
|
312
|
+
</el-table-column>
|
|
313
|
+
<el-table-column prop="plannedFinishDate" label="计划完成日期" width="130" align="center" sortable>
|
|
314
|
+
<template #default="{ row }">
|
|
315
|
+
{{ row.plannedFinishDate }}
|
|
316
|
+
</template>
|
|
317
|
+
</el-table-column>
|
|
318
|
+
<el-table-column prop="progress" label="进度" width="100" align="center" sortable>
|
|
319
|
+
<template #default="{ row }">
|
|
320
|
+
<span>{{ row.progress }}</span>
|
|
321
|
+
</template>
|
|
322
|
+
</el-table-column>
|
|
323
|
+
<el-table-column prop="status" label="状态" width="100" align="center" sortable>
|
|
324
|
+
<template #default="{ row }">
|
|
325
|
+
<DictTag dictName="status" :value="row.status" :showColor="false" />
|
|
326
|
+
</template>
|
|
327
|
+
</el-table-column>
|
|
328
|
+
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
|
|
329
|
+
<el-table-column prop="imageDocument" label="图片" width="100" align="center" sortable>
|
|
330
|
+
<template #default="{ row }">
|
|
331
|
+
<ImageView :loadKeys="row.newImageFiles" preview maxHeight="50px" />
|
|
332
|
+
</template>
|
|
333
|
+
</el-table-column>
|
|
334
|
+
<el-table-column prop="attachmentDocument" label="附件" width="100" align="center" sortable>
|
|
335
|
+
<template #default="{ row }">
|
|
336
|
+
<PdfView :loadKeys="row.newAttachmentFile" isPdf />
|
|
337
|
+
</template>
|
|
338
|
+
</el-table-column>
|
|
339
|
+
<el-table-column label="操作" width="120" align="center" fixed="right">
|
|
340
|
+
<template #default="{ row }">
|
|
341
|
+
<el-button link type="primary" @click.stop="openEdit(row)">编辑</el-button>
|
|
342
|
+
<el-button link type="danger" @click.stop="handleDelete(row)">删除</el-button>
|
|
343
|
+
</template>
|
|
344
|
+
</el-table-column>
|
|
345
|
+
</JnTable>
|
|
346
|
+
|
|
347
|
+
<!-- 表格分页 -->
|
|
348
|
+
<JnPagination :total="total" v-model="pagination" @change="getList" />
|
|
349
|
+
</el-card>
|
|
350
|
+
</template>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onActivated } from 'vue'
|
|
3
|
+
import { ProjectListApi } from '@/api/demos/index'
|
|
4
|
+
import { JnDatetime, JnPagination, JnTable } from '@jnrs/vue-core/components'
|
|
5
|
+
import ImageView from '@/components/common/ImageView.vue'
|
|
6
|
+
import PdfView from '@/components/common/PdfView.vue'
|
|
7
|
+
import DictTag from '@/components/common/DictTag.vue'
|
|
8
|
+
import { useCrud } from '@/composables/useCrud'
|
|
9
|
+
|
|
10
|
+
const { loading, tableData, total, pagination, getList } = useCrud({
|
|
11
|
+
listApi: ProjectListApi
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
onActivated(() => {
|
|
15
|
+
getList()
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<el-card v-loading="loading">
|
|
21
|
+
<template #header>
|
|
22
|
+
<div style="display: flex; justify-content: space-between; align-items: center">
|
|
23
|
+
<span>项目列表 - 组合式</span>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<!-- 数据列表 -->
|
|
28
|
+
<JnTable
|
|
29
|
+
:data="tableData"
|
|
30
|
+
:pagination="pagination"
|
|
31
|
+
:autoHeight="true"
|
|
32
|
+
:showScrollbar="true"
|
|
33
|
+
:showIndexColumn="true"
|
|
34
|
+
>
|
|
35
|
+
<el-table-column prop="code" label="项目编号" min-width="200" sortable show-overflow-tooltip />
|
|
36
|
+
<el-table-column prop="name" label="项目名称" min-width="200" sortable show-overflow-tooltip />
|
|
37
|
+
<el-table-column prop="program" label="项目集名称" min-width="200" sortable show-overflow-tooltip />
|
|
38
|
+
<el-table-column prop="projectType" label="项目类型" width="100" align="center" sortable>
|
|
39
|
+
<template #default="{ row }">
|
|
40
|
+
<DictTag dictName="projectType" :value="row.projectType" />
|
|
41
|
+
</template>
|
|
42
|
+
</el-table-column>
|
|
43
|
+
<el-table-column prop="manager" label="项目经理" width="100" align="center" sortable />
|
|
44
|
+
<el-table-column prop="budget" label="预算(¥)" width="100" align="center" sortable>
|
|
45
|
+
<template #default="{ row }">
|
|
46
|
+
{{ row.budget }}
|
|
47
|
+
</template>
|
|
48
|
+
</el-table-column>
|
|
49
|
+
<el-table-column prop="plannedStartDate" label="计划开始时间" width="130" align="center" sortable>
|
|
50
|
+
<template #default="{ row }">
|
|
51
|
+
<JnDatetime :value="row.plannedStartDate" />
|
|
52
|
+
</template>
|
|
53
|
+
</el-table-column>
|
|
54
|
+
<el-table-column prop="plannedFinishDate" label="计划完成日期" width="130" align="center" sortable>
|
|
55
|
+
<template #default="{ row }">
|
|
56
|
+
{{ row.plannedFinishDate }}
|
|
57
|
+
</template>
|
|
58
|
+
</el-table-column>
|
|
59
|
+
<el-table-column prop="progress" label="进度" width="100" align="center" sortable>
|
|
60
|
+
<template #default="{ row }">
|
|
61
|
+
<span>{{ row.progress }}</span>
|
|
62
|
+
</template>
|
|
63
|
+
</el-table-column>
|
|
64
|
+
<el-table-column prop="status" label="状态" width="100" align="center" sortable>
|
|
65
|
+
<template #default="{ row }">
|
|
66
|
+
<DictTag dictName="status" :value="row.status" :showColor="false" />
|
|
67
|
+
</template>
|
|
68
|
+
</el-table-column>
|
|
69
|
+
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
|
|
70
|
+
<el-table-column prop="imageDocument" label="图片" width="100" align="center" sortable>
|
|
71
|
+
<template #default="{ row }">
|
|
72
|
+
<ImageView :loadKeys="row.newImageFiles" preview maxHeight="50px" />
|
|
73
|
+
</template>
|
|
74
|
+
</el-table-column>
|
|
75
|
+
<el-table-column prop="attachmentDocument" label="附件" width="100" align="center" sortable>
|
|
76
|
+
<template #default="{ row }">
|
|
77
|
+
<PdfView :loadKeys="row.newAttachmentFile" isPdf />
|
|
78
|
+
</template>
|
|
79
|
+
</el-table-column>
|
|
80
|
+
</JnTable>
|
|
81
|
+
|
|
82
|
+
<!-- 表格分页 -->
|
|
83
|
+
<JnPagination :total="total" v-model="pagination" @change="getList" />
|
|
84
|
+
</el-card>
|
|
85
|
+
</template>
|
|
@@ -6,7 +6,7 @@ import { ref, onActivated, nextTick } from 'vue'
|
|
|
6
6
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
7
7
|
import { Plus } from '@element-plus/icons-vue'
|
|
8
8
|
import { useRoute } from '@jnrs/vue-core/router'
|
|
9
|
-
import { objectMatchAssign, dateFormatsToObject,
|
|
9
|
+
import { objectMatchAssign, dateFormatsToObject, verifyNumberGtZero } from '@jnrs/shared'
|
|
10
10
|
import { debounce } from '@jnrs/shared/lodash'
|
|
11
11
|
import { getDictList, downloadFile, extractFieldId } from '@/utils'
|
|
12
12
|
import { useI18n } from '@/locales'
|
|
@@ -40,7 +40,7 @@ const route = useRoute()
|
|
|
40
40
|
const editDialogRef = ref()
|
|
41
41
|
const ruleFormRef = ref<FormInstance>()
|
|
42
42
|
const ruleForm = ref<EditProject>({
|
|
43
|
-
id:
|
|
43
|
+
id: 0,
|
|
44
44
|
name: '',
|
|
45
45
|
projectType: undefined,
|
|
46
46
|
managerId: undefined,
|
|
@@ -59,7 +59,7 @@ const rules = ref<FormRules>({
|
|
|
59
59
|
budget: [
|
|
60
60
|
{ required: true, message: '请输入', trigger: 'change' },
|
|
61
61
|
{
|
|
62
|
-
validator:
|
|
62
|
+
validator: verifyNumberGtZero,
|
|
63
63
|
trigger: 'change'
|
|
64
64
|
}
|
|
65
65
|
]
|
|
@@ -223,8 +223,8 @@ onActivated(() => {
|
|
|
223
223
|
format="YYYY-MM-DD HH:mm:ss"
|
|
224
224
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
225
225
|
:disabled-date="
|
|
226
|
-
(time:
|
|
227
|
-
return time < Date.now() - 86400000
|
|
226
|
+
(time: Date) => {
|
|
227
|
+
return time.getTime() < Date.now() - 86400000
|
|
228
228
|
}
|
|
229
229
|
"
|
|
230
230
|
/>
|
|
@@ -17,7 +17,7 @@ const loginParams = ref({
|
|
|
17
17
|
|
|
18
18
|
const handleDataApi = async () => {
|
|
19
19
|
try {
|
|
20
|
-
const res = await DataApiTest('123')
|
|
20
|
+
const res = await DataApiTest({ id: '123' })
|
|
21
21
|
console.log(res)
|
|
22
22
|
console.log(res.name)
|
|
23
23
|
ElMessage({
|
|
@@ -33,7 +33,7 @@ const handleDataApi = async () => {
|
|
|
33
33
|
|
|
34
34
|
const handleFullDataApi = async () => {
|
|
35
35
|
try {
|
|
36
|
-
const res = await FullDataApiTest('123')
|
|
36
|
+
const res = await FullDataApiTest({ id: '123' })
|
|
37
37
|
console.log(res)
|
|
38
38
|
console.log(res.data?.name)
|
|
39
39
|
ElMessage({
|