工作流表单+模型代码提交

This commit is contained in:
2024-05-09 14:18:39 +08:00
parent eeef608ccd
commit 06611f527a
266 changed files with 18227 additions and 22039 deletions

View File

@@ -11,16 +11,24 @@
"dependencies": {
"@ant-design/colors": "^7.0.2",
"@element-plus/icons-vue": "^2.3.1",
"@form-create/designer": "^3.1.3",
"@form-create/element-ui": "^3.1.24",
"qs": "^6.12.0",
"vue-i18n": "9.10.2",
"web-storage-cache": "^1.1.1",
"vue-types": "^5.1.1",
"jsencrypt": "^3.3.2",
"dayjs": "^1.11.10",
"@fortawesome/fontawesome-free": "^6.5.1",
"@vueuse/core": "^10.7.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.2",
"bpmn-js": "^7.3.1",
"bpmn-js-properties-panel": "^0.37.2",
"bpmn-js-token-simulation": "0.10.0",
"bpmn-moddle": "^6.0.0",
"camunda-bpmn-moddle": "^4.5.0",
"bpmn-js-token-simulation": "^0.10.0",
"camunda-bpmn-moddle": "^7.0.1",
"bpmn-js": "8.9.0",
"bpmn-js-properties-panel": "0.46.0",
"crypto-js": "^4.2.0",
"diagram-js": "^11.4.1",
"diagram-js-minimap": "^2.0.4",
@@ -46,7 +54,6 @@
"splitpanes": "^3.1.5",
"steady-xml": "0.1.0",
"use-element-plus-theme": "^0.0.5",
"vform3-builds": "^3.0.10",
"vue": "^3.3.11",
"vue-baidu-map-3x": "^1.0.35",
"vue-baidu-map-offline": "^1.0.7",
@@ -59,7 +66,9 @@
},
"devDependencies": {
"@types/lodash-es": "^4.17.12",
"unplugin-auto-import": "^0.16.7",
"@types/node": "^20.10.5",
"@purge-icons/generated": "^0.9.0",
"@types/splitpanes": "^2.2.6",
"@vitejs/plugin-vue": "^4.5.2",
"@vitejs/plugin-vue-jsx": "^3.1.0",

5505
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
import createAxios from '@/utils/request'
import { BPM_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = BPM_BOOT + '/bpm/category'
/**
* 查询流程分类数据
*/
export const listCategory = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/list',
method: 'POST',
data: data
})
}
/**
* 查询所有流程分类
*/
export const getCategorySimpleList = () => {
return createAxios({
url: MAPPING_PATH + '/simpleList',
method: 'GET'
})
}
/**
* 根据id查询分类详细信息
*/
export const getById = (id: string) => {
return createAxios({
url: MAPPING_PATH + '/getById?id=' + id,
method: 'GET'
})
}
/**
* 新增流程分类
*/
export const addCategory = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/add',
method: 'POST',
data: data
})
}
/**
* 更新流程分类
*/
export const updateCategory = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/update',
method: 'POST',
data: data
})
}
/**
* 删除流程分类
*/
export const deleteCategory = (data: any) => {
let ids = [data]
return createAxios({
url: MAPPING_PATH + '/delete',
method: 'POST',
data: ids
})
}

73
src/api/bpm-boot/form.ts Normal file
View File

@@ -0,0 +1,73 @@
import createAxios from '@/utils/request'
import { BPM_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = BPM_BOOT + '/bpm/form'
/**
* 查询流程表单数据
*/
export const listForm = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/list',
method: 'POST',
data: data
})
}
/**
* 查询所有流程表单
*/
export const getFormSimpleList = () => {
return createAxios({
url: MAPPING_PATH + '/simpleList',
method: 'GET'
})
}
/**
* 根据id查询表单详细信息
*/
export const getById = (id: string) => {
return createAxios({
url: MAPPING_PATH + '/getById?id=' + id,
method: 'GET'
})
}
/**
* 新增流程表单
*/
export const addForm = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/add',
method: 'POST',
data: data
})
}
/**
* 更新流程表单
*/
export const updateForm = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/update',
method: 'POST',
data: data
})
}
/**
* 删除流程表单
*/
export const deleteForm = (data: any) => {
let ids = [data]
return createAxios({
url: MAPPING_PATH + '/delete',
method: 'POST',
data: ids
})
}

83
src/api/bpm-boot/model.ts Normal file
View File

@@ -0,0 +1,83 @@
import createAxios from '@/utils/request'
import { BPM_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = BPM_BOOT + '/bpm/model'
/**
* 查询流程模型数据
*/
export const listModel = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/list',
method: 'POST',
data: data
})
}
/**
* 查询所有流程模型
*/
export const getModelSimpleList = () => {
return createAxios({
url: MAPPING_PATH + '/simpleList',
method: 'GET'
})
}
/**
* 根据id查询模型详细信息
*/
export const getById = (id: string) => {
return createAxios({
url: MAPPING_PATH + '/getById?id=' + id,
method: 'GET'
})
}
/**
* 根据id部署模型
*/
export const deployModel = (id: string) => {
return createAxios({
url: MAPPING_PATH + '/deploy?id=' + id,
method: 'POST'
})
}
/**
* 新增流程模型
*/
export const addModel = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/add',
method: 'POST',
data: data
})
}
/**
* 更新流程模型
*/
export const updateModel = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/update',
method: 'POST',
data: data
})
}
/**
* 删除流程模型
*/
export const deleteModel = (data: any) => {
let ids = [data]
return createAxios({
url: MAPPING_PATH + '/delete',
method: 'POST',
data: ids
})
}

View File

@@ -0,0 +1,42 @@
import request from '@/config/axios'
// BPM 流程表达式 VO
export interface ProcessExpressionVO {
id: number // 编号
name: string // 表达式名字
status: number // 表达式状态
expression: string // 表达式
}
// BPM 流程表达式 API
export const ProcessExpressionApi = {
// 查询BPM 流程表达式分页
getProcessExpressionPage: async (params: any) => {
return await request.get({ url: `/bpm/process-expression/page`, params })
},
// 查询BPM 流程表达式详情
getProcessExpression: async (id: number) => {
return await request.get({ url: `/bpm/process-expression/get?id=` + id })
},
// 新增BPM 流程表达式
createProcessExpression: async (data: ProcessExpressionVO) => {
return await request.post({ url: `/bpm/process-expression/create`, data })
},
// 修改BPM 流程表达式
updateProcessExpression: async (data: ProcessExpressionVO) => {
return await request.put({ url: `/bpm/process-expression/update`, data })
},
// 删除BPM 流程表达式
deleteProcessExpression: async (id: number) => {
return await request.delete({ url: `/bpm/process-expression/delete?id=` + id })
},
// 导出BPM 流程表达式 Excel
exportProcessExpression: async (params) => {
return await request.download({ url: `/bpm/process-expression/export-excel`, params })
}
}

View File

@@ -0,0 +1,40 @@
import request from '@/config/axios'
// BPM 流程监听器 VO
export interface ProcessListenerVO {
id: number // 编号
name: string // 监听器名字
type: string // 监听器类型
status: number // 监听器状态
event: string // 监听事件
valueType: string // 监听器值类型
value: string // 监听器值
}
// BPM 流程监听器 API
export const ProcessListenerApi = {
// 查询流程监听器分页
getProcessListenerPage: async (params: any) => {
return await request.get({ url: `/bpm/process-listener/page`, params })
},
// 查询流程监听器详情
getProcessListener: async (id: number) => {
return await request.get({ url: `/bpm/process-listener/get?id=` + id })
},
// 新增流程监听器
createProcessListener: async (data: ProcessListenerVO) => {
return await request.post({ url: `/bpm/process-listener/create`, data })
},
// 修改流程监听器
updateProcessListener: async (data: ProcessListenerVO) => {
return await request.put({ url: `/bpm/process-listener/update`, data })
},
// 删除流程监听器
deleteProcessListener: async (id: number) => {
return await request.delete({ url: `/bpm/process-listener/delete?id=` + id })
}
}

View File

@@ -0,0 +1,47 @@
import request from '@/config/axios'
export type UserGroupVO = {
id: number
name: string
description: string
userIds: number[]
status: number
remark: string
createTime: string
}
// 创建用户组
export const createUserGroup = async (data: UserGroupVO) => {
return await request.post({
url: '/bpm/user-group/create',
data: data
})
}
// 更新用户组
export const updateUserGroup = async (data: UserGroupVO) => {
return await request.put({
url: '/bpm/user-group/update',
data: data
})
}
// 删除用户组
export const deleteUserGroup = async (id: number) => {
return await request.delete({ url: '/bpm/user-group/delete?id=' + id })
}
// 获得用户组
export const getUserGroup = async (id: number) => {
return await request.get({ url: '/bpm/user-group/get?id=' + id })
}
// 获得用户组分页
export const getUserGroupPage = async (params) => {
return await request.get({ url: '/bpm/user-group/page', params })
}
// 获取用户组精简信息列表
export const getUserGroupSimpleList = async (): Promise<UserGroupVO[]> => {
return await request.get({ url: '/bpm/user-group/simple-list' })
}

View File

@@ -1,45 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/tenapp/dbs/${url}`, ...arg)
/**
* 多租户
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取数据源分页
dbsPage(data) {
return request('storage/page', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'storage/edit' : 'storage/add', data)
},
// 删除数据源
dbsDelete(data) {
return request('storage/delete', data)
},
// 获取数据源详情
dbsDetail(data) {
return request('storage/detail', data, 'get')
},
// 获取数据库中所有表
dbsTables(data) {
return request('tables', data, 'get')
},
// 获取数据库表中所有字段
dbsTableColumns(data) {
return request('tableColumns', data, 'get')
}
}

View File

@@ -1,89 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/flwapp/flw/model/${url}`, ...arg)
/**
* 模型
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取模型分页
modelPage(data) {
return request('page', data, 'get')
},
// 获取所有模型列表
modelAllList(data) {
return request('allList', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除模型
modelDelete(data) {
return request('delete', data)
},
// 部署模型
modelDeploy(data) {
return request('deploy', data)
},
// 获取模型详情
modelDetail(data) {
return request('detail', data, 'get')
},
// 停用模型
modelDisable(data) {
return request('disableModel', data)
},
// 启用模型
modelEnable(data) {
return request('enableModel', data)
},
// 模型降版
modelDownVersion(data) {
return request('downVersion', data)
},
// 获取组织树选择器
modelOrgTreeSelector(data) {
return request('orgTreeSelector', data, 'get')
},
// 获取组织列表选择器
modelOrgListSelector(data) {
return request('orgListSelector', data, 'get')
},
// 获取职位选择器
modelPositionSelector(data) {
return request('positionSelector', data, 'get')
},
// 获取角色选择器
modelRoleSelector(data) {
return request('roleSelector', data, 'get')
},
// 获取用户选择器
modelUserSelector(data) {
return request('userSelector', data, 'get')
},
// 获取执行监听器选择器
modelExecutionListenerSelector(data) {
return request('executionListenerSelector', data, 'get')
},
// 获取自定义事件执行监听器选择器
modelExecutionListenerSelectorForCustomEvent(data) {
return request('executionListenerSelectorForCustomEvent', data, 'get')
},
// 获取任务监听器选择器
modelTaskListenerSelector(data) {
return request('taskListenerSelector', data, 'get')
}
}

View File

@@ -1,93 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/flwapp/flw/process/monitor/${url}`, ...arg)
/**
* 流程
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取所有流程分页
processMonitorPage(data) {
return request('monitorPage', data, 'get')
},
// 删除流程
processDelete(data) {
return request('delete', data)
},
// 终止流程
processEnd(data) {
return request('end', data)
},
// 撤回流程
processRevoke(data) {
return request('revoke', data)
},
// 挂起流程
processSuspend(data) {
return request('suspend', data)
},
// 激活流程
processActive(data) {
return request('active', data)
},
// 转办流程
processTurn(data) {
return request('turn', data)
},
// 跳转流程
processJump(data) {
return request('jump', data)
},
// 复活流程
processRestart(data) {
return request('restart', data)
},
// 迁移流程
processMigrate(data) {
return request('migrate', data)
},
// 获取流程变量分页
processVariablePage(data) {
return request('variablePage', data, 'get')
},
// 批量编辑流程变量
processVariableUpdateBatch(data) {
return request('variableUpdateBatch', data)
},
// 获取流程详情
processDetail(data) {
return request('detail', data, 'get')
},
// 获取可跳转节点列表
processGetCanJumpNodeInfoList(data) {
return request('getCanJumpNodeInfoList', data, 'get')
},
// 获取可复活到节点列表
processGetCanRestartNodeInfoList(data) {
return request('getCanRestartNodeInfoList', data, 'get')
},
// 获取可迁移到节点列表
processGetCanMigrateNodeInfoList(data) {
return request('getCanMigrateNodeInfoList', data, 'get')
},
// 获取组织树选择器
processOrgTreeSelector(data) {
return request('orgTreeSelector', data, 'get')
},
// 获取用户选择器
processUserSelector(data) {
return request('userSelector', data, 'get')
}
}

View File

@@ -1,73 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/flwapp/flw/process/${url}`, ...arg)
/**
* 我的流程
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取我可以发起的流程模型列表
processMyModelList(data) {
return request('myModelList', data, 'get')
},
// 保存草稿
processSaveDraft(data) {
return request('saveDraft', data)
},
// 发起流程
processStart(data) {
return request('start', data)
},
// 获取我的草稿分页
processMyDraftPage(data) {
return request('myDraftPage', data, 'get')
},
// 获取草稿详情
processDraftDetail(data) {
return request('draftDetail', data, 'get')
},
// 删除草稿
processDeleteDraft(data) {
return request('deleteDraft', data)
},
// 获取我发起的流程分页
processMyPage(data) {
return request('myPage', data, 'get')
},
// 获取我的待阅流程分页
processMyCopyUnreadPage(data) {
return request('myCopyUnreadPage', data, 'get')
},
// 设置待阅流程为已阅
processReadMyCopyProcess(data) {
return request('readMyCopyProcess', data)
},
// 获取我的已阅流程分页
processMyCopyHasReadPage(data) {
return request('myCopyHasReadPage', data, 'get')
},
// 删除我的已阅流程
processDeleteMyHasReadProcess(data) {
return request('deleteMyHasReadProcess', data)
},
// 撤回流程
processRevoke(data) {
return request('revoke', data)
},
// 获取流程详情
processDetail(data) {
return request('detail', data, 'get')
}
}

View File

@@ -1,81 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/flwapp/flw/task/${url}`, ...arg)
/**
* 待办任务
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取待办任务分页
taskTodoPage(data) {
return request('todoPage', data, 'get')
},
// 获取已办任务分页
taskDonePage(data) {
return request('donePage', data, 'get')
},
// 调整申请
taskAdjust(data) {
return request('adjust', data)
},
// 审批保存
taskSave(data) {
return request('save', data)
},
// 审批同意
taskPass(data) {
return request('pass', data)
},
// 审批拒绝
taskReject(data) {
return request('reject', data)
},
// 审批退回
taskBack(data) {
return request('back', data)
},
// 任务转办
taskTurn(data) {
return request('turn', data)
},
// 审批跳转
taskJump(data) {
return request('jump', data)
},
// 任务加签
taskAddSign(data) {
return request('addSign', data)
},
// 任务详情
taskDetail(data) {
return request('detail', data, 'get')
},
// 获取可驳回节点列表
taskGetCanBackNodeInfoList(data) {
return request('getCanBackNodeInfoList', data, 'get')
},
// 获取可跳转节点列表
taskGetCanJumpNodeInfoList(data) {
return request('getCanJumpNodeInfoList', data, 'get')
},
// 获取组织树选择器
taskOrgTreeSelector(data) {
return request('orgTreeSelector', data, 'get')
},
// 获取用户选择器
taskUserSelector(data) {
return request('userSelector', data, 'get')
}
}

View File

@@ -1,41 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/flwapp/flw/templatePrint/${url}`, ...arg)
/**
* 打印模板
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取打印模板分页
templatePrintPage(data) {
return request('page', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
templatePrintSubmitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除打印模板
templatePrintDelete(data) {
return request('delete', data)
},
// 获取打印模板详情
templatePrintDetail(data) {
return request('detail', data, 'get')
},
// 获取打印模板选择列表
templateFlwTemplatePrintSelector(data) {
return request('flwTemplatePrintSelector', data, 'get')
}
}

View File

@@ -1,41 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/flwapp/flw/templateSn/${url}`, ...arg)
/**
* 流水号
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取流水号模板分页
templateSnPage(data) {
return request('page', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
templateSnSubmitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除流水号模板
templateSnDelete(data) {
return request('delete', data)
},
// 获取流水号模板详情
templateSnDetail(data) {
return request('detail', data, 'get')
},
// 获取序列号模板选择列表
templateFlwTemplateSnSelector(data) {
return request('flwTemplateSnSelector', data, 'get')
}
}

View File

@@ -1,53 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/index/${url}`, ...arg)
/**
* 系统首页控制器
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 添加当前用户日程
indexScheduleAdd(data) {
return request('schedule/add', data)
},
// 删除日程
indexScheduleDeleteSchedule(data) {
return request('schedule/deleteSchedule', data)
},
// 获取当前用户日程列表
indexScheduleList(data) {
return request('schedule/list', data, 'get')
},
// 获取当前用户站内信列表
indexMessageList(data) {
return request('message/list', data, 'get')
},
// 获取站内信详情
indexMessageDetail(data) {
return request('message/detail', data, 'get')
},
//站内信全部标记已读
indexMessageAllMarkRead(data) {
return request('message/allMessageMarkRead', data)
},
// 获取当前用户访问日志列表
indexVisLogList(data) {
return request('visLog/list', data, 'get')
},
// 获取当前用户操作日志列表
indexOpLogList(data) {
return request('opLog/list', data, 'get')
}
}

View File

@@ -1,53 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/org/${url}`, ...arg)
/**
* 机构
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取组织分页
orgPage(data) {
return request('page', data, 'get')
},
// 获取组织列表
orgList(data) {
return request('list', data, 'get')
},
// 获取组织树
orgTree(data) {
return request('tree', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除组织
orgDelete(data) {
return request('delete', data)
},
// 获取组织详情
orgDetail(data) {
return request('detail', data, 'get')
},
// 获取组织树选择器
orgOrgTreeSelector(data) {
return request('orgTreeSelector', data, 'get')
},
// 获取用户选择器
orgUserSelector(data) {
return request('userSelector', data, 'get')
}
}

View File

@@ -1,49 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/position/${url}`, ...arg)
/**
* 职位
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取职位分页
positionPage(data) {
return request('page', data, 'get')
},
// 获取职位列表
positionList(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除职位
positionDelete(data) {
return request('delete', data)
},
// 获取职位详情
positionDetail(data) {
return request('detail', data, 'get')
},
// 获取组织树选择器
positionOrgTreeSelector(data) {
return request('orgTreeSelector', data, 'get')
},
// 获取职位选择器
positionPositionSelector(data) {
return request('positionSelector', data, 'get')
}
}

View File

@@ -1,37 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/button/${url}`, ...arg)
/**
* 按钮
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取按钮分页
buttonPage(data) {
return request('page', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除按钮
buttonDelete(data) {
return request('delete', data)
},
// 获取按钮详情
buttonDetail(data) {
return request('detail', data, 'get')
}
}

View File

@@ -1,45 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/field/${url}`, ...arg)
/**
* 字段
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取字段分页
fieldPage(data) {
return request('page', data, 'get')
},
// 获取字段树
fieldTree(data) {
return request('tree', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除字段
fieldDelete(data) {
return request('delete', data)
},
// 获取字段详情
fieldDetail(data) {
return request('detail', data, 'get')
},
// 获取菜单树选择器
fieldMenuTreeSelector(data) {
return request('MenuTreeSelector', data, 'get')
}
}

View File

@@ -1,49 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/menu/${url}`, ...arg)
/**
* 菜单
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取菜单树
menuTree(data) {
return request('tree', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 更改菜单所属模块
menuChangeModule(data) {
return request('changeModule', data)
},
// 删除菜单
menuDelete(data) {
return request('delete', data)
},
// 获取菜单详情
menuDetail(data) {
return request('detail', data, 'get')
},
// 获取模块选择器
menuModuleSelector(data) {
return request('moduleSelector', data, 'get')
},
// 获取菜单树选择器
menuTreeSelector(data) {
return request('menuTreeSelector', data, 'get')
}
}

View File

@@ -1,37 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/module/${url}`, ...arg)
/**
* 模块
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取模块分页
modulePage(data) {
return request('page', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除模块
moduleDelete(data) {
return request('delete', data)
},
// 获取模块详情
moduleDetail(data) {
return request('detail', data, 'get')
}
}

View File

@@ -1,97 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/role/${url}`, ...arg)
/**
* 角色
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取角色分页
rolePage(data) {
return request('page', data, 'get')
},
// 获取角色列表
roleList(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除角色
roleDelete(data) {
return request('delete', data)
},
// 获取角色详情
roleDetail(data) {
return request('detail', data, 'get')
},
// 获取角色拥有资源
roleOwnResource(data) {
return request('ownResource', data, 'get')
},
// 给角色授权资源
roleGrantResource(data) {
return request('grantResource', data)
},
// 获取角色拥有移动端菜单
roleOwnMobileMenu(data) {
return request('ownMobileMenu', data, 'get')
},
// 给角色授权移动端菜单
roleGrantMobileMenu(data) {
return request('grantMobileMenu', data)
},
// 获取角色拥有权限
roleOwnPermission(data) {
return request('ownPermission', data, 'get')
},
// 给角色授权权限
roleGrantPermission(data) {
return request('grantPermission', data)
},
// 获取角色下的用户
roleOwnUser(data) {
return request('ownUser', data, 'get')
},
// 给角色授权用户
roleGrantUser(data) {
return request('grantUser', data)
},
// 获取机构树
roleOrgTreeSelector(data) {
return request('orgTreeSelector', data, 'get')
},
// 获取资源授权树
roleResourceTreeSelector(data) {
return request('resourceTreeSelector', data, 'get')
},
// 获取移动端菜单授权树
roleMobileMenuTreeSelector(data) {
return request('mobileMenuTreeSelector', data, 'get')
},
// 获取权限授权树
rolePermissionTreeSelector(data) {
return request('permissionTreeSelector', data, 'get')
},
// 获取角色选择器
roleRoleSelector(data) {
return request('roleSelector', data, 'get')
},
// 获取用户选择器
roleUserSelector(data) {
return request('userSelector', data, 'get')
}
}

View File

@@ -1,111 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/user/${url}`, ...arg)
/**
* 用户接口api
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取用户分页
userPage(data) {
return request('page', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
submitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除用户
userDelete(data) {
return request('delete', data)
},
// 获取用户详情
userDetail(data) {
return request('detail', data, 'get')
},
// 禁用用户
userDisableUser(data) {
return request('disableUser', data)
},
// 启用用户
userEnableUser(data) {
return request('enableUser', data)
},
// 重置用户密码
userResetPassword(data) {
return request('resetPassword', data)
},
// 获取组织选择器
userOrgTreeSelector(data) {
return request('orgTreeSelector', data, 'get')
},
// 获取职位选择器
userPositionSelector(data) {
return request('positionSelector', data, 'get')
},
// 获取角色选择器
userRoleSelector(data) {
return request('roleSelector', data, 'get')
},
// 获取用户选择器
userSelector(data) {
return request('userSelector', data, 'get')
},
// 用户拥有角色
userOwnRole(data) {
return request('ownRole', data, 'get')
},
// 给用户授权角色
grantRole(data) {
return request('grantRole', data)
},
// 获取用户拥有资源
userOwnResource(data) {
return request('ownResource', data, 'get')
},
// 给用户授权资源
userGrantResource(data) {
return request('grantResource', data)
},
// 获取用户拥有权限
userOwnPermission(data) {
return request('ownPermission', data, 'get')
},
// 给用户授权权限
userGrantPermission(data) {
return request('grantPermission', data)
},
// 下载用户导入模板
userDownloadImportUserTemplate(data) {
return request('downloadImportUserTemplate', data, 'get', {
responseType: 'blob'
})
},
// 用户导入
userImport(data) {
return request('import', data)
},
// 用户导出
userExport(data) {
return request('export', data, 'get', {
responseType: 'blob'
})
},
// 导出用户个人信息
userExportUserInfo(data) {
return request('exportUserInfo', data, 'get', {
responseType: 'blob'
})
}
}

View File

@@ -1,101 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/api/webapp/sys/userCenter/${url}`, ...arg)
/**
* 用户个人控制器
*
* @author yubaoshan
* @date 2022-09-22 22:33:20
*/
export default {
// 获取图片验证码
userGetPicCaptcha(data) {
return request('getPicCaptcha', data, 'get')
},
// 找回密码获取手机验证码
userFindPasswordGetPhoneValidCode(data) {
return request('findPasswordGetPhoneValidCode', data, 'get')
},
// 找回密码获取邮箱验证码
userFindPasswordGetEmailValidCode(data) {
return request('findPasswordGetEmailValidCode', data, 'get')
},
// 通过手机号找回用户密码
userFindPasswordByPhone(data) {
return request('findPasswordByPhone', data)
},
// 通过邮箱找回用户密码
userFindPasswordByEmail(data) {
return request('findPasswordByEmail', data)
},
// 修改用户密码
userUpdatePassword(data) {
return request('updatePassword', data)
},
// 修改用户头像
userUpdateAvatar(data) {
return request('updateAvatar', data)
},
// 修改用户签名图片
userUpdateSignature(data) {
return request('updateSignature', data)
},
// 获取登录用户的菜单
userLoginMenu(data) {
return request('loginMenu', data, 'get')
},
// 获取登录用户组织树
userLoginOrgTree(data) {
return request('loginOrgTree', data, 'get')
},
// 获取登录用户的职位信息
userLoginPositionInfo(data) {
return request('loginPositionInfo', data, 'get')
},
// 编辑个人信息
userUpdateUserInfo(data) {
return request('updateUserInfo', data)
},
// 编辑个人工作台
userUpdateUserWorkbench(data) {
return request('updateUserWorkbench', data)
},
// 获取登录用户的工作台
userLoginWorkbench(data) {
return request('loginWorkbench', data, 'get')
},
// 获取登录用户的站内信分页
userLoginUnreadMessagePage(data) {
return request('loginUnreadMessagePage', data, 'get')
},
// 读取登录用户站内信详情
userLoginUnreadMessageDetail(data) {
return request('loginUnreadMessageDetail', data, 'get')
},
// 根据id集合获取组织集合
userCenterGetOrgListByIdList(data) {
return request('getOrgListByIdList', data)
},
// 根据id集合获取用户集合
userCenterGetUserListByIdList(data) {
return request('getUserListByIdList', data)
},
// 根据id集合获取职位集合
userCenterGetPositionListByIdList(data) {
return request('getPositionListByIdList', data)
},
// 根据id集合获取角色集合
userCenterGetRoleListByIdList(data) {
return request('getRoleListByIdList', data)
}
}

