Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev
commit
21e663a719
|
|
@ -62,16 +62,16 @@
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[less]": {
|
"[less]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
|
@ -86,8 +86,9 @@
|
||||||
"source.fixAll.eslint": "explicit",
|
"source.fixAll.eslint": "explicit",
|
||||||
"source.fixAll.stylelint": "explicit"
|
"source.fixAll.stylelint": "explicit"
|
||||||
},
|
},
|
||||||
|
"editor.formatOnSave": true,
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.defaultFormatter": "octref.vetur"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"i18n-ally.localesPaths": ["src/locales"],
|
"i18n-ally.localesPaths": ["src/locales"],
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "yudao-ui-admin-vue3",
|
"name": "yudao-ui-admin-vue3",
|
||||||
"version": "2.4.2-snapshot",
|
"version": "2.6.1-snapshot",
|
||||||
"description": "基于vue3、vite4、element-plus、typesScript",
|
"description": "基于vue3、vite4、element-plus、typesScript",
|
||||||
"author": "xingyu",
|
"author": "xingyu",
|
||||||
"private": false,
|
"private": false,
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.6.8",
|
"axios": "1.9.0",
|
||||||
"benz-amr-recorder": "^1.1.5",
|
"benz-amr-recorder": "^1.1.5",
|
||||||
"bpmn-js-token-simulation": "^0.36.0",
|
"bpmn-js-token-simulation": "^0.36.0",
|
||||||
"camunda-bpmn-moddle": "^7.0.1",
|
"camunda-bpmn-moddle": "^7.0.1",
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ export type ApprovalNodeInfo = {
|
||||||
status: number
|
status: number
|
||||||
startTime?: Date
|
startTime?: Date
|
||||||
endTime?: Date
|
endTime?: Date
|
||||||
|
processInstanceId?: string
|
||||||
candidateUsers?: User[]
|
candidateUsers?: User[]
|
||||||
tasks: ApprovalTaskInfo[]
|
tasks: ApprovalTaskInfo[]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,3 +105,8 @@ export const createCodegenList = (data) => {
|
||||||
export const deleteCodegenTable = (id: number) => {
|
export const deleteCodegenTable = (id: number) => {
|
||||||
return request.delete({ url: '/infra/codegen/delete?tableId=' + id })
|
return request.delete({ url: '/infra/codegen/delete?tableId=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除代码生成表定义
|
||||||
|
export const deleteCodegenTableList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/infra/codegen/delete-list', params: { tableIds: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,12 @@ export const deleteConfig = (id: number) => {
|
||||||
return request.delete({ url: '/infra/config/delete?id=' + id })
|
return request.delete({ url: '/infra/config/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除参数
|
||||||
|
export const deleteConfigList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/infra/config/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 导出参数
|
// 导出参数
|
||||||
export const exportConfig = (params) => {
|
export const exportConfig = (params) => {
|
||||||
return request.download({ url: '/infra/config/export', params })
|
return request.download({ url: '/infra/config/export-excel', params })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@ export const deleteDataSourceConfig = (id: number) => {
|
||||||
return request.delete({ url: '/infra/data-source-config/delete?id=' + id })
|
return request.delete({ url: '/infra/data-source-config/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除数据源配置
|
||||||
|
export const deleteDataSourceConfigList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/infra/data-source-config/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 查询数据源配置详情
|
// 查询数据源配置详情
|
||||||
export const getDataSourceConfig = (id: number) => {
|
export const getDataSourceConfig = (id: number) => {
|
||||||
return request.get({ url: '/infra/data-source-config/get?id=' + id })
|
return request.get({ url: '/infra/data-source-config/get?id=' + id })
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,50 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
import type { Dayjs } from 'dayjs'
|
||||||
|
|
||||||
export interface Demo01ContactVO {
|
/** 示例联系人信息 */
|
||||||
id: number
|
export interface Demo01Contact {
|
||||||
name: string
|
id: number // 编号
|
||||||
sex: number
|
name?: string // 名字
|
||||||
birthday: Date
|
sex?: number // 性别
|
||||||
description: string
|
birthday?: string | Dayjs // 出生年
|
||||||
avatar: string
|
description?: string // 简介
|
||||||
|
avatar: string // 头像
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询示例联系人分页
|
// 示例联系人 API
|
||||||
export const getDemo01ContactPage = async (params) => {
|
export const Demo01ContactApi = {
|
||||||
return await request.get({ url: `/infra/demo01-contact/page`, params })
|
// 查询示例联系人分页
|
||||||
}
|
getDemo01ContactPage: async (params: any) => {
|
||||||
|
return await request.get({ url: `/infra/demo01-contact/page`, params })
|
||||||
|
},
|
||||||
|
|
||||||
// 查询示例联系人详情
|
// 查询示例联系人详情
|
||||||
export const getDemo01Contact = async (id: number) => {
|
getDemo01Contact: async (id: number) => {
|
||||||
return await request.get({ url: `/infra/demo01-contact/get?id=` + id })
|
return await request.get({ url: `/infra/demo01-contact/get?id=` + id })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 新增示例联系人
|
// 新增示例联系人
|
||||||
export const createDemo01Contact = async (data: Demo01ContactVO) => {
|
createDemo01Contact: async (data: Demo01Contact) => {
|
||||||
return await request.post({ url: `/infra/demo01-contact/create`, data })
|
return await request.post({ url: `/infra/demo01-contact/create`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 修改示例联系人
|
// 修改示例联系人
|
||||||
export const updateDemo01Contact = async (data: Demo01ContactVO) => {
|
updateDemo01Contact: async (data: Demo01Contact) => {
|
||||||
return await request.put({ url: `/infra/demo01-contact/update`, data })
|
return await request.put({ url: `/infra/demo01-contact/update`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 删除示例联系人
|
// 删除示例联系人
|
||||||
export const deleteDemo01Contact = async (id: number) => {
|
deleteDemo01Contact: async (id: number) => {
|
||||||
return await request.delete({ url: `/infra/demo01-contact/delete?id=` + id })
|
return await request.delete({ url: `/infra/demo01-contact/delete?id=` + id })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 导出示例联系人 Excel
|
/** 批量删除示例联系人 */
|
||||||
export const exportDemo01Contact = async (params) => {
|
deleteDemo01ContactList: async (ids: number[]) => {
|
||||||
return await request.download({ url: `/infra/demo01-contact/export-excel`, params })
|
return await request.delete({ url: `/infra/demo01-contact/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出示例联系人 Excel
|
||||||
|
exportDemo01Contact: async (params) => {
|
||||||
|
return await request.download({ url: `/infra/demo01-contact/export-excel`, params })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,127 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
export interface Demo03StudentVO {
|
/** 学生课程信息 */
|
||||||
id: number
|
export interface Demo03Course {
|
||||||
name: string
|
id: number; // 编号
|
||||||
sex: number
|
studentId?: number; // 学生编号
|
||||||
birthday: Date
|
name?: string; // 名字
|
||||||
description: string
|
score?: number; // 分数
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询学生分页
|
/** 学生班级信息 */
|
||||||
export const getDemo03StudentPage = async (params) => {
|
export interface Demo03Grade {
|
||||||
return await request.get({ url: `/infra/demo03-student/page`, params })
|
id: number; // 编号
|
||||||
|
studentId?: number; // 学生编号
|
||||||
|
name?: string; // 名字
|
||||||
|
teacher?: string; // 班主任
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询学生详情
|
/** 学生信息 */
|
||||||
export const getDemo03Student = async (id: number) => {
|
export interface Demo03Student {
|
||||||
return await request.get({ url: `/infra/demo03-student/get?id=` + id })
|
id: number; // 编号
|
||||||
|
name?: string; // 名字
|
||||||
|
sex?: number; // 性别
|
||||||
|
birthday?: string | Dayjs; // 出生日期
|
||||||
|
description?: string; // 简介
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增学生
|
// 学生 API
|
||||||
export const createDemo03Student = async (data: Demo03StudentVO) => {
|
export const Demo03StudentApi = {
|
||||||
return await request.post({ url: `/infra/demo03-student/create`, data })
|
// 查询学生分页
|
||||||
}
|
getDemo03StudentPage: async (params: any) => {
|
||||||
|
return await request.get({ url: `/infra/demo03-student-erp/page`, params })
|
||||||
|
},
|
||||||
|
|
||||||
// 修改学生
|
// 查询学生详情
|
||||||
export const updateDemo03Student = async (data: Demo03StudentVO) => {
|
getDemo03Student: async (id: number) => {
|
||||||
return await request.put({ url: `/infra/demo03-student/update`, data })
|
return await request.get({ url: `/infra/demo03-student-erp/get?id=` + id })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 删除学生
|
// 新增学生
|
||||||
export const deleteDemo03Student = async (id: number) => {
|
createDemo03Student: async (data: Demo03Student) => {
|
||||||
return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })
|
return await request.post({ url: `/infra/demo03-student-erp/create`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 导出学生 Excel
|
// 修改学生
|
||||||
export const exportDemo03Student = async (params) => {
|
updateDemo03Student: async (data: Demo03Student) => {
|
||||||
return await request.download({ url: `/infra/demo03-student/export-excel`, params })
|
return await request.put({ url: `/infra/demo03-student-erp/update`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// 删除学生
|
||||||
|
deleteDemo03Student: async (id: number) => {
|
||||||
|
return await request.delete({ url: `/infra/demo03-student-erp/delete?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 批量删除学生 */
|
||||||
|
deleteDemo03StudentList: async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: `/infra/demo03-student-erp/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出学生 Excel
|
||||||
|
exportDemo03Student: async (params) => {
|
||||||
|
return await request.download({ url: `/infra/demo03-student-erp/export-excel`, params })
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== 子表(学生课程) ====================
|
// ==================== 子表(学生课程) ====================
|
||||||
|
|
||||||
// 获得学生课程分页
|
// 获得学生课程分页
|
||||||
export const getDemo03CoursePage = async (params) => {
|
getDemo03CoursePage: async (params) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-course/page`, params })
|
return await request.get({ url: `/infra/demo03-student-erp/demo03-course/page`, params })
|
||||||
}
|
},
|
||||||
// 新增学生课程
|
// 新增学生课程
|
||||||
export const createDemo03Course = async (data) => {
|
createDemo03Course: async (data: Demo03Course) => {
|
||||||
return await request.post({ url: `/infra/demo03-student/demo03-course/create`, data })
|
return await request.post({ url: `/infra/demo03-student-erp/demo03-course/create`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 修改学生课程
|
// 修改学生课程
|
||||||
export const updateDemo03Course = async (data) => {
|
updateDemo03Course: async (data: Demo03Course) => {
|
||||||
return await request.put({ url: `/infra/demo03-student/demo03-course/update`, data })
|
return await request.put({ url: `/infra/demo03-student-erp/demo03-course/update`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 删除学生课程
|
// 删除学生课程
|
||||||
export const deleteDemo03Course = async (id: number) => {
|
deleteDemo03Course: async (id: number) => {
|
||||||
return await request.delete({ url: `/infra/demo03-student/demo03-course/delete?id=` + id })
|
return await request.delete({ url: `/infra/demo03-student-erp/demo03-course/delete?id=` + id })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 获得学生课程
|
/** 批量删除学生课程 */
|
||||||
export const getDemo03Course = async (id: number) => {
|
deleteDemo03CourseList: async (ids: number[]) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-course/get?id=` + id })
|
return await request.delete({ url: `/infra/demo03-student-erp/demo03-course/delete-list?ids=${ids.join(',')}` })
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// 获得学生课程
|
||||||
|
getDemo03Course: async (id: number) => {
|
||||||
|
return await request.get({ url: `/infra/demo03-student-erp/demo03-course/get?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== 子表(学生班级) ====================
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
// 获得学生班级分页
|
// 获得学生班级分页
|
||||||
export const getDemo03GradePage = async (params) => {
|
getDemo03GradePage: async (params) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-grade/page`, params })
|
return await request.get({ url: `/infra/demo03-student-erp/demo03-grade/page`, params })
|
||||||
}
|
},
|
||||||
// 新增学生班级
|
// 新增学生班级
|
||||||
export const createDemo03Grade = async (data) => {
|
createDemo03Grade: async (data: Demo03Grade) => {
|
||||||
return await request.post({ url: `/infra/demo03-student/demo03-grade/create`, data })
|
return await request.post({ url: `/infra/demo03-student-erp/demo03-grade/create`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 修改学生班级
|
// 修改学生班级
|
||||||
export const updateDemo03Grade = async (data) => {
|
updateDemo03Grade: async (data: Demo03Grade) => {
|
||||||
return await request.put({ url: `/infra/demo03-student/demo03-grade/update`, data })
|
return await request.put({ url: `/infra/demo03-student-erp/demo03-grade/update`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 删除学生班级
|
// 删除学生班级
|
||||||
export const deleteDemo03Grade = async (id: number) => {
|
deleteDemo03Grade: async (id: number) => {
|
||||||
return await request.delete({ url: `/infra/demo03-student/demo03-grade/delete?id=` + id })
|
return await request.delete({ url: `/infra/demo03-student-erp/demo03-grade/delete?id=` + id })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 获得学生班级
|
/** 批量删除学生班级 */
|
||||||
export const getDemo03Grade = async (id: number) => {
|
deleteDemo03GradeList: async (ids: number[]) => {
|
||||||
return await request.get({ url: `/infra/demo03-student/demo03-grade/get?id=` + id })
|
return await request.delete({ url: `/infra/demo03-student-erp/demo03-grade/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得学生班级
|
||||||
|
getDemo03Grade: async (id: number) => {
|
||||||
|
return await request.get({ url: `/infra/demo03-student-erp/demo03-grade/get?id=` + id })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,81 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
export interface Demo03StudentVO {
|
/** 学生课程信息 */
|
||||||
id: number
|
export interface Demo03Course {
|
||||||
name: string
|
id: number; // 编号
|
||||||
sex: number
|
studentId?: number; // 学生编号
|
||||||
birthday: Date
|
name?: string; // 名字
|
||||||
description: string
|
score?: number; // 分数
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询学生分页
|
/** 学生班级信息 */
|
||||||
export const getDemo03StudentPage = async (params) => {
|
export interface Demo03Grade {
|
||||||
return await request.get({ url: `/infra/demo03-student/page`, params })
|
id: number; // 编号
|
||||||
|
studentId?: number; // 学生编号
|
||||||
|
name?: string; // 名字
|
||||||
|
teacher?: string; // 班主任
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询学生详情
|
/** 学生信息 */
|
||||||
export const getDemo03Student = async (id: number) => {
|
export interface Demo03Student {
|
||||||
return await request.get({ url: `/infra/demo03-student/get?id=` + id })
|
id: number; // 编号
|
||||||
|
name?: string; // 名字
|
||||||
|
sex?: number; // 性别
|
||||||
|
birthday?: string | Dayjs; // 出生日期
|
||||||
|
description?: string; // 简介
|
||||||
|
demo03courses?: Demo03Course[]
|
||||||
|
demo03grade?: Demo03Grade
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增学生
|
// 学生 API
|
||||||
export const createDemo03Student = async (data: Demo03StudentVO) => {
|
export const Demo03StudentApi = {
|
||||||
return await request.post({ url: `/infra/demo03-student/create`, data })
|
// 查询学生分页
|
||||||
}
|
getDemo03StudentPage: async (params: any) => {
|
||||||
|
return await request.get({ url: `/infra/demo03-student-inner/page`, params })
|
||||||
|
},
|
||||||
|
|
||||||
// 修改学生
|
// 查询学生详情
|
||||||
export const updateDemo03Student = async (data: Demo03StudentVO) => {
|
getDemo03Student: async (id: number) => {
|
||||||
return await request.put({ url: `/infra/demo03-student/update`, data })
|
return await request.get({ url: `/infra/demo03-student-inner/get?id=` + id })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 删除学生
|
// 新增学生
|
||||||
export const deleteDemo03Student = async (id: number) => {
|
createDemo03Student: async (data: Demo03Student) => {
|
||||||
return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })
|
return await request.post({ url: `/infra/demo03-student-inner/create`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 导出学生 Excel
|
// 修改学生
|
||||||
export const exportDemo03Student = async (params) => {
|
updateDemo03Student: async (data: Demo03Student) => {
|
||||||
return await request.download({ url: `/infra/demo03-student/export-excel`, params })
|
return await request.put({ url: `/infra/demo03-student-inner/update`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// 删除学生
|
||||||
|
deleteDemo03Student: async (id: number) => {
|
||||||
|
return await request.delete({ url: `/infra/demo03-student-inner/delete?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 批量删除学生 */
|
||||||
|
deleteDemo03StudentList: async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: `/infra/demo03-student-inner/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出学生 Excel
|
||||||
|
exportDemo03Student: async (params) => {
|
||||||
|
return await request.download({ url: `/infra/demo03-student-inner/export-excel`, params })
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== 子表(学生课程) ====================
|
// ==================== 子表(学生课程) ====================
|
||||||
|
|
||||||
// 获得学生课程列表
|
// 获得学生课程列表
|
||||||
export const getDemo03CourseListByStudentId = async (studentId) => {
|
getDemo03CourseListByStudentId: async (studentId) => {
|
||||||
return await request.get({
|
return await request.get({ url: `/infra/demo03-student-inner/demo03-course/list-by-student-id?studentId=` + studentId })
|
||||||
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
|
},
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 子表(学生班级) ====================
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
// 获得学生班级
|
// 获得学生班级
|
||||||
export const getDemo03GradeByStudentId = async (studentId) => {
|
getDemo03GradeByStudentId: async (studentId) => {
|
||||||
return await request.get({
|
return await request.get({ url: `/infra/demo03-student-inner/demo03-grade/get-by-student-id?studentId=` + studentId })
|
||||||
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
|
},
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,81 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
export interface Demo03StudentVO {
|
/** 学生课程信息 */
|
||||||
id: number
|
export interface Demo03Course {
|
||||||
name: string
|
id: number; // 编号
|
||||||
sex: number
|
studentId?: number; // 学生编号
|
||||||
birthday: Date
|
name?: string; // 名字
|
||||||
description: string
|
score?: number; // 分数
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询学生分页
|
/** 学生班级信息 */
|
||||||
export const getDemo03StudentPage = async (params) => {
|
export interface Demo03Grade {
|
||||||
return await request.get({ url: `/infra/demo03-student/page`, params })
|
id: number; // 编号
|
||||||
|
studentId?: number; // 学生编号
|
||||||
|
name?: string; // 名字
|
||||||
|
teacher?: string; // 班主任
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询学生详情
|
/** 学生信息 */
|
||||||
export const getDemo03Student = async (id: number) => {
|
export interface Demo03Student {
|
||||||
return await request.get({ url: `/infra/demo03-student/get?id=` + id })
|
id: number; // 编号
|
||||||
|
name?: string; // 名字
|
||||||
|
sex?: number; // 性别
|
||||||
|
birthday?: string | Dayjs; // 出生日期
|
||||||
|
description?: string; // 简介
|
||||||
|
demo03courses?: Demo03Course[]
|
||||||
|
demo03grade?: Demo03Grade
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增学生
|
// 学生 API
|
||||||
export const createDemo03Student = async (data: Demo03StudentVO) => {
|
export const Demo03StudentApi = {
|
||||||
return await request.post({ url: `/infra/demo03-student/create`, data })
|
// 查询学生分页
|
||||||
}
|
getDemo03StudentPage: async (params: any) => {
|
||||||
|
return await request.get({ url: `/infra/demo03-student-normal/page`, params })
|
||||||
|
},
|
||||||
|
|
||||||
// 修改学生
|
// 查询学生详情
|
||||||
export const updateDemo03Student = async (data: Demo03StudentVO) => {
|
getDemo03Student: async (id: number) => {
|
||||||
return await request.put({ url: `/infra/demo03-student/update`, data })
|
return await request.get({ url: `/infra/demo03-student-normal/get?id=` + id })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 删除学生
|
// 新增学生
|
||||||
export const deleteDemo03Student = async (id: number) => {
|
createDemo03Student: async (data: Demo03Student) => {
|
||||||
return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })
|
return await request.post({ url: `/infra/demo03-student-normal/create`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
// 导出学生 Excel
|
// 修改学生
|
||||||
export const exportDemo03Student = async (params) => {
|
updateDemo03Student: async (data: Demo03Student) => {
|
||||||
return await request.download({ url: `/infra/demo03-student/export-excel`, params })
|
return await request.put({ url: `/infra/demo03-student-normal/update`, data })
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// 删除学生
|
||||||
|
deleteDemo03Student: async (id: number) => {
|
||||||
|
return await request.delete({ url: `/infra/demo03-student-normal/delete?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 批量删除学生 */
|
||||||
|
deleteDemo03StudentList: async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: `/infra/demo03-student-normal/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出学生 Excel
|
||||||
|
exportDemo03Student: async (params) => {
|
||||||
|
return await request.download({ url: `/infra/demo03-student-normal/export-excel`, params })
|
||||||
|
},
|
||||||
|
|
||||||
// ==================== 子表(学生课程) ====================
|
// ==================== 子表(学生课程) ====================
|
||||||
|
|
||||||
// 获得学生课程列表
|
// 获得学生课程列表
|
||||||
export const getDemo03CourseListByStudentId = async (studentId) => {
|
getDemo03CourseListByStudentId: async (studentId) => {
|
||||||
return await request.get({
|
return await request.get({ url: `/infra/demo03-student-normal/demo03-course/list-by-student-id?studentId=` + studentId })
|
||||||
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
|
},
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 子表(学生班级) ====================
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
// 获得学生班级
|
// 获得学生班级
|
||||||
export const getDemo03GradeByStudentId = async (studentId) => {
|
getDemo03GradeByStudentId: async (studentId) => {
|
||||||
return await request.get({
|
return await request.get({ url: `/infra/demo03-student-normal/demo03-grade/get-by-student-id?studentId=` + studentId })
|
||||||
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
|
},
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,11 @@ export const deleteFile = (id: number) => {
|
||||||
return request.delete({ url: '/infra/file/delete?id=' + id })
|
return request.delete({ url: '/infra/file/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除文件
|
||||||
|
export const deleteFileList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/infra/file/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 获取文件预签名地址
|
// 获取文件预签名地址
|
||||||
export const getFilePresignedUrl = (name: string, directory?: string) => {
|
export const getFilePresignedUrl = (name: string, directory?: string) => {
|
||||||
return request.get<FilePresignedUrlRespVO>({
|
return request.get<FilePresignedUrlRespVO>({
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,11 @@ export const deleteFileConfig = (id: number) => {
|
||||||
return request.delete({ url: '/infra/file-config/delete?id=' + id })
|
return request.delete({ url: '/infra/file-config/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除文件配置
|
||||||
|
export const deleteFileConfigList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/infra/file-config/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 测试文件配置
|
// 测试文件配置
|
||||||
export const testFileConfig = (id: number) => {
|
export const testFileConfig = (id: number) => {
|
||||||
return request.get({ url: '/infra/file-config/test?id=' + id })
|
return request.get({ url: '/infra/file-config/test?id=' + id })
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,11 @@ export const deleteJob = (id: number) => {
|
||||||
return request.delete({ url: '/infra/job/delete?id=' + id })
|
return request.delete({ url: '/infra/job/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除定时任务调度
|
||||||
|
export const deleteJobList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/infra/job/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 导出定时任务调度
|
// 导出定时任务调度
|
||||||
export const exportJob = (params) => {
|
export const exportJob = (params) => {
|
||||||
return request.download({ url: '/infra/job/export-excel', params })
|
return request.download({ url: '/infra/job/export-excel', params })
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export const deleteSpu = (id: number) => {
|
||||||
|
|
||||||
// 导出商品 Spu Excel
|
// 导出商品 Spu Excel
|
||||||
export const exportSpu = async (params: any) => {
|
export const exportSpu = async (params: any) => {
|
||||||
return await request.download({ url: '/product/spu/export', params })
|
return await request.download({ url: '/product/spu/export-excel', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得商品 SPU 精简列表
|
// 获得商品 SPU 精简列表
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export interface DeptVO {
|
export interface DeptVO {
|
||||||
id?: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
parentId: number
|
parentId: number
|
||||||
status: number
|
status: number
|
||||||
|
|
@ -13,31 +13,41 @@ export interface DeptVO {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询部门(精简)列表
|
// 查询部门(精简)列表
|
||||||
export const getSimpleDeptList = async (): Promise<DeptVO[]> => {
|
export const getSimpleDeptList = (): Promise<DeptVO[]> => {
|
||||||
return await request.get({ url: '/system/dept/simple-list' })
|
return request.get({ url: '/system/dept/simple-list' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询部门列表
|
// 查询部门列表
|
||||||
|
export const getDeptList = (params: any) => {
|
||||||
|
return request.get({ url: '/system/dept/list', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询部门分页
|
||||||
export const getDeptPage = async (params: PageParam) => {
|
export const getDeptPage = async (params: PageParam) => {
|
||||||
return await request.get({ url: '/system/dept/list', params })
|
return await request.get({ url: '/system/dept/list', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询部门详情
|
// 查询部门详情
|
||||||
export const getDept = async (id: number) => {
|
export const getDept = (id: number) => {
|
||||||
return await request.get({ url: '/system/dept/get?id=' + id })
|
return request.get({ url: '/system/dept/get?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增部门
|
// 新增部门
|
||||||
export const createDept = async (data: DeptVO) => {
|
export const createDept = (data: DeptVO) => {
|
||||||
return await request.post({ url: '/system/dept/create', data: data })
|
return request.post({ url: '/system/dept/create', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改部门
|
// 修改部门
|
||||||
export const updateDept = async (params: DeptVO) => {
|
export const updateDept = (data: DeptVO) => {
|
||||||
return await request.put({ url: '/system/dept/update', data: params })
|
return request.put({ url: '/system/dept/update', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除部门
|
// 删除部门
|
||||||
export const deleteDept = async (id: number) => {
|
export const deleteDept = async (id: number) => {
|
||||||
return await request.delete({ url: '/system/dept/delete?id=' + id })
|
return await request.delete({ url: '/system/dept/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除部门
|
||||||
|
export const deleteDeptList = async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: '/system/dept/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export type DictDataVO = {
|
export interface DictDataVO {
|
||||||
id: number | undefined
|
id: number
|
||||||
sort: number | undefined
|
sort: number
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value: string
|
||||||
dictType: string
|
dictType: string
|
||||||
|
|
@ -28,6 +28,11 @@ export const getDictData = (id: number) => {
|
||||||
return request.get({ url: '/system/dict-data/get?id=' + id })
|
return request.get({ url: '/system/dict-data/get?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据字典类型查询字典数据
|
||||||
|
export const getDictDataByType = (dictType: string) => {
|
||||||
|
return request.get({ url: '/system/dict-data/type?type=' + dictType })
|
||||||
|
}
|
||||||
|
|
||||||
// 新增字典数据
|
// 新增字典数据
|
||||||
export const createDictData = (data: DictDataVO) => {
|
export const createDictData = (data: DictDataVO) => {
|
||||||
return request.post({ url: '/system/dict-data/create', data })
|
return request.post({ url: '/system/dict-data/create', data })
|
||||||
|
|
@ -43,7 +48,12 @@ export const deleteDictData = (id: number) => {
|
||||||
return request.delete({ url: '/system/dict-data/delete?id=' + id })
|
return request.delete({ url: '/system/dict-data/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出字典类型数据
|
// 批量删除字典数据
|
||||||
export const exportDictData = (params) => {
|
export const deleteDictDataList = (ids: number[]) => {
|
||||||
return request.download({ url: '/system/dict-data/export', params })
|
return request.delete({ url: '/system/dict-data/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出字典数据
|
||||||
|
export const exportDictData = (params: any) => {
|
||||||
|
return request.download({ url: '/system/dict-data/export-excel', params })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export type DictTypeVO = {
|
export interface DictTypeVO {
|
||||||
id: number | undefined
|
id: number
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
status: number
|
status: number
|
||||||
|
|
@ -10,8 +10,8 @@ export type DictTypeVO = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询字典(精简)列表
|
// 查询字典(精简)列表
|
||||||
export const getSimpleDictTypeList = () => {
|
export const getSimpleDictTypeList = (): Promise<DictTypeVO[]> => {
|
||||||
return request.get({ url: '/system/dict-type/list-all-simple' })
|
return request.get({ url: '/system/dict-type/simple-list' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询字典列表
|
// 查询字典列表
|
||||||
|
|
@ -38,7 +38,16 @@ export const updateDictType = (data: DictTypeVO) => {
|
||||||
export const deleteDictType = (id: number) => {
|
export const deleteDictType = (id: number) => {
|
||||||
return request.delete({ url: '/system/dict-type/delete?id=' + id })
|
return request.delete({ url: '/system/dict-type/delete?id=' + id })
|
||||||
}
|
}
|
||||||
// 导出字典类型
|
|
||||||
export const exportDictType = (params) => {
|
// 批量删除字典类型
|
||||||
return request.download({ url: '/system/dict-type/export', params })
|
export const deleteDictTypeList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/dict-type/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出字典
|
||||||
|
export const exportDictType = (params) => {
|
||||||
|
return request.download({
|
||||||
|
url: '/system/dict-type/export-excel',
|
||||||
|
params
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,5 +21,5 @@ export const getLoginLogPage = (params: PageParam) => {
|
||||||
|
|
||||||
// 导出登录日志
|
// 导出登录日志
|
||||||
export const exportLoginLog = (params) => {
|
export const exportLoginLog = (params) => {
|
||||||
return request.download({ url: '/system/login-log/export', params })
|
return request.download({ url: '/system/login-log/export-excel', params })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@ export const deleteMailAccount = async (id: number) => {
|
||||||
return await request.delete({ url: '/system/mail-account/delete?id=' + id })
|
return await request.delete({ url: '/system/mail-account/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除邮箱账号
|
||||||
|
export const deleteMailAccountList = async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: '/system/mail-account/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 获得邮箱账号精简列表
|
// 获得邮箱账号精简列表
|
||||||
export const getSimpleMailAccountList = async () => {
|
export const getSimpleMailAccountList = async () => {
|
||||||
return request.get({ url: '/system/mail-account/simple-list' })
|
return request.get({ url: '/system/mail-account/simple-list' })
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,8 @@ export const getMailLogPage = async (params: PageParam) => {
|
||||||
export const getMailLog = async (id: number) => {
|
export const getMailLog = async (id: number) => {
|
||||||
return await request.get({ url: '/system/mail-log/get?id=' + id })
|
return await request.get({ url: '/system/mail-log/get?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出邮件日志
|
||||||
|
export const exportMailLog = (params) => {
|
||||||
|
return request.download({ url: '/system/mail-log/export-excel', params })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,11 @@ export const deleteMailTemplate = async (id: number) => {
|
||||||
return await request.delete({ url: '/system/mail-template/delete?id=' + id })
|
return await request.delete({ url: '/system/mail-template/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除邮件模版
|
||||||
|
export const deleteMailTemplateList = async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: '/system/mail-template/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 发送邮件
|
// 发送邮件
|
||||||
export const sendMail = (data: MailSendReqVO) => {
|
export const sendMail = (data: MailSendReqVO) => {
|
||||||
return request.post({ url: '/system/mail-template/send-mail', data })
|
return request.post({ url: '/system/mail-template/send-mail', data })
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@ export const deleteNotice = (id: number) => {
|
||||||
return request.delete({ url: '/system/notice/delete?id=' + id })
|
return request.delete({ url: '/system/notice/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除公告
|
||||||
|
export const deleteNoticeList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/notice/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 推送公告
|
// 推送公告
|
||||||
export const pushNotice = (id: number) => {
|
export const pushNotice = (id: number) => {
|
||||||
return request.post({ url: '/system/notice/push?id=' + id })
|
return request.post({ url: '/system/notice/push?id=' + id })
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,11 @@ export const deleteNotifyTemplate = async (id: number) => {
|
||||||
return await request.delete({ url: '/system/notify-template/delete?id=' + id })
|
return await request.delete({ url: '/system/notify-template/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除站内信模板
|
||||||
|
export const deleteNotifyTemplateList = async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: '/system/notify-template/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 发送站内信
|
// 发送站内信
|
||||||
export const sendNotify = (data: NotifySendReqVO) => {
|
export const sendNotify = (data: NotifySendReqVO) => {
|
||||||
return request.post({ url: '/system/notify-template/send-notify', data })
|
return request.post({ url: '/system/notify-template/send-notify', data })
|
||||||
|
|
|
||||||
|
|
@ -45,3 +45,8 @@ export const updateOAuth2Client = (data: OAuth2ClientVO) => {
|
||||||
export const deleteOAuth2Client = (id: number) => {
|
export const deleteOAuth2Client = (id: number) => {
|
||||||
return request.delete({ url: '/system/oauth2-client/delete?id=' + id })
|
return request.delete({ url: '/system/oauth2-client/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除 OAuth2 客户端
|
||||||
|
export const deleteOAuth2ClientList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/oauth2-client/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,5 @@ export const getOperateLogPage = (params: PageParam) => {
|
||||||
}
|
}
|
||||||
// 导出操作日志
|
// 导出操作日志
|
||||||
export const exportOperateLog = (params: any) => {
|
export const exportOperateLog = (params: any) => {
|
||||||
return request.download({ url: '/system/operate-log/export', params })
|
return request.download({ url: '/system/operate-log/export-excel', params })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,12 @@ export const deletePost = async (id: number) => {
|
||||||
return await request.delete({ url: '/system/post/delete?id=' + id })
|
return await request.delete({ url: '/system/post/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除岗位
|
||||||
|
export const deletePostList = async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: '/system/post/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 导出岗位
|
// 导出岗位
|
||||||
export const exportPost = async (params) => {
|
export const exportPost = async (params) => {
|
||||||
return await request.download({ url: '/system/post/export', params })
|
return await request.download({ url: '/system/post/export-excel', params })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,11 @@ export const deleteRole = async (id: number) => {
|
||||||
return await request.delete({ url: '/system/role/delete?id=' + id })
|
return await request.delete({ url: '/system/role/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除角色
|
||||||
|
export const deleteRoleList = async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: '/system/role/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 导出角色
|
// 导出角色
|
||||||
export const exportRole = (params) => {
|
export const exportRole = (params) => {
|
||||||
return request.download({
|
return request.download({
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,8 @@ export const updateSmsChannel = (data: SmsChannelVO) => {
|
||||||
export const deleteSmsChannel = (id: number) => {
|
export const deleteSmsChannel = (id: number) => {
|
||||||
return request.delete({ url: '/system/sms-channel/delete?id=' + id })
|
return request.delete({ url: '/system/sms-channel/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除短信渠道
|
||||||
|
export const deleteSmsChannelList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/sms-channel/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,11 @@ export const deleteSmsTemplate = (id: number) => {
|
||||||
return request.delete({ url: '/system/sms-template/delete?id=' + id })
|
return request.delete({ url: '/system/sms-template/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除短信模板
|
||||||
|
export const deleteSmsTemplateList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/sms-template/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 导出短信模板
|
// 导出短信模板
|
||||||
export const exportSmsTemplate = (params) => {
|
export const exportSmsTemplate = (params) => {
|
||||||
return request.download({
|
return request.download({
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export interface SocialUserVO {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询社交用户列表
|
// 查询社交用户列表
|
||||||
export const getSocialUserPage = async (params) => {
|
export const getSocialUserPage = async (params: any) => {
|
||||||
return await request.get({ url: `/system/social-user/page`, params })
|
return await request.get({ url: `/system/social-user/page`, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,3 +22,8 @@ export const getSocialUserPage = async (params) => {
|
||||||
export const getSocialUser = async (id: number) => {
|
export const getSocialUser = async (id: number) => {
|
||||||
return await request.get({ url: `/system/social-user/get?id=` + id })
|
return await request.get({ url: `/system/social-user/get?id=` + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获得绑定社交用户列表
|
||||||
|
export const getBindSocialUserList = async () => {
|
||||||
|
return await request.get({ url: '/system/social-user/get-bind-list' })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,11 @@ export const deleteTenant = (id: number) => {
|
||||||
return request.delete({ url: '/system/tenant/delete?id=' + id })
|
return request.delete({ url: '/system/tenant/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除租户
|
||||||
|
export const deleteTenantList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/tenant/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 导出租户
|
// 导出租户
|
||||||
export const exportTenant = (params: TenantExportReqVO) => {
|
export const exportTenant = (params: TenantExportReqVO) => {
|
||||||
return request.download({ url: '/system/tenant/export-excel', params })
|
return request.download({ url: '/system/tenant/export-excel', params })
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,12 @@ export const updateTenantPackage = (data: TenantPackageVO) => {
|
||||||
export const deleteTenantPackage = (id: number) => {
|
export const deleteTenantPackage = (id: number) => {
|
||||||
return request.delete({ url: '/system/tenant-package/delete?id=' + id })
|
return request.delete({ url: '/system/tenant-package/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除租户套餐
|
||||||
|
export const deleteTenantPackageList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/tenant-package/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 获取租户套餐精简信息列表
|
// 获取租户套餐精简信息列表
|
||||||
export const getTenantPackageList = () => {
|
export const getTenantPackageList = () => {
|
||||||
return request.get({ url: '/system/tenant-package/simple-list' })
|
return request.get({ url: '/system/tenant-package/simple-list' })
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,14 @@ export const deleteUser = (id: number) => {
|
||||||
return request.delete({ url: '/system/user/delete?id=' + id })
|
return request.delete({ url: '/system/user/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除用户
|
||||||
|
export const deleteUserList = (ids: number[]) => {
|
||||||
|
return request.delete({ url: '/system/user/delete-list', params: { ids: ids.join(',') } })
|
||||||
|
}
|
||||||
|
|
||||||
// 导出用户
|
// 导出用户
|
||||||
export const exportUser = (params: any) => {
|
export const exportUser = (params: any) => {
|
||||||
return request.download({ url: '/system/user/export', params })
|
return request.download({ url: '/system/user/export-excel', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载用户导入模板
|
// 下载用户导入模板
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@ export interface ProfileVO {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
}[]
|
}[]
|
||||||
socialUsers: {
|
|
||||||
type: number
|
|
||||||
openid: string
|
|
||||||
}[]
|
|
||||||
email: string
|
email: string
|
||||||
mobile: string
|
mobile: string
|
||||||
sex: number
|
sex: number
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1676209854312" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3033" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M173.077333 362.666667l91.114667-214.677334a65.6 65.6 0 0 1 86.016-34.773333c11.584 4.906667 24.96 10.282667 40.896 16.448 8.277333 3.2 16.789333 6.464 27.904 10.666667 28.202667 10.709333 39.296 14.933333 46.144 17.642666l51.477333-51.669333c28.181333-28.16 74.112-27.946667 102.570667 0.533333l195.925333 195.925334c16.426667 16.426667 23.445333 38.634667 21.056 59.904H896a42.666667 42.666667 0 0 1 42.666667 42.666666v490.666667a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V405.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h45.077333z m48.96 0h39.104l169.194667-169.770667-27.328-10.389333c-11.2-4.245333-19.818667-7.530667-28.224-10.794667a1459.2 1459.2 0 0 1-42.197333-17.002667 20.522667 20.522667 0 0 0-26.901334 10.88L222.037333 362.666667z m108.842667 0h454.954667a23.509333 23.509333 0 0 0-5.290667-25.322667l-195.925333-195.925333a23.36 23.36 0 0 0-33.024-0.213334L330.88 362.666667zM128 405.333333v490.666667h768V405.333333H128z m597.333333 320a85.333333 85.333333 0 1 1 0-170.666666 85.333333 85.333333 0 0 1 0 170.666666z m0-42.666666a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" fill="#4296d5" p-id="3034"></path></svg>
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747409043186" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4834" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M44.416 853.333333v-85.205333a170.666667 170.666667 0 0 1 170.666667-170.666667h170.837333a37637.589333 37637.589333 0 0 1 0-206.165333C324.309333 352.170667 281.6 285.141333 281.6 211.968c0-116.906667 90.197333-211.072 231.168-211.072 140.970667 0 230.741333 94.208 230.741333 211.072 0 73.216-40.96 140.245333-102.528 179.328 0.256 0.170667 0.256 68.906667 0 206.165333h171.989334a170.666667 170.666667 0 0 1 170.666666 170.666667V853.333333a170.666667 170.666667 0 0 1-170.666666 170.666667H215.082667a170.666667 170.666667 0 0 1-170.666667-170.666667z m84.266667-84.650666v104.277333a85.333333 85.333333 0 0 0 85.333333 85.333333H811.52a85.333333 85.333333 0 0 0 85.333333-85.333333v-104.277333a85.333333 85.333333 0 0 0-85.333333-85.333334h-256.64l8.96-342.698666c66.944-21.333333 100.394667-64.256 100.394667-128.682667 0-61.952-57.344-129.322667-151.466667-129.322667-94.122667 0-146.645333 61.610667-146.645333 129.322667 0 71.466667 34.816 114.346667 104.362666 128.682667v342.698666H214.016a85.333333 85.333333 0 0 0-85.333333 85.333334z m167.125333 138.368c-50.432 0-91.434667-41.557333-91.434667-92.586667s41.002667-92.586667 91.434667-92.586667c50.389333 0 91.434667 41.557333 91.434667 92.586667 0 24.832-9.6 48.170667-27.008 65.706667-17.237333 17.322667-40.106667 26.88-64.426667 26.88z m0-119.466667a27.093333 27.093333 0 0 0-27.306667 26.88c0 14.805333 12.245333 26.88 27.306667 26.88a27.306667 27.306667 0 0 0 19.498667-8.106667 26.453333 26.453333 0 0 0 7.808-18.773333 27.093333 27.093333 0 0 0-27.306667-26.88z" fill="#1296db" p-id="4835"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1676209854312" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3033" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M173.077333 362.666667l91.114667-214.677334a65.6 65.6 0 0 1 86.016-34.773333c11.584 4.906667 24.96 10.282667 40.896 16.448 8.277333 3.2 16.789333 6.464 27.904 10.666667 28.202667 10.709333 39.296 14.933333 46.144 17.642666l51.477333-51.669333c28.181333-28.16 74.112-27.946667 102.570667 0.533333l195.925333 195.925334c16.426667 16.426667 23.445333 38.634667 21.056 59.904H896a42.666667 42.666667 0 0 1 42.666667 42.666666v490.666667a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667V405.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h45.077333z m48.96 0h39.104l169.194667-169.770667-27.328-10.389333c-11.2-4.245333-19.818667-7.530667-28.224-10.794667a1459.2 1459.2 0 0 1-42.197333-17.002667 20.522667 20.522667 0 0 0-26.901334 10.88L222.037333 362.666667z m108.842667 0h454.954667a23.509333 23.509333 0 0 0-5.290667-25.322667l-195.925333-195.925333a23.36 23.36 0 0 0-33.024-0.213334L330.88 362.666667zM128 405.333333v490.666667h768V405.333333H128z m597.333333 320a85.333333 85.333333 0 1 1 0-170.666666 85.333333 85.333333 0 0 1 0 170.666666z m0-42.666666a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" fill="#4296d5" p-id="3034"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -80,7 +80,8 @@ const activeAppLink = ref({} as AppLink)
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const open = (link: string) => {
|
const open = (link: string) => {
|
||||||
activeAppLink.value.path = link
|
// 进入页面时先重置 activeAppLink
|
||||||
|
activeAppLink.value = { name: '', path: '' }
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
||||||
// 滚动到当前的链接
|
// 滚动到当前的链接
|
||||||
|
|
@ -102,8 +103,11 @@ defineExpose({ open })
|
||||||
|
|
||||||
// 处理 APP 链接选中
|
// 处理 APP 链接选中
|
||||||
const handleAppLinkSelected = (appLink: AppLink) => {
|
const handleAppLinkSelected = (appLink: AppLink) => {
|
||||||
|
// 只有不同链接时才更新(避免重复触发)
|
||||||
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
|
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
|
||||||
activeAppLink.value = appLink
|
// 如果新链接的 path 为空,则沿用当前 activeAppLink 的 path
|
||||||
|
const path = appLink.path || activeAppLink.value.path
|
||||||
|
activeAppLink.value = { ...appLink, path: path }
|
||||||
}
|
}
|
||||||
switch (appLink.type) {
|
switch (appLink.type) {
|
||||||
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
|
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
|
||||||
|
|
@ -170,7 +174,7 @@ const groupBtnRefs = ref<ButtonInstance[]>([])
|
||||||
const scrollToGroupBtn = (group: string) => {
|
const scrollToGroupBtn = (group: string) => {
|
||||||
const groupBtn = groupBtnRefs.value
|
const groupBtn = groupBtnRefs.value
|
||||||
.map((btn: ButtonInstance) => btn['ref'])
|
.map((btn: ButtonInstance) => btn['ref'])
|
||||||
.find((ref: Node) => ref.textContent === group)
|
.find((ref: HTMLButtonElement) => ref.textContent === group)
|
||||||
if (groupBtn) {
|
if (groupBtn) {
|
||||||
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
|
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,6 @@ function openModal() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
debugger
|
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { isNumber } from '@/utils/is'
|
import { isNumber } from '@/utils/is'
|
||||||
|
|
||||||
defineOptions({ name: 'Dialog' })
|
defineOptions({ name: 'Dialog' })
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
const emits = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: propTypes.bool.def(false),
|
modelValue: propTypes.bool.def(false),
|
||||||
|
|
@ -55,6 +57,17 @@ const dialogStyle = computed(() => {
|
||||||
height: unref(dialogHeight)
|
height: unref(dialogHeight)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const closing = ref(false)
|
||||||
|
|
||||||
|
function closeHandler() {
|
||||||
|
emits('update:modelValue', false)
|
||||||
|
closing.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function closedHandler() {
|
||||||
|
closing.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -68,7 +81,8 @@ const dialogStyle = computed(() => {
|
||||||
draggable
|
draggable
|
||||||
class="com-dialog"
|
class="com-dialog"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
@close="$emit('update:modelValue', false)"
|
@close="closeHandler"
|
||||||
|
@closed="closedHandler"
|
||||||
>
|
>
|
||||||
<template #header="{ close }">
|
<template #header="{ close }">
|
||||||
<div class="relative h-54px flex items-center justify-between pl-15px pr-15px">
|
<div class="relative h-54px flex items-center justify-between pl-15px pr-15px">
|
||||||
|
|
@ -102,7 +116,9 @@ const dialogStyle = computed(() => {
|
||||||
</ElScrollbar>
|
</ElScrollbar>
|
||||||
<slot v-else></slot>
|
<slot v-else></slot>
|
||||||
<template v-if="slots.footer" #footer>
|
<template v-if="slots.footer" #footer>
|
||||||
<slot name="footer"></slot>
|
<div :style="{ 'pointer-events': closing ? 'none' : 'auto' }">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ export interface CarouselProperty {
|
||||||
autoplay: boolean
|
autoplay: boolean
|
||||||
// 播放间隔
|
// 播放间隔
|
||||||
interval: number
|
interval: number
|
||||||
|
// 轮播高度
|
||||||
|
height: number
|
||||||
// 轮播内容
|
// 轮播内容
|
||||||
items: CarouselItemProperty[]
|
items: CarouselItemProperty[]
|
||||||
// 组件样式
|
// 组件样式
|
||||||
|
|
@ -37,6 +39,7 @@ export const component = {
|
||||||
indicator: 'dot',
|
indicator: 'dot',
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
interval: 3,
|
interval: 3,
|
||||||
|
height: 174,
|
||||||
items: [
|
items: [
|
||||||
{ type: 'img', imgUrl: 'https://static.iocoder.cn/mall/banner-01.jpg', videoUrl: '' },
|
{ type: 'img', imgUrl: 'https://static.iocoder.cn/mall/banner-01.jpg', videoUrl: '' },
|
||||||
{ type: 'img', imgUrl: 'https://static.iocoder.cn/mall/banner-02.jpg', videoUrl: '' }
|
{ type: 'img', imgUrl: 'https://static.iocoder.cn/mall/banner-02.jpg', videoUrl: '' }
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="relative">
|
<div v-else class="relative">
|
||||||
<el-carousel
|
<el-carousel
|
||||||
height="174px"
|
:height="property.height + 'px'"
|
||||||
:type="property.type === 'card' ? 'card' : ''"
|
:type="property.type === 'card' ? 'card' : ''"
|
||||||
:autoplay="property.autoplay"
|
:autoplay="property.autoplay"
|
||||||
:interval="property.interval * 1000"
|
:interval="property.interval * 1000"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="高度" prop="height">
|
||||||
|
<el-input-number class="!w-50% mr-10px" controls-position="right" v-model="formData.height" /> px
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="指示器" prop="indicator">
|
<el-form-item label="指示器" prop="indicator">
|
||||||
<el-radio-group v-model="formData.indicator">
|
<el-radio-group v-model="formData.indicator">
|
||||||
<el-radio value="dot">小圆点</el-radio>
|
<el-radio value="dot">小圆点</el-radio>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
|
<template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
|
||||||
<template v-if="selectedHotAreaIndex === cellIndex">
|
<template v-if="selectedHotAreaIndex === cellIndex">
|
||||||
<el-form-item :prop="`cell[${cellIndex}].type`" label="类型">
|
<el-form-item :prop="`cell[${cellIndex}].type`" label="类型">
|
||||||
<el-radio-group v-model="cell.type">
|
<el-radio-group v-model="cell.type" @change="handleHotAreaSelected(cell, cellIndex)">
|
||||||
<el-radio value="text">文字</el-radio>
|
<el-radio value="text">文字</el-radio>
|
||||||
<el-radio value="image">图片</el-radio>
|
<el-radio value="image">图片</el-radio>
|
||||||
<el-radio value="search">搜索框</el-radio>
|
<el-radio value="search">搜索框</el-radio>
|
||||||
|
|
@ -44,9 +44,32 @@
|
||||||
</template>
|
</template>
|
||||||
<!-- 3. 搜索框 -->
|
<!-- 3. 搜索框 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<el-form-item label="框体颜色" prop="backgroundColor">
|
||||||
|
<ColorInput v-model="cell.backgroundColor" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="lef" label="文本颜色" prop="textColor">
|
||||||
|
<ColorInput v-model="cell.textColor" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item :prop="`cell[${cellIndex}].placeholder`" label="提示文字">
|
<el-form-item :prop="`cell[${cellIndex}].placeholder`" label="提示文字">
|
||||||
<el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
|
<el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="文本位置" prop="placeholderPosition">
|
||||||
|
<el-radio-group v-model="cell!.placeholderPosition">
|
||||||
|
<el-tooltip content="居左" placement="top">
|
||||||
|
<el-radio-button value="left">
|
||||||
|
<Icon icon="ant-design:align-left-outlined" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="居中" placement="top">
|
||||||
|
<el-radio-button value="center">
|
||||||
|
<Icon icon="ant-design:align-center-outlined" />
|
||||||
|
</el-radio-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="扫一扫" prop="showScan">
|
||||||
|
<el-switch v-model="cell!.showScan" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item :prop="`cell[${cellIndex}].borderRadius`" label="圆角">
|
<el-form-item :prop="`cell[${cellIndex}].borderRadius`" label="圆角">
|
||||||
<el-slider
|
<el-slider
|
||||||
v-model="cell.borderRadius"
|
v-model="cell.borderRadius"
|
||||||
|
|
@ -88,10 +111,17 @@ const cellCount = computed(() => (props.isMp ? 6 : 8))
|
||||||
const selectedHotAreaIndex = ref(0)
|
const selectedHotAreaIndex = ref(0)
|
||||||
const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: number) => {
|
const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: number) => {
|
||||||
selectedHotAreaIndex.value = index
|
selectedHotAreaIndex.value = index
|
||||||
|
// 默认设置为选中文字,并设置属性
|
||||||
if (!cellValue.type) {
|
if (!cellValue.type) {
|
||||||
cellValue.type = 'text'
|
cellValue.type = 'text'
|
||||||
cellValue.textColor = '#111111'
|
cellValue.textColor = '#111111'
|
||||||
}
|
}
|
||||||
|
// 如果点击的是搜索框,则初始化搜索框的属性
|
||||||
|
if (cellValue.type === 'search') {
|
||||||
|
cellValue.placeholderPosition = 'left'
|
||||||
|
cellValue.backgroundColor = '#EEEEEE'
|
||||||
|
cellValue.textColor = '#969799'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,14 @@ export interface NavigationBarCellProperty {
|
||||||
imgUrl: string
|
imgUrl: string
|
||||||
// 图片链接
|
// 图片链接
|
||||||
url: string
|
url: string
|
||||||
|
// 搜索框:框体颜色
|
||||||
|
backgroundColor: string
|
||||||
// 搜索框:提示文字
|
// 搜索框:提示文字
|
||||||
placeholder: string
|
placeholder: string
|
||||||
|
// 搜索框:提示文字位置
|
||||||
|
placeholderPosition: string
|
||||||
|
// 搜索框:是否显示扫一扫
|
||||||
|
showScan: boolean
|
||||||
// 搜索框:边框圆角半径
|
// 搜索框:边框圆角半径
|
||||||
borderRadius: number
|
borderRadius: number
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,12 @@ const getCellStyle = (cell: NavigationBarCellProperty) => {
|
||||||
const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
|
const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
|
||||||
return {
|
return {
|
||||||
height: 30,
|
height: 30,
|
||||||
showScan: false,
|
backgroundColor: cell.backgroundColor,
|
||||||
|
showScan: cell.showScan,
|
||||||
placeholder: cell.placeholder,
|
placeholder: cell.placeholder,
|
||||||
borderRadius: cell.borderRadius
|
borderRadius: cell.borderRadius,
|
||||||
|
textColor: cell.textColor,
|
||||||
|
placeholderPosition: cell.placeholderPosition
|
||||||
} as SearchProperty
|
} as SearchProperty
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,11 @@ watch(
|
||||||
if (!val || selectedComponentIndex.value === -1) {
|
if (!val || selectedComponentIndex.value === -1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 如果是基础设置页,默认选中的索引改成 -1,为了防止删除组件后切换到此页导致报错
|
||||||
|
// https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/792
|
||||||
|
if (props.showTabBar) {
|
||||||
|
selectedComponentIndex.value = -1
|
||||||
|
}
|
||||||
pageComponents.value[selectedComponentIndex.value] = selectedComponent.value!
|
pageComponents.value[selectedComponentIndex.value] = selectedComponent.value!
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ watch(
|
||||||
(options) => {
|
(options) => {
|
||||||
if (echartRef) {
|
if (echartRef) {
|
||||||
echartRef?.setOption(options)
|
echartRef?.setOption(options)
|
||||||
|
echartRef?.resize()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,9 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
message.success('正在上传文件,请稍候...')
|
message.success('正在上传文件,请稍候...')
|
||||||
|
// 只有在验证通过后才增加计数器
|
||||||
uploadNumber.value++
|
uploadNumber.value++
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
// 处理上传的文件发生变化
|
// 处理上传的文件发生变化
|
||||||
// const handleFileChange = (uploadFile: UploadFile): void => {
|
// const handleFileChange = (uploadFile: UploadFile): void => {
|
||||||
|
|
@ -149,6 +151,8 @@ const handleExceed: UploadProps['onExceed'] = (): void => {
|
||||||
// 上传错误提示
|
// 上传错误提示
|
||||||
const excelUploadError: UploadProps['onError'] = (): void => {
|
const excelUploadError: UploadProps['onError'] = (): void => {
|
||||||
message.error('导入数据失败,请您重新上传!')
|
message.error('导入数据失败,请您重新上传!')
|
||||||
|
// 上传失败时减少计数器,避免后续上传被阻塞
|
||||||
|
uploadNumber.value = Math.max(0, uploadNumber.value - 1)
|
||||||
}
|
}
|
||||||
// 删除上传文件
|
// 删除上传文件
|
||||||
const handleRemove = (file: UploadFile) => {
|
const handleRemove = (file: UploadFile) => {
|
||||||
|
|
|
||||||
|
|
@ -97,20 +97,28 @@ const uploadList = ref<UploadUserFile[]>([])
|
||||||
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||||
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
|
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
|
||||||
const imgType = props.fileType
|
const imgType = props.fileType
|
||||||
if (!imgType.includes(rawFile.type as FileTypes))
|
const isValidType = imgType.includes(rawFile.type as FileTypes)
|
||||||
|
const isValidSize = imgSize
|
||||||
|
|
||||||
|
if (!isValidType)
|
||||||
ElNotification({
|
ElNotification({
|
||||||
title: '温馨提示',
|
title: '温馨提示',
|
||||||
message: '上传图片不符合所需的格式!',
|
message: '上传图片不符合所需的格式!',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
if (!imgSize)
|
if (!isValidSize)
|
||||||
ElNotification({
|
ElNotification({
|
||||||
title: '温馨提示',
|
title: '温馨提示',
|
||||||
message: `上传图片大小不能超过 ${props.fileSize}M!`,
|
message: `上传图片大小不能超过 ${props.fileSize}M!`,
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
})
|
||||||
uploadNumber.value++
|
|
||||||
return imgType.includes(rawFile.type as FileTypes) && imgSize
|
// 只有在验证通过后才增加计数器
|
||||||
|
if (isValidType && isValidSize) {
|
||||||
|
uploadNumber.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValidType && isValidSize
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片上传成功
|
// 图片上传成功
|
||||||
|
|
@ -172,6 +180,8 @@ const uploadError = () => {
|
||||||
message: '图片上传失败,请您重新上传!',
|
message: '图片上传失败,请您重新上传!',
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
|
// 上传失败时减少计数器,避免后续上传被阻塞
|
||||||
|
uploadNumber.value = Math.max(0, uploadNumber.value - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件数超出提示
|
// 文件数超出提示
|
||||||
|
|
|
||||||
|
|
@ -1014,6 +1014,16 @@
|
||||||
"name": "fields",
|
"name": "fields",
|
||||||
"type": "Field",
|
"type": "Field",
|
||||||
"isMany": true
|
"isMany": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"type": "String",
|
||||||
|
"isAttr": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventDefinitions",
|
||||||
|
"type": "bpmn:TimerEventDefinition",
|
||||||
|
"isMany": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -23,22 +23,20 @@
|
||||||
|
|
||||||
export default function customTranslate(translations) {
|
export default function customTranslate(translations) {
|
||||||
return function (template, replacements) {
|
return function (template, replacements) {
|
||||||
replacements = replacements || {}
|
replacements = replacements || {};
|
||||||
// Translate
|
// 将模板和翻译字典的键统一转换为小写进行匹配
|
||||||
template = translations[template] || template
|
const lowerTemplate = template.toLowerCase();
|
||||||
|
const translation = Object.keys(translations).find(key => key.toLowerCase() === lowerTemplate);
|
||||||
|
|
||||||
// Replace
|
// 如果找到匹配的翻译,使用翻译后的模板
|
||||||
|
if (translation) {
|
||||||
|
template = translations[translation];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换模板中的占位符
|
||||||
return template.replace(/{([^}]+)}/g, function (_, key) {
|
return template.replace(/{([^}]+)}/g, function (_, key) {
|
||||||
let str = replacements[key]
|
// 如果替换值存在,返回替换值;否则返回原始占位符
|
||||||
if (
|
return replacements[key] !== undefined ? replacements[key] : `{${key}}`;
|
||||||
translations[replacements[key]] !== null &&
|
});
|
||||||
translations[replacements[key]] !== undefined
|
};
|
||||||
) {
|
|
||||||
// eslint-disable-next-line no-mixed-spaces-and-tabs
|
|
||||||
str = translations[replacements[key]]
|
|
||||||
// eslint-disable-next-line no-mixed-spaces-and-tabs
|
|
||||||
}
|
|
||||||
return str || '{' + key + '}'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +45,8 @@ export default {
|
||||||
'Service Task': '服务任务',
|
'Service Task': '服务任务',
|
||||||
'Script Task': '脚本任务',
|
'Script Task': '脚本任务',
|
||||||
'Call Activity': '调用活动',
|
'Call Activity': '调用活动',
|
||||||
'Sub Process (collapsed)': '子流程(折叠的)',
|
'Sub-Process (collapsed)': '子流程(折叠的)',
|
||||||
'Sub Process (expanded)': '子流程(展开的)',
|
'Sub-Process (expanded)': '子流程(展开的)',
|
||||||
'Start Event': '开始事件',
|
'Start Event': '开始事件',
|
||||||
StartEvent: '开始事件',
|
StartEvent: '开始事件',
|
||||||
'Intermediate Throw Event': '中间事件',
|
'Intermediate Throw Event': '中间事件',
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,11 @@
|
||||||
:business-object="elementBusinessObject"
|
:business-object="elementBusinessObject"
|
||||||
/>
|
/>
|
||||||
</el-collapse-item>
|
</el-collapse-item>
|
||||||
|
<!-- 新增的时间事件配置项 -->
|
||||||
|
<el-collapse-item v-if="elementType === 'IntermediateCatchEvent'" name="timeEvent">
|
||||||
|
<template #title><Icon icon="ep:timer" />时间事件</template>
|
||||||
|
<TimeEventConfig :businessObject="bpmnElement.value?.businessObject" :key="elementId" />
|
||||||
|
</el-collapse-item>
|
||||||
</el-collapse>
|
</el-collapse>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -83,6 +88,8 @@ import ElementProperties from './properties/ElementProperties.vue'
|
||||||
// import ElementForm from './form/ElementForm.vue'
|
// import ElementForm from './form/ElementForm.vue'
|
||||||
import UserTaskListeners from './listeners/UserTaskListeners.vue'
|
import UserTaskListeners from './listeners/UserTaskListeners.vue'
|
||||||
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data'
|
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data'
|
||||||
|
import TimeEventConfig from './time-event-config/TimeEventConfig.vue'
|
||||||
|
import { ref, watch, onMounted } from 'vue'
|
||||||
|
|
||||||
defineOptions({ name: 'MyPropertiesPanel' })
|
defineOptions({ name: 'MyPropertiesPanel' })
|
||||||
|
|
||||||
|
|
@ -121,6 +128,8 @@ const formVisible = ref(false) // 表单配置
|
||||||
const bpmnElement = ref()
|
const bpmnElement = ref()
|
||||||
const isReady = ref(false)
|
const isReady = ref(false)
|
||||||
|
|
||||||
|
const type = ref('time')
|
||||||
|
const condition = ref('')
|
||||||
provide('prefix', props.prefix)
|
provide('prefix', props.prefix)
|
||||||
provide('width', props.width)
|
provide('width', props.width)
|
||||||
|
|
||||||
|
|
@ -141,7 +150,7 @@ const initBpmnInstances = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查所有实例是否都存在
|
// 检查所有实例是否都存在
|
||||||
const allInstancesExist = Object.values(instances).every(instance => instance)
|
const allInstancesExist = Object.values(instances).every((instance) => instance)
|
||||||
if (allInstancesExist) {
|
if (allInstancesExist) {
|
||||||
const w = window as any
|
const w = window as any
|
||||||
w.bpmnInstances = instances
|
w.bpmnInstances = instances
|
||||||
|
|
@ -255,4 +264,48 @@ watch(
|
||||||
activeTab.value = 'base'
|
activeTab.value = 'base'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function updateNode() {
|
||||||
|
const moddle = window.bpmnInstances?.moddle
|
||||||
|
const modeling = window.bpmnInstances?.modeling
|
||||||
|
const elementRegistry = window.bpmnInstances?.elementRegistry
|
||||||
|
if (!moddle || !modeling || !elementRegistry) return
|
||||||
|
|
||||||
|
const element = elementRegistry.get(props.businessObject.id)
|
||||||
|
if (!element) return
|
||||||
|
|
||||||
|
let timerDef = moddle.create('bpmn:TimerEventDefinition', {})
|
||||||
|
if (type.value === 'time') {
|
||||||
|
timerDef.timeDate = moddle.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
} else if (type.value === 'duration') {
|
||||||
|
timerDef.timeDuration = moddle.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
} else if (type.value === 'cycle') {
|
||||||
|
timerDef.timeCycle = moddle.create('bpmn:FormalExpression', { body: condition.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
modeling.updateModdleProperties(element, element.businessObject, {
|
||||||
|
eventDefinitions: [timerDef]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化和监听
|
||||||
|
function syncFromBusinessObject() {
|
||||||
|
if (props.businessObject) {
|
||||||
|
const timerDef = (props.businessObject.eventDefinitions || [])[0]
|
||||||
|
if (timerDef) {
|
||||||
|
if (timerDef.timeDate) {
|
||||||
|
type.value = 'time'
|
||||||
|
condition.value = timerDef.timeDate.body
|
||||||
|
} else if (timerDef.timeDuration) {
|
||||||
|
type.value = 'duration'
|
||||||
|
condition.value = timerDef.timeDuration.body
|
||||||
|
} else if (timerDef.timeCycle) {
|
||||||
|
type.value = 'cycle'
|
||||||
|
condition.value = timerDef.timeCycle.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(syncFromBusinessObject)
|
||||||
|
watch(() => props.businessObject, syncFromBusinessObject, { deep: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,12 @@
|
||||||
v-model="timeDuration"
|
v-model="timeDuration"
|
||||||
:min="1"
|
:min="1"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
@change="() => updateTimeModdle()"
|
@change="
|
||||||
|
() => {
|
||||||
|
updateTimeModdle()
|
||||||
|
updateElementExtensions()
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-select
|
<el-select
|
||||||
|
|
@ -55,7 +60,12 @@
|
||||||
v-model="maxRemindCount"
|
v-model="maxRemindCount"
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="10"
|
:max="10"
|
||||||
@change="() => updateTimeModdle()"
|
@change="
|
||||||
|
() => {
|
||||||
|
updateTimeModdle()
|
||||||
|
updateElementExtensions()
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -65,7 +75,7 @@
|
||||||
import {
|
import {
|
||||||
TimeUnitType,
|
TimeUnitType,
|
||||||
TIME_UNIT_TYPES,
|
TIME_UNIT_TYPES,
|
||||||
TIMEOUT_HANDLER_TYPES,
|
TIMEOUT_HANDLER_TYPES
|
||||||
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
} from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
import { convertTimeUnit } from '@/components/SimpleProcessDesignerV2/src/utils'
|
import { convertTimeUnit } from '@/components/SimpleProcessDesignerV2/src/utils'
|
||||||
|
|
||||||
|
|
@ -195,6 +205,7 @@ const onTimeUnitChange = () => {
|
||||||
timeDuration.value = 1
|
timeDuration.value = 1
|
||||||
}
|
}
|
||||||
updateTimeModdle()
|
updateTimeModdle()
|
||||||
|
updateElementExtensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTimeModdle = () => {
|
const updateTimeModdle = () => {
|
||||||
|
|
|
||||||
|
|
@ -354,12 +354,13 @@ const resetTaskForm = () => {
|
||||||
const changeCandidateStrategy = () => {
|
const changeCandidateStrategy = () => {
|
||||||
userTaskForm.value.candidateParam = []
|
userTaskForm.value.candidateParam = []
|
||||||
deptLevel.value = 1
|
deptLevel.value = 1
|
||||||
if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
|
// 注释 by 芋艿:这个交互很多用户反馈费解,https://t.zsxq.com/xNmas 所以暂时屏蔽
|
||||||
// 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
|
// if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
|
||||||
if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
|
// // 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
|
||||||
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
|
// if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
|
||||||
}
|
// userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
updateElementTask()
|
updateElementTask()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
<template>
|
||||||
|
<el-tabs v-model="tab">
|
||||||
|
<el-tab-pane label="CRON表达式" name="cron">
|
||||||
|
<div style="margin-bottom: 10px">
|
||||||
|
<el-input
|
||||||
|
v-model="cronStr"
|
||||||
|
readonly
|
||||||
|
style="width: 400px; font-weight: bold"
|
||||||
|
:key="'cronStr'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; margin-bottom: 8px">
|
||||||
|
<el-input v-model="fields.second" placeholder="秒" style="width: 80px" :key="'second'" />
|
||||||
|
<el-input v-model="fields.minute" placeholder="分" style="width: 80px" :key="'minute'" />
|
||||||
|
<el-input v-model="fields.hour" placeholder="时" style="width: 80px" :key="'hour'" />
|
||||||
|
<el-input v-model="fields.day" placeholder="天" style="width: 80px" :key="'day'" />
|
||||||
|
<el-input v-model="fields.month" placeholder="月" style="width: 80px" :key="'month'" />
|
||||||
|
<el-input v-model="fields.week" placeholder="周" style="width: 80px" :key="'week'" />
|
||||||
|
<el-input v-model="fields.year" placeholder="年" style="width: 80px" :key="'year'" />
|
||||||
|
</div>
|
||||||
|
<el-tabs v-model="activeField" type="card" style="margin-bottom: 8px">
|
||||||
|
<el-tab-pane v-for="f in cronFieldList" :label="f.label" :name="f.key" :key="f.key">
|
||||||
|
<div style="margin-bottom: 8px">
|
||||||
|
<el-radio-group v-model="cronMode[f.key]" :key="'radio-' + f.key">
|
||||||
|
<el-radio label="every" :key="'every-' + f.key">每{{ f.label }}</el-radio>
|
||||||
|
<el-radio label="range" :key="'range-' + f.key"
|
||||||
|
>从
|
||||||
|
<el-input-number
|
||||||
|
v-model="cronRange[f.key][0]"
|
||||||
|
:min="f.min"
|
||||||
|
:max="f.max"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px"
|
||||||
|
:key="'range0-' + f.key"
|
||||||
|
/>
|
||||||
|
到
|
||||||
|
<el-input-number
|
||||||
|
v-model="cronRange[f.key][1]"
|
||||||
|
:min="f.min"
|
||||||
|
:max="f.max"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px"
|
||||||
|
:key="'range1-' + f.key"
|
||||||
|
/>
|
||||||
|
之间每{{ f.label }}</el-radio
|
||||||
|
>
|
||||||
|
<el-radio label="step" :key="'step-' + f.key"
|
||||||
|
>从第
|
||||||
|
<el-input-number
|
||||||
|
v-model="cronStep[f.key][0]"
|
||||||
|
:min="f.min"
|
||||||
|
:max="f.max"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px"
|
||||||
|
:key="'step0-' + f.key"
|
||||||
|
/>
|
||||||
|
开始每
|
||||||
|
<el-input-number
|
||||||
|
v-model="cronStep[f.key][1]"
|
||||||
|
:min="1"
|
||||||
|
:max="f.max"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px"
|
||||||
|
:key="'step1-' + f.key"
|
||||||
|
/>
|
||||||
|
{{ f.label }}</el-radio
|
||||||
|
>
|
||||||
|
<el-radio label="appoint" :key="'appoint-' + f.key">指定</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
<div v-if="cronMode[f.key] === 'appoint'">
|
||||||
|
<el-checkbox-group v-model="cronAppoint[f.key]" :key="'group-' + f.key">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="n in f.max + 1"
|
||||||
|
:label="pad(n - 1)"
|
||||||
|
:key="'cb-' + f.key + '-' + (n - 1)"
|
||||||
|
>{{ pad(n - 1) }}</el-checkbox
|
||||||
|
>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="标准格式" name="iso" :key="'iso-tab'">
|
||||||
|
<div style="margin-bottom: 10px">
|
||||||
|
<el-input
|
||||||
|
v-model="isoStr"
|
||||||
|
placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S"
|
||||||
|
style="width: 400px; font-weight: bold"
|
||||||
|
:key="'isoStr'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 10px"
|
||||||
|
>循环次数:<el-input-number v-model="repeat" :min="1" style="width: 100px" :key="'repeat'"
|
||||||
|
/></div>
|
||||||
|
<div style="margin-bottom: 10px"
|
||||||
|
>日期时间:<el-date-picker
|
||||||
|
v-model="isoDate"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="选择日期时间"
|
||||||
|
style="width: 200px"
|
||||||
|
:key="'isoDate'"
|
||||||
|
/></div>
|
||||||
|
<div style="margin-bottom: 10px"
|
||||||
|
>当前时长:<el-input
|
||||||
|
v-model="isoDuration"
|
||||||
|
placeholder="如P3DT30M30S"
|
||||||
|
style="width: 200px"
|
||||||
|
:key="'isoDuration'"
|
||||||
|
/></div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
>秒:<el-button
|
||||||
|
v-for="s in [5, 10, 30, 50]"
|
||||||
|
@click="setDuration('S', s)"
|
||||||
|
:key="'sec-' + s"
|
||||||
|
>{{ s }}</el-button
|
||||||
|
>自定义</div
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
>分:<el-button
|
||||||
|
v-for="m in [5, 10, 30, 50]"
|
||||||
|
@click="setDuration('M', m)"
|
||||||
|
:key="'min-' + m"
|
||||||
|
>{{ m }}</el-button
|
||||||
|
>自定义</div
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
>小时:<el-button
|
||||||
|
v-for="h in [4, 8, 12, 24]"
|
||||||
|
@click="setDuration('H', h)"
|
||||||
|
:key="'hour-' + h"
|
||||||
|
>{{ h }}</el-button
|
||||||
|
>自定义</div
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
>天:<el-button
|
||||||
|
v-for="d in [1, 2, 3, 4]"
|
||||||
|
@click="setDuration('D', d)"
|
||||||
|
:key="'day-' + d"
|
||||||
|
>{{ d }}</el-button
|
||||||
|
>自定义</div
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
>月:<el-button
|
||||||
|
v-for="mo in [1, 2, 3, 4]"
|
||||||
|
@click="setDuration('M', mo)"
|
||||||
|
:key="'mon-' + mo"
|
||||||
|
>{{ mo }}</el-button
|
||||||
|
>自定义</div
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
>年:<el-button
|
||||||
|
v-for="y in [1, 2, 3, 4]"
|
||||||
|
@click="setDuration('Y', y)"
|
||||||
|
:key="'year-' + y"
|
||||||
|
>{{ y }}</el-button
|
||||||
|
>自定义</div
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
const props = defineProps({ value: String })
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const tab = ref('cron')
|
||||||
|
const cronStr = ref(props.value || '* * * * * ?')
|
||||||
|
const fields = ref({
|
||||||
|
second: '*',
|
||||||
|
minute: '*',
|
||||||
|
hour: '*',
|
||||||
|
day: '*',
|
||||||
|
month: '*',
|
||||||
|
week: '?',
|
||||||
|
year: ''
|
||||||
|
})
|
||||||
|
const cronFieldList = [
|
||||||
|
{ key: 'second', label: '秒', min: 0, max: 59 },
|
||||||
|
{ key: 'minute', label: '分', min: 0, max: 59 },
|
||||||
|
{ key: 'hour', label: '时', min: 0, max: 23 },
|
||||||
|
{ key: 'day', label: '天', min: 1, max: 31 },
|
||||||
|
{ key: 'month', label: '月', min: 1, max: 12 },
|
||||||
|
{ key: 'week', label: '周', min: 1, max: 7 },
|
||||||
|
{ key: 'year', label: '年', min: 1970, max: 2099 }
|
||||||
|
]
|
||||||
|
const activeField = ref('second')
|
||||||
|
const cronMode = ref({
|
||||||
|
second: 'appoint',
|
||||||
|
minute: 'every',
|
||||||
|
hour: 'every',
|
||||||
|
day: 'every',
|
||||||
|
month: 'every',
|
||||||
|
week: 'every',
|
||||||
|
year: 'every'
|
||||||
|
})
|
||||||
|
const cronAppoint = ref({
|
||||||
|
second: ['00', '01'],
|
||||||
|
minute: [],
|
||||||
|
hour: [],
|
||||||
|
day: [],
|
||||||
|
month: [],
|
||||||
|
week: [],
|
||||||
|
year: []
|
||||||
|
})
|
||||||
|
const cronRange = ref({
|
||||||
|
second: [0, 1],
|
||||||
|
minute: [0, 1],
|
||||||
|
hour: [0, 1],
|
||||||
|
day: [1, 2],
|
||||||
|
month: [1, 2],
|
||||||
|
week: [1, 2],
|
||||||
|
year: [1970, 1971]
|
||||||
|
})
|
||||||
|
const cronStep = ref({
|
||||||
|
second: [1, 1],
|
||||||
|
minute: [1, 1],
|
||||||
|
hour: [1, 1],
|
||||||
|
day: [1, 1],
|
||||||
|
month: [1, 1],
|
||||||
|
week: [1, 1],
|
||||||
|
year: [1970, 1]
|
||||||
|
})
|
||||||
|
|
||||||
|
function pad(n) {
|
||||||
|
return n < 10 ? '0' + n : '' + n
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[fields, cronMode, cronAppoint, cronRange, cronStep],
|
||||||
|
() => {
|
||||||
|
// 组装cron表达式
|
||||||
|
let arr = cronFieldList.map((f) => {
|
||||||
|
if (cronMode.value[f.key] === 'every') return '*'
|
||||||
|
if (cronMode.value[f.key] === 'appoint') return cronAppoint.value[f.key].join(',') || '*'
|
||||||
|
if (cronMode.value[f.key] === 'range')
|
||||||
|
return `${cronRange.value[f.key][0]}-${cronRange.value[f.key][1]}`
|
||||||
|
if (cronMode.value[f.key] === 'step')
|
||||||
|
return `${cronStep.value[f.key][0]}/${cronStep.value[f.key][1]}`
|
||||||
|
return fields.value[f.key] || '*'
|
||||||
|
})
|
||||||
|
// week和year特殊处理
|
||||||
|
arr[5] = arr[5] || '?'
|
||||||
|
cronStr.value = arr.join(' ')
|
||||||
|
if (tab.value === 'cron') emit('change', cronStr.value)
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 标准格式
|
||||||
|
const isoStr = ref('')
|
||||||
|
const repeat = ref(1)
|
||||||
|
const isoDate = ref('')
|
||||||
|
const isoDuration = ref('')
|
||||||
|
function setDuration(type, val) {
|
||||||
|
// 组装ISO 8601字符串
|
||||||
|
let d = isoDuration.value
|
||||||
|
if (!d.includes(type)) d += val + type
|
||||||
|
else d = d.replace(new RegExp(`\\d+${type}`), val + type)
|
||||||
|
isoDuration.value = d
|
||||||
|
updateIsoStr()
|
||||||
|
}
|
||||||
|
function updateIsoStr() {
|
||||||
|
let str = `R${repeat.value}`
|
||||||
|
if (isoDate.value)
|
||||||
|
str +=
|
||||||
|
'/' +
|
||||||
|
(typeof isoDate.value === 'string' ? isoDate.value : new Date(isoDate.value).toISOString())
|
||||||
|
if (isoDuration.value) str += '/' + isoDuration.value
|
||||||
|
isoStr.value = str
|
||||||
|
if (tab.value === 'iso') emit('change', isoStr.value)
|
||||||
|
}
|
||||||
|
watch([repeat, isoDate, isoDuration], updateIsoStr)
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(val) => {
|
||||||
|
if (!val) return
|
||||||
|
if (tab.value === 'cron') cronStr.value = val
|
||||||
|
if (tab.value === 'iso') isoStr.value = val
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div style="margin-bottom: 10px"
|
||||||
|
>当前选择:<el-input v-model="isoString" readonly style="width: 300px"
|
||||||
|
/></div>
|
||||||
|
<div v-for="unit in units" :key="unit.key" style="margin-bottom: 8px">
|
||||||
|
<span>{{ unit.label }}:</span>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button
|
||||||
|
v-for="val in unit.presets"
|
||||||
|
:key="val"
|
||||||
|
size="mini"
|
||||||
|
@click="setUnit(unit.key, val)"
|
||||||
|
>{{ val }}</el-button
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model.number="custom[unit.key]"
|
||||||
|
size="mini"
|
||||||
|
style="width: 60px; margin-left: 8px"
|
||||||
|
placeholder="自定义"
|
||||||
|
@change="setUnit(unit.key, custom[unit.key])"
|
||||||
|
/>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
const props = defineProps({ value: String })
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const units = [
|
||||||
|
{ key: 'Y', label: '年', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'M', label: '月', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'D', label: '天', presets: [1, 2, 3, 4] },
|
||||||
|
{ key: 'H', label: '时', presets: [4, 8, 12, 24] },
|
||||||
|
{ key: 'm', label: '分', presets: [5, 10, 30, 50] },
|
||||||
|
{ key: 'S', label: '秒', presets: [5, 10, 30, 50] }
|
||||||
|
]
|
||||||
|
const custom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' })
|
||||||
|
const isoString = ref('')
|
||||||
|
|
||||||
|
function setUnit(key, val) {
|
||||||
|
if (!val || isNaN(val)) {
|
||||||
|
custom.value[key] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
custom.value[key] = val
|
||||||
|
updateIsoString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateIsoString() {
|
||||||
|
let str = 'P'
|
||||||
|
if (custom.value.Y) str += custom.value.Y + 'Y'
|
||||||
|
if (custom.value.M) str += custom.value.M + 'M'
|
||||||
|
if (custom.value.D) str += custom.value.D + 'D'
|
||||||
|
if (custom.value.H || custom.value.m || custom.value.S) str += 'T'
|
||||||
|
if (custom.value.H) str += custom.value.H + 'H'
|
||||||
|
if (custom.value.m) str += custom.value.m + 'M'
|
||||||
|
if (custom.value.S) str += custom.value.S + 'S'
|
||||||
|
isoString.value = str === 'P' ? '' : str
|
||||||
|
emit('change', isoString.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(val) => {
|
||||||
|
if (!val) return
|
||||||
|
// 解析ISO 8601字符串到custom
|
||||||
|
const match = val.match(
|
||||||
|
/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/
|
||||||
|
)
|
||||||
|
if (match) {
|
||||||
|
custom.value.Y = match[1] || ''
|
||||||
|
custom.value.M = match[2] || ''
|
||||||
|
custom.value.D = match[3] || ''
|
||||||
|
custom.value.H = match[4] || ''
|
||||||
|
custom.value.m = match[5] || ''
|
||||||
|
custom.value.S = match[6] || ''
|
||||||
|
updateIsoString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,312 @@
|
||||||
|
<template>
|
||||||
|
<div class="panel-tab__content">
|
||||||
|
<div style="margin-top: 10px">
|
||||||
|
<span>类型:</span>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button size="mini" :type="type === 'time' ? 'primary' : ''" @click="setType('time')"
|
||||||
|
>时间</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:type="type === 'duration' ? 'primary' : ''"
|
||||||
|
@click="setType('duration')"
|
||||||
|
>持续</el-button
|
||||||
|
>
|
||||||
|
<el-button size="mini" :type="type === 'cycle' ? 'primary' : ''" @click="setType('cycle')"
|
||||||
|
>循环</el-button
|
||||||
|
>
|
||||||
|
</el-button-group>
|
||||||
|
<el-icon v-if="valid" color="green" style="margin-left: 8px"><CircleCheckFilled /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 10px; display: flex; align-items: center">
|
||||||
|
<span>条件:</span>
|
||||||
|
<el-input
|
||||||
|
v-model="condition"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
style="width: calc(100% - 100px)"
|
||||||
|
:readonly="type !== 'duration' && type !== 'cycle'"
|
||||||
|
@focus="handleInputFocus"
|
||||||
|
@blur="updateNode"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<el-tooltip v-if="!valid" content="格式错误" placement="top">
|
||||||
|
<el-icon color="orange"><WarningFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip :content="helpText" placement="top">
|
||||||
|
<el-icon color="#409EFF" style="cursor: pointer" @click="showHelp = true"
|
||||||
|
><QuestionFilled
|
||||||
|
/></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button
|
||||||
|
v-if="type === 'time'"
|
||||||
|
@click="showDatePicker = true"
|
||||||
|
style="margin-left: 4px"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:calendar" />
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="type === 'duration'"
|
||||||
|
@click="showDurationDialog = true"
|
||||||
|
style="margin-left: 4px"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:timer" />
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="type === 'cycle'"
|
||||||
|
@click="showCycleDialog = true"
|
||||||
|
style="margin-left: 4px"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:setting" />
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<!-- 时间选择器 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showDatePicker"
|
||||||
|
title="选择时间"
|
||||||
|
width="400px"
|
||||||
|
@close="showDatePicker = false"
|
||||||
|
>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateValue"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="选择日期时间"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="onDateChange"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showDatePicker = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onDateConfirm">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 持续时长选择器 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showDurationDialog"
|
||||||
|
title="时间配置"
|
||||||
|
width="600px"
|
||||||
|
@close="showDurationDialog = false"
|
||||||
|
>
|
||||||
|
<DurationConfig :value="condition" @change="onDurationChange" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showDurationDialog = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onDurationConfirm">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 循环配置器 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="showCycleDialog"
|
||||||
|
title="时间配置"
|
||||||
|
width="800px"
|
||||||
|
@close="showCycleDialog = false"
|
||||||
|
>
|
||||||
|
<CycleConfig :value="condition" @change="onCycleChange" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showCycleDialog = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onCycleConfirm">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 帮助说明 -->
|
||||||
|
<el-dialog v-model="showHelp" title="格式说明" width="600px" @close="showHelp = false">
|
||||||
|
<div v-html="helpHtml"></div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showHelp = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
|
import { CircleCheckFilled, WarningFilled, QuestionFilled } from '@element-plus/icons-vue'
|
||||||
|
import DurationConfig from './DurationConfig.vue'
|
||||||
|
import CycleConfig from './CycleConfig.vue'
|
||||||
|
import { createListenerObject, updateElementExtensions } from '../../utils'
|
||||||
|
const bpmnInstances = () => (window as any).bpmnInstances
|
||||||
|
const props = defineProps({ businessObject: Object })
|
||||||
|
const type = ref('time')
|
||||||
|
const condition = ref('')
|
||||||
|
const valid = ref(true)
|
||||||
|
const showDatePicker = ref(false)
|
||||||
|
const showDurationDialog = ref(false)
|
||||||
|
const showCycleDialog = ref(false)
|
||||||
|
const showHelp = ref(false)
|
||||||
|
const dateValue = ref(null)
|
||||||
|
const bpmnElement = ref(null)
|
||||||
|
|
||||||
|
const placeholder = computed(() => {
|
||||||
|
if (type.value === 'time') return '请输入时间'
|
||||||
|
if (type.value === 'duration') return '请输入持续时长'
|
||||||
|
if (type.value === 'cycle') return '请输入循环表达式'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const helpText = computed(() => {
|
||||||
|
if (type.value === 'time') return '选择具体时间'
|
||||||
|
if (type.value === 'duration') return 'ISO 8601格式,如PT1H'
|
||||||
|
if (type.value === 'cycle') return 'CRON表达式或ISO 8601周期'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
const helpHtml = computed(() => {
|
||||||
|
if (type.value === 'duration') {
|
||||||
|
return `指定定时器之前要等待多长时间。S表示秒,M表示分,D表示天;P表示时间段,T表示精确到时间的时间段。<br>
|
||||||
|
时间格式依然为ISO 8601格式,一年两个月三天四小时五分六秒内,可以写成P1Y2M3DT4H5M6S。<br>
|
||||||
|
P是开始标记,T是时间和日期分割标记,没有日期只有时间T是不能省去的,比如1小时执行一次应写成PT1H。`
|
||||||
|
}
|
||||||
|
if (type.value === 'cycle') {
|
||||||
|
return `支持CRON表达式(如0 0/30 * * * ?)或ISO 8601周期(如R3/PT10M)。`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化和监听
|
||||||
|
function syncFromBusinessObject() {
|
||||||
|
if (props.businessObject) {
|
||||||
|
const timerDef = (props.businessObject.eventDefinitions || [])[0]
|
||||||
|
if (timerDef) {
|
||||||
|
if (timerDef.timeDate) {
|
||||||
|
type.value = 'time'
|
||||||
|
condition.value = timerDef.timeDate.body
|
||||||
|
} else if (timerDef.timeDuration) {
|
||||||
|
type.value = 'duration'
|
||||||
|
condition.value = timerDef.timeDuration.body
|
||||||
|
} else if (timerDef.timeCycle) {
|
||||||
|
type.value = 'cycle'
|
||||||
|
condition.value = timerDef.timeCycle.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(syncFromBusinessObject)
|
||||||
|
|
||||||
|
// 切换类型
|
||||||
|
function setType(t) {
|
||||||
|
type.value = t
|
||||||
|
condition.value = ''
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入校验
|
||||||
|
watch([type, condition], () => {
|
||||||
|
valid.value = validate()
|
||||||
|
// updateNode() // 可以注释掉,避免频繁触发
|
||||||
|
})
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
if (type.value === 'time') {
|
||||||
|
return !!condition.value && !isNaN(Date.parse(condition.value))
|
||||||
|
}
|
||||||
|
if (type.value === 'duration') {
|
||||||
|
return /^P.*$/.test(condition.value)
|
||||||
|
}
|
||||||
|
if (type.value === 'cycle') {
|
||||||
|
return /^([0-9*\/?, ]+|R\d*\/P.*)$/.test(condition.value)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择时间
|
||||||
|
function onDateChange(val) {
|
||||||
|
dateValue.value = val
|
||||||
|
}
|
||||||
|
function onDateConfirm() {
|
||||||
|
if (dateValue.value) {
|
||||||
|
condition.value = new Date(dateValue.value).toISOString()
|
||||||
|
showDatePicker.value = false
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 持续时长
|
||||||
|
function onDurationChange(val) {
|
||||||
|
condition.value = val
|
||||||
|
}
|
||||||
|
function onDurationConfirm() {
|
||||||
|
showDurationDialog.value = false
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环
|
||||||
|
function onCycleChange(val) {
|
||||||
|
condition.value = val
|
||||||
|
}
|
||||||
|
function onCycleConfirm() {
|
||||||
|
showCycleDialog.value = false
|
||||||
|
updateNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入框聚焦时弹窗(可选)
|
||||||
|
function handleInputFocus() {
|
||||||
|
if (type.value === 'time') showDatePicker.value = true
|
||||||
|
if (type.value === 'duration') showDurationDialog.value = true
|
||||||
|
if (type.value === 'cycle') showCycleDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步到节点
|
||||||
|
function updateNode() {
|
||||||
|
const moddle = window.bpmnInstances?.moddle
|
||||||
|
const modeling = window.bpmnInstances?.modeling
|
||||||
|
const elementRegistry = window.bpmnInstances?.elementRegistry
|
||||||
|
if (!moddle || !modeling || !elementRegistry) return
|
||||||
|
|
||||||
|
// 获取元素
|
||||||
|
if (!props.businessObject || !props.businessObject.id) return
|
||||||
|
const element = elementRegistry.get(props.businessObject.id)
|
||||||
|
if (!element) return
|
||||||
|
|
||||||
|
// 1. 复用原有 timerDef,或新建
|
||||||
|
let timerDef =
|
||||||
|
element.businessObject.eventDefinitions && element.businessObject.eventDefinitions[0]
|
||||||
|
if (!timerDef) {
|
||||||
|
timerDef = bpmnInstances().bpmnFactory.create('bpmn:TimerEventDefinition', {})
|
||||||
|
modeling.updateProperties(element, {
|
||||||
|
eventDefinitions: [timerDef]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 清空原有
|
||||||
|
delete timerDef.timeDate
|
||||||
|
delete timerDef.timeDuration
|
||||||
|
delete timerDef.timeCycle
|
||||||
|
|
||||||
|
// 3. 设置新的
|
||||||
|
if (type.value === 'time' && condition.value) {
|
||||||
|
timerDef.timeDate = bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', {
|
||||||
|
body: condition.value
|
||||||
|
})
|
||||||
|
} else if (type.value === 'duration' && condition.value) {
|
||||||
|
timerDef.timeDuration = bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', {
|
||||||
|
body: condition.value
|
||||||
|
})
|
||||||
|
} else if (type.value === 'cycle' && condition.value) {
|
||||||
|
timerDef.timeCycle = bpmnInstances().bpmnFactory.create('bpmn:FormalExpression', {
|
||||||
|
body: condition.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
bpmnInstances().modeling.updateProperties(toRaw(element), {
|
||||||
|
eventDefinitions: [timerDef]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.businessObject,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
nextTick(() => {
|
||||||
|
syncFromBusinessObject()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 相关样式 */
|
||||||
|
</style>
|
||||||
|
|
@ -55,6 +55,7 @@ import {
|
||||||
ElCollapse,
|
ElCollapse,
|
||||||
ElCollapseItem,
|
ElCollapseItem,
|
||||||
ElCard,
|
ElCard,
|
||||||
|
ElTreeSelect
|
||||||
// ElFormItem,
|
// ElFormItem,
|
||||||
// ElOption
|
// ElOption
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
|
|
@ -97,6 +98,7 @@ const components = [
|
||||||
ElTableColumn,
|
ElTableColumn,
|
||||||
ElTabPane,
|
ElTabPane,
|
||||||
ElTabs,
|
ElTabs,
|
||||||
|
ElTreeSelect,
|
||||||
ElDropdown,
|
ElDropdown,
|
||||||
ElDropdownMenu,
|
ElDropdownMenu,
|
||||||
ElDropdownItem,
|
ElDropdownItem,
|
||||||
|
|
@ -119,7 +121,7 @@ const components = [
|
||||||
Editor,
|
Editor,
|
||||||
ElCollapse,
|
ElCollapse,
|
||||||
ElCollapseItem,
|
ElCollapseItem,
|
||||||
ElCard,
|
ElCard
|
||||||
]
|
]
|
||||||
|
|
||||||
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
|
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const router = createRouter({
|
||||||
})
|
})
|
||||||
|
|
||||||
export const resetRouter = (): void => {
|
export const resetRouter = (): void => {
|
||||||
const resetWhiteNameList = ['Redirect', 'Login', 'NoFind', 'Root']
|
const resetWhiteNameList = ['Redirect', 'Login', 'NoFound', 'Home']
|
||||||
router.getRoutes().forEach((route) => {
|
router.getRoutes().forEach((route) => {
|
||||||
const { name } = route
|
const { name } = route
|
||||||
if (name && !resetWhiteNameList.includes(name as string)) {
|
if (name && !resetWhiteNameList.includes(name as string)) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
import { store } from '../index'
|
|
||||||
import { humpToUnderline, setCssVar } from '@/utils'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
import { ElementPlusSize } from '@/types/elementPlus'
|
import { ElementPlusSize } from '@/types/elementPlus'
|
||||||
import { LayoutType } from '@/types/layout'
|
import { LayoutType } from '@/types/layout'
|
||||||
import { ThemeTypes } from '@/types/theme'
|
import { ThemeTypes } from '@/types/theme'
|
||||||
|
import { humpToUnderline, setCssVar } from '@/utils'
|
||||||
|
import { getCssColorVariable, hexToRGB, mix } from '@/utils/color'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { store } from '../index'
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
|
@ -183,6 +184,40 @@ export const useAppStore = defineStore('app', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
setPrimaryLight() {
|
||||||
|
if (this.theme.elColorPrimary) {
|
||||||
|
const elColorPrimary = this.theme.elColorPrimary
|
||||||
|
const color = this.isDark ? '#000000' : '#ffffff'
|
||||||
|
const lightList = [3, 5, 7, 8, 9]
|
||||||
|
lightList.forEach((v) => {
|
||||||
|
setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
|
||||||
|
})
|
||||||
|
setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
|
||||||
|
|
||||||
|
this.setAllColorRgbVars()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理element自带的主题色和辅助色的-rgb切换主题变化,如:--el-color-primary-rgb
|
||||||
|
setAllColorRgbVars() {
|
||||||
|
// 需要处理的颜色类型列表
|
||||||
|
const colorTypes = ['primary', 'success', 'warning', 'danger', 'error', 'info']
|
||||||
|
|
||||||
|
colorTypes.forEach((type) => {
|
||||||
|
// 获取当前颜色值
|
||||||
|
const colorValue = getCssColorVariable(`--el-color-${type}`)
|
||||||
|
if (colorValue) {
|
||||||
|
// 转换为rgba并提取RGB部分
|
||||||
|
const rgbaString = hexToRGB(colorValue, 1)
|
||||||
|
const rgbValues = rgbaString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i)
|
||||||
|
if (rgbValues) {
|
||||||
|
const [, r, g, b] = rgbValues
|
||||||
|
// 设置对应的RGB变量
|
||||||
|
setCssVar(`--el-color-${type}-rgb`, `${r}, ${g}, ${b}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
setBreadcrumb(breadcrumb: boolean) {
|
setBreadcrumb(breadcrumb: boolean) {
|
||||||
this.breadcrumb = breadcrumb
|
this.breadcrumb = breadcrumb
|
||||||
},
|
},
|
||||||
|
|
@ -256,6 +291,7 @@ export const useAppStore = defineStore('app', {
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
}
|
}
|
||||||
wsCache.set(CACHE_KEY.IS_DARK, this.isDark)
|
wsCache.set(CACHE_KEY.IS_DARK, this.isDark)
|
||||||
|
this.setPrimaryLight()
|
||||||
},
|
},
|
||||||
setCurrentSize(currentSize: ElementPlusSize) {
|
setCurrentSize(currentSize: ElementPlusSize) {
|
||||||
this.currentSize = currentSize
|
this.currentSize = currentSize
|
||||||
|
|
@ -272,6 +308,7 @@ export const useAppStore = defineStore('app', {
|
||||||
for (const key in this.theme) {
|
for (const key in this.theme) {
|
||||||
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
|
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
|
||||||
}
|
}
|
||||||
|
this.setPrimaryLight()
|
||||||
},
|
},
|
||||||
setFooter(footer: boolean) {
|
setFooter(footer: boolean) {
|
||||||
this.footer = footer
|
this.footer = footer
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export const useUserStore = defineStore('admin-user', {
|
||||||
userInfo = await getInfo()
|
userInfo = await getInfo()
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
this.permissions = new Set(userInfo.permissions)
|
this.permissions = new Set(userInfo.permissions || []) // 兜底为 [] https://t.zsxq.com/xCJew
|
||||||
this.roles = userInfo.roles
|
this.roles = userInfo.roles
|
||||||
this.user = userInfo.user
|
this.user = userInfo.user
|
||||||
this.isSetUser = true
|
this.isSetUser = true
|
||||||
|
|
|
||||||
|
|
@ -172,3 +172,46 @@ export const PREDEFINE_COLORS = [
|
||||||
'#1f73c3',
|
'#1f73c3',
|
||||||
'#711f57'
|
'#711f57'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixes two colors.
|
||||||
|
*
|
||||||
|
* @param {string} color1 - The first color, should be a 6-digit hexadecimal color code starting with `#`.
|
||||||
|
* @param {string} color2 - The second color, should be a 6-digit hexadecimal color code starting with `#`.
|
||||||
|
* @param {number} [weight=0.5] - The weight of color1 in the mix, should be a number between 0 and 1, where 0 represents 100% of color2, and 1 represents 100% of color1.
|
||||||
|
* @returns {string} The mixed color, a 6-digit hexadecimal color code starting with `#`.
|
||||||
|
*/
|
||||||
|
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
|
||||||
|
let color = '#'
|
||||||
|
for (let i = 0; i <= 2; i++) {
|
||||||
|
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16)
|
||||||
|
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16)
|
||||||
|
const c = Math.round(c1 * weight + c2 * (1 - weight))
|
||||||
|
color += c.toString(16).padStart(2, '0')
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getCssColorVariable
|
||||||
|
* @description 获取css变量的颜色值
|
||||||
|
* @param colorVariable css变量名
|
||||||
|
* @param opacity 透明度
|
||||||
|
* @returns {string} 颜色值
|
||||||
|
* @example getCssColorVariable('--el-color-primary', 0.5)
|
||||||
|
* @example getCssColorVariable('--el-color-primary')
|
||||||
|
* @example getCssColorVariable()
|
||||||
|
*/
|
||||||
|
export const getCssColorVariable = (
|
||||||
|
colorVariable: string = '--el-color-primary',
|
||||||
|
opacity?: number
|
||||||
|
) => {
|
||||||
|
const colorValue = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue(colorVariable)
|
||||||
|
.trim()
|
||||||
|
if (colorValue) {
|
||||||
|
return opacity ? hexToRGB(colorValue, opacity) : colorValue
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* 针对 https://github.com/xaboy/form-create-designer 封装的工具类
|
* 针对 https://github.com/xaboy/form-create-designer 封装的工具类
|
||||||
*/
|
*/
|
||||||
|
import { isRef } from 'vue'
|
||||||
|
|
||||||
// 编码表单 Conf
|
// 编码表单 Conf
|
||||||
export const encodeConf = (designerRef: object) => {
|
export const encodeConf = (designerRef: object) => {
|
||||||
|
|
@ -47,10 +48,156 @@ export const setConfAndFields2 = (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
detailPreview = detailPreview.value
|
detailPreview = detailPreview.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修复所有函数类型(解决设计器保存后函数变成字符串的问题)。例如说:
|
||||||
|
// https://t.zsxq.com/rADff
|
||||||
|
// https://t.zsxq.com/ZfbGt
|
||||||
|
// https://t.zsxq.com/mHOoj
|
||||||
|
// https://t.zsxq.com/BSylB
|
||||||
|
const option = JSON.parse(conf)
|
||||||
|
const rule = decodeFields(fields)
|
||||||
|
// 🔧 修复所有函数类型 - 解决设计器保存后函数变成字符串的问题
|
||||||
|
const fixFunctions = (obj: any) => {
|
||||||
|
if (obj && typeof obj === 'object') {
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
// 检查是否是函数相关的属性
|
||||||
|
if (isFunctionProperty(key)) {
|
||||||
|
// 如果不是函数类型,重新构建为函数
|
||||||
|
if (typeof obj[key] !== 'function') {
|
||||||
|
obj[key] = createDefaultFunction(key)
|
||||||
|
}
|
||||||
|
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||||
|
// 递归处理嵌套对象
|
||||||
|
fixFunctions(obj[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 判断是否是函数属性
|
||||||
|
const isFunctionProperty = (key: string): boolean => {
|
||||||
|
const functionKeys = [
|
||||||
|
'beforeFetch', // 请求前处理
|
||||||
|
'afterFetch', // 请求后处理
|
||||||
|
'onSubmit', // 表单提交
|
||||||
|
'onReset', // 表单重置
|
||||||
|
'onChange', // 值变化
|
||||||
|
'onInput', // 输入事件
|
||||||
|
'onClick', // 点击事件
|
||||||
|
'onFocus', // 获取焦点
|
||||||
|
'onBlur', // 失去焦点
|
||||||
|
'onMounted', // 组件挂载
|
||||||
|
'onCreated', // 组件创建
|
||||||
|
'onReload', // 重新加载
|
||||||
|
'remoteMethod', // 远程搜索方法
|
||||||
|
'parseFunc', // 解析函数
|
||||||
|
'validator', // 验证器
|
||||||
|
'asyncValidator', // 异步验证器
|
||||||
|
'formatter', // 格式化函数
|
||||||
|
'parser', // 解析函数
|
||||||
|
'beforeUpload', // 上传前处理
|
||||||
|
'onSuccess', // 成功回调
|
||||||
|
'onError', // 错误回调
|
||||||
|
'onProgress', // 进度回调
|
||||||
|
'onPreview', // 预览回调
|
||||||
|
'onRemove', // 移除回调
|
||||||
|
'onExceed', // 超出限制回调
|
||||||
|
'filterMethod', // 过滤方法
|
||||||
|
'sortMethod', // 排序方法
|
||||||
|
'loadData', // 加载数据
|
||||||
|
'renderContent', // 渲染内容
|
||||||
|
'render' // 渲染函数
|
||||||
|
]
|
||||||
|
// 检查是否以函数相关前缀开头
|
||||||
|
const functionPrefixes = ['on', 'before', 'after', 'handle']
|
||||||
|
return functionKeys.includes(key) || functionPrefixes.some((prefix) => key.startsWith(prefix))
|
||||||
|
}
|
||||||
|
// 根据函数名创建默认函数
|
||||||
|
const createDefaultFunction = (key: string): Function => {
|
||||||
|
switch (key) {
|
||||||
|
case 'beforeFetch':
|
||||||
|
return (config: any) => {
|
||||||
|
// 添加 Token 认证头。例如说:
|
||||||
|
// https://t.zsxq.com/hK3FO
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
if (token) {
|
||||||
|
config.headers = {
|
||||||
|
...config.headers,
|
||||||
|
Authorization: 'Bearer ' + token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加通用请求头
|
||||||
|
config.headers = {
|
||||||
|
...config.headers,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
// 添加时间戳防止缓存
|
||||||
|
config.params = {
|
||||||
|
...config.params,
|
||||||
|
_t: Date.now()
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
case 'afterFetch':
|
||||||
|
return (data: any) => {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
case 'onSubmit':
|
||||||
|
return (_formData: any) => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 'onReset':
|
||||||
|
return () => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 'onChange':
|
||||||
|
return (_value: any, _oldValue: any) => {}
|
||||||
|
case 'remoteMethod':
|
||||||
|
return (query: string) => {
|
||||||
|
console.log('remoteMethod被调用:', query)
|
||||||
|
}
|
||||||
|
case 'parseFunc':
|
||||||
|
return (data: any) => {
|
||||||
|
// 默认解析逻辑:如果是数组直接返回,否则尝试获取list属性
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return data?.list || data?.data || []
|
||||||
|
}
|
||||||
|
case 'validator':
|
||||||
|
return (_rule: any, _value: any, callback: Function) => {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
case 'beforeUpload':
|
||||||
|
return (_file: any) => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// 通用默认函数
|
||||||
|
return (...args: any[]) => {
|
||||||
|
// 对于事件处理函数,返回true表示继续执行
|
||||||
|
if (key.startsWith('on') || key.startsWith('handle')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 对于其他函数,返回第一个参数(通常是数据传递)
|
||||||
|
return args[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 修复 option 中的所有函数
|
||||||
|
fixFunctions(option)
|
||||||
|
// 修复 rule 中的所有函数(包括组件的 props)
|
||||||
|
if (Array.isArray(rule)) {
|
||||||
|
rule.forEach((item: any) => {
|
||||||
|
fixFunctions(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
detailPreview.option = JSON.parse(conf)
|
detailPreview.option = option
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
detailPreview.rule = decodeFields(fields)
|
detailPreview.rule = rule
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
detailPreview.value = value
|
detailPreview.value = value
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
|
||||||
if (!route.children && route.parentId == 0 && route.component) {
|
if (!route.children && route.parentId == 0 && route.component) {
|
||||||
data.component = Layout
|
data.component = Layout
|
||||||
data.meta = {
|
data.meta = {
|
||||||
hidden: meta.hidden,
|
hidden: meta.hidden
|
||||||
}
|
}
|
||||||
data.name = toCamelCase(route.path, true) + 'Parent'
|
data.name = toCamelCase(route.path, true) + 'Parent'
|
||||||
data.redirect = ''
|
data.redirect = ''
|
||||||
|
|
@ -170,8 +170,9 @@ const generateRoutePath = (parentPath: string, path: string) => {
|
||||||
}
|
}
|
||||||
export const pathResolve = (parentPath: string, path: string) => {
|
export const pathResolve = (parentPath: string, path: string) => {
|
||||||
if (isUrl(path)) return path
|
if (isUrl(path)) return path
|
||||||
const childPath = path.startsWith('/') || !path ? path : `/${path}`
|
if (!path) return parentPath // 修复 path 为空时返回 parentPath,避免拼接出错 https://t.zsxq.com/QVr6b
|
||||||
return `${parentPath}${childPath}`.replace(/\/\//g, '/')
|
const childPath = path.startsWith('/') ? path : `/${path}`
|
||||||
|
return `${parentPath}${childPath}`.replace(/\/+/g, '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由降级
|
// 路由降级
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,12 @@
|
||||||
@click="handleProjectClick(item.message)"
|
@click="handleProjectClick(item.message)"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Icon :icon="item.icon" :size="25" class="mr-8px" :style="{ color: item.color }" />
|
<Icon
|
||||||
|
:icon="item.icon"
|
||||||
|
:size="25"
|
||||||
|
class="mr-8px"
|
||||||
|
:style="{ color: item.color }"
|
||||||
|
/>
|
||||||
<span class="text-16px">{{ item.name }}</span>
|
<span class="text-16px">{{ item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-12px text-12px text-gray-400">{{ t(item.message) }}</div>
|
<div class="mt-12px text-12px text-gray-400">{{ t(item.message) }}</div>
|
||||||
|
|
@ -181,17 +186,17 @@ import { EChartsOption } from 'echarts'
|
||||||
import { formatTime } from '@/utils'
|
import { formatTime } from '@/utils'
|
||||||
|
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { useWatermark } from '@/hooks/web/useWatermark'
|
// import { useWatermark } from '@/hooks/web/useWatermark'
|
||||||
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
||||||
import { pieOptions, barOptions } from './echarts-data'
|
import { pieOptions, barOptions } from './echarts-data'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
defineOptions({ name: 'Home' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { setWatermark } = useWatermark()
|
// const { setWatermark } = useWatermark()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const avatar = userStore.getUser.avatar
|
const avatar = userStore.getUser.avatar
|
||||||
const username = userStore.getUser.nickname
|
const username = userStore.getUser.nickname
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@
|
||||||
label-width="120px"
|
label-width="120px"
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
<el-row class="mx-[-10px]">
|
||||||
<!-- 租户名 -->
|
<!-- 租户名 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<LoginFormTitle style="width: 100%" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName">
|
<el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="resetPasswordData.tenantName"
|
v-model="resetPasswordData.tenantName"
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 手机号 -->
|
<!-- 手机号 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="mobile">
|
<el-form-item prop="mobile">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="resetPasswordData.mobile"
|
v-model="resetPasswordData.mobile"
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
@success="getSmsCode"
|
@success="getSmsCode"
|
||||||
/>
|
/>
|
||||||
<!-- 验证码 -->
|
<!-- 验证码 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="code">
|
<el-form-item prop="code">
|
||||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
|
|
@ -73,44 +73,44 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<InputPassword
|
<InputPassword
|
||||||
v-model="resetPasswordData.password"
|
v-model="resetPasswordData.password"
|
||||||
:placeholder="t('login.passwordPlaceholder')"
|
:placeholder="t('login.passwordPlaceholder')"
|
||||||
style="width: 100%"
|
class="w-full"
|
||||||
strength="true"
|
:strength="true"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="check_password">
|
<el-form-item prop="check_password">
|
||||||
<InputPassword
|
<InputPassword
|
||||||
v-model="resetPasswordData.check_password"
|
v-model="resetPasswordData.check_password"
|
||||||
:placeholder="t('login.checkPassword')"
|
:placeholder="t('login.checkPassword')"
|
||||||
style="width: 100%"
|
class="w-full"
|
||||||
strength="true"
|
:strength="true"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 登录按钮 / 返回按钮 -->
|
<!-- 登录按钮 / 返回按钮 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton
|
||||||
:loading="loginLoading"
|
:loading="loginLoading"
|
||||||
:title="t('login.resetPassword')"
|
:title="t('login.resetPassword')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="resetPassword()"
|
@click="resetPassword()"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton
|
||||||
:loading="loginLoading"
|
:loading="loginLoading"
|
||||||
:title="t('login.backLogin')"
|
:title="t('login.backLogin')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
@click="handleBackLogin()"
|
@click="handleBackLogin()"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -134,7 +134,7 @@ const verify = ref()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { currentRoute, push } = useRouter()
|
const { currentRoute } = useRouter()
|
||||||
const formSmsResetPassword = ref()
|
const formSmsResetPassword = ref()
|
||||||
const loginLoading = ref(false)
|
const loginLoading = ref(false)
|
||||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||||
|
|
@ -145,7 +145,7 @@ const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
|
||||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||||
|
|
||||||
const validatePass2 = (rule, value, callback) => {
|
const validatePass2 = (_rule, value, callback) => {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
callback(new Error('请再次输入密码'))
|
callback(new Error('请再次输入密码'))
|
||||||
} else if (value !== resetPasswordData.password) {
|
} else if (value !== resetPasswordData.password) {
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@
|
||||||
label-width="120px"
|
label-width="120px"
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
<el-row class="mx-[-10px]">
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<LoginFormTitle style="width: 100%" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginData.loginForm.tenantName"
|
v-model="loginData.loginForm.tenantName"
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginData.loginForm.username"
|
v-model="loginData.loginForm.username"
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginData.loginForm.password"
|
v-model="loginData.loginForm.password"
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col
|
<el-col
|
||||||
:span="24"
|
:span="24"
|
||||||
style="padding-right: 10px; padding-left: 10px; margin-top: -20px; margin-bottom: -20px"
|
class="px-10px mt-[-20px] mb-[-20px]"
|
||||||
>
|
>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-row justify="space-between" style="width: 100%">
|
<el-row justify="space-between" style="width: 100%">
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :offset="6" :span="12">
|
<el-col :offset="6" :span="12">
|
||||||
<el-link
|
<el-link
|
||||||
style="float: right"
|
class="float-right"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
|
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
|
||||||
>
|
>
|
||||||
|
|
@ -70,12 +70,12 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton
|
||||||
:loading="loginLoading"
|
:loading="loginLoading"
|
||||||
:title="t('login.login')"
|
:title="t('login.login')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="getCode()"
|
@click="getCode()"
|
||||||
/>
|
/>
|
||||||
|
|
@ -89,27 +89,27 @@
|
||||||
mode="pop"
|
mode="pop"
|
||||||
@success="handleLogin"
|
@success="handleLogin"
|
||||||
/>
|
/>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<XButton
|
<XButton
|
||||||
:title="t('login.btnMobile')"
|
:title="t('login.btnMobile')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
@click="setLoginState(LoginStateEnum.MOBILE)"
|
@click="setLoginState(LoginStateEnum.MOBILE)"
|
||||||
/>
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<XButton
|
<XButton
|
||||||
:title="t('login.btnQRCode')"
|
:title="t('login.btnQRCode')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
@click="setLoginState(LoginStateEnum.QR_CODE)"
|
@click="setLoginState(LoginStateEnum.QR_CODE)"
|
||||||
/>
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<XButton
|
<XButton
|
||||||
:title="t('login.btnRegister')"
|
:title="t('login.btnRegister')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
@click="setLoginState(LoginStateEnum.REGISTER)"
|
@click="setLoginState(LoginStateEnum.REGISTER)"
|
||||||
/>
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
@ -117,9 +117,9 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div class="w-[100%] flex justify-between">
|
<div class="w-full flex justify-between">
|
||||||
<Icon
|
<Icon
|
||||||
v-for="(item, key) in socialList"
|
v-for="(item, key) in socialList"
|
||||||
:key="key"
|
:key="key"
|
||||||
|
|
@ -133,9 +133,9 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-divider content-position="center">萌新必读</el-divider>
|
<el-divider content-position="center">萌新必读</el-divider>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div class="w-[100%] flex justify-between">
|
<div class="w-full flex justify-between">
|
||||||
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
|
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
|
||||||
<el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
|
<el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
|
||||||
<el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">
|
<el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">
|
||||||
|
|
@ -239,11 +239,13 @@ const getLoginFormCache = () => {
|
||||||
}
|
}
|
||||||
// 根据域名,获得租户信息
|
// 根据域名,获得租户信息
|
||||||
const getTenantByWebsite = async () => {
|
const getTenantByWebsite = async () => {
|
||||||
const website = location.host
|
if (loginData.tenantEnable === 'true') {
|
||||||
const res = await LoginApi.getTenantByWebsite(website)
|
const website = location.host
|
||||||
if (res) {
|
const res = await LoginApi.getTenantByWebsite(website)
|
||||||
loginData.loginForm.tenantName = res.name
|
if (res) {
|
||||||
authUtil.setTenantId(res.id)
|
loginData.loginForm.tenantName = res.name
|
||||||
|
authUtil.setTenantId(res.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const loading = ref() // ElLoading.service 返回的实例
|
const loading = ref() // ElLoading.service 返回的实例
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@
|
||||||
label-width="120px"
|
label-width="120px"
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
<el-row class="mx-[-10px]">
|
||||||
<!-- 租户名 -->
|
<!-- 租户名 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<LoginFormTitle style="width: 100%" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginData.loginForm.tenantName"
|
v-model="loginData.loginForm.tenantName"
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 手机号 -->
|
<!-- 手机号 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="mobileNumber">
|
<el-form-item prop="mobileNumber">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginData.loginForm.mobileNumber"
|
v-model="loginData.loginForm.mobileNumber"
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 验证码 -->
|
<!-- 验证码 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="code">
|
<el-form-item prop="code">
|
||||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
|
|
@ -68,23 +68,23 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 登录按钮 / 返回按钮 -->
|
<!-- 登录按钮 / 返回按钮 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton
|
||||||
:loading="loginLoading"
|
:loading="loginLoading"
|
||||||
:title="t('login.login')"
|
:title="t('login.login')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="signIn()"
|
@click="signIn()"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton
|
||||||
:loading="loginLoading"
|
:loading="loginLoading"
|
||||||
:title="t('login.backLogin')"
|
:title="t('login.backLogin')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
@click="handleBackLogin()"
|
@click="handleBackLogin()"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<el-row v-show="getShow" class="login-form" style="margin-right: -10px; margin-left: -10px">
|
<el-row v-show="getShow" class="login-form mx-[-10px]">
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<LoginFormTitle style="width: 100%" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-card class="mb-10px text-center" shadow="hover">
|
<el-card class="mb-10px text-center" shadow="hover">
|
||||||
<Qrcode :logo="logoImg" />
|
<Qrcode :logo="logoImg" />
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider>
|
<el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<div class="mt-15px w-[100%]">
|
<div class="mt-4 w-full">
|
||||||
<XButton :title="t('login.backLogin')" class="w-[100%]" @click="handleBackLogin()" />
|
<XButton :title="t('login.backLogin')" class="w-full" @click="handleBackLogin()" />
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@
|
||||||
label-width="120px"
|
label-width="120px"
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
<el-row class="mx-[-10px]">
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<LoginFormTitle style="width: 100%" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item v-if="registerData.tenantEnable === 'true'" prop="tenantName">
|
<el-form-item v-if="registerData.tenantEnable === 'true'" prop="tenantName">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="registerData.registerForm.tenantName"
|
v-model="registerData.registerForm.tenantName"
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="registerData.registerForm.username"
|
v-model="registerData.registerForm.username"
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="registerData.registerForm.nickname"
|
v-model="registerData.registerForm.nickname"
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="registerData.registerForm.password"
|
v-model="registerData.registerForm.password"
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="confirmPassword">
|
<el-form-item prop="confirmPassword">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="registerData.registerForm.confirmPassword"
|
v-model="registerData.registerForm.confirmPassword"
|
||||||
|
|
@ -73,12 +73,12 @@
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton
|
||||||
:loading="loginLoading"
|
:loading="loginLoading"
|
||||||
:title="t('login.register')"
|
:title="t('login.register')"
|
||||||
class="w-[100%]"
|
class="w-full"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="getCode()"
|
@click="getCode()"
|
||||||
/>
|
/>
|
||||||
|
|
@ -93,7 +93,7 @@
|
||||||
@success="handleRegister"
|
@success="handleRegister"
|
||||||
/>
|
/>
|
||||||
</el-row>
|
</el-row>
|
||||||
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" />
|
<XButton :title="t('login.hasUser')" class="w-full" @click="handleBackLogin()" />
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
@ -123,7 +123,7 @@ const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
||||||
|
|
||||||
const equalToPassword = (rule, value, callback) => {
|
const equalToPassword = (_rule, value, callback) => {
|
||||||
if (registerData.registerForm.password !== value) {
|
if (registerData.registerForm.password !== value) {
|
||||||
callback(new Error('两次输入的密码不一致'))
|
callback(new Error('两次输入的密码不一致'))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -233,11 +233,13 @@ const getTenantId = async () => {
|
||||||
|
|
||||||
// 根据域名,获得租户信息
|
// 根据域名,获得租户信息
|
||||||
const getTenantByWebsite = async () => {
|
const getTenantByWebsite = async () => {
|
||||||
const website = location.host
|
if (registerData.tenantEnable === 'true') {
|
||||||
const res = await LoginApi.getTenantByWebsite(website)
|
const website = location.host
|
||||||
if (res) {
|
const res = await LoginApi.getTenantByWebsite(website)
|
||||||
registerData.registerForm.tenantName = res.name
|
if (res) {
|
||||||
authUtil.setTenantId(res.id)
|
registerData.registerForm.tenantName = res.name
|
||||||
|
authUtil.setTenantId(res.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const loading = ref() // ElLoading.service 返回的实例
|
const loading = ref() // ElLoading.service 返回的实例
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-show="ssoVisible" class="form-cont">
|
<div v-show="ssoVisible" class="form-cont">
|
||||||
<!-- 应用名 -->
|
<!-- 应用名 -->
|
||||||
<LoginFormTitle style="width: 100%" />
|
<LoginFormTitle class="w-full" />
|
||||||
<el-tabs class="form" style="float: none" value="uname">
|
<el-tabs class="form" style="float: none" value="uname">
|
||||||
<el-tab-pane :label="client.name" name="uname" />
|
<el-tab-pane :label="client.name" name="uname" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
@ -15,17 +15,17 @@
|
||||||
v-for="scope in queryParams.scopes"
|
v-for="scope in queryParams.scopes"
|
||||||
:key="scope"
|
:key="scope"
|
||||||
:value="scope"
|
:value="scope"
|
||||||
style="display: block; margin-bottom: -10px"
|
class="block mb-[-10px]"
|
||||||
>
|
>
|
||||||
{{ formatScope(scope) }}
|
{{ formatScope(scope) }}
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<!-- 下方的登录按钮 -->
|
<!-- 下方的登录按钮 -->
|
||||||
<el-form-item class="w-1/1">
|
<el-form-item class="w-full">
|
||||||
<el-button
|
<el-button
|
||||||
:loading="formLoading"
|
:loading="formLoading"
|
||||||
class="w-6/10"
|
class="w-3/5"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click.prevent="handleAuthorize(true)"
|
@click.prevent="handleAuthorize(true)"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import { useUserStore } from '@/store/modules/user'
|
||||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||||
import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
||||||
|
|
||||||
// TODO @芋艿:合并到 ProfileUser 组件中,更简洁一点
|
|
||||||
defineOptions({ name: 'UserAvatar' })
|
defineOptions({ name: 'UserAvatar' })
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
|
|
@ -30,10 +29,12 @@ const userStore = useUserStore()
|
||||||
const cropperRef = ref()
|
const cropperRef = ref()
|
||||||
const handelUpload = async ({ data }) => {
|
const handelUpload = async ({ data }) => {
|
||||||
const { httpRequest } = useUpload()
|
const { httpRequest } = useUpload()
|
||||||
const avatar = ((await httpRequest({
|
const avatar = (
|
||||||
file: data,
|
(await httpRequest({
|
||||||
filename: 'avatar.png',
|
file: data,
|
||||||
} as UploadRequestOptions)) as unknown as { data: string }).data
|
filename: 'avatar.png'
|
||||||
|
} as UploadRequestOptions)) as unknown as { data: string }
|
||||||
|
).data
|
||||||
await updateUserProfile({ avatar })
|
await updateUserProfile({ avatar })
|
||||||
|
|
||||||
// 关闭弹窗,并更新 userStore
|
// 关闭弹窗,并更新 userStore
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { SystemUserSocialTypeEnum } from '@/utils/constants'
|
import { SystemUserSocialTypeEnum } from '@/utils/constants'
|
||||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
import { getBindSocialUserList } from '@/api/system/social/user'
|
||||||
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
||||||
|
|
||||||
defineOptions({ name: 'UserSocial' })
|
defineOptions({ name: 'UserSocial' })
|
||||||
|
|
@ -32,19 +32,19 @@ defineProps<{
|
||||||
}>()
|
}>()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const socialUsers = ref<any[]>([])
|
const socialUsers = ref<any[]>([])
|
||||||
const userInfo = ref<ProfileVO>()
|
|
||||||
|
|
||||||
const initSocial = async () => {
|
const initSocial = async () => {
|
||||||
socialUsers.value = [] // 重置避免无限增长
|
socialUsers.value = [] // 重置避免无限增长
|
||||||
const res = await getUserProfile()
|
// 获取已绑定的社交用户列表
|
||||||
userInfo.value = res
|
const bindSocialUserList = await getBindSocialUserList()
|
||||||
|
// 检查该社交平台是否已绑定
|
||||||
for (const i in SystemUserSocialTypeEnum) {
|
for (const i in SystemUserSocialTypeEnum) {
|
||||||
const socialUser = { ...SystemUserSocialTypeEnum[i] }
|
const socialUser = { ...SystemUserSocialTypeEnum[i] }
|
||||||
socialUsers.value.push(socialUser)
|
socialUsers.value.push(socialUser)
|
||||||
if (userInfo.value?.socialUsers) {
|
if (bindSocialUserList && bindSocialUserList.length > 0) {
|
||||||
for (const j in userInfo.value.socialUsers) {
|
for (const bindUser of bindSocialUserList) {
|
||||||
if (socialUser.type === userInfo.value.socialUsers[j].type) {
|
if (socialUser.type === bindUser.type) {
|
||||||
socialUser.openid = userInfo.value.socialUsers[j].openid
|
socialUser.openid = bindUser.openid
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
<!-- AI 对话 -->
|
<!-- AI 对话 -->
|
||||||
<template>
|
<template>
|
||||||
<el-aside width="260px" class="conversation-container h-100%">
|
<el-aside
|
||||||
|
width="260px"
|
||||||
|
class="h-100% relative flex flex-col justify-between px-2.5 pt-2.5 pb-0 overflow-hidden"
|
||||||
|
>
|
||||||
<!-- 左顶部:对话 -->
|
<!-- 左顶部:对话 -->
|
||||||
<div class="h-100%">
|
<div class="h-100%">
|
||||||
<el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation">
|
<el-button class="w-1/1 py-4.5" type="primary" @click="createConversation">
|
||||||
<Icon icon="ep:plus" class="mr-5px" />
|
<Icon icon="ep:plus" class="mr-5px" />
|
||||||
新建对话
|
新建对话
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
@ -12,7 +15,7 @@
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchName"
|
v-model="searchName"
|
||||||
size="large"
|
size="large"
|
||||||
class="mt-10px search-input"
|
class="mt-5"
|
||||||
placeholder="搜索历史记录"
|
placeholder="搜索历史记录"
|
||||||
@keyup="searchConversation"
|
@keyup="searchConversation"
|
||||||
>
|
>
|
||||||
|
|
@ -22,19 +25,18 @@
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<!-- 左中间:对话列表 -->
|
<!-- 左中间:对话列表 -->
|
||||||
<div class="conversation-list">
|
<div class="overflow-auto h-full">
|
||||||
<!-- 情况一:加载中 -->
|
<!-- 情况一:加载中 -->
|
||||||
<el-empty v-if="loading" description="." :v-loading="loading" />
|
<el-empty v-if="loading" description="." :v-loading="loading" />
|
||||||
<!-- 情况二:按照 group 分组,展示聊天会话 list 列表 -->
|
<!-- 情况二:按照 group 分组,展示聊天会话 list 列表 -->
|
||||||
<div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey">
|
<div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey">
|
||||||
<div
|
<div class="mt-1.25 pt-2.5" v-if="conversationMap[conversationKey].length">
|
||||||
class="conversation-item classify-title"
|
<el-text class="mx-1" size="small" tag="b">
|
||||||
v-if="conversationMap[conversationKey].length"
|
{{ conversationKey }}
|
||||||
>
|
</el-text>
|
||||||
<el-text class="mx-1" size="small" tag="b">{{ conversationKey }}</el-text>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="conversation-item"
|
class="mt-1.25"
|
||||||
v-for="conversation in conversationMap[conversationKey]"
|
v-for="conversation in conversationMap[conversationKey]"
|
||||||
:key="conversation.id"
|
:key="conversation.id"
|
||||||
@click="handleConversationClick(conversation.id)"
|
@click="handleConversationClick(conversation.id)"
|
||||||
|
|
@ -42,25 +44,48 @@
|
||||||
@mouseout="hoverConversationId = ''"
|
@mouseout="hoverConversationId = ''"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="
|
class="flex flex-row justify-between flex-1 px-1.25 cursor-pointer rounded-1.25 items-center leading-7.5"
|
||||||
conversation.id === activeConversationId ? 'conversation active' : 'conversation'
|
:style="
|
||||||
|
conversation.id === activeConversationId
|
||||||
|
? 'background-color: var(--el-color-primary-light-9); border: 1px solid var(--el-color-primary-light-7);'
|
||||||
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="title-wrapper">
|
<div class="flex flex-row items-center">
|
||||||
<img class="avatar" :src="conversation.roleAvatar || roleAvatarDefaultImg" />
|
<img
|
||||||
<span class="title">{{ conversation.title }}</span>
|
class="w-6.25 h-6.25 rounded-1.25 flex flex-row justify-center"
|
||||||
|
:src="conversation.roleAvatar || roleAvatarDefaultImg"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="py-0.5 px-2.5"
|
||||||
|
style="
|
||||||
|
max-width: 220px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ conversation.title }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-wrapper" v-show="hoverConversationId === conversation.id">
|
<div
|
||||||
<el-button class="btn" link @click.stop="handleTop(conversation)">
|
class="right-0.5 flex flex-row justify-center"
|
||||||
|
style="color: var(--el-text-color-regular)"
|
||||||
|
v-show="hoverConversationId === conversation.id"
|
||||||
|
>
|
||||||
|
<el-button class="m-0" link @click.stop="handleTop(conversation)">
|
||||||
<el-icon title="置顶" v-if="!conversation.pinned"><Top /></el-icon>
|
<el-icon title="置顶" v-if="!conversation.pinned"><Top /></el-icon>
|
||||||
<el-icon title="置顶" v-if="conversation.pinned"><Bottom /></el-icon>
|
<el-icon title="置顶" v-if="conversation.pinned"><Bottom /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button class="btn" link @click.stop="updateConversationTitle(conversation)">
|
<el-button class="m-0" link @click.stop="updateConversationTitle(conversation)">
|
||||||
<el-icon title="编辑">
|
<el-icon title="编辑">
|
||||||
<Icon icon="ep:edit" />
|
<Icon icon="ep:edit" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button class="btn" link @click.stop="deleteChatConversation(conversation)">
|
<el-button class="m-0" link @click.stop="deleteChatConversation(conversation)">
|
||||||
<el-icon title="删除对话">
|
<el-icon title="删除对话">
|
||||||
<Icon icon="ep:delete" />
|
<Icon icon="ep:delete" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
|
|
@ -75,14 +100,29 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 左底部:工具栏 -->
|
<!-- 左底部:工具栏 -->
|
||||||
<div class="tool-box">
|
<div
|
||||||
<div @click="handleRoleRepository">
|
class="absolute bottom-0 left-0 right-0 px-5 leading-8.75 flex justify-between items-center"
|
||||||
|
style="
|
||||||
|
background-color: var(--el-fill-color-extra-light);
|
||||||
|
box-shadow: 0 0 1px 1px var(--el-border-color-lighter);
|
||||||
|
color: var(--el-text-color);
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center p-0 m-0 cursor-pointer"
|
||||||
|
style="color: var(--el-text-color-regular)"
|
||||||
|
@click="handleRoleRepository"
|
||||||
|
>
|
||||||
<Icon icon="ep:user" />
|
<Icon icon="ep:user" />
|
||||||
<el-text size="small">角色仓库</el-text>
|
<el-text class="ml-1.25" size="small">角色仓库</el-text>
|
||||||
</div>
|
</div>
|
||||||
<div @click="handleClearConversation">
|
<div
|
||||||
|
class="flex items-center p-0 m-0 cursor-pointer"
|
||||||
|
style="color: var(--el-text-color-regular)"
|
||||||
|
@click="handleClearConversation"
|
||||||
|
>
|
||||||
<Icon icon="ep:delete" />
|
<Icon icon="ep:delete" />
|
||||||
<el-text size="small">清空未置顶对话</el-text>
|
<el-text class="ml-1.25" size="small">清空未置顶对话</el-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -193,12 +233,12 @@ const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
|
||||||
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
|
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
|
||||||
// noinspection NonAsciiCharacters
|
// noinspection NonAsciiCharacters
|
||||||
const groupMap = {
|
const groupMap = {
|
||||||
置顶: [],
|
置顶: [] as ChatConversationVO[],
|
||||||
今天: [],
|
今天: [] as ChatConversationVO[],
|
||||||
一天前: [],
|
一天前: [] as ChatConversationVO[],
|
||||||
三天前: [],
|
三天前: [] as ChatConversationVO[],
|
||||||
七天前: [],
|
七天前: [] as ChatConversationVO[],
|
||||||
三十天前: []
|
三十天前: [] as ChatConversationVO[]
|
||||||
}
|
}
|
||||||
// 当前时间的时间戳
|
// 当前时间的时间戳
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
@ -349,124 +389,3 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.conversation-container {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 10px 0;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.btn-new-conversation {
|
|
||||||
padding: 18px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list {
|
|
||||||
overflow: auto;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.classify-title {
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-item {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex: 1;
|
|
||||||
padding: 0 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 5px;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 30px;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
|
|
||||||
.button {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding: 2px 10px;
|
|
||||||
max-width: 220px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: rgba(0, 0, 0, 0.77);
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对话编辑、删除
|
|
||||||
.button-wrapper {
|
|
||||||
right: 2px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-items: center;
|
|
||||||
color: #606266;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 角色仓库、清空未设置对话
|
|
||||||
.tool-box {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
//width: 100%;
|
|
||||||
padding: 0 20px;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
box-shadow: 0 0 1px 1px rgba(228, 228, 228, 0.8);
|
|
||||||
line-height: 35px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--el-text-color);
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #606266;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,86 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="messageContainer" class="h-100% overflow-y-auto relative">
|
<div ref="messageContainer" class="h-100% overflow-y-auto relative">
|
||||||
<div class="chat-list" v-for="(item, index) in list" :key="index">
|
<div class="flex flex-col overflow-y-hidden px-20px" v-for="(item, index) in list" :key="index">
|
||||||
<!-- 靠左 message:system、assistant 类型 -->
|
<!-- 靠左 message:system、assistant 类型 -->
|
||||||
<div class="left-message message-item" v-if="item.type !== 'user'">
|
<div class="flex flex-row mt-50px" v-if="item.type !== 'user'">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<el-avatar :src="roleAvatar" />
|
<el-avatar :src="roleAvatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="message">
|
<div class="flex flex-col text-left mx-15px">
|
||||||
<div>
|
<div>
|
||||||
<el-text class="time">{{ formatDate(item.createTime) }}</el-text>
|
<el-text class="text-left leading-30px">{{ formatDate(item.createTime) }}</el-text>
|
||||||
</div>
|
</div>
|
||||||
<div class="left-text-container" ref="markdownViewRef">
|
<div
|
||||||
<MarkdownView class="left-text" :content="item.content" />
|
class="relative flex flex-col break-words bg-[var(--el-fill-color-light)] shadow-[0_0_0_1px_var(--el-border-color-light)] rounded-10px pt-10px px-10px pb-5px"
|
||||||
|
ref="markdownViewRef"
|
||||||
|
>
|
||||||
|
<MarkdownView
|
||||||
|
class="text-[var(--el-text-color-primary)] text-[0.95rem]"
|
||||||
|
:content="item.content"
|
||||||
|
/>
|
||||||
<MessageKnowledge v-if="item.segments" :segments="item.segments" />
|
<MessageKnowledge v-if="item.segments" :segments="item.segments" />
|
||||||
</div>
|
</div>
|
||||||
<div class="left-btns">
|
<div class="flex flex-row mt-8px">
|
||||||
<el-button class="btn-cus" link @click="copyContent(item.content)">
|
<el-button
|
||||||
<img class="btn-image" src="@/assets/ai/copy.svg" />
|
class="flex bg-transparent items-center hover:cursor-pointer hover:bg-[var(--el-fill-color-lighter)]"
|
||||||
|
link
|
||||||
|
@click="copyContent(item.content)"
|
||||||
|
>
|
||||||
|
<img class="h-20px" src="@/assets/ai/copy.svg" />
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="item.id > 0" class="btn-cus" link @click="onDelete(item.id)">
|
<el-button
|
||||||
<img class="btn-image h-17px" src="@/assets/ai/delete.svg" />
|
v-if="item.id > 0"
|
||||||
|
class="flex bg-transparent items-center hover:cursor-pointer hover:bg-[var(--el-fill-color-lighter)]"
|
||||||
|
link
|
||||||
|
@click="onDelete(item.id)"
|
||||||
|
>
|
||||||
|
<img class="h-17px" src="@/assets/ai/delete.svg" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 靠右 message:user 类型 -->
|
<!-- 靠右 message:user 类型 -->
|
||||||
<div class="right-message message-item" v-if="item.type === 'user'">
|
<div class="flex flex-row-reverse justify-start mt-50px" v-if="item.type === 'user'">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<el-avatar :src="userAvatar" />
|
<el-avatar :src="userAvatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="message">
|
<div class="flex flex-col text-left mx-15px">
|
||||||
<div>
|
<div>
|
||||||
<el-text class="time">{{ formatDate(item.createTime) }}</el-text>
|
<el-text class="text-left leading-30px">{{ formatDate(item.createTime) }}</el-text>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-text-container">
|
<div class="flex flex-row-reverse">
|
||||||
<div class="right-text">{{ item.content }}</div>
|
<div
|
||||||
|
class="text-[0.95rem] text-[var(--el-color-white)] inline bg-[var(--el-color-primary)] shadow-[0_0_0_1px_var(--el-color-primary)] rounded-10px p-10px w-auto break-words whitespace-pre-wrap"
|
||||||
|
>{{ item.content }}</div
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-btns">
|
<div class="flex flex-row-reverse mt-8px">
|
||||||
<el-button class="btn-cus" link @click="copyContent(item.content)">
|
<el-button
|
||||||
<img class="btn-image" src="@/assets/ai/copy.svg" />
|
class="flex bg-transparent items-center hover:cursor-pointer hover:bg-[var(--el-fill-color-lighter)]"
|
||||||
|
link
|
||||||
|
@click="copyContent(item.content)"
|
||||||
|
>
|
||||||
|
<img class="h-20px" src="@/assets/ai/copy.svg" />
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button class="btn-cus" link @click="onDelete(item.id)">
|
<el-button
|
||||||
<img class="btn-image h-17px mr-12px" src="@/assets/ai/delete.svg" />
|
class="flex bg-transparent items-center hover:cursor-pointer hover:bg-[var(--el-fill-color-lighter)]"
|
||||||
|
link
|
||||||
|
@click="onDelete(item.id)"
|
||||||
|
>
|
||||||
|
<img class="h-17px mr-12px" src="@/assets/ai/delete.svg" />
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button class="btn-cus" link @click="onRefresh(item)">
|
<el-button
|
||||||
|
class="flex bg-transparent items-center hover:cursor-pointer hover:bg-[var(--el-fill-color-lighter)]"
|
||||||
|
link
|
||||||
|
@click="onRefresh(item)"
|
||||||
|
>
|
||||||
<el-icon size="17"><RefreshRight /></el-icon>
|
<el-icon size="17"><RefreshRight /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button class="btn-cus" link @click="onEdit(item)">
|
<el-button
|
||||||
|
class="flex bg-transparent items-center hover:cursor-pointer hover:bg-[var(--el-fill-color-lighter)]"
|
||||||
|
link
|
||||||
|
@click="onEdit(item)"
|
||||||
|
>
|
||||||
<el-icon size="17"><Edit /></el-icon>
|
<el-icon size="17"><Edit /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -55,7 +89,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 回到底部 -->
|
<!-- 回到底部 -->
|
||||||
<div v-if="isScrolling" class="to-bottom" @click="handleGoBottom">
|
<div v-if="isScrolling" class="absolute z-1000 bottom-0 right-50%" @click="handleGoBottom">
|
||||||
<el-button :icon="ArrowDownBold" circle />
|
<el-button :icon="ArrowDownBold" circle />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -142,7 +176,7 @@ defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用
|
||||||
// ============ 处理消息操作 ==============
|
// ============ 处理消息操作 ==============
|
||||||
|
|
||||||
/** 复制 */
|
/** 复制 */
|
||||||
const copyContent = async (content) => {
|
const copyContent = async (content: string) => {
|
||||||
await copy(content)
|
await copy(content)
|
||||||
message.success('复制成功!')
|
message.success('复制成功!')
|
||||||
}
|
}
|
||||||
|
|
@ -171,114 +205,3 @@ onMounted(async () => {
|
||||||
messageContainer.value.addEventListener('scroll', handleScroll)
|
messageContainer.value.addEventListener('scroll', handleScroll)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.message-container {
|
|
||||||
position: relative;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 中间
|
|
||||||
.chat-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: hidden;
|
|
||||||
padding: 0 20px;
|
|
||||||
.message-item {
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-message {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-message {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: left;
|
|
||||||
margin: 0 15px;
|
|
||||||
|
|
||||||
.time {
|
|
||||||
text-align: left;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-text-container {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
background-color: rgba(228, 228, 228, 0.8);
|
|
||||||
box-shadow: 0 0 0 1px rgba(228, 228, 228, 0.8);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 10px 10px 5px 10px;
|
|
||||||
|
|
||||||
.left-text {
|
|
||||||
color: #393939;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-text-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
|
|
||||||
.right-text {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: #fff;
|
|
||||||
display: inline;
|
|
||||||
background-color: #267fff;
|
|
||||||
box-shadow: 0 0 0 1px #267fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
width: auto;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-btns {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-btns {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制、删除按钮
|
|
||||||
.btn-cus {
|
|
||||||
display: flex;
|
|
||||||
background-color: transparent;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.btn-image {
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-cus:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 回到底部
|
|
||||||
.to-bottom {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1000;
|
|
||||||
bottom: 0;
|
|
||||||
right: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<!-- 消息列表为空时,展示 prompt 列表 -->
|
<!-- 消息列表为空时,展示 prompt 列表 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="chat-empty">
|
<div class="relative flex flex-row justify-center w-full h-full">
|
||||||
<!-- title -->
|
<!-- title -->
|
||||||
<div class="center-container">
|
<div class="flex flex-col justify-center">
|
||||||
<div class="title">芋道 AI</div>
|
<div class="text-28px font-bold text-center">芋道 AI</div>
|
||||||
<div class="role-list">
|
<div class="flex flex-row flex-wrap items-center justify-center w-460px mt-20px">
|
||||||
<div
|
<div
|
||||||
class="role-item"
|
class="flex justify-center w-180px leading-50px border border-solid border-[#e4e4e4] rounded-10px m-10px cursor-pointer hover:bg-[rgba(243,243,243,0.73)]"
|
||||||
v-for="prompt in promptList"
|
v-for="prompt in promptList"
|
||||||
:key="prompt.prompt"
|
:key="prompt.prompt"
|
||||||
@click="handlerPromptClick(prompt)"
|
@click="handlerPromptClick(prompt)"
|
||||||
|
|
@ -34,50 +34,3 @@ const handlerPromptClick = async ({ prompt }) => {
|
||||||
emits('onPrompt', prompt)
|
emits('onPrompt', prompt)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
.chat-empty {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.center-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 460px;
|
|
||||||
margin-top: 20px;
|
|
||||||
|
|
||||||
.role-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
width: 180px;
|
|
||||||
line-height: 50px;
|
|
||||||
border: 1px solid #e4e4e4;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role-item:hover {
|
|
||||||
background-color: rgba(243, 243, 243, 0.73);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
<!-- message 加载页面 -->
|
<!-- message 加载页面 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="message-loading" >
|
<div class="p-30px">
|
||||||
<el-skeleton animated />
|
<el-skeleton animated />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.message-loading {
|
|
||||||
padding: 30px 30px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<!-- 无聊天对话时,在 message 区域,可以新增对话 -->
|
<!-- 无聊天对话时,在 message 区域,可以新增对话 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="new-chat">
|
<div class="flex flex-row justify-center w-100% h-100%">
|
||||||
<div class="box-center">
|
<div class="flex flex-col justify-center">
|
||||||
<div class="tip">点击下方按钮,开始你的对话吧</div>
|
<div class="text-14px text-#858585">点击下方按钮,开始你的对话吧</div>
|
||||||
<div class="btns">
|
<div class="flex flex-row justify-center mt-20px">
|
||||||
<el-button type="primary" round @click="handlerNewChat">新建对话</el-button>
|
<el-button type="primary" round @click="handlerNewChat">新建对话</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -17,30 +17,3 @@ const handlerNewChat = () => {
|
||||||
emits('onNewConversation')
|
emits('onNewConversation')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
.new-chat {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.box-center {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.tip {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #858585;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="category-list">
|
<div class="flex flex-row flex-wrap items-center">
|
||||||
<div class="category" v-for="category in categoryList" :key="category">
|
<div class="flex flex-row mr-10px" v-for="category in categoryList" :key="category">
|
||||||
<el-button
|
<el-button
|
||||||
plain
|
plain
|
||||||
round
|
round
|
||||||
|
|
@ -37,17 +37,3 @@ const handleCategoryClick = async (category: string) => {
|
||||||
emits('onCategoryClick', category)
|
emits('onCategoryClick', category)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
.category-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.category {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<!-- header -->
|
<!-- header -->
|
||||||
<template>
|
<template>
|
||||||
<el-header class="chat-header">
|
<el-header class="flex flex-row justify-between items-center px-10px whitespace-nowrap text-ellipsis w-full" :style="{ backgroundColor: 'var(--el-bg-color-page)' }">
|
||||||
<div class="title">
|
<div class="text-20px font-bold overflow-hidden max-w-220px" :style="{ color: 'var(--el-text-color-primary)' }">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</div>
|
</div>
|
||||||
<div class="title-right">
|
<div class="flex flex-row">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
|
|
@ -19,30 +19,3 @@ defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.chat-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
background-color: #ececec;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
overflow: hidden;
|
|
||||||
color: #3e3e3e;
|
|
||||||
max-width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-right {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card-list" ref="tabsRef" @scroll="handleTabsScroll">
|
<div
|
||||||
<div class="card-item" v-for="role in roleList" :key="role.id">
|
class="flex flex-row flex-wrap relative h-full overflow-auto px-25px pb-140px items-start content-start justify-start"
|
||||||
<el-card class="card" body-class="card-body">
|
ref="tabsRef"
|
||||||
|
@scroll="handleTabsScroll"
|
||||||
|
>
|
||||||
|
<div v-for="role in roleList" :key="role.id">
|
||||||
|
<el-card
|
||||||
|
class="inline-block mr-20px rounded-10px mb-20px relative"
|
||||||
|
body-class="max-w-240px w-240px pt-15px px-15px pb-10px flex flex-row justify-start relative"
|
||||||
|
>
|
||||||
<!-- 更多操作 -->
|
<!-- 更多操作 -->
|
||||||
<div class="more-container" v-if="showMore">
|
<div class="absolute top-0 right-12px" v-if="showMore">
|
||||||
<el-dropdown @command="handleMoreClick">
|
<el-dropdown @command="handleMoreClick">
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
<el-button type="text">
|
<el-button type="text">
|
||||||
|
|
@ -13,10 +20,10 @@
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item :command="['edit', role]">
|
<el-dropdown-item :command="['edit', role]">
|
||||||
<Icon icon="ep:edit" color="#787878" />编辑
|
<Icon icon="ep:edit" color="var(--el-text-color-placeholder)" />编辑
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item :command="['delete', role]" style="color: red">
|
<el-dropdown-item :command="['delete', role]" style="color: var(--el-color-danger)">
|
||||||
<Icon icon="ep:delete" color="red" />删除
|
<Icon icon="ep:delete" color="var(--el-color-danger)" />删除
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -24,14 +31,18 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- 角色信息 -->
|
<!-- 角色信息 -->
|
||||||
<div>
|
<div>
|
||||||
<img class="avatar" :src="role.avatar" />
|
<img class="w-40px h-40px rounded-10px overflow-hidden" :src="role.avatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="right-container">
|
<div class="ml-10px w-full">
|
||||||
<div class="content-container">
|
<div class="h-85px">
|
||||||
<div class="title">{{ role.name }}</div>
|
<div class="text-18px font-bold" style="color: var(--el-text-color-primary)">
|
||||||
<div class="description">{{ role.description }}</div>
|
{{ role.name }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-10px text-14px" style="color: var(--el-text-color-regular)">
|
||||||
|
{{ role.description }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-container">
|
<div class="flex flex-row-reverse mt-2px">
|
||||||
<el-button type="primary" size="small" @click="handleUseClick(role)">使用</el-button>
|
<el-button type="primary" size="small" @click="handleUseClick(role)">使用</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -79,7 +90,7 @@ const handleMoreClick = async (data) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选中 */
|
/** 选中 */
|
||||||
const handleUseClick = (role) => {
|
const handleUseClick = (role: any) => {
|
||||||
emits('onUse', role)
|
emits('onUse', role)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,87 +99,8 @@ const handleTabsScroll = async () => {
|
||||||
if (tabsRef.value) {
|
if (tabsRef.value) {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = tabsRef.value
|
const { scrollTop, scrollHeight, clientHeight } = tabsRef.value
|
||||||
if (scrollTop + clientHeight >= scrollHeight - 20 && !props.loading) {
|
if (scrollTop + clientHeight >= scrollHeight - 20 && !props.loading) {
|
||||||
await emits('onPage')
|
emits('onPage')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
// 重写 card 组件 body 样式
|
|
||||||
.card-body {
|
|
||||||
max-width: 240px;
|
|
||||||
width: 240px;
|
|
||||||
padding: 15px 15px 10px 15px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
// 卡片列表
|
|
||||||
.card-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 0px 25px;
|
|
||||||
padding-bottom: 140px;
|
|
||||||
align-items: start;
|
|
||||||
align-content: flex-start;
|
|
||||||
justify-content: start;
|
|
||||||
|
|
||||||
.card {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.more-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-container {
|
|
||||||
margin-left: 10px;
|
|
||||||
width: 100%;
|
|
||||||
//height: 100px;
|
|
||||||
|
|
||||||
.content-container {
|
|
||||||
height: 85px;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #3e3e3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6a6a6a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
<!-- chat 角色仓库 -->
|
<!-- chat 角色仓库 -->
|
||||||
<template>
|
<template>
|
||||||
<el-container class="role-container">
|
<el-container
|
||||||
|
class="role-container absolute w-full h-full m-0 p-0 left-0 right-0 top-0 bottom-0 bg-[var(--el-bg-color)] overflow-hidden flex !flex-col"
|
||||||
|
>
|
||||||
<ChatRoleForm ref="formRef" @success="handlerAddRoleSuccess" />
|
<ChatRoleForm ref="formRef" @success="handlerAddRoleSuccess" />
|
||||||
<!-- header -->
|
<!-- header -->
|
||||||
<RoleHeader title="角色仓库" class="relative" />
|
<RoleHeader title="角色仓库" class="relative" />
|
||||||
<!-- main -->
|
<!-- main -->
|
||||||
<el-main class="role-main">
|
<el-main class="flex-1 overflow-hidden m-0 !p-0 relative">
|
||||||
<div class="search-container">
|
<div class="mx-5 mt-5 mb-0 absolute right-0 -top-1.25 z-100">
|
||||||
<!-- 搜索按钮 -->
|
<!-- 搜索按钮 -->
|
||||||
<el-input
|
<el-input
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
v-model="search"
|
v-model="search"
|
||||||
class="search-input"
|
class="!w-60"
|
||||||
size="default"
|
size="default"
|
||||||
placeholder="请输入搜索的内容"
|
placeholder="请输入搜索的内容"
|
||||||
:suffix-icon="Search"
|
:suffix-icon="Search"
|
||||||
|
|
@ -23,13 +25,21 @@
|
||||||
@click="handlerAddRole"
|
@click="handlerAddRole"
|
||||||
class="ml-20px"
|
class="ml-20px"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:user" style="margin-right: 5px;" />
|
<Icon icon="ep:user" class="mr-1.25" />
|
||||||
添加角色
|
添加角色
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- tabs -->
|
<!-- tabs -->
|
||||||
<el-tabs v-model="activeTab" class="tabs" @tab-click="handleTabsClick">
|
<el-tabs
|
||||||
<el-tab-pane class="role-pane" label="我的角色" name="my-role">
|
v-model="activeTab"
|
||||||
|
@tab-click="handleTabsClick"
|
||||||
|
class="relative h-full [&_.el-tabs__nav-scroll]:my-2.5 [&_.el-tabs__nav-scroll]:mx-5"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
label="我的角色"
|
||||||
|
name="my-role"
|
||||||
|
class="flex flex-col h-full overflow-y-auto relative"
|
||||||
|
>
|
||||||
<RoleList
|
<RoleList
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:role-list="myRoleList"
|
:role-list="myRoleList"
|
||||||
|
|
@ -43,7 +53,7 @@
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="公共角色" name="public-role">
|
<el-tab-pane label="公共角色" name="public-role">
|
||||||
<RoleCategoryList
|
<RoleCategoryList
|
||||||
class="role-category-list"
|
class="mx-6.75"
|
||||||
:category-list="categoryList"
|
:category-list="categoryList"
|
||||||
:active="activeCategory"
|
:active="activeCategory"
|
||||||
@on-category-click="handlerCategoryClick"
|
@on-category-click="handlerCategoryClick"
|
||||||
|
|
@ -64,15 +74,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref} from 'vue'
|
import { ref } from 'vue'
|
||||||
import RoleHeader from './RoleHeader.vue'
|
import RoleHeader from './RoleHeader.vue'
|
||||||
import RoleList from './RoleList.vue'
|
import RoleList from './RoleList.vue'
|
||||||
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
|
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
|
||||||
import RoleCategoryList from './RoleCategoryList.vue'
|
import RoleCategoryList from './RoleCategoryList.vue'
|
||||||
import {ChatRoleApi, ChatRolePageReqVO, ChatRoleVO} from '@/api/ai/model/chatRole'
|
import { ChatRoleApi, ChatRolePageReqVO, ChatRoleVO } from '@/api/ai/model/chatRole'
|
||||||
import {ChatConversationApi, ChatConversationVO} from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import {Search} from '@element-plus/icons-vue'
|
import { Search } from '@element-plus/icons-vue'
|
||||||
import {TabsPaneContext} from 'element-plus'
|
import { TabsPaneContext } from 'element-plus'
|
||||||
|
|
||||||
const router = useRouter() // 路由对象
|
const router = useRouter() // 路由对象
|
||||||
|
|
||||||
|
|
@ -220,70 +230,9 @@ onMounted(async () => {
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<!-- 覆盖 element ui css -->
|
<!-- 覆盖 element plus css -->
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.el-tabs__content {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.el-tabs__nav-scroll {
|
.el-tabs__nav-scroll {
|
||||||
margin: 10px 20px;
|
margin: 10px 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<!-- 样式 -->
|
|
||||||
<style scoped lang="scss">
|
|
||||||
// 跟容器
|
|
||||||
.role-container {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #ffffff;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.role-main {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.search-container {
|
|
||||||
margin: 20px 20px 0px 20px;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: -5px;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
width: 240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.role-category-list {
|
|
||||||
margin: 0 27px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.role-pane {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<el-container class="ai-layout">
|
<el-container class="absolute flex-1 top-0 left-0 h-full w-full">
|
||||||
<!-- 左侧:对话列表 -->
|
<!-- 左侧:对话列表 -->
|
||||||
<ConversationList
|
<ConversationList
|
||||||
:active-id="activeConversationId"
|
:active-id="activeConversationId"
|
||||||
|
|
@ -10,33 +10,38 @@
|
||||||
@on-conversation-delete="handlerConversationDelete"
|
@on-conversation-delete="handlerConversationDelete"
|
||||||
/>
|
/>
|
||||||
<!-- 右侧:对话详情 -->
|
<!-- 右侧:对话详情 -->
|
||||||
<el-container class="detail-container">
|
<el-container class="bg-[var(--el-bg-color)]">
|
||||||
<el-header class="header">
|
<el-header
|
||||||
<div class="title">
|
class="flex flex-row items-center justify-between bg-[var(--el-bg-color-page)] shadow-[0_0_0_0_var(--el-border-color-light)]"
|
||||||
|
>
|
||||||
|
<div class="text-18px font-bold">
|
||||||
{{ activeConversation?.title ? activeConversation?.title : '对话' }}
|
{{ activeConversation?.title ? activeConversation?.title : '对话' }}
|
||||||
<span v-if="activeMessageList.length">({{ activeMessageList.length }})</span>
|
<span v-if="activeMessageList.length">({{ activeMessageList.length }})</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="btns" v-if="activeConversation">
|
<div class="flex w-300px flex-row justify-end" v-if="activeConversation">
|
||||||
<el-button type="primary" bg plain size="small" @click="openChatConversationUpdateForm">
|
<el-button type="primary" bg plain size="small" @click="openChatConversationUpdateForm">
|
||||||
<span v-html="activeConversation?.modelName"></span>
|
<span v-html="activeConversation?.modelName"></span>
|
||||||
<Icon icon="ep:setting" class="ml-10px" />
|
<Icon icon="ep:setting" class="ml-10px" />
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" class="btn" @click="handlerMessageClear">
|
<el-button size="small" class="p-10px" @click="handlerMessageClear">
|
||||||
<Icon icon="heroicons-outline:archive-box-x-mark" color="#787878" />
|
<Icon
|
||||||
|
icon="heroicons-outline:archive-box-x-mark"
|
||||||
|
color="var(--el-text-color-placeholder)"
|
||||||
|
/>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" class="btn">
|
<el-button size="small" class="p-10px">
|
||||||
<Icon icon="ep:download" color="#787878" />
|
<Icon icon="ep:download" color="var(--el-text-color-placeholder)" />
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" class="btn" @click="handleGoTopMessage">
|
<el-button size="small" class="p-10px" @click="handleGoTopMessage">
|
||||||
<Icon icon="ep:top" color="#787878" />
|
<Icon icon="ep:top" color="var(--el-text-color-placeholder)" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
|
|
||||||
<!-- main:消息列表 -->
|
<!-- main:消息列表 -->
|
||||||
<el-main class="main-container">
|
<el-main class="m-0 p-0 relative h-full w-full">
|
||||||
<div>
|
<div>
|
||||||
<div class="message-container">
|
<div class="absolute top-0 bottom-0 left-0 right-0 overflow-y-hidden p-0 m-0">
|
||||||
<!-- 情况一:消息加载中 -->
|
<!-- 情况一:消息加载中 -->
|
||||||
<MessageLoading v-if="activeMessageListLoading" />
|
<MessageLoading v-if="activeMessageListLoading" />
|
||||||
<!-- 情况二:无聊天对话时 -->
|
<!-- 情况二:无聊天对话时 -->
|
||||||
|
|
@ -64,10 +69,14 @@
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|
||||||
<!-- 底部 -->
|
<!-- 底部 -->
|
||||||
<el-footer class="footer-container">
|
<el-footer class="flex flex-col !h-auto !p-0">
|
||||||
<form class="prompt-from">
|
<!-- TODO @芋艿:这块要想办法迁移下! -->
|
||||||
|
<form
|
||||||
|
class="mt-10px mx-20px mb-20px py-9px px-10px flex flex-col h-auto rounded-10px"
|
||||||
|
style="border: 1px solid var(--el-border-color)"
|
||||||
|
>
|
||||||
<textarea
|
<textarea
|
||||||
class="prompt-input"
|
class="h-80px border-none box-border resize-none py-0 px-2px overflow-auto focus:outline-none"
|
||||||
v-model="prompt"
|
v-model="prompt"
|
||||||
@keydown="handleSendByKeydown"
|
@keydown="handleSendByKeydown"
|
||||||
@input="handlePromptInput"
|
@input="handlePromptInput"
|
||||||
|
|
@ -75,7 +84,7 @@
|
||||||
@compositionend="onCompositionend"
|
@compositionend="onCompositionend"
|
||||||
placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
|
placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="prompt-btns">
|
<div class="flex justify-between pb-0 pt-5px">
|
||||||
<div>
|
<div>
|
||||||
<el-switch v-model="enableContext" />
|
<el-switch v-model="enableContext" />
|
||||||
<span class="ml-5px text-14px text-#8f8f8f">上下文</span>
|
<span class="ml-5px text-14px text-#8f8f8f">上下文</span>
|
||||||
|
|
@ -571,204 +580,3 @@ onMounted(async () => {
|
||||||
await getMessageList()
|
await getMessageList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ai-layout {
|
|
||||||
position: absolute;
|
|
||||||
flex: 1;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-container {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 10px 0;
|
|
||||||
|
|
||||||
.btn-new-conversation {
|
|
||||||
padding: 18px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list {
|
|
||||||
margin-top: 20px;
|
|
||||||
|
|
||||||
.conversation {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex: 1;
|
|
||||||
padding: 0 5px;
|
|
||||||
margin-top: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 5px;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 30px;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
|
|
||||||
.button {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding: 5px 10px;
|
|
||||||
max-width: 220px;
|
|
||||||
font-size: 14px;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对话编辑、删除
|
|
||||||
.button-wrapper {
|
|
||||||
right: 2px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-items: center;
|
|
||||||
color: #606266;
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 角色仓库、清空未设置对话
|
|
||||||
.tool-box {
|
|
||||||
line-height: 35px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--el-text-color);
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #606266;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 头部
|
|
||||||
.detail-container {
|
|
||||||
background: #ffffff;
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: #fbfbfb;
|
|
||||||
box-shadow: 0 0 0 0 #dcdfe6;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
width: 300px;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
//justify-content: space-between;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// main 容器
|
|
||||||
.main-container {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.message-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
overflow-y: hidden;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部
|
|
||||||
.footer-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: auto;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.prompt-from {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: auto;
|
|
||||||
border: 1px solid #e3e3e3;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin: 10px 20px 20px 20px;
|
|
||||||
padding: 9px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-input {
|
|
||||||
height: 80px;
|
|
||||||
//box-shadow: none;
|
|
||||||
border: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
resize: none;
|
|
||||||
padding: 0 2px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-input:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-bottom: 0;
|
|
||||||
padding-top: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<el-card body-class="" class="image-card">
|
<el-card
|
||||||
<div class="image-operation">
|
body-class=""
|
||||||
|
class="!w-80 !h-auto !rounded-10px !relative !flex !flex-col"
|
||||||
|
>
|
||||||
|
<div class="!flex !flex-row !justify-between">
|
||||||
<div>
|
<div>
|
||||||
<el-button type="primary" text bg v-if="detail?.status === AiImageStatusEnum.IN_PROGRESS">
|
<el-button type="primary" text bg v-if="detail?.status === AiImageStatusEnum.IN_PROGRESS">
|
||||||
生成中
|
生成中
|
||||||
|
|
@ -15,24 +18,34 @@
|
||||||
<!-- 操作区 -->
|
<!-- 操作区 -->
|
||||||
<div>
|
<div>
|
||||||
<el-button
|
<el-button
|
||||||
class="btn"
|
class="!p-10px !m-0"
|
||||||
text
|
text
|
||||||
:icon="Download"
|
:icon="Download"
|
||||||
@click="handleButtonClick('download', detail)"
|
@click="handleButtonClick('download', detail)"
|
||||||
/>
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
class="btn"
|
class="!p-10px !m-0"
|
||||||
text
|
text
|
||||||
:icon="RefreshRight"
|
:icon="RefreshRight"
|
||||||
@click="handleButtonClick('regeneration', detail)"
|
@click="handleButtonClick('regeneration', detail)"
|
||||||
/>
|
/>
|
||||||
<el-button class="btn" text :icon="Delete" @click="handleButtonClick('delete', detail)" />
|
<el-button
|
||||||
<el-button class="btn" text :icon="More" @click="handleButtonClick('more', detail)" />
|
class="!p-10px !m-0"
|
||||||
|
text
|
||||||
|
:icon="Delete"
|
||||||
|
@click="handleButtonClick('delete', detail)"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
class="!p-10px !m-0"
|
||||||
|
text
|
||||||
|
:icon="More"
|
||||||
|
@click="handleButtonClick('more', detail)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-wrapper" ref="cardImageRef">
|
<div class="!overflow-hidden !mt-20px !h-280px !flex-1" ref="cardImageRef">
|
||||||
<el-image
|
<el-image
|
||||||
class="image"
|
class="!w-full !rounded-10px"
|
||||||
:src="detail?.picUrl"
|
:src="detail?.picUrl"
|
||||||
:preview-src-list="[detail.picUrl]"
|
:preview-src-list="[detail.picUrl]"
|
||||||
preview-teleported
|
preview-teleported
|
||||||
|
|
@ -42,7 +55,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Midjourney 专属操作 -->
|
<!-- Midjourney 专属操作 -->
|
||||||
<div class="image-mj-btns">
|
<div class="!mt-5px !w-full !flex !flex-row !flex-wrap !justify-start">
|
||||||
<el-button
|
<el-button
|
||||||
size="small"
|
size="small"
|
||||||
v-for="button in detail?.buttons"
|
v-for="button in detail?.buttons"
|
||||||
|
|
@ -116,47 +129,3 @@ onMounted(async () => {
|
||||||
await handleLoading(props.detail.status as string)
|
await handleLoading(props.detail.status as string)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.image-card {
|
|
||||||
width: 320px;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 10px;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.image-operation {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
//border: 1px solid red;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-wrapper {
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 20px;
|
|
||||||
height: 280px;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.image {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-mj-btns {
|
|
||||||
margin-top: 5px;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -5,139 +5,111 @@
|
||||||
@close="handleDrawerClose"
|
@close="handleDrawerClose"
|
||||||
custom-class="drawer-class"
|
custom-class="drawer-class"
|
||||||
>
|
>
|
||||||
<!-- 图片 -->
|
<!-- 图片预览 -->
|
||||||
<div class="item">
|
<div class="mb-5">
|
||||||
<div class="body">
|
<el-image
|
||||||
<el-image
|
:src="detail?.picUrl"
|
||||||
class="image"
|
:preview-src-list="[detail.picUrl]"
|
||||||
:src="detail?.picUrl"
|
preview-teleported
|
||||||
:preview-src-list="[detail.picUrl]"
|
class="w-full rounded-2"
|
||||||
preview-teleported
|
fit="contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 时间 -->
|
|
||||||
<div class="item">
|
|
||||||
<div class="tip">时间</div>
|
|
||||||
<div class="body">
|
|
||||||
<div>提交时间:{{ formatTime(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}</div>
|
|
||||||
<div>生成时间:{{ formatTime(detail.finishTime, 'yyyy-MM-dd HH:mm:ss') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 模型 -->
|
|
||||||
<div class="item">
|
|
||||||
<div class="tip">模型</div>
|
|
||||||
<div class="body"> {{ detail.model }}({{ detail.height }}x{{ detail.width }}) </div>
|
|
||||||
</div>
|
|
||||||
<!-- 提示词 -->
|
|
||||||
<div class="item">
|
|
||||||
<div class="tip">提示词</div>
|
|
||||||
<div class="body">
|
|
||||||
{{ detail.prompt }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 地址 -->
|
|
||||||
<div class="item">
|
|
||||||
<div class="tip">图片地址</div>
|
|
||||||
<div class="body">
|
|
||||||
{{ detail.picUrl }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 基础信息 -->
|
||||||
|
<el-descriptions title="基础信息" :column="1" :label-width="100" border>
|
||||||
|
<el-descriptions-item label="提交时间">
|
||||||
|
{{ formatTime(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="生成时间">
|
||||||
|
{{ formatTime(detail.finishTime, 'yyyy-MM-dd HH:mm:ss') }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="模型">
|
||||||
|
{{ detail.model }}({{ detail.height }}x{{ detail.width }})
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="提示词">
|
||||||
|
<div class="break-words">{{ detail.prompt }}</div>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="图片地址">
|
||||||
|
<div class="break-all text-xs">{{ detail.picUrl }}</div>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
|
||||||
<!-- StableDiffusion 专属区域 -->
|
<!-- StableDiffusion 专属区域 -->
|
||||||
<div
|
<el-descriptions
|
||||||
class="item"
|
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && hasStableDiffusionOptions"
|
||||||
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.sampler"
|
title="StableDiffusion 参数"
|
||||||
|
:column="1"
|
||||||
|
:label-width="100"
|
||||||
|
border
|
||||||
|
class="mt-5"
|
||||||
>
|
>
|
||||||
<div class="tip">采样方法</div>
|
<el-descriptions-item v-if="detail?.options?.sampler" label="采样方法">
|
||||||
<div class="body">
|
|
||||||
{{
|
{{
|
||||||
StableDiffusionSamplers.find(
|
StableDiffusionSamplers.find(
|
||||||
(item: ImageModelVO) => item.key === detail?.options?.sampler
|
(item: ImageModelVO) => item.key === detail?.options?.sampler
|
||||||
)?.name
|
)?.name
|
||||||
}}
|
}}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
<el-descriptions-item v-if="detail?.options?.clipGuidancePreset" label="CLIP">
|
||||||
<div
|
|
||||||
class="item"
|
|
||||||
v-if="
|
|
||||||
detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.clipGuidancePreset
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="tip">CLIP</div>
|
|
||||||
<div class="body">
|
|
||||||
{{
|
{{
|
||||||
StableDiffusionClipGuidancePresets.find(
|
StableDiffusionClipGuidancePresets.find(
|
||||||
(item: ImageModelVO) => item.key === detail?.options?.clipGuidancePreset
|
(item: ImageModelVO) => item.key === detail?.options?.clipGuidancePreset
|
||||||
)?.name
|
)?.name
|
||||||
}}
|
}}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
<el-descriptions-item v-if="detail?.options?.stylePreset" label="风格">
|
||||||
<div
|
|
||||||
class="item"
|
|
||||||
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.stylePreset"
|
|
||||||
>
|
|
||||||
<div class="tip">风格</div>
|
|
||||||
<div class="body">
|
|
||||||
{{
|
{{
|
||||||
StableDiffusionStylePresets.find(
|
StableDiffusionStylePresets.find(
|
||||||
(item: ImageModelVO) => item.key === detail?.options?.stylePreset
|
(item: ImageModelVO) => item.key === detail?.options?.stylePreset
|
||||||
)?.name
|
)?.name
|
||||||
}}
|
}}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
<el-descriptions-item v-if="detail?.options?.steps" label="迭代步数">
|
||||||
<div
|
|
||||||
class="item"
|
|
||||||
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.steps"
|
|
||||||
>
|
|
||||||
<div class="tip">迭代步数</div>
|
|
||||||
<div class="body">
|
|
||||||
{{ detail?.options?.steps }}
|
{{ detail?.options?.steps }}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
<el-descriptions-item v-if="detail?.options?.scale" label="引导系数">
|
||||||
<div
|
|
||||||
class="item"
|
|
||||||
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.scale"
|
|
||||||
>
|
|
||||||
<div class="tip">引导系数</div>
|
|
||||||
<div class="body">
|
|
||||||
{{ detail?.options?.scale }}
|
{{ detail?.options?.scale }}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
<el-descriptions-item v-if="detail?.options?.seed" label="随机因子">
|
||||||
<div
|
|
||||||
class="item"
|
|
||||||
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && detail?.options?.seed"
|
|
||||||
>
|
|
||||||
<div class="tip">随机因子</div>
|
|
||||||
<div class="body">
|
|
||||||
{{ detail?.options?.seed }}
|
{{ detail?.options?.seed }}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
</el-descriptions>
|
||||||
|
|
||||||
<!-- Dall3 专属区域 -->
|
<!-- Dall3 专属区域 -->
|
||||||
<div class="item" v-if="detail.platform === AiPlatformEnum.OPENAI && detail?.options?.style">
|
<el-descriptions
|
||||||
<div class="tip">风格选择</div>
|
v-if="detail.platform === AiPlatformEnum.OPENAI && detail?.options?.style"
|
||||||
<div class="body">
|
title="DALL-E 3 参数"
|
||||||
|
:column="1"
|
||||||
|
:label-width="100"
|
||||||
|
border
|
||||||
|
class="mt-5"
|
||||||
|
>
|
||||||
|
<el-descriptions-item label="风格选择">
|
||||||
{{ Dall3StyleList.find((item: ImageModelVO) => item.key === detail?.options?.style)?.name }}
|
{{ Dall3StyleList.find((item: ImageModelVO) => item.key === detail?.options?.style)?.name }}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
</el-descriptions>
|
||||||
|
|
||||||
<!-- Midjourney 专属区域 -->
|
<!-- Midjourney 专属区域 -->
|
||||||
<div
|
<el-descriptions
|
||||||
class="item"
|
v-if="detail.platform === AiPlatformEnum.MIDJOURNEY && hasMidjourneyOptions"
|
||||||
v-if="detail.platform === AiPlatformEnum.MIDJOURNEY && detail?.options?.version"
|
title="Midjourney 参数"
|
||||||
|
:column="1"
|
||||||
|
:label-width="100"
|
||||||
|
border
|
||||||
|
class="mt-5"
|
||||||
>
|
>
|
||||||
<div class="tip">模型版本</div>
|
<el-descriptions-item v-if="detail?.options?.version" label="模型版本">
|
||||||
<div class="body">
|
|
||||||
{{ detail?.options?.version }}
|
{{ detail?.options?.version }}
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
</div>
|
<el-descriptions-item v-if="detail?.options?.referImageUrl" label="参考图">
|
||||||
<div
|
<el-image
|
||||||
class="item"
|
:src="detail.options.referImageUrl"
|
||||||
v-if="detail.platform === AiPlatformEnum.MIDJOURNEY && detail?.options?.referImageUrl"
|
class="max-w-[200px] rounded-2"
|
||||||
>
|
fit="contain"
|
||||||
<div class="tip">参考图</div>
|
/>
|
||||||
<div class="body">
|
</el-descriptions-item>
|
||||||
<el-image :src="detail.options.referImageUrl" />
|
</el-descriptions>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -156,6 +128,25 @@ import { formatTime } from '@/utils'
|
||||||
const showDrawer = ref<boolean>(false) // 是否显示
|
const showDrawer = ref<boolean>(false) // 是否显示
|
||||||
const detail = ref<ImageVO>({} as ImageVO) // 图片详细信息
|
const detail = ref<ImageVO>({} as ImageVO) // 图片详细信息
|
||||||
|
|
||||||
|
// 计算属性:判断是否有 StableDiffusion 选项
|
||||||
|
const hasStableDiffusionOptions = computed(() => {
|
||||||
|
const options = detail.value?.options
|
||||||
|
return (
|
||||||
|
options?.sampler ||
|
||||||
|
options?.clipGuidancePreset ||
|
||||||
|
options?.stylePreset ||
|
||||||
|
options?.steps ||
|
||||||
|
options?.scale ||
|
||||||
|
options?.seed
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:判断是否有 Midjourney 选项
|
||||||
|
const hasMidjourneyOptions = computed(() => {
|
||||||
|
const options = detail.value?.options
|
||||||
|
return options?.version || options?.referImageUrl
|
||||||
|
})
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -175,7 +166,7 @@ const handleDrawerClose = async () => {
|
||||||
|
|
||||||
/** 监听 drawer 是否打开 */
|
/** 监听 drawer 是否打开 */
|
||||||
const { show } = toRefs(props)
|
const { show } = toRefs(props)
|
||||||
watch(show, async (newValue, oldValue) => {
|
watch(show, async (newValue, _oldValue) => {
|
||||||
showDrawer.value = newValue as boolean
|
showDrawer.value = newValue as boolean
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -186,7 +177,7 @@ const getImageDetail = async (id: number) => {
|
||||||
|
|
||||||
/** 监听 id 变化,加载最新图片详情 */
|
/** 监听 id 变化,加载最新图片详情 */
|
||||||
const { id } = toRefs(props)
|
const { id } = toRefs(props)
|
||||||
watch(id, async (newVal, oldVal) => {
|
watch(id, async (newVal, _oldVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
await getImageDetail(newVal)
|
await getImageDetail(newVal)
|
||||||
}
|
}
|
||||||
|
|
@ -194,31 +185,3 @@ watch(id, async (newVal, oldVal) => {
|
||||||
|
|
||||||
const emits = defineEmits(['handleDrawerClose'])
|
const emits = defineEmits(['handleDrawerClose'])
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
.item {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
word-wrap: break-word;
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
margin-top: 10px;
|
|
||||||
color: #616161;
|
|
||||||
|
|
||||||
.taskImage {
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<el-card class="dr-task" body-class="task-card" shadow="never">
|
<el-card
|
||||||
|
class="wh-full"
|
||||||
|
:body-style="{ margin: 0, padding: 0, height: '100%', position: 'relative' }"
|
||||||
|
shadow="never"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
绘画任务
|
绘画任务
|
||||||
<!-- TODO @fan:看看,怎么优化下这个样子哈。 -->
|
<!-- TODO @fan:看看,怎么优化下这个样子哈。 -->
|
||||||
<el-button @click="handleViewPublic">绘画作品</el-button>
|
<el-button @click="handleViewPublic">绘画作品</el-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- 图片列表 -->
|
<!-- 图片列表 -->
|
||||||
<div class="task-image-list" ref="imageListRef">
|
<div
|
||||||
|
class="relative flex flex-row flex-wrap content-start h-full overflow-auto p-5 pb-[140px] box-border [&>div]:mr-5 [&>div]:mb-5"
|
||||||
|
ref="imageListRef"
|
||||||
|
>
|
||||||
<ImageCard
|
<ImageCard
|
||||||
v-for="image in imageList"
|
v-for="image in imageList"
|
||||||
:key="image.id"
|
:key="image.id"
|
||||||
|
|
@ -15,7 +22,9 @@
|
||||||
@on-mj-btn-click="handleImageMidjourneyButtonClick"
|
@on-mj-btn-click="handleImageMidjourneyButtonClick"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-image-pagination">
|
<div
|
||||||
|
class="absolute bottom-[60px] h-[50px] leading-[90px] w-full z-[999] bg-white flex flex-row justify-center items-center"
|
||||||
|
>
|
||||||
<Pagination
|
<Pagination
|
||||||
:total="pageTotal"
|
:total="pageTotal"
|
||||||
v-model:page="queryParams.pageNo"
|
v-model:page="queryParams.pageNo"
|
||||||
|
|
@ -150,12 +159,12 @@ const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
|
||||||
}
|
}
|
||||||
// 下载
|
// 下载
|
||||||
if (type === 'download') {
|
if (type === 'download') {
|
||||||
await download.image({ url: imageDetail.picUrl })
|
download.image({ url: imageDetail.picUrl })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 重新生成
|
// 重新生成
|
||||||
if (type === 'regeneration') {
|
if (type === 'regeneration') {
|
||||||
await emits('onRegeneration', imageDetail)
|
emits('onRegeneration', imageDetail)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,49 +206,3 @@ onUnmounted(async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
|
||||||
.dr-task {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.task-card {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-image-list {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-content: flex-start;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 20px 20px 140px;
|
|
||||||
box-sizing: border-box; /* 确保内边距不会增加高度 */
|
|
||||||
|
|
||||||
> div {
|
|
||||||
margin-right: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
> div:last-of-type {
|
|
||||||
//margin-bottom: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-image-pagination {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 60px;
|
|
||||||
height: 50px;
|
|
||||||
line-height: 90px;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 999;
|
|
||||||
background-color: #ffffff;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="hot-words">
|
<div class="flex flex-col mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">随机热词</el-text>
|
<el-text tag="b">随机热词</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="word-list">
|
<el-space wrap class="flex flex-row flex-wrap justify-start mt-15px">
|
||||||
<el-button
|
<el-button
|
||||||
round
|
round
|
||||||
class="btn"
|
class="m-0"
|
||||||
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
||||||
v-for="hotWord in ImageHotWords"
|
v-for="hotWord in ImageHotWords"
|
||||||
:key="hotWord"
|
:key="hotWord"
|
||||||
|
|
@ -31,11 +31,11 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">平台</el-text>
|
<el-text tag="b">平台</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="otherPlatform"
|
v-model="otherPlatform"
|
||||||
placeholder="Select"
|
placeholder="Select"
|
||||||
|
|
@ -52,11 +52,11 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">模型</el-text>
|
<el-text tag="b">模型</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-select v-model="modelId" placeholder="Select" size="large" class="!w-350px">
|
<el-select v-model="modelId" placeholder="Select" size="large" class="!w-350px">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in platformModels"
|
v-for="item in platformModels"
|
||||||
|
|
@ -67,16 +67,16 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">图片尺寸</el-text>
|
<el-text tag="b">图片尺寸</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-input v-model="width" type="number" class="w-170px" placeholder="图片宽度" />
|
<el-input v-model="width" type="number" class="w-170px" placeholder="图片宽度" />
|
||||||
<el-input v-model="height" type="number" class="w-170px" placeholder="图片高度" />
|
<el-input v-model="height" type="number" class="w-170px" placeholder="图片高度" />
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="btns">
|
<div class="flex justify-center mt-50px">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
|
|
@ -187,38 +187,3 @@ watch(
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
.hot-words {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.word-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: start;
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模型
|
|
||||||
.group-item {
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.group-item-body {
|
|
||||||
margin-top: 15px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="hot-words">
|
<div class="flex flex-col mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">随机热词</el-text>
|
<el-text tag="b">随机热词</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="word-list">
|
<el-space wrap class="flex flex-row flex-wrap justify-start mt-15px">
|
||||||
<el-button
|
<el-button
|
||||||
round
|
round
|
||||||
class="btn"
|
class="m-0"
|
||||||
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
||||||
v-for="hotWord in ImageHotWords"
|
v-for="hotWord in ImageHotWords"
|
||||||
:key="hotWord"
|
:key="hotWord"
|
||||||
|
|
@ -31,57 +31,57 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="model">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">模型选择</el-text>
|
<el-text tag="b">模型选择</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="model-list">
|
<el-space wrap class="mt-15px">
|
||||||
<div
|
<div
|
||||||
:class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
|
:class="selectModel === model.key ? 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-#1293ff rounded-5px cursor-pointer' : 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-transparent cursor-pointer'"
|
||||||
v-for="model in Dall3Models"
|
v-for="model in Dall3Models"
|
||||||
:key="model.key"
|
:key="model.key"
|
||||||
>
|
>
|
||||||
<el-image :src="model.image" fit="contain" @click="handleModelClick(model)" />
|
<el-image :src="model.image" fit="contain" @click="handleModelClick(model)" />
|
||||||
<div class="model-font">{{ model.name }}</div>
|
<div class="text-14px color-#3e3e3e font-bold">{{ model.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-style">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">风格选择</el-text>
|
<el-text tag="b">风格选择</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="image-style-list">
|
<el-space wrap class="mt-15px">
|
||||||
<div
|
<div
|
||||||
:class="style === imageStyle.key ? 'image-style-item selectImageStyle' : 'image-style-item'"
|
:class="style === imageStyle.key ? 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-#1293ff rounded-5px cursor-pointer' : 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-transparent cursor-pointer'"
|
||||||
v-for="imageStyle in Dall3StyleList"
|
v-for="imageStyle in Dall3StyleList"
|
||||||
:key="imageStyle.key"
|
:key="imageStyle.key"
|
||||||
>
|
>
|
||||||
<el-image :src="imageStyle.image" fit="contain" @click="handleStyleClick(imageStyle)" />
|
<el-image :src="imageStyle.image" fit="contain" @click="handleStyleClick(imageStyle)" />
|
||||||
<div class="style-font">{{ imageStyle.name }}</div>
|
<div class="text-14px color-#3e3e3e font-bold">{{ imageStyle.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-size">
|
<div class="w-full mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">画面比例</el-text>
|
<el-text tag="b">画面比例</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="size-list">
|
<el-space wrap class="flex flex-row justify-between w-full mt-20px">
|
||||||
<div
|
<div
|
||||||
class="size-item"
|
class="flex flex-col items-center cursor-pointer"
|
||||||
v-for="imageSize in Dall3SizeList"
|
v-for="imageSize in Dall3SizeList"
|
||||||
:key="imageSize.key"
|
:key="imageSize.key"
|
||||||
@click="handleSizeClick(imageSize)"
|
@click="handleSizeClick(imageSize)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="selectSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'"
|
:class="selectSize === imageSize.key ? 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-#1293ff' : 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-white'"
|
||||||
>
|
>
|
||||||
<div :style="imageSize.style"></div>
|
<div :style="imageSize.style"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="size-font">{{ imageSize.name }}</div>
|
<div class="text-14px color-#3e3e3e font-bold">{{ imageSize.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="btns">
|
<div class="flex justify-center mt-50px">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
|
|
@ -229,135 +229,4 @@ const settingValues = async (detail: ImageVO) => {
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
// 热词
|
|
||||||
.hot-words {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.word-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: start;
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模型
|
|
||||||
.model {
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.model-list {
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
.modal-item {
|
|
||||||
width: 110px;
|
|
||||||
//outline: 1px solid blue;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
border: 3px solid transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.model-font {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #3e3e3e;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectModel {
|
|
||||||
border: 3px solid #1293ff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 样式 style
|
|
||||||
.image-style {
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.image-style-list {
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
.image-style-item {
|
|
||||||
width: 110px;
|
|
||||||
//outline: 1px solid blue;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
border: 3px solid transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.style-font {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #3e3e3e;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectImageStyle {
|
|
||||||
border: 3px solid #1293ff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尺寸
|
|
||||||
.image-size {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.size-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 20px;
|
|
||||||
|
|
||||||
.size-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.size-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding: 4px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-font {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #3e3e3e;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectImageSize {
|
|
||||||
border: 1px solid #1293ff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="hot-words">
|
<div class="flex flex-col mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">随机热词</el-text>
|
<el-text tag="b">随机热词</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="word-list">
|
<el-space wrap class="flex flex-row flex-wrap justify-start mt-15px">
|
||||||
<el-button
|
<el-button
|
||||||
round
|
round
|
||||||
class="btn"
|
class="m-0"
|
||||||
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
||||||
v-for="hotWord in ImageHotWords"
|
v-for="hotWord in ImageHotWords"
|
||||||
:key="hotWord"
|
:key="hotWord"
|
||||||
|
|
@ -31,49 +31,49 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="image-size">
|
<div class="w-full mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">尺寸</el-text>
|
<el-text tag="b">尺寸</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="size-list">
|
<el-space wrap class="flex flex-row justify-between w-full mt-20px">
|
||||||
<div
|
<div
|
||||||
class="size-item"
|
class="flex flex-col items-center cursor-pointer"
|
||||||
v-for="imageSize in MidjourneySizeList"
|
v-for="imageSize in MidjourneySizeList"
|
||||||
:key="imageSize.key"
|
:key="imageSize.key"
|
||||||
@click="handleSizeClick(imageSize)"
|
@click="handleSizeClick(imageSize)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="selectSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'"
|
:class="selectSize === imageSize.key ? 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-#1293ff' : 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-white'"
|
||||||
>
|
>
|
||||||
<div :style="imageSize.style"></div>
|
<div :style="imageSize.style"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="size-font">{{ imageSize.key }}</div>
|
<div class="text-14px color-#3e3e3e font-bold">{{ imageSize.key }}</div>
|
||||||
</div>
|
</div>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="model">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">模型</el-text>
|
<el-text tag="b">模型</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="model-list">
|
<el-space wrap class="mt-15px">
|
||||||
<div
|
<div
|
||||||
:class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
|
:class="selectModel === model.key ? 'flex flex-col items-center w-150px overflow-hidden border-3 border-solid border-#1293ff rounded-5px cursor-pointer' : 'flex flex-col items-center w-150px overflow-hidden border-3 border-solid border-transparent cursor-pointer'"
|
||||||
v-for="model in MidjourneyModels"
|
v-for="model in MidjourneyModels"
|
||||||
:key="model.key"
|
:key="model.key"
|
||||||
>
|
>
|
||||||
<el-image :src="model.image" fit="contain" @click="handleModelClick(model)" />
|
<el-image :src="model.image" fit="contain" @click="handleModelClick(model)" />
|
||||||
<div class="model-font">{{ model.name }}</div>
|
<div class="text-14px color-#3e3e3e font-bold">{{ model.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="version">
|
<div class="mt-20px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">版本</el-text>
|
<el-text tag="b">版本</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="version-list">
|
<el-space wrap class="mt-20px w-full">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="selectVersion"
|
v-model="selectVersion"
|
||||||
class="version-select !w-350px"
|
class="!w-350px"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请选择版本"
|
placeholder="请选择版本"
|
||||||
>
|
>
|
||||||
|
|
@ -86,15 +86,15 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="model">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">参考图</el-text>
|
<el-text tag="b">参考图</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="model-list">
|
<el-space wrap class="mt-15px">
|
||||||
<UploadImg v-model="referImageUrl" height="120px" width="120px" />
|
<UploadImg v-model="referImageUrl" height="120px" width="120px" />
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="btns">
|
<div class="flex justify-center mt-50px">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
|
|
@ -233,118 +233,4 @@ const settingValues = async (detail: ImageVO) => {
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
// 提示词
|
|
||||||
.prompt {
|
|
||||||
}
|
|
||||||
|
|
||||||
// 热词
|
|
||||||
.hot-words {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.word-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: start;
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// version
|
|
||||||
.version {
|
|
||||||
margin-top: 20px;
|
|
||||||
|
|
||||||
.version-list {
|
|
||||||
margin-top: 20px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模型
|
|
||||||
.model {
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.model-list {
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
.modal-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 150px;
|
|
||||||
//outline: 1px solid blue;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 3px solid transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.model-font {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #3e3e3e;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectModel {
|
|
||||||
border: 3px solid #1293ff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尺寸
|
|
||||||
.image-size {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.size-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 20px;
|
|
||||||
|
|
||||||
.size-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.size-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding: 4px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-font {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #3e3e3e;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectImageSize {
|
|
||||||
border: 1px solid #1293ff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="hot-words">
|
<div class="flex flex-col mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">随机热词</el-text>
|
<el-text tag="b">随机热词</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="word-list">
|
<el-space wrap class="flex flex-row flex-wrap justify-start mt-15px">
|
||||||
<el-button
|
<el-button
|
||||||
round
|
round
|
||||||
class="btn"
|
class="m-0"
|
||||||
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
:type="selectHotWord === hotWord ? 'primary' : 'default'"
|
||||||
v-for="hotWord in ImageHotEnglishWords"
|
v-for="hotWord in ImageHotEnglishWords"
|
||||||
:key="hotWord"
|
:key="hotWord"
|
||||||
|
|
@ -31,11 +31,11 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">采样方法</el-text>
|
<el-text tag="b">采样方法</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-select v-model="sampler" placeholder="Select" size="large" class="!w-350px">
|
<el-select v-model="sampler" placeholder="Select" size="large" class="!w-350px">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in StableDiffusionSamplers"
|
v-for="item in StableDiffusionSamplers"
|
||||||
|
|
@ -46,11 +46,11 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">CLIP</el-text>
|
<el-text tag="b">CLIP</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-select v-model="clipGuidancePreset" placeholder="Select" size="large" class="!w-350px">
|
<el-select v-model="clipGuidancePreset" placeholder="Select" size="large" class="!w-350px">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in StableDiffusionClipGuidancePresets"
|
v-for="item in StableDiffusionClipGuidancePresets"
|
||||||
|
|
@ -61,11 +61,11 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">风格</el-text>
|
<el-text tag="b">风格</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-select v-model="stylePreset" placeholder="Select" size="large" class="!w-350px">
|
<el-select v-model="stylePreset" placeholder="Select" size="large" class="!w-350px">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in StableDiffusionStylePresets"
|
v-for="item in StableDiffusionStylePresets"
|
||||||
|
|
@ -76,20 +76,20 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">图片尺寸</el-text>
|
<el-text tag="b">图片尺寸</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-input v-model="width" class="w-170px" placeholder="图片宽度" />
|
<el-input v-model="width" class="w-170px" placeholder="图片宽度" />
|
||||||
<el-input v-model="height" class="w-170px" placeholder="图片高度" />
|
<el-input v-model="height" class="w-170px" placeholder="图片高度" />
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">迭代步数</el-text>
|
<el-text tag="b">迭代步数</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="steps"
|
v-model="steps"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -99,11 +99,11 @@
|
||||||
/>
|
/>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">引导系数</el-text>
|
<el-text tag="b">引导系数</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="scale"
|
v-model="scale"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -113,11 +113,11 @@
|
||||||
/>
|
/>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="group-item">
|
<div class="mt-30px">
|
||||||
<div>
|
<div>
|
||||||
<el-text tag="b">随机因子</el-text>
|
<el-text tag="b">随机因子</el-text>
|
||||||
</div>
|
</div>
|
||||||
<el-space wrap class="group-item-body">
|
<el-space wrap class="mt-15px w-full">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="seed"
|
v-model="seed"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
/>
|
/>
|
||||||
</el-space>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
<div class="btns">
|
<div class="flex justify-center mt-50px">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
|
|
@ -254,43 +254,4 @@ const settingValues = async (detail: ImageVO) => {
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
// 提示词
|
|
||||||
.prompt {
|
|
||||||
}
|
|
||||||
|
|
||||||
// 热词
|
|
||||||
.hot-words {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.word-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: start;
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模型
|
|
||||||
.group-item {
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
.group-item-body {
|
|
||||||
margin-top: 15px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
<!-- image -->
|
<!-- image -->
|
||||||
<template>
|
<template>
|
||||||
<div class="ai-image">
|
<div class="absolute inset-0 flex flex-row wh-full">
|
||||||
<div class="left">
|
<div class="flex flex-col p-5 w-[390px]">
|
||||||
<div class="segmented">
|
<div class="mb-[30px]">
|
||||||
<el-segmented v-model="selectPlatform" :options="platformOptions" />
|
<el-segmented
|
||||||
|
v-model="selectPlatform"
|
||||||
|
:options="platformOptions"
|
||||||
|
class="w-[350px] !bg-[#ececec] [--el-border-radius-base:16px] [--el-segmented-item-selected-color:#fff]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-switch-container">
|
<div class="h-full overflow-y-auto">
|
||||||
<Common
|
<Common
|
||||||
v-if="selectPlatform === 'common'"
|
v-if="selectPlatform === 'common'"
|
||||||
ref="commonRef"
|
ref="commonRef"
|
||||||
|
|
@ -32,7 +36,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="flex-1 bg-white">
|
||||||
<ImageList ref="imageListRef" @on-regeneration="handleRegeneration" />
|
<ImageList ref="imageListRef" @on-regeneration="handleRegeneration" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -79,10 +83,10 @@ const platformOptions = [
|
||||||
const models = ref<ModelVO[]>([]) // 模型列表
|
const models = ref<ModelVO[]>([]) // 模型列表
|
||||||
|
|
||||||
/** 绘画 start */
|
/** 绘画 start */
|
||||||
const handleDrawStart = async (platform: string) => {}
|
const handleDrawStart = async (_platform: string) => {}
|
||||||
|
|
||||||
/** 绘画 complete */
|
/** 绘画 complete */
|
||||||
const handleDrawComplete = async (platform: string) => {
|
const handleDrawComplete = async (_platform: string) => {
|
||||||
await imageListRef.value.getImageList()
|
await imageListRef.value.getImageList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,48 +112,3 @@ onMounted(async () => {
|
||||||
models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.IMAGE)
|
models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.IMAGE)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.ai-image {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 20px;
|
|
||||||
width: 390px;
|
|
||||||
|
|
||||||
.segmented .el-segmented {
|
|
||||||
--el-border-radius-base: 16px;
|
|
||||||
--el-segmented-item-selected-color: #fff;
|
|
||||||
background-color: #ececec;
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-switch-container {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
flex: 1;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
width: 350px;
|
|
||||||
background-color: #f7f8fa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="square-container">
|
<div class="bg-white p-20px">
|
||||||
<!-- TODO @fan:style 建议换成 unocss -->
|
<!-- TODO @fan:style 建议换成 unocss -->
|
||||||
<!-- TODO @fan:Search 可以换成 Icon 组件么? -->
|
<!-- TODO @fan:Search 可以换成 Icon 组件么? -->
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.prompt"
|
v-model="queryParams.prompt"
|
||||||
style="width: 100%; margin-bottom: 20px"
|
class="!w-full !mb-20px"
|
||||||
size="large"
|
size="large"
|
||||||
placeholder="请输入要搜索的内容"
|
placeholder="请输入要搜索的内容"
|
||||||
:suffix-icon="Search"
|
:suffix-icon="Search"
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
<div class="gallery">
|
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-10px bg-white shadow-[0_0_10px_rgba(0,0,0,0.1)]">
|
||||||
<!-- TODO @fan:这个图片的风格,要不和 ImageCard.vue 界面一致?(只有卡片,没有操作);因为看着更有相框的感觉~~~ -->
|
<!-- TODO @fan:这个图片的风格,要不和 ImageCard.vue 界面一致?(只有卡片,没有操作);因为看着更有相框的感觉~~~ -->
|
||||||
<div v-for="item in list" :key="item.id" class="gallery-item">
|
<div v-for="item in list" :key="item.id" class="relative overflow-hidden bg-gray-100 cursor-pointer transition-transform duration-300 hover:scale-105">
|
||||||
<img :src="item.picUrl" class="img" />
|
<img :src="item.picUrl" class="w-full h-auto block transition-transform duration-300 hover:scale-110" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO @fan:缺少翻页 -->
|
<!-- TODO @fan:缺少翻页 -->
|
||||||
|
|
@ -64,41 +64,4 @@ onMounted(async () => {
|
||||||
await getList()
|
await getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
|
||||||
.square-container {
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
.gallery {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
//max-width: 1000px;
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #f0f0f0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
transition: transform 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item:hover img {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-item:hover {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,3 @@ const emits = defineEmits<{
|
||||||
(e: 'update:modelValue', value: string): void
|
(e: 'update:modelValue', value: string): void
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
<style scoped></style>
|
|
||||||
|
|
|
||||||
|
|
@ -510,10 +510,15 @@ const isManagerUser = (row: any) => {
|
||||||
|
|
||||||
/** 处理模型的排序 **/
|
/** 处理模型的排序 **/
|
||||||
const handleModelSort = () => {
|
const handleModelSort = () => {
|
||||||
// 保存初始数据
|
if (isModelSorting.value) {
|
||||||
originalData.value = cloneDeep(props.categoryInfo.modelList)
|
// 如果已经在排序状态,则取消排序
|
||||||
isModelSorting.value = true
|
handleModelSortCancel()
|
||||||
initSort()
|
} else {
|
||||||
|
// 保存初始数据
|
||||||
|
originalData.value = cloneDeep(props.categoryInfo.modelList)
|
||||||
|
isModelSorting.value = true
|
||||||
|
initSort()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理模型的排序提交 */
|
/** 处理模型的排序提交 */
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,23 @@ const currentSelectType = ref<'start' | 'manager'>('start')
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||||
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
key: [
|
||||||
|
{ required: true, message: '流程标识不能为空', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: (_rule: any, value: string, callback: any) => {
|
||||||
|
if (!value) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!/^[a-zA-Z_][\-_.0-9_a-zA-Z$]*$/.test(value)) {
|
||||||
|
callback(new Error('只能包含字母、数字、下划线、连字符和点号,且必须以字母或下划线开头'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
|
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
|
||||||
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,16 @@ const initData = async () => {
|
||||||
// 特殊:复制场景
|
// 特殊:复制场景
|
||||||
if (route.params.type === 'copy') {
|
if (route.params.type === 'copy') {
|
||||||
delete formData.value.id
|
delete formData.value.id
|
||||||
|
if (formData.value.bpmnXml) {
|
||||||
|
formData.value.bpmnXml = formData.value.bpmnXml.replaceAll(
|
||||||
|
formData.value.name,
|
||||||
|
formData.value.name + '副本'
|
||||||
|
)
|
||||||
|
formData.value.bpmnXml = formData.value.bpmnXml.replaceAll(
|
||||||
|
formData.value.key,
|
||||||
|
formData.value.key + '_copy'
|
||||||
|
)
|
||||||
|
}
|
||||||
formData.value.name += '副本'
|
formData.value.name += '副本'
|
||||||
formData.value.key += '_copy'
|
formData.value.key += '_copy'
|
||||||
tagsView.setTitle('复制流程')
|
tagsView.setTitle('复制流程')
|
||||||
|
|
|
||||||
|
|
@ -685,6 +685,7 @@ watch(
|
||||||
|
|
||||||
/** 弹出气泡卡 */
|
/** 弹出气泡卡 */
|
||||||
const openPopover = async (type: string) => {
|
const openPopover = async (type: string) => {
|
||||||
|
if (popOverVisible.value[type] === true) return
|
||||||
if (type === 'approve') {
|
if (type === 'approve') {
|
||||||
// 校验流程表单
|
// 校验流程表单
|
||||||
const valid = await validateNormalForm()
|
const valid = await validateNormalForm()
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activity.nodeType === NodeType.CHILD_PROCESS_NODE">
|
<div v-if="activity.nodeType === NodeType.CHILD_PROCESS_NODE">
|
||||||
<el-button type="primary" plain size="small" @click="handleChildProcess(activity)">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
size="small"
|
||||||
|
@click="handleChildProcess(activity)"
|
||||||
|
:disabled="!activity.processInstanceId"
|
||||||
|
>
|
||||||
查看子流程
|
查看子流程
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -324,7 +330,9 @@ const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
|
||||||
|
|
||||||
/** 跳转子流程 */
|
/** 跳转子流程 */
|
||||||
const handleChildProcess = (activity: any) => {
|
const handleChildProcess = (activity: any) => {
|
||||||
// TODO @lesan:貌似跳不过去?!
|
if (!activity.processInstanceId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
push({
|
push({
|
||||||
name: 'BpmProcessInstanceDetail',
|
name: 'BpmProcessInstanceDetail',
|
||||||
query: {
|
query: {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue