YOMO.LEE 2025-07-30 18:05:15 +08:00
commit b0da657781
49 changed files with 516 additions and 303 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "yudao-ui-admin-vue3", "name": "yudao-ui-admin-vue3",
"version": "2.6.0-snapshot", "version": "2.6.1-snapshot",
"description": "基于vue3、vite4、element-plus、typesScript", "description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu", "author": "xingyu",
"private": false, "private": false,

View File

@ -48,6 +48,7 @@ export type ApprovalNodeInfo = {
status: number status: number
startTime?: Date startTime?: Date
endTime?: Date endTime?: Date
processInstanceId?: string
candidateUsers?: User[] candidateUsers?: User[]
tasks: ApprovalTaskInfo[] tasks: ApprovalTaskInfo[]
} }

View File

@ -14,7 +14,7 @@ export interface SocialUserVO {
} }
// 查询社交用户列表 // 查询社交用户列表
export const getSocialUserPage = async (params) => { export const getSocialUserPage = async (params: any) => {
return await request.get({ url: `/system/social-user/page`, params }) return await request.get({ url: `/system/social-user/page`, params })
} }
@ -22,3 +22,8 @@ export const getSocialUserPage = async (params) => {
export const getSocialUser = async (id: number) => { export const getSocialUser = async (id: number) => {
return await request.get({ url: `/system/social-user/get?id=` + id }) return await request.get({ url: `/system/social-user/get?id=` + id })
} }
// 获得绑定社交用户列表
export const getBindSocialUserList = async () => {
return await request.get({ url: '/system/social-user/get-bind-list' })
}

View File

@ -16,10 +16,6 @@ export interface ProfileVO {
id: number id: number
name: string name: string
}[] }[]
socialUsers: {
type: number
openid: string
}[]
email: string email: string
mobile: string mobile: string
sex: number sex: number

View File