View File

@@ -8,6 +8,15 @@ export function dictTypeList(data: any) {
})
}
export function dictTypeListAll() {
return request({
url: '/system-boot/dictType/listAll',
method: 'get'
})
}
export function dictTypeUpdate(data: any) {
return request({
url: '/system-boot/dictType/update',

View File

@@ -0,0 +1,49 @@
import request from '@/config/axios'
export type DictDataVO = {
id: number | undefined
sort: number | undefined
label: string
value: string
dictType: string
status: number
colorType: string
cssClass: string
remark: string
createTime: Date
}
// 查询字典数据(精简)列表
export const getSimpleDictDataList = () => {
return request.get({ url: '/system/dict-data/simple-list' })
}
// 查询字典数据列表
export const getDictDataPage = (params: PageParam) => {
return request.get({ url: '/system/dict-data/page', params })
}
// 查询字典数据详情
export const getDictData = (id: number) => {
return request.get({ url: '/system/dict-data/get?id=' + id })
}
// 新增字典数据
export const createDictData = (data: DictDataVO) => {
return request.post({ url: '/system/dict-data/create', data })
}
// 修改字典数据
export const updateDictData = (data: DictDataVO) => {
return request.put({ url: '/system/dict-data/update', data })
}
// 删除字典数据
export const deleteDictData = (id: number) => {
return request.delete({ url: '/system/dict-data/delete?id=' + id })
}
// 导出字典类型数据
export const exportDictData = (params) => {
return request.download({ url: '/system/dict-data/export', params })
}

View File

@@ -0,0 +1,44 @@
import request from '@/config/axios'
export type DictTypeVO = {
id: number | undefined
name: string
type: string
status: number
remark: string
createTime: Date
}
// 查询字典(精简)列表
export const getSimpleDictTypeList = () => {
return request.get({ url: '/system/dict-type/list-all-simple' })
}
// 查询字典列表
export const getDictTypePage = (params: PageParam) => {
return request.get({ url: '/system/dict-type/page', params })
}
// 查询字典详情
export const getDictType = (id: number) => {
return request.get({ url: '/system/dict-type/get?id=' + id })
}
// 新增字典
export const createDictType = (data: DictTypeVO) => {
return request.post({ url: '/system/dict-type/create', data })
}
// 修改字典
export const updateDictType = (data: DictTypeVO) => {
return request.put({ url: '/system/dict-type/update', data })
}
// 删除字典
export const deleteDictType = (id: number) => {
return request.delete({ url: '/system/dict-type/delete?id=' + id })
}
// 导出字典类型
export const exportDictType = (params) => {
return request.download({ url: '/system/dict-type/export', params })
}

View File

@@ -0,0 +1,49 @@
import request from '@/config/axios'
import qs from 'qs'
export interface NotifyMessageVO {
id: number
userId: number
userType: number
templateId: number
templateCode: string
templateNickname: string
templateContent: string
templateType: number
templateParams: string
readStatus: boolean
readTime: Date
createTime: Date
}
// 查询站内信消息列表
export const getNotifyMessagePage = async (params: PageParam) => {
return await request.get({ url: '/system/notify-message/page', params })
}
// 获得我的站内信分页
export const getMyNotifyMessagePage = async (params: PageParam) => {
return await request.get({ url: '/system/notify-message/my-page', params })
}
// 批量标记已读
export const updateNotifyMessageRead = async (ids) => {
return await request.put({
url: '/system/notify-message/update-read?' + qs.stringify({ ids: ids }, { indices: false })
})
}
// 标记所有站内信为已读
export const updateAllNotifyMessageRead = async () => {
return await request.put({ url: '/system/notify-message/update-all-read' })
}
// 获取当前用户的最新站内信列表
export const getUnreadNotifyMessageList = async () => {
return await request.get({ url: '/system/notify-message/get-unread-list' })
}
// 获得当前用户的未读站内信数量
export const getUnreadNotifyMessageCount = async () => {
return await request.get({ url: '/system/notify-message/get-unread-count' })
}

View File

@@ -0,0 +1,49 @@
import request from '@/config/axios'
export interface NotifyTemplateVO {
id?: number
name: string
nickname: string
code: string
content: string
type?: number
params: string
status: number
remark: string
}
export interface NotifySendReqVO {
userId: number | null
templateCode: string
templateParams: Map<String, Object>
}
// 查询站内信模板列表
export const getNotifyTemplatePage = async (params: PageParam) => {
return await request.get({ url: '/system/notify-template/page', params })
}
// 查询站内信模板详情
export const getNotifyTemplate = async (id: number) => {
return await request.get({ url: '/system/notify-template/get?id=' + id })
}
// 新增站内信模板
export const createNotifyTemplate = async (data: NotifyTemplateVO) => {
return await request.post({ url: '/system/notify-template/create', data })
}
// 修改站内信模板
export const updateNotifyTemplate = async (data: NotifyTemplateVO) => {
return await request.put({ url: '/system/notify-template/update', data })
}
// 删除站内信模板
export const deleteNotifyTemplate = async (id: number) => {
return await request.delete({ url: '/system/notify-template/delete?id=' + id })
}
// 发送站内信
export const sendNotify = (data: NotifySendReqVO) => {
return request.post({ url: '/system/notify-template/send-notify', data })
}

View File

@@ -51,4 +51,14 @@ export function getRoleListByIds(data:any) {
})
}
/**
* 查询所有角色
*/
export const getRoleSimpleList = () => {
return createAxios({
url: '/user-boot/role/simpleList',
method: 'GET'
})
}

View File

@@ -171,5 +171,16 @@ export function getUserListByIds(data:any) {
})
}
/**
* 查询所有用户
*/
export const getUserSimpleList = () => {
return request({
url: '/user-boot/user/simpleList',
method: 'GET'
})
}

View File

