Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/iot
# Conflicts: # pnpm-lock.yamlpull/764/MERGE
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 242 KiB |
18
README.md
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
* nodejs > 16.18.0 && pnpm > 8.6.0 (强制使用pnpm)
|
* nodejs > 16.18.0 && pnpm > 8.6.0 (强制使用pnpm)
|
||||||
* 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
* 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
||||||
* 演示地址【Vue3 + vben(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
* 演示地址【Vue3 + vben5.0(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
||||||
* 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
* 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
||||||
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
||||||
* 视频教程:<https://doc.iocoder.cn/video/>
|
* 视频教程:<https://doc.iocoder.cn/video/>
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
* 改换 saas,自动引入等功能
|
* 改换 saas,自动引入等功能
|
||||||
* 使用 Element Plus 免费开源的中后台模版,具备如下特性:
|
* 使用 Element Plus 免费开源的中后台模版,具备如下特性:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
* **最新技术栈**:使用 Vue3、Vite4 等前端前沿技术开发
|
* **最新技术栈**:使用 Vue3、Vite4 等前端前沿技术开发
|
||||||
* **TypeScript**: 应用程序级 JavaScript 的语言
|
* **TypeScript**: 应用程序级 JavaScript 的语言
|
||||||
|
|
@ -38,15 +38,15 @@
|
||||||
|
|
||||||
| 框架 | 说明 | 版本 |
|
| 框架 | 说明 | 版本 |
|
||||||
|----------------------------------------------------------------------|------------------|--------|
|
|----------------------------------------------------------------------|------------------|--------|
|
||||||
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.8 |
|
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.8 |
|
||||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.5.0 |
|
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.5.0 |
|
||||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.4.2 |
|
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.4.2 |
|
||||||
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.2.2 |
|
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.2.2 |
|
||||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.7 |
|
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.7 |
|
||||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.6.1 |
|
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.6.1 |
|
||||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.6.5 |
|
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.6.5 |
|
||||||
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
|
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
|
||||||
| [unocss](https://uno.antfu.me/) | 原子 css | 0.57.4 |
|
| [unocss](https://uno.antfu.me/) | 原子 css | 0.57.4 |
|
||||||
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
|
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
|
||||||
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 |
|
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 |
|
||||||
|
|
||||||
|
|
@ -121,9 +121,9 @@
|
||||||
|
|
||||||
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
|
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
|
||||||
|
|
||||||
| BPMN 设计器 | 钉钉/飞书设计器 |
|
| BPMN 设计器 | 钉钉/飞书设计器 |
|
||||||
|------------------------------|--------------------------------|
|
|-----------------------------|-------------------------------|
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
|
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||||
import viteCompression from 'vite-plugin-compression'
|
import viteCompression from 'vite-plugin-compression'
|
||||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons-ng'
|
||||||
import UnoCSS from 'unocss/vite'
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
export function createVitePlugins() {
|
export function createVitePlugins() {
|
||||||
|
|
@ -78,7 +78,6 @@ export function createVitePlugins() {
|
||||||
createSvgIconsPlugin({
|
createSvgIconsPlugin({
|
||||||
iconDirs: [pathResolve('src/assets/svgs')],
|
iconDirs: [pathResolve('src/assets/svgs')],
|
||||||
symbolId: 'icon-[dir]-[name]',
|
symbolId: 'icon-[dir]-[name]',
|
||||||
svgoOptions: true
|
|
||||||
}),
|
}),
|
||||||
viteCompression({
|
viteCompression({
|
||||||
verbose: true, // 是否在控制台输出压缩结果
|
verbose: true, // 是否在控制台输出压缩结果
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "yudao-ui-admin-vue3",
|
"name": "yudao-ui-admin-vue3",
|
||||||
"version": "2.4.1-snapshot",
|
"version": "2.6.0-snapshot",
|
||||||
"description": "基于vue3、vite4、element-plus、typesScript",
|
"description": "基于vue3、vite4、element-plus、typesScript",
|
||||||
"author": "xingyu",
|
"author": "xingyu",
|
||||||
"private": false,
|
"private": false,
|
||||||
|
|
@ -134,7 +134,7 @@
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-progress": "^0.0.7",
|
"vite-plugin-progress": "^0.0.7",
|
||||||
"vite-plugin-purge-icons": "^0.10.0",
|
"vite-plugin-purge-icons": "^0.10.0",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons-ng": "^1.3.1",
|
||||||
"vite-plugin-top-level-await": "^1.4.4",
|
"vite-plugin-top-level-await": "^1.4.4",
|
||||||
"vue-eslint-parser": "^9.3.2",
|
"vue-eslint-parser": "^9.3.2",
|
||||||
"vue-tsc": "^1.8.27"
|
"vue-tsc": "^1.8.27"
|
||||||
|
|
|
||||||
3861
pnpm-lock.yaml
BIN
public/home.png
|
Before Width: | Height: | Size: 73 KiB |
|
|
@ -0,0 +1,25 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export const getWorkflowPage = async (params) => {
|
||||||
|
return await request.get({ url: '/ai/workflow/page', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getWorkflow = async (id) => {
|
||||||
|
return await request.get({ url: '/ai/workflow/get?id=' + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createWorkflow = async (data) => {
|
||||||
|
return await request.post({ url: '/ai/workflow/create', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateWorkflow = async (data) => {
|
||||||
|
return await request.put({ url: '/ai/workflow/update', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteWorkflow = async (id) => {
|
||||||
|
return await request.delete({ url: '/ai/workflow/delete?id=' + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testWorkflow = async (data) => {
|
||||||
|
return await request.post({ url: '/ai/workflow/test', data })
|
||||||
|
}
|
||||||
|
|
@ -46,11 +46,6 @@ export type DatabaseTableVO = {
|
||||||
comment: string
|
comment: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CodegenDetailVO = {
|
|
||||||
table: CodegenTableVO
|
|
||||||
columns: CodegenColumnVO[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CodegenPreviewVO = {
|
export type CodegenPreviewVO = {
|
||||||
filePath: string
|
filePath: string
|
||||||
code: string
|
code: string
|
||||||
|
|
@ -61,11 +56,6 @@ export type CodegenUpdateReqVO = {
|
||||||
columns: CodegenColumnVO[]
|
columns: CodegenColumnVO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CodegenCreateListReqVO = {
|
|
||||||
dataSourceConfigId: number
|
|
||||||
tableNames: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询列表代码生成表定义
|
// 查询列表代码生成表定义
|
||||||
export const getCodegenTableList = (dataSourceConfigId: number) => {
|
export const getCodegenTableList = (dataSourceConfigId: number) => {
|
||||||
return request.get({ url: '/infra/codegen/table/list?dataSourceConfigId=' + dataSourceConfigId })
|
return request.get({ url: '/infra/codegen/table/list?dataSourceConfigId=' + dataSourceConfigId })
|
||||||
|
|
@ -81,11 +71,6 @@ export const getCodegenTable = (id: number) => {
|
||||||
return request.get({ url: '/infra/codegen/detail?tableId=' + id })
|
return request.get({ url: '/infra/codegen/detail?tableId=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增代码生成表定义
|
|
||||||
export const createCodegenTable = (data: CodegenCreateListReqVO) => {
|
|
||||||
return request.post({ url: '/infra/codegen/create', data })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改代码生成表定义
|
// 修改代码生成表定义
|
||||||
export const updateCodegenTable = (data: CodegenUpdateReqVO) => {
|
export const updateCodegenTable = (data: CodegenUpdateReqVO) => {
|
||||||
return request.put({ url: '/infra/codegen/update', data })
|
return request.put({ url: '/infra/codegen/update', data })
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export interface FilePageReqVO extends PageParam {
|
|
||||||
path?: string
|
|
||||||
type?: string
|
|
||||||
createTime?: Date[]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件预签名地址 Response VO
|
// 文件预签名地址 Response VO
|
||||||
export interface FilePresignedUrlRespVO {
|
export interface FilePresignedUrlRespVO {
|
||||||
// 文件配置编号
|
// 文件配置编号
|
||||||
|
|
@ -14,10 +8,12 @@ export interface FilePresignedUrlRespVO {
|
||||||
uploadUrl: string
|
uploadUrl: string
|
||||||
// 文件 URL
|
// 文件 URL
|
||||||
url: string
|
url: string
|
||||||
|
// 文件路径
|
||||||
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询文件列表
|
// 查询文件列表
|
||||||
export const getFilePage = (params: FilePageReqVO) => {
|
export const getFilePage = (params: PageParam) => {
|
||||||
return request.get({ url: '/infra/file/page', params })
|
return request.get({ url: '/infra/file/page', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,10 +23,10 @@ export const deleteFile = (id: number) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取文件预签名地址
|
// 获取文件预签名地址
|
||||||
export const getFilePresignedUrl = (path: string) => {
|
export const getFilePresignedUrl = (name: string, directory?: string) => {
|
||||||
return request.get<FilePresignedUrlRespVO>({
|
return request.get<FilePresignedUrlRespVO>({
|
||||||
url: '/infra/file/presigned-url',
|
url: '/infra/file/presigned-url',
|
||||||
params: { path }
|
params: { name, directory }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export interface FileClientConfig {
|
||||||
bucket?: string
|
bucket?: string
|
||||||
accessKey?: string
|
accessKey?: string
|
||||||
accessSecret?: string
|
accessSecret?: string
|
||||||
|
enablePathStyleAccess?: boolean
|
||||||
domain: string
|
domain: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
import { getRefreshToken } from '@/utils/auth'
|
|
||||||
import type { RegisterVO, UserLoginVO } from './types'
|
import type { RegisterVO, UserLoginVO } from './types'
|
||||||
|
|
||||||
export interface SmsCodeVO {
|
export interface SmsCodeVO {
|
||||||
|
|
@ -72,7 +71,6 @@ export const socialAuthRedirect = (type: number, redirectUri: string) => {
|
||||||
}
|
}
|
||||||
// 获取验证图片以及 token
|
// 获取验证图片以及 token
|
||||||
export const getCode = (data: any) => {
|
export const getCode = (data: any) => {
|
||||||
debugger
|
|
||||||
return request.postOriginal({ url: 'system/captcha/get', data })
|
return request.postOriginal({ url: 'system/captcha/get', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,19 @@ export interface BrokerageWithdrawVO {
|
||||||
feePrice: number
|
feePrice: number
|
||||||
totalPrice: number
|
totalPrice: number
|
||||||
type: number
|
type: number
|
||||||
name: string
|
userName: string
|
||||||
accountNo: string
|
userAccount: string
|
||||||
bankName: string
|
bankName: string
|
||||||
bankAddress: string
|
bankAddress: string
|
||||||
accountQrCodeUrl: string
|
qrCodeUrl: string
|
||||||
status: number
|
status: number
|
||||||
auditReason: string
|
auditReason: string
|
||||||
auditTime: Date
|
auditTime: Date
|
||||||
remark: string
|
remark: string
|
||||||
|
payTransferId?: number
|
||||||
|
transferChannelCode?: string
|
||||||
|
transferTime?: Date
|
||||||
|
transferErrorMsg?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询佣金提现列表
|
// 查询佣金提现列表
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,6 @@ export function createDemoOrder(data: DemoOrderVO) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得示例订单
|
|
||||||
export function getDemoOrder(id: number) {
|
|
||||||
return request.get({
|
|
||||||
url: '/pay/demo-order/get?id=' + id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获得示例订单分页
|
// 获得示例订单分页
|
||||||
export function getDemoOrderPage(query: PageParam) {
|
export function getDemoOrderPage(query: PageParam) {
|
||||||
return request.get({
|
return request.get({
|
||||||
|
|
@ -29,7 +22,7 @@ export function getDemoOrderPage(query: PageParam) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退款示例订单
|
// 退款示例订单
|
||||||
export function refundDemoOrder(id) {
|
export function refundDemoOrder(id: number) {
|
||||||
return request.put({
|
return request.put({
|
||||||
url: '/pay/demo-order/refund?id=' + id
|
url: '/pay/demo-order/refund?id=' + id
|
||||||
})
|
})
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import request from '@/config/axios'
|
|
||||||
|
|
||||||
export interface DemoTransferVO {
|
|
||||||
price: number
|
|
||||||
type: number
|
|
||||||
userName: string
|
|
||||||
alipayLogonId: string
|
|
||||||
openid: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建示例转账单
|
|
||||||
export function createDemoTransfer(data: DemoTransferVO) {
|
|
||||||
return request.post({
|
|
||||||
url: '/pay/demo-transfer/create',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获得示例订单分页
|
|
||||||
export function getDemoTransferPage(query: PageParam) {
|
|
||||||
return request.get({
|
|
||||||
url: '/pay/demo-transfer/page',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export interface PayDemoWithdrawVO {
|
||||||
|
id?: number
|
||||||
|
subject: string
|
||||||
|
price: number
|
||||||
|
userName: string
|
||||||
|
userAccount: string
|
||||||
|
type: number
|
||||||
|
status?: number
|
||||||
|
payTransferId?: number
|
||||||
|
transferChannelCode?: string
|
||||||
|
transferTime?: Date
|
||||||
|
transferErrorMsg?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询示例提现单列表
|
||||||
|
export const getDemoWithdrawPage = (params: PageParam) => {
|
||||||
|
return request.get({ url: '/pay/demo-withdraw/page', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建示例提现单
|
||||||
|
export const createDemoWithdraw = (data: PayDemoWithdrawVO) => {
|
||||||
|
return request.post({ url: '/pay/demo-withdraw/create', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起提现单转账
|
||||||
|
export const transferDemoWithdraw = (id: number) => {
|
||||||
|
return request.post({ url: '/pay/demo-withdraw/transfer', params: { id } })
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,16 @@
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export interface TransferVO {
|
|
||||||
appId: number
|
|
||||||
channelCode: string
|
|
||||||
merchantTransferId: string
|
|
||||||
type: number
|
|
||||||
price: number
|
|
||||||
subject: string
|
|
||||||
userName: string
|
|
||||||
alipayLogonId: string
|
|
||||||
openid: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增转账单
|
|
||||||
export const createTransfer = async (data: TransferVO) => {
|
|
||||||
return await request.post({ url: `/pay/transfer/create`, data })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询转账单列表
|
// 查询转账单列表
|
||||||
export const getTransferPage = async (params) => {
|
export const getTransferPage = async (params: PageParam) => {
|
||||||
return await request.get({ url: `/pay/transfer/page`, params })
|
return await request.get({ url: `/pay/transfer/page`, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询转账单详情
|
||||||
export const getTransfer = async (id: number) => {
|
export const getTransfer = async (id: number) => {
|
||||||
return await request.get({ url: '/pay/transfer/get?id=' + id })
|
return await request.get({ url: '/pay/transfer/get?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出转账单
|
||||||
|
export const exportTransfer = async (params: PageParam) => {
|
||||||
|
return await request.download({ url: '/pay/transfer/export-excel', params })
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ export const getTenant = (id: number) => {
|
||||||
return request.get({ url: '/system/tenant/get?id=' + id })
|
return request.get({ url: '/system/tenant/get?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取租户精简信息列表
|
||||||
|
export const getTenantList = () => {
|
||||||
|
return request.get({ url: '/system/tenant/simple-list' })
|
||||||
|
}
|
||||||
|
|
||||||
// 新增租户
|
// 新增租户
|
||||||
export const createTenant = (data: TenantVO) => {
|
export const createTenant = (data: TenantVO) => {
|
||||||
return request.post({ url: '/system/tenant/create', data })
|
return request.post({ url: '/system/tenant/create', data })
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,6 @@ export const getUserPage = (params: PageParam) => {
|
||||||
return request.get({ url: '/system/user/page', params })
|
return request.get({ url: '/system/user/page', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询所有用户列表
|
|
||||||
export const getAllUser = () => {
|
|
||||||
return request.get({ url: '/system/user/all' })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询用户详情
|
// 查询用户详情
|
||||||
export const getUser = (id: number) => {
|
export const getUser = (id: number) => {
|
||||||
return request.get({ url: '/system/user/get?id=' + id })
|
return request.get({ url: '/system/user/get?id=' + id })
|
||||||
|
|
@ -48,7 +43,7 @@ export const deleteUser = (id: number) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出用户
|
// 导出用户
|
||||||
export const exportUser = (params) => {
|
export const exportUser = (params: any) => {
|
||||||
return request.download({ url: '/system/user/export', params })
|
return request.download({ url: '/system/user/export', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +53,7 @@ export const importUserTemplate = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户密码重置
|
// 用户密码重置
|
||||||
export const resetUserPwd = (id: number, password: string) => {
|
export const resetUserPassword = (id: number, password: string) => {
|
||||||
const data = {
|
const data = {
|
||||||
id,
|
id,
|
||||||
password
|
password
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,11 @@ export interface ProfileVO {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserProfileUpdateReqVO {
|
export interface UserProfileUpdateReqVO {
|
||||||
nickname: string
|
nickname?: string
|
||||||
email: string
|
email?: string
|
||||||
mobile: string
|
mobile?: string
|
||||||
sex: number
|
sex?: number
|
||||||
|
avatar?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询用户个人信息
|
// 查询用户个人信息
|
||||||
|
|
@ -58,8 +59,3 @@ export const updateUserPassword = (oldPassword: string, newPassword: string) =>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户头像上传
|
|
||||||
export const uploadAvatar = (data) => {
|
|
||||||
return request.upload({ url: '/system/user/profile/update-avatar', data: data })
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div @click.stop>
|
||||||
<Dialog
|
<Dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:canFullscreen="false"
|
:canFullscreen="false"
|
||||||
|
|
@ -181,6 +181,7 @@ function openModal() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
|
debugger
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="部门选择" width="600">
|
||||||
|
<el-row v-loading="formLoading">
|
||||||
|
<el-col :span="24">
|
||||||
|
<ContentWrap class="h-1/1">
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:data="deptTree"
|
||||||
|
:props="defaultProps"
|
||||||
|
show-checkbox
|
||||||
|
:check-strictly="checkStrictly"
|
||||||
|
check-on-click-node
|
||||||
|
default-expand-all
|
||||||
|
highlight-current
|
||||||
|
node-key="id"
|
||||||
|
@check="handleCheck"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<template #footer>
|
||||||
|
<el-button
|
||||||
|
:disabled="formLoading || !selectedDeptIds?.length"
|
||||||
|
type="primary"
|
||||||
|
@click="submitForm"
|
||||||
|
>
|
||||||
|
确 定
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
|
import * as DeptApi from '@/api/system/dept'
|
||||||
|
|
||||||
|
defineOptions({ name: 'DeptSelectForm' })
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
confirm: [deptList: any[]]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 是否严格的遵循父子不互相关联
|
||||||
|
checkStrictly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否支持多选
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const treeRef = ref()
|
||||||
|
const deptTree = ref<Tree[]>([]) // 部门树形结构
|
||||||
|
const selectedDeptIds = ref<number[]>([]) // 选中的部门 ID 列表
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const formLoading = ref(false) // 表单的加载中
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (selectedList?: DeptApi.DeptVO[]) => {
|
||||||
|
resetForm()
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
// 加载部门列表
|
||||||
|
const deptData = await DeptApi.getSimpleDeptList()
|
||||||
|
deptTree.value = handleTree(deptData)
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
// 设置已选择的部门
|
||||||
|
if (selectedList?.length) {
|
||||||
|
await nextTick()
|
||||||
|
const selectedIds = selectedList
|
||||||
|
.map((dept) => dept.id)
|
||||||
|
.filter((id): id is number => id !== undefined)
|
||||||
|
selectedDeptIds.value = selectedIds
|
||||||
|
treeRef.value?.setCheckedKeys(selectedIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理选中状态变化 */
|
||||||
|
const handleCheck = (data: any, checked: any) => {
|
||||||
|
selectedDeptIds.value = treeRef.value.getCheckedKeys()
|
||||||
|
if (!props.multiple && selectedDeptIds.value.length > 1) {
|
||||||
|
// 单选模式下,只保留最后选择的节点
|
||||||
|
const lastSelectedId = selectedDeptIds.value[selectedDeptIds.value.length - 1]
|
||||||
|
selectedDeptIds.value = [lastSelectedId]
|
||||||
|
treeRef.value.setCheckedKeys([lastSelectedId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交选择 */
|
||||||
|
const submitForm = async () => {
|
||||||
|
try {
|
||||||
|
// 获取选中的完整部门数据
|
||||||
|
const checkedNodes = treeRef.value.getCheckedNodes()
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('confirm', checkedNodes)
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
deptTree.value = []
|
||||||
|
selectedDeptIds.value = []
|
||||||
|
if (treeRef.value) {
|
||||||
|
treeRef.value.setCheckedKeys([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
</script>
|
||||||
|
|
@ -68,6 +68,7 @@ const dialogStyle = computed(() => {
|
||||||
draggable
|
draggable
|
||||||
class="com-dialog"
|
class="com-dialog"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
|
@close="$emit('update:modelValue', false)"
|
||||||
>
|
>
|
||||||
<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">
|
||||||
|
|
@ -90,7 +91,7 @@ const dialogStyle = computed(() => {
|
||||||
icon="ep:close"
|
icon="ep:close"
|
||||||
hover-color="var(--el-color-primary)"
|
hover-color="var(--el-color-primary)"
|
||||||
color="var(--el-color-info)"
|
color="var(--el-color-info)"
|
||||||
@click="close"
|
@click.stop="close"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export const CouponDiscount = defineComponent({
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
|
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
|
||||||
// 折扣
|
// 折扣
|
||||||
let value = coupon.discountPercent + ''
|
let value = coupon.discountPercent / 10 + ''
|
||||||
let suffix = ' 折'
|
let suffix = ' 折'
|
||||||
// 满减
|
// 满减
|
||||||
if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
|
if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
|
||||||
|
|
@ -43,7 +43,7 @@ export const CouponDiscountDesc = defineComponent({
|
||||||
const discountDesc =
|
const discountDesc =
|
||||||
coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
|
coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
|
||||||
? `减${floatToFixed2(coupon.discountPrice)}元`
|
? `减${floatToFixed2(coupon.discountPrice)}元`
|
||||||
: `打${coupon.discountPercent}折`
|
: `打${coupon.discountPercent / 10.0}折`
|
||||||
return () => (
|
return () => (
|
||||||
<div>
|
<div>
|
||||||
<span>{useCondition}</span>
|
<span>{useCondition}</span>
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,13 @@
|
||||||
<div class="flex flex-col justify-evenly gap-4px">
|
<div class="flex flex-col justify-evenly gap-4px">
|
||||||
<!-- 优惠值 -->
|
<!-- 优惠值 -->
|
||||||
<CouponDiscount :coupon="coupon" />
|
<CouponDiscount :coupon="coupon" />
|
||||||
<div>{{ coupon.name }}</div>
|
<!-- 优惠描述 -->
|
||||||
|
<CouponDiscountDesc :coupon="coupon" />
|
||||||
|
<!-- 领取说明 -->
|
||||||
|
<div v-if="coupon.totalCount >= 0">
|
||||||
|
仅剩:{{ coupon.totalCount - coupon.takeCount }}张
|
||||||
|
</div>
|
||||||
|
<div v-else-if="coupon.totalCount === -1">仅剩:不限制</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div
|
<div
|
||||||
|
|
@ -67,7 +73,8 @@
|
||||||
<div v-else class="flex flex-col items-center justify-around gap-4px p-4px">
|
<div v-else class="flex flex-col items-center justify-around gap-4px p-4px">
|
||||||
<!-- 优惠值 -->
|
<!-- 优惠值 -->
|
||||||
<CouponDiscount :coupon="coupon" />
|
<CouponDiscount :coupon="coupon" />
|
||||||
<div>{{ coupon.name }}</div>
|
<!-- 优惠描述 -->
|
||||||
|
<CouponDiscountDesc :coupon="coupon" />
|
||||||
<div
|
<div
|
||||||
class="rounded-20px p-x-8px p-y-2px"
|
class="rounded-20px p-x-8px p-y-2px"
|
||||||
:style="{
|
:style="{
|
||||||
|
|
@ -124,7 +131,7 @@ watch(
|
||||||
() => {
|
() => {
|
||||||
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
|
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
|
||||||
couponWidth.value =
|
couponWidth.value =
|
||||||
(phoneWidth.value * 0.95 - props.property.space * (props.property.columns - 1)) /
|
(phoneWidth.value - props.property.space * (props.property.columns - 1)) /
|
||||||
props.property.columns
|
props.property.columns
|
||||||
// 显示滚动条
|
// 显示滚动条
|
||||||
scrollbarWidth.value = `${
|
scrollbarWidth.value = `${
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative"
|
class="relative"
|
||||||
:style="{ height: `${rowCount * CUBE_SIZE}px`, width: `${4 * CUBE_SIZE}px` }"
|
:style="{
|
||||||
|
height: `${rowCount * CUBE_SIZE}px`,
|
||||||
|
width: `${4 * CUBE_SIZE}px`,
|
||||||
|
padding: `${property.space}px`
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in property.list"
|
v-for="(item, index) in property.list"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="absolute"
|
class="absolute"
|
||||||
:style="{
|
:style="{
|
||||||
width: `${item.width * CUBE_SIZE - property.space * 2}px`,
|
width: `${item.width * CUBE_SIZE - property.space}px`,
|
||||||
height: `${item.height * CUBE_SIZE - property.space * 2}px`,
|
height: `${item.height * CUBE_SIZE - property.space}px`,
|
||||||
margin: `${property.space}px`,
|
|
||||||
top: `${item.top * CUBE_SIZE}px`,
|
top: `${item.top * CUBE_SIZE}px`,
|
||||||
left: `${item.left * CUBE_SIZE}px`
|
left: `${item.left * CUBE_SIZE}px`
|
||||||
}"
|
}"
|
||||||
|
|
@ -63,10 +66,10 @@ const rowCount = computed(() => {
|
||||||
let count = 0
|
let count = 0
|
||||||
if (props.property.list.length > 0) {
|
if (props.property.list.length > 0) {
|
||||||
// 最大行号
|
// 最大行号
|
||||||
count = Math.max(...props.property.list.map((item) => item.bottom))
|
count = Math.max(...props.property.list.map((item) => item.top + item.height))
|
||||||
}
|
}
|
||||||
// 行号从 0 开始,所以加 1
|
// 保证至少有一行
|
||||||
return count + 1
|
return count == 0 ? 1 : count
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-carousel-item>
|
</el-carousel-item>
|
||||||
</el-carousel>
|
</el-carousel>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ const props = defineProps<{ property: MenuSwiperProperty }>()
|
||||||
// 标题的高度
|
// 标题的高度
|
||||||
const TITLE_HEIGHT = 20
|
const TITLE_HEIGHT = 20
|
||||||
// 图标的高度
|
// 图标的高度
|
||||||
const ICON_SIZE = 42
|
const ICON_SIZE = 32
|
||||||
// 垂直间距:一行上下的间距
|
// 垂直间距:一行上下的间距
|
||||||
const SPACE_Y = 16
|
const SPACE_Y = 16
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,10 @@
|
||||||
<ColorInput v-model="formData.bgColor" />
|
<ColorInput v-model="formData.bgColor" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="背景图片" prop="bgImg" v-else>
|
<el-form-item label="背景图片" prop="bgImg" v-else>
|
||||||
<UploadImg v-model="formData.bgImg" :limit="1" width="56px" height="56px" />
|
<div class="flex items-center">
|
||||||
|
<UploadImg v-model="formData.bgImg" :limit="1" width="56px" height="56px" />
|
||||||
|
<span class="text-xs text-gray-400 ml-2 mb-2">建议宽度:750</span>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-card class="property-group" shadow="never">
|
<el-card class="property-group" shadow="never">
|
||||||
<template #header>
|
<template #header>
|
||||||
|
|
@ -39,8 +42,9 @@
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="formData._local.previewMp"
|
v-model="formData._local.previewMp"
|
||||||
@change="formData._local.previewOther = !formData._local.previewMp"
|
@change="formData._local.previewOther = !formData._local.previewMp"
|
||||||
>预览</el-checkbox
|
|
||||||
>
|
>
|
||||||
|
预览
|
||||||
|
</el-checkbox>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -54,8 +58,9 @@
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="formData._local.previewOther"
|
v-model="formData._local.previewOther"
|
||||||
@change="formData._local.previewMp = !formData._local.previewOther"
|
@change="formData._local.previewMp = !formData._local.previewOther"
|
||||||
>预览</el-checkbox
|
|
||||||
>
|
>
|
||||||
|
预览
|
||||||
|
</el-checkbox>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,8 @@ export const component = {
|
||||||
bgEndColor: '#FE832A',
|
bgEndColor: '#FE832A',
|
||||||
imgUrl: ''
|
imgUrl: ''
|
||||||
},
|
},
|
||||||
borderRadiusTop: 8,
|
borderRadiusTop: 6,
|
||||||
borderRadiusBottom: 8,
|
borderRadiusBottom: 6,
|
||||||
space: 8,
|
space: 8,
|
||||||
spuIds: [],
|
spuIds: [],
|
||||||
style: {
|
style: {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,10 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<!-- 角标 -->
|
<!-- 角标 -->
|
||||||
<div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
|
<div
|
||||||
|
v-if="property.badge.show && property.badge.imgUrl"
|
||||||
|
class="absolute left-0 top-0 z-1 items-center justify-center"
|
||||||
|
>
|
||||||
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
|
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 商品封面图 -->
|
<!-- 商品封面图 -->
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<!-- 表单 -->
|
<!-- 表单 -->
|
||||||
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||||
<el-card header="搜索热词" class="property-group" shadow="never">
|
<el-card header="搜索热词" class="property-group" shadow="never">
|
||||||
<Draggable v-model="formData.hotKeywords" :empty-item="''">
|
<Draggable v-model="formData.hotKeywords" :empty-item="''" :min="0">
|
||||||
<template #default="{ index }">
|
<template #default="{ index }">
|
||||||
<el-input v-model="formData.hotKeywords[index]" placeholder="请输入热词" />
|
<el-input v-model="formData.hotKeywords[index]" placeholder="请输入热词" />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
|
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
|
||||||
|
import { isString } from '@/utils/is'
|
||||||
|
|
||||||
/** 搜索框属性面板 */
|
/** 搜索框属性面板 */
|
||||||
defineOptions({ name: 'SearchProperty' })
|
defineOptions({ name: 'SearchProperty' })
|
||||||
|
|
@ -68,6 +69,19 @@ defineOptions({ name: 'SearchProperty' })
|
||||||
const props = defineProps<{ modelValue: SearchProperty }>()
|
const props = defineProps<{ modelValue: SearchProperty }>()
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const formData = useVModel(props, 'modelValue', emit)
|
const formData = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
|
// 监听热词数组变化
|
||||||
|
watch(
|
||||||
|
() => formData.value.hotKeywords,
|
||||||
|
(newVal) => {
|
||||||
|
// 找到非字符串项的索引
|
||||||
|
const nonStringIndex = newVal.findIndex((item) => !isString(item))
|
||||||
|
if (nonStringIndex !== -1) {
|
||||||
|
formData.value.hotKeywords[nonStringIndex] = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, flush: 'post' }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
|
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||||
|
|
||||||
/** 标题栏属性 */
|
/** 标题栏属性 */
|
||||||
export interface TitleBarProperty {
|
export interface TitleBarProperty {
|
||||||
|
// 背景图
|
||||||
|
bgImgUrl: string
|
||||||
// 偏移
|
// 偏移
|
||||||
marginLeft: number
|
marginLeft: number
|
||||||
// 显示位置
|
// 显示位置
|
||||||
|
|
@ -22,6 +24,8 @@ export interface TitleBarProperty {
|
||||||
titleColor: string
|
titleColor: string
|
||||||
// 描述颜色
|
// 描述颜色
|
||||||
descriptionColor: string
|
descriptionColor: string
|
||||||
|
// 高度
|
||||||
|
height: number
|
||||||
// 查看更多
|
// 查看更多
|
||||||
more: {
|
more: {
|
||||||
// 是否显示查看更多
|
// 是否显示查看更多
|
||||||
|
|
@ -52,6 +56,8 @@ export const component = {
|
||||||
descriptionWeight: 200,
|
descriptionWeight: 200,
|
||||||
titleColor: 'rgba(50, 50, 51, 10)',
|
titleColor: 'rgba(50, 50, 51, 10)',
|
||||||
descriptionColor: 'rgba(150, 151, 153, 10)',
|
descriptionColor: 'rgba(150, 151, 153, 10)',
|
||||||
|
marginLeft: 0,
|
||||||
|
height: 40,
|
||||||
more: {
|
more: {
|
||||||
//查看更多
|
//查看更多
|
||||||
show: false,
|
show: false,
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,49 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="title-bar" :style="{ height: `${property.height}px` }">
|
||||||
:style="{
|
<el-image v-if="property.bgImgUrl" :src="property.bgImgUrl" fit="cover" class="w-full" />
|
||||||
background:
|
<div class="absolute left-0 top-0 w-full h-full flex flex-col justify-center">
|
||||||
property.style.bgType === 'color' ? property.style.bgColor : `url(${property.style.bgImg})`,
|
|
||||||
backgroundSize: '100% 100%',
|
|
||||||
backgroundRepeat: 'no-repeat'
|
|
||||||
}"
|
|
||||||
class="title-bar"
|
|
||||||
>
|
|
||||||
<!-- 内容 -->
|
|
||||||
<div>
|
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<div
|
<div
|
||||||
v-if="property.title"
|
|
||||||
:style="{
|
:style="{
|
||||||
fontSize: `${property.titleSize}px`,
|
fontSize: `${property.titleSize}px`,
|
||||||
fontWeight: property.titleWeight,
|
fontWeight: property.titleWeight,
|
||||||
color: property.titleColor,
|
color: property.titleColor,
|
||||||
textAlign: property.textAlign
|
textAlign: property.textAlign,
|
||||||
|
marginLeft: `${property.marginLeft}px`,
|
||||||
|
marginBottom: '4px'
|
||||||
}"
|
}"
|
||||||
|
v-if="property.title"
|
||||||
>
|
>
|
||||||
{{ property.title }}
|
{{ property.title }}
|
||||||
</div>
|
</div>
|
||||||
<!-- 副标题 -->
|
<!-- 副标题 -->
|
||||||
<div
|
<div
|
||||||
v-if="property.description"
|
|
||||||
:style="{
|
:style="{
|
||||||
fontSize: `${property.descriptionSize}px`,
|
fontSize: `${property.descriptionSize}px`,
|
||||||
fontWeight: property.descriptionWeight,
|
fontWeight: property.descriptionWeight,
|
||||||
color: property.descriptionColor,
|
color: property.descriptionColor,
|
||||||
textAlign: property.textAlign
|
textAlign: property.textAlign,
|
||||||
|
marginLeft: `${property.marginLeft}px`
|
||||||
}"
|
}"
|
||||||
class="m-t-8px"
|
v-if="property.description"
|
||||||
>
|
>
|
||||||
{{ property.description }}
|
{{ property.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 更多 -->
|
<!-- 更多 -->
|
||||||
<div
|
<div
|
||||||
|
class="more"
|
||||||
v-show="property.more.show"
|
v-show="property.more.show"
|
||||||
:style="{
|
:style="{
|
||||||
color: property.descriptionColor
|
color: property.descriptionColor
|
||||||
}"
|
}"
|
||||||
class="more"
|
|
||||||
>
|
>
|
||||||
<span v-if="property.more.type !== 'icon'"> {{ property.more.text }} </span>
|
<span v-if="property.more.type !== 'icon'"> {{ property.more.text }} </span>
|
||||||
<Icon v-if="property.more.type !== 'text'" icon="ep:arrow-right" />
|
<Icon icon="ep:arrow-right" v-if="property.more.type !== 'text'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script setup lang="ts">
|
||||||
import { TitleBarProperty } from './config'
|
import { TitleBarProperty } from './config'
|
||||||
|
|
||||||
/** 标题栏 */
|
/** 标题栏 */
|
||||||
|
|
@ -57,7 +51,7 @@ defineOptions({ name: 'TitleBar' })
|
||||||
|
|
||||||
defineProps<{ property: TitleBarProperty }>()
|
defineProps<{ property: TitleBarProperty }>()
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style scoped lang="scss">
|
||||||
.title-bar {
|
.title-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<ComponentContainerProperty v-model="formData.style">
|
<ComponentContainerProperty v-model="formData.style">
|
||||||
<el-form :model="formData" :rules="rules" label-width="85px">
|
<el-form label-width="85px" :model="formData" :rules="rules">
|
||||||
<el-card class="property-group" header="风格" shadow="never">
|
<el-card header="风格" class="property-group" shadow="never">
|
||||||
|
<el-form-item label="背景图片" prop="bgImgUrl">
|
||||||
|
<UploadImg v-model="formData.bgImgUrl" width="100%" height="40px">
|
||||||
|
<template #tip>建议尺寸 750*80</template>
|
||||||
|
</UploadImg>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="标题位置" prop="textAlign">
|
<el-form-item label="标题位置" prop="textAlign">
|
||||||
<el-radio-group v-model="formData!.textAlign">
|
<el-radio-group v-model="formData!.textAlign">
|
||||||
<el-tooltip content="居左" placement="top">
|
<el-tooltip content="居左" placement="top">
|
||||||
|
|
@ -16,66 +21,84 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="偏移量" prop="marginLeft" label-width="70px">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.marginLeft"
|
||||||
|
:max="100"
|
||||||
|
:min="0"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="高度" prop="height" label-width="70px">
|
||||||
|
<el-slider
|
||||||
|
v-model="formData.height"
|
||||||
|
:max="200"
|
||||||
|
:min="20"
|
||||||
|
show-input
|
||||||
|
input-size="small"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card class="property-group" header="主标题" shadow="never">
|
<el-card header="主标题" class="property-group" shadow="never">
|
||||||
<el-form-item label="文字" label-width="40px" prop="title">
|
<el-form-item label="文字" prop="title" label-width="40px">
|
||||||
<InputWithColor
|
<InputWithColor
|
||||||
v-model="formData.title"
|
v-model="formData.title"
|
||||||
v-model:color="formData.titleColor"
|
v-model:color="formData.titleColor"
|
||||||
maxlength="20"
|
|
||||||
show-word-limit
|
show-word-limit
|
||||||
|
maxlength="20"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="大小" label-width="40px" prop="titleSize">
|
<el-form-item label="大小" prop="titleSize" label-width="40px">
|
||||||
<el-slider
|
<el-slider
|
||||||
v-model="formData.titleSize"
|
v-model="formData.titleSize"
|
||||||
:max="60"
|
:max="60"
|
||||||
:min="10"
|
:min="10"
|
||||||
input-size="small"
|
|
||||||
show-input
|
show-input
|
||||||
|
input-size="small"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="粗细" label-width="40px" prop="titleWeight">
|
<el-form-item label="粗细" prop="titleWeight" label-width="40px">
|
||||||
<el-slider
|
<el-slider
|
||||||
v-model="formData.titleWeight"
|
v-model="formData.titleWeight"
|
||||||
:max="900"
|
|
||||||
:min="100"
|
:min="100"
|
||||||
|
:max="900"
|
||||||
:step="100"
|
:step="100"
|
||||||
input-size="small"
|
|
||||||
show-input
|
show-input
|
||||||
|
input-size="small"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card class="property-group" header="副标题" shadow="never">
|
<el-card header="副标题" class="property-group" shadow="never">
|
||||||
<el-form-item label="文字" label-width="40px" prop="description">
|
<el-form-item label="文字" prop="description" label-width="40px">
|
||||||
<InputWithColor
|
<InputWithColor
|
||||||
v-model="formData.description"
|
v-model="formData.description"
|
||||||
v-model:color="formData.descriptionColor"
|
v-model:color="formData.descriptionColor"
|
||||||
maxlength="50"
|
|
||||||
show-word-limit
|
show-word-limit
|
||||||
|
maxlength="50"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="大小" label-width="40px" prop="descriptionSize">
|
<el-form-item label="大小" prop="descriptionSize" label-width="40px">
|
||||||
<el-slider
|
<el-slider
|
||||||
v-model="formData.descriptionSize"
|
v-model="formData.descriptionSize"
|
||||||
:max="60"
|
:max="60"
|
||||||
:min="10"
|
:min="10"
|
||||||
input-size="small"
|
|
||||||
show-input
|
show-input
|
||||||
|
input-size="small"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="粗细" label-width="40px" prop="descriptionWeight">
|
<el-form-item label="粗细" prop="descriptionWeight" label-width="40px">
|
||||||
<el-slider
|
<el-slider
|
||||||
v-model="formData.descriptionWeight"
|
v-model="formData.descriptionWeight"
|
||||||
:max="900"
|
|
||||||
:min="100"
|
:min="100"
|
||||||
|
:max="900"
|
||||||
:step="100"
|
:step="100"
|
||||||
input-size="small"
|
|
||||||
show-input
|
show-input
|
||||||
|
input-size="small"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card class="property-group" header="查看更多" shadow="never">
|
<el-card header="查看更多" class="property-group" shadow="never">
|
||||||
<el-form-item label="是否显示" prop="more.show">
|
<el-form-item label="是否显示" prop="more.show">
|
||||||
<el-checkbox v-model="formData.more.show" />
|
<el-checkbox v-model="formData.more.show" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -88,7 +111,7 @@
|
||||||
<el-radio value="all">文字+图标</el-radio>
|
<el-radio value="all">文字+图标</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-show="formData.more.type !== 'icon'" label="更多文字" prop="more.text">
|
<el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
|
||||||
<el-input v-model="formData.more.text" />
|
<el-input v-model="formData.more.text" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="跳转链接" prop="more.url">
|
<el-form-item label="跳转链接" prop="more.url">
|
||||||
|
|
@ -99,7 +122,7 @@
|
||||||
</el-form>
|
</el-form>
|
||||||
</ComponentContainerProperty>
|
</ComponentContainerProperty>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script setup lang="ts">
|
||||||
import { TitleBarProperty } from './config'
|
import { TitleBarProperty } from './config'
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
// 导航栏属性面板
|
// 导航栏属性面板
|
||||||
|
|
@ -113,4 +136,4 @@ const formData = useVModel(props, 'modelValue', emit)
|
||||||
const rules = {}
|
const rules = {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
<Icon
|
<Icon
|
||||||
icon="ep:delete"
|
icon="ep:delete"
|
||||||
class="cursor-pointer text-red-5"
|
class="cursor-pointer text-red-5"
|
||||||
v-if="formData.length > 1"
|
v-if="formData.length > min"
|
||||||
@click="handleDelete(index)"
|
@click="handleDelete(index)"
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
@ -69,7 +69,9 @@ const props = defineProps({
|
||||||
// 空的元素:点击添加按钮时,创建元素并添加到列表;默认为空对象
|
// 空的元素:点击添加按钮时,创建元素并添加到列表;默认为空对象
|
||||||
emptyItem: any<unknown>().def({}),
|
emptyItem: any<unknown>().def({}),
|
||||||
// 数量限制:默认为0,表示不限制
|
// 数量限制:默认为0,表示不限制
|
||||||
limit: propTypes.number.def(0)
|
limit: propTypes.number.def(0),
|
||||||
|
// 最小数量:默认为1
|
||||||
|
min: propTypes.number.def(1)
|
||||||
})
|
})
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,18 @@ export const useApiSelect = (option: ApiSelectProps) => {
|
||||||
if (isEmpty(props.url)) {
|
if (isEmpty(props.url)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (props.method) {
|
switch (props.method) {
|
||||||
case 'GET':
|
case 'GET':
|
||||||
let url: string = props.url
|
let url: string = props.url
|
||||||
if (props.remote) {
|
if (props.remote) {
|
||||||
url = `${url}?${props.remoteField}=${queryParam.value}`
|
if (queryParam.value != undefined) {
|
||||||
|
if (url.includes('?')) {
|
||||||
|
url = `${url}&${props.remoteField}=${queryParam.value}`
|
||||||
|
} else {
|
||||||
|
url = `${url}?${props.remoteField}=${queryParam.value}`
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
parseOptions(await request.get({ url: url }))
|
parseOptions(await request.get({ url: url }))
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export const useSelectRule = (option: SelectRuleOption) => {
|
||||||
icon: option.icon,
|
icon: option.icon,
|
||||||
label,
|
label,
|
||||||
name,
|
name,
|
||||||
|
event: option.event,
|
||||||
rule() {
|
rule() {
|
||||||
return {
|
return {
|
||||||
type: name,
|
type: name,
|
||||||
|
|
|
||||||
|
|
@ -46,5 +46,6 @@ export interface SelectRuleOption {
|
||||||
label: string // label 名称
|
label: string // label 名称
|
||||||
name: string // 组件名称
|
name: string // 组件名称
|
||||||
icon: string // 组件图标
|
icon: string // 组件图标
|
||||||
props?: any[] // 组件规则
|
props?: any[], // 组件规则
|
||||||
|
event?: any[] // 事件配置
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ export const useFormCreateDesigner = async (designer: Ref) => {
|
||||||
name: 'ApiSelect',
|
name: 'ApiSelect',
|
||||||
label: '接口选择器',
|
label: '接口选择器',
|
||||||
icon: 'icon-server',
|
icon: 'icon-server',
|
||||||
props: [...apiSelectRule]
|
props: [...apiSelectRule],
|
||||||
|
event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus']
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,13 @@
|
||||||
>
|
>
|
||||||
<!-- 右上角热区删除按钮 -->
|
<!-- 右上角热区删除按钮 -->
|
||||||
<div
|
<div
|
||||||
v-if="selectedHotAreaIndex === index"
|
v-if="selectedHotAreaIndex === index && hotArea.width && hotArea.height"
|
||||||
class="btn-delete"
|
class="btn-delete"
|
||||||
@click="handleDeleteHotArea(index)"
|
@click="handleDeleteHotArea(index)"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:circle-close-filled" />
|
<Icon icon="ep:circle-close-filled" />
|
||||||
</div>
|
</div>
|
||||||
{{ `${hotArea.width}×${hotArea.height}` }}
|
<span v-if="hotArea.width">{{ `${hotArea.width}×${hotArea.height}` }}</span>
|
||||||
</div>
|
</div>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ import {
|
||||||
DEFAULT_CONDITION_GROUP_VALUE
|
DEFAULT_CONDITION_GROUP_VALUE
|
||||||
} from './consts'
|
} from './consts'
|
||||||
import { generateUUID } from '@/utils'
|
import { generateUUID } from '@/utils'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'NodeHandler'
|
name: 'NodeHandler'
|
||||||
|
|
@ -184,7 +185,7 @@ const addNode = (type: number) => {
|
||||||
conditionSetting: {
|
conditionSetting: {
|
||||||
defaultFlow: false,
|
defaultFlow: false,
|
||||||
conditionType: ConditionType.RULE,
|
conditionType: ConditionType.RULE,
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -242,7 +243,7 @@ const addNode = (type: number) => {
|
||||||
conditionSetting: {
|
conditionSetting: {
|
||||||
defaultFlow: false,
|
defaultFlow: false,
|
||||||
conditionType: ConditionType.RULE,
|
conditionType: ConditionType.RULE,
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,11 @@ const props = defineProps({
|
||||||
startUserIds: {
|
startUserIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
// 可发起流程的部门编号
|
||||||
|
startDeptIds: {
|
||||||
|
type: Array,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -82,6 +87,7 @@ provide('deptList', deptOptions)
|
||||||
provide('userGroupList', userGroupOptions)
|
provide('userGroupList', userGroupOptions)
|
||||||
provide('deptTree', deptTreeOptions)
|
provide('deptTree', deptTreeOptions)
|
||||||
provide('startUserIds', props.startUserIds)
|
provide('startUserIds', props.startUserIds)
|
||||||
|
provide('startDeptIds', props.startDeptIds)
|
||||||
provide('tasks', [])
|
provide('tasks', [])
|
||||||
provide('processInstance', {})
|
provide('processInstance', {})
|
||||||
const message = useMessage() // 国际化
|
const message = useMessage() // 国际化
|
||||||
|
|
|
||||||
|
|
@ -25,21 +25,46 @@
|
||||||
</template>
|
</template>
|
||||||
<el-tabs type="border-card" v-model="activeTabName">
|
<el-tabs type="border-card" v-model="activeTabName">
|
||||||
<el-tab-pane label="权限" name="user">
|
<el-tab-pane label="权限" name="user">
|
||||||
<el-text v-if="!startUserIds || startUserIds.length === 0"> 全部成员可以发起流程 </el-text>
|
<el-text
|
||||||
<el-text v-else-if="startUserIds.length == 1">
|
v-if="
|
||||||
{{ getUserNicknames(startUserIds) }} 可发起流程
|
(!startUserIds || startUserIds.length === 0) &&
|
||||||
</el-text>
|
(!startDeptIds || startDeptIds.length === 0)
|
||||||
<el-text v-else>
|
"
|
||||||
<el-tooltip
|
>
|
||||||
class="box-item"
|
全部成员可以发起流程
|
||||||
effect="dark"
|
|
||||||
placement="top"
|
|
||||||
:content="getUserNicknames(startUserIds)"
|
|
||||||
>
|
|
||||||
{{ getUserNicknames(startUserIds.slice(0, 2)) }} 等
|
|
||||||
{{ startUserIds.length }} 人可发起流程
|
|
||||||
</el-tooltip>
|
|
||||||
</el-text>
|
</el-text>
|
||||||
|
<div v-else-if="startUserIds && startUserIds.length > 0">
|
||||||
|
<el-text v-if="startUserIds.length == 1">
|
||||||
|
{{ getUserNicknames(startUserIds) }} 可发起流程
|
||||||
|
</el-text>
|
||||||
|
<el-text v-else>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
placement="top"
|
||||||
|
:content="getUserNicknames(startUserIds)"
|
||||||
|
>
|
||||||
|
{{ getUserNicknames(startUserIds.slice(0, 2)) }} 等
|
||||||
|
{{ startUserIds.length }} 人可发起流程
|
||||||
|
</el-tooltip>
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="startDeptIds && startDeptIds.length > 0">
|
||||||
|
<el-text v-if="startDeptIds.length == 1">
|
||||||
|
{{ getDeptNames(startDeptIds) }} 可发起流程
|
||||||
|
</el-text>
|
||||||
|
<el-text v-else>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
placement="top"
|
||||||
|
:content="getDeptNames(startDeptIds)"
|
||||||
|
>
|
||||||
|
{{ getDeptNames(startDeptIds.slice(0, 2)) }} 等
|
||||||
|
{{ startDeptIds.length }} 个部门可发起流程
|
||||||
|
</el-tooltip>
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
|
<el-tab-pane label="表单字段权限" name="fields" v-if="formType === 10">
|
||||||
<div class="field-setting-pane">
|
<div class="field-setting-pane">
|
||||||
|
|
@ -107,6 +132,7 @@
|
||||||
import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
|
import { SimpleFlowNode, NodeType, FieldPermissionType, START_USER_BUTTON_SETTING } from '../consts'
|
||||||
import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
|
import { useWatchNode, useDrawer, useNodeName, useFormFieldsPermission } from '../node'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import * as DeptApi from '@/api/system/dept'
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'StartUserNodeConfig'
|
name: 'StartUserNodeConfig'
|
||||||
})
|
})
|
||||||
|
|
@ -118,8 +144,12 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
// 可发起流程的用户编号
|
// 可发起流程的用户编号
|
||||||
const startUserIds = inject<Ref<any[]>>('startUserIds')
|
const startUserIds = inject<Ref<any[]>>('startUserIds')
|
||||||
|
// 可发起流程的部门编号
|
||||||
|
const startDeptIds = inject<Ref<any[]>>('startDeptIds')
|
||||||
// 用户列表
|
// 用户列表
|
||||||
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList')
|
const userOptions = inject<Ref<UserApi.UserVO[]>>('userList')
|
||||||
|
// 部门列表
|
||||||
|
const deptOptions = inject<Ref<DeptApi.DeptVO[]>>('deptList')
|
||||||
// 抽屉配置
|
// 抽屉配置
|
||||||
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
|
||||||
// 当前节点
|
// 当前节点
|
||||||
|
|
@ -145,6 +175,19 @@ const getUserNicknames = (userIds: number[]): string => {
|
||||||
})
|
})
|
||||||
return nicknames.join(',')
|
return nicknames.join(',')
|
||||||
}
|
}
|
||||||
|
const getDeptNames = (deptIds: number[]): string => {
|
||||||
|
if (!deptIds || deptIds.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const deptNames: string[] = []
|
||||||
|
deptIds.forEach((deptId) => {
|
||||||
|
const found = deptOptions?.value.find((item) => item.id === deptId)
|
||||||
|
if (found && found.name) {
|
||||||
|
deptNames.push(found.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return deptNames.join(',')
|
||||||
|
}
|
||||||
// 保存配置
|
// 保存配置
|
||||||
const saveConfig = async () => {
|
const saveConfig = async () => {
|
||||||
activeTabName.value = 'user'
|
activeTabName.value = 'user'
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,7 @@ import {
|
||||||
import { useWatchNode, useDrawer, useNodeName, useFormFields, getConditionShowText } from '../node'
|
import { useWatchNode, useDrawer, useNodeName, useFormFields, getConditionShowText } from '../node'
|
||||||
import HttpRequestSetting from './components/HttpRequestSetting.vue'
|
import HttpRequestSetting from './components/HttpRequestSetting.vue'
|
||||||
import ConditionDialog from './components/ConditionDialog.vue'
|
import ConditionDialog from './components/ConditionDialog.vue'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
const { proxy } = getCurrentInstance() as any
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|
@ -290,7 +291,7 @@ const configForm = ref<TriggerSetting>({
|
||||||
},
|
},
|
||||||
formSettings: [
|
formSettings: [
|
||||||
{
|
{
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE),
|
||||||
updateFormFields: {},
|
updateFormFields: {},
|
||||||
deleteFields: []
|
deleteFields: []
|
||||||
}
|
}
|
||||||
|
|
@ -346,7 +347,7 @@ const changeTriggerType = () => {
|
||||||
? originalSetting.formSettings
|
? originalSetting.formSettings
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE),
|
||||||
updateFormFields: {},
|
updateFormFields: {},
|
||||||
deleteFields: []
|
deleteFields: []
|
||||||
}
|
}
|
||||||
|
|
@ -361,7 +362,7 @@ const changeTriggerType = () => {
|
||||||
? originalSetting.formSettings
|
? originalSetting.formSettings
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE),
|
||||||
updateFormFields: undefined,
|
updateFormFields: undefined,
|
||||||
deleteFields: []
|
deleteFields: []
|
||||||
}
|
}
|
||||||
|
|
@ -374,7 +375,7 @@ const changeTriggerType = () => {
|
||||||
/** 添加新的修改表单设置 */
|
/** 添加新的修改表单设置 */
|
||||||
const addFormSetting = () => {
|
const addFormSetting = () => {
|
||||||
configForm.value.formSettings!.push({
|
configForm.value.formSettings!.push({
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE),
|
||||||
updateFormFields: {},
|
updateFormFields: {},
|
||||||
deleteFields: []
|
deleteFields: []
|
||||||
})
|
})
|
||||||
|
|
@ -509,7 +510,7 @@ const showTriggerNodeConfig = (node: SimpleFlowNode) => {
|
||||||
},
|
},
|
||||||
formSettings: node.triggerSetting.formSettings || [
|
formSettings: node.triggerSetting.formSettings || [
|
||||||
{
|
{
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE,
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE),
|
||||||
updateFormFields: {},
|
updateFormFields: {},
|
||||||
deleteFields: []
|
deleteFields: []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@ import {
|
||||||
} from '../../consts'
|
} from '../../consts'
|
||||||
import { BpmModelFormType } from '@/utils/constants'
|
import { BpmModelFormType } from '@/utils/constants'
|
||||||
import { useFormFieldsAndStartUser } from '../../node'
|
import { useFormFieldsAndStartUser } from '../../node'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -196,7 +197,7 @@ const formRef = ref() // 表单 Ref
|
||||||
const changeConditionType = () => {
|
const changeConditionType = () => {
|
||||||
if (condition.value.conditionType === ConditionType.RULE) {
|
if (condition.value.conditionType === ConditionType.RULE) {
|
||||||
if (!condition.value.conditionGroups) {
|
if (!condition.value.conditionGroups) {
|
||||||
condition.value.conditionGroups = DEFAULT_CONDITION_GROUP_VALUE
|
condition.value.conditionGroups = cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!-- TODO @jason:有可能,它里面套 Condition 么? -->
|
<!-- TODO @jason:有可能,它里面套 Condition 么? -->
|
||||||
<!-- TODO 怕影响其它节点功能,后面看看如何如何复用 Condtion -->
|
<!-- TODO 怕影响其它节点功能,后面看看如何如何复用 Condtion -->
|
||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" title="条件配置" width="600px" :fullscreen="false">
|
<Dialog v-model="dialogVisible" title="条件配置" width="600px" :fullscreen="false">
|
||||||
<div class="h-410px">
|
<div class="h-410px">
|
||||||
|
|
@ -165,6 +165,7 @@ import {
|
||||||
} from '../../consts'
|
} from '../../consts'
|
||||||
import { BpmModelFormType } from '@/utils/constants'
|
import { BpmModelFormType } from '@/utils/constants'
|
||||||
import { useFormFieldsAndStartUser } from '../../node'
|
import { useFormFieldsAndStartUser } from '../../node'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ConditionDialog'
|
name: 'ConditionDialog'
|
||||||
})
|
})
|
||||||
|
|
@ -175,7 +176,7 @@ const condition = ref<{
|
||||||
conditionGroups?: ConditionGroup
|
conditionGroups?: ConditionGroup
|
||||||
}>({
|
}>({
|
||||||
conditionType: ConditionType.RULE,
|
conditionType: ConditionType.RULE,
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
@ -210,7 +211,7 @@ const formRef = ref() // 表单 Ref
|
||||||
const changeConditionType = () => {
|
const changeConditionType = () => {
|
||||||
if (condition.value.conditionType === ConditionType.RULE) {
|
if (condition.value.conditionType === ConditionType.RULE) {
|
||||||
if (!condition.value.conditionGroups) {
|
if (!condition.value.conditionGroups) {
|
||||||
condition.value.conditionGroups = DEFAULT_CONDITION_GROUP_VALUE
|
condition.value.conditionGroups = cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,18 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NodeHandler from '../NodeHandler.vue'
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||||
import { SimpleFlowNode, NodeType, ConditionType, DEFAULT_CONDITION_GROUP_VALUE, NODE_DEFAULT_TEXT } from '../consts'
|
import {
|
||||||
|
SimpleFlowNode,
|
||||||
|
NodeType,
|
||||||
|
ConditionType,
|
||||||
|
DEFAULT_CONDITION_GROUP_VALUE,
|
||||||
|
NODE_DEFAULT_TEXT
|
||||||
|
} from '../consts'
|
||||||
import { getDefaultConditionNodeName } from '../utils'
|
import { getDefaultConditionNodeName } from '../utils'
|
||||||
import { useTaskStatusClass } from '../node'
|
import { useTaskStatusClass } from '../node'
|
||||||
import { generateUUID } from '@/utils'
|
import { generateUUID } from '@/utils'
|
||||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
const { proxy } = getCurrentInstance() as any
|
const { proxy } = getCurrentInstance() as any
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ExclusiveNode'
|
name: 'ExclusiveNode'
|
||||||
|
|
@ -149,7 +156,8 @@ const blurEvent = (index: number) => {
|
||||||
showInputs.value[index] = false
|
showInputs.value[index] = false
|
||||||
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||||
conditionNode.name =
|
conditionNode.name =
|
||||||
conditionNode.name || getDefaultConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
|
conditionNode.name ||
|
||||||
|
getDefaultConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击条件名称
|
// 点击条件名称
|
||||||
|
|
@ -181,7 +189,7 @@ const addCondition = () => {
|
||||||
conditionSetting: {
|
conditionSetting: {
|
||||||
defaultFlow: false,
|
defaultFlow: false,
|
||||||
conditionType: ConditionType.RULE,
|
conditionType: ConditionType.RULE,
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conditionNodes.splice(lastIndex, 0, conditionData)
|
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||||
|
|
|
||||||
|
|
@ -110,11 +110,18 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import NodeHandler from '../NodeHandler.vue'
|
import NodeHandler from '../NodeHandler.vue'
|
||||||
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
import ProcessNodeTree from '../ProcessNodeTree.vue'
|
||||||
import { SimpleFlowNode, NodeType, ConditionType, DEFAULT_CONDITION_GROUP_VALUE, NODE_DEFAULT_TEXT } from '../consts'
|
import {
|
||||||
|
SimpleFlowNode,
|
||||||
|
NodeType,
|
||||||
|
ConditionType,
|
||||||
|
DEFAULT_CONDITION_GROUP_VALUE,
|
||||||
|
NODE_DEFAULT_TEXT
|
||||||
|
} from '../consts'
|
||||||
import { useTaskStatusClass } from '../node'
|
import { useTaskStatusClass } from '../node'
|
||||||
import { getDefaultInclusiveConditionNodeName } from '../utils'
|
import { getDefaultInclusiveConditionNodeName } from '../utils'
|
||||||
import { generateUUID } from '@/utils'
|
import { generateUUID } from '@/utils'
|
||||||
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
const { proxy } = getCurrentInstance() as any
|
const { proxy } = getCurrentInstance() as any
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'InclusiveNode'
|
name: 'InclusiveNode'
|
||||||
|
|
@ -153,7 +160,8 @@ const blurEvent = (index: number) => {
|
||||||
showInputs.value[index] = false
|
showInputs.value[index] = false
|
||||||
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
|
||||||
conditionNode.name =
|
conditionNode.name =
|
||||||
conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
|
conditionNode.name ||
|
||||||
|
getDefaultInclusiveConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击条件名称
|
// 点击条件名称
|
||||||
|
|
@ -185,7 +193,7 @@ const addCondition = () => {
|
||||||
conditionSetting: {
|
conditionSetting: {
|
||||||
defaultFlow: false,
|
defaultFlow: false,
|
||||||
conditionType: ConditionType.RULE,
|
conditionType: ConditionType.RULE,
|
||||||
conditionGroups: DEFAULT_CONDITION_GROUP_VALUE
|
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conditionNodes.splice(lastIndex, 0, conditionData)
|
conditionNodes.splice(lastIndex, 0, conditionData)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<div ref="divRef" :class="['tinyflow', className]" :style="style" style="height: 100%"> </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Item, Tinyflow as TinyflowNative } from './ui'
|
||||||
|
import './ui/index.css'
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
className?: string
|
||||||
|
style?: Record<string, string>
|
||||||
|
data?: Record<string, any>
|
||||||
|
provider?: {
|
||||||
|
llm?: () => Item[] | Promise<Item[]>
|
||||||
|
knowledge?: () => Item[] | Promise<Item[]>
|
||||||
|
internal?: () => Item[] | Promise<Item[]>
|
||||||
|
}
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const divRef = ref<HTMLDivElement | null>(null)
|
||||||
|
let tinyflow: TinyflowNative | null = null
|
||||||
|
// 定义默认的 provider 方法
|
||||||
|
const defaultProvider = {
|
||||||
|
llm: () => [] as Item[],
|
||||||
|
knowledge: () => [] as Item[],
|
||||||
|
internal: () => [] as Item[]
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (divRef.value) {
|
||||||
|
// 合并默认 provider 和传入的 props.provider
|
||||||
|
const mergedProvider = {
|
||||||
|
...defaultProvider,
|
||||||
|
...props.provider
|
||||||
|
}
|
||||||
|
tinyflow = new TinyflowNative({
|
||||||
|
element: divRef.value as Element,
|
||||||
|
data: props.data || {},
|
||||||
|
provider: mergedProvider
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (tinyflow) {
|
||||||
|
tinyflow.destroy()
|
||||||
|
tinyflow = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
if (tinyflow) {
|
||||||
|
return tinyflow.getData()
|
||||||
|
}
|
||||||
|
console.warn('Tinyflow instance is not initialized')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getData
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Edge } from '@xyflow/svelte';
|
||||||
|
import { Node as Node_2 } from '@xyflow/svelte';
|
||||||
|
import { useSvelteFlow } from '@xyflow/svelte';
|
||||||
|
import { Viewport } from '@xyflow/svelte';
|
||||||
|
|
||||||
|
export declare type Item = {
|
||||||
|
value: number | string;
|
||||||
|
label: string;
|
||||||
|
children?: Item[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export declare class Tinyflow {
|
||||||
|
private options;
|
||||||
|
private rootEl;
|
||||||
|
private svelteFlowInstance;
|
||||||
|
constructor(options: TinyflowOptions);
|
||||||
|
private _init;
|
||||||
|
private _setOptions;
|
||||||
|
getOptions(): TinyflowOptions;
|
||||||
|
getData(): {
|
||||||
|
nodes: Node_2[];
|
||||||
|
edges: Edge[];
|
||||||
|
viewport: Viewport;
|
||||||
|
};
|
||||||
|
setData(data: TinyflowData): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type TinyflowData = Partial<ReturnType<ReturnType<typeof useSvelteFlow>['toObject']>>;
|
||||||
|
|
||||||
|
export declare type TinyflowOptions = {
|
||||||
|
element: string | Element;
|
||||||
|
data?: TinyflowData;
|
||||||
|
provider?: {
|
||||||
|
llm?: () => Item[] | Promise<Item[]>;
|
||||||
|
knowledge?: () => Item[] | Promise<Item[]>;
|
||||||
|
internal?: () => Item[] | Promise<Item[]>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { }
|
||||||
|
|
@ -86,7 +86,8 @@ const props = defineProps({
|
||||||
autoUpload: propTypes.bool.def(true), // 自动上传
|
autoUpload: propTypes.bool.def(true), // 自动上传
|
||||||
drag: propTypes.bool.def(false), // 拖拽上传
|
drag: propTypes.bool.def(false), // 拖拽上传
|
||||||
isShowTip: propTypes.bool.def(true), // 是否显示提示
|
isShowTip: propTypes.bool.def(true), // 是否显示提示
|
||||||
disabled: propTypes.bool.def(false) // 是否禁用上传组件 ==> 非必传(默认为 false)
|
disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
|
||||||
|
directory: propTypes.string.def(undefined) // 上传目录 ==> 非必传(默认为 undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ========== 上传相关 ==========
|
// ========== 上传相关 ==========
|
||||||
|
|
@ -95,7 +96,7 @@ const uploadList = ref<UploadUserFile[]>([])
|
||||||
const fileList = ref<UploadUserFile[]>([])
|
const fileList = ref<UploadUserFile[]>([])
|
||||||
const uploadNumber = ref<number>(0)
|
const uploadNumber = ref<number>(0)
|
||||||
|
|
||||||
const { uploadUrl, httpRequest } = useUpload()
|
const { uploadUrl, httpRequest } = useUpload(props.directory)
|
||||||
|
|
||||||
// 文件上传之前判断
|
// 文件上传之前判断
|
||||||
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
|
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,8 @@ const props = defineProps({
|
||||||
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
|
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
|
||||||
borderradius: propTypes.string.def('8px'), // 组件边框圆角 ==> 非必传(默认为 8px)
|
borderradius: propTypes.string.def('8px'), // 组件边框圆角 ==> 非必传(默认为 8px)
|
||||||
showDelete: propTypes.bool.def(true), // 是否显示删除按钮
|
showDelete: propTypes.bool.def(true), // 是否显示删除按钮
|
||||||
showBtnText: propTypes.bool.def(true) // 是否显示按钮文字
|
showBtnText: propTypes.bool.def(true), // 是否显示按钮文字
|
||||||
|
directory: propTypes.string.def(undefined) // 上传目录 ==> 非必传(默认为 undefined)
|
||||||
})
|
})
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
@ -99,7 +100,7 @@ const deleteImg = () => {
|
||||||
emit('update:modelValue', '')
|
emit('update:modelValue', '')
|
||||||
}
|
}
|
||||||
|
|
||||||
const { uploadUrl, httpRequest } = useUpload()
|
const { uploadUrl, httpRequest } = useUpload(props.directory)
|
||||||
|
|
||||||
const editImg = () => {
|
const editImg = () => {
|
||||||
const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
|
const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,11 @@ const props = defineProps({
|
||||||
fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
|
fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
|
||||||
height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
|
height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
|
||||||
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
|
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
|
||||||
borderradius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
|
borderradius: propTypes.string.def('8px'), // 组件边框圆角 ==> 非必传(默认为 8px)
|
||||||
|
directory: propTypes.string.def(undefined) // 上传目录 ==> 非必传(默认为 undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
const { uploadUrl, httpRequest } = useUpload()
|
const { uploadUrl, httpRequest } = useUpload(props.directory)
|
||||||
|
|
||||||
const fileList = ref<UploadUserFile[]>([])
|
const fileList = ref<UploadUserFile[]>([])
|
||||||
const uploadNumber = ref<number>(0)
|
const uploadNumber = ref<number>(0)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as FileApi from '@/api/infra/file'
|
import * as FileApi from '@/api/infra/file'
|
||||||
import CryptoJS from 'crypto-js'
|
// import CryptoJS from 'crypto-js'
|
||||||
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ export const getUploadUrl = (): string => {
|
||||||
return import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload'
|
return import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUpload = () => {
|
export const useUpload = (directory?: string) => {
|
||||||
// 后端上传地址
|
// 后端上传地址
|
||||||
const uploadUrl = getUploadUrl()
|
const uploadUrl = getUploadUrl()
|
||||||
// 是否使用前端直连上传
|
// 是否使用前端直连上传
|
||||||
|
|
@ -22,7 +22,7 @@ export const useUpload = () => {
|
||||||
// 1.1 生成文件名称
|
// 1.1 生成文件名称
|
||||||
const fileName = await generateFileName(options.file)
|
const fileName = await generateFileName(options.file)
|
||||||
// 1.2 获取文件预签名地址
|
// 1.2 获取文件预签名地址
|
||||||
const presignedInfo = await FileApi.getFilePresignedUrl(fileName)
|
const presignedInfo = await FileApi.getFilePresignedUrl(fileName, directory)
|
||||||
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持)
|
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持)
|
||||||
return axios
|
return axios
|
||||||
.put(presignedInfo.uploadUrl, options.file, {
|
.put(presignedInfo.uploadUrl, options.file, {
|
||||||
|
|
@ -32,7 +32,7 @@ export const useUpload = () => {
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 1.4. 记录文件信息到后端(异步)
|
// 1.4. 记录文件信息到后端(异步)
|
||||||
createFile(presignedInfo, fileName, options.file)
|
createFile(presignedInfo, options.file)
|
||||||
// 通知成功,数据格式保持与后端上传的返回结果一致
|
// 通知成功,数据格式保持与后端上传的返回结果一致
|
||||||
return { data: presignedInfo.url }
|
return { data: presignedInfo.url }
|
||||||
})
|
})
|
||||||
|
|
@ -40,7 +40,7 @@ export const useUpload = () => {
|
||||||
// 模式二:后端上传
|
// 模式二:后端上传
|
||||||
// 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
|
// 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
FileApi.updateFile({ file: options.file })
|
FileApi.updateFile({ file: options.file, directory })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
resolve(res)
|
resolve(res)
|
||||||
|
|
@ -67,11 +67,11 @@ export const useUpload = () => {
|
||||||
* @param name 文件名称
|
* @param name 文件名称
|
||||||
* @param file 文件
|
* @param file 文件
|
||||||
*/
|
*/
|
||||||
function createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: UploadRawFile) {
|
function createFile(vo: FileApi.FilePresignedUrlRespVO, file: UploadRawFile) {
|
||||||
const fileVo = {
|
const fileVo = {
|
||||||
configId: vo.configId,
|
configId: vo.configId,
|
||||||
url: vo.url,
|
url: vo.url,
|
||||||
path: name,
|
path: vo.path,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size
|
size: file.size
|
||||||
|
|
@ -85,14 +85,15 @@ function createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: Uplo
|
||||||
* @param file 要上传的文件
|
* @param file 要上传的文件
|
||||||
*/
|
*/
|
||||||
async function generateFileName(file: UploadRawFile) {
|
async function generateFileName(file: UploadRawFile) {
|
||||||
// 读取文件内容
|
// // 读取文件内容
|
||||||
const data = await file.arrayBuffer()
|
// const data = await file.arrayBuffer()
|
||||||
const wordArray = CryptoJS.lib.WordArray.create(data)
|
// const wordArray = CryptoJS.lib.WordArray.create(data)
|
||||||
// 计算SHA256
|
// // 计算SHA256
|
||||||
const sha256 = CryptoJS.SHA256(wordArray).toString()
|
// const sha256 = CryptoJS.SHA256(wordArray).toString()
|
||||||
// 拼接后缀
|
// // 拼接后缀
|
||||||
const ext = file.name.substring(file.name.lastIndexOf('.'))
|
// const ext = file.name.substring(file.name.lastIndexOf('.'))
|
||||||
return `${sha256}${ext}`
|
// return `${sha256}${ext}`
|
||||||
|
return file.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -237,7 +237,7 @@ const props = defineProps({
|
||||||
const prefix = inject('prefix')
|
const prefix = inject('prefix')
|
||||||
const width = inject('width')
|
const width = inject('width')
|
||||||
|
|
||||||
const formKey = ref('')
|
const formKey = ref(undefined)
|
||||||
const businessKey = ref('')
|
const businessKey = ref('')
|
||||||
const optionModelTitle = ref('')
|
const optionModelTitle = ref('')
|
||||||
const fieldList = ref<any[]>([])
|
const fieldList = ref<any[]>([])
|
||||||
|
|
@ -462,6 +462,7 @@ const updateElementExtensions = () => {
|
||||||
const formList = ref([]) // 流程表单的下拉框的数据
|
const formList = ref([]) // 流程表单的下拉框的数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
formList.value = await FormApi.getFormSimpleList()
|
formList.value = await FormApi.getFormSimpleList()
|
||||||
|
formKey.value = parseInt(formKey.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,6 @@ const removeListenerField = (index) => {
|
||||||
}
|
}
|
||||||
// 移除监听器
|
// 移除监听器
|
||||||
const removeListener = (index) => {
|
const removeListener = (index) => {
|
||||||
debugger
|
|
||||||
ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
|
ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
|
||||||
confirmButtonText: '确 认',
|
confirmButtonText: '确 认',
|
||||||
cancelButtonText: '取 消'
|
cancelButtonText: '取 消'
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { toRaw } from 'vue'
|
||||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||||
// 创建监听器实例
|
// 创建监听器实例
|
||||||
export function createListenerObject(options, isTask, prefix) {
|
export function createListenerObject(options, isTask, prefix) {
|
||||||
debugger
|
|
||||||
const listenerObj = Object.create(null)
|
const listenerObj = Object.create(null)
|
||||||
listenerObj.event = options.event
|
listenerObj.event = options.event
|
||||||
isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段
|
isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,14 @@ import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestCo
|
||||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import { config } from '@/config/axios/config'
|
import { config } from '@/config/axios/config'
|
||||||
import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth'
|
import {
|
||||||
|
getAccessToken,
|
||||||
|
getRefreshToken,
|
||||||
|
getTenantId,
|
||||||
|
getVisitTenantId,
|
||||||
|
removeToken,
|
||||||
|
setToken
|
||||||
|
} from '@/utils/auth'
|
||||||
import errorCode from './errorCode'
|
import errorCode from './errorCode'
|
||||||
|
|
||||||
import { resetRouter } from '@/router'
|
import { resetRouter } from '@/router'
|
||||||
|
|
@ -24,7 +31,7 @@ export const isRelogin = { show: false }
|
||||||
let requestList: any[] = []
|
let requestList: any[] = []
|
||||||
// 是否正在刷新中
|
// 是否正在刷新中
|
||||||
let isRefreshToken = false
|
let isRefreshToken = false
|
||||||
// 请求白名单,无须token的接口
|
// 请求白名单,无须 token 的接口
|
||||||
const whiteList: string[] = ['/login', '/refresh-token']
|
const whiteList: string[] = ['/login', '/refresh-token']
|
||||||
|
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
|
|
@ -55,6 +62,11 @@ service.interceptors.request.use(
|
||||||
if (tenantEnable && tenantEnable === 'true') {
|
if (tenantEnable && tenantEnable === 'true') {
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
if (tenantId) config.headers['tenant-id'] = tenantId
|
if (tenantId) config.headers['tenant-id'] = tenantId
|
||||||
|
// 只有登录时,才设置 visit-tenant-id 访问租户
|
||||||
|
const visitTenantId = getVisitTenantId()
|
||||||
|
if (config.headers.Authorization && visitTenantId) {
|
||||||
|
config.headers['visit-tenant-id'] = visitTenantId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const method = config.method?.toUpperCase()
|
const method = config.method?.toUpperCase()
|
||||||
// 防止 GET 请求缓存
|
// 防止 GET 请求缓存
|
||||||
|
|
@ -201,6 +213,10 @@ const refreshToken = async () => {
|
||||||
const handleAuthorized = () => {
|
const handleAuthorized = () => {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
if (!isRelogin.show) {
|
if (!isRelogin.show) {
|
||||||
|
// 如果已经到登录页面则不进行弹窗提示
|
||||||
|
if (window.location.href.includes('login')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
isRelogin.show = true
|
isRelogin.show = true
|
||||||
ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
|
ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
|
||||||
showCancelButton: false,
|
showCancelButton: false,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export const CACHE_KEY = {
|
||||||
// 用户相关
|
// 用户相关
|
||||||
ROLE_ROUTERS: 'roleRouters',
|
ROLE_ROUTERS: 'roleRouters',
|
||||||
USER: 'user',
|
USER: 'user',
|
||||||
|
VisitTenantId: 'visitTenantId',
|
||||||
// 系统设置
|
// 系统设置
|
||||||
IS_DARK: 'isDark',
|
IS_DARK: 'isDark',
|
||||||
LANG: 'lang',
|
LANG: 'lang',
|
||||||
|
|
@ -35,5 +36,6 @@ export const deleteUserCache = () => {
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
wsCache.delete(CACHE_KEY.USER)
|
wsCache.delete(CACHE_KEY.USER)
|
||||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
||||||
|
wsCache.delete(CACHE_KEY.VisitTenantId)
|
||||||
// 注意,不要清理 LoginForm 登录表单
|
// 注意,不要清理 LoginForm 登录表单
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import { watch } from 'vue'
|
||||||
|
|
||||||
const domSymbol = Symbol('watermark-dom')
|
const domSymbol = Symbol('watermark-dom')
|
||||||
|
|
||||||
export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||||
let func: Fn = () => {}
|
let func: Fn = () => {}
|
||||||
const id = domSymbol.toString()
|
const id = domSymbol.toString()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
let watermarkStr = ''
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
const domId = document.getElementById(id)
|
const domId = document.getElementById(id)
|
||||||
if (domId) {
|
if (domId) {
|
||||||
|
|
@ -22,7 +28,7 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||||
if (cans) {
|
if (cans) {
|
||||||
cans.rotate((-20 * Math.PI) / 120)
|
cans.rotate((-20 * Math.PI) / 120)
|
||||||
cans.font = '15px Vedana'
|
cans.font = '15px Vedana'
|
||||||
cans.fillStyle = 'rgba(0, 0, 0, 0.15)'
|
cans.fillStyle = appStore.getIsDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)'
|
||||||
cans.textAlign = 'left'
|
cans.textAlign = 'left'
|
||||||
cans.textBaseline = 'middle'
|
cans.textBaseline = 'middle'
|
||||||
cans.fillText(str, can.width / 20, can.height)
|
cans.fillText(str, can.width / 20, can.height)
|
||||||
|
|
@ -44,6 +50,7 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWatermark(str: string) {
|
function setWatermark(str: string) {
|
||||||
|
watermarkStr = str
|
||||||
createWatermark(str)
|
createWatermark(str)
|
||||||
func = () => {
|
func = () => {
|
||||||
createWatermark(str)
|
createWatermark(str)
|
||||||
|
|
@ -51,5 +58,15 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||||
window.addEventListener('resize', func)
|
window.addEventListener('resize', func)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听主题变化
|
||||||
|
watch(
|
||||||
|
() => appStore.getIsDark,
|
||||||
|
() => {
|
||||||
|
if (watermarkStr) {
|
||||||
|
createWatermark(watermarkStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return { setWatermark, clear }
|
return { setWatermark, clear }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-select
|
||||||
|
filterable
|
||||||
|
placeholder="请选择租户"
|
||||||
|
class="!w-180px"
|
||||||
|
v-model="value"
|
||||||
|
@change="handleChange"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option v-for="item in tenants" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import * as TenantApi from '@/api/system/tenant'
|
||||||
|
import { getVisitTenantId, setVisitTenantId } from '@/utils/auth'
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
|
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const tagsView = useTagsView() // 标签页操作
|
||||||
|
|
||||||
|
const value = ref(getVisitTenantId()) // 当前选中的租户 ID
|
||||||
|
const tenants = ref<any[]>([]) // 租户列表
|
||||||
|
|
||||||
|
const handleChange = (id: number) => {
|
||||||
|
// 设置访问租户 ID
|
||||||
|
setVisitTenantId(id)
|
||||||
|
// 关闭其他标签页,只保留当前页
|
||||||
|
tagsView.closeOther()
|
||||||
|
// 刷新当前页面
|
||||||
|
tagsView.refreshPage()
|
||||||
|
// 提示切换成功
|
||||||
|
const tenant = tenants.value.find((item) => item.id === id)
|
||||||
|
if (tenant) {
|
||||||
|
message.success(`切换当前租户为: ${tenant.name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
tenants.value = await TenantApi.getTenantList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -8,8 +8,10 @@ import { Breadcrumb } from '@/layout/components/Breadcrumb'
|
||||||
import { SizeDropdown } from '@/layout/components/SizeDropdown'
|
import { SizeDropdown } from '@/layout/components/SizeDropdown'
|
||||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||||
import RouterSearch from '@/components/RouterSearch/index.vue'
|
import RouterSearch from '@/components/RouterSearch/index.vue'
|
||||||
|
import TenantVisit from '@/layout/components/TenantVisit/index.vue'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
import { checkPermi } from '@/utils/permission'
|
||||||
|
|
||||||
const { getPrefixCls, variables } = useDesign()
|
const { getPrefixCls, variables } = useDesign()
|
||||||
|
|
||||||
|
|
@ -41,6 +43,11 @@ const locale = computed(() => appStore.getLocale)
|
||||||
// 消息图标
|
// 消息图标
|
||||||
const message = computed(() => appStore.getMessage)
|
const message = computed(() => appStore.getMessage)
|
||||||
|
|
||||||
|
// 租户切换权限
|
||||||
|
const hasTenantVisitPermission = computed(
|
||||||
|
() => import.meta.env.VITE_APP_TENANT_ENABLE === 'true' && checkPermi(['system:tenant:visit'])
|
||||||
|
)
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ToolHeader',
|
name: 'ToolHeader',
|
||||||
setup() {
|
setup() {
|
||||||
|
|
@ -62,6 +69,7 @@ export default defineComponent({
|
||||||
</div>
|
</div>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
<div class="h-full flex items-center">
|
<div class="h-full flex items-center">
|
||||||
|
{hasTenantVisitPermission.value ? <TenantVisit /> : undefined}
|
||||||
{screenfull.value ? (
|
{screenfull.value ? (
|
||||||
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
|
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|
|
||||||
|
|
@ -142,9 +142,9 @@ export default {
|
||||||
qrcode: '扫描二维码登录',
|
qrcode: '扫描二维码登录',
|
||||||
btnRegister: '注册',
|
btnRegister: '注册',
|
||||||
SmsSendMsg: '验证码已发送',
|
SmsSendMsg: '验证码已发送',
|
||||||
resetPassword: "重置密码",
|
resetPassword: '重置密码',
|
||||||
resetPasswordSuccess: "重置密码成功",
|
resetPasswordSuccess: '重置密码成功',
|
||||||
invalidTenantName: "无效的租户名称"
|
invalidTenantName: '无效的租户名称'
|
||||||
},
|
},
|
||||||
captcha: {
|
captcha: {
|
||||||
verification: '请完成安全验证',
|
verification: '请完成安全验证',
|
||||||
|
|
@ -416,9 +416,9 @@ export default {
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
title: '基本信息',
|
title: '基本信息',
|
||||||
basicInfo: '基本资料',
|
basicInfo: '基本设置',
|
||||||
resetPwd: '修改密码',
|
resetPwd: '密码设置',
|
||||||
userSocial: '社交信息'
|
userSocial: '社交绑定'
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
nickname: '请输入用户昵称',
|
nickname: '请输入用户昵称',
|
||||||
|
|
|
||||||
|
|
@ -476,9 +476,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||||
name: 'DiyTemplateDecorate',
|
name: 'DiyTemplateDecorate',
|
||||||
meta: {
|
meta: {
|
||||||
title: '模板装修',
|
title: '模板装修',
|
||||||
noCache: true,
|
noCache: false,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
activeMenu: '/mall/promotion/diy/template'
|
activeMenu: '/mall/promotion/diy-template/diy-template'
|
||||||
},
|
},
|
||||||
component: () => import('@/views/mall/promotion/diy/template/decorate.vue')
|
component: () => import('@/views/mall/promotion/diy/template/decorate.vue')
|
||||||
},
|
},
|
||||||
|
|
@ -487,9 +487,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||||
name: 'DiyPageDecorate',
|
name: 'DiyPageDecorate',
|
||||||
meta: {
|
meta: {
|
||||||
title: '页面装修',
|
title: '页面装修',
|
||||||
noCache: true,
|
noCache: false,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
activeMenu: '/mall/promotion/diy/page'
|
activeMenu: '/mall/promotion/diy-template/diy-page'
|
||||||
},
|
},
|
||||||
component: () => import('@/views/mall/promotion/diy/page/decorate.vue')
|
component: () => import('@/views/mall/promotion/diy/page/decorate.vue')
|
||||||
}
|
}
|
||||||
|
|
@ -667,6 +667,30 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||||
hidden: true,
|
hidden: true,
|
||||||
activeMenu: '/ai/knowledge'
|
activeMenu: '/ai/knowledge'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'console/workflow/create',
|
||||||
|
component: () => import('@/views/ai/workflow/form/index.vue'),
|
||||||
|
name: 'AiWorkflowCreate',
|
||||||
|
meta: {
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true,
|
||||||
|
title: '设计 AI 工作流',
|
||||||
|
activeMenu: '/ai/console/workflow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'console/workflow/:type/:id',
|
||||||
|
component: () => import('@/views/ai/workflow/form/index.vue'),
|
||||||
|
name: 'AiWorkflowUpdate',
|
||||||
|
meta: {
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true,
|
||||||
|
title: '设计 AI 工作流',
|
||||||
|
activeMenu: '/ai/console/workflow'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,11 @@ export const useUserStore = defineStore('admin-user', {
|
||||||
let userInfo = wsCache.get(CACHE_KEY.USER)
|
let userInfo = wsCache.get(CACHE_KEY.USER)
|
||||||
if (!userInfo) {
|
if (!userInfo) {
|
||||||
userInfo = await getInfo()
|
userInfo = await getInfo()
|
||||||
|
} else {
|
||||||
|
// 特殊:在有缓存的情况下,进行加载。但是即使加载失败,也不影响后续的操作,保证可以进入系统
|
||||||
|
try {
|
||||||
|
userInfo = await getInfo()
|
||||||
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
this.permissions = new Set(userInfo.permissions)
|
this.permissions = new Set(userInfo.permissions)
|
||||||
this.roles = userInfo.roles
|
this.roles = userInfo.roles
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,14 @@ export const getTenantId = () => {
|
||||||
return wsCache.get(CACHE_KEY.TenantId)
|
return wsCache.get(CACHE_KEY.TenantId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setTenantId = (username: string) => {
|
export const setTenantId = (tenantId: number) => {
|
||||||
wsCache.set(CACHE_KEY.TenantId, username)
|
wsCache.set(CACHE_KEY.TenantId, tenantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getVisitTenantId = () => {
|
||||||
|
return wsCache.get(CACHE_KEY.VisitTenantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setVisitTenantId = (visitTenantId: number) => {
|
||||||
|
wsCache.set(CACHE_KEY.VisitTenantId, visitTenantId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export const SystemUserSocialTypeEnum = {
|
||||||
export const InfraCodegenTemplateTypeEnum = {
|
export const InfraCodegenTemplateTypeEnum = {
|
||||||
CRUD: 1, // 基础 CRUD
|
CRUD: 1, // 基础 CRUD
|
||||||
TREE: 2, // 树形 CRUD
|
TREE: 2, // 树形 CRUD
|
||||||
SUB: 3 // 主子表 CRUD
|
SUB: 15 // 主子表 CRUD
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -461,5 +461,5 @@ export const BpmProcessInstanceStatus = {
|
||||||
export const BpmAutoApproveType = {
|
export const BpmAutoApproveType = {
|
||||||
NONE: 0, // 不自动通过
|
NONE: 0, // 不自动通过
|
||||||
APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
|
APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
|
||||||
APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
|
APPROVE_SEQUENT: 2 // 仅针对连续审批的节点自动通过
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,6 @@ export enum DICT_TYPE {
|
||||||
PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态
|
PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态
|
||||||
PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态
|
PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态
|
||||||
PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态
|
PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态
|
||||||
PAY_TRANSFER_TYPE = 'pay_transfer_type', // 转账订单状态
|
|
||||||
|
|
||||||
// ========== MP 模块 ==========
|
// ========== MP 模块 ==========
|
||||||
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
||||||
|
|
|
||||||
|
|
@ -517,8 +517,8 @@ export function jsonParse(str: string) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(str)
|
return JSON.parse(str)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`str[${str}] 不是一个 JSON 字符串`)
|
console.warn(`str[${str}] 不是一个 JSON 字符串`)
|
||||||
return ''
|
return str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,12 +83,21 @@
|
||||||
:sm="24"
|
:sm="24"
|
||||||
:xs="24"
|
:xs="24"
|
||||||
>
|
>
|
||||||
<el-card shadow="hover" class="mr-5px mt-5px">
|
<el-card
|
||||||
|
shadow="hover"
|
||||||
|
class="mr-5px mt-5px cursor-pointer"
|
||||||
|
@click="handleProjectClick(item.message)"
|
||||||
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Icon :icon="item.icon" :size="25" class="mr-8px" />
|
<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-9px text-gray-400">{{ t(item.message) }}</div>
|
<div class="mt-12px text-12px text-gray-400">{{ t(item.message) }}</div>
|
||||||
<div class="mt-12px flex justify-between text-12px text-gray-400">
|
<div class="mt-12px flex justify-between text-12px text-gray-400">
|
||||||
<span>{{ item.personal }}</span>
|
<span>{{ item.personal }}</span>
|
||||||
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
||||||
|
|
@ -131,8 +140,8 @@
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
|
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Icon :icon="item.icon" class="mr-8px" />
|
<Icon :icon="item.icon" class="mr-8px" :style="{ color: item.color }" />
|
||||||
<el-link type="default" :underline="false" @click="setWatermark(item.name)">
|
<el-link type="default" :underline="false" @click="handleShortcutClick(item.url)">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -177,15 +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'
|
||||||
|
|
||||||
defineOptions({ name: 'Home' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
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
|
||||||
|
|
@ -212,45 +223,51 @@ const getProject = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
name: 'ruoyi-vue-pro',
|
name: 'ruoyi-vue-pro',
|
||||||
icon: 'akar-icons:github-fill',
|
icon: 'simple-icons:springboot',
|
||||||
message: 'https://github.com/YunaiV/ruoyi-vue-pro',
|
message: 'github.com/YunaiV/ruoyi-vue-pro',
|
||||||
personal: 'Spring Boot 单体架构',
|
personal: 'Spring Boot 单体架构',
|
||||||
time: new Date()
|
time: new Date('2025-01-02'),
|
||||||
|
color: '#6DB33F'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'yudao-ui-admin-vue3',
|
name: 'yudao-ui-admin-vue3',
|
||||||
icon: 'logos:vue',
|
icon: 'ep:element-plus',
|
||||||
message: 'https://github.com/yudaocode/yudao-ui-admin-vue3',
|
message: 'github.com/yudaocode/yudao-ui-admin-vue3',
|
||||||
personal: 'Vue3 + element-plus',
|
personal: 'Vue3 + element-plus 管理后台',
|
||||||
time: new Date()
|
time: new Date('2025-02-03'),
|
||||||
},
|
color: '#409EFF'
|
||||||
{
|
|
||||||
name: 'yudao-ui-admin-vben',
|
|
||||||
icon: 'logos:vue',
|
|
||||||
message: 'https://github.com/yudaocode/yudao-ui-admin-vben',
|
|
||||||
personal: 'Vue3 + vben(antd)',
|
|
||||||
time: new Date()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'yudao-cloud',
|
|
||||||
icon: 'akar-icons:github',
|
|
||||||
message: 'https://github.com/YunaiV/yudao-cloud',
|
|
||||||
personal: 'Spring Cloud 微服务架构',
|
|
||||||
time: new Date()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'yudao-ui-mall-uniapp',
|
name: 'yudao-ui-mall-uniapp',
|
||||||
icon: 'logos:vue',
|
icon: 'icon-park-outline:mall-bag',
|
||||||
message: 'https://github.com/yudaocode/yudao-ui-admin-uniapp',
|
message: 'github.com/yudaocode/yudao-ui-mall-uniapp',
|
||||||
personal: 'Vue3 + uniapp',
|
personal: 'Vue3 + uniapp 商城手机端',
|
||||||
time: new Date()
|
time: new Date('2025-03-04'),
|
||||||
|
color: '#ff4d4f'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'yudao-ui-admin-vue2',
|
name: 'yudao-cloud',
|
||||||
icon: 'logos:vue',
|
icon: 'material-symbols:cloud-outline',
|
||||||
message: 'https://github.com/yudaocode/yudao-ui-admin-vue2',
|
message: 'github.com/YunaiV/yudao-cloud',
|
||||||
personal: 'Vue2 + element-ui',
|
personal: 'Spring Cloud 微服务架构',
|
||||||
time: new Date()
|
time: new Date('2025-04-05'),
|
||||||
|
color: '#1890ff'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'yudao-ui-admin-vben',
|
||||||
|
icon: 'devicon:antdesign',
|
||||||
|
message: 'github.com/yudaocode/yudao-ui-admin-vben',
|
||||||
|
personal: 'Vue3 + vben5(antd) 管理后台',
|
||||||
|
time: new Date('2025-05-06'),
|
||||||
|
color: '#e18525'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'yudao-ui-admin-uniapp',
|
||||||
|
icon: 'ant-design:mobile',
|
||||||
|
message: 'github.com/yudaocode/yudao-ui-admin-uniapp',
|
||||||
|
personal: 'Vue3 + uniapp 管理手机端',
|
||||||
|
time: new Date('2025-06-01'),
|
||||||
|
color: '#2979ff'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
projects = Object.assign(projects, data)
|
projects = Object.assign(projects, data)
|
||||||
|
|
@ -262,26 +279,26 @@ const getNotice = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
title: '系统支持 JDK 8/17/21,Vue 2/3',
|
title: '系统支持 JDK 8/17/21,Vue 2/3',
|
||||||
type: '通知',
|
type: '技术兼容性',
|
||||||
keys: ['通知', '8', '17', '21', '2', '3'],
|
keys: ['JDK', 'Vue'],
|
||||||
date: new Date()
|
date: new Date()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构',
|
title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构',
|
||||||
type: '公告',
|
type: '架构灵活性',
|
||||||
keys: ['公告', 'Boot', 'Cloud'],
|
keys: ['Boot', 'Cloud'],
|
||||||
date: new Date()
|
date: new Date()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '全部开源,个人与企业可 100% 直接使用,无需授权',
|
title: '全部开源,个人与企业可 100% 直接使用,无需授权',
|
||||||
type: '通知',
|
type: '开源免授权',
|
||||||
keys: ['通知', '无需授权'],
|
keys: ['无需授权'],
|
||||||
date: new Date()
|
date: new Date()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '国内使用最广泛的快速开发平台,超 300+ 人贡献',
|
title: '国内使用最广泛的快速开发平台,远超 10w+ 企业使用',
|
||||||
type: '公告',
|
type: '广泛企业认可',
|
||||||
keys: ['公告', '最广泛'],
|
keys: ['最广泛', '10w+'],
|
||||||
date: new Date()
|
date: new Date()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -294,34 +311,40 @@ let shortcut = reactive<Shortcut[]>([])
|
||||||
const getShortcut = async () => {
|
const getShortcut = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
name: 'Github',
|
name: '首页',
|
||||||
icon: 'akar-icons:github-fill',
|
icon: 'ion:home-outline',
|
||||||
url: 'github.io'
|
url: '/',
|
||||||
|
color: '#1fdaca'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Vue',
|
name: '商城中心',
|
||||||
icon: 'logos:vue',
|
icon: 'ep:shop',
|
||||||
url: 'vuejs.org'
|
url: '/mall/home',
|
||||||
|
color: '#ff6b6b'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Vite',
|
name: 'AI 大模型',
|
||||||
icon: 'vscode-icons:file-type-vite',
|
icon: 'tabler:ai',
|
||||||
url: 'https://vitejs.dev/'
|
url: '/ai/chat',
|
||||||
|
color: '#7c3aed'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Angular',
|
name: 'ERP 系统',
|
||||||
icon: 'logos:angular-icon',
|
icon: 'simple-icons:erpnext',
|
||||||
url: 'github.io'
|
url: '/erp/home',
|
||||||
|
color: '#3fb27f'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'React',
|
name: 'CRM 系统',
|
||||||
icon: 'logos:react',
|
icon: 'simple-icons:civicrm',
|
||||||
url: 'github.io'
|
url: '/crm/backlog',
|
||||||
|
color: '#4daf1bc9'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Webpack',
|
name: 'IoT 物联网',
|
||||||
icon: 'logos:webpack',
|
icon: 'fa-solid:hdd',
|
||||||
url: 'github.io'
|
url: '/iot/home',
|
||||||
|
color: '#1a73e8'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
shortcut = Object.assign(shortcut, data)
|
shortcut = Object.assign(shortcut, data)
|
||||||
|
|
@ -387,5 +410,13 @@ const getAllApi = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleProjectClick = (message: string) => {
|
||||||
|
window.open(`https://${message}`, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleShortcutClick = (url: string) => {
|
||||||
|
router.push(url)
|
||||||
|
}
|
||||||
|
|
||||||
getAllApi()
|
getAllApi()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export type Project = {
|
||||||
message: string
|
message: string
|
||||||
personal: string
|
personal: string
|
||||||
time: Date | number | string
|
time: Date | number | string
|
||||||
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Notice = {
|
export type Notice = {
|
||||||
|
|
@ -23,6 +24,7 @@ export type Shortcut = {
|
||||||
name: string
|
name: string
|
||||||
icon: string
|
icon: string
|
||||||
url: string
|
url: string
|
||||||
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RadarData = {
|
export type RadarData = {
|
||||||
|
|
|
||||||
|
|
@ -312,8 +312,8 @@ const doSocialLogin = async (type: number) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 计算 redirectUri
|
// 计算 redirectUri
|
||||||
// tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。
|
// 注意: type、redirect 需要先 encode 一次,否则钉钉回调会丢失。
|
||||||
// 配合 Login/SocialLogin.vue#getUrlValue() 使用
|
// 配合 social-login.vue#getUrlValue() 使用
|
||||||
const redirectUri =
|
const redirectUri =
|
||||||
location.origin +
|
location.origin +
|
||||||
'/social-login?' +
|
'/social-login?' +
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- TODO @芋艿:可优化,对标 vben 版本 -->
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-card class="user w-1/3" shadow="hover">
|
<el-card class="user w-1/3" shadow="hover">
|
||||||
<template #header>
|
<template #header>
|
||||||
|
|
@ -6,18 +7,13 @@
|
||||||
<span>{{ t('profile.user.title') }}</span>
|
<span>{{ t('profile.user.title') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<ProfileUser />
|
<ProfileUser ref="profileUserRef" />
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card class="user ml-3 w-2/3" shadow="hover">
|
<el-card class="user ml-3 w-2/3" shadow="hover">
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<span>{{ t('profile.info.title') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div>
|
<div>
|
||||||
<el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
|
<el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
|
||||||
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
||||||
<BasicInfo />
|
<BasicInfo @success="handleBasicInfoSuccess" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
|
<el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
|
||||||
<ResetPwd />
|
<ResetPwd />
|
||||||
|
|
@ -36,6 +32,12 @@ import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
defineOptions({ name: 'Profile' })
|
defineOptions({ name: 'Profile' })
|
||||||
const activeName = ref('basicInfo')
|
const activeName = ref('basicInfo')
|
||||||
|
const profileUserRef = ref()
|
||||||
|
|
||||||
|
// 处理基本信息更新成功
|
||||||
|
const handleBasicInfoSuccess = async () => {
|
||||||
|
await profileUserRef.value?.refresh()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.user {
|
.user {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,12 @@ defineOptions({ name: 'BasicInfo' })
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 定义事件
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
// 表单校验
|
// 表单校验
|
||||||
const rules = reactive<FormRules>({
|
const rules = reactive<FormRules>({
|
||||||
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
||||||
|
|
@ -82,6 +88,8 @@ const submit = () => {
|
||||||
message.success(t('common.updateSuccess'))
|
message.success(t('common.updateSuccess'))
|
||||||
const profile = await init()
|
const profile = await init()
|
||||||
userStore.setUserNicknameAction(profile.nickname)
|
userStore.setUserNicknameAction(profile.nickname)
|
||||||
|
// 发送成功事件
|
||||||
|
emit('success')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,12 @@ const getUserInfo = async () => {
|
||||||
const users = await getUserProfile()
|
const users = await getUserProfile()
|
||||||
userInfo.value = users
|
userInfo.value = users
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 暴露刷新方法
|
||||||
|
defineExpose({
|
||||||
|
refresh: getUserInfo
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getUserInfo()
|
await getUserInfo()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,13 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { uploadAvatar } from '@/api/system/user/profile'
|
import { updateUserProfile } from '@/api/system/user/profile'
|
||||||
import { CropperAvatar } from '@/components/Cropper'
|
import { CropperAvatar } from '@/components/Cropper'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||||
|
import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
||||||
|
|
||||||
|
// TODO @芋艿:合并到 ProfileUser 组件中,更简洁一点
|
||||||
defineOptions({ name: 'UserAvatar' })
|
defineOptions({ name: 'UserAvatar' })
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
|
|
@ -25,12 +27,18 @@ defineProps({
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
|
||||||
const cropperRef = ref()
|
const cropperRef = ref()
|
||||||
const handelUpload = async ({ data }) => {
|
const handelUpload = async ({ data }) => {
|
||||||
const res = await uploadAvatar({ avatarFile: data })
|
const { httpRequest } = useUpload()
|
||||||
|
const avatar = ((await httpRequest({
|
||||||
|
file: data,
|
||||||
|
filename: 'avatar.png',
|
||||||
|
} as UploadRequestOptions)) as unknown as { data: string }).data
|
||||||
|
await updateUserProfile({ avatar })
|
||||||
|
|
||||||
|
// 关闭弹窗,并更新 userStore
|
||||||
cropperRef.value.close()
|
cropperRef.value.close()
|
||||||
userStore.setUserAvatarAction(res.data)
|
await userStore.setUserAvatarAction(avatar)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,8 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
(error) => {
|
(error) => {
|
||||||
message.alert(`对话异常! ${error}`)
|
message.alert(`对话异常! ${error}`)
|
||||||
stopStream()
|
stopStream()
|
||||||
|
// 需要抛出异常,禁止重试
|
||||||
|
throw error
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
stopStream()
|
stopStream()
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ const submit = (data: AiMindMapGenerateReqVO) => {
|
||||||
onError(err) {
|
onError(err) {
|
||||||
console.error('生成思维导图失败', err)
|
console.error('生成思维导图失败', err)
|
||||||
stopStream()
|
stopStream()
|
||||||
|
// 需要抛出异常,禁止重试
|
||||||
|
throw error
|
||||||
},
|
},
|
||||||
ctrl: ctrl.value
|
ctrl: ctrl.value
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-width="120px"
|
label-width="130px"
|
||||||
v-loading="formLoading"
|
v-loading="formLoading"
|
||||||
>
|
>
|
||||||
<el-form-item label="所属平台" prop="platform">
|
<el-form-item label="所属平台" prop="platform">
|
||||||
|
|
@ -146,7 +146,10 @@ const formRules = reactive({
|
||||||
platform: [{ required: true, message: '所属平台不能为空', trigger: 'blur' }],
|
platform: [{ required: true, message: '所属平台不能为空', trigger: 'blur' }],
|
||||||
type: [{ required: true, message: '模型类型不能为空', trigger: 'blur' }],
|
type: [{ required: true, message: '模型类型不能为空', trigger: 'blur' }],
|
||||||
sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
|
sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||||
|
temperature: [{ required: true, message: '温度参数不能为空', trigger: 'blur' }],
|
||||||
|
maxTokens: [{ required: true, message: '回复数 Token 数不能为空', trigger: 'blur' }],
|
||||||
|
maxContexts: [{ required: true, message: '上下文数量不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export const AiPlatformEnum = {
|
||||||
DEEP_SEEK: 'DeepSeek', // DeepSeek
|
DEEP_SEEK: 'DeepSeek', // DeepSeek
|
||||||
ZHI_PU: 'ZhiPu', // 智谱 AI
|
ZHI_PU: 'ZhiPu', // 智谱 AI
|
||||||
XING_HUO: 'XingHuo', // 讯飞
|
XING_HUO: 'XingHuo', // 讯飞
|
||||||
|
SiliconFlow: 'SiliconFlow', // 硅基流动
|
||||||
OPENAI: 'OpenAI',
|
OPENAI: 'OpenAI',
|
||||||
Ollama: 'Ollama',
|
Ollama: 'Ollama',
|
||||||
STABLE_DIFFUSION: 'StableDiffusion', // Stability AI
|
STABLE_DIFFUSION: 'StableDiffusion', // Stability AI
|
||||||
|
|
@ -44,6 +45,10 @@ export const OtherPlatformEnum: ImageModelVO[] = [
|
||||||
{
|
{
|
||||||
key: AiPlatformEnum.ZHI_PU,
|
key: AiPlatformEnum.ZHI_PU,
|
||||||
name: '智谱 AI'
|
name: '智谱 AI'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: AiPlatformEnum.SiliconFlow,
|
||||||
|
name: '硅基流动'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<el-form ref="formRef" :model="modelData" :rules="formRules" label-width="120px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="流程标识" prop="code">
|
||||||
|
<el-input v-model="modelData.code" placeholder="请输入流程标识" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="流程名称" prop="name">
|
||||||
|
<el-input v-model="modelData.name" placeholder="请输入流程名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="modelData.status" placeholder="请选择状态">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input v-model="modelData.remark" :rows="2" type="textarea" placeholder="请输入备注" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { FormRules } from 'element-plus'
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
|
||||||
|
const modelData = defineModel<any>()
|
||||||
|
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const formRules = reactive<FormRules>({
|
||||||
|
code: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||||
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
const validate = async () => {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
validate
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
<template>
|
||||||
|
<div class="relative" style="width: 100%; height: 700px">
|
||||||
|
<Tinyflow
|
||||||
|
v-if="workflowData"
|
||||||
|
ref="tinyflowRef"
|
||||||
|
:className="'custom-class'"
|
||||||
|
:style="{ width: '100%', height: '100%' }"
|
||||||
|
:data="workflowData"
|
||||||
|
:provider="provider"
|
||||||
|
/>
|
||||||
|
<div class="absolute top-30px right-30px">
|
||||||
|
<el-button @click="testWorkflowModel" type="primary" v-hasPermi="['ai:workflow:test']">
|
||||||
|
测试
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 测试窗口 -->
|
||||||
|
<el-drawer v-model="showTestDrawer" title="工作流测试" :modal="false">
|
||||||
|
<fieldset>
|
||||||
|
<legend class="ml-15px"><h3>运行参数配置</h3></legend>
|
||||||
|
<div class="p-20px">
|
||||||
|
<div
|
||||||
|
class="flex justify-around mb-10px"
|
||||||
|
v-for="(param, index) in params4Test"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<el-select class="w-200px!" v-model="param.key" placeholder="参数名">
|
||||||
|
<el-option
|
||||||
|
v-for="(value, key) in paramsOfStartNode"
|
||||||
|
:key="key"
|
||||||
|
:label="value?.description || key"
|
||||||
|
:value="key"
|
||||||
|
:disabled="!!value?.disabled"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input class="w-200px!" v-model="param.value" placeholder="参数值" />
|
||||||
|
<el-button type="danger" plain :icon="Delete" circle @click="removeParam(index)" />
|
||||||
|
</div>
|
||||||
|
<!-- TODO @lesan:是不是不用添加和删除参数,直接把必填和选填列出来,然后加上参数校验? -->
|
||||||
|
<el-button type="primary" plain @click="addParam">添加参数</el-button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="mt-20px bg-#f8f9fa">
|
||||||
|
<legend class="ml-15px"><h3>运行结果</h3></legend>
|
||||||
|
<div class="p-20px">
|
||||||
|
<div v-if="loading"> <el-text type="primary">执行中...</el-text></div>
|
||||||
|
<div v-else-if="error">
|
||||||
|
<el-text type="danger">{{ error }}</el-text>
|
||||||
|
</div>
|
||||||
|
<pre v-else-if="testResult" class="result-content"
|
||||||
|
>{{ JSON.stringify(testResult, null, 2) }}
|
||||||
|
</pre>
|
||||||
|
<div v-else> <el-text type="info">点击运行查看结果</el-text> </div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<el-button class="mt-20px w-100%" size="large" type="success" @click="goRun">
|
||||||
|
运行流程
|
||||||
|
</el-button>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Tinyflow from '@/components/Tinyflow/Tinyflow.vue'
|
||||||
|
import * as WorkflowApi from '@/api/ai/workflow'
|
||||||
|
// TODO @lesan:要不使用 ICon 哪个组件哈
|
||||||
|
import { Delete } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
provider: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const tinyflowRef = ref()
|
||||||
|
const workflowData = inject('workflowData') as Ref
|
||||||
|
const showTestDrawer = ref(false)
|
||||||
|
const params4Test = ref([])
|
||||||
|
const paramsOfStartNode = ref({})
|
||||||
|
const testResult = ref(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref(null)
|
||||||
|
|
||||||
|
/** 展示工作流测试抽屉 */
|
||||||
|
const testWorkflowModel = () => {
|
||||||
|
showTestDrawer.value = !showTestDrawer.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 运行流程 */
|
||||||
|
const goRun = async () => {
|
||||||
|
try {
|
||||||
|
const val = tinyflowRef.value.getData()
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
testResult.value = null
|
||||||
|
/// 查找start节点
|
||||||
|
const startNode = getStartNode()
|
||||||
|
|
||||||
|
// 获取参数定义
|
||||||
|
const parameters = startNode.data?.parameters || []
|
||||||
|
const paramDefinitions = {}
|
||||||
|
parameters.forEach((param) => {
|
||||||
|
paramDefinitions[param.name] = param.dataType
|
||||||
|
})
|
||||||
|
|
||||||
|
// 参数类型转换
|
||||||
|
const convertedParams = {}
|
||||||
|
for (const { key, value } of params4Test.value) {
|
||||||
|
const paramKey = key.trim()
|
||||||
|
if (!paramKey) continue
|
||||||
|
|
||||||
|
let dataType = paramDefinitions[paramKey]
|
||||||
|
if (!dataType) {
|
||||||
|
dataType = 'String'
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
convertedParams[paramKey] = convertParamValue(value, dataType)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`参数 ${paramKey} 转换失败: ${e.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
graph: JSON.stringify(val),
|
||||||
|
params: convertedParams
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await WorkflowApi.testWorkflow(data)
|
||||||
|
testResult.value = response
|
||||||
|
} catch (err) {
|
||||||
|
error.value = err.response?.data?.message || '运行失败,请检查参数和网络连接'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听测试抽屉的开启,获取开始节点参数列表 */
|
||||||
|
watch(showTestDrawer, (value) => {
|
||||||
|
if (!value) return
|
||||||
|
|
||||||
|
/// 查找start节点
|
||||||
|
const startNode = getStartNode()
|
||||||
|
|
||||||
|
// 获取参数定义
|
||||||
|
const parameters = startNode.data?.parameters || []
|
||||||
|
const paramDefinitions = {}
|
||||||
|
|
||||||
|
// 加入参数选项方便用户添加非必须参数
|
||||||
|
parameters.forEach((param) => {
|
||||||
|
paramDefinitions[param.name] = param
|
||||||
|
})
|
||||||
|
|
||||||
|
function mergeIfRequiredButNotSet(target) {
|
||||||
|
let needPushList = []
|
||||||
|
for (let key in paramDefinitions) {
|
||||||
|
let param = paramDefinitions[key]
|
||||||
|
|
||||||
|
if (param.required) {
|
||||||
|
let item = target.find((item) => item.key === key)
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
needPushList.push({ key: param.name, value: param.defaultValue || '' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.push(...needPushList)
|
||||||
|
}
|
||||||
|
// 自动装载需必填的参数
|
||||||
|
mergeIfRequiredButNotSet(params4Test.value)
|
||||||
|
|
||||||
|
paramsOfStartNode.value = paramDefinitions
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 获取开始节点 */
|
||||||
|
const getStartNode = () => {
|
||||||
|
const val = tinyflowRef.value.getData()
|
||||||
|
const startNode = val.nodes.find((node) => node.type === 'startNode')
|
||||||
|
if (!startNode) {
|
||||||
|
throw new Error('流程缺少开始节点')
|
||||||
|
}
|
||||||
|
return startNode
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加参数项 */
|
||||||
|
const addParam = () => {
|
||||||
|
params4Test.value.push({ key: '', value: '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除参数项 */
|
||||||
|
const removeParam = (index) => {
|
||||||
|
params4Test.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 类型转换函数 */
|
||||||
|
const convertParamValue = (value, dataType) => {
|
||||||
|
if (value === '') return null // 空值处理
|
||||||
|
|
||||||
|
switch (dataType) {
|
||||||
|
case 'String':
|
||||||
|
return String(value)
|
||||||
|
case 'Number':
|
||||||
|
const num = Number(value)
|
||||||
|
if (isNaN(num)) throw new Error('非数字格式')
|
||||||
|
return num
|
||||||
|
case 'Boolean':
|
||||||
|
if (value.toLowerCase() === 'true') return true
|
||||||
|
if (value.toLowerCase() === 'false') return false
|
||||||
|
throw new Error('必须为 true/false')
|
||||||
|
case 'Object':
|
||||||
|
case 'Array':
|
||||||
|
try {
|
||||||
|
return JSON.parse(value)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`JSON格式错误: ${e.message}`)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`不支持的类型: ${dataType}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
const validate = async () => {
|
||||||
|
try {
|
||||||
|
// 获取最新的流程数据
|
||||||
|
if (!workflowData.value) {
|
||||||
|
throw new Error('请设计流程')
|
||||||
|
}
|
||||||
|
workflowData.value = tinyflowRef.value.getData()
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
validate
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.result-content {
|
||||||
|
background: white;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
font-family: Monaco, Consolas, monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<!-- 头部导航栏 -->
|
||||||
|
<div
|
||||||
|
class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
|
||||||
|
>
|
||||||
|
<!-- 左侧标题 -->
|
||||||
|
<div class="w-200px flex items-center overflow-hidden">
|
||||||
|
<Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
|
||||||
|
<span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
|
||||||
|
{{ formData.name || '创建流程' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤条 -->
|
||||||
|
<div class="flex-1 flex items-center justify-center h-full">
|
||||||
|
<div class="w-400px flex items-center justify-between h-full">
|
||||||
|
<div
|
||||||
|
v-for="(step, index) in steps"
|
||||||
|
:key="index"
|
||||||
|
class="flex items-center cursor-pointer mx-15px relative h-full"
|
||||||
|
:class="[
|
||||||
|
currentStep === index
|
||||||
|
? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
|
||||||
|
: 'text-gray-500'
|
||||||
|
]"
|
||||||
|
@click="handleStepClick(index)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
|
||||||
|
:class="[
|
||||||
|
currentStep === index
|
||||||
|
? 'bg-[#3473ff] text-white border-[#3473ff]'
|
||||||
|
: 'border-gray-300 bg-white text-gray-500'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ index + 1 }}
|
||||||
|
</div>
|
||||||
|
<span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧按钮 -->
|
||||||
|
<div class="w-200px flex items-center justify-end gap-2">
|
||||||
|
<el-button type="primary" @click="handleSave"> 保 存 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主体内容 -->
|
||||||
|
<div class="mt-50px">
|
||||||
|
<!-- 第一步:基本信息 -->
|
||||||
|
<div v-if="currentStep === 0" class="mx-auto w-560px">
|
||||||
|
<BasicInfo v-model="formData" ref="basicInfoRef" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二步:工作流设计 -->
|
||||||
|
<WorkflowDesign
|
||||||
|
v-if="currentStep === 1"
|
||||||
|
v-model="formData"
|
||||||
|
:provider="llmProvider"
|
||||||
|
ref="workflowDesignRef"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import * as WorkflowApi from '@/api/ai/workflow'
|
||||||
|
import BasicInfo from './BasicInfo.vue'
|
||||||
|
import WorkflowDesign from './WorkflowDesign.vue'
|
||||||
|
import { ModelApi } from '@/api/ai/model/model'
|
||||||
|
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const { delView } = useTagsViewStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const basicInfoRef = ref()
|
||||||
|
const workflowDesignRef = ref()
|
||||||
|
|
||||||
|
const validateBasic = async () => {
|
||||||
|
await basicInfoRef.value?.validate()
|
||||||
|
}
|
||||||
|
const validateWorkflow = async () => {
|
||||||
|
await workflowDesignRef.value?.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentStep = ref(-1)
|
||||||
|
const steps = [
|
||||||
|
{ title: '基本信息', validator: validateBasic },
|
||||||
|
{ title: '工作流设计', validator: validateWorkflow }
|
||||||
|
]
|
||||||
|
|
||||||
|
const formData: any = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
code: '',
|
||||||
|
remark: '',
|
||||||
|
graph: '',
|
||||||
|
status: CommonStatusEnum.ENABLE
|
||||||
|
})
|
||||||
|
const llmProvider = ref<any>([])
|
||||||
|
const workflowData = ref<any>({})
|
||||||
|
provide('workflowData', workflowData)
|
||||||
|
|
||||||
|
/** 初始化数据 */
|
||||||
|
const actionType = route.params.type as string
|
||||||
|
const initData = async () => {
|
||||||
|
// 编辑情况下,需要加载工作流配置
|
||||||
|
if (actionType === 'update') {
|
||||||
|
const workflowId = route.params.id as string
|
||||||
|
formData.value = await WorkflowApi.getWorkflow(workflowId)
|
||||||
|
workflowData.value = JSON.parse(formData.value.graph)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载模型列表
|
||||||
|
const models = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
|
||||||
|
llmProvider.value = {
|
||||||
|
llm: () =>
|
||||||
|
models.map(({ id, name }) => ({
|
||||||
|
value: id,
|
||||||
|
label: name
|
||||||
|
})),
|
||||||
|
knowledge: () => [],
|
||||||
|
internal: () => []
|
||||||
|
}
|
||||||
|
// TODO @lesan:知识库(可以看下 knowledge)
|
||||||
|
// TODO @lesan:搜索引擎(这个之前有个 pr 搞了,,,可能来接下)
|
||||||
|
|
||||||
|
// 设置当前步骤
|
||||||
|
currentStep.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验所有步骤数据是否完整 */
|
||||||
|
const validateAllSteps = async () => {
|
||||||
|
try {
|
||||||
|
// 基本信息校验
|
||||||
|
try {
|
||||||
|
await validateBasic()
|
||||||
|
} catch (error) {
|
||||||
|
currentStep.value = 0
|
||||||
|
throw new Error('请完善基本信息')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工作流设计校验
|
||||||
|
try {
|
||||||
|
await validateWorkflow()
|
||||||
|
} catch (error) {
|
||||||
|
currentStep.value = 1
|
||||||
|
throw new Error('请完善工作流信息')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存操作 */
|
||||||
|
const handleSave = async () => {
|
||||||
|
try {
|
||||||
|
// 保存前校验所有步骤的数据
|
||||||
|
await validateAllSteps()
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
const data = {
|
||||||
|
...formData.value,
|
||||||
|
graph: JSON.stringify(workflowData.value)
|
||||||
|
}
|
||||||
|
if (actionType === 'update') {
|
||||||
|
await WorkflowApi.updateWorkflow(data)
|
||||||
|
} else {
|
||||||
|
await WorkflowApi.createWorkflow(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存成功,提示并跳转到列表页
|
||||||
|
message.success('保存成功')
|
||||||
|
delView(unref(router.currentRoute))
|
||||||
|
await router.push({ name: 'AiWorkflow' })
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('保存失败:', error)
|
||||||
|
message.warning(error.message || '请完善所有步骤的必填信息')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 步骤切换处理 */
|
||||||
|
const handleStepClick = async (index: number) => {
|
||||||
|
try {
|
||||||
|
if (index !== 0) {
|
||||||
|
await validateBasic()
|
||||||
|
}
|
||||||
|
if (index !== 1) {
|
||||||
|
await validateWorkflow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换步骤
|
||||||
|
currentStep.value = index
|
||||||
|
} catch (error) {
|
||||||
|
console.error('步骤切换失败:', error)
|
||||||
|
message.warning('请先完善当前步骤必填信息')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回列表页 */
|
||||||
|
const handleBack = () => {
|
||||||
|
// 先删除当前页签
|
||||||
|
delView(unref(router.currentRoute))
|
||||||
|
// 跳转到列表页
|
||||||
|
router.push({ name: 'AiWorkflow' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(async () => {
|
||||||
|
await initData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- TODO @lesan:可以用 cursor 搞成 unocss 哈 -->
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
color: #3473ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
background-color: #3473ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-primary {
|
||||||
|
border-color: #3473ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,193 @@
|
||||||
|
<template>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="流程标识" prop="code">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.code"
|
||||||
|
placeholder="请输入流程标识"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="流程名称" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.name"
|
||||||
|
placeholder="请输入流程名称"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="状态" clearable class="!w-240px">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建时间" prop="createTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.createTime"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
type="daterange"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||||
|
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['ai:workflow:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" /> 新增
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
<el-table-column label="编号" align="center" prop="id" />
|
||||||
|
<el-table-column label="流程标识" align="center" prop="code" />
|
||||||
|
<el-table-column label="流程名称" align="center" prop="name" />
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column label="备注" align="center" prop="remark" />
|
||||||
|
<el-table-column label="状态" align="center" key="status">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['ai:workflow:update']"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['ai:workflow:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 添加或修改工作流对话框 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import * as WorkflowApi from '@/api/ai/workflow'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
|
||||||
|
defineOptions({ name: 'AiWorkflow' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const { push } = useRouter() // 路由
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref([]) // 列表的数据
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
status: undefined,
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await WorkflowApi.getWorkflowPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await WorkflowApi.deleteWorkflow(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const openForm = async (type: string, id?: number) => {
|
||||||
|
if (type === 'create') {
|
||||||
|
await push({ name: 'AiWorkflowCreate' })
|
||||||
|
} else {
|
||||||
|
await push({
|
||||||
|
name: 'AiWorkflowUpdate',
|
||||||
|
params: { id, type }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -57,9 +57,11 @@ const submit = (data: WriteVO) => {
|
||||||
},
|
},
|
||||||
ctrl: abortController.value,
|
ctrl: abortController.value,
|
||||||
onClose: stopStream,
|
onClose: stopStream,
|
||||||
onError: (...err) => {
|
onError: (error) => {
|
||||||
console.error('写作异常', ...err)
|
console.error('写作异常', error)
|
||||||
stopStream()
|
stopStream()
|
||||||
|
// 需要抛出异常,禁止重试
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,10 +97,23 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="可见范围" prop="startUserIds" min-width="150">
|
<el-table-column label="可见范围" prop="startUserIds" min-width="150">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-text v-if="!row.startUsers?.length"> 全部可见 </el-text>
|
<el-text v-if="!row.startUsers?.length && !row.startDepts?.length"> 全部可见 </el-text>
|
||||||
<el-text v-else-if="row.startUsers.length === 1">
|
<el-text v-else-if="row.startUsers.length === 1">
|
||||||
{{ row.startUsers[0].nickname }}
|
{{ row.startUsers[0].nickname }}
|
||||||
</el-text>
|
</el-text>
|
||||||
|
<el-text v-else-if="row.startDepts?.length === 1">
|
||||||
|
{{ row.startDepts[0].name }}
|
||||||
|
</el-text>
|
||||||
|
<el-text v-else-if="row.startDepts?.length > 1">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
placement="top"
|
||||||
|
:content="row.startDepts.map((dept: any) => dept.name).join('、')"
|
||||||
|
>
|
||||||
|
{{ row.startDepts[0].name }}等 {{ row.startDepts.length }} 个部门可见
|
||||||
|
</el-tooltip>
|
||||||
|
</el-text>
|
||||||
<el-text v-else>
|
<el-text v-else>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
|
|
@ -436,7 +449,6 @@ const handleChangeState = async (row: any) => {
|
||||||
try {
|
try {
|
||||||
// 修改状态的二次确认
|
// 修改状态的二次确认
|
||||||
const id = row.id
|
const id = row.id
|
||||||
debugger
|
|
||||||
const statusState = state === 1 ? '停用' : '启用'
|
const statusState = state === 1 ? '停用' : '启用'
|
||||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||||
await message.confirm(content)
|
await message.confirm(content)
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@
|
||||||
>
|
>
|
||||||
<el-option label="全员" :value="0" />
|
<el-option label="全员" :value="0" />
|
||||||
<el-option label="指定人员" :value="1" />
|
<el-option label="指定人员" :value="1" />
|
||||||
|
<el-option label="指定部门" :value="2" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
|
<div v-if="modelData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
|
||||||
<div
|
<div
|
||||||
|
|
@ -99,6 +100,24 @@
|
||||||
<Icon icon="ep:plus" /> 选择人员
|
<Icon icon="ep:plus" /> 选择人员
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="modelData.startUserType === 2" class="mt-2 flex flex-wrap gap-2">
|
||||||
|
<div
|
||||||
|
v-for="dept in selectedStartDepts"
|
||||||
|
:key="dept.id"
|
||||||
|
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:office-building" class="!m-5px text-20px" />
|
||||||
|
{{ dept.name }}
|
||||||
|
<Icon
|
||||||
|
icon="ep:close"
|
||||||
|
class="ml-2 cursor-pointer hover:text-red-500"
|
||||||
|
@click="handleRemoveStartDept(dept)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" link @click="openStartDeptSelect">
|
||||||
|
<Icon icon="ep:plus" /> 选择部门
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="流程管理员" prop="managerUserIds" class="mb-20px">
|
<el-form-item label="流程管理员" prop="managerUserIds" class="mb-20px">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
|
|
@ -127,11 +146,19 @@
|
||||||
|
|
||||||
<!-- 用户选择弹窗 -->
|
<!-- 用户选择弹窗 -->
|
||||||
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
|
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
|
||||||
|
<!-- 部门选择弹窗 -->
|
||||||
|
<DeptSelectForm
|
||||||
|
ref="deptSelectFormRef"
|
||||||
|
:multiple="true"
|
||||||
|
:check-strictly="true"
|
||||||
|
@confirm="handleDeptSelectConfirm"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||||
import { UserVO } from '@/api/system/user'
|
import { UserVO } from '@/api/system/user'
|
||||||
|
import { DeptVO } from '@/api/system/dept'
|
||||||
import { CategoryVO } from '@/api/bpm/category'
|
import { CategoryVO } from '@/api/bpm/category'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -142,13 +169,19 @@ const props = defineProps({
|
||||||
userList: {
|
userList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
deptList: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const selectedStartUsers = ref<UserVO[]>([])
|
const selectedStartUsers = ref<UserVO[]>([])
|
||||||
|
const selectedStartDepts = ref<DeptVO[]>([])
|
||||||
const selectedManagerUsers = ref<UserVO[]>([])
|
const selectedManagerUsers = ref<UserVO[]>([])
|
||||||
const userSelectFormRef = ref()
|
const userSelectFormRef = ref()
|
||||||
|
const deptSelectFormRef = ref()
|
||||||
const currentSelectType = ref<'start' | 'manager'>('start')
|
const currentSelectType = ref<'start' | 'manager'>('start')
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
|
|
@ -174,6 +207,13 @@ watch(
|
||||||
} else {
|
} else {
|
||||||
selectedStartUsers.value = []
|
selectedStartUsers.value = []
|
||||||
}
|
}
|
||||||
|
if (newVal.startDeptIds?.length) {
|
||||||
|
selectedStartDepts.value = props.deptList.filter((dept: DeptVO) =>
|
||||||
|
newVal.startDeptIds.includes(dept.id)
|
||||||
|
) as DeptVO[]
|
||||||
|
} else {
|
||||||
|
selectedStartDepts.value = []
|
||||||
|
}
|
||||||
if (newVal.managerUserIds?.length) {
|
if (newVal.managerUserIds?.length) {
|
||||||
selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
|
selectedManagerUsers.value = props.userList.filter((user: UserVO) =>
|
||||||
newVal.managerUserIds.includes(user.id)
|
newVal.managerUserIds.includes(user.id)
|
||||||
|
|
@ -193,6 +233,11 @@ const openStartUserSelect = () => {
|
||||||
userSelectFormRef.value.open(0, selectedStartUsers.value)
|
userSelectFormRef.value.open(0, selectedStartUsers.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 打开部门选择 */
|
||||||
|
const openStartDeptSelect = () => {
|
||||||
|
deptSelectFormRef.value.open(selectedStartDepts.value)
|
||||||
|
}
|
||||||
|
|
||||||
/** 打开管理员选择 */
|
/** 打开管理员选择 */
|
||||||
const openManagerUserSelect = () => {
|
const openManagerUserSelect = () => {
|
||||||
currentSelectType.value = 'manager'
|
currentSelectType.value = 'manager'
|
||||||
|
|
@ -214,9 +259,28 @@ const handleUserSelectConfirm = (_, users: UserVO[]) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 处理部门选择确认 */
|
||||||
|
const handleDeptSelectConfirm = (depts: DeptVO[]) => {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
startDeptIds: depts.map((d) => d.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 处理发起人类型变化 */
|
/** 处理发起人类型变化 */
|
||||||
const handleStartUserTypeChange = (value: number) => {
|
const handleStartUserTypeChange = (value: number) => {
|
||||||
if (value !== 1) {
|
if (value === 0) {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
startUserIds: [],
|
||||||
|
startDeptIds: []
|
||||||
|
}
|
||||||
|
} else if (value === 1) {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
startDeptIds: []
|
||||||
|
}
|
||||||
|
} else if (value === 2) {
|
||||||
modelData.value = {
|
modelData.value = {
|
||||||
...modelData.value,
|
...modelData.value,
|
||||||
startUserIds: []
|
startUserIds: []
|
||||||
|
|
@ -232,6 +296,14 @@ const handleRemoveStartUser = (user: UserVO) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 移除部门 */
|
||||||
|
const handleRemoveStartDept = (dept: DeptVO) => {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
startDeptIds: modelData.value.startDeptIds.filter((id: number) => id !== dept.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 移除管理员 */
|
/** 移除管理员 */
|
||||||
const handleRemoveManagerUser = (user: UserVO) => {
|
const handleRemoveManagerUser = (user: UserVO) => {
|
||||||
modelData.value = {
|
modelData.value = {
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="processBeforeTriggerEnable"
|
v-model="processBeforeTriggerEnable"
|
||||||
@change="handlePreProcessNotifyEnableChange"
|
@change="handleProcessBeforeTriggerEnableChange"
|
||||||
/>
|
/>
|
||||||
<div class="ml-80px">流程启动后通知</div>
|
<div class="ml-80px">流程启动后通知</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -168,9 +168,9 @@
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="processAfterTriggerEnable"
|
v-model="processAfterTriggerEnable"
|
||||||
@change="handlePostProcessNotifyEnableChange"
|
@change="handleProcessAfterTriggerEnableChange"
|
||||||
/>
|
/>
|
||||||
<div class="ml-80px">流程启动后通知</div>
|
<div class="ml-80px">流程结束后通知</div>
|
||||||
</div>
|
</div>
|
||||||
<HttpRequestSetting
|
<HttpRequestSetting
|
||||||
v-if="processAfterTriggerEnable"
|
v-if="processAfterTriggerEnable"
|
||||||
|
|
@ -180,6 +180,46 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item class="mb-20px">
|
||||||
|
<template #label>
|
||||||
|
<el-text size="large" tag="b">任务前置通知</el-text>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col w-100%">
|
||||||
|
<div class="flex">
|
||||||
|
<el-switch
|
||||||
|
v-model="taskBeforeTriggerEnable"
|
||||||
|
@change="handleTaskBeforeTriggerEnableChange"
|
||||||
|
/>
|
||||||
|
<div class="ml-80px">任务执行时通知</div>
|
||||||
|
</div>
|
||||||
|
<HttpRequestSetting
|
||||||
|
v-if="taskBeforeTriggerEnable"
|
||||||
|
v-model:setting="modelData.taskBeforeTriggerSetting"
|
||||||
|
:responseEnable="true"
|
||||||
|
:formItemPrefix="'taskBeforeTriggerSetting'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="mb-20px">
|
||||||
|
<template #label>
|
||||||
|
<el-text size="large" tag="b">任务后置通知</el-text>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col w-100%">
|
||||||
|
<div class="flex">
|
||||||
|
<el-switch
|
||||||
|
v-model="taskAfterTriggerEnable"
|
||||||
|
@change="handleTaskAfterTriggerEnableChange"
|
||||||
|
/>
|
||||||
|
<div class="ml-80px">任务结束后通知</div>
|
||||||
|
</div>
|
||||||
|
<HttpRequestSetting
|
||||||
|
v-if="taskAfterTriggerEnable"
|
||||||
|
v-model:setting="modelData.taskAfterTriggerSetting"
|
||||||
|
:responseEnable="true"
|
||||||
|
:formItemPrefix="'taskAfterTriggerSetting'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -248,7 +288,7 @@ const numberExample = computed(() => {
|
||||||
|
|
||||||
/** 是否开启流程前置通知 */
|
/** 是否开启流程前置通知 */
|
||||||
const processBeforeTriggerEnable = ref(false)
|
const processBeforeTriggerEnable = ref(false)
|
||||||
const handlePreProcessNotifyEnableChange = (val: boolean | string | number) => {
|
const handleProcessBeforeTriggerEnableChange = (val: boolean | string | number) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
modelData.value.processBeforeTriggerSetting = {
|
modelData.value.processBeforeTriggerSetting = {
|
||||||
url: '',
|
url: '',
|
||||||
|
|
@ -263,7 +303,7 @@ const handlePreProcessNotifyEnableChange = (val: boolean | string | number) => {
|
||||||
|
|
||||||
/** 是否开启流程后置通知 */
|
/** 是否开启流程后置通知 */
|
||||||
const processAfterTriggerEnable = ref(false)
|
const processAfterTriggerEnable = ref(false)
|
||||||
const handlePostProcessNotifyEnableChange = (val: boolean | string | number) => {
|
const handleProcessAfterTriggerEnableChange = (val: boolean | string | number) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
modelData.value.processAfterTriggerSetting = {
|
modelData.value.processAfterTriggerSetting = {
|
||||||
url: '',
|
url: '',
|
||||||
|
|
@ -276,6 +316,36 @@ const handlePostProcessNotifyEnableChange = (val: boolean | string | number) =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 是否开启任务前置通知 */
|
||||||
|
const taskBeforeTriggerEnable = ref(false)
|
||||||
|
const handleTaskBeforeTriggerEnableChange = (val: boolean | string | number) => {
|
||||||
|
if (val) {
|
||||||
|
modelData.value.taskBeforeTriggerSetting = {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modelData.value.taskBeforeTriggerSetting = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否开启任务后置通知 */
|
||||||
|
const taskAfterTriggerEnable = ref(false)
|
||||||
|
const handleTaskAfterTriggerEnableChange = (val: boolean | string | number) => {
|
||||||
|
if (val) {
|
||||||
|
modelData.value.taskAfterTriggerSetting = {
|
||||||
|
url: '',
|
||||||
|
header: [],
|
||||||
|
body: [],
|
||||||
|
response: []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modelData.value.taskAfterTriggerSetting = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 表单选项 */
|
/** 表单选项 */
|
||||||
const formField = ref<Array<{ field: string; title: string }>>([])
|
const formField = ref<Array<{ field: string; title: string }>>([])
|
||||||
const formFieldOptions4Title = computed(() => {
|
const formFieldOptions4Title = computed(() => {
|
||||||
|
|
@ -341,6 +411,12 @@ const initData = () => {
|
||||||
if (modelData.value.processAfterTriggerSetting) {
|
if (modelData.value.processAfterTriggerSetting) {
|
||||||
processAfterTriggerEnable.value = true
|
processAfterTriggerEnable.value = true
|
||||||
}
|
}
|
||||||
|
if (modelData.value.taskBeforeTriggerSetting) {
|
||||||
|
taskBeforeTriggerEnable.value = true
|
||||||
|
}
|
||||||
|
if (modelData.value.taskAfterTriggerSetting) {
|
||||||
|
taskAfterTriggerEnable.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ initData })
|
defineExpose({ initData })
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
:model-key="modelData.key"
|
:model-key="modelData.key"
|
||||||
:model-name="modelData.name"
|
:model-name="modelData.name"
|
||||||
:start-user-ids="modelData.startUserIds"
|
:start-user-ids="modelData.startUserIds"
|
||||||
|
:start-dept-ids="modelData.startDeptIds"
|
||||||
@success="handleDesignSuccess"
|
@success="handleDesignSuccess"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@
|
||||||
v-model="formData"
|
v-model="formData"
|
||||||
:categoryList="categoryList"
|
:categoryList="categoryList"
|
||||||
:userList="userList"
|
:userList="userList"
|
||||||
|
:deptList="deptList"
|
||||||
ref="basicInfoRef"
|
ref="basicInfoRef"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -92,6 +93,7 @@ import * as ModelApi from '@/api/bpm/model'
|
||||||
import * as FormApi from '@/api/bpm/form'
|
import * as FormApi from '@/api/bpm/form'
|
||||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import * as DeptApi from '@/api/system/dept'
|
||||||
import * as DefinitionApi from '@/api/bpm/definition'
|
import * as DefinitionApi from '@/api/bpm/definition'
|
||||||
import { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
|
import { BpmModelFormType, BpmModelType, BpmAutoApproveType } from '@/utils/constants'
|
||||||
import BasicInfo from './BasicInfo.vue'
|
import BasicInfo from './BasicInfo.vue'
|
||||||
|
|
@ -153,6 +155,7 @@ const formData: any = ref({
|
||||||
visible: true,
|
visible: true,
|
||||||
startUserType: undefined,
|
startUserType: undefined,
|
||||||
startUserIds: [],
|
startUserIds: [],
|
||||||
|
startDeptIds: [],
|
||||||
managerUserIds: [],
|
managerUserIds: [],
|
||||||
allowCancelRunningProcess: true,
|
allowCancelRunningProcess: true,
|
||||||
processIdRule: {
|
processIdRule: {
|
||||||
|
|
@ -183,6 +186,7 @@ provide('modelData', formData)
|
||||||
const formList = ref([])
|
const formList = ref([])
|
||||||
const categoryList = ref<CategoryVO[]>([])
|
const categoryList = ref<CategoryVO[]>([])
|
||||||
const userList = ref<UserApi.UserVO[]>([])
|
const userList = ref<UserApi.UserVO[]>([])
|
||||||
|
const deptList = ref<DeptApi.DeptVO[]>([])
|
||||||
|
|
||||||
/** 初始化数据 */
|
/** 初始化数据 */
|
||||||
const actionType = route.params.type as string
|
const actionType = route.params.type as string
|
||||||
|
|
@ -200,14 +204,17 @@ const initData = async () => {
|
||||||
data.simpleModel = JSON.parse(data.simpleModel)
|
data.simpleModel = JSON.parse(data.simpleModel)
|
||||||
}
|
}
|
||||||
formData.value = data
|
formData.value = data
|
||||||
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
|
formData.value.startUserType =
|
||||||
|
formData.value.startUserIds?.length > 0 ? 1 : formData.value?.startDeptIds?.length > 0 ? 2 : 0
|
||||||
} else if (['update', 'copy'].includes(actionType)) {
|
} else if (['update', 'copy'].includes(actionType)) {
|
||||||
// 情况二:修改场景/复制场景
|
// 情况二:修改场景/复制场景
|
||||||
const modelId = route.params.id as string
|
const modelId = route.params.id as string
|
||||||
formData.value = await ModelApi.getModel(modelId)
|
formData.value = await ModelApi.getModel(modelId)
|
||||||
formData.value.startUserType = formData.value.startUserIds?.length > 0 ? 1 : 0
|
formData.value.startUserType =
|
||||||
|
formData.value.startUserIds?.length > 0 ? 1 : formData.value?.startDeptIds?.length > 0 ? 2 : 0
|
||||||
|
|
||||||
// 特殊:复制场景
|
// 特殊:复制场景
|
||||||
if (actionType === 'copy') {
|
if (route.params.type === 'copy') {
|
||||||
delete formData.value.id
|
delete formData.value.id
|
||||||
formData.value.name += '副本'
|
formData.value.name += '副本'
|
||||||
formData.value.key += '_copy'
|
formData.value.key += '_copy'
|
||||||
|
|
@ -225,6 +232,8 @@ const initData = async () => {
|
||||||
categoryList.value = await CategoryApi.getCategorySimpleList()
|
categoryList.value = await CategoryApi.getCategorySimpleList()
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
|
// 获取部门列表
|
||||||
|
deptList.value = await DeptApi.getSimpleDeptList()
|
||||||
|
|
||||||
// 最终,设置 currentStep 切换到第一步
|
// 最终,设置 currentStep 切换到第一步
|
||||||
currentStep.value = 0
|
currentStep.value = 0
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,78 @@
|
||||||
<template>
|
<template>
|
||||||
<el-form
|
<el-row :gutter="20">
|
||||||
ref="formRef"
|
<el-col :span="16">
|
||||||
v-loading="formLoading"
|
<ContentWrap title="申请信息">
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
label-width="80px"
|
|
||||||
>
|
|
||||||
<el-form-item label="请假类型" prop="type">
|
|
||||||
<el-select v-model="formData.type" clearable placeholder="请选择请假类型">
|
|
||||||
<el-option
|
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
|
|
||||||
:key="dict.value"
|
|
||||||
:label="dict.label"
|
|
||||||
:value="dict.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="开始时间" prop="startTime">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="formData.startTime"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择开始时间"
|
|
||||||
type="datetime"
|
|
||||||
value-format="x"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="结束时间" prop="endTime">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="formData.endTime"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择结束时间"
|
|
||||||
type="datetime"
|
|
||||||
value-format="x"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="原因" prop="reason">
|
|
||||||
<el-input v-model="formData.reason" placeholder="请输请假原因" type="textarea" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-col v-if="startUserSelectTasks.length > 0">
|
|
||||||
<el-card class="mb-10px">
|
|
||||||
<template #header>指定审批人</template>
|
|
||||||
<el-form
|
<el-form
|
||||||
:model="startUserSelectAssignees"
|
ref="formRef"
|
||||||
:rules="startUserSelectAssigneesFormRules"
|
v-loading="formLoading"
|
||||||
ref="startUserSelectAssigneesFormRef"
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="80px"
|
||||||
>
|
>
|
||||||
<el-form-item
|
<el-form-item label="请假类型" prop="type">
|
||||||
v-for="userTask in startUserSelectTasks"
|
<el-select v-model="formData.type" clearable placeholder="请选择请假类型">
|
||||||
:key="userTask.id"
|
|
||||||
:label="`任务【${userTask.name}】`"
|
|
||||||
:prop="userTask.id"
|
|
||||||
>
|
|
||||||
<el-select
|
|
||||||
v-model="startUserSelectAssignees[userTask.id]"
|
|
||||||
multiple
|
|
||||||
placeholder="请选择审批人"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="user in userList"
|
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
|
||||||
:key="user.id"
|
:key="dict.value"
|
||||||
:label="user.nickname"
|
:label="dict.label"
|
||||||
:value="user.id"
|
:value="dict.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="开始时间" prop="startTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.startTime"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择开始时间"
|
||||||
|
type="datetime"
|
||||||
|
value-format="x"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结束时间" prop="endTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.endTime"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择结束时间"
|
||||||
|
type="datetime"
|
||||||
|
value-format="x"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="原因" prop="reason">
|
||||||
|
<el-input v-model="formData.reason" placeholder="请输入请假原因" type="textarea" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">
|
||||||
|
确 定
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</ContentWrap>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-form-item>
|
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
<!-- 审批相关:流程信息 -->
|
||||||
</el-form-item>
|
<el-col :span="8">
|
||||||
</el-form>
|
<ContentWrap title="审批流程" :bodyStyle="{ padding: '0 20px 0' }">
|
||||||
|
<ProcessInstanceTimeline
|
||||||
|
ref="timelineRef"
|
||||||
|
:activity-nodes="activityNodes"
|
||||||
|
:show-status-icon="false"
|
||||||
|
@select-user-confirm="selectUserConfirm"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import * as LeaveApi from '@/api/bpm/leave'
|
import * as LeaveApi from '@/api/bpm/leave'
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
|
||||||
|
// 审批相关:import
|
||||||
import * as DefinitionApi from '@/api/bpm/definition'
|
import * as DefinitionApi from '@/api/bpm/definition'
|
||||||
import * as UserApi from '@/api/system/user'
|
import ProcessInstanceTimeline from '@/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue'
|
||||||
|
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||||
|
import { CandidateStrategy, NodeId } from '@/components/SimpleProcessDesignerV2/src/consts'
|
||||||
|
import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmOALeaveCreate' })
|
defineOptions({ name: 'BpmOALeaveCreate' })
|
||||||
|
|
||||||
|
|
@ -100,30 +95,37 @@ const formRules = reactive({
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
// 指定审批人
|
// 审批相关:变量
|
||||||
const processDefineKey = 'oa_leave' // 流程定义 Key
|
const processDefineKey = 'oa_leave' // 流程定义 Key
|
||||||
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
|
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
|
||||||
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
|
||||||
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
|
const tempStartUserSelectAssignees = ref({}) // 历史发起人选择审批人的数据,用于每次表单变更时,临时保存
|
||||||
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
|
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
|
||||||
const userList = ref<any[]>([]) // 用户列表
|
const processDefinitionId = ref('')
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
// 校验表单
|
// 1.1 校验表单
|
||||||
if (!formRef) return
|
if (!formRef) return
|
||||||
const valid = await formRef.value.validate()
|
const valid = await formRef.value.validate()
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
// 校验指定审批人
|
// 1.2 审批相关:校验指定审批人
|
||||||
if (startUserSelectTasks.value?.length > 0) {
|
if (startUserSelectTasks.value?.length > 0) {
|
||||||
await startUserSelectAssigneesFormRef.value.validate()
|
for (const userTask of startUserSelectTasks.value) {
|
||||||
|
if (
|
||||||
|
Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
|
||||||
|
startUserSelectAssignees.value[userTask.id].length === 0
|
||||||
|
) {
|
||||||
|
return message.warning(`请选择${userTask.name}的审批人`)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交请求
|
// 2. 提交请求
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = { ...formData.value } as unknown as LeaveApi.LeaveVO
|
const data = { ...formData.value } as unknown as LeaveApi.LeaveVO
|
||||||
// 设置指定审批人
|
// 审批相关:设置指定审批人
|
||||||
if (startUserSelectTasks.value?.length > 0) {
|
if (startUserSelectTasks.value?.length > 0) {
|
||||||
data.startUserSelectAssignees = startUserSelectAssignees.value
|
data.startUserSelectAssignees = startUserSelectAssignees.value
|
||||||
}
|
}
|
||||||
|
|
@ -137,28 +139,93 @@ const submitForm = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 审批相关:获取审批详情 */
|
||||||
|
const getApprovalDetail = async () => {
|
||||||
|
try {
|
||||||
|
const data = await ProcessInstanceApi.getApprovalDetail({
|
||||||
|
processDefinitionId: processDefinitionId.value,
|
||||||
|
// TODO 小北:可以支持 processDefinitionKey 查询
|
||||||
|
activityId: NodeId.START_USER_NODE_ID,
|
||||||
|
processVariablesStr: JSON.stringify({ day: daysDifference() }) // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
message.error('查询不到审批详情信息!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取审批节点,显示 Timeline 的数据
|
||||||
|
activityNodes.value = data.activityNodes
|
||||||
|
|
||||||
|
// 获取发起人自选的任务
|
||||||
|
startUserSelectTasks.value = data.activityNodes?.filter(
|
||||||
|
(node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
|
||||||
|
)
|
||||||
|
// 恢复之前的选择审批人
|
||||||
|
if (startUserSelectTasks.value?.length > 0) {
|
||||||
|
for (const node of startUserSelectTasks.value) {
|
||||||
|
if (
|
||||||
|
tempStartUserSelectAssignees.value[node.id] &&
|
||||||
|
tempStartUserSelectAssignees.value[node.id].length > 0
|
||||||
|
) {
|
||||||
|
startUserSelectAssignees.value[node.id] = tempStartUserSelectAssignees.value[node.id]
|
||||||
|
} else {
|
||||||
|
startUserSelectAssignees.value[node.id] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 审批相关:选择发起人 */
|
||||||
|
const selectUserConfirm = (id: string, userList: any[]) => {
|
||||||
|
startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算天数差
|
||||||
|
// TODO @小北:可以搞到 formatTime 里面去,然后看看 dayjs 里面有没有现成的方法,或者辅助计算的方法。
|
||||||
|
const daysDifference = () => {
|
||||||
|
const oneDay = 24 * 60 * 60 * 1000 // 一天的毫秒数
|
||||||
|
const diffTime = Math.abs(Number(formData.value.endTime) - Number(formData.value.startTime))
|
||||||
|
return Math.floor(diffTime / oneDay)
|
||||||
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// TODO @小北:这里可以简化,统一通过 getApprovalDetail 处理么?
|
||||||
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
|
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
|
||||||
undefined,
|
undefined,
|
||||||
processDefineKey
|
processDefineKey
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!processDefinitionDetail) {
|
if (!processDefinitionDetail) {
|
||||||
message.error('OA 请假的流程模型未配置,请检查!')
|
message.error('OA 请假的流程模型未配置,请检查!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
processDefinitionId.value = processDefinitionDetail.id
|
||||||
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
|
||||||
// 设置指定审批人
|
|
||||||
if (startUserSelectTasks.value?.length > 0) {
|
// 审批相关:加载最新的审批详情,主要用于节点预测
|
||||||
// 设置校验规则
|
await getApprovalDetail()
|
||||||
for (const userTask of startUserSelectTasks.value) {
|
|
||||||
startUserSelectAssignees.value[userTask.id] = []
|
|
||||||
startUserSelectAssigneesFormRules.value[userTask.id] = [
|
|
||||||
{ required: true, message: '请选择审批人', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
// 加载用户列表
|
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 审批相关:预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次, formData.value可改成实际业务中的特定字段 */
|
||||||
|
watch(
|
||||||
|
formData.value,
|
||||||
|
(newValue, oldValue) => {
|
||||||
|
if (!oldValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (newValue && Object.keys(newValue).length > 0) {
|
||||||
|
// 记录之前的节点审批人
|
||||||
|
tempStartUserSelectAssignees.value = startUserSelectAssignees.value
|
||||||
|
startUserSelectAssignees.value = {}
|
||||||
|
// 加载最新的审批详情,主要用于节点预测
|
||||||
|
getApprovalDetail()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@
|
||||||
{{ getApprovalNodeTime(activity) }}
|
{{ getApprovalNodeTime(activity) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="activity.nodeType === NodeType.CHILD_PROCESS_NODE">
|
||||||
|
<el-button type="primary" plain size="small" @click="handleChildProcess(activity)">
|
||||||
|
查看子流程
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
<!-- 需要自定义选择审批人 -->
|
<!-- 需要自定义选择审批人 -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-wrap gap2 items-center"
|
class="flex flex-wrap gap2 items-center"
|
||||||
|
|
@ -194,6 +199,7 @@ withDefaults(
|
||||||
showStatusIcon: true // 默认值为 true
|
showStatusIcon: true // 默认值为 true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
const { push } = useRouter() // 路由
|
||||||
|
|
||||||
// 审批节点
|
// 审批节点
|
||||||
const statusIconMap2 = {
|
const statusIconMap2 = {
|
||||||
|
|
@ -310,4 +316,15 @@ const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
|
||||||
customApproveUsers.value[activityId] = userList || []
|
customApproveUsers.value[activityId] = userList || []
|
||||||
emit('selectUserConfirm', activityId, userList)
|
emit('selectUserConfirm', activityId, userList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 跳转子流程 */
|
||||||
|
const handleChildProcess = (activity: any) => {
|
||||||
|
// TODO @lesan:貌似跳不过去?!
|
||||||
|
push({
|
||||||
|
name: 'BpmProcessInstanceDetail',
|
||||||
|
query: {
|
||||||
|
id: activity.processInstanceId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
:model-name="modelName"
|
:model-name="modelName"
|
||||||
@success="handleSuccess"
|
@success="handleSuccess"
|
||||||
:start-user-ids="startUserIds"
|
:start-user-ids="startUserIds"
|
||||||
|
:start-dept-ids="startDeptIds"
|
||||||
ref="designerRef"
|
ref="designerRef"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
@ -22,6 +23,7 @@ defineProps<{
|
||||||
modelKey?: string
|
modelKey?: string
|
||||||
modelName?: string
|
modelName?: string
|
||||||
startUserIds?: number[]
|
startUserIds?: number[]
|
||||||
|
startDeptIds?: number[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@
|
||||||
:formatter="dateFormatter"
|
:formatter="dateFormatter"
|
||||||
align="center"
|
align="center"
|
||||||
label="发起时间"
|
label="发起时间"
|
||||||
prop="createTime"
|
prop="processInstance.createTime"
|
||||||
width="180"
|
width="180"
|
||||||
/>
|
/>
|
||||||
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
<el-table-column align="center" label="当前任务" prop="name" width="180" />
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,11 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button :disabled="tableList.length === 0" type="primary" @click="handleImportTable">
|
<el-button
|
||||||
|
:disabled="tableList.length === 0 || dbTableLoading"
|
||||||
|
type="primary"
|
||||||
|
@click="handleImportTable"
|
||||||
|
>
|
||||||
导入
|
导入
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="close">关闭</el-button>
|
<el-button @click="close">关闭</el-button>
|
||||||
|
|
@ -139,13 +143,18 @@ const handleSelectionChange = (selection) => {
|
||||||
|
|
||||||
/** 导入按钮操作 */
|
/** 导入按钮操作 */
|
||||||
const handleImportTable = async () => {
|
const handleImportTable = async () => {
|
||||||
await CodegenApi.createCodegenList({
|
dbTableLoading.value = true
|
||||||
dataSourceConfigId: queryParams.dataSourceConfigId,
|
try {
|
||||||
tableNames: tableList.value
|
await CodegenApi.createCodegenList({
|
||||||
})
|
dataSourceConfigId: queryParams.dataSourceConfigId,
|
||||||
message.success('导入成功')
|
tableNames: tableList.value
|
||||||
emit('success')
|
})
|
||||||
close()
|
message.success('导入成功')
|
||||||
|
emit('success')
|
||||||
|
close()
|
||||||
|
} finally {
|
||||||
|
dbTableLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||