@ -80,7 +80,8 @@ const activeAppLink = ref({} as AppLink)
/** 打开弹窗 */ /** 打开弹窗 */
const dialogVisible = ref(false) const dialogVisible = ref(false)
const open = (link: string) => { const open = (link: string) => {
activeAppLink.value.path = link // activeAppLink
activeAppLink.value = { name: '', path: '' }
dialogVisible.value = true dialogVisible.value = true
// //
@ -102,8 +103,11 @@ defineExpose({ open })
// APP // APP
const handleAppLinkSelected = (appLink: AppLink) => { const handleAppLinkSelected = (appLink: AppLink) => {
//
if (!isSameLink(appLink.path, activeAppLink.value.path)) { if (!isSameLink(appLink.path, activeAppLink.value.path)) {
activeAppLink.value = appLink // path 沿 activeAppLink path
const path = appLink.path || activeAppLink.value.path
activeAppLink.value = { ...appLink, path: path }
} }
switch (appLink.type) { switch (appLink.type) {
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST: case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
@ -170,7 +174,7 @@ const groupBtnRefs = ref<ButtonInstance[]>([])
const scrollToGroupBtn = (group: string) => { const scrollToGroupBtn = (group: string) => {
const groupBtn = groupBtnRefs.value const groupBtn = groupBtnRefs.value
.map((btn: ButtonInstance) => btn['ref']) .map((btn: ButtonInstance) => btn['ref'])
.find((ref: Node) => ref.textContent === group) .find((ref: HTMLButtonElement) => ref.textContent === group)
if (groupBtn) { if (groupBtn) {
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop) groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
} }

View File

@ -181,7 +181,6 @@ function openModal() {
} }
function closeModal() { function closeModal() {
debugger
dialogVisible.value = false dialogVisible.value = false
} }

View File

@ -13,7 +13,7 @@
<template v-for="(cell, cellIndex) in cellList" :key="cellIndex"> <template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
<template v-if="selectedHotAreaIndex === cellIndex"> <template v-if="selectedHotAreaIndex === cellIndex">
<el-form-item :prop="`cell[${cellIndex}].type`" label="类型"> <el-form-item :prop="`cell[${cellIndex}].type`" label="类型">
<el-radio-group v-model="cell.type"> <el-radio-group v-model="cell.type" @change="handleHotAreaSelected(cell, cellIndex)">
<el-radio value="text">文字</el-radio> <el-radio value="text">文字</el-radio>
<el-radio value="image">图片</el-radio> <el-radio value="image">图片</el-radio>
<el-radio value="search">搜索框</el-radio> <el-radio value="search">搜索框</el-radio>
@ -44,9 +44,32 @@
</template> </template>
<!-- 3. 搜索框 --> <!-- 3. 搜索框 -->
<template v-else> <template v-else>
<el-form-item label="框体颜色" prop="backgroundColor">
<ColorInput v-model="cell.backgroundColor" />
</el-form-item>
<el-form-item class="lef" label="文本颜色" prop="textColor">
<ColorInput v-model="cell.textColor" />
</el-form-item>
<el-form-item :prop="`cell[${cellIndex}].placeholder`" label="提示文字"> <el-form-item :prop="`cell[${cellIndex}].placeholder`" label="提示文字">
<el-input v-model="cell.placeholder" maxlength="10" show-word-limit /> <el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
</el-form-item> </el-form-item>
<el-form-item label="文本位置" prop="placeholderPosition">
<el-radio-group v-model="cell!.placeholderPosition">
<el-tooltip content="居左" placement="top">
<el-radio-button value="left">
<Icon icon="ant-design:align-left-outlined" />
</el-radio-button>
</el-tooltip>
<el-tooltip content="居中" placement="top">
<el-radio-button value="center">
<Icon icon="ant-design:align-center-outlined" />
</el-radio-button>
</el-tooltip>
</el-radio-group>
</el-form-item>
<el-form-item label="扫一扫" prop="showScan">
<el-switch v-model="cell!.showScan" />
</el-form-item>
<el-form-item :prop="`cell[${cellIndex}].borderRadius`" label="圆角"> <el-form-item :prop="`cell[${cellIndex}].borderRadius`" label="圆角">
<el-slider <el-slider
v-model="cell.borderRadius" v-model="cell.borderRadius"
@ -88,10 +111,17 @@ const cellCount = computed(() => (props.isMp ? 6 : 8))
const selectedHotAreaIndex = ref(0) const selectedHotAreaIndex = ref(0)
const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: number) => { const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: number) => {
selectedHotAreaIndex.value = index selectedHotAreaIndex.value = index
//
if (!cellValue.type) { if (!cellValue.type) {
cellValue.type = 'text' cellValue.type = 'text'
cellValue.textColor = '#111111' cellValue.textColor = '#111111'
} }
//
if (cellValue.type === 'search') {
cellValue.placeholderPosition = 'left'
cellValue.backgroundColor = '#EEEEEE'
cellValue.textColor = '#969799'
}
} }
</script> </script>

View File

@ -45,8 +45,14 @@ export interface NavigationBarCellProperty {
imgUrl: string imgUrl: string
// 图片链接 // 图片链接
url: string url: string
// 搜索框:框体颜色
backgroundColor: string
// 搜索框:提示文字 // 搜索框:提示文字
placeholder: string placeholder: string
// 搜索框:提示文字位置
placeholderPosition: string
// 搜索框:是否显示扫一扫
showScan: boolean
// 搜索框:边框圆角半径 // 搜索框:边框圆角半径
borderRadius: number borderRadius: number
} }

View File

@ -54,9 +54,12 @@ const getCellStyle = (cell: NavigationBarCellProperty) => {
const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => { const getSearchProp = computed(() => (cell: NavigationBarCellProperty) => {
return { return {
height: 30, height: 30,
showScan: false, backgroundColor: cell.backgroundColor,
showScan: cell.showScan,
placeholder: cell.placeholder, placeholder: cell.placeholder,
borderRadius: cell.borderRadius borderRadius: cell.borderRadius,
textColor: cell.textColor,
placeholderPosition: cell.placeholderPosition
} as SearchProperty } as SearchProperty
}) })
</script> </script>

View File

@ -269,6 +269,11 @@ watch(
if (!val || selectedComponentIndex.value === -1) { if (!val || selectedComponentIndex.value === -1) {
return return
} }
// -1
// https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/792
if (props.showTabBar) {
selectedComponentIndex.value = -1
}
pageComponents.value[selectedComponentIndex.value] = selectedComponent.value! pageComponents.value[selectedComponentIndex.value] = selectedComponent.value!
}, },
{ deep: true } { deep: true }

View File

@ -72,6 +72,7 @@ watch(
(options) => { (options) => {
if (echartRef) { if (echartRef) {
echartRef?.setOption(options) echartRef?.setOption(options)
echartRef?.resize()
} }
}, },
{ {

View File

@ -26,8 +26,7 @@
<script setup lang="ts"> <script setup lang="ts">
import SimpleProcessModel from './SimpleProcessModel.vue' import SimpleProcessModel from './SimpleProcessModel.vue'
import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts' import { SimpleFlowNode, NodeType, NodeId, NODE_DEFAULT_TEXT } from './consts'
import { getModel } from '@/api/bpm/model' import { getForm } from '@/api/bpm/form'
import { getForm, FormVO } from '@/api/bpm/form'
import { handleTree } from '@/utils/tree' import { handleTree } from '@/utils/tree'
import * as RoleApi from '@/api/system/role' import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
@ -43,18 +42,22 @@ defineOptions({
const emits = defineEmits(['success']) // const emits = defineEmits(['success']) //
const props = defineProps({ const props = defineProps({
modelId: {
type: String,
required: false
},
modelKey: {
type: String,
required: false
},
modelName: { modelName: {
type: String, type: String,
required: false required: false
}, },
// ID
modelFormId: {
type: Number,
required: false,
default: undefined,
},
//
modelFormType: {
type: Number,
required: false,
default: BpmModelFormType.NORMAL,
},
// //
startUserIds: { startUserIds: {
type: Array, type: Array,
@ -70,7 +73,31 @@ const props = defineProps({
const processData = inject('processData') as Ref const processData = inject('processData') as Ref
const loading = ref(false) const loading = ref(false)
const formFields = ref<string[]>([]) const formFields = ref<string[]>([])
const formType = ref(20) const formType = ref(props.modelFormType);
// modelFormType
watch(
() => props.modelFormType,
(newVal) => {
formType.value = newVal;
},
);
// modelFormId
watch(
() => props.modelFormId,
async (newVal) => {
if (newVal) {
const form = await getForm(newVal);
formFields.value = form?.fields;
} else {
// modelFormId
formFields.value = [];
}
},
{ immediate: true },
);
const roleOptions = ref<RoleApi.RoleVO[]>([]) // const roleOptions = ref<RoleApi.RoleVO[]>([]) //
const postOptions = ref<PostApi.PostVO[]>([]) // const postOptions = ref<PostApi.PostVO[]>([]) //
const userOptions = ref<UserApi.UserVO[]>([]) // const userOptions = ref<UserApi.UserVO[]>([]) //
@ -90,6 +117,8 @@ provide('startUserIds', props.startUserIds)
provide('startDeptIds', props.startDeptIds) provide('startDeptIds', props.startDeptIds)
provide('tasks', []) provide('tasks', [])
provide('processInstance', {}) provide('processInstance', {})
const message = useMessage() // const message = useMessage() //
const processNodeTree = ref<SimpleFlowNode | undefined>() const processNodeTree = ref<SimpleFlowNode | undefined>()
provide('processNodeTree', processNodeTree) provide('processNodeTree', processNodeTree)
@ -169,17 +198,17 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
onMounted(async () => { onMounted(async () => {
try { try {
loading.value = true loading.value = true
// // //
if (props.modelId) { // if (props.modelId) {
const bpmnModel = await getModel(props.modelId) // const bpmnModel = await getModel(props.modelId)
if (bpmnModel) { // if (bpmnModel) {
formType.value = bpmnModel.formType // formType.value = bpmnModel.formType
if (formType.value === BpmModelFormType.NORMAL && bpmnModel.formId) { // if (formType.value === BpmModelFormType.NORMAL && bpmnModel.formId) {
const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO // const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
formFields.value = bpmnForm?.fields // formFields.value = bpmnForm?.fields
} // }
} // }
} // }
// //
roleOptions.value = await RoleApi.getSimpleRoleList() roleOptions.value = await RoleApi.getSimpleRoleList()
// //

View File

@ -1,6 +1,6 @@
<template> <template>
<el-form-item label-position="top" label="请求头"> <el-form-item label-position="top" label="请求头">
<div class="flex pt-2" v-for="(item, index) in props.header" :key="index"> <div class="flex pb-4" v-for="(item, index) in props.header" :key="index">
<div class="mr-2"> <div class="mr-2">
<el-form-item <el-form-item
:prop="`${bind}.header.${index}.key`" :prop="`${bind}.header.${index}.key`"
@ -10,18 +10,20 @@
trigger: 'blur' trigger: 'blur'
}" }"
> >
<el-input class="w-160px" v-model="item.key" /> <el-input v-model="item.key" style="width: 160px" />
</el-form-item> </el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-select class="w-100px!" v-model="item.type"> <el-form-item>
<el-option <el-select v-model="item.type" style="width: 160px" @change="handleTypeChange(item)">
v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES" <el-option
:key="types.value" v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES"
:label="types.label" :key="types.value"
:value="types.value" :label="types.label"
/> :value="types.value"
</el-select> />
</el-select>
</el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-form-item <el-form-item
@ -34,8 +36,8 @@
> >
<el-input <el-input
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE" v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value" v-model="item.value"
style="width: 200px"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
@ -48,8 +50,8 @@
> >
<el-select <el-select
v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM" v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value" v-model="item.value"
style="width: 200px"
> >
<el-option <el-option
v-for="(field, fIdx) in formFieldOptions" v-for="(field, fIdx) in formFieldOptions"
@ -70,7 +72,7 @@
</el-button> </el-button>
</el-form-item> </el-form-item>
<el-form-item label-position="top" label="请求体"> <el-form-item label-position="top" label="请求体">
<div class="flex pt-2" v-for="(item, index) in props.body" :key="index"> <div class="flex pb-4" v-for="(item, index) in props.body" :key="index">
<div class="mr-2"> <div class="mr-2">
<el-form-item <el-form-item
:prop="`${bind}.body.${index}.key`" :prop="`${bind}.body.${index}.key`"
@ -80,18 +82,20 @@
trigger: 'blur' trigger: 'blur'
}" }"
> >
<el-input class="w-160px" v-model="item.key" /> <el-input v-model="item.key" style="width: 160px" />
</el-form-item> </el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-select class="w-100px!" v-model="item.type"> <el-form-item>
<el-option <el-select v-model="item.type" style="width: 160px" @change="handleTypeChange(item)">
v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES" <el-option
:key="types.value" v-for="types in BPM_HTTP_REQUEST_PARAM_TYPES"
:label="types.label" :key="types.value"
:value="types.value" :label="types.label"
/> :value="types.value"
</el-select> />
</el-select>
</el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-form-item <el-form-item
@ -104,8 +108,8 @@
> >
<el-input <el-input
v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE" v-if="item.type === BpmHttpRequestParamTypeEnum.FIXED_VALUE"
class="w-160px"
v-model="item.value" v-model="item.value"
style="width: 200px"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item
@ -118,8 +122,8 @@
> >
<el-select <el-select
v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM" v-if="item.type === BpmHttpRequestParamTypeEnum.FROM_FORM"
class="w-160px!"
v-model="item.value" v-model="item.value"
style="width: 200px"
> >
<el-option <el-option
v-for="(field, fIdx) in formFieldOptions" v-for="(field, fIdx) in formFieldOptions"
@ -170,6 +174,13 @@ const props = defineProps({
// //
const formFieldOptions = useFormFieldsAndStartUser() const formFieldOptions = useFormFieldsAndStartUser()
/** 监听类型变化,清空值 */
const handleTypeChange = (item: HttpRequestParam) => {
//
item.value = ''
}
/** 添加请求配置项 */ /** 添加请求配置项 */
const addHttpRequestParam = (arr: HttpRequestParam[]) => { const addHttpRequestParam = (arr: HttpRequestParam[]) => {
arr.push({ arr.push({

View File

@ -33,7 +33,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div class="flex pt-2" v-for="(item, index) in setting.response" :key="index"> <div class="flex pt-4" v-for="(item, index) in setting.response" :key="index">
<div class="mr-2"> <div class="mr-2">
<el-form-item <el-form-item
:prop="`${formItemPrefix}.response.${index}.key`" :prop="`${formItemPrefix}.response.${index}.key`"
@ -74,10 +74,12 @@
/> />
</div> </div>
</div> </div>
</el-form-item>
<div class="pt-1">
<el-button type="primary" text @click="addHttpResponseSetting(setting.response!)"> <el-button type="primary" text @click="addHttpResponseSetting(setting.response!)">
<Icon icon="ep:plus" class="mr-5px" />添加一行 <Icon icon="ep:plus" class="mr-5px" />添加一行
</el-button> </el-button>
</el-form-item> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -1014,6 +1014,16 @@
"name": "fields", "name": "fields",
"type": "Field", "type": "Field",
"isMany": true "isMany": true
},
{
"name": "id",
"type": "String",
"isAttr": true
},
{
"name": "eventDefinitions",
"type": "bpmn:TimerEventDefinition",
"isMany": true
} }
] ]
}, },

View File

@ -28,7 +28,12 @@
v-model="timeDuration" v-model="timeDuration"
:min="1" :min="1"
controls-position="right" controls-position="right"
@change="() => updateTimeModdle()" @change="
() => {
updateTimeModdle()
updateElementExtensions()
}
"
/> />
</el-form-item> </el-form-item>
<el-select <el-select
@ -55,7 +60,12 @@
v-model="maxRemindCount" v-model="maxRemindCount"
:min="1" :min="1"
:max="10" :max="10"
@change="() => updateTimeModdle()" @change="
() => {
updateTimeModdle()
updateElementExtensions()
}
"
/> />
</el-form-item> </el-form-item>
</div> </div>
@ -65,7 +75,7 @@
import { import {
TimeUnitType, TimeUnitType,
TIME_UNIT_TYPES, TIME_UNIT_TYPES,
TIMEOUT_HANDLER_TYPES, TIMEOUT_HANDLER_TYPES
} from '@/components/SimpleProcessDesignerV2/src/consts' } from '@/components/SimpleProcessDesignerV2/src/consts'
import { convertTimeUnit } from '@/components/SimpleProcessDesignerV2/src/utils' import { convertTimeUnit } from '@/components/SimpleProcessDesignerV2/src/utils'
@ -195,6 +205,7 @@ const onTimeUnitChange = () => {
timeDuration.value = 1 timeDuration.value = 1
} }
updateTimeModdle() updateTimeModdle()
updateElementExtensions()
} }
const updateTimeModdle = () => { const updateTimeModdle = () => {

View File

@ -354,12 +354,13 @@ const resetTaskForm = () => {
const changeCandidateStrategy = () => { const changeCandidateStrategy = () => {
userTaskForm.value.candidateParam = [] userTaskForm.value.candidateParam = []
deptLevel.value = 1 deptLevel.value = 1
if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) { // by https://t.zsxq.com/xNmas
// // if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) { // //
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER // if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
} // userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
} // }
// }
updateElementTask() updateElementTask()
} }

View File

@ -55,6 +55,7 @@ import {
ElCollapse, ElCollapse,
ElCollapseItem, ElCollapseItem,
ElCard, ElCard,
ElTreeSelect
// ElFormItem, // ElFormItem,
// ElOption // ElOption
} from 'element-plus' } from 'element-plus'
@ -97,6 +98,7 @@ const components = [
ElTableColumn, ElTableColumn,
ElTabPane, ElTabPane,
ElTabs, ElTabs,
ElTreeSelect,
ElDropdown, ElDropdown,
ElDropdownMenu, ElDropdownMenu,
ElDropdownItem, ElDropdownItem,
@ -119,7 +121,7 @@ const components = [
Editor, Editor,
ElCollapse, ElCollapse,
ElCollapseItem, ElCollapseItem,
ElCard, ElCard
] ]
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档 // 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档

View File

@ -62,7 +62,7 @@ export const useUserStore = defineStore('admin-user', {
userInfo = await getInfo() userInfo = await getInfo()
} catch (error) {} } catch (error) {}
} }
this.permissions = new Set(userInfo.permissions) this.permissions = new Set(userInfo.permissions || []) // 兜底为 [] https://t.zsxq.com/xCJew
this.roles = userInfo.roles this.roles = userInfo.roles
this.user = userInfo.user this.user = userInfo.user
this.isSetUser = true this.isSetUser = true

View File

@ -49,14 +49,17 @@ export const setConfAndFields2 = (
detailPreview = detailPreview.value detailPreview = detailPreview.value
} }
// 解析配置 // 修复所有函数类型(解决设计器保存后函数变成字符串的问题)。例如说:
// https://t.zsxq.com/rADff
// https://t.zsxq.com/ZfbGt
// https://t.zsxq.com/mHOoj
// https://t.zsxq.com/BSylB
const option = JSON.parse(conf) const option = JSON.parse(conf)
const rule = decodeFields(fields) const rule = decodeFields(fields)
// 🔧 修复所有函数类型 - 解决设计器保存后函数变成字符串的问题 // 🔧 修复所有函数类型 - 解决设计器保存后函数变成字符串的问题
const fixFunctions = (obj: any) => { const fixFunctions = (obj: any) => {
if (obj && typeof obj === 'object') { if (obj && typeof obj === 'object') {
Object.keys(obj).forEach(key => { Object.keys(obj).forEach((key) => {
// 检查是否是函数相关的属性 // 检查是否是函数相关的属性
if (isFunctionProperty(key)) { if (isFunctionProperty(key)) {
// 如果不是函数类型,重新构建为函数 // 如果不是函数类型,重新构建为函数
@ -70,135 +73,108 @@ export const setConfAndFields2 = (
}) })
} }
} }
// 判断是否是函数属性 // 判断是否是函数属性
const isFunctionProperty = (key: string): boolean => { const isFunctionProperty = (key: string): boolean => {
const functionKeys = [ const functionKeys = [
'beforeFetch', // 请求前处理 'beforeFetch', // 请求前处理
'afterFetch', // 请求后处理 'afterFetch', // 请求后处理
'onSubmit', // 表单提交 'onSubmit', // 表单提交
'onReset', // 表单重置 'onReset', // 表单重置
'onChange', // 值变化 'onChange', // 值变化
'onInput', // 输入事件 'onInput', // 输入事件
'onClick', // 点击事件 'onClick', // 点击事件
'onFocus', // 获取焦点 'onFocus', // 获取焦点
'onBlur', // 失去焦点 'onBlur', // 失去焦点
'onMounted', // 组件挂载 'onMounted', // 组件挂载
'onCreated', // 组件创建 'onCreated', // 组件创建
'onReload', // 重新加载 'onReload', // 重新加载
'remoteMethod', // 远程搜索方法 'remoteMethod', // 远程搜索方法
'parseFunc', // 解析函数 'parseFunc', // 解析函数
'validator', // 验证器 'validator', // 验证器
'asyncValidator', // 异步验证器 'asyncValidator', // 异步验证器
'formatter', // 格式化函数 'formatter', // 格式化函数
'parser', // 解析函数 'parser', // 解析函数
'beforeUpload', // 上传前处理 'beforeUpload', // 上传前处理
'onSuccess', // 成功回调 'onSuccess', // 成功回调
'onError', // 错误回调 'onError', // 错误回调
'onProgress', // 进度回调 'onProgress', // 进度回调
'onPreview', // 预览回调 'onPreview', // 预览回调
'onRemove', // 移除回调 'onRemove', // 移除回调
'onExceed', // 超出限制回调 'onExceed', // 超出限制回调
'filterMethod', // 过滤方法 'filterMethod', // 过滤方法
'sortMethod', // 排序方法 'sortMethod', // 排序方法
'loadData', // 加载数据 'loadData', // 加载数据
'renderContent', // 渲染内容 'renderContent', // 渲染内容
'render' // 渲染函数 'render' // 渲染函数
] ]
// 检查是否以函数相关前缀开头 // 检查是否以函数相关前缀开头
const functionPrefixes = ['on', 'before', 'after', 'handle'] const functionPrefixes = ['on', 'before', 'after', 'handle']
return functionKeys.includes(key) || functionPrefixes.some((prefix) => key.startsWith(prefix))
return functionKeys.includes(key) ||
functionPrefixes.some(prefix => key.startsWith(prefix))
} }
// 根据函数名创建默认函数 // 根据函数名创建默认函数
const createDefaultFunction = (key: string): Function => { const createDefaultFunction = (key: string): Function => {
switch (key) { switch (key) {
case 'beforeFetch': case 'beforeFetch':
return (config: any) => { return (config: any) => {
console.log('beforeFetch被调用:', config) // 添加 Token 认证头。例如说:
// https://t.zsxq.com/hK3FO
// 添加认证头
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token) { if (token) {
config.headers = { config.headers = {
...config.headers, ...config.headers,
'Authorization': 'Bearer ' + token Authorization: 'Bearer ' + token
} }
} }
// 添加通用请求头 // 添加通用请求头
config.headers = { config.headers = {
...config.headers, ...config.headers,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest' 'X-Requested-With': 'XMLHttpRequest'
} }
// 添加时间戳防止缓存 // 添加时间戳防止缓存
config.params = { config.params = {
...config.params, ...config.params,
_t: Date.now() _t: Date.now()
} }
return config return config
} }
case 'afterFetch': case 'afterFetch':
return (data: any) => { return (data: any) => {
console.log('afterFetch被调用:', data)
return data return data
} }
case 'onSubmit': case 'onSubmit':
return (formData: any) => { return (_formData: any) => {
console.log('onSubmit被调用:', formData)
return true return true
} }
case 'onReset': case 'onReset':
return () => { return () => {
console.log('onReset被调用')
return true return true
} }
case 'onChange': case 'onChange':
return (value: any, oldValue: any) => { return (_value: any, _oldValue: any) => {}
console.log('onChange被调用:', { value, oldValue })
}
case 'remoteMethod': case 'remoteMethod':
return (query: string) => { return (query: string) => {
console.log('remoteMethod被调用:', query) console.log('remoteMethod被调用:', query)
} }
case 'parseFunc': case 'parseFunc':
return (data: any) => { return (data: any) => {
console.log('parseFunc被调用:', data)
// 默认解析逻辑如果是数组直接返回否则尝试获取list属性 // 默认解析逻辑如果是数组直接返回否则尝试获取list属性
if (Array.isArray(data)) { if (Array.isArray(data)) {
return data return data
} }
return data?.list || data?.data || [] return data?.list || data?.data || []
} }
case 'validator': case 'validator':
return (rule: any, value: any, callback: Function) => { return (_rule: any, _value: any, callback: Function) => {
console.log('validator被调用:', { rule, value })
callback() callback()
} }
case 'beforeUpload': case 'beforeUpload':
return (file: any) => { return (_file: any) => {
console.log('beforeUpload被调用:', file)
return true return true
} }
default: default:
// 通用默认函数 // 通用默认函数
return (...args: any[]) => { return (...args: any[]) => {
console.log(`${key}被调用:`, args)
// 对于事件处理函数返回true表示继续执行 // 对于事件处理函数返回true表示继续执行
if (key.startsWith('on') || key.startsWith('handle')) { if (key.startsWith('on') || key.startsWith('handle')) {
return true return true
@ -208,11 +184,9 @@ export const setConfAndFields2 = (
} }
} }
} }
// 修复 option 中的所有函数
// 修复option中的所有函数
fixFunctions(option) fixFunctions(option)
// 修复 rule 中的所有函数(包括组件的 props
// 修复rule中的所有函数包括组件的props
if (Array.isArray(rule)) { if (Array.isArray(rule)) {
rule.forEach((item: any) => { rule.forEach((item: any) => {
fixFunctions(item) fixFunctions(item)

View File

@ -101,7 +101,7 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
if (!route.children && route.parentId == 0 && route.component) { if (!route.children && route.parentId == 0 && route.component) {
data.component = Layout data.component = Layout
data.meta = { data.meta = {
hidden: meta.hidden, hidden: meta.hidden
} }
data.name = toCamelCase(route.path, true) + 'Parent' data.name = toCamelCase(route.path, true) + 'Parent'
data.redirect = '' data.redirect = ''
@ -170,8 +170,9 @@ const generateRoutePath = (parentPath: string, path: string) => {
} }
export const pathResolve = (parentPath: string, path: string) => { export const pathResolve = (parentPath: string, path: string) => {
if (isUrl(path)) return path if (isUrl(path)) return path
const childPath = path.startsWith('/') || !path ? path : `/${path}` if (!path) return parentPath // 修复 path 为空时返回 parentPath避免拼接出错 https://t.zsxq.com/QVr6b
return `${parentPath}${childPath}`.replace(/\/\//g, '/') const childPath = path.startsWith('/') ? path : `/${path}`
return `${parentPath}${childPath}`.replace(/\/+/g, '/')
} }
// 路由降级 // 路由降级

View File

@ -9,14 +9,14 @@
label-width="120px" label-width="120px"
size="large" size="large"
> >
<el-row style="margin-right: -10px; margin-left: -10px"> <el-row class="mx-[-10px]">
<!-- 租户名 --> <!-- 租户名 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<LoginFormTitle style="width: 100%" /> <LoginFormTitle class="w-full" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName"> <el-form-item v-if="resetPasswordData.tenantEnable === 'true'" prop="tenantName">
<el-input <el-input
v-model="resetPasswordData.tenantName" v-model="resetPasswordData.tenantName"
@ -28,7 +28,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 手机号 --> <!-- 手机号 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="mobile"> <el-form-item prop="mobile">
<el-input <el-input
v-model="resetPasswordData.mobile" v-model="resetPasswordData.mobile"
@ -45,7 +45,7 @@
@success="getSmsCode" @success="getSmsCode"
/> />
<!-- 验证码 --> <!-- 验证码 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="code"> <el-form-item prop="code">
<el-row :gutter="5" justify="space-between" style="width: 100%"> <el-row :gutter="5" justify="space-between" style="width: 100%">
<el-col :span="24"> <el-col :span="24">
@ -73,44 +73,44 @@
</el-row> </el-row>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="password"> <el-form-item prop="password">
<InputPassword <InputPassword
v-model="resetPasswordData.password" v-model="resetPasswordData.password"
:placeholder="t('login.passwordPlaceholder')" :placeholder="t('login.passwordPlaceholder')"
style="width: 100%" class="w-full"
strength="true" :strength="true"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="check_password"> <el-form-item prop="check_password">
<InputPassword <InputPassword
v-model="resetPasswordData.check_password" v-model="resetPasswordData.check_password"
:placeholder="t('login.checkPassword')" :placeholder="t('login.checkPassword')"
style="width: 100%" class="w-full"
strength="true" :strength="true"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 登录按钮 / 返回按钮 --> <!-- 登录按钮 / 返回按钮 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<XButton <XButton
:loading="loginLoading" :loading="loginLoading"
:title="t('login.resetPassword')" :title="t('login.resetPassword')"
class="w-[100%]" class="w-full"
type="primary" type="primary"
@click="resetPassword()" @click="resetPassword()"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<XButton <XButton
:loading="loginLoading" :loading="loginLoading"
:title="t('login.backLogin')" :title="t('login.backLogin')"
class="w-[100%]" class="w-full"
@click="handleBackLogin()" @click="handleBackLogin()"
/> />
</el-form-item> </el-form-item>
@ -134,7 +134,7 @@ const verify = ref()
const { t } = useI18n() const { t } = useI18n()
const message = useMessage() const message = useMessage()
const { currentRoute, push } = useRouter() const { currentRoute } = useRouter()
const formSmsResetPassword = ref() const formSmsResetPassword = ref()
const loginLoading = ref(false) const loginLoading = ref(false)
const iconHouse = useIcon({ icon: 'ep:house' }) const iconHouse = useIcon({ icon: 'ep:house' })
@ -145,7 +145,7 @@ const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD) const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
const captchaType = ref('blockPuzzle') // blockPuzzle clickWord const captchaType = ref('blockPuzzle') // blockPuzzle clickWord
const validatePass2 = (rule, value, callback) => { const validatePass2 = (_rule, value, callback) => {
if (value === '') { if (value === '') {
callback(new Error('请再次输入密码')) callback(new Error('请再次输入密码'))
} else if (value !== resetPasswordData.password) { } else if (value !== resetPasswordData.password) {

View File

@ -9,13 +9,13 @@
label-width="120px" label-width="120px"
size="large" size="large"
> >
<el-row style="margin-right: -10px; margin-left: -10px"> <el-row class="mx-[-10px]">
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<LoginFormTitle style="width: 100%" /> <LoginFormTitle class="w-full" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName"> <el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
<el-input <el-input
v-model="loginData.loginForm.tenantName" v-model="loginData.loginForm.tenantName"
@ -26,7 +26,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
v-model="loginData.loginForm.username" v-model="loginData.loginForm.username"
@ -35,7 +35,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input
v-model="loginData.loginForm.password" v-model="loginData.loginForm.password"
@ -49,7 +49,7 @@
</el-col> </el-col>
<el-col <el-col
:span="24" :span="24"
style="padding-right: 10px; padding-left: 10px; margin-top: -20px; margin-bottom: -20px" class="px-10px mt-[-20px] mb-[-20px]"
> >
<el-form-item> <el-form-item>
<el-row justify="space-between" style="width: 100%"> <el-row justify="space-between" style="width: 100%">
@ -60,7 +60,7 @@
</el-col> </el-col>
<el-col :offset="6" :span="12"> <el-col :offset="6" :span="12">
<el-link <el-link
style="float: right" class="float-right"
type="primary" type="primary"
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
> >
@ -70,12 +70,12 @@
</el-row> </el-row>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<XButton <XButton
:loading="loginLoading" :loading="loginLoading"
:title="t('login.login')" :title="t('login.login')"
class="w-[100%]" class="w-full"
type="primary" type="primary"
@click="getCode()" @click="getCode()"
/> />
@ -89,27 +89,27 @@
mode="pop" mode="pop"
@success="handleLogin" @success="handleLogin"
/> />
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<el-row :gutter="5" justify="space-between" style="width: 100%"> <el-row :gutter="5" justify="space-between" style="width: 100%">
<el-col :span="8"> <el-col :span="8">
<XButton <XButton
:title="t('login.btnMobile')" :title="t('login.btnMobile')"
class="w-[100%]" class="w-full"
@click="setLoginState(LoginStateEnum.MOBILE)" @click="setLoginState(LoginStateEnum.MOBILE)"
/> />
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<XButton <XButton
:title="t('login.btnQRCode')" :title="t('login.btnQRCode')"
class="w-[100%]" class="w-full"
@click="setLoginState(LoginStateEnum.QR_CODE)" @click="setLoginState(LoginStateEnum.QR_CODE)"
/> />
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<XButton <XButton
:title="t('login.btnRegister')" :title="t('login.btnRegister')"
class="w-[100%]" class="w-full"
@click="setLoginState(LoginStateEnum.REGISTER)" @click="setLoginState(LoginStateEnum.REGISTER)"
/> />
</el-col> </el-col>
@ -117,9 +117,9 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider> <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<div class="w-[100%] flex justify-between"> <div class="w-full flex justify-between">
<Icon <Icon
v-for="(item, key) in socialList" v-for="(item, key) in socialList"
:key="key" :key="key"
@ -133,9 +133,9 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-divider content-position="center">萌新必读</el-divider> <el-divider content-position="center">萌新必读</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<div class="w-[100%] flex justify-between"> <div class="w-full flex justify-between">
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link> <el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
<el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link> <el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
<el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank"> <el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">
@ -241,11 +241,13 @@ const getLoginFormCache = () => {
} }
// //
const getTenantByWebsite = async () => { const getTenantByWebsite = async () => {
const website = location.host if (loginData.tenantEnable === 'true') {
const res = await LoginApi.getTenantByWebsite(website) const website = location.host
if (res) { const res = await LoginApi.getTenantByWebsite(website)
loginData.loginForm.tenantName = res.name if (res) {
authUtil.setTenantId(res.id) loginData.loginForm.tenantName = res.name
authUtil.setTenantId(res.id)
}
} }
} }
const loading = ref() // ElLoading.service const loading = ref() // ElLoading.service

View File

@ -9,14 +9,14 @@
label-width="120px" label-width="120px"
size="large" size="large"
> >
<el-row style="margin-right: -10px; margin-left: -10px"> <el-row class="mx-[-10px]">
<!-- 租户名 --> <!-- 租户名 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<LoginFormTitle style="width: 100%" /> <LoginFormTitle class="w-full" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName"> <el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
<el-input <el-input
v-model="loginData.loginForm.tenantName" v-model="loginData.loginForm.tenantName"
@ -28,7 +28,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 手机号 --> <!-- 手机号 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="mobileNumber"> <el-form-item prop="mobileNumber">
<el-input <el-input
v-model="loginData.loginForm.mobileNumber" v-model="loginData.loginForm.mobileNumber"
@ -38,7 +38,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 验证码 --> <!-- 验证码 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="code"> <el-form-item prop="code">
<el-row :gutter="5" justify="space-between" style="width: 100%"> <el-row :gutter="5" justify="space-between" style="width: 100%">
<el-col :span="24"> <el-col :span="24">
@ -68,23 +68,23 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- 登录按钮 / 返回按钮 --> <!-- 登录按钮 / 返回按钮 -->
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<XButton <XButton
:loading="loginLoading" :loading="loginLoading"
:title="t('login.login')" :title="t('login.login')"
class="w-[100%]" class="w-full"
type="primary" type="primary"
@click="signIn()" @click="signIn()"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<XButton <XButton
:loading="loginLoading" :loading="loginLoading"
:title="t('login.backLogin')" :title="t('login.backLogin')"
class="w-[100%]" class="w-full"
@click="handleBackLogin()" @click="handleBackLogin()"
/> />
</el-form-item> </el-form-item>

View File

@ -1,17 +1,17 @@
<template> <template>
<el-row v-show="getShow" class="login-form" style="margin-right: -10px; margin-left: -10px"> <el-row v-show="getShow" class="login-form mx-[-10px]">
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<LoginFormTitle style="width: 100%" /> <LoginFormTitle class="w-full" />
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-card class="mb-10px text-center" shadow="hover"> <el-card class="mb-10px text-center" shadow="hover">
<Qrcode :logo="logoImg" /> <Qrcode :logo="logoImg" />
</el-card> </el-card>
</el-col> </el-col>
<el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider> <el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<div class="mt-15px w-[100%]"> <div class="mt-4 w-full">
<XButton :title="t('login.backLogin')" class="w-[100%]" @click="handleBackLogin()" /> <XButton :title="t('login.backLogin')" class="w-full" @click="handleBackLogin()" />
</div> </div>
</el-col> </el-col>
</el-row> </el-row>

View File

@ -9,13 +9,13 @@
label-width="120px" label-width="120px"
size="large" size="large"
> >
<el-row style="margin-right: -10px; margin-left: -10px"> <el-row class="mx-[-10px]">
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<LoginFormTitle style="width: 100%" /> <LoginFormTitle class="w-full" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item v-if="registerData.tenantEnable === 'true'" prop="tenantName"> <el-form-item v-if="registerData.tenantEnable === 'true'" prop="tenantName">
<el-input <el-input
v-model="registerData.registerForm.tenantName" v-model="registerData.registerForm.tenantName"
@ -27,7 +27,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
v-model="registerData.registerForm.username" v-model="registerData.registerForm.username"
@ -37,7 +37,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
v-model="registerData.registerForm.nickname" v-model="registerData.registerForm.nickname"
@ -47,7 +47,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input
v-model="registerData.registerForm.password" v-model="registerData.registerForm.password"
@ -60,7 +60,7 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item prop="confirmPassword"> <el-form-item prop="confirmPassword">
<el-input <el-input
v-model="registerData.registerForm.confirmPassword" v-model="registerData.registerForm.confirmPassword"
@ -73,12 +73,12 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24" style="padding-right: 10px; padding-left: 10px"> <el-col :span="24" class="px-10px">
<el-form-item> <el-form-item>
<XButton <XButton
:loading="loginLoading" :loading="loginLoading"
:title="t('login.register')" :title="t('login.register')"
class="w-[100%]" class="w-full"
type="primary" type="primary"
@click="getCode()" @click="getCode()"
/> />
@ -93,7 +93,7 @@
@success="handleRegister" @success="handleRegister"
/> />
</el-row> </el-row>
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" /> <XButton :title="t('login.hasUser')" class="w-full" @click="handleBackLogin()" />
</el-form> </el-form>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -123,7 +123,7 @@ const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER) const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
const equalToPassword = (rule, value, callback) => { const equalToPassword = (_rule, value, callback) => {
if (registerData.registerForm.password !== value) { if (registerData.registerForm.password !== value) {
callback(new Error('两次输入的密码不一致')) callback(new Error('两次输入的密码不一致'))
} else { } else {
@ -233,11 +233,13 @@ const getTenantId = async () => {
// //
const getTenantByWebsite = async () => { const getTenantByWebsite = async () => {
const website = location.host if (registerData.tenantEnable === 'true') {
const res = await LoginApi.getTenantByWebsite(website) const website = location.host
if (res) { const res = await LoginApi.getTenantByWebsite(website)
registerData.registerForm.tenantName = res.name if (res) {
authUtil.setTenantId(res.id) registerData.registerForm.tenantName = res.name
authUtil.setTenantId(res.id)
}
} }
} }
const loading = ref() // ElLoading.service const loading = ref() // ElLoading.service

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-show="ssoVisible" class="form-cont"> <div v-show="ssoVisible" class="form-cont">
<!-- 应用名 --> <!-- 应用名 -->
<LoginFormTitle style="width: 100%" /> <LoginFormTitle class="w-full" />
<el-tabs class="form" style="float: none" value="uname"> <el-tabs class="form" style="float: none" value="uname">
<el-tab-pane :label="client.name" name="uname" /> <el-tab-pane :label="client.name" name="uname" />
</el-tabs> </el-tabs>
@ -15,17 +15,17 @@
v-for="scope in queryParams.scopes" v-for="scope in queryParams.scopes"
:key="scope" :key="scope"
:value="scope" :value="scope"
style="display: block; margin-bottom: -10px" class="block mb-[-10px]"
> >
{{ formatScope(scope) }} {{ formatScope(scope) }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<!-- 下方的登录按钮 --> <!-- 下方的登录按钮 -->
<el-form-item class="w-1/1"> <el-form-item class="w-full">
<el-button <el-button
:loading="formLoading" :loading="formLoading"
class="w-6/10" class="w-3/5"
type="primary" type="primary"
@click.prevent="handleAuthorize(true)" @click.prevent="handleAuthorize(true)"
> >

View File

@ -18,7 +18,6 @@ import { useUserStore } from '@/store/modules/user'
import { useUpload } from '@/components/UploadFile/src/useUpload' import { useUpload } from '@/components/UploadFile/src/useUpload'
import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload' import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
// TODO @ ProfileUser
defineOptions({ name: 'UserAvatar' }) defineOptions({ name: 'UserAvatar' })
defineProps({ defineProps({
@ -30,10 +29,12 @@ const userStore = useUserStore()
const cropperRef = ref() const cropperRef = ref()
const handelUpload = async ({ data }) => { const handelUpload = async ({ data }) => {
const { httpRequest } = useUpload() const { httpRequest } = useUpload()
const avatar = ((await httpRequest({ const avatar = (
file: data, (await httpRequest({
filename: 'avatar.png', file: data,
} as UploadRequestOptions)) as unknown as { data: string }).data filename: 'avatar.png'
} as UploadRequestOptions)) as unknown as { data: string }
).data
await updateUserProfile({ avatar }) await updateUserProfile({ avatar })
// userStore // userStore

View File

@ -23,7 +23,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { SystemUserSocialTypeEnum } from '@/utils/constants' import { SystemUserSocialTypeEnum } from '@/utils/constants'
import { getUserProfile, ProfileVO } from '@/api/system/user/profile' import { getBindSocialUserList } from '@/api/system/social/user'
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser' import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
defineOptions({ name: 'UserSocial' }) defineOptions({ name: 'UserSocial' })
@ -32,19 +32,19 @@ defineProps<{
}>() }>()
const message = useMessage() const message = useMessage()
const socialUsers = ref<any[]>([]) const socialUsers = ref<any[]>([])
const userInfo = ref<ProfileVO>()
const initSocial = async () => { const initSocial = async () => {
socialUsers.value = [] // socialUsers.value = [] //
const res = await getUserProfile() //
userInfo.value = res const bindSocialUserList = await getBindSocialUserList()
//
for (const i in SystemUserSocialTypeEnum) { for (const i in SystemUserSocialTypeEnum) {
const socialUser = { ...SystemUserSocialTypeEnum[i] } const socialUser = { ...SystemUserSocialTypeEnum[i] }
socialUsers.value.push(socialUser) socialUsers.value.push(socialUser)
if (userInfo.value?.socialUsers) { if (bindSocialUserList && bindSocialUserList.length > 0) {
for (const j in userInfo.value.socialUsers) { for (const bindUser of bindSocialUserList) {
if (socialUser.type === userInfo.value.socialUsers[j].type) { if (socialUser.type === bindUser.type) {
socialUser.openid = userInfo.value.socialUsers[j].openid socialUser.openid = bindUser.openid
break break
} }
} }

View File

@ -391,7 +391,8 @@ onMounted(async () => {
line-height: 30px; line-height: 30px;
&.active { &.active {
background-color: #e6e6e6; background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-7);
.button { .button {
display: inline-block; display: inline-block;
@ -409,7 +410,7 @@ onMounted(async () => {
max-width: 220px; max-width: 220px;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
color: rgba(0, 0, 0, 0.77); color: var(--el-text-color-regular);
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -430,7 +431,7 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-items: center; justify-items: center;
color: #606266; color: var(--el-text-color-regular);
.btn { .btn {
margin: 0; margin: 0;
@ -447,8 +448,8 @@ onMounted(async () => {
right: 0; right: 0;
//width: 100%; //width: 100%;
padding: 0 20px; padding: 0 20px;
background-color: #f4f4f4; background-color: var(--el-fill-color-extra-light);
box-shadow: 0 0 1px 1px rgba(228, 228, 228, 0.8); box-shadow: 0 0 1px 1px var(--el-border-color-lighter);
line-height: 35px; line-height: 35px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -458,7 +459,7 @@ onMounted(async () => {
> div { > div {
display: flex; display: flex;
align-items: center; align-items: center;
color: #606266; color: var(--el-text-color-regular);
padding: 0; padding: 0;
margin: 0; margin: 0;
cursor: pointer; cursor: pointer;

View File

@ -215,13 +215,13 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-wrap: break-word; overflow-wrap: break-word;
background-color: rgba(228, 228, 228, 0.8); background-color: var(--el-fill-color-light);
box-shadow: 0 0 0 1px rgba(228, 228, 228, 0.8); box-shadow: 0 0 0 1px var(--el-border-color-light);
border-radius: 10px; border-radius: 10px;
padding: 10px 10px 5px 10px; padding: 10px 10px 5px 10px;
.left-text { .left-text {
color: #393939; color: var(--el-text-color-primary);
font-size: 0.95rem; font-size: 0.95rem;
} }
} }
@ -232,10 +232,10 @@ onMounted(async () => {
.right-text { .right-text {
font-size: 0.95rem; font-size: 0.95rem;
color: #fff; color: var(--el-color-white);
display: inline; display: inline;
background-color: #267fff; background-color: var(--el-color-primary);
box-shadow: 0 0 0 1px #267fff; box-shadow: 0 0 0 1px var(--el-color-primary);
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
width: auto; width: auto;
@ -270,7 +270,7 @@ onMounted(async () => {
.btn-cus:hover { .btn-cus:hover {
cursor: pointer; cursor: pointer;
background-color: #f6f6f6; background-color: var(--el-fill-color-lighter);
} }
} }

View File

@ -29,14 +29,14 @@ defineProps({
padding: 0 10px; padding: 0 10px;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
background-color: #ececec; background-color: var(--el-bg-color-page);
width: 100%; width: 100%;
.title { .title {
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
overflow: hidden; overflow: hidden;
color: #3e3e3e; color: var(--el-text-color-primary);
max-width: 220px; max-width: 220px;
} }

View File

@ -13,10 +13,10 @@
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item :command="['edit', role]"> <el-dropdown-item :command="['edit', role]">
<Icon icon="ep:edit" color="#787878" />编辑 <Icon icon="ep:edit" color="var(--el-text-color-placeholder)" />编辑
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item :command="['delete', role]" style="color: red"> <el-dropdown-item :command="['delete', role]" style="color: var(--el-color-danger)">
<Icon icon="ep:delete" color="red" />删除 <Icon icon="ep:delete" color="var(--el-color-danger)" />删除
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@ -153,13 +153,13 @@ const handleTabsScroll = async () => {
.title { .title {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
color: #3e3e3e; color: var(--el-text-color-primary);
} }
.description { .description {
margin-top: 10px; margin-top: 10px;
font-size: 14px; font-size: 14px;
color: #6a6a6a; color: var(--el-text-color-regular);
} }
} }

View File

@ -23,7 +23,7 @@
@click="handlerAddRole" @click="handlerAddRole"
class="ml-20px" class="ml-20px"
> >
<Icon icon="ep:user" style="margin-right: 5px;" /> <Icon icon="ep:user" style="margin-right: 5px" />
添加角色 添加角色
</el-button> </el-button>
</div> </div>
@ -64,15 +64,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref} from 'vue' import { ref } from 'vue'
import RoleHeader from './RoleHeader.vue' import RoleHeader from './RoleHeader.vue'
import RoleList from './RoleList.vue' import RoleList from './RoleList.vue'
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue' import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
import RoleCategoryList from './RoleCategoryList.vue' import RoleCategoryList from './RoleCategoryList.vue'
import {ChatRoleApi, ChatRolePageReqVO, ChatRoleVO} from '@/api/ai/model/chatRole' import { ChatRoleApi, ChatRolePageReqVO, ChatRoleVO } from '@/api/ai/model/chatRole'
import {ChatConversationApi, ChatConversationVO} from '@/api/ai/chat/conversation' import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
import {Search} from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import {TabsPaneContext} from 'element-plus' import { TabsPaneContext } from 'element-plus'
const router = useRouter() // const router = useRouter() //
@ -244,7 +244,7 @@ onMounted(async () => {
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
background-color: #ffffff; background-color: var(--el-bg-color);
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -22,13 +22,16 @@
<Icon icon="ep:setting" class="ml-10px" /> <Icon icon="ep:setting" class="ml-10px" />
</el-button> </el-button>
<el-button size="small" class="btn" @click="handlerMessageClear"> <el-button size="small" class="btn" @click="handlerMessageClear">
<Icon icon="heroicons-outline:archive-box-x-mark" color="#787878" /> <Icon
icon="heroicons-outline:archive-box-x-mark"
color="var(--el-text-color-placeholder)"
/>
</el-button> </el-button>
<el-button size="small" class="btn"> <el-button size="small" class="btn">
<Icon icon="ep:download" color="#787878" /> <Icon icon="ep:download" color="var(--el-text-color-placeholder)" />
</el-button> </el-button>
<el-button size="small" class="btn" @click="handleGoTopMessage"> <el-button size="small" class="btn" @click="handleGoTopMessage">
<Icon icon="ep:top" color="#787878" /> <Icon icon="ep:top" color="var(--el-text-color-placeholder)" />
</el-button> </el-button>
</div> </div>
</el-header> </el-header>
@ -613,7 +616,8 @@ onMounted(async () => {
line-height: 30px; line-height: 30px;
&.active { &.active {
background-color: #e6e6e6; background-color: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-7);
.button { .button {
display: inline-block; display: inline-block;
@ -649,7 +653,7 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-items: center; justify-items: center;
color: #606266; color: var(--el-text-color-regular);
.el-icon { .el-icon {
margin-right: 5px; margin-right: 5px;
@ -669,7 +673,7 @@ onMounted(async () => {
> div { > div {
display: flex; display: flex;
align-items: center; align-items: center;
color: #606266; color: var(--el-text-color-regular);
padding: 0; padding: 0;
margin: 0; margin: 0;
cursor: pointer; cursor: pointer;
@ -683,15 +687,15 @@ onMounted(async () => {
// //
.detail-container { .detail-container {
background: #ffffff; background: var(--el-bg-color);
.header { .header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
background: #fbfbfb; background: var(--el-bg-color-page);
box-shadow: 0 0 0 0 #dcdfe6; box-shadow: 0 0 0 0 var(--el-border-color-light);
.title { .title {
font-size: 18px; font-size: 18px;
@ -744,7 +748,7 @@ onMounted(async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: auto; height: auto;
border: 1px solid #e3e3e3; border: 1px solid var(--el-border-color);
border-radius: 10px; border-radius: 10px;
margin: 10px 20px 20px 20px; margin: 10px 20px 20px 20px;
padding: 9px 10px; padding: 9px 10px;

View File

@ -510,10 +510,15 @@ const isManagerUser = (row: any) => {
/** 处理模型的排序 **/ /** 处理模型的排序 **/
const handleModelSort = () => { const handleModelSort = () => {
// if (isModelSorting.value) {
originalData.value = cloneDeep(props.categoryInfo.modelList) //
isModelSorting.value = true handleModelSortCancel()
initSort() } else {
//
originalData.value = cloneDeep(props.categoryInfo.modelList)
isModelSorting.value = true
initSort()
}
} }
/** 处理模型的排序提交 */ /** 处理模型的排序提交 */

View File

@ -186,7 +186,23 @@ const currentSelectType = ref<'start' | 'manager'>('start')
const rules = { const rules = {
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }], name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }], key: [
{ required: true, message: '流程标识不能为空', trigger: 'blur' },
{
validator: (_rule: any, value: string, callback: any) => {
if (!value) {
callback()
return
}
if (!/^[a-zA-Z_][\-_.0-9_a-zA-Z$]*$/.test(value)) {
callback(new Error('只能包含字母、数字、下划线、连字符和点号,且必须以字母或下划线开头'))
return
}
callback()
},
trigger: 'blur'
}
],
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }], category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }], type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }], visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],

View File

@ -232,6 +232,34 @@ import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/co
import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue' import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue'
const modelData = defineModel<any>() const modelData = defineModel<any>()
const formFields = ref<string[]>([])
const props = defineProps({
// ID
modelFormId: {
type: Number,
required: false,
default: undefined,
}
})
// modelFormId
watch(
() => props.modelFormId,
async (newVal) => {
if (newVal) {
const form = await FormApi.getForm(newVal);
formFields.value = form?.fields;
} else {
// modelFormId
formFields.value = [];
}
},
{ immediate: true },
);
// 使
provide('formFields', formFields)
/** 自定义 ID 流程编码 */ /** 自定义 ID 流程编码 */
const timeOptions = ref([ const timeOptions = ref([

View File

@ -14,9 +14,9 @@
<template v-else> <template v-else>
<SimpleModelDesign <SimpleModelDesign
v-if="showDesigner" v-if="showDesigner"
:model-id="modelData.id"
:model-key="modelData.key"
:model-name="modelData.name" :model-name="modelData.name"
:model-form-id="modelData.formId"
:model-form-type="modelData.formType"
:start-user-ids="modelData.startUserIds" :start-user-ids="modelData.startUserIds"
:start-dept-ids="modelData.startDeptIds" :start-dept-ids="modelData.startDeptIds"
@success="handleDesignSuccess" @success="handleDesignSuccess"

View File

@ -77,7 +77,10 @@
<!-- 第四步更多设置 --> <!-- 第四步更多设置 -->
<div v-show="currentStep === 3" class="mx-auto w-700px"> <div v-show="currentStep === 3" class="mx-auto w-700px">
<ExtraSettings v-model="formData" ref="extraSettingsRef" /> <ExtraSettings
ref="extraSettingsRef"
v-model="formData"
:model-form-id="formData.formId"/>
</div> </div>
</div> </div>
</div> </div>
@ -216,6 +219,16 @@ const initData = async () => {
// //
if (route.params.type === 'copy') { if (route.params.type === 'copy') {
delete formData.value.id delete formData.value.id
if (formData.value.bpmnXml) {
formData.value.bpmnXml = formData.value.bpmnXml.replaceAll(
formData.value.name,
formData.value.name + '副本'
)
formData.value.bpmnXml = formData.value.bpmnXml.replaceAll(
formData.value.key,
formData.value.key + '_copy'
)
}
formData.value.name += '副本' formData.value.name += '副本'
formData.value.key += '_copy' formData.value.key += '_copy'
tagsView.setTitle('复制流程') tagsView.setTitle('复制流程')

View File

@ -209,15 +209,18 @@ onActivated(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
:deep() { :deep() {
.el-table--fit .el-table__inner-wrapper:before { .el-table--fit .el-table__inner-wrapper::before {
height: 0; height: 0;
} }
.el-card { .el-card {
border-radius: 8px; border-radius: 8px;
} }
.el-form--inline .el-form-item { .el-form--inline .el-form-item {
margin-right: 10px; margin-right: 10px;
} }
.el-divider--horizontal { .el-divider--horizontal {
margin-top: 6px; margin-top: 6px;
} }

View File

@ -685,6 +685,7 @@ watch(
/** 弹出气泡卡 */ /** 弹出气泡卡 */
const openPopover = async (type: string) => { const openPopover = async (type: string) => {
if (popOverVisible.value[type] === true) return
if (type === 'approve') { if (type === 'approve') {
// //
const valid = await validateNormalForm() const valid = await validateNormalForm()

View File

@ -38,7 +38,13 @@
</div> </div>
</div> </div>
<div v-if="activity.nodeType === NodeType.CHILD_PROCESS_NODE"> <div v-if="activity.nodeType === NodeType.CHILD_PROCESS_NODE">
<el-button type="primary" plain size="small" @click="handleChildProcess(activity)"> <el-button
type="primary"
plain
size="small"
@click="handleChildProcess(activity)"
:disabled="!activity.processInstanceId"
>
查看子流程 查看子流程
</el-button> </el-button>
</div> </div>
@ -319,7 +325,9 @@ const handleUserSelectConfirm = (activityId: string, userList: any[]) => {
/** 跳转子流程 */ /** 跳转子流程 */
const handleChildProcess = (activity: any) => { const handleChildProcess = (activity: any) => {
// TODO @lesan if (!activity.processInstanceId) {
return
}
push({ push({
name: 'BpmProcessInstanceDetail', name: 'BpmProcessInstanceDetail',
query: { query: {

View File

@ -1,12 +1,11 @@
<template> <template>
<ContentWrap :bodyStyle="{ padding: '20px 16px' }"> <ContentWrap :bodyStyle="{ padding: '20px 16px' }">
<SimpleProcessDesigner <SimpleProcessDesigner
:model-id="modelId" :model-form-id="modelFormId"
:model-key="modelKey" :model-form-type="modelFormType"
:model-name="modelName"
@success="handleSuccess"
:start-user-ids="startUserIds" :start-user-ids="startUserIds"
:start-dept-ids="startDeptIds" :start-dept-ids="startDeptIds"
@success="handleSuccess"
ref="designerRef" ref="designerRef"
/> />
</ContentWrap> </ContentWrap>
@ -19,9 +18,9 @@ defineOptions({
}) })
defineProps<{ defineProps<{
modelId?: string
modelKey?: string
modelName?: string modelName?: string
modelFormId?: number
modelFormType?: number
startUserIds?: number[] startUserIds?: number[]
startDeptIds?: number[] startDeptIds?: number[]
}>() }>()

View File

@ -56,7 +56,7 @@ import type { UploadUserFile } from 'element-plus'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
defineOptions({ name: 'SystemUserImportForm' }) defineOptions({ name: 'CrmCustomerImportForm' })
const message = useMessage() // const message = useMessage() //

View File

@ -24,6 +24,38 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="跟进内容" prop="content" /> <el-table-column align="center" label="跟进内容" prop="content" />
<el-table-column label="图片" align="center">
<template #default="scope">
<div v-if="scope.row.picUrls && scope.row.picUrls.length > 0" class="flex">
<el-image
v-for="(url, index) in scope.row.picUrls"
:key="index"
:src="url"
:preview-src-list="scope.row.picUrls"
class="w-10 h-10 mr-1"
:initial-index="index"
fit="cover"
preview-teleported
/>
</div>
</template>
</el-table-column>
<el-table-column label="附件" align="center">
<template #default="scope">
<div v-if="scope.row.fileUrls && scope.row.fileUrls.length > 0" class="flex flex-col">
<el-link
v-for="(url, index) in scope.row.fileUrls"
:key="index"
:href="url"
type="primary"
target="_blank"
download
>
{{ getFileName(url) }}
</el-link>
</div>
</template>
</el-table-column>
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
@ -97,6 +129,14 @@ import { BizTypeEnum } from '@/api/crm/permission'
/** 跟进记录列表 */ /** 跟进记录列表 */
defineOptions({ name: 'FollowUpRecord' }) defineOptions({ name: 'FollowUpRecord' })
const getFileName = (url: string) => {
if (!url) {
return ''
}
return url.substring(url.lastIndexOf('/') + 1)
}
const props = defineProps<{ const props = defineProps<{
bizType: number bizType: number
bizId: number bizId: number

View File

@ -107,16 +107,15 @@ const initGiveCouponList = async () => {
/** 设置赠送的优惠券 */ /** 设置赠送的优惠券 */
const setGiveCouponList = () => { const setGiveCouponList = () => {
if (isEmpty(rewardRule.value) || isEmpty(list.value)) { if (isEmpty(rewardRule.value)) {
return return
} }
// rewardRule.value.giveCouponTemplateCounts
rewardRule.value.giveCouponTemplateCounts = {}
// //
list.value.forEach((rule) => { list.value.forEach((rule) => {
if (!rewardRule.value.giveCouponTemplateCounts) { rewardRule.value.giveCouponTemplateCounts![rule.id] = rule.giveCount!
rewardRule.value.giveCouponTemplateCounts = {}
}
rewardRule.value.giveCouponTemplateCounts[rule.id] = rule.giveCount!
}) })
} }
defineExpose({ setGiveCouponList }) defineExpose({ setGiveCouponList })

View File

@ -94,7 +94,7 @@ import { MenuVO } from '@/api/system/menu'
import MenuForm from './MenuForm.vue' import MenuForm from './MenuForm.vue'
import DictTag from '@/components/DictTag/src/DictTag.vue' import DictTag from '@/components/DictTag/src/DictTag.vue'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { ElButton, TableV2FixedDir } from 'element-plus' import { ElButton, TableV2FixedDir, ElSwitch } from 'element-plus'
import { checkPermi } from '@/utils/permission' import { checkPermi } from '@/utils/permission'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
@ -175,7 +175,7 @@ const columns = [
fixed: TableV2FixedDir.RIGHT, fixed: TableV2FixedDir.RIGHT,
cellRenderer: ({ rowData }) => { cellRenderer: ({ rowData }) => {
// //
const buttons = [] const buttons: InstanceType<typeof ElButton>[] = []
// //
if (checkPermi(['system:menu:update'])) { if (checkPermi(['system:menu:update'])) {