@@ -1,173 +0,0 @@
@font-face {
font-family: "snowy"; /* Project id 3880534 */
src: url('iconfont.ttf?t=1675528061732') format('truetype');
}
.snowy {
font-family: "snowy" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.arrow-up-filling:before {
content: "\e688";
}
.arrow-down-filling:before {
content: "\e689";
}
.arrow-left-filling:before {
content: "\e68a";
}
.arrow-right-filling:before {
content: "\e68b";
}
.caps-unlock-filling:before {
content: "\e68c";
}
.comment-filling:before {
content: "\e68d";
}
.check-item-filling:before {
content: "\e68e";
}
.clock-filling:before {
content: "\e68f";
}
.delete-filling:before {
content: "\e690";
}
.decline-filling:before {
content: "\e691";
}
.dynamic-filling:before {
content: "\e692";
}
.intermediate-filling:before {
content: "\e693";
}
.favorite-filling:before {
content: "\e694";
}
.layout-filling:before {
content: "\e695";
}
.help-filling:before {
content: "\e696";
}
.history-filling:before {
content: "\e697";
}
.filter-filling:before {
content: "\e698";
}
.file-common-filling:before {
content: "\e699";
}
.news-filling:before {
content: "\e69a";
}
.edit-filling:before {
content: "\e69b";
}
.fullscreen-expand-filling:before {
content: "\e69c";
}
.smile-filling:before {
content: "\e69d";
}
.rise-filling:before {
content: "\e69e";
}
.picture-filling:before {
content: "\e69f";
}
.notification-filling:before {
content: "\e6a0";
}
.user-filling:before {
content: "\e6a1";
}
.setting-filling:before {
content: "\e6a2";
}
.switch-filling:before {
content: "\e6a3";
}
.work-filling:before {
content: "\e6a4";
}
.task-filling:before {
content: "\e6a5";
}
.success-filling:before {
content: "\e6a6";
}
.warning-filling:before {
content: "\e6a7";
}
.folder-filling:before {
content: "\e6a8";
}
.map-filling:before {
content: "\e6a9";
}
.prompt-filling:before {
content: "\e6aa";
}
.meh-filling:before {
content: "\e6ab";
}
.cry-filling:before {
content: "\e6ac";
}
.top-filling:before {
content: "\e6ad";
}
.home-filling:before {
content: "\e6ae";
}
.sorting:before {
content: "\e6af";
}

View File

@@ -1,289 +0,0 @@
{
"id": "3880534",
"name": "snowy-app-filled",
"font_family": "snowy",
"css_prefix_text": "",
"description": "",
"glyphs": [
{
"icon_id": "15838581",
"name": "arrow-up-filling",
"font_class": "arrow-up-filling",
"unicode": "e688",
"unicode_decimal": 59016
},
{
"icon_id": "15838582",
"name": "arrow-down-filling",
"font_class": "arrow-down-filling",
"unicode": "e689",
"unicode_decimal": 59017
},
{
"icon_id": "15838583",
"name": "arrow-left-filling",
"font_class": "arrow-left-filling",
"unicode": "e68a",
"unicode_decimal": 59018
},
{
"icon_id": "15838584",
"name": "arrow-right-filling",
"font_class": "arrow-right-filling",
"unicode": "e68b",
"unicode_decimal": 59019
},
{
"icon_id": "15838585",
"name": "caps-unlock-filling",
"font_class": "caps-unlock-filling",
"unicode": "e68c",
"unicode_decimal": 59020
},
{
"icon_id": "15838586",
"name": "comment-filling",
"font_class": "comment-filling",
"unicode": "e68d",
"unicode_decimal": 59021
},
{
"icon_id": "15838587",
"name": "check-item-filling",
"font_class": "check-item-filling",
"unicode": "e68e",
"unicode_decimal": 59022
},
{
"icon_id": "15838588",
"name": "clock-filling",
"font_class": "clock-filling",
"unicode": "e68f",
"unicode_decimal": 59023
},
{
"icon_id": "15838589",
"name": "delete-filling",
"font_class": "delete-filling",
"unicode": "e690",
"unicode_decimal": 59024
},
{
"icon_id": "15838590",
"name": "decline-filling",
"font_class": "decline-filling",
"unicode": "e691",
"unicode_decimal": 59025
},
{
"icon_id": "15838591",
"name": "dynamic-filling",
"font_class": "dynamic-filling",
"unicode": "e692",
"unicode_decimal": 59026
},
{
"icon_id": "15838592",
"name": "intermediate-filling",
"font_class": "intermediate-filling",
"unicode": "e693",
"unicode_decimal": 59027
},
{
"icon_id": "15838593",
"name": "favorite-filling",
"font_class": "favorite-filling",
"unicode": "e694",
"unicode_decimal": 59028
},
{
"icon_id": "15838594",
"name": "layout-filling",
"font_class": "layout-filling",
"unicode": "e695",
"unicode_decimal": 59029
},
{
"icon_id": "15838595",
"name": "help-filling",
"font_class": "help-filling",
"unicode": "e696",
"unicode_decimal": 59030
},
{
"icon_id": "15838596",
"name": "history-filling",
"font_class": "history-filling",
"unicode": "e697",
"unicode_decimal": 59031
},
{
"icon_id": "15838597",
"name": "filter-filling",
"font_class": "filter-filling",
"unicode": "e698",
"unicode_decimal": 59032
},
{
"icon_id": "15838598",
"name": "file-common-filling",
"font_class": "file-common-filling",
"unicode": "e699",
"unicode_decimal": 59033
},
{
"icon_id": "15838599",
"name": "news-filling",
"font_class": "news-filling",
"unicode": "e69a",
"unicode_decimal": 59034
},
{
"icon_id": "15838600",
"name": "edit-filling",
"font_class": "edit-filling",
"unicode": "e69b",
"unicode_decimal": 59035
},
{
"icon_id": "15838601",
"name": "fullscreen-expand-filling",
"font_class": "fullscreen-expand-filling",
"unicode": "e69c",
"unicode_decimal": 59036
},
{
"icon_id": "15838602",
"name": "smile-filling",
"font_class": "smile-filling",
"unicode": "e69d",
"unicode_decimal": 59037
},
{
"icon_id": "15838603",
"name": "rise-filling",
"font_class": "rise-filling",
"unicode": "e69e",
"unicode_decimal": 59038
},
{
"icon_id": "15838604",
"name": "picture-filling",
"font_class": "picture-filling",
"unicode": "e69f",
"unicode_decimal": 59039
},
{
"icon_id": "15838605",
"name": "notification-filling",
"font_class": "notification-filling",
"unicode": "e6a0",
"unicode_decimal": 59040
},
{
"icon_id": "15838606",
"name": "user-filling",
"font_class": "user-filling",
"unicode": "e6a1",
"unicode_decimal": 59041
},
{
"icon_id": "15838607",
"name": "setting-filling",
"font_class": "setting-filling",
"unicode": "e6a2",
"unicode_decimal": 59042
},
{
"icon_id": "15838608",
"name": "switch-filling",
"font_class": "switch-filling",
"unicode": "e6a3",
"unicode_decimal": 59043
},
{
"icon_id": "15838609",
"name": "work-filling",
"font_class": "work-filling",
"unicode": "e6a4",
"unicode_decimal": 59044
},
{
"icon_id": "15838610",
"name": "task-filling",
"font_class": "task-filling",
"unicode": "e6a5",
"unicode_decimal": 59045
},
{
"icon_id": "15838611",
"name": "success-filling",
"font_class": "success-filling",
"unicode": "e6a6",
"unicode_decimal": 59046
},
{
"icon_id": "15838612",
"name": "warning-filling",
"font_class": "warning-filling",
"unicode": "e6a7",
"unicode_decimal": 59047
},
{
"icon_id": "15838613",
"name": "folder-filling",
"font_class": "folder-filling",
"unicode": "e6a8",
"unicode_decimal": 59048
},
{
"icon_id": "15838614",
"name": "map-filling",
"font_class": "map-filling",
"unicode": "e6a9",
"unicode_decimal": 59049
},
{
"icon_id": "15838615",
"name": "prompt-filling",
"font_class": "prompt-filling",
"unicode": "e6aa",
"unicode_decimal": 59050
},
{
"icon_id": "15838616",
"name": "meh-filling",
"font_class": "meh-filling",
"unicode": "e6ab",
"unicode_decimal": 59051
},
{
"icon_id": "15838617",
"name": "cry-filling",
"font_class": "cry-filling",
"unicode": "e6ac",
"unicode_decimal": 59052
},
{
"icon_id": "15838618",
"name": "top-filling",
"font_class": "top-filling",
"unicode": "e6ad",
"unicode_decimal": 59053
},
{
"icon_id": "15838619",
"name": "home-filling",
"font_class": "home-filling",
"unicode": "e6ae",
"unicode_decimal": 59054
},
{
"icon_id": "15838620",
"name": "sorting",
"font_class": "sorting",
"unicode": "e6af",
"unicode_decimal": 59055
}
]
}

View File

@@ -1,36 +0,0 @@
/**
* Copyright [2022] [https://www.xiaonuo.vip]
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
import './line/iconfont.css'
import lineJsonData from './line/iconfont.json'
import './filled/iconfont.css'
import filledJsonData from './filled/iconfont.json'
export default {
icons: [
{
name: '基础',
key: 'default',
iconItem: [
{
name: '线框风格',
key: 'default',
item: lineJsonData.glyphs
},
{
name: '实底风格',
key: 'filled',
item: filledJsonData.glyphs
}
]
}
]
}

View File

@@ -1,825 +0,0 @@
@font-face {
font-family: "snowy"; /* Project id 3791763 */
src: url('iconfont.ttf?t=1675526220710') format('truetype');
}
.snowy {
font-family: "snowy" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.export-outlined:before {
content: "\e792";
}
.experiment-outlined:before {
content: "\e7c9";
}
.expand-outlined:before {
content: "\e915";
}
.expand-alt-outlined:before {
content: "\e7e9";
}
.exception-outlined:before {
content: "\e7bb";
}
.euro-outlined:before {
content: "\e78f";
}
.euro-circle-outlined:before {
content: "\eb62";
}
.environment-outlined:before {
content: "\e790";
}
.ellipsis-outlined:before {
content: "\e815";
}
.download-outlined:before {
content: "\e814";
}
.dollar-outlined:before {
content: "\e78d";
}
.dollar-circle-outlined:before {
content: "\eb61";
}
.dislike-outlined:before {
content: "\e7c8";
}
.disconnect-outlined:before {
content: "\e7e8";
}
.dingtalk-outlined:before {
content: "\e881";
}
.desktop-outlined:before {
content: "\e845";
}
.deployment-unit-outlined:before {
content: "\e7d2";
}
.delivered-procedure-outlined:before {
content: "\e911";
}
.delete-column-outlined:before {
content: "\e901";
}
.delete-row-outlined:before {
content: "\e902";
}
.database-outlined:before {
content: "\e7b9";
}
.dashboard-outlined:before {
content: "\e78b";
}
.customer-service-outlined:before {
content: "\e7ca";
}
.crown-outlined:before {
content: "\e844";
}
.credit-card-outlined:before {
content: "\e7e5";
}
.copyright-outlined:before {
content: "\e789";
}
.copyright-circle-outlined:before {
content: "\eb60";
}
.control-outlined:before {
content: "\e79c";
}
.container-outlined:before {
content: "\e7b8";
}
.contacts-outlined:before {
content: "\e7e4";
}
.console-sql-outlined:before {
content: "\e910";
}
.compress-outlined:before {
content: "\e914";
}
.compass-outlined:before {
content: "\e786";
}
.comment-outlined:before {
content: "\e8ea";
}
.coffee-outlined:before {
content: "\e6b5";
}
.code-outlined:before {
content: "\e79b";
}
.cloud-server-outlined:before {
content: "\e7db";
}
.cloud-upload-outlined:before {
content: "\e7dc";
}
.cloud-outlined:before {
content: "\e7dd";
}
.cloud-download-outlined:before {
content: "\e7de";
}
.cloud-sync-outlined:before {
content: "\e7e0";
}
.clear-outlined:before {
content: "\e900";
}
.ci-circle-outlined:before {
content: "\e77f";
}
.carry-out-outlined:before {
content: "\e7d6";
}
.car-outlined:before {
content: "\e7da";
}
.ci-outlined:before {
content: "\eb5f";
}
.camera-outlined:before {
content: "\e7d9";
}
.calendar-outlined:before {
content: "\e7d4";
}
.calculator-outlined:before {
content: "\e79a";
}
.bulb-outlined:before {
content: "\e7c7";
}
.build-outlined:before {
content: "\e7d5";
}
.bug-outlined:before {
content: "\e8e9";
}
.branches-outlined:before {
content: "\e7e7";
}
.borderless-table-outlined:before {
content: "\e813";
}
.border-outlined:before {
content: "\e7b7";
}
.book-outlined:before {
content: "\e7b6";
}
.block-outlined:before {
content: "\e7df";
}
.bell-outlined:before {
content: "\e7c5";
}
.bars-outlined:before {
content: "\e71a";
}
.barcode-outlined:before {
content: "\e7d8";
}
.bank-outlined:before {
content: "\e7c6";
}
.audit-outlined:before {
content: "\e7c0";
}
.audio-outlined:before {
content: "\e89b";
}
.audio-muted-outlined:before {
content: "\e8e8";
}
.api-outlined:before {
content: "\e7e3";
}
.apartment-outlined:before {
content: "\e89a";
}
.alert-outlined:before {
content: "\e7c4";
}
.aim-outlined:before {
content: "\e913";
}
.account-book-outlined:before {
content: "\e7d3";
}
.column-height-outlined:before {
content: "\e811";
}
.column-width-outlined:before {
content: "\e812";
}
.radius-setting-outlined:before {
content: "\e7b5";
}
.unordered-list-outlined:before {
content: "\e80f";
}
.ordered-list-outlined:before {
content: "\e810";
}
.drag-outlined:before {
content: "\e843";
}
.sort-descending-outlined:before {
content: "\e80d";
}
.sort-ascending-outlined:before {
content: "\e80e";
}
.font-colors-outlined:before {
content: "\e808";
}
.font-size-outlined:before {
content: "\e809";
}
.line-height-outlined:before {
content: "\e80a";
}
.dash-outlined:before {
content: "\e80b";
}
.small-dash-outlined:before {
content: "\e80c";
}
.zoom-out-outlined:before {
content: "\e898";
}
.zoom-in-outlined:before {
content: "\e899";
}
.undo-outlined:before {
content: "\e787";
}
.redo-outlined:before {
content: "\e788";
}
.bold-outlined:before {
content: "\e804";
}
.strikethrough-outlined:before {
content: "\e805";
}
.underline-outlined:before {
content: "\e806";
}
.italic-outlined:before {
content: "\e807";
}
.bg-colors-outlined:before {
content: "\e803";
}
.align-right-outlined:before {
content: "\e7fb";
}
.align-left-outlined:before {
content: "\e802";
}
.align-center-outlined:before {
content: "\e7f5";
}
.highlight-outlined:before {
content: "\e7e2";
}
.diff-outlined:before {
content: "\e7bf";
}
.snippets-outlined:before {
content: "\e7bd";
}
.delete-outlined:before {
content: "\e7c3";
}
.scissor-outlined:before {
content: "\e7e6";
}
.copy-outlined:before {
content: "\e7bc";
}
.form-outlined:before {
content: "\e791";
}
.edit-outlined:before {
content: "\e7e1";
}
.stop-outlined:before {
content: "\e842";
}
.issues-close-outlined:before {
content: "\e68e";
}
.warning-outlined:before {
content: "\e682";
}
.clock-circle-outlined:before {
content: "\e784";
}
.check-circle-outlined:before {
content: "\e77d";
}
.check-square-outlined:before {
content: "\e794";
}
.check-outlined:before {
content: "\e7fc";
}
.exclamation-circle-outlined:before {
content: "\e785";
}
.exclamation-outlined:before {
content: "\e7fa";
}
.info-circle-outlined:before {
content: "\e77e";
}
.info-outlined:before {
content: "\e7f9";
}
.minus-square-outlined:before {
content: "\e796";
}
.plus-square-outlined:before {
content: "\e797";
}
.minus-circle-outlined:before {
content: "\e780";
}
.minus-outlined:before {
content: "\e801";
}
.pause-circle-outlined:before {
content: "\e783";
}
.pause-outlined:before {
content: "\e800";
}
.plus-circle-outlined:before {
content: "\e781";
}
.plus-outlined:before {
content: "\e8fe";
}
.question-circle-outlined:before {
content: "\e782";
}
.question-outlined:before {
content: "\e7ff";
}
.fullscreen-outlined:before {
content: "\e7ec";
}
.fullscreen-exit-outlined:before {
content: "\e7ed";
}
.radius-bottomleft-outlined:before {
content: "\e7b1";
}
.radius-bottomright-outlined:before {
content: "\e7b2";
}
.radius-upleft-outlined:before {
content: "\e7b3";
}
.radius-upright-outlined:before {
content: "\e7b4";
}
.pic-center-outlined:before {
content: "\e7f6";
}
.pic-right-outlined:before {
content: "\e7f7";
}
.pic-left-outlined:before {
content: "\e7f8";
}
.border-outer-outlined:before {
content: "\e7a9";
}
.border-top-outlined:before {
content: "\e7aa";
}
.border-bottom-outlined:before {
content: "\e7ab";
}
.border-left-outlined:before {
content: "\e7ac";
}
.border-right-outlined:before {
content: "\e7ad";
}
.border-inner-outlined:before {
content: "\e7ae";
}
.border-verticle-outlined:before {
content: "\e7af";
}
.border-horizontal-outlined:before {
content: "\e7b0";
}
.menu-unfold-outlined:before {
content: "\e7f3";
}
.menu-fold-outlined:before {
content: "\e7f4";
}
.logout-outlined:before {
content: "\e78c";
}
.login-outlined:before {
content: "\e8f4";
}
.cluster-outlined:before {
content: "\e7d7";
}
.down-square-outlined:before {
content: "\e793";
}
.left-square-outlined:before {
content: "\e795";
}
.right-square-outlined:before {
content: "\e798";
}
.up-Square-outlined:before {
content: "\e799";
}
.play-circle-outlined:before {
content: "\e67a";
}
.arrow-down-outlined:before {
content: "\e66d";
}
.arrow-right-outlined:before {
content: "\e66e";
}
.arrow-up-outlined:before {
content: "\e66f";
}
.arrow-left-outlined:before {
content: "\e670";
}
.swap-outlined:before {
content: "\e7f2";
}
.swap-right-outlined:before {
content: "\e8f2";
}
.swap-left-outlined:before {
content: "\e8f3";
}
.enter-outlined:before {
content: "\e7fd";
}
.rollback-outlined:before {
content: "\e7fe";
}
.retweet-outlined:before {
content: "\e8f1";
}
.fast-backward-outlined:before {
content: "\e8ed";
}
.fast-forward-outlined:before {
content: "\e8ee";
}
.vertical-align-bottom-outlined:before {
content: "\e7ef";
}
.vertical-align-middle-outlined:before {
content: "\e7f0";
}
.vertical-align-top-outlined:before {
content: "\e7f1";
}
.vertical-right-outlined:before {
content: "\e7ea";
}
.vertical-left-outlined:before {
content: "\e7eb";
}
.double-left-outlined:before {
content: "\e66b";
}
.double-right-outlined:before {
content: "\e66c";
}
.up-circle-outlined:before {
content: "\e666";
}
.right-circle-outlined:before {
content: "\e667";
}
.left-circle-outlined:before {
content: "\e66a";
}
.down-circle-outlined:before {
content: "\eb5e";
}
.caret-up-outlined:before {
content: "\e689";
}
.caret-down-outlined:before {
content: "\e68a";
}
.caret-left-outlined:before {
content: "\e68b";
}
.caret-right-outlined:before {
content: "\e68c";
}
.left-outlined:before {
content: "\e685";
}
.up-outlined:before {
content: "\e686";
}
.down-outlined:before {
content: "\e687";
}
.right-outlined:before {
content: "\e688";
}
.arrows-alt-outlined:before {
content: "\e665";
}
.shrink-outlined:before {
content: "\e68d";
}
.step-backward-outlined:before {
content: "\e8ef";
}
.step-forward-outlined:before {
content: "\e8f0";
}
.robot-outlined:before {
content: "\e897";
}
.file-word-outlined:before {
content: "\e7ba";
}
.usergroup-delete-outlined:before {
content: "\e760";
}
.field-time-outlined:before {
content: "\eb5d";
}
.setting-outlined:before {
content: "\e78e";
}
.file-search-outlined:before {
content: "\e730";
}
.team-outlined:before {
content: "\e67d";
}
.message-outlined:before {
content: "\e78a";
}
.mail-outlined:before {
content: "\e62e";
}
.send-outlined:before {
content: "\e622";
}
.appstore-add-outlined:before {
content: "\e8eb";
}
.user-outlined:before {
content: "\e641";
}
.project-outlined:before {
content: "\e746";
}
.hdd-outlined:before {
content: "\e734";
}
.tool-outlined:before {
content: "\e75b";
}
.user-switch-outlined:before {
content: "\ea3d";
}
.appstore-outlined:before {
content: "\e601";
}
.home-outlined:before {
content: "\e965";
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +0,0 @@
<template>
<div class="snowy-color-picker" @click="forceResize">
<color-picker v-bind="$attrs" format="hex" :pureColor="props.value" @update:pureColor="update" />
</div>
</template>
<script setup>
import { ColorPicker } from 'vue3-colorpicker'
import 'vue3-colorpicker/style.css'
const emit = defineEmits(['update:value'])
const props = defineProps({
value: {
type: String,
default: '#1890ff'
}
})
const forceResize = () => {
window.dispatchEvent(new Event('resize'))
}
const update = (val) => {
const currentColor = document.querySelector('.current-color')
if (currentColor) {
currentColor.textContent = val
}
emit('update:value', val)
}
</script>
<style lang="less">
.snowy-color-picker {
.vc-color-wrap {
width: auto;
}
.current-color {
color: #fff;
padding: 0 10px;
}
}
</style>

View File

@@ -0,0 +1,3 @@
import ContentWrap from './src/ContentWrap.vue'
export { ContentWrap }

View File

@@ -0,0 +1,38 @@
<script lang='ts' setup>
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { QuestionFilled } from '@element-plus/icons-vue'
defineOptions({ name: 'ContentWrap' })
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('content-wrap')
defineProps({
title: propTypes.string.def(''),
message: propTypes.string.def('')
})
</script>
<template>
<ElCard :class="[prefixCls, 'mb-15px']" shadow='never'>
<template v-if='title' #header>
<div class='flex items-center'>
<span class='text-16px font-700'>{{ title }}</span>
<ElTooltip v-if='message' effect='dark' placement='right'>
<template #content>
<div class='max-w-200px'>{{ message }}</div>
</template>
<QuestionFilled :size='14' class='ml-5px' />
</ElTooltip>
<div class='flex flex-grow pl-20px'>
<slot name='header'></slot>
</div>
</div>
</template>
<div>
<slot></slot>
</div>
</ElCard>
</template>

View File

@@ -0,0 +1,3 @@
import Dialog from './src/Dialog.vue'
export { Dialog }

View File

@@ -0,0 +1,140 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { isNumber } from '@/utils/is'
defineOptions({ name: 'Dialog' })
const slots = useSlots()
const props = defineProps({
modelValue: propTypes.bool.def(false),
title: propTypes.string.def('Dialog'),
fullscreen: propTypes.bool.def(true),
width: propTypes.oneOfType([String, Number]).def('40%'),
scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度
maxHeight: propTypes.oneOfType([String, Number]).def('400px')
})
const getBindValue = computed(() => {
const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody']
const attrs = useAttrs()
const obj = { ...attrs, ...props }
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return obj
})
const isFullscreen = ref(false)
const toggleFull = () => {
isFullscreen.value = !unref(isFullscreen)
}
const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
watch(
() => isFullscreen.value,
async (val: boolean) => {
await nextTick()
if (val) {
const windowHeight = document.documentElement.offsetHeight
dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`
} else {
dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight
}
},
{
immediate: true
}
)
const dialogStyle = computed(() => {
return {
height: unref(dialogHeight)
}
})
</script>
<template>
<ElDialog
v-bind="getBindValue"
:close-on-click-modal="true"
:fullscreen="isFullscreen"
:width="width"
destroy-on-close
lock-scroll
draggable
class="com-dialog"
:show-close="false"
>
<template #header="{ close }">
<div class="relative h-54px flex items-center justify-between pl-15px pr-15px">
<slot name="title">
{{ title }}
</slot>
<div
class="absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between"
>
<Icon
v-if="fullscreen"
class="is-hover mr-10px cursor-pointer"
:icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
color="var(--el-color-info)"
hover-color="var(--el-color-primary)"
@click="toggleFull"
/>
<Icon
class="is-hover cursor-pointer"
icon="ep:close"
hover-color="var(--el-color-primary)"
color="var(--el-color-info)"
@click="close"
/>
</div>
</div>
</template>
<ElScrollbar v-if="scroll" :style="dialogStyle">
<slot></slot>
</ElScrollbar>
<slot v-else></slot>
<template v-if="slots.footer" #footer>
<slot name="footer"></slot>
</template>
</ElDialog>
</template>
<style lang="scss">
.com-dialog {
.el-overlay-dialog {
display: flex;
justify-content: center;
align-items: center;
}
.el-dialog {
margin: 0 !important;
&__header {
height: 54px;
padding: 0;
margin-right: 0 !important;
border-bottom: 1px solid var(--el-border-color);
}
&__body {
padding: 15px !important;
}
&__footer {
border-top: 1px solid var(--el-border-color);
}
&__headerbtn {
top: 0;
}
}
}
</style>

View File

@@ -0,0 +1,3 @@
import DictSelect from './src/DictSelect.vue'
export { DictSelect }

View File

@@ -0,0 +1,46 @@
<!-- 数据字典 Select 选择器 -->
<template>
<el-select class="w-1/1" v-bind="attrs">
<template v-if="valueType === 'int'">
<el-option
v-for="(dict, index) in getIntDictOptions(dictType)"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</template>
<template v-if="valueType === 'str'">
<el-option
v-for="(dict, index) in getStrDictOptions(dictType)"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</template>
<template v-if="valueType === 'bool'">
<el-option
v-for="(dict, index) in getBoolDictOptions(dictType)"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</template>
</el-select>
</template>
<script lang="ts" setup>
import { getBoolDictOptions, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
// 接受父组件参数
interface Props {
dictType: string // 字典类型
valueType: string // 字典值类型
}
withDefaults(defineProps<Props>(), {
dictType: '',
valueType: 'str'
})
const attrs = useAttrs()
defineOptions({ name: 'DictSelect' })
</script>

View File

@@ -0,0 +1,3 @@
import DictTag from './src/DictTag.vue'
export { DictTag }

View File

@@ -0,0 +1,60 @@
<script lang="tsx">
import { defineComponent, PropType, ref } from 'vue'
import { isHexColor } from '@/utils/color'
import { ElTag } from 'element-plus'
import { DictDataType, getDictOptions } from '@/utils/dict'
export default defineComponent({
name: 'DictTag',
props: {
type: {
type: String as PropType<string>,
required: true
},
value: {
type: [String, Number, Boolean] as PropType<string | number | boolean>,
required: true
}
},
setup(props) {
const dictData = ref<DictDataType>()
const getDictObj = (dictType: string, value: string) => {
const dictOptions = getDictOptions(dictType)
dictOptions.forEach((dict: DictDataType) => {
if (dict.value === value) {
if (dict.colorType + '' === 'primary' || dict.colorType + '' === 'default') {
dict.colorType = ''
}
dictData.value = dict
}
})
}
const rederDictTag = () => {
if (!props.type) {
return null
}
// 解决自定义字典标签值为零时标签不渲染的问题
if (props.value === undefined || props.value === null) {
return null
}
getDictObj(props.type, props.value.toString())
// 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
return (
<ElTag
style={dictData.value?.cssClass ? 'color: #fff' : ''}
type={dictData.value?.colorType}
color={
dictData.value?.cssClass && isHexColor(dictData.value?.cssClass)
? dictData.value?.cssClass
: ''
}
disableTransitions={true}
>
{dictData.value?.label}
</ElTag>
)
}
return () => rederDictTag()
}
})
</script>

View File

@@ -0,0 +1,8 @@
import Editor from './src/Editor.vue'
import { IDomEditor } from '@wangeditor/editor'
export interface EditorExpose {
getEditorRef: () => Promise<IDomEditor>
}
export { Editor }

View File

@@ -0,0 +1,202 @@
<script lang="ts" setup>
import { PropType } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { i18nChangeLanguage, IDomEditor, IEditorConfig } from '@wangeditor/editor'
import { propTypes } from '@/utils/propTypes'
import { isNumber } from '@/utils/is'
import { ElMessage } from 'element-plus'
import { useLocaleStore } from '@/stores/modules/locale'
import { getAccessToken, getTenantId } from '@/utils/auth'
defineOptions({ name: 'Editor' })
type InsertFnType = (url: string, alt: string, href: string) => void
const localeStore = useLocaleStore()
const currentLocale = computed(() => localeStore.getCurrentLocale)
i18nChangeLanguage(unref(currentLocale).lang)
const props = defineProps({
editorId: propTypes.string.def('wangeEditor-1'),
height: propTypes.oneOfType([Number, String]).def('500px'),
editorConfig: {
type: Object as PropType<Partial<IEditorConfig>>,
default: () => undefined
},
readonly: propTypes.bool.def(false),
modelValue: propTypes.string.def('')
})
const emit = defineEmits(['change', 'update:modelValue'])
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef<IDomEditor>()
const valueHtml = ref('')
watch(
() => props.modelValue,
(val: string) => {
if (val === unref(valueHtml)) return
valueHtml.value = val
},
{
immediate: true
}
)
// 监听
watch(
() => valueHtml.value,
(val: string) => {
emit('update:modelValue', val)
}
)
const handleCreated = (editor: IDomEditor) => {
editorRef.value = editor
}
// 编辑器配置
const editorConfig = computed((): IEditorConfig => {
return Object.assign(
{
placeholder: '请输入内容...',
readOnly: props.readonly,
customAlert: (s: string, t: string) => {
switch (t) {
case 'success':
ElMessage.success(s)
break
case 'info':
ElMessage.info(s)
break
case 'warning':
ElMessage.warning(s)
break
case 'error':
ElMessage.error(s)
break
default:
ElMessage.info(s)
break
}
},
autoFocus: false,
scroll: true,
MENU_CONF: {
['uploadImage']: {
server: import.meta.env.VITE_UPLOAD_URL,
// 单个文件的最大体积限制,默认为 2M
maxFileSize: 5 * 1024 * 1024,
// 最多可上传几个文件,默认为 100
maxNumberOfFiles: 10,
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
allowedFileTypes: ['image/*'],
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
meta: { updateSupport: 0 },
// 将 meta 拼接到 url 参数中,默认 false
metaWithUrl: true,
// 自定义增加 http header
headers: {
Accept: '*',
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
},
// 跨域是否传递 cookie ,默认为 false
withCredentials: true,
// 超时时间,默认为 10 秒
timeout: 5 * 1000, // 5 秒
// form-data fieldName后端接口参数名称默认值wangeditor-uploaded-image
fieldName: 'file',
// 上传之前触发
onBeforeUpload(file: File) {
console.log(file)
return file
},
// 上传进度的回调函数
onProgress(progress: number) {
// progress 是 0-100 的数字
console.log('progress', progress)
},
onSuccess(file: File, res: any) {
console.log('onSuccess', file, res)
},
onFailed(file: File, res: any) {
alert(res.message)
console.log('onFailed', file, res)
},
onError(file: File, err: any, res: any) {
alert(err.message)
console.error('onError', file, err, res)
},
// 自定义插入图片
customInsert(res: any, insertFn: InsertFnType) {
insertFn(res.data, 'image', res.data)
}
}
},
uploadImgShowBase64: true
},
props.editorConfig || {}
)
})
const editorStyle = computed(() => {
return {
height: isNumber(props.height) ? `${props.height}px` : props.height
}
})
// 回调函数
const handleChange = (editor: IDomEditor) => {
emit('change', editor)
}
// 组件销毁时,及时销毁编辑器
onBeforeUnmount(() => {
const editor = unref(editorRef.value)
// 销毁,并移除 editor
editor?.destroy()
})
const getEditorRef = async (): Promise<IDomEditor> => {
await nextTick()
return unref(editorRef.value) as IDomEditor
}
defineExpose({
getEditorRef
})
</script>
<template>
<div class="border-1 border-solid border-[var(--tags-view-border-color)] z-10">
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:editorId="editorId"
class="border-0 b-b-1 border-solid border-[var(--tags-view-border-color)]"
/>
<!-- 编辑器 -->
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
:editorId="editorId"
:style="editorStyle"
@on-change="handleChange"
@on-created="handleCreated"
/>
</div>
</template>
<style src="@wangeditor/editor/dist/css/style.css"></style>

View File

@@ -0,0 +1,3 @@
import { useFormCreateDesigner } from './src/useFormCreateDesigner'
export { useFormCreateDesigner }

View File

@@ -0,0 +1,15 @@
import { useUploadFileRule } from './useUploadFileRule'
import { useUploadImgRule } from './useUploadImgRule'
import { useUploadImgsRule } from './useUploadImgsRule'
import { useDictSelectRule } from './useDictSelectRule'
import { useUserSelectRule } from './useUserSelectRule'
import { useEditorRule } from './useEditorRule'
export {
useUploadFileRule,
useUploadImgRule,
useUploadImgsRule,
useDictSelectRule,
useUserSelectRule,
useEditorRule
}

View File

@@ -0,0 +1,71 @@
const selectRule = [
{ type: 'switch', field: 'multiple', title: '是否多选' },
{
type: 'switch',
field: 'disabled',
title: '是否禁用'
},
{ type: 'switch', field: 'clearable', title: '是否可以清空选项' },
{
type: 'switch',
field: 'collapseTags',
title: '多选时是否将选中值按文字的形式展示'
},
{
type: 'inputNumber',
field: 'multipleLimit',
title: '多选时用户最多可以选择的项目数,为 0 则不限制',
props: { min: 0 }
},
{
type: 'input',
field: 'autocomplete',
title: 'autocomplete 属性'
},
{ type: 'input', field: 'placeholder', title: '占位符' },
{
type: 'switch',
field: 'filterable',
title: '是否可搜索'
},
{ type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' },
{
type: 'input',
field: 'noMatchText',
title: '搜索条件无匹配时显示的文字'
},
{
type: 'switch',
field: 'remote',
title: '其中的选项是否从服务器远程加载'
},
{
type: 'Struct',
field: 'remoteMethod',
title: '自定义远程搜索方法'
},
{ type: 'input', field: 'noDataText', title: '选项为空时显示的文字' },
{
type: 'switch',
field: 'reserveKeyword',
title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词'
},
{
type: 'switch',
field: 'defaultFirstOption',
title: '在输入框按下回车,选择第一个匹配项'
},
{
type: 'switch',
field: 'popperAppendToBody',
title: '是否将弹出框插入至 body 元素',
value: true
},
{
type: 'switch',
field: 'automaticDropdown',
title: '对于不可搜索的 Select是否在输入框获得焦点后自动弹出选项菜单'
}
]
export default selectRule

View File

@@ -0,0 +1,64 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
import selectRule from '@/components/FormCreate/src/config/selectRule'
import { dictTypeListAll } from '@/api/system-boot/dictType'
import { ref, onMounted } from 'vue'
export const useDictSelectRule = () => {
const label = '字典选择器'
const name = 'DictSelect'
const dictOptions = ref<{ label: string; value: string }[]>([]) // 字典类型下拉数据
onMounted(async () => {
let data: any = []
await dictTypeListAll().then(res => {
data = res.data
})
if (data.length === 0) {
return
}
dictOptions.value =
data.map((item: any) => ({
label: item.name,
value: item.id
})) ?? []
})
return {
icon: 'icon-select',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'select',
field: 'dictType',
title: '字典类型',
value: '',
options: dictOptions.value
},
{
type: 'select',
field: 'valueType',
title: '字典值类型',
value: 'str',
options: [
{ label: '数字', value: 'int' },
{ label: '字符串', value: 'str' },
{ label: '布尔值', value: 'bool' }
]
},
...selectRule
])
}
}
}

View File

@@ -0,0 +1,32 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useEditorRule = () => {
const label = '富文本'
const name = 'Editor'
return {
icon: 'icon-editor',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'input',
field: 'height',
title: '高度'
},
{ type: 'switch', field: 'readonly', title: '是否只读' }
])
}
}
}

View File

@@ -0,0 +1,80 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useUploadFileRule = () => {
const label = '文件上传'
const name = 'UploadFile'
return {
icon: 'icon-upload',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'select',
field: 'fileType',
title: '文件类型',
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
options: [
{ label: 'doc', value: 'doc' },
{ label: 'xls', value: 'xls' },
{ label: 'ppt', value: 'ppt' },
{ label: 'txt', value: 'txt' },
{ label: 'pdf', value: 'pdf' }
],
props: {
multiple: true
}
},
{
type: 'switch',
field: 'autoUpload',
title: '是否在选取文件后立即进行上传',
value: true
},
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false
},
{
type: 'switch',
field: 'isShowTip',
title: '是否显示提示',
value: true
},
{
type: 'inputNumber',
field: 'fileSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 }
},
{
type: 'inputNumber',
field: 'limit',
title: '数量限制',
value: 5,
props: { min: 0 }
},
{
type: 'switch',
field: 'disabled',
title: '是否禁用',
value: false
}
])
}
}
}

View File

@@ -0,0 +1,89 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useUploadImgRule = () => {
const label = '单图上传'
const name = 'UploadImg'
return {
icon: 'icon-upload',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false
},
{
type: 'select',
field: 'fileType',
title: '图片类型限制',
value: ['image/jpeg', 'image/png', 'image/gif'],
options: [
{ label: 'image/apng', value: 'image/apng' },
{ label: 'image/bmp', value: 'image/bmp' },
{ label: 'image/gif', value: 'image/gif' },
{ label: 'image/jpeg', value: 'image/jpeg' },
{ label: 'image/pjpeg', value: 'image/pjpeg' },
{ label: 'image/svg+xml', value: 'image/svg+xml' },
{ label: 'image/tiff', value: 'image/tiff' },
{ label: 'image/webp', value: 'image/webp' },
{ label: 'image/x-icon', value: 'image/x-icon' }
],
props: {
multiple: true
}
},
{
type: 'inputNumber',
field: 'fileSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 }
},
{
type: 'input',
field: 'height',
title: '组件高度',
value: '150px'
},
{
type: 'input',
field: 'width',
title: '组件宽度',
value: '150px'
},
{
type: 'input',
field: 'borderradius',
title: '组件边框圆角',
value: '8px'
},
{
type: 'switch',
field: 'disabled',
title: '是否显示删除按钮',
value: true
},
{
type: 'switch',
field: 'showBtnText',
title: '是否显示按钮文字',
value: true
}
])
}
}
}

View File

@@ -0,0 +1,84 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useUploadImgsRule = () => {
const label = '多图上传'
const name = 'UploadImgs'
return {
icon: 'icon-upload',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false
},
{
type: 'select',
field: 'fileType',
title: '图片类型限制',
value: ['image/jpeg', 'image/png', 'image/gif'],
options: [
{ label: 'image/apng', value: 'image/apng' },
{ label: 'image/bmp', value: 'image/bmp' },
{ label: 'image/gif', value: 'image/gif' },
{ label: 'image/jpeg', value: 'image/jpeg' },
{ label: 'image/pjpeg', value: 'image/pjpeg' },
{ label: 'image/svg+xml', value: 'image/svg+xml' },
{ label: 'image/tiff', value: 'image/tiff' },
{ label: 'image/webp', value: 'image/webp' },
{ label: 'image/x-icon', value: 'image/x-icon' }
],
props: {
multiple: true
}
},
{
type: 'inputNumber',
field: 'fileSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 }
},
{
type: 'inputNumber',
field: 'limit',
title: '数量限制',
value: 5,
props: { min: 0 }
},
{
type: 'input',
field: 'height',
title: '组件高度',
value: '150px'
},
{
type: 'input',
field: 'width',
title: '组件宽度',
value: '150px'
},
{
type: 'input',
field: 'borderradius',
title: '组件边框圆角',
value: '8px'
}
])
}
}
}

View File

@@ -0,0 +1,25 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
import selectRule from '@/components/FormCreate/src/config/selectRule'
export const useUserSelectRule = () => {
const label = '用户选择器'
const name = 'UserSelect'
return {
icon: 'icon-select',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [makeRequiredRule(), ...selectRule])
}
}
}

View File

@@ -0,0 +1,53 @@
import {
useDictSelectRule,
useEditorRule,
useUploadFileRule,
useUploadImgRule,
useUploadImgsRule,
useUserSelectRule
} from './config'
import { Ref } from 'vue'
/**
* 表单设计器增强 hook
* 新增
* - 文件上传
* - 单图上传
* - 多图上传
* - 字典选择器
* - 系统用户选择器
* - 富文本
*/
export const useFormCreateDesigner = (designer: Ref) => {
const editorRule = useEditorRule()
const uploadFileRule = useUploadFileRule()
const uploadImgRule = useUploadImgRule()
const uploadImgsRule = useUploadImgsRule()
const dictSelectRule = useDictSelectRule()
const userSelectRule = useUserSelectRule()
onMounted(() => {
// 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代
designer.value?.removeMenuItem('upload')
// 移除自带的富文本组件规则,使用 editorRule 替代
designer.value?.removeMenuItem('fc-editor')
const components = [
editorRule,
uploadFileRule,
uploadImgRule,
uploadImgsRule,
dictSelectRule,
userSelectRule
]
components.forEach((component) => {
// 插入组件规则
designer.value?.addComponent(component)
// 插入拖拽按钮到 `main` 分类下
designer.value?.appendMenuItem('main', {
icon: component.icon,
name: component.name,
label: component.label
})
})
})
}

View File

@@ -0,0 +1,79 @@
// TODO puhui999: 借鉴一下 form-create-designer utils 方法 🤣 (导入不了只能先 copy 过来用下)
export function makeRequiredRule() {
return {
type: 'Required',
field: 'formCreate$required',
title: '是否必填'
}
}
export const localeProps = (t, prefix, rules) => {
return rules.map((rule) => {
if (rule.field === 'formCreate$required') {
rule.title = t('props.required') || rule.title
} else if (rule.field && rule.field !== '_optionType') {
rule.title = t('components.' + prefix + '.' + rule.field) || rule.title
}
return rule
})
}
export function upper(str) {
return str.replace(str[0], str[0].toLocaleUpperCase())
}
export function makeOptionsRule(t, to, userOptions) {
console.log(userOptions[0])
const options = [
{ label: t('props.optionsType.struct'), value: 0 },
{ label: t('props.optionsType.json'), value: 1 },
{ label: '用户数据', value: 2 }
]
const control = [
{
value: 0,
rule: [
{
type: 'TableOptions',
field: 'formCreate' + upper(to).replace('.', '>'),
props: { defaultValue: [] }
}
]
},
{
value: 1,
rule: [
{
type: 'Struct',
field: 'formCreate' + upper(to).replace('.', '>'),
props: { defaultValue: [] }
}
]
},
{
value: 2,
rule: [
{
type: 'TableOptions',
field: 'formCreate' + upper(to).replace('.', '>'),
props: { modelValue: [] }
}
]
}
]
options.splice(0, 0)
control.push()
return {
type: 'radio',
title: t('props.options'),
field: '_optionType',
value: 0,
options,
props: {
type: 'button'
},
control
}
}

View File

@@ -0,0 +1,33 @@
import ImageViewer from './src/ImageViewer.vue'
import { isClient } from '@/utils/is'
import { createVNode, render, VNode } from 'vue'
import { ImageViewerProps } from './src/types'
let instance: Nullable<VNode> = null
export function createImageViewer(options: ImageViewerProps) {
if (!isClient) return
const {
urlList,
initialIndex = 0,
infinite = true,
hideOnClickModal = false,
teleported = false,
zIndex = 2000,
show = true
} = options
const propsData: Partial<ImageViewerProps> = {}
const container = document.createElement('div')
propsData.urlList = urlList
propsData.initialIndex = initialIndex
propsData.infinite = infinite
propsData.hideOnClickModal = hideOnClickModal
propsData.teleported = teleported
propsData.zIndex = zIndex
propsData.show = show
document.body.appendChild(container)
instance = createVNode(ImageViewer, propsData)
render(instance, container)
}

View File

@@ -0,0 +1,35 @@
<script lang="ts" setup>
import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'ImageViewer' })
const props = defineProps({
urlList: {
type: Array as PropType<string[]>,
default: (): string[] => []
},
zIndex: propTypes.number.def(200),
initialIndex: propTypes.number.def(0),
infinite: propTypes.bool.def(true),
hideOnClickModal: propTypes.bool.def(false),
teleported: propTypes.bool.def(false),
show: propTypes.bool.def(false)
})
const getBindValue = computed(() => {
const propsData: Recordable = { ...props }
delete propsData.show
return propsData
})
const show = ref(props.show)
const close = () => {
show.value = false
}
</script>
<template>
<ElImageViewer v-if="show" v-bind="getBindValue" @close="close" />
</template>

View File

@@ -0,0 +1,9 @@
export interface ImageViewerProps {
urlList?: string[]
zIndex?: number
initialIndex?: number
infinite?: boolean
hideOnClickModal?: boolean
teleported?: boolean
show?: boolean
}

View File

@@ -0,0 +1,111 @@
<template>
<ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索">
<el-select
filterable
:reserve-keyword="false"
remote
placeholder="请输入菜单内容"
:remote-method="remoteMethod"
style="width: 100%"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</ElDialog>
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
<Icon icon="ep:search" />
<el-select
filterable
:reserve-keyword="false"
remote
placeholder="请输入菜单内容"
:remote-method="remoteMethod"
class="overflow-hidden transition-all-600"
:class="showTopSearch ? '!w-220px ml2' : '!w-0'"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</template>
<script lang="ts" setup>
defineProps({
isModal: {
type: Boolean,
default: true
}
})
const router = useRouter() // 路由对象
const showSearch = ref(false) // 是否显示弹框
const showTopSearch = ref(false) // 是否显示顶部搜索框
const value: Ref = ref('') // 用户输入的值
const routers = router.getRoutes() // 路由对象
const options = computed(() => {
// 提示选项
if (!value.value) {
return []
}
const list = routers.filter((item: any) => {
if (item.meta.title?.indexOf(value.value) > -1 || item.path.indexOf(value.value) > -1) {
return true
}
})
return list.map((item) => {
return {
label: `${item.meta.title}${item.path}`,
value: item.path
}
})
})
function remoteMethod(data) {
// 这里可以执行相应的操作(例如打开搜索框等)
value.value = data
}
function handleChange(path) {
router.push({ path })
hiddenTopSearch()
}
function hiddenTopSearch() {
showTopSearch.value = false
}
onMounted(() => {
window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch)
})
onUnmounted(() => {
window.removeEventListener('keydown', listenKey)
window.removeEventListener('click', hiddenTopSearch)
})
// 监听 ctrl + k
function listenKey(event) {
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
showSearch.value = !showSearch.value
// 这里可以执行相应的操作(例如打开搜索框等)
}
}
defineExpose({
openSearch: () => {
showSearch.value = true
}
})
</script>

View File

@@ -1,142 +0,0 @@
<template>
<a-modal
v-model:visible="visible"
title="移动端图标选择"
:mask-closable="false"
:width="800"
:destroy-on-close="true"
:footer="null"
@cancel="onCancel"
>
<a-tabs v-model:activeKey="activeKey" tab-position="left" size="small" @change="paneChange">
<a-tab-pane v-for="item in iconData" :key="item.key" :tab="item.name">
<div v-if="item.iconItem.length > 1" class="xn-icon-select-radio">
<a-radio-group v-model:value="iconItemDefault" @change="radioGroupChange">
<a-radio-button v-for="iconItem in item.iconItem" :key="iconItem.key" :value="iconItem.key">{{
iconItem.name
}}</a-radio-button>
</a-radio-group>
</div>
<div :key="iconItemIns" v-for="iconItemIns in item.iconItem">
<div v-if="iconItemIns.key === iconItemDefault" class="xn-icon-select-list">
<ul>
<li
v-for="icon in iconItemIns.item"
:key="icon"
:class="icon === modelValue ? 'active' : ''"
@click="selectIcon(icon.font_class)"
>
<span class="snowy xn-icons" :class="icon.font_class"></span>
</li>
</ul>
</div>
</div>
</a-tab-pane>
</a-tabs>
</a-modal>
</template>
<script>
import config from '@/assets/icons/mobile'
export default {
data() {
return {
visible: false,
iconData: [],
modelValue: '',
activeKey: 'default',
iconItemDefault: 'default'
}
},
mounted() {
this.iconData.push(...config.icons)
},
methods: {
// 打开
showIconModal(value) {
this.visible = true
this.defaultSetting(value)
},
// 默认配置
defaultSetting(value) {
if (value) {
this.modelValue = value
// 判断展开哪个
if (value.indexOf('-outlined') > -1 || value.indexOf('-filled') > -1 || value.indexOf('-two-tone') > -1) {
this.activeKey = 'default'
if (value.indexOf('-two-tone') > -1) {
this.iconItemDefault = 'twotone'
} else if (value.indexOf('-filled') > -1) {
this.iconItemDefault = 'filled'
}
} else if (value.indexOf('-extend') > -1) {
// 扩展列表
this.activeKey = 'extend'
// 如扩展其他顶部单选的情况,默认选中在这里配置,同时这里需要做判断
// this.iconItemDefault = '您的json中配置的'
}
}
},
// 切换标签页,如果是切换到了没用额外的标签页的地方,我们将其置为默认
paneChange(e) {
if (e.indexOf('default') === -1) {
this.iconItemDefault = 'default'
}
},
// 切换icon风格
radioGroupChange(e) {
this.iconItemDefault = e.target.value
},
// 选择图标后关闭并返回
selectIcon(value) {
this.defaultValue = value
this.visible = false
// eslint-disable-next-line vue/require-explicit-emits
this.$emit('iconCallBack', this.defaultValue)
},
onCancel() {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
.xn-icon-select-radio {
padding-left: 5px;
padding-bottom: 10px;
}
.xn-icons {
font-size: 26px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.xn-icon-select-list {
height: 360px;
overflow: auto;
}
.xn-icon-select-list ul {
li {
display: inline-block;
width: 60px;
height: 60px;
padding: 18px;
margin: 5px;
border-radius: 2px;
vertical-align: top;
box-shadow: 0 0 0 1px var(--border-color-split);
transition: all 0.1s;
position: relative;
&:hover,
&.active {
cursor: pointer;
color: #ffffff;
background-color: var(--primary-color);
}
}
}
</style>

View File

@@ -1,143 +0,0 @@
<template>
<a-modal
v-model:visible="visible"
title="图标选择"
:mask-closable="false"
:width="800"
:destroy-on-close="true"
:footer="null"
@cancel="onCancel"
>
<a-tabs v-model:activeKey="activeKey" tab-position="left" size="small" @change="paneChange">
<a-tab-pane v-for="item in iconData" :key="item.key" :tab="item.name">
<div v-if="item.iconItem.length > 1" class="xn-icon-select-radio">
<a-radio-group v-model:value="iconItemDefault" @change="radioGroupChange">
<a-radio-button v-for="iconItem in item.iconItem" :key="iconItem.key" :value="iconItem.key">{{
iconItem.name
}}</a-radio-button>
</a-radio-group>
</div>
<div :key="iconItemIns" v-for="iconItemIns in item.iconItem">
<div v-if="iconItemIns.key === iconItemDefault" class="xn-icon-select-list">
<ul>
<li
v-for="icon in iconItemIns.item"
:key="icon"
:class="icon === modelValue ? 'active' : ''"
@click="selectIcon(icon)"
>
<component :is="icon" class="xn-icons" />
</li>
</ul>
</div>
</div>
</a-tab-pane>
</a-tabs>
</a-modal>
</template>
<script>
import config from '@/config/iconSelect'
export default {
data() {
return {
visible: false,
iconData: [],
modelValue: '',
activeKey: 'default',
iconItemDefault: 'default'
}
},
mounted() {
this.iconData.push(...config.icons)
},
methods: {
// 打开
showIconModal(value) {
this.visible = true
this.defaultSetting(value)
},
// 默认配置
defaultSetting(value) {
if (value) {
this.modelValue = value
// 判断展开哪个
if (value.indexOf('-outlined') > -1 || value.indexOf('-filled') > -1 || value.indexOf('-two-tone') > -1) {
this.activeKey = 'default'
if (value.indexOf('-two-tone') > -1) {
this.iconItemDefault = 'twotone'
} else if (value.indexOf('-filled') > -1) {
this.iconItemDefault = 'filled'
}
} else if (value.indexOf('-extend') > -1) {
// 扩展列表
this.activeKey = 'extend'
// 如扩展其他顶部单选的情况,默认选中在这里配置,同时这里需要做判断
// this.iconItemDefault = '您的json中配置的'
}
}
},
// 切换标签页,如果是切换到了没用额外的标签页的地方,我们将其置为默认
paneChange(e) {
if (e.indexOf('default') === -1) {
this.iconItemDefault = 'default'
}
},
// 切换icon风格
radioGroupChange(e) {
this.iconItemDefault = e.target.value
},
// 选择图标后关闭并返回
selectIcon(value) {
this.defaultValue = value
this.visible = false
// eslint-disable-next-line vue/require-explicit-emits
this.$emit('iconCallBack', this.defaultValue)
},
onCancel() {
this.visible = false
}
}
}
</script>
<style lang="less" scoped>
.xn-icon-select-radio {
padding-left: 5px;
padding-bottom: 10px;
}
.xn-icons {
font-size: 26px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.xn-icon-select-list {
height: 360px;
overflow: auto;
}
.xn-icon-select-list ul {
li {
display: inline-block;
width: 60px;
height: 60px;
padding: 18px;
margin: 5px;
border-radius: 2px;
vertical-align: top;
box-shadow: 0 0 0 1px var(--border-color-split);
transition: all 0.1s;
position: relative;
&:hover,
&.active {
cursor: pointer;
color: #ffffff;
background-color: var(--primary-color);
}
}
}
</style>

View File

@@ -1,496 +0,0 @@
<template>
<el-dialog v-model="visible" title="机构选择" :align-center="true" width="90%" style="height: 600px">
<!-- :mask-closable="false"
:destroy-on-close="true"
@ok="handleOk"
@cancel="handleClose" -->
<div class="drawer_body">
<el-row :gutter="10">
<el-col :span="7">
<el-card size="small" :loading="cardLoading" class="selectorTreeDiv">
<el-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:data="treeData"
:field-names="treeFieldNames"
:props="defaultProps"
accordion
:default-expand-all="true"
@node-click="treeSelect"
></el-tree>
</el-card>
</el-col>
<el-col :span="11">
<div class="table-operator" style="margin-bottom: 10px">
<el-form
ref="searchFormRef"
name="advanced_search"
class="ant-advanced-search-form"
:model="searchFormState"
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item name="searchKey">
<el-input
v-model="searchFormState.searchKey"
placeholder="请输入机构名"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-button type="primary" class="primarySele" @click="loadData()">查询</el-button>
<el-button class="snowy-buttom-left" @click="() => reset()">重置</el-button>
</el-col>
</el-row>
</el-form>
</div>
<div class="org-table">
<div class="org_table_head">
<p>待选择列表</p>
<div class="org_table_head_add">
添加当前数据
</div>
</div>
<el-table
ref="table"
size="small"
:columns="commons"
:data="tableData"
:expand-row-by-click="true"
:loading="pageLoading"
bordered
:pagination="false"
border
stripe
height="330"
>
<template #title>
<span>待选择列表 {{ tableRecordNum }} </span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" size="small" @click="addAllPageRecord">
添加当前数据
</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'category'">
{{ $TOOL.dictTypeData('ORG_CATEGORY', record.category) }}
</template>
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" size="small" @click="addRecord(record)">添加</el-button>
</template>
</template>
<el-table-column prop="action" label="操作" width="80" align="center">
<el-button type="dashed" size="small" @click="addRecord(record)">添加</el-button>
</el-table-column>
<el-table-column prop="name" label="机构名" />
<el-table-column prop="type" label="分类"></el-table-column>
</el-table>
<div class="mt-2">
<el-pagination
v-if="!isEmpty(tableData)"
v-model:current="current"
v-model:page-size="pageSize"
:total="total"
size="small"
showSizeChanger
@change="paginationChange"
/>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="org-table">
<div class="org_table_heads">
<p>待选择列表</p>
<div class="org_table_head_delete">
全部移除
</div>
</div>
<el-table
ref="selectedTable"
size="small"
:columns="selectedCommons"
:data="selectedData"
:expand-row-by-click="true"
:loading="selectedTableListLoading"
bordered
border
>
<template #title>
<span>已选择: {{ selectedData.length }}</span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" danger size="small" @click="delAllRecord">
全部移除
</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" danger size="small" @click="delRecord(record)">
移除
</el-button>
</template>
</template>
<el-table-column prop="action" label="操作" width="80" align="center">
<el-button type="dashed" size="small" @click="addRecord(record)">添加</el-button>
</el-table-column>
<el-table-column prop="name" label="机构名" />
</el-table>
</div>
</el-col>
</el-row>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="visible = false">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="orgSelectorPlus">
import { ElMessage } from 'element-plus'
import { remove, isEmpty } from 'lodash-es'
import { ref } from 'vue'
// 弹窗是否打开
const visible = ref(false)
// 主表格common
const commons = [
{
label: '操作',
prop: 'action',
align: 'center',
width: 80
},
{
label: '机构名',
prop: 'name',
ellipsis: true
},
{
label: '分类',
prop: 'category'
}
]
// 选中表格的表格common
const selectedCommons = [
{
label: '操作',
prop: 'action',
align: 'center',
width: 80
},
{
label: '机构名',
prop: 'name',
ellipsis: true
}
]
// 主表格的ref 名称
const table = ref()
// 选中表格的ref 名称
const selectedTable = ref()
const tableRecordNum = ref()
const searchFormState = ref({})
const searchFormRef = ref()
const cardLoading = ref(true)
const pageLoading = ref(false)
const selectedTableListLoading = ref(false)
//树状图数据配置
const defaultProps = {
children: 'children',
label: 'name'
}
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 获取机构树数据
const treeData = ref()
// 默认展开二级树的节点id
const defaultExpandedKeys = ref([])
const emit = defineEmits({ onBack: null })
const tableData = ref([])
const selectedData = ref([])
const recordIds = ref()
const props = defineProps(['orgPageApi', 'orgTreeApi', 'radioModel', 'dataIsConverterFlw', 'checkedOrgListApi'])
// 是否是单选
const radioModel = props.radioModel || false
// 数据是否转换成工作流格式
const dataIsConverterFlw = props.dataIsConverterFlw || false
// 分页相关
const current = ref(0) // 当前页数
const pageSize = ref(20) // 每页条数
const total = ref(0) // 数据总数
// 打开弹框
const showOrgPlusModal = ids => {
visible.value = true
if (dataIsConverterFlw) {
ids = goDataConverter(ids)
}
recordIds.value = ids
if (props.orgTreeApi) {
// 获取机构树
props.orgTreeApi().then(data => {
cardLoading.value = false
if (data !== null) {
console.log(data, 'data,++++++++++++')
treeData.value = data.data
// 默认展开2级
treeData.value.forEach(item => {
// 因为0的顶级
if (item.parentId === '0') {
defaultExpandedKeys.value.push(item.id)
// 取到下级ID
if (item.children) {
item.children.forEach(items => {
defaultExpandedKeys.value.push(items.id)
})
}
}
})
}
})
}
searchFormState.value.size = pageSize.value
loadData()
if (props.checkedOrgListApi) {
if (isEmpty(recordIds.value)) {
return
}
const param = {
idList: recordIds.value
}
selectedTableListLoading.value = true
props
.checkedOrgListApi(param)
.then(data => {
selectedData.value = data
})
.finally(() => {
selectedTableListLoading.value = false
})
}
}
// 查询主表格数据
const loadData = () => {
pageLoading.value = true
props
.orgPageApi(searchFormState.value)
.then(data => {
current.value = data.current
total.value = data.total
// 重置、赋值
tableData.value = []
tableRecordNum.value = 0
console.log(data, '999999999999')
tableData.value = data.data.records
if (data.records) {
tableRecordNum.value = data.data.records.length
} else {
tableRecordNum.value = 0
}
})
.finally(() => {
pageLoading.value = false
})
}
// pageSize改变回调分页事件
const paginationChange = (page, pageSize) => {
searchFormState.value.current = page
searchFormState.value.size = pageSize
loadData()
}
const judge = () => {
if (radioModel && selectedData.value.length > 0) {
return false
}
return true
}
// 添加记录
const addRecord = record => {
if (!judge()) {
message.warning('只可选择一条')
return
}
const selectedRecord = selectedData.value.filter(item => item.id === record.id)
if (selectedRecord.length === 0) {
selectedData.value.push(record)
} else {
message.warning('该记录已存在')
}
}
// 添加全部
const addAllPageRecord = () => {
let newArray = selectedData.value.concat(tableData.value)
let list = []
for (let item1 of newArray) {
let flag = true
for (let item2 of list) {
if (item1.id === item2.id) {
flag = false
}
}
if (flag) {
list.push(item1)
}
}
selectedData.value = list
}
// 删减记录
const delRecord = record => {
remove(selectedData.value, item => item.id === record.id)
}
// 删减记录
const delAllRecord = () => {
selectedData.value = []
}
// 点击树查询
const treeSelect = selectedKeys => {
console.log(selectedKeys.id, '111112222333')
searchFormState.value.current = 0
if (selectedKeys.id) {
searchFormState.value.orgId = selectedKeys.id.toString()
} else {
// delete searchFormState.value.orgId
delete searchFormState.value.orgId
}
loadData()
}
// 确定
const handleOk = () => {
const value = []
selectedData.value.forEach(item => {
const obj = {
id: item.id,
name: item.name,
category: item.category
}
value.push(obj)
})
// 判断是否做数据的转换为工作流需要的
if (dataIsConverterFlw) {
emit('onBack', outDataConverter(value))
} else {
emit('onBack', value)
}
handleClose()
}
// 重置
const reset = () => {
delete searchFormState.value.searchKey
loadData()
}
const handleClose = () => {
searchFormState.value = {}
tableRecordNum.value = 0
tableData.value = []
current.value = 0
pageSize.value = 20
total.value = 0
selectedData.value = []
visible.value = false
}
// 数据进入后转换
const goDataConverter = data => {
const resultData = []
if (data.length > 0) {
const values = data[0].value.split(',')
if (JSON.stringify(values) !== '[""]') {
for (let i = 0; i < values.length; i++) {
resultData.push(values[i])
}
}
}
return resultData
}
// 数据出口转换器
const outDataConverter = data => {
const obj = {}
let label = ''
let value = ''
for (let i = 0; i < data.length; i++) {
if (data.length === i + 1) {
label = label + data[i].name
value = value + data[i].id
} else {
label = label + data[i].name + ','
value = value + data[i].id + ','
}
}
obj.key = 'ORG'
obj.label = label
obj.value = value
obj.extJson = ''
return obj
}
defineExpose({
showOrgPlusModal
})
</script>
<style lang="scss" scoped>
.selectorTreeDiv {
height: 500px;
overflow: auto;
}
.cardTag {
margin-left: 10px;
}
.primarySele {
margin-right: 10px;
}
.ant-form-item {
margin-bottom: 0 !important;
}
.org-table {
overflow: auto;
max-height: 450px;
}
.drawer_body {
width: 100%;
height: 350px;
}
.dialog-footer{
position:absolute;
right:10px;
bottom:10px;
z-index:2005;
}
.org_table_head,.org_table_heads{
width:100%;
height: 40px;
border: 1px solid #F0F0F0;
display:flex;
align-items:center;
justify-content: space-between;
padding:0 10px;
p{
font-size: 12px;
}
.org_table_head_add,.org_table_head_delete{
width:100px;
height:24px;
font-size: 12px;
border:2px dashed #F0F0F0;
border-radius:2px;
cursor:pointer;
display:flex;
align-items: center;
justify-content: center;
}
.org_table_head_add:hover{
border:2px dashed #3FA9FF;
color:#3FA9FF;
}
.org_table_head_delete{
width:72px;
height:24px;
border:2px dashed #FF4D4F;
color:#FF4D4F;
}
}
</style>

View File

@@ -1,399 +0,0 @@
<template>
<el-dialog
v-model="visible"
title="职位选择"
:width="1000"
:mask-closable="false"
:destroy-on-close="true"
@ok="handleOk"
@cancel="handleClose"
>
<el-row :gutter="10">
<el-col :span="7">
<el-card size="small" :loading="cardLoading" class="selectorTreeDiv">
<el-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:tree-data="treeData"
:field-names="treeFieldNames"
@node-click="treeSelect"
>
</el-tree>
</el-card>
</el-col>
<el-col :span="11">
<div class="table-operator" style="margin-bottom: 10px">
<el-form ref="searchFormRef" name="advanced_search" class="ant-advanced-search-form" :model="searchFormState">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item name="searchKey">
<el-input v-model:value="searchFormState.searchKey" placeholder="请输入职位名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-button type="primary" class="primarySele" @click="loadData()"> 查询 </el-button>
<el-button class="snowy-buttom-left" @click="() => reset()"> 重置 </el-button>
</el-col>
</el-row>
</el-form>
</div>
<div class="pos-table">
<el-table
ref="table"
size="small"
:columns="commons"
:datel="tableData"
:expand-row-by-click="true"
:loading="pageLoading"
bordered
:pagination="false"
>
<template #title>
<span>待选择列表 {{ tableRecordNum }} </span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" size="small" @click="addAllPageRecord">添加当前数据</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" size="small" @click="addRecord(record)">添加</el-button>
</template>
<template v-if="column.dataIndex === 'category'">
{{ $TOOL.dictTypeData('POSITION_CATEGORY', record.category) }}
</template>
</template>
</el-table>
<div class="mt-2">
<el-pagination
v-if="!isEmpty(tableData)"
v-model:current="current"
v-model:page-size="pageSize"
:total="total"
size="small"
showSizeChanger
@change="paginationChange"
/>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="pos-table">
<el-table
ref="selectedTable"
size="small"
:columns="selectedCommons"
:datel-source="selectedData"
:expand-row-by-click="true"
:loading="selectedTableListLoading"
bordered
>
<template #title>
<span>已选择: {{ selectedData.length }}</span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" danger size="small" @click="delAllRecord">全部移除</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" danger size="small" @click="delRecord(record)">移除</el-button>
</template>
</template>
</el-table>
</div>
</el-col>
</el-row>
</el-dialog>
</template>
<script setup name="posSelectorPlus">
import { ElMessage } from 'element-plus'
import { remove, isEmpty } from 'lodash-es'
import { ref } from 'vue'
// 弹窗是否打开
const visible = ref(false)
// 主表格common
const commons = [
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: 80
},
{
title: '职位名',
dataIndex: 'name',
ellipsis: true
},
{
title: '分类',
dataIndex: 'category'
}
]
// 选中表格的表格common
const selectedCommons = [
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: 80
},
{
title: '职位名',
dataIndex: 'name',
ellipsis: true
}
]
// 主表格的ref 名称
const table = ref()
// 选中表格的ref 名称
const selectedTable = ref()
const tableRecordNum = ref()
const searchFormState = ref({})
const searchFormRef = ref()
const cardLoading = ref(true)
const pageLoading = ref(false)
const selectedTableListLoading = ref(false)
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 获取机构树数据
const treeData = ref()
// 默认展开二级树的节点id
const defaultExpandedKeys = ref([])
const emit = defineEmits({ onBack: null })
const tableData = ref([])
const selectedData = ref([])
const recordIds = ref()
const props = defineProps(['posPageApi', 'orgTreeApi', 'radioModel', 'dataIsConverterFlw', 'checkedPosListApi'])
// 是否是单选
const radioModel = props.radioModel || false
// 数据是否转换成工作流格式
const dataIsConverterFlw = props.dataIsConverterFlw || false
// 分页相关
const current = ref(0) // 当前页数
const pageSize = ref(20) // 每页条数
const total = ref(0) // 数据总数
// 打开弹框
const showPosPlusModal = (ids) => {
visible.value = true
if (dataIsConverterFlw) {
ids = goDataConverter(ids)
}
recordIds.value = ids
// 获取机构树
if (props.orgTreeApi) {
// 获取机构树
props.orgTreeApi().then((data) => {
cardLoading.value = false
if (data !== null) {
treeData.value = data
// 默认展开2级
treeData.value.forEach((item) => {
// 因为0的顶级
if (item.parentId === '0') {
defaultExpandedKeys.value.push(item.id)
// 取到下级ID
if (item.children) {
item.children.forEach((items) => {
defaultExpandedKeys.value.push(items.id)
})
}
}
})
}
})
}
searchFormState.value.size = pageSize.value
loadData()
if (props.checkedPosListApi) {
if (isEmpty(recordIds.value)) {
return
}
const param = {
idList: recordIds.value
}
selectedTableListLoading.value = true
props
.checkedPosListApi(param)
.then((data) => {
selectedData.value = data
})
.finally(() => {
selectedTableListLoading.value = false
})
}
}
// 查询主表格数据
const loadData = () => {
pageLoading.value = true
props
.posPageApi(searchFormState.value)
.then((data) => {
current.value = data.current
total.value = data.total
// 重置、赋值
tableData.value = []
tableRecordNum.value = 0
tableData.value = data.records
if (data.records) {
tableRecordNum.value = data.records.length
} else {
tableRecordNum.value = 0
}
})
.finally(() => {
pageLoading.value = false
})
}
// pageSize改变回调分页事件
const paginationChange = (page, pageSize) => {
searchFormState.value.current = page
searchFormState.value.size = pageSize
loadData()
}
const judge = () => {
if (radioModel && selectedData.value.length > 0) {
return false
}
return true
}
// 添加记录
const addRecord = (record) => {
if (!judge()) {
message.warning('只可选择一条')
return
}
const selectedRecord = selectedData.value.filter((item) => item.id === record.id)
if (selectedRecord.length === 0) {
selectedData.value.push(record)
} else {
message.warning('该记录已存在')
}
}
// 添加全部
const addAllPageRecord = () => {
let newArray = selectedData.value.concat(tableData.value)
let list = []
for (let item1 of newArray) {
let flag = true
for (let item2 of list) {
if (item1.id === item2.id) {
flag = false
}
}
if (flag) {
list.push(item1)
}
}
selectedData.value = list
}
// 删减记录
const delRecord = (record) => {
remove(selectedData.value, (item) => item.id === record.id)
}
// 删减记录
const delAllRecord = () => {
selectedData.value = []
}
// 点击树查询
const treeSelect = (selectedKeys) => {
searchFormState.value.current = 0
if (selectedKeys.length > 0) {
searchFormState.value.orgId = selectedKeys.toString()
} else {
delete searchFormState.value.orgId
}
loadData()
}
// 确定
const handleOk = () => {
const value = []
selectedData.value.forEach((item) => {
const obj = {
id: item.id,
name: item.name,
account: item.account
}
value.push(obj)
})
// 判断是否做数据的转换为工作流需要的
if (dataIsConverterFlw) {
emit('onBack', outDataConverter(value))
} else {
emit('onBack', value)
}
handleClose()
}
// 重置
const reset = () => {
delete searchFormState.value.searchKey
loadData()
}
const handleClose = () => {
searchFormState.value = {}
tableRecordNum.value = 0
tableData.value = []
current.value = 0
pageSize.value = 20
total.value = 0
selectedData.value = []
visible.value = false
}
// 数据进入后转换
const goDataConverter = (data) => {
const resultData = []
if (data.length > 0) {
const values = data[0].value.split(',')
if (JSON.stringify(values) !== '[""]') {
for (let i = 0; i < values.length; i++) {
resultData.push(values[i])
}
}
}
return resultData
}
// 数据出口转换器
const outDataConverter = (data) => {
const obj = {}
let label = ''
let value = ''
for (let i = 0; i < data.length; i++) {
if (data.length === i + 1) {
label = label + data[i].name
value = value + data[i].id
} else {
label = label + data[i].name + ','
value = value + data[i].id + ','
}
}
obj.key = 'POSITION'
obj.label = label
obj.value = value
obj.extJson = ''
return obj
}
defineExpose({
showPosPlusModal
})
</script>
<style lang="less" scoped>
.selectorTreeDiv {
max-height: 500px;
overflow: auto;
}
.cardTag {
margin-left: 10px;
}
.primarySele {
margin-right: 10px;
}
.ant-form-item {
margin-bottom: 0 !important;
}
.pos-table {
overflow: auto;
max-height: 450px;
}
</style>

View File

@@ -1,449 +0,0 @@
<template>
<el-dialog
v-model="visible"
title="角色选择"
:width="1000"
:mask-closable="false"
:destroy-on-close="true"
@ok="handleOk"
@cancel="handleClose"
>
<el-row :gutter="10">
<el-col :span="7">
<el-card size="small" :loading="cardLoading" class="selectorTreeDiv">
<el-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:data="treeData"
:field-names="treeFieldNames"
@select="treeSelect"
>
</el-tree>
</el-card>
</el-col>
<el-col :span="11">
<div class="table-operator" style="margin-bottom: 10px">
<el-form ref="searchFormRef" name="advanced_search" class="ant-advanced-search-form" :model="searchFormState">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item name="searchKey">
<el-input v-model:value="searchFormState.searchKey" placeholder="请输入角色名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-button type="primary" class="primarySele" @click="loadData()"> 查询 </el-button>
<el-button class="snowy-buttom-left" @click="() => reset()"> 重置 </el-button>
</el-col>
</el-row>
</el-form>
</div>
<div class="role-table">
<el-table
ref="table"
size="small"
:columns="commons"
:data="tableData"
:expand-row-by-click="true"
:loading="pageLoading"
bordered
:pagination="false"
>
<template #title>
<span>待选择列表 {{ tableRecordNum }} </span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" size="small" @click="addAllPageRecord">添加当前数据</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" size="small" @click="addRecord(record)">添加</el-button>
</template>
<template v-if="column.dataIndex === 'category'">
{{ $TOOL.dictTypeData('ROLE_CATEGORY', record.category) }}
</template>
</template>
</el-table>
<div class="mt-2">
<el-pagination
v-if="!isEmpty(tableData)"
v-model:current="current"
v-model:page-size="pageSize"
:total="total"
size="small"
showSizeChanger
@change="paginationChange"
/>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="role-table">
<el-table
ref="selectedTable"
size="small"
:columns="selectedCommons"
:data-source="selectedData"
:expand-row-by-click="true"
:loading="selectedTableListLoading"
bordered
>
<template #title>
<span>已选择: {{ selectedData.length }}</span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" danger size="small" @click="delAllRecord">全部移除</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" danger size="small" @click="delRecord(record)">移除</el-button>
</template>
</template>
</el-table>
</div>
</el-col>
</el-row>
</el-dialog>
</template>
<script setup name="roleSelectorPlus">
import { ElMessage } from 'element-plus'
import { remove, isEmpty } from 'lodash-es'
import { ref } from 'vue'
// 弹窗是否打开
const visible = ref(false)
// 主表格common
const commons = [
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: 80
},
{
title: '角色名',
dataIndex: 'name',
ellipsis: true
},
{
title: '分类',
dataIndex: 'category'
}
]
// 选中表格的表格common
const selectedCommons = [
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: 80
},
{
title: '角色名',
dataIndex: 'name',
ellipsis: true
}
]
// 主表格的ref 名称
const table = ref()
// 选中表格的ref 名称
const selectedTable = ref()
const tableRecordNum = ref()
const searchFormState = ref({})
const searchFormRef = ref()
const cardLoading = ref(true)
const pageLoading = ref(false)
const selectedTableListLoading = ref(false)
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 获取机构树数据
const treeData = ref()
// 默认展开二级树的节点id
const defaultExpandedKeys = ref([])
const emit = defineEmits({ onBack: null })
const tableData = ref([])
const selectedData = ref([])
const recordIds = ref()
const props = defineProps({
rolePageApi: {
type: Function
},
orgTreeApi: {
type: Function
},
// 是否是单选
radioModel: {
type: Boolean,
default: false,
required: false
},
// 数据是否转换成工作流格式
dataIsConverterFlw: {
type: Boolean,
default: false,
required: false
},
// 是否展示‘全局’这个节点
roleGlobal: {
type: Boolean,
default: true,
required: false
},
checkedRoleListApi: {
type: Function
}
})
// 是否是单选
const radioModel = props.radioModel
// 数据是否转换成工作流格式
const dataIsConverterFlw = props.dataIsConverterFlw
// 是否展示‘全局’这个节点
const roleGlobal = props.roleGlobal
// 分页相关
const current = ref(0) // 当前页数
const pageSize = ref(20) // 每页条数
const total = ref(0) // 数据总数
// 打开弹框
const showRolePlusModal = (ids) => {
visible.value = true
if (dataIsConverterFlw) {
ids = goDataConverter(ids)
}
recordIds.value = ids
if (props.orgTreeApi) {
// 获取机构树
props.orgTreeApi().then((data) => {
cardLoading.value = false
if (data !== null) {
treeData.value = data
// 树中插入全局角色类型
if (roleGlobal) {
const globalRoleType = [
{
id: 'GLOBAL',
parentId: '-1',
name: '全局'
}
]
treeData.value = globalRoleType.concat(data)
}
// 默认展开2级
treeData.value.forEach((item) => {
// 因为0的顶级
if (item.parentId === '0') {
defaultExpandedKeys.value.push(item.id)
// 取到下级ID
if (item.children) {
item.children.forEach((items) => {
defaultExpandedKeys.value.push(items.id)
})
}
}
})
}
})
}
searchFormState.value.size = pageSize.value
loadData()
if (props.checkedRoleListApi) {
if (isEmpty(recordIds.value)) {
return
}
const param = {
idList: recordIds.value
}
selectedTableListLoading.value = true
props
.checkedRoleListApi(param)
.then((data) => {
selectedData.value = data
})
.finally(() => {
selectedTableListLoading.value = false
})
}
}
// 查询主表格数据
const loadData = () => {
// 如果不是用全局的,我们每次查询加上这个条件
if (!roleGlobal) {
searchFormState.value.category = 'ORG'
}
pageLoading.value = true
props
.rolePageApi(searchFormState.value)
.then((data) => {
current.value = data.current
total.value = data.total
// 重置、赋值
tableData.value = []
tableRecordNum.value = 0
tableData.value = data.records
if (data.records) {
tableRecordNum.value = data.records.length
} else {
tableRecordNum.value = 0
}
})
.finally(() => {
pageLoading.value = false
})
}
// pageSize改变回调分页事件
const paginationChange = (page, pageSize) => {
searchFormState.value.current = page
searchFormState.value.size = pageSize
loadData()
}
const judge = () => {
if (radioModel && selectedData.value.length > 0) {
return false
}
return true
}
// 添加记录
const addRecord = (record) => {
if (!judge()) {
message.warning('只可选择一条')
return
}
const selectedRecord = selectedData.value.filter((item) => item.id === record.id)
if (selectedRecord.length === 0) {
selectedData.value.push(record)
} else {
message.warning('该记录已存在')
}
}
// 添加全部
const addAllPageRecord = () => {
let newArray = selectedData.value.concat(tableData.value)
let list = []
for (let item1 of newArray) {
let flag = true
for (let item2 of list) {
if (item1.id === item2.id) {
flag = false
}
}
if (flag) {
list.push(item1)
}
}
selectedData.value = list
}
// 删减记录
const delRecord = (record) => {
remove(selectedData.value, (item) => item.id === record.id)
}
// 删减记录
const delAllRecord = () => {
selectedData.value = []
}
// 点击树查询
const treeSelect = (selectedKeys) => {
searchFormState.value.current = 0
if (selectedKeys.length > 0) {
if (selectedKeys[0] === 'GLOBAL') {
searchFormState.value.category = selectedKeys[0]
delete searchFormState.value.orgId
} else {
searchFormState.value.orgId = selectedKeys.toString()
delete searchFormState.value.category
}
} else {
delete searchFormState.value.category
delete searchFormState.value.orgId
}
loadData()
}
// 确定
const handleOk = () => {
const value = []
selectedData.value.forEach((item) => {
const obj = {
id: item.id,
name: item.name
}
value.push(obj)
})
// 判断是否做数据的转换为工作流需要的
if (dataIsConverterFlw) {
emit('onBack', outDataConverter(value))
} else {
emit('onBack', value)
}
handleClose()
}
// 重置
const reset = () => {
delete searchFormState.value.searchKey
loadData()
}
const handleClose = () => {
searchFormState.value = {}
tableRecordNum.value = 0
tableData.value = []
current.value = 0
pageSize.value = 20
total.value = 0
selectedData.value = []
visible.value = false
}
// 数据进入后转换
const goDataConverter = (data) => {
const resultData = []
if (data.length > 0) {
const values = data[0].value.split(',')
if (JSON.stringify(values) !== '[""]') {
for (let i = 0; i < values.length; i++) {
resultData.push(values[i])
}
}
}
return resultData
}
// 数据出口转换器
const outDataConverter = (data) => {
const obj = {}
let label = ''
let value = ''
for (let i = 0; i < data.length; i++) {
if (data.length === i + 1) {
label = label + data[i].name
value = value + data[i].id
} else {
label = label + data[i].name + ','
value = value + data[i].id + ','
}
}
obj.key = 'ROLE'
obj.label = label
obj.value = value
obj.extJson = ''
return obj
}
defineExpose({
showRolePlusModal
})
</script>
<style lang="less" scoped>
.selectorTreeDiv {
max-height: 500px;
overflow: auto;
}
.cardTag {
margin-left: 20px;
}
.primarySele {
margin-right: 20px;
}
.ant-form-item {
margin-bottom: 0 !important;
}
.role-table {
overflow: auto;
max-height: 450px;
}
</style>

View File

@@ -1,401 +0,0 @@
<template>
<el-dialog
v-model="visible"
title="用户选择"
:width="1000"
:mask-closable="false"
:destroy-on-close="true"
@ok="handleOk"
@cancel="handleClose"
:append-to-body="true"
>
<el-row :gutter="10">
<el-col :span="7">
<el-card size="small" :loading="cardLoading" class="selectorTreeDiv">
<el-tree
v-if="treeData"
v-model:expandedKeys="defaultExpandedKeys"
:data="treeData"
:field-names="treeFieldNames"
@node-click="treeSelect"
>
</el-tree>
</el-card>
</el-col>
<el-col :span="11">
<div class="table-operator" style="margin-bottom: 10px">
<el-form ref="searchFormRef" name="advanced_search" class="ant-advanced-search-form" :model="searchFormState">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item name="searchKey">
<el-input v-model:value="searchFormState.searchKey" placeholder="请输入用户名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-button type="primary" class="primarySele" @click="loadData()"> 查询 </el-button>
<el-button class="snowy-buttom-left" @click="reset()"> 重置 </el-button>
</el-col>
</el-row>
</el-form>
</div>
<div class="user-table">
<el-table
ref="table"
size="small"
:columns="commons"
:data-source="tableData"
:expand-row-by-click="true"
:loading="pageLoading"
bordered
:pagination="false"
>
<template #title>
<span>待选择列表 {{ tableRecordNum }} </span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" size="small" @click="addAllPageRecord">添加当前数据</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" size="small" @click="addRecord(record)">添加</el-button>
</template>
<template v-if="column.dataIndex === 'category'">
{{ $TOOL.dictTypeData('ROLE_CATEGORY', record.category) }}
</template>
</template>
</el-table>
<div class="mt-2">
<el-pagination
v-if="!isEmpty(tableData)"
v-model:current="current"
v-model:page-size="pageSize"
:total="total"
size="small"
showSizeChanger
@change="paginationChange"
/>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="user-table">
<el-table
ref="selectedTable"
size="small"
:columns="selectedCommons"
:data-source="selectedData"
:expand-row-by-click="true"
:loading="selectedTableListLoading"
bordered
>
<template #title>
<span>已选择: {{ selectedData.length }}</span>
<div v-if="!radioModel" style="float: right">
<el-button type="dashed" danger size="small" @click="delAllRecord">全部移除</el-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'">
<el-button type="dashed" danger size="small" @click="delRecord(record)">移除</el-button>
</template>
</template>
</el-table>
</div>
</el-col>
</el-row>
</el-dialog>
</template>
<script setup name="userSelectorPlus">
import { ElMessage } from 'element-plus'
import { remove, isEmpty } from 'lodash-es'
import { ref } from 'vue'
// 弹窗是否打开
const visible = ref(false)
// 主表格common
const commons = [
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: 80
},
{
title: '用户名',
dataIndex: 'name',
ellipsis: true
},
{
title: '账号',
dataIndex: 'account'
}
]
// 选中表格的表格common
const selectedCommons = [
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: 80
},
{
title: '用户名',
dataIndex: 'name',
ellipsis: true
}
]
// 主表格的ref 名称
const table = ref()
// 选中表格的ref 名称
const selectedTable = ref()
const tableRecordNum = ref()
const searchFormState = ref({})
const searchFormRef = ref()
const cardLoading = ref(true)
const pageLoading = ref(false)
const selectedTableListLoading = ref(false)
// 替换treeNode 中 title,key,children
const treeFieldNames = { children: 'children', title: 'name', key: 'id' }
// 获取机构树数据
const treeData = ref()
// 默认展开二级树的节点id
const defaultExpandedKeys = ref([])
const emit = defineEmits({ onBack: null })
const tableData = ref([])
const selectedData = ref([])
const recordIds = ref()
const props = defineProps(['radioModel', 'dataIsConverterFlw', 'orgTreeApi', 'userPageApi', 'checkedUserListApi'])
// 是否是单选
const radioModel = props.radioModel || false
// 数据是否转换成工作流格式
const dataIsConverterFlw = props.dataIsConverterFlw || false
// 分页相关
const current = ref(0) // 当前页数
const pageSize = ref(20) // 每页条数
const total = ref(0) // 数据总数
// 打开弹框
const showUserPlusModal = (ids = []) => {
visible.value = true
if (dataIsConverterFlw) {
ids = goDataConverter(ids)
}
recordIds.value = ids
// 加载机构树
if (props.orgTreeApi) {
// 获取机构树
props.orgTreeApi().then((data) => {
cardLoading.value = false
if (data !== null) {
treeData.value = data.data
// 默认展开2级
treeData.value.forEach((item) => {
// 因为0的顶级
if (item.parentId === '0') {
defaultExpandedKeys.value.push(item.id)
// 取到下级ID
if (item.children) {
item.children.forEach((items) => {
defaultExpandedKeys.value.push(items.id)
})
}
}
})
}
})
}
searchFormState.value.size = pageSize.value
loadData()
if (props.checkedUserListApi) {
if (isEmpty(recordIds.value)) {
return
}
const param = {
idList: recordIds.value
}
selectedTableListLoading.value = true
props
.checkedUserListApi(param)
.then((data) => {
selectedData.value = data
})
.finally(() => {
selectedTableListLoading.value = false
})
}
}
// 查询主表格数据
const loadData = () => {
pageLoading.value = true
props
.userPageApi(searchFormState.value)
.then((data) => {
current.value = data.current
// pageSize.value = data.size
total.value = data.total
// 重置、赋值
tableData.value = []
tableRecordNum.value = 0
tableData.value = data.records
if (data.records) {
tableRecordNum.value = data.records.length
} else {
tableRecordNum.value = 0
}
})
.finally(() => {
pageLoading.value = false
})
}
// pageSize改变回调分页事件
const paginationChange = (page, pageSize) => {
searchFormState.value.current = page
searchFormState.value.size = pageSize
loadData()
}
const judge = () => {
if (radioModel && selectedData.value.length > 0) {
return false
}
return true
}
// 添加记录
const addRecord = (record) => {
if (!judge()) {
message.warning('只可选择一条')
return
}
const selectedRecord = selectedData.value.filter((item) => item.id === record.id)
if (selectedRecord.length === 0) {
selectedData.value.push(record)
} else {
message.warning('该记录已存在')
}
}
// 添加全部
const addAllPageRecord = () => {
let newArray = selectedData.value.concat(tableData.value)
let list = []
for (let item1 of newArray) {
let flag = true
for (let item2 of list) {
if (item1.id === item2.id) {
flag = false
}
}
if (flag) {
list.push(item1)
}
}
selectedData.value = list
}
// 删减记录
const delRecord = (record) => {
remove(selectedData.value, (item) => item.id === record.id)
}
// 删减记录
const delAllRecord = () => {
selectedData.value = []
}
// 点击树查询
const treeSelect = (selectedKeys) => {
searchFormState.value.current = 0
if (selectedKeys.length > 0) {
searchFormState.value.orgId = selectedKeys.toString()
} else {
delete searchFormState.value.orgId
}
loadData()
}
// 确定
const handleOk = () => {
const value = []
selectedData.value.forEach((item) => {
const obj = {
id: item.id,
name: item.name,
account: item.account
}
value.push(obj)
})
// 判断是否做数据的转换为工作流需要的
if (dataIsConverterFlw) {
emit('onBack', outDataConverter(value))
} else {
emit('onBack', value)
}
handleClose()
}
// 重置
const reset = () => {
delete searchFormState.value.searchKey
loadData()
}
const handleClose = () => {
searchFormState.value = {}
tableRecordNum.value = 0
tableData.value = []
current.value = 0
pageSize.value = 20
total.value = 0
selectedData.value = []
visible.value = false
}
// 数据进入后转换
const goDataConverter = (data) => {
const resultData = []
if (data.length > 0) {
const values = data[0].value.split(',')
if (JSON.stringify(values) !== '[""]') {
for (let i = 0; i < values.length; i++) {
resultData.push(values[i])
}
}
}
return resultData
}
// 数据出口转换器
const outDataConverter = (data) => {
const obj = {}
let label = ''
let value = ''
for (let i = 0; i < data.length; i++) {
if (data.length === i + 1) {
label = label + data[i].name
value = value + data[i].id
} else {
label = label + data[i].name + ','
value = value + data[i].id + ','
}
}
obj.key = 'USER'
obj.label = label
obj.value = value
obj.extJson = ''
return obj
}
defineExpose({
showUserPlusModal
})
</script>
<style lang="less" scoped>
.selectorTreeDiv {
max-height: 500px;
overflow: auto;
}
.cardTag {
margin-left: 10px;
}
.primarySele {
margin-right: 10px;
}
.ant-form-item {
margin-bottom: 0 !important;
}
.user-table {
overflow: auto;
max-height: 450px;
}
</style>

View File

@@ -0,0 +1,5 @@
import UploadImg from './src/UploadImg.vue'
import UploadImgs from './src/UploadImgs.vue'
import UploadFile from './src/UploadFile.vue'
export { UploadImg, UploadImgs, UploadFile }

View File

@@ -0,0 +1,214 @@
<template>
<div class='upload-file'>
<el-upload
ref='uploadRef'
v-model:file-list='fileList'
:action='uploadUrl'
:auto-upload='autoUpload'
:before-upload='beforeUpload'
:disabled='disabled'
:drag='drag'
:http-request='httpRequest'
:limit='props.limit'
:multiple='props.limit > 1'
:on-error='excelUploadError'
:on-exceed='handleExceed'
:on-preview='handlePreview'
:on-remove='handleRemove'
:on-success='handleFileSuccess'
:show-file-list='true'
class='upload-file-uploader'
name='file'
>
<el-button v-if='!disabled' type='primary'>
<UploadFilled />
选取文件
</el-button>
<template v-if='isShowTip && !disabled' #tip>
<div style='font-size: 8px'>
大小不超过 <b style='color: #f56c6c'>{{ fileSize }}MB</b>
</div>
<div style='font-size: 8px'>
格式为 <b style='color: #f56c6c'>{{ fileType.join('/') }}</b> 的文件
</div>
</template>
<template #file='row'>
<div class='flex items-center'>
<span>{{ row.file.name }}</span>
<div class='ml-10px'>
<el-link
:href='row.file.url'
:underline='false'
download
target='_blank'
type='primary'
>
下载
</el-link>
</div>
<div class='ml-10px'>
<el-button link type='danger' @click='handleRemove(row.file)'> 删除</el-button>
</div>
</div>
</template>
</el-upload>
</div>
</template>
<script lang='ts' setup>
import { propTypes } from '@/utils/propTypes'
import { UploadFilled } from '@element-plus/icons-vue'
import type { UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
import { isString } from '@/utils/is'
import { useUpload } from '@/components/UploadFile/src/useUpload'
import { UploadFile } from 'element-plus/es/components/upload/src/upload'
defineOptions({ name: 'UploadFile' })
const message = useMessage() // 消息弹窗
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
fileSize: propTypes.number.def(5), // 大小限制(MB)
limit: propTypes.number.def(5), // 数量限制
autoUpload: propTypes.bool.def(true), // 自动上传
drag: propTypes.bool.def(false), // 拖拽上传
isShowTip: propTypes.bool.def(true), // 是否显示提示
disabled: propTypes.bool.def(false) // 是否禁用上传组件 ==> 非必传(默认为 false
})
// ========== 上传相关 ==========
const uploadRef = ref<UploadInstance>()
const uploadList = ref<UploadUserFile[]>([])
const fileList = ref<UploadUserFile[]>([])
const uploadNumber = ref<number>(0)
const { uploadUrl, httpRequest } = useUpload()
// 文件上传之前判断
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) {
message.error(`上传文件数量不能超过${props.limit}个!`)
return false
}
let fileExtension = ''
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
}
const isImg = props.fileType.some((type: string) => {
if (file.type.indexOf(type) > -1) return true
return !!(fileExtension && fileExtension.indexOf(type) > -1)
})
const isLimit = file.size < props.fileSize * 1024 * 1024
if (!isImg) {
message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
return false
}
if (!isLimit) {
message.error(`上传文件大小不能超过${props.fileSize}MB!`)
return false
}
message.success('正在上传文件,请稍候...')
uploadNumber.value++
}
// 处理上传的文件发生变化
// const handleFileChange = (uploadFile: UploadFile): void => {
// uploadRef.value.data.path = uploadFile.name
// }
// 文件上传成功
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功')
// 删除自身
const index = fileList.value.findIndex((item) => item.response?.data === res.data)
fileList.value.splice(index, 1)
uploadList.value.push({ name: res.data, url: res.data })
if (uploadList.value.length == uploadNumber.value) {
fileList.value.push(...uploadList.value)
uploadList.value = []
uploadNumber.value = 0
emitUpdateModelValue()
}
}
// 文件数超出提示
const handleExceed: UploadProps['onExceed'] = (): void => {
message.error(`上传文件数量不能超过${props.limit}个!`)
}
// 上传错误提示
const excelUploadError: UploadProps['onError'] = (): void => {
message.error('导入数据失败,请您重新上传!')
}
// 删除上传文件
const handleRemove = (file: UploadFile) => {
const index = fileList.value.map((f) => f.name).indexOf(file.name)
if (index > -1) {
fileList.value.splice(index, 1)
emitUpdateModelValue()
}
}
const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
console.log(uploadFile)
}
// 监听模型绑定值变动
watch(
() => props.modelValue,
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix处理掉缓存表单重置后上传组件的内容并没有重置
return
}
fileList.value = [] // 保障数据为空
// 情况1字符串
if (isString(val)) {
fileList.value.push(
...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
)
return
}
// 情况2数组
fileList.value.push(
...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
)
},
{ immediate: true, deep: true }
)
// 发送文件链接列表更新
const emitUpdateModelValue = () => {
// 情况1数组结果
let result: string | string[] = fileList.value.map((file) => file.url!)
// 情况2逗号分隔的字符串
if (props.limit === 1 || isString(props.modelValue)) {
result = result.join(',')
}
emit('update:modelValue', result)
}
</script>
<style lang='scss' scoped>
.upload-file-uploader {
margin-bottom: 5px;
}
:deep(.upload-file-list .el-upload-list__item) {
position: relative;
margin-bottom: 10px;
line-height: 2;
border: 1px solid #e4e7ed;
}
:deep(.el-upload-list__item-file-name) {
max-width: 250px;
}
:deep(.upload-file-list .ele-upload-list__item-content) {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
:deep(.ele-upload-list__item-content-action .el-link) {
margin-right: 10px;
}
</style>

View File

@@ -0,0 +1,273 @@
<template>
<div class='upload-box'>
<el-upload
:id='uuid'
:accept="fileType.join(',')"
:action='uploadUrl'
:before-upload='beforeUpload'
:class="['upload', drag ? 'no-border' : '']"
:disabled='disabled'
:drag='drag'
:http-request='httpRequest'
:multiple='false'
:on-error='uploadError'
:on-success='uploadSuccess'
:show-file-list='false'
>
<template v-if='modelValue'>
<img :src='modelValue' class='upload-image' />
<div class='upload-handle' @click.stop>
<div v-if='!disabled' class='handle-icon' @click='editImg'>
<Edit />
<span v-if='showBtnText'>{{ t('action.edit') }}</span>
</div>
<div class='handle-icon' @click='imagePreview(modelValue)'>
<ZoomIn />
<span v-if='showBtnText'>{{ t('action.detail') }}</span>
</div>
<div v-if='showDelete && !disabled' class='handle-icon' @click='deleteImg'>
<Delete />
<span v-if='showBtnText'>{{ t('action.del') }}</span>
</div>
</div>
</template>
<template v-else>
<div class='upload-empty'>
<slot name='empty'>
<Plus />
<!-- <span>请上传图片</span> -->
</slot>
</div>
</template>
</el-upload>
<div class='el-upload__tip'>
<slot name='tip'></slot>
</div>
</div>
</template>
<script lang='ts' setup>
import type { UploadProps } from 'element-plus'
import { Plus,Edit,ZoomIn,Delete} from '@element-plus/icons-vue'
import { generateUUID } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { createImageViewer } from '@/components/ImageViewer'
import { useUpload } from '@/components/UploadFile/src/useUpload'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
defineOptions({ name: 'UploadImg' })
type FileTypes =
| 'image/apng'
| 'image/bmp'
| 'image/gif'
| 'image/jpeg'
| 'image/pjpeg'
| 'image/png'
| 'image/svg+xml'
| 'image/tiff'
| 'image/webp'
| 'image/x-icon'
// 接受父组件参数
const props = defineProps({
modelValue: propTypes.string.def(''),
drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true
disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false
fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M
fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"]
height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px
borderradius: propTypes.string.def('8px'), // 组件边框圆角 ==> 非必传(默认为 8px
showDelete: propTypes.bool.def(true), // 是否显示删除按钮
showBtnText: propTypes.bool.def(true) // 是否显示按钮文字
})
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
// 生成组件唯一id
const uuid = ref('id-' + generateUUID())
// 查看图片
const imagePreview = (imgUrl: string) => {
createImageViewer({
zIndex: 9999999,
urlList: [imgUrl]
})
}
const emit = defineEmits(['update:modelValue'])
const deleteImg = () => {
emit('update:modelValue', '')
}
const { uploadUrl, httpRequest } = useUpload()
const editImg = () => {
const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
dom && dom.dispatchEvent(new MouseEvent('click'))
}
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
const imgType = props.fileType
if (!imgType.includes(rawFile.type as FileTypes))
message.notifyWarning('上传图片不符合所需的格式!')
if (!imgSize) message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M`)
return imgType.includes(rawFile.type as FileTypes) && imgSize
}
// 图片上传成功提示
const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功')
emit('update:modelValue', res.data)
}
// 图片上传错误提示
const uploadError = () => {
message.notifyError('图片上传失败,请您重新上传!')
}
</script>
<style lang='scss' scoped>
.is-error {
.upload {
:deep(.el-upload),
:deep(.el-upload-dragger) {
border: 1px dashed var(--el-color-danger) !important;
&:hover {
border-color: var(--el-color-primary) !important;
}
}
}
}
:deep(.disabled) {
.el-upload,
.el-upload-dragger {
cursor: not-allowed !important;
background: var(--el-disabled-bg-color);
border: 1px dashed var(--el-border-color-darker) !important;
&:hover {
border: 1px dashed var(--el-border-color-darker) !important;
}
}
}
.upload-box {
.no-border {
:deep(.el-upload) {
border: none !important;
}
}
:deep(.upload) {
.el-upload {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: v-bind(width);
height: v-bind(height);
overflow: hidden;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderradius);
transition: var(--el-transition-duration-fast);
&:hover {
border-color: var(--el-color-primary);
.upload-handle {
opacity: 1;
}
}
.el-upload-dragger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
overflow: hidden;
background-color: transparent;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderradius);
&:hover {
border: 1px dashed var(--el-color-primary);
}
}
.el-upload-dragger.is-dragover {
background-color: var(--el-color-primary-light-9);
border: 2px dashed var(--el-color-primary) !important;
}
.upload-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.upload-empty {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 30px;
color: var(--el-color-info);
.el-icon {
font-size: 28px;
color: var(--el-text-color-secondary);
}
}
.upload-handle {
position: absolute;
top: 0;
right: 0;
display: flex;
width: 100%;
height: 100%;
cursor: pointer;
background: rgb(0 0 0 / 60%);
opacity: 0;
box-sizing: border-box;
transition: var(--el-transition-duration-fast);
align-items: center;
justify-content: center;
.handle-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 6%;
color: aliceblue;
.el-icon {
margin-bottom: 40%;
font-size: 130%;
line-height: 130%;
}
span {
font-size: 85%;
line-height: 85%;
}
}
}
}
}
.el-upload__tip {
line-height: 18px;
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,324 @@
<template>
<div class='upload-box'>
<el-upload
v-model:file-list='fileList'
:accept="fileType.join(',')"
:action='uploadUrl'
:before-upload='beforeUpload'
:class="['upload', drag ? 'no-border' : '']"
:disabled='disabled'
:drag='drag'
:http-request='httpRequest'
:limit='limit'
:multiple='true'
:on-error='uploadError'
:on-exceed='handleExceed'
:on-success='uploadSuccess'
list-type='picture-card'
>
<div class='upload-empty'>
<slot name='empty'>
<Plus />
<!-- <span>请上传图片</span> -->
</slot>
</div>
<template #file='{ file }'>
<img :src='file.url' class='upload-image' />
<div class='upload-handle' @click.stop>
<div class='handle-icon' @click='handlePictureCardPreview(file)'>
<ZoomIn />
<span>查看</span>
</div>
<div v-if='!disabled' class='handle-icon' @click='handleRemove(file)'>
<Delete />
<span>删除</span>
</div>
</div>
</template>
</el-upload>
<div class='el-upload__tip'>
<slot name='tip'></slot>
</div>
<el-image-viewer
v-if='imgViewVisible'
:url-list='[viewImageUrl]'
@close='imgViewVisible = false'
/>
</div>
</template>
<script lang='ts' setup>
import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
import { ElNotification } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { useUpload } from '@/components/UploadFile/src/useUpload'
import { Plus, Edit, ZoomIn, Delete } from '@element-plus/icons-vue'
defineOptions({ name: 'UploadImgs' })
const message = useMessage() // 消息弹窗
type FileTypes =
| 'image/apng'
| 'image/bmp'
| 'image/gif'
| 'image/jpeg'
| 'image/pjpeg'
| 'image/png'
| 'image/svg+xml'
| 'image/tiff'
| 'image/webp'
| 'image/x-icon'
const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true
disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false
limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传(默认为 5张
fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M
fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"]
height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px
borderradius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px
})
const { uploadUrl, httpRequest } = useUpload()
const fileList = ref<UploadUserFile[]>([])
const uploadNumber = ref<number>(0)
const uploadList = ref<UploadUserFile[]>([])
/**
* @description 文件上传之前判断
* @param rawFile 上传的文件
* */
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
const imgType = props.fileType
if (!imgType.includes(rawFile.type as FileTypes))
ElNotification({
title: '温馨提示',
message: '上传图片不符合所需的格式!',
type: 'warning'
})
if (!imgSize)
ElNotification({
title: '温馨提示',
message: `上传图片大小不能超过 ${props.fileSize}M`,
type: 'warning'
})
uploadNumber.value++
return imgType.includes(rawFile.type as FileTypes) && imgSize
}
// 图片上传成功
interface UploadEmits {
(e: 'update:modelValue', value: string[]): void
}
const emit = defineEmits<UploadEmits>()
const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功')
// 删除自身
const index = fileList.value.findIndex((item) => item.response?.data === res.data)
fileList.value.splice(index, 1)
uploadList.value.push({ name: res.data, url: res.data })
if (uploadList.value.length == uploadNumber.value) {
fileList.value.push(...uploadList.value)
uploadList.value = []
uploadNumber.value = 0
emitUpdateModelValue()
}
}
// 监听模型绑定值变动
watch(
() => props.modelValue,
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix处理掉缓存表单重置后上传组件的内容并没有重置
return
}
fileList.value = [] // 保障数据为空
fileList.value.push(
...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
)
},
{ immediate: true, deep: true }
)
// 发送图片链接列表更新
const emitUpdateModelValue = () => {
let result: string[] = fileList.value.map((file) => file.url!)
emit('update:modelValue', result)
}
// 删除图片
const handleRemove = (uploadFile: UploadFile) => {
fileList.value = fileList.value.filter(
(item) => item.url !== uploadFile.url || item.name !== uploadFile.name
)
emit(
'update:modelValue',
fileList.value.map((file) => file.url!)
)
}
// 图片上传错误提示
const uploadError = () => {
ElNotification({
title: '温馨提示',
message: '图片上传失败,请您重新上传!',
type: 'error'
})
}
// 文件数超出提示
const handleExceed = () => {
ElNotification({
title: '温馨提示',
message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
type: 'warning'
})
}
// 图片预览
const viewImageUrl = ref('')
const imgViewVisible = ref(false)
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
viewImageUrl.value = uploadFile.url!
imgViewVisible.value = true
}
</script>
<style lang='scss' scoped>
.is-error {
.upload {
:deep(.el-upload--picture-card),
:deep(.el-upload-dragger) {
border: 1px dashed var(--el-color-danger) !important;
&:hover {
border-color: var(--el-color-primary) !important;
}
}
}
}
:deep(.disabled) {
.el-upload--picture-card,
.el-upload-dragger {
cursor: not-allowed;
background: var(--el-disabled-bg-color) !important;
border: 1px dashed var(--el-border-color-darker);
&:hover {
border-color: var(--el-border-color-darker) !important;
}
}
}
.upload-box {
.no-border {
:deep(.el-upload--picture-card) {
border: none !important;
}
}
:deep(.upload) {
.el-upload-dragger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
overflow: hidden;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderradius);
&:hover {
border: 1px dashed var(--el-color-primary);
}
}
.el-upload-dragger.is-dragover {
background-color: var(--el-color-primary-light-9);
border: 2px dashed var(--el-color-primary) !important;
}
.el-upload-list__item,
.el-upload--picture-card {
width: v-bind(width);
height: v-bind(height);
background-color: transparent;
border-radius: v-bind(borderradius);
}
.upload-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.upload-handle {
position: absolute;
top: 0;
right: 0;
display: flex;
width: 100%;
height: 100%;
cursor: pointer;
background: rgb(0 0 0 / 60%);
opacity: 0;
box-sizing: border-box;
transition: var(--el-transition-duration-fast);
align-items: center;
justify-content: center;
.handle-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 6%;
color: aliceblue;
.el-icon {
margin-bottom: 15%;
font-size: 140%;
}
span {
font-size: 100%;
}
}
}
.el-upload-list__item {
&:hover {
.upload-handle {
opacity: 1;
}
}
}
.upload-empty {
display: flex;
flex-direction: column;
align-items: center;
font-size: 12px;
line-height: 30px;
color: var(--el-color-info);
.el-icon {
font-size: 28px;
color: var(--el-text-color-secondary);
}
}
}
.el-upload__tip {
line-height: 15px;
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,99 @@
// import * as FileApi from '@/api/infra/file'
import CryptoJS from 'crypto-js'
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
import axios from 'axios'
import { uploadFile } from '@/api/system-boot/file'
export const useUpload = () => {
// 后端上传地址
const uploadUrl = 'import.meta.env.VITE_UPLOAD_URL'
// // 是否使用前端直连上传
// const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE
const isClientUpload = false
// 重写ElUpload上传方法
const httpRequest = async (options: UploadRequestOptions) => {
// 模式一:前端上传
// if (isClientUpload) {
// // 1.1 生成文件名称
// const fileName = await generateFileName(options.file)
// // 1.2 获取文件预签名地址
// const presignedInfo = await FileApi.getFilePresignedUrl(fileName)
// // 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传Minio 不支持)
// return axios.put(presignedInfo.uploadUrl, options.file, {
// headers: {
// 'Content-Type': options.file.type,
// }
// }).then(() => {
// // 1.4. 记录文件信息到后端(异步)
// createFile(presignedInfo, fileName, options.file)
// // 通知成功,数据格式保持与后端上传的返回结果一致
// return { data: presignedInfo.url }
// })
// } else {
// 模式二:后端上传
// 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子
return new Promise((resolve, reject) => {
uploadFile({ file: options.file, path: 'backUpFile' })
.then((res) => {
if (res.code === 0) {
resolve(res)
} else {
reject(res)
}
})
.catch((res) => {
reject(res)
})
})
// }
}
return {
uploadUrl,
httpRequest
}
}
/**
* 创建文件信息
* @param vo 文件预签名信息
* @param name 文件名称
* @param file 文件
*/
function createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: UploadRawFile) {
const fileVo = {
configId: vo.configId,
url: vo.url,
path: name,
name: file.name,
type: file.type,
size: file.size
}
FileApi.createFile(fileVo)
return fileVo
}
/**
* 生成文件名称使用算法SHA256
* @param file 要上传的文件
*/
async function generateFileName(file: UploadRawFile) {
// 读取文件内容
const data = await file.arrayBuffer()
const wordArray = CryptoJS.lib.WordArray.create(data)
// 计算SHA256
const sha256 = CryptoJS.SHA256(wordArray).toString()
// 拼接后缀
const ext = file.name.substring(file.name.lastIndexOf('.'))
return `${sha256}${ext}`
}
/**
* 上传类型
*/
enum UPLOAD_TYPE {
// 客户端直接上传只支持S3服务
CLIENT = 'client',
// 客户端发送到后端上传
SERVER = 'server'
}

View File

@@ -0,0 +1,4 @@
import XButton from './src/XButton.vue'
import XTextButton from './src/XTextButton.vue'
export { XButton, XTextButton }

View File

@@ -0,0 +1,50 @@
<script lang="ts" setup>
import { PropType,computed,useAttrs } from 'vue'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'XButton' })
const props = defineProps({
modelValue: propTypes.bool.def(false),
loading: propTypes.bool.def(false),
preIcon: propTypes.string.def(''),
postIcon: propTypes.string.def(''),
title: propTypes.string.def(''),
type: propTypes.oneOf(['', 'primary', 'success', 'warning', 'danger', 'info']).def(''),
link: propTypes.bool.def(false),
circle: propTypes.bool.def(false),
round: propTypes.bool.def(false),
plain: propTypes.bool.def(false),
onClick: { type: Function as PropType<(...args) => any>, default: null }
})
const getBindValue = computed(() => {
const delArr: string[] = ['title', 'preIcon', 'postIcon', 'onClick']
const attrs = useAttrs()
const obj = { ...attrs, ...props }
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return obj
})
</script>
<template>
<el-button v-bind="getBindValue" @click="onClick">
<Icon v-if="preIcon" :icon="preIcon" class="mr-1px" />
{{ title ? title : '' }}
<Icon v-if="postIcon" :icon="postIcon" class="mr-1px" />
</el-button>
</template>
<style lang="scss" scoped>
:deep(.el-button.is-text) {
padding: 8px 4px;
margin-left: 0;
}
:deep(.el-button.is-link) {
padding: 8px 4px;
margin-left: 0;
}
</style>

View File

@@ -0,0 +1,49 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { PropType } from 'vue'
defineOptions({ name: 'XTextButton' })
const props = defineProps({
modelValue: propTypes.bool.def(false),
loading: propTypes.bool.def(false),
preIcon: propTypes.string.def(''),
postIcon: propTypes.string.def(''),
title: propTypes.string.def(''),
type: propTypes.oneOf(['', 'primary', 'success', 'warning', 'danger', 'info']).def('primary'),
circle: propTypes.bool.def(false),
round: propTypes.bool.def(false),
plain: propTypes.bool.def(false),
onClick: { type: Function as PropType<(...args) => any>, default: null }
})
const getBindValue = computed(() => {
const delArr: string[] = ['title', 'preIcon', 'postIcon', 'onClick']
const attrs = useAttrs()
const obj = { ...attrs, ...props }
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return obj
})
</script>
<template>
<el-button link v-bind="getBindValue" @click="onClick">
<Icon v-if="preIcon" :icon="preIcon" class="mr-1px" />
{{ title ? title : '' }}
<Icon v-if="postIcon" :icon="postIcon" class="mr-1px" />
</el-button>
</template>
<style lang="scss" scoped>
:deep(.el-button.is-text) {
padding: 8px 4px;
margin-left: 0;
}
:deep(.el-button.is-link) {
padding: 8px 4px;
margin-left: 0;
}
</style>

View File

@@ -1,57 +0,0 @@
<template>
<start-events v-if="childNode.type === 'startEvent'" v-model="childNode" :currentActivityId="currentActivityId" />
<start-tasks v-if="childNode.type === 'startTask'" v-model="childNode" :currentActivityId="currentActivityId" />
<user-tasks v-if="childNode.type === 'userTask'" v-model="childNode" :currentActivityId="currentActivityId" />
<service-tasks v-if="childNode.type === 'serviceTask'" v-model="childNode" :currentActivityId="currentActivityId" />
<exclusive-gateways v-if="childNode.type === 'exclusiveGateway'" v-model="childNode" :currentActivityId="currentActivityId">
<template #default="slot">
<c-node-wrap v-if="slot.node" v-model="slot.node.childNode" :currentActivityId="currentActivityId" />
</template>
</exclusive-gateways>
<parallel-gateways v-if="childNode.type === 'parallelGateway'" v-model="childNode" :currentActivityId="currentActivityId">
<template #default="slot">
<c-node-wrap v-if="slot.node" v-model="slot.node.childNode" :currentActivityId="currentActivityId" />
</template>
</parallel-gateways>
<c-node-wrap v-if="childNode.childNode" v-model="childNode.childNode" :currentActivityId="currentActivityId" />
</template>
<script>
import startEvents from './nodes/cStartEvent.vue'
import startTasks from './nodes/cStartTask.vue'
import userTasks from './nodes/cUserTask.vue'
import exclusiveGateways from './nodes/cExclusiveGateway.vue'
import parallelGateways from './nodes/cParallelGateway.vue'
import serviceTasks from './nodes/cServiceTask.vue'
export default {
components: {
startEvents,
startTasks,
userTasks,
exclusiveGateways,
parallelGateways,
serviceTasks
},
props: {
modelValue: { type: Object, default: () => {} },
currentActivityId: { type: String, default: () => '' }
},
data() {
return {
childNode: {}
}
},
watch: {
modelValue(val) {
this.childNode = val
},
childNode(val) {
this.$emit('update:modelValue', val)
}
},
mounted() {
this.childNode = this.modelValue
}
}
</script>

View File

@@ -1,80 +0,0 @@
<template>
<div class="workflow-design">
<div class="workflow-design-btns">
<a-space>
<a-tooltip>
<template #title>放大</template>
<a-button @click="handleZoom(true)" :disabled="zoom > 2">
<template #icon><plus-outlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>缩小</template>
<a-button @click="handleZoom(false)" :disabled="zoom < 1">
<template #icon><minus-outlined /></template>
</a-button>
</a-tooltip>
</a-space>
</div>
<div id="div1">
<div class="box-scale" :style="style">
<node-wrap-chart v-if="childNode" v-model="childNode.childNode" :currentActivityId="currentActivityId" />
<div class="end-node">
<div class="end-node-circle"></div>
<div class="end-node-text">流程结束</div>
</div>
</div>
</div>
</div>
</template>
<script>
import nodeWrapChart from '@/components/XnWorkflow/chart/cNodeWrap.vue'
import FullscreenPreviewHelper from './zoom_helper'
const FullScreenRightSpace = 20
export default {
components: {
nodeWrapChart
},
props: {
modelValue: { type: Object, default: () => {} },
currentActivityId: { type: String, default: () => '' }
},
data() {
return {
childNode: this.modelValue,
style: {},
zoom: 1
}
},
computed: {},
watch: {
modelValue(val) {
this.childNode = val
},
childNode(val) {
this.$emit('update:modelValue', val)
}
},
methods: {
handleGetStyle(zoom) {
return FullscreenPreviewHelper.getZoomStyles(zoom, 1, 0, FullScreenRightSpace)
},
handleZoom(zoomIn) {
const zoom = this.zoom
const zoomData = FullscreenPreviewHelper.getZoomData(zoomIn, zoom)
const style = this.handleGetStyle(zoomData.zoom)
this.style = style
this.zoom = zoomData.zoom
}
}
}
</script>
<style lang="less">
@import '../flowIndex.less';
.workflow-design-btns {
width: 100px;
}
</style>

View File

@@ -1,5 +0,0 @@
<template>
<div class="add-node-btn-box">
<div class="add-node-btn"></div>
</div>
</template>

View File

@@ -1,141 +0,0 @@
<template>
<div class="branch-wrap">
<div class="branch-box-wrap">
<div class="branch-box" style="margin-top: 0px">
<div v-for="(item, index) in childNode.conditionNodeList" :key="index" class="col-box">
<div class="condition-node">
<div class="condition-node-box">
<div class="auto-judge">
<div class="title">
<span class="node-title">{{ item.title }}</span>
<span class="priority-title">优先级{{ item.properties.configInfo.priorityLevel }}</span>
</div>
<div class="content">
<span v-if="toText(childNode, index)">{{ toText(childNode, index) }}</span>
<span v-else class="placeholder">请设置条件</span>
</div>
</div>
<add-nodes v-model="item.childNode" />
</div>
</div>
<slot v-if="item.childNode" :node="item"></slot>
<div v-if="index == 0" class="top-left-cover-line"></div>
<div v-if="index == 0" class="bottom-left-cover-line"></div>
<div v-if="index == childNode.conditionNodeList.length - 1" class="top-right-cover-line"></div>
<div v-if="index == childNode.conditionNodeList.length - 1" class="bottom-right-cover-line"></div>
</div>
</div>
<add-nodes v-model="childNode.childNode" :parent-data="childNode" />
</div>
</div>
</template>
<script>
import addNodes from './cAddNode.vue'
export default {
components: {
addNodes
},
props: {
modelValue: { type: Object, default: () => {} },
currentActivityId: { type: String, default: () => '' }
},
data() {
return {
childNode: {},
index: 0,
operatorList: [
{
label: '等于',
value: '=='
},
{
label: '不等于',
value: '!='
},
{
label: '大于',
value: '>'
},
{
label: '大于等于',
value: '>='
},
{
label: '小于',
value: '<'
},
{
label: '小于等于',
value: '<='
},
{
label: '包含',
value: 'include'
},
{
label: '不包含',
value: 'notInclude'
}
]
}
},
watch: {
modelValue() {
this.childNode = this.modelValue
}
},
mounted() {
this.childNode = this.modelValue
},
methods: {
toText(childNode, index) {
const conditionList = childNode.conditionNodeList[index].properties.conditionInfo
const priorityLevel = childNode.conditionNodeList[index].properties.configInfo.priorityLevel
const len = this.childNode.conditionNodeList.length
const priorityLevelMax = this.childNode.conditionNodeList[len - 1].properties.configInfo.priorityLevel
if (JSON.stringify(conditionList) !== undefined && conditionList.length > 0) {
let text = ''
for (let i = 0; i < conditionList.length; i++) {
for (let j = 0; j < conditionList[i].length; j++) {
if (j + 1 !== conditionList[i].length) {
text =
text +
conditionList[i][j].label +
this.getOperatorLabel(conditionList[i][j].operator) +
conditionList[i][j].value +
' 且 '
} else {
text =
text +
conditionList[i][j].label +
this.getOperatorLabel(conditionList[i][j].operator) +
conditionList[i][j].value
}
}
if (i + 1 !== conditionList.length) {
text = text + ' 或 '
}
}
return text
} else if (conditionList.length === 0 && priorityLevel < priorityLevelMax) {
return false
} else {
return '其他条件进入此流程'
}
},
// 通过value 获取界面显示的label汉字
getOperatorLabel(value) {
return this.operatorList.find((item) => item.value === value).label
}
}
}
</script>
<style scoped type="less">
.workflow-design .condition-node {
min-height: 200px !important;
}
</style>

View File

@@ -1,50 +0,0 @@
<template>
<div class="branch-wrap">
<div class="branch-box-wrap">
<div class="branch-box">
<div v-for="(item, index) in childNode.conditionNodeList" :key="index" class="col-box">
<div class="condition-node">
<div class="condition-node-box">
<user-tasks v-model="childNode.conditionNodeList[index]" :currentActivityId="currentActivityId" />
</div>
</div>
<slot v-if="item.childNode" :node="item"></slot>
<div v-if="index == 0" class="top-left-cover-line"></div>
<div v-if="index == 0" class="bottom-left-cover-line"></div>
<div v-if="index == childNode.conditionNodeList.length - 1" class="top-right-cover-line"></div>
<div v-if="index == childNode.conditionNodeList.length - 1" class="bottom-right-cover-line"></div>
</div>
</div>
<add-nodes v-model="childNode.childNode" :parent-data="childNode" />
</div>
</div>
</template>
<script>
import addNodes from './cAddNode.vue'
import userTasks from './cUserTask.vue'
export default {
components: {
addNodes,
userTasks
},
props: {
modelValue: { type: Object, default: () => {} },
currentActivityId: { type: String, default: () => '' }
},
data() {
return {
childNode: {},
index: 0
}
},
watch: {
modelValue() {
this.childNode = this.modelValue
}
},
mounted() {
this.childNode = this.modelValue
}
}
</script>

View File

@@ -1,80 +0,0 @@
<template>
<div class="node-wrap">
<a-tooltip placement="left">
<template #title v-if="childNode.properties && childNode.properties.commentList.length > 0">
<div v-for="comment in childNode.properties.commentList" :key="comment.id">
{{ comment.userName }}{{ comment.approveTime }}
<a-tag color="rgb(212, 212, 212)">{{ comment.operateText }}</a-tag
>意见{{ comment.comment }}
</div>
</template>
<div :class="childNode.properties && childNode.properties.commentList.length > 0 ? 'node-state-label' : ''">
<div class="node-wrap-box">
<div class="title" style="background: #3296fa">
<send-outlined class="icon" />
<span>{{ childNode.title }}</span>
</div>
<div class="content">
<span v-if="toText(childNode)">{{ toText(childNode) }}</span>
<span v-else class="placeholder">未选择人员</span>
</div>
</div>
</div>
</a-tooltip>
<add-nodes v-model="childNode.childNode" />
</div>
</template>
<script>
import addNodes from './cAddNode.vue'
export default {
components: {
addNodes
},
props: {
modelValue: { type: Object, default: () => {} },
currentActivityId: { type: String, default: () => '' }
},
data() {
return {
childNode: {}
}
},
watch: {
modelValue() {
this.childNode = this.modelValue
}
},
mounted() {
this.childNode = this.modelValue
},
methods: {
toText(childNode) {
if (JSON.stringify(childNode) !== '{}') {
const participateInfo = childNode.properties.participateInfo
if (participateInfo.length > 0) {
let resultArray = []
if (participateInfo[0].label.indexOf(',') !== -1) {
resultArray = participateInfo[0].label.split(',')
} else {
resultArray.push(participateInfo[0].label)
}
return resultArray.toString()
} else {
return false
}
} else {
return false
}
}
}
}
</script>
<style scoped>
.node-state-label {
padding: 1px;
border: 3px solid rgb(24, 144, 255);
border-radius: 2px;
}
</style>

View File

@@ -1,24 +0,0 @@
<template>
<div class="node-wrap"></div>
</template>
<script>
export default {
props: {
modelValue: { type: Object, default: () => {} }
},
data() {
return {
childNode: {}
}
},
watch: {
modelValue() {
this.childNode = this.modelValue
}
},
mounted() {
this.childNode = this.modelValue
}
}
</script>

View File

@@ -1,76 +0,0 @@
<template>
<div class="node-wrap">
<a-tooltip placement="left">
<template #title v-if="childNode.properties && childNode.properties.commentList.length > 0">
<div v-for="comment in childNode.properties.commentList" :key="comment.id">
{{ comment.userName }}{{ comment.approveTime }}
<a-tag color="rgb(212, 212, 212)">{{ comment.operateText }}</a-tag
>意见{{ comment.comment }}
</div>
</template>
<div :class="getClassName()">
<div class="node-wrap-box start-node">
<div class="title" style="background: #576a95">
<user-outlined class="icon" />
<span>{{ childNode.title }}</span>
</div>
<div class="content">
<span>{{ toText(childNode) }}</span>
</div>
</div>
</div>
</a-tooltip>
<add-nodes v-model="childNode.childNode" />
</div>
</template>
<script>
import addNodes from './cAddNode.vue'
export default {
components: {
addNodes
},
props: {
modelValue: { type: Object, default: () => {} },
currentActivityId: { type: String, default: () => '' }
},
data() {
return {
childNode: {}
}
},
watch: {
modelValue() {
this.childNode = this.modelValue
}
},
mounted() {
this.childNode = this.modelValue
},
methods: {
toText() {
return '系统自动配置参与人'
},
getClassName() {
if (this.childNode.id === this.currentActivityId) {
return 'node-state-label-activity'
} else {
return this.childNode.properties && this.childNode.properties.commentList.length > 0 ? 'node-state-label' : ''
}
}
}
}
</script>
<style scoped>
.node-state-label {
padding: 1px;
border: 3px solid rgb(24, 144, 255);
border-radius: 2px;
}
.node-state-label-activity {
padding: 1px;
border: 3px dashed #00e97c;
border-radius: 2px;
}
</style>

View File

@@ -1,147 +0,0 @@
<template>
<div class="node-wrap">
<a-tooltip placement="left">
<template #title v-if="childNode.properties && childNode.properties.commentList.length > 0">
<div v-for="comment in childNode.properties.commentList" :key="comment.id">
{{ comment.userName }}{{ comment.approveTime }}
<a-tag color="rgb(212, 212, 212)">{{ comment.operateText }}</a-tag
>意见{{ comment.comment }}
</div>
</template>
<div :class="getClassName()">
<div class="node-wrap-box">
<div class="title" style="background: #ff943e">
<user-outlined class="icon" />
<span>{{ childNode.title }}</span>
</div>
<div class="content">
<span v-if="toText(childNode)">{{ toText(childNode) }}</span>
<span v-else class="placeholder">未选择审批人</span>
</div>
</div>
</div>
</a-tooltip>
<add-nodes v-model="childNode.childNode" />
</div>
</template>
<script>
import addNodes from './cAddNode.vue'
export default {
components: {
addNodes
},
props: {
modelValue: { type: Object, default: () => {} },
currentActivityId: { type: String, default: () => '' }
},
data() {
return {
childNode: {}
}
},
watch: {
modelValue() {
this.childNode = this.modelValue
}
},
mounted() {
this.childNode = this.modelValue
},
methods: {
toText(childNode) {
if (JSON.stringify(childNode) !== '{}') {
const strArray = this.toTag(childNode.properties.participateInfo[0])
if (strArray.length > 0) {
let value = ''
// eslint-disable-next-line no-plusplus
for (let i = 0; i < strArray.length; i++) {
if (strArray.length === i + 1) {
value = value + strArray[i]
} else {
value = value + strArray[i] + ''
}
}
return value
} else {
return false
}
} else {
return false
}
},
toTag(participateInfo) {
// eslint-disable-next-line no-undefined
if (participateInfo === undefined) {
return []
}
if (participateInfo.label === '') {
return []
} else {
let resultArray = []
if (participateInfo.label.indexOf(',') !== -1) {
resultArray = participateInfo.label.split(',')
} else {
resultArray.push(participateInfo.label)
}
return resultArray
}
},
getClassName() {
if (this.childNode.properties && this.childNode.properties.commentList.length > 0) {
if (this.childNode.properties.commentList.length === 1) {
if (this.childNode.properties.commentList[0].operateType === 'PASS') {
return 'node-state-label-pass'
}
if (this.childNode.properties.commentList[0].operateType === 'REJECT') {
return 'node-state-label-reject'
}
if (this.childNode.properties.commentList[0].operateType === 'JUMP') {
return 'node-state-label-jump'
}
if (this.childNode.properties.commentList[0].operateType === 'BACK') {
return 'node-state-label-back'
}
}
return 'node-state-label'
} else {
if (this.childNode.id === this.currentActivityId) {
return 'node-state-label-activity'
}
}
}
}
}
</script>
<style scoped>
.node-state-label {
padding: 1px;
border: 3px solid #1890ff;
border-radius: 2px;
}
.node-state-label-pass {
padding: 1px;
border: 3px solid #52c41a;
border-radius: 2px;
}
.node-state-label-reject {
padding: 1px;
border: 3px solid #ff4d4f;
border-radius: 2px;
}
.node-state-label-jump {
padding: 1px;
border: 3px solid #bfbfbf;
border-radius: 2px;
}
.node-state-label-back {
padding: 1px;
border: 3px solid #bfbfbf;
border-radius: 2px;
}
.node-state-label-activity {
padding: 1px;
border: 3px dashed #00e97c;
border-radius: 2px;
}
</style>

View File

@@ -1,39 +0,0 @@
const defaultZoomMargin = 360
const perZoom = 0.25 // 每次放大缩小0.25倍
const ZoomHelper = {
// 处理全屏放大的样式
getZoomStyles(zoom, MinZoom = 1, ZoomMargin = defaultZoomMargin, rightSpace = 0) {
const width = document.querySelector('.workflow-design').clientWidth
let style = {}
// 兼容Firefox浏览器的放大缩小
if (zoom !== MinZoom) {
style = {
transform: `scale(${zoom})`,
'transform-origin': '0 0',
width: (width - ZoomMargin - rightSpace) / zoom + 'px'
}
}
return style
},
// 获取最大的放大倍数
getMaxZoom() {
const width = window.innerWidth
const mediumWidth = 1600
const smallScreenScale = 2.5 // 小屏幕下附件放大3倍会有样式问题, 所以取2.5
const bigScreenScale = 3 // 大于1600的最大倍数为3
const maxZoom = width > mediumWidth ? bigScreenScale : smallScreenScale
return maxZoom
},
// 获取点击放大缩小之后生成的最终的放大倍数和样式
getZoomData(zoomIn, zoom) {
const zoomResult = zoomIn ? zoom + perZoom : zoom - perZoom // 放大倍数加一次或者减少一次
const zoomData = {
style: this.getZoomStyles(zoomResult),
zoom: zoomResult
}
return zoomData
}
}
export default ZoomHelper

View File

@@ -1,9 +0,0 @@
审批中 蓝色 #1890FF
已挂起 黄色 #FCC02E
已完成 绿色 #52C41A
已终止 黄色 #FF5A5A
已撤回 灰色 #BFBFBF
已拒绝 红色 #FF4D4F
同意 绿色 #52C41A

View File

@@ -1,132 +0,0 @@
<template>
<el-dialog v-model="dialogFormVisible" title="新增" width="500" :append-to-body="true">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px" label-position="top">
<el-form-item label="监听类型:" prop="listenerType">
<el-select v-model="formData.listenerType" placeholder="请选择类型">
<el-option
v-for="item in listenerTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="JAVA监听器" prop="javaClass">
<el-select v-model="formData.javaClass" placeholder="请选择JAVA监听器">
<el-option
v-for="item in listenerValueArray"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleAdd">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted, defineProps, defineEmits, watch } from 'vue'
const dialogFormVisible = ref(true)
const formLabelWidth = '140px'
const props = defineProps({
addFlag: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['close', 'addWacth'])
watch(
() => props.addFlag,
(val, oldVal) => {
console.log(val, oldVal)
if (val) {
dialogFormVisible.value = val
}
},
{
immediate: true,
deep: true
}
)
//必填规则
const formRules = {
listenerType: [{ required: true, message: '请选择监听类型', trigger: 'change' }],
javaClass: [{ required: true, message: '请选择JAVA监听器', trigger: 'change' }]
}
//form
const formData = reactive({
listenerType: '',
javaClass: ''
})
//监听类型数组
const listenerTypeOptions = reactive([
{
label: '开始',
value: '开始'
},
{
label: '完成',
value: '完成'
},
{
label: '拒绝',
value: '拒绝'
},
{
label: '终止',
value: '终止'
},
{
label: '撤回',
value: '撤回'
},
{
label: '删除',
value: '删除'
}
])
//java监听器数组
const listenerValueArray = reactive([
{
label: 'vip.xiaonuo.flw.core.listener.FlwTestExecutionListener',
value: 'vip.xiaonuo.flw.core.listener.FlwTestExecutionListener'
}
])
//关闭
async function handleClose() {
emit('close')
}
//提交
const formRef = ref(null)
async function handleAdd() {
formRef.value.validate(valid => {
if (valid) {
console.log(valid)
emit("addWatch",formData);
emit("close");
} else {
console.log('error submit!')
return false
}
})
}
onMounted(() => {
console.log()
})
</script>
<style lang="scss" scoped>
.dialog-footer {
padding: 20px 15px !important;
}
.el-form {
width: 96%;
margin: 0 auto;
margin-top: 20px;
}
</style>

View File

@@ -1,92 +0,0 @@
<template>
<div class="drawers">
<el-drawer
v-model="drawerVisibile"
title="全局属性"
direction="rtl"
size="50%"
:before-close="handleClose"
@closed="handleClose"
>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="人员配置" name="0">
<user v-if="activeName == 0"></user>
</el-tab-pane>
<el-tab-pane label="基础配置" name="1">
<basic v-if="activeName == 1"></basic>
</el-tab-pane>
<el-tab-pane label="通知配置" name="2">
<notice v-if="activeName == 2"></notice>
</el-tab-pane>
<el-tab-pane label="表单预设" name="3">
<formPreset v-if="activeName == 3"></formPreset>
</el-tab-pane>
<el-tab-pane label="执行监听" name="4">
<watchs v-if="activeName == 4"></watchs>
</el-tab-pane>
</el-tabs>
<template #footer>
<div style="flex: auto" class="drawer_footer">
<el-button type="primary" @click="handleConfirm">保存</el-button>
<el-button @click="handleClose">取消</el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script>
import user from '../globalAttribute/user.vue'
import basic from '../globalAttribute/basic.vue'
import notice from '../globalAttribute/notice.vue'
import formPreset from '../globalAttribute/formPreset.vue'
import watchs from '../globalAttribute/watchs.vue'
export default {
components: {
user,
basic,
notice,
formPreset,
watchs
},
data() {
return {
drawerVisibile: true,
activeName: '0'
}
},
mounted() {},
methods: {
handleClose() {
this.$emit('updateDrawer')
},
handleClick(val) {
console.log(val)
},
handleConfirm(){
}
}
}
</script>
<style scoped>
.drawers ::v-deep(.el-drawer__header) {
margin-bottom: 0;
display: flex;
align-items: center;
}
.drawers ::v-deep(.el-drawer__body){
position:fixed;
top:40px;
left:50%;
width:50%;
}
.drawer_footer{
display: flex;
width: 100px;
justify-content: space-between;
float: right;
position:absolute;
bottom: 20px;
right:50px;
}
</style>

View File

@@ -1,22 +0,0 @@
<template>
<div class="nav_title" :style="{ borderLeft: `4px solid ${borderColor}` }">
<p><slot name="nav_name"></slot></p>
</div>
</template>
<script setup>
import { useConfig } from '@/stores/config'
import { ref } from 'vue'
const configStore = useConfig()
const borderColor =configStore.getColorVal('elementUiPrimary')
</script>
<style lang="scss" scoped>
.nav_title {
padding-left: 8px;
margin-top: 10px;
p {
font-size: 15px;
color: #212121;
font-weight: 600;
}
}
</style>

View File

@@ -1,3 +0,0 @@
<template>
<a-result status="404" title="未找到表单" sub-title="对不起该节点配置的自定义表单不存在请联系管理员" />
</template>

View File

@@ -1,28 +0,0 @@
/**
* 自定义表单导入
*
* @author yubaoshan
* @date 2023-05-11 00:12:22
*/
const modules = import.meta.glob('/src/views/flw/customform/**/**.vue')
const notFound = () => import(/* @vite-ignore */ `/src/components/XnWorkflow/customForm/404.vue`)
// 直接渲染组件
export const loadComponent = (component) => {
if (component) {
const link = modules[`/src/views/flw/customform/${component}.vue`]
return markRaw(defineAsyncComponent(link ? link : notFound))
} else {
return markRaw(defineAsyncComponent(notFound))
}
}
// 给出判断如果使用的地方取到是404那么他的下一步就不走了
export const verdictComponent = (component) => {
if (component) {
const link = modules[`/src/views/flw/customform/${component}.vue`]
return !!link
} else {
return false
}
}

View File

@@ -1,425 +0,0 @@
.workflow-design {
overflow: auto;
width: 100%;
}
.workflow-design .box-scale {
display: inline-block;
position: relative;
width: 100%;
padding-top: 10px;
padding-bottom: 55px;
align-items: flex-start;
justify-content: center;
flex-wrap: wrap;
min-width: min-content;
}
.nodeLegal {
border: 2px solid red;
border-radius: 1px;
}
.workflow-design {
.node-wrap {
display: inline-flex;
width: 100%;
flex-flow: column wrap;
justify-content: flex-start;
align-items: center;
padding: 0px 50px;
position: relative;
z-index: 1;
}
.node-wrap-box {
display: inline-flex;
flex-direction: column;
position: relative;
width: 220px;
min-height: 72px;
flex-shrink: 0;
background: var(--node-wrap-box-color);
border-radius: 1px;
cursor: pointer;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2);
}
.node-wrap-box::before {
background: var(--auto-judge-before-color);
content: '';
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
width: 0px;
border-style: solid;
border-width: 8px 6px 4px;
border-color: #cacaca transparent transparent;
}
.node-wrap-box.start-node:before {
content: none;
}
.node-wrap-box .title {
height: 24px;
line-height: 24px;
color: #fff;
padding-left: 16px;
padding-right: 30px;
border-radius: 2px 2px 0 0;
position: relative;
display: flex;
align-items: center;
}
.node-wrap-box .title .icon {
margin-right: 5px;
}
.node-wrap-box .title .close {
font-size: 15px;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 10px;
display: none;
}
.node-wrap-box .title .success {
font-size: 20px;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: -25px;
display: block;
color: #00bb00;
}
.node-wrap-box .content {
position: relative;
padding: 15px;
}
.node-wrap-box .content .placeholder {
color: red;
}
.node-wrap-box:hover .close {
display: block;
}
.add-node-btn-box {
width: 240px;
display: inline-flex;
flex-shrink: 0;
position: relative;
z-index: 1;
}
.add-node-btn-box:before {
content: '';
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: -1;
margin: auto;
width: 2px;
height: 100%;
background-color: rgb(202, 202, 202);
}
.add-node-btn {
user-select: none;
width: 240px;
padding: 20px 0px 32px;
display: flex;
justify-content: center;
flex-shrink: 0;
flex-grow: 1;
}
.add-node-btn span {
}
.add-branch {
justify-content: center;
padding: 0px 10px;
position: absolute;
top: -16px;
left: 50%;
transform: translateX(-50%);
transform-origin: center center;
z-index: 1;
display: inline-flex;
align-items: center;
}
.branch-wrap {
display: inline-flex;
width: 100%;
}
.branch-box-wrap {
display: flex;
flex-flow: column wrap;
align-items: center;
min-height: 270px;
width: 100%;
flex-shrink: 0;
}
.col-box {
display: inline-flex;
flex-direction: column;
align-items: center;
position: relative;
background: var(--component-background);
}
// 分支 上面横线
.branch-box {
display: flex;
overflow: visible;
min-height: 180px;
height: auto;
border-bottom: 2px solid #ccc;
border-top: 2px solid #ccc;
position: relative;
margin-top: 15px;
}
.branch-box .col-box::before {
content: '';
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 0;
margin: auto;
width: 2px;
height: 100%;
background-color: rgb(202, 202, 202);
}
.condition-node {
display: inline-flex;
flex-direction: column;
min-height: 220px;
}
.condition-node-box {
padding-top: 30px;
padding-right: 50px;
padding-left: 50px;
justify-content: center;
align-items: center;
flex-grow: 1;
position: relative;
display: inline-flex;
flex-direction: column;
}
.auto-judge {
position: relative;
width: 220px;
min-height: 72px;
background: var(--node-wrap-box-color);
border-radius: 2px;
padding: 15px 15px;
cursor: pointer;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2);
}
// 箭头框框
.auto-judge::before {
content: '';
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
width: 0px;
border-style: solid;
border-width: 8px 6px 4px;
border-color: #cacaca transparent transparent;
background: var(--auto-judge-before-color);
}
.auto-judge .title {
line-height: 16px;
}
.auto-judge .title .node-title {
color: #15bc83;
}
.auto-judge .title .close {
font-size: 15px;
position: absolute;
top: 15px;
right: 15px;
color: #999;
display: none;
}
.auto-judge .title .success {
font-size: 15px;
position: absolute;
top: 15px;
right: 15px;
color: #999;
display: block;
}
.auto-judge .title .priority-title {
position: absolute;
top: 15px;
right: 15px;
color: #999;
}
.auto-judge .content {
position: relative;
padding-top: 15px;
}
.auto-judge .content .placeholder {
color: red;
}
.auto-judge:hover {
.close {
display: block;
}
.priority-title {
display: none;
}
}
.top-left-cover-line,
.top-right-cover-line {
position: absolute;
height: 3px;
width: 50%;
background-color: var(--component-background);
top: -2px;
}
.bottom-left-cover-line,
.bottom-right-cover-line {
position: absolute;
height: 3px;
width: 50%;
background-color: var(--component-background);
bottom: -2px;
}
.top-left-cover-line {
left: -1px;
}
.top-right-cover-line {
right: -1px;
}
.bottom-left-cover-line {
left: -1px;
}
.bottom-right-cover-line {
right: -1px;
}
.end-node {
border-radius: 50%;
font-size: 14px;
color: rgba(25, 31, 37, 0.4);
text-align: left;
}
// 结束的小点点
.end-node-circle {
width: 10px;
height: 10px;
margin: auto;
border-radius: 50%;
background: #dbdcdc;
}
.end-node-text {
margin-top: 5px;
text-align: center;
}
.auto-judge:hover {
.sort-left {
display: flex;
}
.sort-right {
display: flex;
}
}
.auto-judge .sort-left {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
left: 0;
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
}
.auto-judge .sort-right {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
right: 0;
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
}
.auto-judge .sort-left:hover,
.auto-judge .sort-right:hover {
background: var(--auto-judge-before-color);
}
.auto-judge:after {
pointer-events: none;
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
border-radius: 2px;
transition: all 0.1s;
}
.auto-judge:hover:after {
border: 1px solid #3296fa;
box-shadow: 0 0 6px 0 rgba(50, 150, 250, 0.3);
}
.node-wrap-box:after {
pointer-events: none;
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
border-radius: 2px;
transition: all 0.1s;
}
.node-wrap-box:hover:after {
border: 1px solid #3296fa;
box-shadow: 0 0 6px 0 rgba(50, 150, 250, 0.3);
}
}
.tags-list {
margin-top: 15px;
width: 100%;
}
.add-node-popover-body {
height: 81px;
}
.add-node-popover-body li {
display: inline-block;
width: 80px;
text-align: center;
padding: 10px 0;
}
.add-node-popover-body li i {
border: 1px solid var(--el-border-color-light);
width: 40px;
height: 40px;
border-radius: 50%;
text-align: center;
line-height: 38px;
font-size: 18px;
cursor: pointer;
}
.add-node-popover-body li i:hover {
border: 1px solid #3296fa;
background: #3296fa;
color: #fff !important;
}
.add-node-popover-body li p {
font-size: 12px;
margin-top: 5px;
}
.node-wrap-drawer__title {
padding-right: 40px;
}
.node-wrap-drawer__title label {
cursor: pointer;
}
.node-wrap-drawer__title label:hover {
border-bottom: 1px dashed #409eff;
}
.node-wrap-drawer__title .node-wrap-drawer-title-edit {
color: #409eff;
margin-left: 10px;
vertical-align: middle;
}

View File

@@ -1,126 +0,0 @@
<!-- 全局属性基础配置页面 -->
<template>
<div class="home">
<navTitle>
<template #nav_name>流程基础全局配置</template>
</navTitle>
<div class="basic_form">
<el-form :model="form" label-width="auto" label-position="top">
<div class="col">
<el-form-item label="流水号">
<el-col :span="12">
<el-select
style="width: 300px !important"
v-model="form.region"
placeholder="please select your zone"
>
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-col>
</el-form-item>
<el-form-item label="打印模板">
<el-col :span="12">
<p style="width: 300px">自定义表单内提供打印方法</p>
</el-col>
</el-form-item>
</div>
<el-form-item label="标题模板">
<div class="form_position_item">
<el-input v-model="form.desc" type="textarea" />
<el-button type="primary" size="small">
置入字段
<el-icon class="el-icon--right"><ArrowDownBold /></el-icon>
</el-button>
</div>
</el-form-item>
<el-form-item label="摘要模板">
<div class="form_position_item">
<el-input v-model="form.desc" type="textarea" />
<el-button type="primary" size="small">
置入字段
<el-icon class="el-icon--right"><ArrowDownBold /></el-icon>
</el-button>
</div>
</el-form-item>
<el-form-item label="开启自动去重">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="">
<el-radio-group v-model="form.resource" class="radioGroup">
<p><el-radio value="0">当审批人和发起人是同一个人审批自动通过</el-radio></p>
<p><el-radio value="1">当同一审批人在流程中连续多次出现时自动去重</el-radio></p>
</el-radio-group>
</el-form-item>
<div class="col">
<el-form-item label="开启审批撤销">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="开启意见必填">
<el-switch v-model="form.delivery" />
</el-form-item>
</div>
</el-form>
</div>
</div>
</template>
<script setup>
import navTitle from '../components/navTitle.vue'
import { ArrowDownBold } from '@element-plus/icons-vue'
import { ref } from 'vue'
const form = ref({})
form.value = {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: ''
}
</script>
<style lang="scss" scoped>
.home {
width: 100%;
height: calc(100vh - 170px);
padding-top: 20px;
overflow: auto;
.basic_form {
width:96%;
padding-top: 10px;
margin: 0 auto;
.col {
width: 100%;
display: flex;
justify-content: space-between;
.el-form-item {
width: 50%;
}
}
.form_position_item {
width: 100%;
height: auto;
position: relative;
.el-input {
height: 100%;
}
.el-button {
position: absolute;
bottom: 5px;
right: 5px;
z-index: 999;
}
}
.radioGroup {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
}
}
</style>

View File

@@ -1,122 +0,0 @@
<!-- 全局属性表单预设页面 -->
<template>
<div class="home">
<navTitle>
<template #nav_name>预设全局需要的表单</template>
</navTitle>
<div class="node_form">
<el-form :model="form" label-width="auto" label-position="top">
<el-form-item
label="开始节点表单"
name="processStartTaskFormUrl"
:rules="[{ required: true, message: '请输入开始节点表单' }]"
>
<el-input
v-model="form.properties.configInfo.processStartTaskFormUrl"
placeholder="请输入开始节点表单组件"
clearable
>
<template #prepend>src/views/flw/customform/</template>
<template #append>.vue</template>
<template #suffix>
<el-button
v-if="form.properties.configInfo.processStartTaskFormUrl"
type="primary"
size="small"
@click="
$refs.previewCustomFormRef.onOpen(
form.properties.configInfo.processStartTaskFormUrl
)
"
>
预览
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item
label="移动端开始节点表单"
name="processStartTaskMobileFormUrl"
:rules="[{ required: true, message: '请输入移动端开始节点表单', trigger: 'blur' }]"
>
<el-input
v-model="form.properties.configInfo.processStartTaskMobileFormUrl"
placeholder="请输入移动端开始节点表单组件"
clearable
>
<template #prepend>pages/flw/customform/</template>
<template #append>.vue</template>
</el-input>
</el-form-item>
<el-form-item
label="人员节点表单"
name="processUserTaskFormUrl"
:rules="[{ required: true, message: '请输入人员节点表单' }]"
>
<el-input
v-model="form.properties.configInfo.processUserTaskFormUrl"
placeholder="请输入人员节点表单组件"
clearable
>
<template #prepend>src/views/flw/customform/</template>
<template #append>.vue</template>
<template #suffix>
<el-button
v-if="form.properties.configInfo.processUserTaskFormUrl"
type="primary"
size="small"
@click="
$refs.previewCustomFormRef.onOpen(form.properties.configInfo.processUserTaskFormUrl)
"
>
预览
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item
label="移动端人员节点表单"
name="processUserTaskMobileFormUrl"
:rules="[{ required: true, message: '请输入移动端人员节点表单' }]"
>
<el-input
v-model="form.properties.configInfo.processUserTaskMobileFormUrl"
placeholder="请输入移动端人员节点表单组件"
clearable
>
<template #prepend>pages/flw/customform/</template>
<template #append>.vue</template>
</el-input>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import navTitle from '../components/navTitle.vue'
import { ref ,reactive} from 'vue'
const form = ref({})
form.value= {
properties: {
configInfo: {
processStartTaskFormUrl: '', //开始节点表单
processStartTaskMobileFormUrl: '', //移动端开始节点表单
processUserTaskFormUrl: '', //人员节点表单
processUserTaskMobileFormUrl: '' //移动端人员节点表单
}
}
}
</script>
<style lang="scss" scoped>
.home {
width: 100%;
height: calc(100vh - 170px);
padding-top: 20px;
overflow: auto;
.node_form{
width:96%;
padding-top: 10px;
margin: 0 auto;
}
}
</style>

View File

@@ -1,47 +0,0 @@
<!-- 全局属性通知配置页面 -->
<template>
<div class="home">
<div class="info">
<navTitle>
<template #nav_name>配置通知事项</template>
</navTitle>
</div>
<div class="basic_form">
<el-form :model="form" label-width="auto" label-position="top">
<el-form-item label="开启退回通知">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="开启待办通知">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="开启抄送通知">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="开启完成通知">
<el-switch v-model="form.delivery" />
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import navTitle from '../components/navTitle.vue';
import { ref } from 'vue'
const form = ref({})
form.value = {
delivery: false,
}
</script>
<style scoped lang="scss">
.home {
width: 100%;
height: calc(100vh - 100px);
padding-top: 20px;
overflow: auto;
.basic_form {
width:96%;
padding-top: 10px;
margin: 0 auto;
}
}
</style>

View File

@@ -1,105 +0,0 @@
<!-- 全局属性人员配置页面 -->
<template>
<div class="home">
<!-- 未选择任何类型的人员 -->
<div class="nobody_info" v-if="checkUserList.length == 0">
<p>未选择任何类型的人员配置默认所有人均可参与此流程</p>
</div>
<navTitle>
<template #nav_name>配置使用该流程的人员</template>
</navTitle>
<div class="info">
<div class="info_item">
<div class="info_item_top">
<el-button type="primary">+ 选择机构</el-button>
</div>
<div class="info_item_bot">
<div class="checked" v-for="(item, index) in 22">工会办公室{{ index+1 }}</div>
</div>
</div>
<div class="info_item">
<div class="info_item_top">
<el-button type="primary">+ 选择角色</el-button>
</div>
<div class="info_item_bot">
<div class="checked" v-for="(item, index) in 30">小诺科技有限公司{{ index+1 }}</div>
</div>
</div>
<div class="info_item">
<div class="info_item_top">
<el-button type="primary">+ 选择用户</el-button>
</div>
<div class="info_item_bot">
<div class="checked" v-for="(item, index) in 40">党群工作部{{ index+1 }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { checkUser } from '@/api/user-boot/user'
import navTitle from '../components/navTitle.vue'
export default {
components: {
navTitle
},
data() {
return {
checkUserList: []
}
},
mounted() {},
methods: {}
}
</script>
<style scoped lang="scss">
.home {
width: 100%;
height: calc(100vh - 170px);
padding-top: 20px;
overflow: auto;
.nobody_info {
width: 100%;
height: 40px;
background-color: #fffbe6;
padding: 8px 15px;
border-radius: 4px;
border: 2px solid #ffe58f;
p {
font-size: 14px;
font-variant: tabular-nums;
margin: 0;
}
}
.info {
margin-top: 10px;
width: 100%;
height: auto;
.info_item {
width: 100%;
min-height: 80px;
border-bottom: 1px solid #f0f0f0;
display: flex;
flex-direction: column;
justify-content: space-around;
.info_item_bot {
height: auto;
display: flex;
flex-wrap: wrap;
.checked {
margin: 3px;
padding: 0 7px;
font-size: 12px;
line-height: 20px;
white-space: nowrap;
background: #FAFAFA;
border: 1px solid #D9D9D9;
border-radius: 2px;
opacity: 1;
transition: all 0.3s;
}
}
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More