style: 使用 Prettier 格式化源码

master-bpm-bug-fix
YunaiV 2026-06-20 07:01:34 -07:00
parent 3208a76868
commit 18ed1cdfed
221 changed files with 1426 additions and 908 deletions

View File

@ -39,4 +39,4 @@ export const ProcessExpressionApi = {
exportProcessExpression: async (params) => { exportProcessExpression: async (params) => {
return await request.download({ url: `/bpm/process-expression/export-excel`, params }) return await request.download({ url: `/bpm/process-expression/export-excel`, params })
} }
} }

View File

@ -1,6 +1,5 @@
import request from '@/config/axios' import request from '@/config/axios'
export const updateBpmSimpleModel = async (data) => { export const updateBpmSimpleModel = async (data) => {
return await request.post({ return await request.post({
url: '/bpm/model/simple/update', url: '/bpm/model/simple/update',

View File

@ -34,7 +34,11 @@ export const getMyFriendList = () => {
} }
// 增量拉取当前用户的好友关系(重连 / 离线补偿) // 增量拉取当前用户的好友关系(重连 / 离线补偿)
export const pullMyFriendList = (params: { lastUpdateTime?: number; lastId?: number; limit: number }) => { export const pullMyFriendList = (params: {
lastUpdateTime?: number
lastId?: number
limit: number
}) => {
return request.get<ImFriendRespVO[]>({ url: '/im/friend/pull', params }) return request.get<ImFriendRespVO[]>({ url: '/im/friend/pull', params })
} }
@ -65,4 +69,3 @@ export const blockFriend = (friendUserId: number | string) => {
export const unblockFriend = (friendUserId: number | string) => { export const unblockFriend = (friendUserId: number | string) => {
return request.put<boolean>({ url: '/im/friend/unblock', params: { friendUserId } }) return request.put<boolean>({ url: '/im/friend/unblock', params: { friendUserId } })
} }

View File

@ -72,6 +72,10 @@ export const getMyGroupRequest = (id: number) => {
} }
// 增量拉取我管理的所有群下加群申请变更(重连 / 离线补偿) // 增量拉取我管理的所有群下加群申请变更(重连 / 离线补偿)
export const pullMyGroupRequestList = (params: { lastUpdateTime?: number; lastId?: number; limit: number }) => { export const pullMyGroupRequestList = (params: {
lastUpdateTime?: number
lastId?: number
limit: number
}) => {
return request.get<ImGroupRequestRespVO[]>({ url: '/im/group-request/pull', params }) return request.get<ImGroupRequestRespVO[]>({ url: '/im/group-request/pull', params })
} }

View File

@ -42,12 +42,18 @@ export const getStatisticsOverview = (): Promise<ImStatisticsOverviewVO> => {
// 获得消息趋势(私聊 + 群聊双线) // 获得消息趋势(私聊 + 群聊双线)
export const getMessageTrend = (days: number): Promise<ImStatisticsTrendVO> => { export const getMessageTrend = (days: number): Promise<ImStatisticsTrendVO> => {
return request.get<ImStatisticsTrendVO>({ url: '/im/manager/statistics/message-trend', params: { days } }) return request.get<ImStatisticsTrendVO>({
url: '/im/manager/statistics/message-trend',
params: { days }
})
} }
// 获得用户趋势(新增注册 + 日活双线) // 获得用户趋势(新增注册 + 日活双线)
export const getUserTrend = (days: number): Promise<ImStatisticsTrendVO> => { export const getUserTrend = (days: number): Promise<ImStatisticsTrendVO> => {
return request.get<ImStatisticsTrendVO>({ url: '/im/manager/statistics/user-trend', params: { days } }) return request.get<ImStatisticsTrendVO>({
url: '/im/manager/statistics/user-trend',
params: { days }
})
} }
// 获得内容类型分布(最近 30 天) // 获得内容类型分布(最近 30 天)

View File

@ -26,7 +26,10 @@ export const deleteDataSourceConfig = (id: number) => {
// 批量删除数据源配置 // 批量删除数据源配置
export const deleteDataSourceConfigList = (ids: number[]) => { export const deleteDataSourceConfigList = (ids: number[]) => {
return request.delete({ url: '/infra/data-source-config/delete-list', params: { ids: ids.join(',') } }) return request.delete({
url: '/infra/data-source-config/delete-list',
params: { ids: ids.join(',') }
})
} }
// 查询数据源配置详情 // 查询数据源配置详情

View File

@ -1,29 +1,29 @@
import request from '@/config/axios' import request from '@/config/axios'
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs'
/** 学生课程信息 */ /** 学生课程信息 */
export interface Demo03Course { export interface Demo03Course {
id: number; // 编号 id: number // 编号
studentId?: number; // 学生编号 studentId?: number // 学生编号
name?: string; // 名字 name?: string // 名字
score?: number; // 分数 score?: number // 分数
} }
/** 学生班级信息 */ /** 学生班级信息 */
export interface Demo03Grade { export interface Demo03Grade {
id: number; // 编号 id: number // 编号
studentId?: number; // 学生编号 studentId?: number // 学生编号
name?: string; // 名字 name?: string // 名字
teacher?: string; // 班主任 teacher?: string // 班主任
} }
/** 学生信息 */ /** 学生信息 */
export interface Demo03Student { export interface Demo03Student {
id: number; // 编号 id: number // 编号
name?: string; // 名字 name?: string // 名字
sex?: number; // 性别 sex?: number // 性别
birthday?: string | Dayjs; // 出生日期 birthday?: string | Dayjs // 出生日期
description?: string; // 简介 description?: string // 简介
} }
// 学生 API // 学生 API
@ -55,7 +55,9 @@ export const Demo03StudentApi = {
/** 批量删除学生 */ /** 批量删除学生 */
deleteDemo03StudentList: async (ids: number[]) => { deleteDemo03StudentList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-erp/delete-list?ids=${ids.join(',')}` }) return await request.delete({
url: `/infra/demo03-student-erp/delete-list?ids=${ids.join(',')}`
})
}, },
// 导出学生 Excel // 导出学生 Excel
@ -63,7 +65,7 @@ export const Demo03StudentApi = {
return await request.download({ url: `/infra/demo03-student-erp/export-excel`, params }) return await request.download({ url: `/infra/demo03-student-erp/export-excel`, params })
}, },
// ==================== 子表(学生课程) ==================== // ==================== 子表(学生课程) ====================
// 获得学生课程分页 // 获得学生课程分页
getDemo03CoursePage: async (params) => { getDemo03CoursePage: async (params) => {
@ -86,7 +88,9 @@ export const Demo03StudentApi = {
/** 批量删除学生课程 */ /** 批量删除学生课程 */
deleteDemo03CourseList: async (ids: number[]) => { deleteDemo03CourseList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-erp/demo03-course/delete-list?ids=${ids.join(',')}` }) return await request.delete({
url: `/infra/demo03-student-erp/demo03-course/delete-list?ids=${ids.join(',')}`
})
}, },
// 获得学生课程 // 获得学生课程
@ -94,7 +98,7 @@ export const Demo03StudentApi = {
return await request.get({ url: `/infra/demo03-student-erp/demo03-course/get?id=` + id }) return await request.get({ url: `/infra/demo03-student-erp/demo03-course/get?id=` + id })
}, },
// ==================== 子表(学生班级) ==================== // ==================== 子表(学生班级) ====================
// 获得学生班级分页 // 获得学生班级分页
getDemo03GradePage: async (params) => { getDemo03GradePage: async (params) => {
@ -117,11 +121,13 @@ export const Demo03StudentApi = {
/** 批量删除学生班级 */ /** 批量删除学生班级 */
deleteDemo03GradeList: async (ids: number[]) => { deleteDemo03GradeList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-erp/demo03-grade/delete-list?ids=${ids.join(',')}` }) return await request.delete({
url: `/infra/demo03-student-erp/demo03-grade/delete-list?ids=${ids.join(',')}`
})
}, },
// 获得学生班级 // 获得学生班级
getDemo03Grade: async (id: number) => { getDemo03Grade: async (id: number) => {
return await request.get({ url: `/infra/demo03-student-erp/demo03-grade/get?id=` + id }) return await request.get({ url: `/infra/demo03-student-erp/demo03-grade/get?id=` + id })
}, }
} }

View File

@ -1,29 +1,29 @@
import request from '@/config/axios' import request from '@/config/axios'
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs'
/** 学生课程信息 */ /** 学生课程信息 */
export interface Demo03Course { export interface Demo03Course {
id: number; // 编号 id: number // 编号
studentId?: number; // 学生编号 studentId?: number // 学生编号
name?: string; // 名字 name?: string // 名字
score?: number; // 分数 score?: number // 分数
} }
/** 学生班级信息 */ /** 学生班级信息 */
export interface Demo03Grade { export interface Demo03Grade {
id: number; // 编号 id: number // 编号
studentId?: number; // 学生编号 studentId?: number // 学生编号
name?: string; // 名字 name?: string // 名字
teacher?: string; // 班主任 teacher?: string // 班主任
} }
/** 学生信息 */ /** 学生信息 */
export interface Demo03Student { export interface Demo03Student {
id: number; // 编号 id: number // 编号
name?: string; // 名字 name?: string // 名字
sex?: number; // 性别 sex?: number // 性别
birthday?: string | Dayjs; // 出生日期 birthday?: string | Dayjs // 出生日期
description?: string; // 简介 description?: string // 简介
demo03courses?: Demo03Course[] demo03courses?: Demo03Course[]
demo03grade?: Demo03Grade demo03grade?: Demo03Grade
} }
@ -57,7 +57,9 @@ export const Demo03StudentApi = {
/** 批量删除学生 */ /** 批量删除学生 */
deleteDemo03StudentList: async (ids: number[]) => { deleteDemo03StudentList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-inner/delete-list?ids=${ids.join(',')}` }) return await request.delete({
url: `/infra/demo03-student-inner/delete-list?ids=${ids.join(',')}`
})
}, },
// 导出学生 Excel // 导出学生 Excel
@ -65,17 +67,21 @@ export const Demo03StudentApi = {
return await request.download({ url: `/infra/demo03-student-inner/export-excel`, params }) return await request.download({ url: `/infra/demo03-student-inner/export-excel`, params })
}, },
// ==================== 子表(学生课程) ==================== // ==================== 子表(学生课程) ====================
// 获得学生课程列表 // 获得学生课程列表
getDemo03CourseListByStudentId: async (studentId) => { getDemo03CourseListByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-inner/demo03-course/list-by-student-id?studentId=` + studentId }) return await request.get({
url: `/infra/demo03-student-inner/demo03-course/list-by-student-id?studentId=` + studentId
})
}, },
// ==================== 子表(学生班级) ==================== // ==================== 子表(学生班级) ====================
// 获得学生班级 // 获得学生班级
getDemo03GradeByStudentId: async (studentId) => { getDemo03GradeByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-inner/demo03-grade/get-by-student-id?studentId=` + studentId }) return await request.get({
}, url: `/infra/demo03-student-inner/demo03-grade/get-by-student-id?studentId=` + studentId
})
}
} }

View File

@ -1,29 +1,29 @@
import request from '@/config/axios' import request from '@/config/axios'
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs'
/** 学生课程信息 */ /** 学生课程信息 */
export interface Demo03Course { export interface Demo03Course {
id: number; // 编号 id: number // 编号
studentId?: number; // 学生编号 studentId?: number // 学生编号
name?: string; // 名字 name?: string // 名字
score?: number; // 分数 score?: number // 分数
} }
/** 学生班级信息 */ /** 学生班级信息 */
export interface Demo03Grade { export interface Demo03Grade {
id: number; // 编号 id: number // 编号
studentId?: number; // 学生编号 studentId?: number // 学生编号
name?: string; // 名字 name?: string // 名字
teacher?: string; // 班主任 teacher?: string // 班主任
} }
/** 学生信息 */ /** 学生信息 */
export interface Demo03Student { export interface Demo03Student {
id: number; // 编号 id: number // 编号
name?: string; // 名字 name?: string // 名字
sex?: number; // 性别 sex?: number // 性别
birthday?: string | Dayjs; // 出生日期 birthday?: string | Dayjs // 出生日期
description?: string; // 简介 description?: string // 简介
demo03courses?: Demo03Course[] demo03courses?: Demo03Course[]
demo03grade?: Demo03Grade demo03grade?: Demo03Grade
} }
@ -57,7 +57,9 @@ export const Demo03StudentApi = {
/** 批量删除学生 */ /** 批量删除学生 */
deleteDemo03StudentList: async (ids: number[]) => { deleteDemo03StudentList: async (ids: number[]) => {
return await request.delete({ url: `/infra/demo03-student-normal/delete-list?ids=${ids.join(',')}` }) return await request.delete({
url: `/infra/demo03-student-normal/delete-list?ids=${ids.join(',')}`
})
}, },
// 导出学生 Excel // 导出学生 Excel
@ -65,17 +67,21 @@ export const Demo03StudentApi = {
return await request.download({ url: `/infra/demo03-student-normal/export-excel`, params }) return await request.download({ url: `/infra/demo03-student-normal/export-excel`, params })
}, },
// ==================== 子表(学生课程) ==================== // ==================== 子表(学生课程) ====================
// 获得学生课程列表 // 获得学生课程列表
getDemo03CourseListByStudentId: async (studentId) => { getDemo03CourseListByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-normal/demo03-course/list-by-student-id?studentId=` + studentId }) return await request.get({
url: `/infra/demo03-student-normal/demo03-course/list-by-student-id?studentId=` + studentId
})
}, },
// ==================== 子表(学生班级) ==================== // ==================== 子表(学生班级) ====================
// 获得学生班级 // 获得学生班级
getDemo03GradeByStudentId: async (studentId) => { getDemo03GradeByStudentId: async (studentId) => {
return await request.get({ url: `/infra/demo03-student-normal/demo03-grade/get-by-student-id?studentId=` + studentId }) return await request.get({
}, url: `/infra/demo03-student-normal/demo03-grade/get-by-student-id?studentId=` + studentId
})
}
} }

View File

@ -38,7 +38,10 @@ export const deleteMailAccount = async (id: number) => {
// 批量删除邮箱账号 // 批量删除邮箱账号
export const deleteMailAccountList = async (ids: number[]) => { export const deleteMailAccountList = async (ids: number[]) => {
return await request.delete({ url: '/system/mail-account/delete-list', params: { ids: ids.join(',') } }) return await request.delete({
url: '/system/mail-account/delete-list',
params: { ids: ids.join(',') }
})
} }
// 获得邮箱账号精简列表 // 获得邮箱账号精简列表

View File

@ -48,5 +48,8 @@ export const deleteOAuth2Client = (id: number) => {
// 批量删除 OAuth2 客户端 // 批量删除 OAuth2 客户端
export const deleteOAuth2ClientList = (ids: number[]) => { export const deleteOAuth2ClientList = (ids: number[]) => {
return request.delete({ url: '/system/oauth2-client/delete-list', params: { ids: ids.join(',') } }) return request.delete({
url: '/system/oauth2-client/delete-list',
params: { ids: ids.join(',') }
})
} }

View File

@ -39,7 +39,10 @@ export const deleteTenantPackage = (id: number) => {
// 批量删除租户套餐 // 批量删除租户套餐
export const deleteTenantPackageList = (ids: number[]) => { export const deleteTenantPackageList = (ids: number[]) => {
return request.delete({ url: '/system/tenant-package/delete-list', params: { ids: ids.join(',') } }) return request.delete({
url: '/system/tenant-package/delete-list',
params: { ids: ids.join(',') }
})
} }
// 获取租户套餐精简信息列表 // 获取租户套餐精简信息列表

View File

@ -17,7 +17,12 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="高度" prop="height"> <el-form-item label="高度" prop="height">
<el-input-number class="!w-50% mr-10px" controls-position="right" v-model="formData.height" /> px <el-input-number
class="!w-50% mr-10px"
controls-position="right"
v-model="formData.height"
/>
px
</el-form-item> </el-form-item>
<el-form-item label="指示器" prop="indicator"> <el-form-item label="指示器" prop="indicator">
<el-radio-group v-model="formData.indicator"> <el-radio-group v-model="formData.indicator">

View File

@ -1,4 +1,4 @@
import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util' import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 积分商城属性 */ /** 积分商城属性 */
export interface PromotionPointProperty { export interface PromotionPointProperty {

View File

@ -31,13 +31,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="高度" prop="height" label-width="70px"> <el-form-item label="高度" prop="height" label-width="70px">
<el-slider <el-slider v-model="formData.height" :max="200" :min="20" show-input input-size="small" />
v-model="formData.height"
:max="200"
:min="20"
show-input
input-size="small"
/>
</el-form-item> </el-form-item>
</el-card> </el-card>
<el-card header="主标题" class="property-group" shadow="never"> <el-card header="主标题" class="property-group" shadow="never">

View File

@ -34,7 +34,7 @@ interface AreaVO {
interface Props { interface Props {
modelValue?: number[] | string[] modelValue?: number[] | string[]
level?: typeof AreaLevelEnum[keyof typeof AreaLevelEnum] level?: (typeof AreaLevelEnum)[keyof typeof AreaLevelEnum]
disabled?: boolean disabled?: boolean
placeholder?: string placeholder?: string
clearable?: boolean clearable?: boolean

View File

@ -155,32 +155,32 @@ const hasValidPresetValue = (): boolean => {
// //
const setDefaultValue = () => { const setDefaultValue = () => {
console.log('[DeptSelect] setDefaultValue called, defaultCurrentDept:', props.defaultCurrentDept) console.log('[DeptSelect] setDefaultValue called, defaultCurrentDept:', props.defaultCurrentDept)
// defaultCurrentDept true // defaultCurrentDept true
if (!props.defaultCurrentDept) { if (!props.defaultCurrentDept) {
console.log('[DeptSelect] defaultCurrentDept is false, skip') console.log('[DeptSelect] defaultCurrentDept is false, skip')
return return
} }
// //
if (hasValidPresetValue()) { if (hasValidPresetValue()) {
console.log('[DeptSelect] has preset value, skip:', props.modelValue) console.log('[DeptSelect] has preset value, skip:', props.modelValue)
return return
} }
// ID // ID
const userStore = useUserStoreWithOut() const userStore = useUserStoreWithOut()
const user = userStore.getUser const user = userStore.getUser
const deptId = user?.deptId const deptId = user?.deptId
console.log('[DeptSelect] current user:', user, 'deptId:', deptId) console.log('[DeptSelect] current user:', user, 'deptId:', deptId)
// deptId 0 // deptId 0
if (!deptId || deptId === 0) { if (!deptId || deptId === 0) {
console.log('[DeptSelect] deptId is invalid, skip') console.log('[DeptSelect] deptId is invalid, skip')
return return
} }
// //
const defaultValue = props.multiple ? [deptId] : deptId const defaultValue = props.multiple ? [deptId] : deptId
console.log('[DeptSelect] setting default value:', defaultValue) console.log('[DeptSelect] setting default value:', defaultValue)

View File

@ -40,7 +40,14 @@ export const useFormCreateDesigner = async (designer: Ref) => {
designer.value?.removeMenuItem('fcEditor') designer.value?.removeMenuItem('fcEditor')
const iframeRule = useIframeRule() const iframeRule = useIframeRule()
const areaSelectRule = useAreaSelectRule() const areaSelectRule = useAreaSelectRule()
const components = [editorRule, uploadFileRule, uploadImgRule, uploadImgsRule, iframeRule, areaSelectRule] const components = [
editorRule,
uploadFileRule,
uploadImgRule,
uploadImgsRule,
iframeRule,
areaSelectRule
]
components.forEach((component) => { components.forEach((component) => {
// 插入组件规则 // 插入组件规则
designer.value?.addComponent(component) designer.value?.addComponent(component)

View File

@ -19,7 +19,7 @@ export const localeProps = (t, prefix, rules) => {
/** /**
* field, title * field, title
* *
* @param rule https://www.form-create.com/v3/guide/rule * @param rule https://www.form-create.com/v3/guide/rule
* @param fields * @param fields
* @param parentTitle * @param parentTitle

View File

@ -125,7 +125,12 @@ watch(
<template> <template>
<div class="selector"> <div class="selector">
<ElInput v-model="inputValue" @click="visible = !visible" :clearable="props.clearable" @clear="clearIcon"> <ElInput
v-model="inputValue"
@click="visible = !visible"
:clearable="props.clearable"
@clear="clearIcon"
>
<template #append> <template #append>
<ElPopover <ElPopover
:popper-options="{ :popper-options="{

View File

@ -5,43 +5,43 @@ export interface JsonEditorProps {
* JSON * JSON
*/ */
modelValue: any modelValue: any
/** /**
* *
* @default 'tree' * @default 'tree'
*/ */
mode?: JSONEditorMode mode?: JSONEditorMode
/** /**
* *
* @default '400px' * @default '400px'
*/ */
height?: string height?: string
/** /**
* *
* @default false * @default false
*/ */
showModeSelection?: boolean showModeSelection?: boolean
/** /**
* *
* @default false * @default false
*/ */
showNavigationBar?: boolean showNavigationBar?: boolean
/** /**
* *
* @default true * @default true
*/ */
showStatusBar?: boolean showStatusBar?: boolean
/** /**
* *
* @default true * @default true
*/ */
showMainMenuBar?: boolean showMainMenuBar?: boolean
/** /**
* JSONEditor * JSONEditor
* @see https://github.com/josdejong/jsoneditor/blob/develop/docs/api.md * @see https://github.com/josdejong/jsoneditor/blob/develop/docs/api.md
@ -57,12 +57,12 @@ export interface JsonEditorEmits {
* *
*/ */
(e: 'update:modelValue', value: any): void (e: 'update:modelValue', value: any): void
/** /**
* *
*/ */
(e: 'change', value: any): void (e: 'change', value: any): void
/** /**
* *
*/ */
@ -77,4 +77,4 @@ export interface JsonEditorExpose {
* JSONEditor * JSONEditor
*/ */
getEditor: () => any getEditor: () => any
} }

View File

@ -50,13 +50,13 @@ const props = defineProps({
modelFormId: { modelFormId: {
type: Number, type: Number,
required: false, required: false,
default: undefined, default: undefined
}, },
// //
modelFormType: { modelFormType: {
type: Number, type: Number,
required: false, required: false,
default: BpmModelFormType.NORMAL, default: BpmModelFormType.NORMAL
}, },
// //
startUserIds: { startUserIds: {
@ -73,30 +73,30 @@ 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(props.modelFormType); const formType = ref(props.modelFormType)
// modelFormType // modelFormType
watch( watch(
() => props.modelFormType, () => props.modelFormType,
(newVal) => { (newVal) => {
formType.value = newVal; formType.value = newVal
}, }
); )
// modelFormId // modelFormId
watch( watch(
() => props.modelFormId, () => props.modelFormId,
async (newVal) => { async (newVal) => {
if (newVal) { if (newVal) {
const form = await getForm(newVal); const form = await getForm(newVal)
formFields.value = form?.fields; formFields.value = form?.fields
} else { } else {
// modelFormId // modelFormId
formFields.value = []; formFields.value = []
} }
}, },
{ immediate: true }, { immediate: true }
); )
const roleOptions = ref<RoleApi.RoleVO[]>([]) // const roleOptions = ref<RoleApi.RoleVO[]>([]) //
const postOptions = ref<PostApi.PostVO[]>([]) // const postOptions = ref<PostApi.PostVO[]>([]) //
@ -118,7 +118,6 @@ 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)

View File

@ -2,4 +2,4 @@ import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
import SimpleProcessViewer from './SimpleProcessViewer.vue' import SimpleProcessViewer from './SimpleProcessViewer.vue'
import '../theme/simple-process-designer.scss' import '../theme/simple-process-designer.scss'
export { SimpleProcessDesigner, SimpleProcessViewer} export { SimpleProcessDesigner, SimpleProcessViewer }

View File

@ -295,10 +295,20 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY"> <el-form-item
v-if="
configForm.multiInstanceSourceType ===
ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY
"
>
<el-input-number v-model="configForm.multiInstanceSource" :min="1" /> <el-input-number v-model="configForm.multiInstanceSource" :min="1" />
</el-form-item> </el-form-item>
<el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM"> <el-form-item
v-if="
configForm.multiInstanceSourceType ===
ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM
"
>
<el-select class="w-200px!" v-model="configForm.multiInstanceSource"> <el-select class="w-200px!" v-model="configForm.multiInstanceSource">
<el-option <el-option
v-for="(field, fIdx) in digitalFormFieldOptions" v-for="(field, fIdx) in digitalFormFieldOptions"
@ -308,7 +318,12 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="configForm.multiInstanceSourceType === ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM"> <el-form-item
v-if="
configForm.multiInstanceSourceType ===
ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM
"
>
<el-select class="w-200px!" v-model="configForm.multiInstanceSource"> <el-select class="w-200px!" v-model="configForm.multiInstanceSource">
<el-option <el-option
v-for="(field, fIdx) in multiFormFieldOptions" v-for="(field, fIdx) in multiFormFieldOptions"
@ -519,7 +534,9 @@ const showChildProcessNodeConfig = (node: SimpleFlowNode) => {
configForm.value.outVariables = node.childProcessSetting.outVariables configForm.value.outVariables = node.childProcessSetting.outVariables
// 6. // 6.
configForm.value.startUserType = node.childProcessSetting.startUserSetting.type configForm.value.startUserType = node.childProcessSetting.startUserSetting.type
configForm.value.startUserEmptyType = node.childProcessSetting.startUserSetting.emptyType ?? ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER configForm.value.startUserEmptyType =
node.childProcessSetting.startUserSetting.emptyType ??
ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER
configForm.value.startUserFormField = node.childProcessSetting.startUserSetting.formField ?? '' configForm.value.startUserFormField = node.childProcessSetting.startUserSetting.formField ?? ''
// 7. // 7.
configForm.value.timeoutEnable = node.childProcessSetting.timeoutSetting.enable ?? false configForm.value.timeoutEnable = node.childProcessSetting.timeoutSetting.enable ?? false

View File

@ -1,7 +1,10 @@
import { TimeUnitType, ApproveType, APPROVE_TYPE } from './consts' import { TimeUnitType, ApproveType, APPROVE_TYPE } from './consts'
// 获取条件节点默认的名称 // 获取条件节点默认的名称
export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => { export const getDefaultConditionNodeName = (
index: number,
defaultFlow: boolean | undefined
): string => {
if (defaultFlow) { if (defaultFlow) {
return '其它情况' return '其它情况'
} }
@ -9,7 +12,10 @@ export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean
} }
// 获取包容分支条件节点默认的名称 // 获取包容分支条件节点默认的名称
export const getDefaultInclusiveConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => { export const getDefaultInclusiveConditionNodeName = (
index: number,
defaultFlow: boolean | undefined
): string => {
if (defaultFlow) { if (defaultFlow) {
return '其它情况' return '其它情况'
} }

View File

@ -174,7 +174,7 @@
} }
.router { .router {
color: #ca3a31 color: #ca3a31;
} }
.transactor { .transactor {
@ -303,7 +303,7 @@
} }
&.router-node { &.router-node {
color: #ca3a31 color: #ca3a31;
} }
&.transactor-task { &.transactor-task {
@ -762,14 +762,15 @@
// iconfont // iconfont
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4495938 */ font-family: 'iconfont'; /* Project id 4495938 */
src: url('iconfont.woff2?t=1737639517142') format('woff2'), src:
url('iconfont.woff?t=1737639517142') format('woff'), url('iconfont.woff2?t=1737639517142') format('woff2'),
url('iconfont.ttf?t=1737639517142') format('truetype'); url('iconfont.woff?t=1737639517142') format('woff'),
url('iconfont.ttf?t=1737639517142') format('truetype');
} }
.iconfont { .iconfont {
font-family: "iconfont" !important; font-family: 'iconfont' !important;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -777,49 +778,49 @@
} }
.icon-trigger:before { .icon-trigger:before {
content: "\e6d3"; content: '\e6d3';
} }
.icon-router:before { .icon-router:before {
content: "\e6b2"; content: '\e6b2';
} }
.icon-delay:before { .icon-delay:before {
content: "\e600"; content: '\e600';
} }
.icon-start-user:before { .icon-start-user:before {
content: "\e679"; content: '\e679';
} }
.icon-inclusive:before { .icon-inclusive:before {
content: "\e602"; content: '\e602';
} }
.icon-copy:before { .icon-copy:before {
content: "\e7eb"; content: '\e7eb';
} }
.icon-transactor:before { .icon-transactor:before {
content: "\e61c"; content: '\e61c';
} }
.icon-exclusive:before { .icon-exclusive:before {
content: "\e717"; content: '\e717';
} }
.icon-approve:before { .icon-approve:before {
content: "\e715"; content: '\e715';
} }
.icon-parallel:before { .icon-parallel:before {
content: "\e688"; content: '\e688';
} }
.icon-async-child-process:before { .icon-async-child-process:before {
content: "\e6f2"; content: '\e6f2';
} }
.icon-child-process:before { .icon-child-process:before {
content: "\e6c1"; content: '\e6c1';
} }

View File

@ -36,7 +36,7 @@
* Verify 验证码组件 * Verify 验证码组件
* @description 分发验证码使用 * @description 分发验证码使用
* */ * */
import {VerifyPictureWord, VerifyPoints, VerifySlide} from './Verify' import { VerifyPictureWord, VerifyPoints, VerifySlide } from './Verify'
import { computed, ref, toRefs, watchEffect } from 'vue' import { computed, ref, toRefs, watchEffect } from 'vue'
export default { export default {
@ -443,4 +443,4 @@ export default {
content: ' '; content: ' ';
inset: 0; inset: 0;
} }
</style> </style>

View File

@ -2,4 +2,4 @@ import VerifySlide from './VerifySlide.vue'
import VerifyPoints from './VerifyPoints.vue' import VerifyPoints from './VerifyPoints.vue'
import VerifyPictureWord from './VerifyPictureWord.vue' import VerifyPictureWord from './VerifyPictureWord.vue'
export { VerifySlide, VerifyPoints, VerifyPictureWord } export { VerifySlide, VerifyPoints, VerifyPictureWord }

View File

@ -1254,11 +1254,11 @@
"allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"] "allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
}, },
"properties": [ "properties": [
{ {
"name": "value", "name": "value",
"type": "Integer", "type": "Integer",
"isBody": true "isBody": true
} }
] ]
}, },
{ {

View File

@ -23,20 +23,20 @@
export default function customTranslate(translations) { export default function customTranslate(translations) {
return function (template, replacements) { return function (template, replacements) {
replacements = replacements || {}; replacements = replacements || {}
// 将模板和翻译字典的键统一转换为小写进行匹配 // 将模板和翻译字典的键统一转换为小写进行匹配
const lowerTemplate = template.toLowerCase(); const lowerTemplate = template.toLowerCase()
const translation = Object.keys(translations).find(key => key.toLowerCase() === lowerTemplate); const translation = Object.keys(translations).find((key) => key.toLowerCase() === lowerTemplate)
// 如果找到匹配的翻译,使用翻译后的模板 // 如果找到匹配的翻译,使用翻译后的模板
if (translation) { if (translation) {
template = translations[translation]; template = translations[translation]
} }
// 替换模板中的占位符 // 替换模板中的占位符
return template.replace(/{([^}]+)}/g, function (_, key) { return template.replace(/{([^}]+)}/g, function (_, key) {
// 如果替换值存在,返回替换值;否则返回原始占位符 // 如果替换值存在,返回替换值;否则返回原始占位符
return replacements[key] !== undefined ? replacements[key] : `{${key}}`; return replacements[key] !== undefined ? replacements[key] : `{${key}}`
}); })
}; }
} }

View File

@ -2,14 +2,15 @@
@use './process-panel.scss'; @use './process-panel.scss';
$success-color: #4eb819; $success-color: #4eb819;
$primary-color: #409EFF; $primary-color: #409eff;
$danger-color: #F56C6C; $danger-color: #f56c6c;
$cancel-color: #909399; $cancel-color: #909399;
.process-viewer { .process-viewer {
position: relative; position: relative;
border: 1px solid #EFEFEF; border: 1px solid #efefef;
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important; background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+')
repeat !important;
.success-arrow { .success-arrow {
fill: $success-color; fill: $success-color;
@ -23,7 +24,7 @@ $cancel-color: #909399;
.success.djs-connection { .success.djs-connection {
.djs-visual path { .djs-visual path {
stroke: $success-color!important; stroke: $success-color !important;
//marker-end: url(#sequenceflow-end-white-success)!important; //marker-end: url(#sequenceflow-end-white-success)!important;
} }
} }
@ -36,82 +37,84 @@ $cancel-color: #909399;
.success.djs-shape { .success.djs-shape {
.djs-visual rect { .djs-visual rect {
stroke: $success-color!important; stroke: $success-color !important;
fill: $success-color!important; fill: $success-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
.djs-visual polygon { .djs-visual polygon {
stroke: $success-color!important; stroke: $success-color !important;
} }
.djs-visual path:nth-child(2) { .djs-visual path:nth-child(2) {
stroke: $success-color!important; stroke: $success-color !important;
fill: $success-color!important; fill: $success-color !important;
} }
.djs-visual circle { .djs-visual circle {
stroke: $success-color!important; stroke: $success-color !important;
fill: $success-color!important; fill: $success-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
} }
.primary.djs-shape { .primary.djs-shape {
.djs-visual rect { .djs-visual rect {
stroke: $primary-color!important; stroke: $primary-color !important;
fill: $primary-color!important; fill: $primary-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
.djs-visual polygon { .djs-visual polygon {
stroke: $primary-color!important; stroke: $primary-color !important;
} }
.djs-visual circle { .djs-visual circle {
stroke: $primary-color!important; stroke: $primary-color !important;
fill: $primary-color!important; fill: $primary-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
} }
.danger.djs-shape { .danger.djs-shape {
.djs-visual rect { .djs-visual rect {
stroke: $danger-color!important; stroke: $danger-color !important;
fill: $danger-color!important; fill: $danger-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
.djs-visual polygon { .djs-visual polygon {
stroke: $danger-color!important; stroke: $danger-color !important;
} }
.djs-visual circle { .djs-visual circle {
stroke: $danger-color!important; stroke: $danger-color !important;
fill: $danger-color!important; fill: $danger-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
} }
.cancel.djs-shape { .cancel.djs-shape {
.djs-visual rect { .djs-visual rect {
stroke: $cancel-color!important; stroke: $cancel-color !important;
fill: $cancel-color!important; fill: $cancel-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
.djs-visual polygon { .djs-visual polygon {
stroke: $cancel-color!important; stroke: $cancel-color !important;
} }
.djs-visual circle { .djs-visual circle {
stroke: $cancel-color!important; stroke: $cancel-color !important;
fill: $cancel-color!important; fill: $cancel-color !important;
fill-opacity: 0.15!important; fill-opacity: 0.15 !important;
} }
} }
} }
.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette { .process-viewer .djs-tooltip-container,
.process-viewer .djs-overlay-container,
.process-viewer .djs-palette {
display: none; display: none;
} }

View File

@ -167,8 +167,8 @@ service.interceptors.response.use(
cb() cb()
}) })
requestList = [] requestList = []
if ((config!.headers || {}).isEncrypt){ if ((config!.headers || {}).isEncrypt) {
(config!.headers || {}).isEncrypted = true ;(config!.headers || {}).isEncrypted = true
} }
return service(config) return service(config)
} catch (e) { } catch (e) {

View File

@ -8,7 +8,7 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
const id = domSymbol.toString() const id = domSymbol.toString()
const appStore = useAppStore() const appStore = useAppStore()
let watermarkStr = '' let watermarkStr = ''
const clear = () => { const clear = () => {
const domId = document.getElementById(id) const domId = document.getElementById(id)
if (domId) { if (domId) {

View File

@ -27,7 +27,10 @@ onMounted(() => {
watch( watch(
() => collapse.value, () => collapse.value,
(collapse: boolean) => { (collapse: boolean) => {
if (getLayoutRenderMode(unref(layout)) === 'topLeft' || getLayoutRenderMode(unref(layout)) === 'cutMenu') { if (
getLayoutRenderMode(unref(layout)) === 'topLeft' ||
getLayoutRenderMode(unref(layout)) === 'cutMenu'
) {
show.value = true show.value = true
return return
} }

View File

@ -8,11 +8,7 @@ import { hasOneShowingChild } from './helper'
import { isUrl } from '@/utils/is' import { isUrl } from '@/utils/is'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { createRouteLocation, resolveDynamicPath } from '@/utils/routeParams' import { createRouteLocation, resolveDynamicPath } from '@/utils/routeParams'
import { import { isHeaderNavLayout, isHorizontalMenuLayout, isTwoColumnLayout } from '@/utils/layout'
isHeaderNavLayout,
isHorizontalMenuLayout,
isTwoColumnLayout
} from '@/utils/layout'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { pathResolve } from '@/utils/routerHelper' import { pathResolve } from '@/utils/routerHelper'
import { import {
@ -69,8 +65,9 @@ export default defineComponent({
const menuRef = ref<InstanceType<typeof ElMenu>>() const menuRef = ref<InstanceType<typeof ElMenu>>()
const menuMode = computed((): 'vertical' | 'horizontal' => const menuMode = computed(
props.mode || (isHorizontalMenuLayout(unref(layout)) ? 'horizontal' : 'vertical') (): 'vertical' | 'horizontal' =>
props.mode || (isHorizontalMenuLayout(unref(layout)) ? 'horizontal' : 'vertical')
) )
const collapse = computed(() => appStore.getCollapse) const collapse = computed(() => appStore.getCollapse)
@ -102,7 +99,8 @@ export default defineComponent({
const routers = computed(() => { const routers = computed(() => {
const sourceRouters = const sourceRouters =
props.menus || (props.split ? permissionStore.getMenuTabRouters : permissionStore.getRouters) props.menus ||
(props.split ? permissionStore.getMenuTabRouters : permissionStore.getRouters)
if (!props.rootOnly) { if (!props.rootOnly) {
return sourceRouters return sourceRouters
} }
@ -113,7 +111,10 @@ export default defineComponent({
const { meta, path } = unref(currentRoute) const { meta, path } = unref(currentRoute)
const currentPath = (meta.activeMenu as string) || path const currentPath = (meta.activeMenu as string) || path
if (props.rootOnly) { if (props.rootOnly) {
return permissionStore.getMenuRootPath || getRootMenuActivePath(permissionStore.getRouters, currentPath) return (
permissionStore.getMenuRootPath ||
getRootMenuActivePath(permissionStore.getRouters, currentPath)
)
} }
// if set path, the sidebar will highlight the path you set // if set path, the sidebar will highlight the path you set
if (meta.activeMenu) { if (meta.activeMenu) {
@ -134,7 +135,10 @@ export default defineComponent({
props.theme === 'header' ? 'var(--el-color-primary)' : 'var(--left-menu-text-active-color)' props.theme === 'header' ? 'var(--el-color-primary)' : 'var(--left-menu-text-active-color)'
) )
const getFirstChildPath = (route: AppRouteRecordRaw, parentPath: string): string | undefined => { const getFirstChildPath = (
route: AppRouteRecordRaw,
parentPath: string
): string | undefined => {
const firstChild = route.children?.find((child) => !child.meta?.hidden) const firstChild = route.children?.find((child) => !child.meta?.hidden)
if (!firstChild) { if (!firstChild) {
return undefined return undefined
@ -218,7 +222,8 @@ export default defineComponent({
const setSplitMenus = (targetPath: string) => { const setSplitMenus = (targetPath: string) => {
const rootInfo = getRootMenuRoute(permissionStore.getRouters, targetPath) const rootInfo = getRootMenuRoute(permissionStore.getRouters, targetPath)
const rootPath = rootInfo?.fullPath || getRootMenuActivePath(permissionStore.getRouters, targetPath) const rootPath =
rootInfo?.fullPath || getRootMenuActivePath(permissionStore.getRouters, targetPath)
const rootRoute = rootInfo?.route const rootRoute = rootInfo?.route
const children = rootRoute?.children?.length const children = rootRoute?.children?.length
? cloneDeep(rootRoute.children).map((child) => { ? cloneDeep(rootRoute.children).map((child) => {
@ -386,7 +391,8 @@ export default defineComponent({
} }
const targetPath = const targetPath =
props.rootOnly && routeInfo?.route.children?.length props.rootOnly && routeInfo?.route.children?.length
? ((routeInfo.route.redirect as string) || getFirstChildPath(routeInfo.route, routeInfo.fullPath)) ? (routeInfo.route.redirect as string) ||
getFirstChildPath(routeInfo.route, routeInfo.fullPath)
: index : index
if (targetPath) { if (targetPath) {
@ -541,9 +547,13 @@ export default defineComponent({
'h-[100%] overflow-hidden flex-col bg-[var(--left-menu-bg-color)]', 'h-[100%] overflow-hidden flex-col bg-[var(--left-menu-bg-color)]',
{ {
'w-[var(--left-menu-min-width)]': 'w-[var(--left-menu-min-width)]':
unref(collapse) && !isTwoColumnLayout(unref(layout)) && unref(menuMode) !== 'horizontal', unref(collapse) &&
!isTwoColumnLayout(unref(layout)) &&
unref(menuMode) !== 'horizontal',
'w-[var(--left-menu-max-width)]': 'w-[var(--left-menu-max-width)]':
!unref(collapse) && !isTwoColumnLayout(unref(layout)) && unref(menuMode) !== 'horizontal' !unref(collapse) &&
!isTwoColumnLayout(unref(layout)) &&
unref(menuMode) !== 'horizontal'
} }
]} ]}
style={{ style={{
@ -642,13 +652,12 @@ $prefix-cls: #{$namespace}-menu;
height: calc(var(--top-tool-height)) !important; height: calc(var(--top-tool-height)) !important;
min-width: 0; min-width: 0;
flex-shrink: 1; flex-shrink: 1;
overflow-x: hidden; overflow: hidden;
overflow-y: hidden;
:deep(.#{$elNamespace}-menu--horizontal) { :deep(.#{$elNamespace}-menu--horizontal) {
height: calc(var(--top-tool-height)); height: calc(var(--top-tool-height));
border-bottom: none;
min-width: 100%; min-width: 100%;
border-bottom: none;
// //
& > .#{$elNamespace}-sub-menu.is-active { & > .#{$elNamespace}-sub-menu.is-active {
.#{$elNamespace}-sub-menu__title { .#{$elNamespace}-sub-menu__title {

View File

@ -33,14 +33,12 @@ export const useRenderMenuItem = () =>
if ( if (
!children.length || !children.length ||
oneShowingChild && (oneShowingChild &&
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) && (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
!meta?.alwaysShow !meta?.alwaysShow)
) { ) {
return ( return (
<ElMenuItem <ElMenuItem index={resolvedOnlyOneChildPath}>
index={resolvedOnlyOneChildPath}
>
{{ {{
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta) default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
}} }}

View File

@ -58,9 +58,9 @@ export const getRootMenuRoute = (
} }
} }
export const getRootMenuActivePath = ( export const getRootMenuActivePath = (routes: AppRouteRecordRaw[], targetPath: string): string => {
routes: AppRouteRecordRaw[], return (
targetPath: string getRootMenuRoute(routes, targetPath)?.fullPath ||
): string => { getRootPath(normalizeMenuTargetPath(targetPath))
return getRootMenuRoute(routes, targetPath)?.fullPath || getRootPath(normalizeMenuTargetPath(targetPath)) )
} }

View File

@ -12,10 +12,7 @@ import { useDesign } from '@/hooks/web/useDesign'
import { isUrl } from '@/utils/is' import { isUrl } from '@/utils/is'
import { createRouteLocation } from '@/utils/routeParams' import { createRouteLocation } from '@/utils/routeParams'
import { getRootPath, isHeaderMixedNavLayout, isTwoColumnLayout } from '@/utils/layout' import { getRootPath, isHeaderMixedNavLayout, isTwoColumnLayout } from '@/utils/layout'
import { import { getRootMenuRoute, normalizeMenuTargetPath } from '@/layout/components/Menu/src/menuRoute'
getRootMenuRoute,
normalizeMenuTargetPath
} from '@/layout/components/Menu/src/menuRoute'
const { getPrefixCls, variables } = useDesign() const { getPrefixCls, variables } = useDesign()
@ -58,7 +55,9 @@ export default defineComponent({
return getRootMenuRoute(unref(routers), targetPath) return getRootMenuRoute(unref(routers), targetPath)
}) })
const rootPath = computed(() => unref(activeRootInfo)?.fullPath || getRootPath(unref(currentMenuPath))) const rootPath = computed(
() => unref(activeRootInfo)?.fullPath || getRootPath(unref(currentMenuPath))
)
const activeRootRoute = computed(() => unref(activeRootInfo)?.route) const activeRootRoute = computed(() => unref(activeRootInfo)?.route)
@ -125,7 +124,9 @@ export default defineComponent({
const isSameMenuRouters = (left: AppRouteRecordRaw[], right: AppRouteRecordRaw[]): boolean => { const isSameMenuRouters = (left: AppRouteRecordRaw[], right: AppRouteRecordRaw[]): boolean => {
return ( return (
left.length === right.length && left.length === right.length &&
left.every((route, index) => route.path === right[index]?.path && route.name === right[index]?.name) left.every(
(route, index) => route.path === right[index]?.path && route.name === right[index]?.name
)
) )
} }
@ -219,7 +220,8 @@ export default defineComponent({
const children = getVisibleChildren(item) const children = getVisibleChildren(item)
if (children.length) { if (children.length) {
if (newPath === oldPath || !unref(showMenu)) { if (newPath === oldPath || !unref(showMenu)) {
showMenu.value = unref(fixedMenu) || unref(headerMixed) || unref(twoColumn) ? true : !unref(showMenu) showMenu.value =
unref(fixedMenu) || unref(headerMixed) || unref(twoColumn) ? true : !unref(showMenu)
} }
if (unref(showMenu)) { if (unref(showMenu)) {
setExtraMenuRouters(buildExtraMenuRouters(children, unref(tabActive))) setExtraMenuRouters(buildExtraMenuRouters(children, unref(tabActive)))

View File

@ -171,7 +171,8 @@ const moveToTarget = (currentTag: RouteLocationNormalizedLoaded) => {
} else { } else {
// find preTag and nextTag // find preTag and nextTag
const currentIndex: number = tagList.findIndex( const currentIndex: number = tagList.findIndex(
(item) => (item?.to as RouteLocationNormalizedLoaded | undefined)?.fullPath === currentTag.fullPath (item) =>
(item?.to as RouteLocationNormalizedLoaded | undefined)?.fullPath === currentTag.fullPath
) )
if (currentIndex < 0) return if (currentIndex < 0) return
const tgsRefs = document.getElementsByClassName(`${prefixCls}__item`) const tgsRefs = document.getElementsByClassName(`${prefixCls}__item`)
@ -376,10 +377,10 @@ watch(
<Icon <Icon
v-if=" v-if="
tagsViewIcon && tagsViewIcon &&
(item?.meta?.icon || (item?.meta?.icon ||
(item?.matched && (item?.matched &&
item.matched[0] && item.matched[0] &&
item.matched[item.matched.length - 1].meta?.icon)) item.matched[item.matched.length - 1].meta?.icon))
" "
:icon="item?.meta?.icon || item.matched[item.matched.length - 1].meta.icon" :icon="item?.meta?.icon || item.matched[item.matched.length - 1].meta.icon"
:size="12" :size="12"
@ -387,7 +388,7 @@ watch(
/> />
{{ {{
t(item?.meta?.title as string) + t(item?.meta?.title as string) +
(item?.meta?.titleSuffix ? ` (${item?.meta?.titleSuffix})` : '') (item?.meta?.titleSuffix ? ` (${item?.meta?.titleSuffix})` : '')
}} }}
<Icon <Icon
:class="`${prefixCls}__item--close`" :class="`${prefixCls}__item--close`"

View File

@ -94,7 +94,11 @@ export default defineComponent({
) : undefined} ) : undefined}
<div class="h-full flex items-center"> <div class="h-full flex items-center">
{hasTenantVisitPermission.value ? <TenantVisit /> : undefined} {hasTenantVisitPermission.value ? <TenantVisit /> : undefined}
<div class="v-setting custom-hover" title={t('setting.projectSetting')} onClick={openSetting}> <div
class="v-setting custom-hover"
title={t('setting.projectSetting')}
onClick={openSetting}
>
<Icon color="var(--top-header-text-color)" size={18} icon="ep:setting" /> <Icon color="var(--top-header-text-color)" size={18} icon="ep:setting" />
</div> </div>
{screenfull.value ? ( {screenfull.value ? (

View File

@ -91,7 +91,7 @@ const handleLock = async () => {
<style lang="scss" scoped> <style lang="scss" scoped>
:global(.v-lock-dialog) { :global(.v-lock-dialog) {
@media (max-width: 767px) { @media (width <= 767px) {
max-width: calc(100vw - 16px); max-width: calc(100vw - 16px);
} }
} }

View File

@ -205,6 +205,7 @@ $error-color: #ed6f6f;
font-size: 90px; font-size: 90px;
} }
} }
@media screen and (min-width: $screen-lg) { @media screen and (min-width: $screen-lg) {
span:not(.meridiem) { span:not(.meridiem) {
font-size: 220px; font-size: 220px;
@ -216,6 +217,7 @@ $error-color: #ed6f6f;
font-size: 260px; font-size: 260px;
} }
} }
@media screen and (min-width: $screen-2xl) { @media screen and (min-width: $screen-2xl) {
span:not(.meridiem) { span:not(.meridiem) {
font-size: 320px; font-size: 320px;
@ -230,7 +232,7 @@ $error-color: #ed6f6f;
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.5); background-color: rgb(0 0 0 / 50%);
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@ -9,11 +9,7 @@ import AppView from './AppView.vue'
import ToolHeader from './ToolHeader.vue' import ToolHeader from './ToolHeader.vue'
import { ElScrollbar } from 'element-plus' import { ElScrollbar } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { import { isHeaderMixedNavLayout, isMixedNavLayout, isTwoColumnLayout } from '@/utils/layout'
isHeaderMixedNavLayout,
isMixedNavLayout,
isTwoColumnLayout
} from '@/utils/layout'
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -251,7 +247,8 @@ export const useRenderLayout = () => {
const renderCutMenu = () => { const renderCutMenu = () => {
const showHeaderMenu = isHeaderMixedNavLayout(layout.value) const showHeaderMenu = isHeaderMixedNavLayout(layout.value)
const fixedTwoColumnMenu = fixedMenu.value || isTwoColumnLayout(layout.value) const fixedTwoColumnMenu = fixedMenu.value || isTwoColumnLayout(layout.value)
const showTwoColumnExtraMenu = fixedTwoColumnMenu && permissionStore.getMenuTabRouters.length > 0 const showTwoColumnExtraMenu =
fixedTwoColumnMenu && permissionStore.getMenuTabRouters.length > 0
return ( return (
<> <>
<div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom"> <div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom">

View File

@ -173,7 +173,6 @@ export const PREDEFINE_COLORS = [
'#711f57' '#711f57'
] ]
/** /**
* Mixes two colors. * Mixes two colors.
* *

View File

@ -172,43 +172,43 @@ export const once = function (el: HTMLElement, event: string, fn: EventListener)
export const getStyle = export const getStyle =
ieVersion < 9 ieVersion < 9
? function (element: Element | any, styleName: string) { ? function (element: Element | any, styleName: string) {
if (isServer) return if (isServer) return
if (!element || !styleName) return null if (!element || !styleName) return null
styleName = camelCase(styleName) styleName = camelCase(styleName)
if (styleName === 'float') { if (styleName === 'float') {
styleName = 'styleFloat' styleName = 'styleFloat'
} }
try { try {
switch (styleName) { switch (styleName) {
case 'opacity': case 'opacity':
try { try {
return element.filters.item('alpha').opacity / 100 return element.filters.item('alpha').opacity / 100
} catch (e) { } catch (e) {
return 1.0 return 1.0
} }
default: default:
return element.style[styleName] || element.currentStyle return element.style[styleName] || element.currentStyle
? element.currentStyle[styleName] ? element.currentStyle[styleName]
: null : null
}
} catch (e) {
return element.style[styleName]
} }
} catch (e) {
return element.style[styleName]
} }
}
: function (element: Element | any, styleName: string) { : function (element: Element | any, styleName: string) {
if (isServer) return if (isServer) return
if (!element || !styleName) return null if (!element || !styleName) return null
styleName = camelCase(styleName) styleName = camelCase(styleName)
if (styleName === 'float') { if (styleName === 'float') {
styleName = 'cssFloat' styleName = 'cssFloat'
}
try {
const computed = (document as any).defaultView.getComputedStyle(element, '')
return element.style[styleName] || computed ? computed[styleName] : null
} catch (e) {
return element.style[styleName]
}
} }
try {
const computed = (document as any).defaultView.getComputedStyle(element, '')
return element.style[styleName] || computed ? computed[styleName] : null
} catch (e) {
return element.style[styleName]
}
}
/* istanbul ignore next */ /* istanbul ignore next */
export function setStyle(element: Element | any, styleName: any, value: any) { export function setStyle(element: Element | any, styleName: any, value: any) {
@ -287,4 +287,3 @@ export const isInContainer = (el: Element, container: any) => {
elRect.left < containerRect.right elRect.left < containerRect.right
) )
} }

View File

@ -216,7 +216,9 @@ export function formatPast2(ms: number): string {
*/ */
export function formatSeconds(seconds: number): string { export function formatSeconds(seconds: number): string {
const s = Math.max(0, Math.floor(seconds || 0)) const s = Math.max(0, Math.floor(seconds || 0))
const mm = Math.floor(s / 60).toString().padStart(2, '0') const mm = Math.floor(s / 60)
.toString()
.padStart(2, '0')
const ss = (s % 60).toString().padStart(2, '0') const ss = (s % 60).toString().padStart(2, '0')
return `${mm}:${ss}` return `${mm}:${ss}`
} }

View File

@ -215,10 +215,10 @@ export const generateUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let random = Math.random() * 16 let random = Math.random() * 16
if (timestamp > 0) { if (timestamp > 0) {
random = (timestamp + random) % 16 | 0 random = ((timestamp + random) % 16) | 0
timestamp = Math.floor(timestamp / 16) timestamp = Math.floor(timestamp / 16)
} else { } else {
random = (performanceNow + random) % 16 | 0 random = ((performanceNow + random) % 16) | 0
performanceNow = Math.floor(performanceNow / 16) performanceNow = Math.floor(performanceNow / 16)
} }
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16) return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)

View File

@ -100,7 +100,7 @@ export const isClient = !isServer
export const isUrl = (path: string): boolean => { export const isUrl = (path: string): boolean => {
// fix:修复hash路由无法跳转的问题 // fix:修复hash路由无法跳转的问题
const reg = const reg =
/(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%#\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/ /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%#\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
return reg.test(path) return reg.test(path)
} }

View File

@ -40,7 +40,9 @@ export const isHeaderNavLayout = (layout?: LayoutType | string | null): boolean
export const isHorizontalMenuLayout = (layout?: LayoutType | string | null): boolean => { export const isHorizontalMenuLayout = (layout?: LayoutType | string | null): boolean => {
const normalized = normalizeLayout(layout) const normalized = normalizeLayout(layout)
return normalized === 'header-nav' || normalized === 'mixed-nav' || normalized === 'header-mixed-nav' return (
normalized === 'header-nav' || normalized === 'mixed-nav' || normalized === 'header-mixed-nav'
)
} }
export const isMixedNavLayout = (layout?: LayoutType | string | null): boolean => { export const isMixedNavLayout = (layout?: LayoutType | string | null): boolean => {

View File

@ -1,6 +1,5 @@
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import {hasPermission} from "@/directives/permission/hasPermi"; import { hasPermission } from '@/directives/permission/hasPermi'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化

View File

@ -23,9 +23,7 @@ export const parseQueryString = (queryString = ''): Record<string, any> => {
return qs.parse(queryString.replace(/^\?/, '')) as Record<string, any> return qs.parse(queryString.replace(/^\?/, '')) as Record<string, any>
} }
export const splitRoutePath = ( export const splitRoutePath = (rawPath: string | null | undefined): ParsedRouteLocation => {
rawPath: string | null | undefined
): ParsedRouteLocation => {
if (!rawPath) { if (!rawPath) {
return { path: '' } return { path: '' }
} }
@ -113,7 +111,8 @@ export const splitDynamicRouteParams = (
} }
} }
const isRepeatableParam = (matched: string): boolean => matched.endsWith('*') || matched.endsWith('+') const isRepeatableParam = (matched: string): boolean =>
matched.endsWith('*') || matched.endsWith('+')
const encodeRouteParam = (matched: string, value: any): string => { const encodeRouteParam = (matched: string, value: any): string => {
if (Array.isArray(value)) { if (Array.isArray(value)) {

View File

@ -113,7 +113,7 @@ const iconHouse = useIcon({ icon: 'ep:house' })
const iconAvatar = useIcon({ icon: 'ep:avatar' }) const iconAvatar = useIcon({ icon: 'ep:avatar' })
const iconLock = useIcon({ icon: 'ep:lock' }) const iconLock = useIcon({ icon: 'ep:lock' })
const formLogin = ref() const formLogin = ref()
const {validForm} = useFormValid(formLogin) const { validForm } = useFormValid(formLogin)
const { handleBackLogin, getLoginState } = useLoginState() const { handleBackLogin, getLoginState } = useLoginState()
const { currentRoute, push } = useRouter() const { currentRoute, push } = useRouter()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()

View File

@ -6,4 +6,12 @@ import QrCodeForm from './QrCodeForm.vue'
import SSOLoginVue from './SSOLogin.vue' import SSOLoginVue from './SSOLogin.vue'
import ForgetPasswordForm from './ForgetPasswordForm.vue' import ForgetPasswordForm from './ForgetPasswordForm.vue'
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue, ForgetPasswordForm } export {
LoginForm,
MobileForm,
LoginFormTitle,
RegisterForm,
QrCodeForm,
SSOLoginVue,
ForgetPasswordForm
}

View File

@ -1,8 +1,5 @@
<template> <template>
<el-card <el-card body-class="" class="!w-80 !h-auto !rounded-10px !relative !flex !flex-col">
body-class=""
class="!w-80 !h-auto !rounded-10px !relative !flex !flex-col"
>
<div class="!flex !flex-row !justify-between"> <div class="!flex !flex-row !justify-between">
<div> <div>
<el-button type="primary" text bg v-if="detail?.status === AiImageStatusEnum.IN_PROGRESS"> <el-button type="primary" text bg v-if="detail?.status === AiImageStatusEnum.IN_PROGRESS">

View File

@ -37,7 +37,11 @@
</div> </div>
<el-space wrap class="mt-15px"> <el-space wrap class="mt-15px">
<div <div
:class="selectModel === model.key ? 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-#1293ff rounded-5px cursor-pointer' : 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-transparent cursor-pointer'" :class="
selectModel === model.key
? 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-#1293ff rounded-5px cursor-pointer'
: 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-transparent cursor-pointer'
"
v-for="model in Dall3Models" v-for="model in Dall3Models"
:key="model.key" :key="model.key"
> >
@ -52,7 +56,11 @@
</div> </div>
<el-space wrap class="mt-15px"> <el-space wrap class="mt-15px">
<div <div
:class="style === imageStyle.key ? 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-#1293ff rounded-5px cursor-pointer' : 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-transparent cursor-pointer'" :class="
style === imageStyle.key
? 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-#1293ff rounded-5px cursor-pointer'
: 'w-110px overflow-hidden flex flex-col items-center border-3 border-solid border-transparent cursor-pointer'
"
v-for="imageStyle in Dall3StyleList" v-for="imageStyle in Dall3StyleList"
:key="imageStyle.key" :key="imageStyle.key"
> >
@ -73,7 +81,11 @@
@click="handleSizeClick(imageSize)" @click="handleSizeClick(imageSize)"
> >
<div <div
:class="selectSize === imageSize.key ? 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-#1293ff' : 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-white'" :class="
selectSize === imageSize.key
? 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-#1293ff'
: 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-white'
"
> >
<div :style="imageSize.style"></div> <div :style="imageSize.style"></div>
</div> </div>
@ -229,4 +241,3 @@ const settingValues = async (detail: ImageVO) => {
/** 暴露组件方法 */ /** 暴露组件方法 */
defineExpose({ settingValues }) defineExpose({ settingValues })
</script> </script>

View File

@ -43,7 +43,11 @@
@click="handleSizeClick(imageSize)" @click="handleSizeClick(imageSize)"
> >
<div <div
:class="selectSize === imageSize.key ? 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-#1293ff' : 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-white'" :class="
selectSize === imageSize.key
? 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-#1293ff'
: 'flex flex-col items-center justify-center rounded-7px p-4px w-50px h-50px bg-white border-1 border-solid border-white'
"
> >
<div :style="imageSize.style"></div> <div :style="imageSize.style"></div>
</div> </div>
@ -57,7 +61,11 @@
</div> </div>
<el-space wrap class="mt-15px"> <el-space wrap class="mt-15px">
<div <div
:class="selectModel === model.key ? 'flex flex-col items-center w-150px overflow-hidden border-3 border-solid border-#1293ff rounded-5px cursor-pointer' : 'flex flex-col items-center w-150px overflow-hidden border-3 border-solid border-transparent cursor-pointer'" :class="
selectModel === model.key
? 'flex flex-col items-center w-150px overflow-hidden border-3 border-solid border-#1293ff rounded-5px cursor-pointer'
: 'flex flex-col items-center w-150px overflow-hidden border-3 border-solid border-transparent cursor-pointer'
"
v-for="model in MidjourneyModels" v-for="model in MidjourneyModels"
:key="model.key" :key="model.key"
> >
@ -71,12 +79,7 @@
<el-text tag="b">版本</el-text> <el-text tag="b">版本</el-text>
</div> </div>
<el-space wrap class="mt-20px w-full"> <el-space wrap class="mt-20px w-full">
<el-select <el-select v-model="selectVersion" class="!w-350px" clearable placeholder="请选择版本">
v-model="selectVersion"
class="!w-350px"
clearable
placeholder="请选择版本"
>
<el-option <el-option
v-for="item in versionList" v-for="item in versionList"
:key="item.value" :key="item.value"
@ -233,4 +236,3 @@ const settingValues = async (detail: ImageVO) => {
/** 暴露组件方法 */ /** 暴露组件方法 */
defineExpose({ settingValues }) defineExpose({ settingValues })
</script> </script>

View File

@ -254,4 +254,3 @@ const settingValues = async (detail: ImageVO) => {
/** 暴露组件方法 */ /** 暴露组件方法 */
defineExpose({ settingValues }) defineExpose({ settingValues })
</script> </script>

View File

@ -10,10 +10,19 @@
:suffix-icon="Search" :suffix-icon="Search"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-10px bg-white shadow-[0_0_10px_rgba(0,0,0,0.1)]"> <div
class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-10px bg-white shadow-[0_0_10px_rgba(0,0,0,0.1)]"
>
<!-- TODO @fan这个图片的风格要不和 ImageCard.vue 界面一致只有卡片没有操作因为看着更有相框的感觉~~~ --> <!-- TODO @fan这个图片的风格要不和 ImageCard.vue 界面一致只有卡片没有操作因为看着更有相框的感觉~~~ -->
<div v-for="item in list" :key="item.id" class="relative overflow-hidden bg-gray-100 cursor-pointer transition-transform duration-300 hover:scale-105"> <div
<img :src="item.picUrl" class="w-full h-auto block transition-transform duration-300 hover:scale-110" /> v-for="item in list"
:key="item.id"
class="relative overflow-hidden bg-gray-100 cursor-pointer transition-transform duration-300 hover:scale-105"
>
<img
:src="item.picUrl"
class="w-full h-auto block transition-transform duration-300 hover:scale-110"
/>
</div> </div>
</div> </div>
<!-- TODO @fan缺少翻页 --> <!-- TODO @fan缺少翻页 -->
@ -64,4 +73,3 @@ onMounted(async () => {
await getList() await getList()
}) })
</script> </script>

View File

@ -11,12 +11,20 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="modelData.formType === BpmModelFormType.NORMAL" label="流程表单" prop="formId"> <el-form-item
v-if="modelData.formType === BpmModelFormType.NORMAL"
label="流程表单"
prop="formId"
>
<el-select v-model="modelData.formId" clearable style="width: 100%"> <el-select v-model="modelData.formId" clearable style="width: 100%">
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" /> <el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单提交路由" prop="formCustomCreatePath"> <el-form-item
v-if="modelData.formType === BpmModelFormType.CUSTOM"
label="表单提交路由"
prop="formCustomCreatePath"
>
<el-input <el-input
v-model="modelData.formCustomCreatePath" v-model="modelData.formCustomCreatePath"
placeholder="请输入表单提交路由" placeholder="请输入表单提交路由"
@ -31,7 +39,11 @@
<Icon icon="ep:question" class="ml-5px" /> <Icon icon="ep:question" class="ml-5px" />
</el-tooltip> </el-tooltip>
</el-form-item> </el-form-item>
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单查看地址" prop="formCustomViewPath"> <el-form-item
v-if="modelData.formType === BpmModelFormType.CUSTOM"
label="表单查看地址"
prop="formCustomViewPath"
>
<el-input <el-input
v-model="modelData.formCustomViewPath" v-model="modelData.formCustomViewPath"
placeholder="请输入表单查看的组件地址" placeholder="请输入表单查看的组件地址"
@ -48,7 +60,11 @@
</el-form-item> </el-form-item>
<!-- 表单预览 --> <!-- 表单预览 -->
<div <div
v-if="modelData.formType === BpmModelFormType.NORMAL && modelData.formId && formPreview.rule.length > 0" v-if="
modelData.formType === BpmModelFormType.NORMAL &&
modelData.formId &&
formPreview.rule.length > 0
"
class="mt-20px" class="mt-20px"
> >
<div class="flex items-center mb-15px"> <div class="flex items-center mb-15px">

View File

@ -86,7 +86,7 @@ onBeforeUnmount(() => {
/> />
</div> </div>
<!-- TODO @unocss 简化 style --> <!-- TODO @unocss 简化 style -->
<div style=" margin: 10px;border: 1px solid #ccc"> <div style="margin: 10px; border: 1px solid #ccc">
<Toolbar <Toolbar
style="border-bottom: 1px solid #ccc" style="border-bottom: 1px solid #ccc"
:editor="editorRef" :editor="editorRef"
@ -106,7 +106,7 @@ onBeforeUnmount(() => {
@insert-mention="insertMention" @insert-mention="insertMention"
/> />
</div> </div>
<div style=" float: right;margin-right: 10px"> <div style="float: right; margin-right: 10px">
<el-button @click="dialogVisible = false"> </el-button> <el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleConfirm"> </el-button> <el-button type="primary" @click="handleConfirm"> </el-button>
</div> </div>

View File

@ -15,11 +15,10 @@ function parseHtml(
): SlateElement { ): SlateElement {
// TS 语法 // TS 语法
// 生成“流程记录”元素(按照此前约定的数据结构) // 生成“流程记录”元素(按照此前约定的数据结构)
const processRecord = { const processRecord = {
type: 'process-record', type: 'process-record',
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!! children: [{ text: '' }] // void node 必须有 children ,其中有一个空字符串,重要!!!
} }
return processRecord return processRecord

View File

@ -220,7 +220,7 @@ onActivated(() => {
.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

@ -268,7 +268,7 @@ const submitForm = async () => {
if (!fApi.value || !props.selectProcessDefinition) { if (!fApi.value || !props.selectProcessDefinition) {
return return
} }
try { try {
// //
await fApi.value.validate() await fApi.value.validate()

View File

@ -252,11 +252,7 @@ const flattenAreaTree = (list: AreaNode[] = [], map: Map<string, string> = new M
return map return map
} }
const mapValueWithLabelMap = ( const mapValueWithLabelMap = (value: unknown, labelMap: Map<string, string>, separator = ', ') => {
value: unknown,
labelMap: Map<string, string>,
separator = ', '
) => {
const values = toValueArray(value) const values = toValueArray(value)
const labels = values const labels = values
.map((item) => escapeHtml(labelMap.get(String(item)) ?? String(item))) .map((item) => escapeHtml(labelMap.get(String(item)) ?? String(item)))
@ -300,11 +296,7 @@ const loadPrintLookupMaps = async (formFieldsObj: FormFieldRule[]) => {
} satisfies PrintLookupMaps } satisfies PrintLookupMaps
} }
const formatPrintField = ( const formatPrintField = (rule: FormFieldRule, value: unknown, lookupMaps: PrintLookupMaps) => {
rule: FormFieldRule,
value: unknown,
lookupMaps: PrintLookupMaps
) => {
const type = String(rule.type ?? '') const type = String(rule.type ?? '')
switch (type) { switch (type) {
@ -356,7 +348,9 @@ const formatPrintField = (
return renderFileListHtml(value) return renderFileListHtml(value)
case 'IframeComponent': { case 'IframeComponent': {
const propsObj = rule.props const propsObj = rule.props
const propsUrl = isPrintableRecord(propsObj) ? String(getRecordValue(propsObj, 'url') ?? '') : '' const propsUrl = isPrintableRecord(propsObj)
? String(getRecordValue(propsObj, 'url') ?? '')
: ''
const iframeUrl = isEmptyValue(value) ? propsUrl : String(value ?? '') const iframeUrl = isEmptyValue(value) ? propsUrl : String(value ?? '')
return iframeUrl ? createFileLinkHtml(iframeUrl) : '' return iframeUrl ? createFileLinkHtml(iframeUrl) : ''
} }
@ -395,10 +389,10 @@ const initPrintDataMap = () => {
printDataMap.value['processNum'] = String(printData.value.processInstance.id ?? '') printDataMap.value['processNum'] = String(printData.value.processInstance.id ?? '')
printDataMap.value['startTime'] = formatDate(printData.value.processInstance.startTime) printDataMap.value['startTime'] = formatDate(printData.value.processInstance.startTime)
printDataMap.value['endTime'] = formatDate(printData.value.processInstance.endTime) printDataMap.value['endTime'] = formatDate(printData.value.processInstance.endTime)
printDataMap.value['processStatus'] = String(getDictLabel( printDataMap.value['processStatus'] = String(
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, getDictLabel(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, printData.value.processInstance.status) ??
printData.value.processInstance.status ''
) ?? '') )
printDataMap.value['printUser'] = userName.value printDataMap.value['printUser'] = userName.value
printDataMap.value['printTime'] = printTime.value printDataMap.value['printTime'] = printTime.value
} }

View File

@ -87,7 +87,12 @@
{{ formatPast2(scope.row.durationInMillis) }} {{ formatPast2(scope.row.durationInMillis) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="流程编号" prop="processInstanceId" :show-overflow-tooltip="true" /> <el-table-column
align="center"
label="流程编号"
prop="processInstanceId"
:show-overflow-tooltip="true"
/>
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" /> <el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
<el-table-column align="center" label="操作" fixed="right" width="80"> <el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope"> <template #default="scope">

View File

@ -77,8 +77,7 @@ const getList = async () => {
const currentUserId = getCurrentUserId() const currentUserId = getCurrentUserId()
const permission = list.value.find( const permission = list.value.find(
(item) => (item) =>
item.userId === currentUserId && item.userId === currentUserId && item.level === PermissionApi.PermissionLevelEnum.OWNER
item.level === PermissionApi.PermissionLevelEnum.OWNER
) )
if (permission) { if (permission) {
formData.value.ownerUserId = currentUserId formData.value.ownerUserId = currentUserId

View File

@ -46,7 +46,7 @@
class="!w-240px" class="!w-240px"
node-key="id" node-key="id"
placeholder="请选择归属部门" placeholder="请选择归属部门"
@change="(queryParams.userId = undefined), handleQuery()" @change="((queryParams.userId = undefined), handleQuery())"
/> />
</el-form-item> </el-form-item>
<el-form-item label="员工" prop="userId"> <el-form-item label="员工" prop="userId">

View File

@ -46,7 +46,7 @@
class="!w-240px" class="!w-240px"
node-key="id" node-key="id"
placeholder="请选择归属部门" placeholder="请选择归属部门"
@change="(queryParams.userId = undefined), handleQuery()" @change="((queryParams.userId = undefined), handleQuery())"
/> />
</el-form-item> </el-form-item>
<el-form-item label="员工" prop="userId"> <el-form-item label="员工" prop="userId">

View File

@ -11,10 +11,7 @@
:index="idx" :index="idx"
:key="resolveItemKey(item, idx)" :key="resolveItemKey(item, idx)"
></slot> ></slot>
<div <div v-if="showFooter" class="py-3 text-xs text-center text-[var(--el-text-color-secondary)]">
v-if="showFooter"
class="py-3 text-xs text-center text-[var(--el-text-color-secondary)]"
>
已到底部 已到底部
</div> </div>
</el-scrollbar> </el-scrollbar>

View File

@ -15,7 +15,9 @@
title="拖拽调整宽度" title="拖拽调整宽度"
@mousedown="startResize" @mousedown="startResize"
> >
<div class="im-resizable-aside__line w-0.5 h-full rounded-0.5 bg-transparent transition-all"></div> <div
class="im-resizable-aside__line w-0.5 h-full rounded-0.5 bg-transparent transition-all"
></div>
</div> </div>
</aside> </aside>
</template> </template>

View File

@ -42,11 +42,7 @@ import { computed } from 'vue'
import UserAvatar from '../user/UserAvatar.vue' import UserAvatar from '../user/UserAvatar.vue'
import { isPrivateConversation } from '@/views/im/utils/constants' import { isPrivateConversation } from '@/views/im/utils/constants'
import { import { getCardLabelInfo, type CardMessage, type CardTarget } from '@/views/im/utils/message'
getCardLabelInfo,
type CardMessage,
type CardTarget
} from '@/views/im/utils/message'
defineOptions({ name: 'ImCardBubble' }) defineOptions({ name: 'ImCardBubble' })

View File

@ -168,9 +168,7 @@ const message = useMessage()
const currentUserId = computed(() => getCurrentUserId()) const currentUserId = computed(() => getCurrentUserId())
/** 搜索结果过滤掉自己;用 v-if 而非 v-show避免 DOM 占位 + 头像无效请求 */ /** 搜索结果过滤掉自己;用 v-if 而非 v-show避免 DOM 占位 + 头像无效请求 */
const visibleUsers = computed(() => const visibleUsers = computed(() => users.value.filter((user) => user.id !== currentUserId.value))
users.value.filter((user) => user.id !== currentUserId.value)
)
const keyword = ref('') const keyword = ref('')
const users = ref<UserVO[]>([]) const users = ref<UserVO[]>([])
const searched = ref(false) const searched = ref(false)

View File

@ -17,11 +17,7 @@ import { computed, ref, watch } from 'vue'
import UserAvatar from '../user/UserAvatar.vue' import UserAvatar from '../user/UserAvatar.vue'
import { useFriendStore } from '../../store/friendStore' import { useFriendStore } from '../../store/friendStore'
import { useGroupStore } from '../../store/groupStore' import { useGroupStore } from '../../store/groupStore'
import { import { buildGroupAvatar, getCachedGroupAvatar, setCachedGroupAvatar } from '../../../utils/group'
buildGroupAvatar,
getCachedGroupAvatar,
setCachedGroupAvatar
} from '../../../utils/group'
import { getMemberDisplayName } from '../../../utils/user' import { getMemberDisplayName } from '../../../utils/user'
import type { GroupMember } from '../../types' import type { GroupMember } from '../../types'

View File

@ -60,9 +60,7 @@ onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
function handleChat(group: GroupLite) { function handleChat(group: GroupLite) {
const cached = groupStore.getGroup(group.id) const cached = groupStore.getGroup(group.id)
// cached getGroupDisplayName contact / cached showGroupName / // cached getGroupDisplayName contact / cached showGroupName /
const displayName = cached const displayName = cached ? getGroupDisplayName(cached) : group.showGroupName || group.name || ''
? getGroupDisplayName(cached)
: group.showGroupName || group.name || ''
// //
conversationStore.openConversation( conversationStore.openConversation(
group.id, group.id,

View File

@ -3,13 +3,9 @@
<el-dialog v-model="visible" title="设置禁言" width="560px" :close-on-click-modal="false"> <el-dialog v-model="visible" title="设置禁言" width="560px" :close-on-click-modal="false">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<!-- 成员信息卡 FriendAddDialog user 卡保持一致的浅色背景 --> <!-- 成员信息卡 FriendAddDialog user 卡保持一致的浅色背景 -->
<div <div class="flex items-center gap-2 px-3 py-2.5 rounded-md bg-[var(--el-fill-color-light)]">
class="flex items-center gap-2 px-3 py-2.5 rounded-md bg-[var(--el-fill-color-light)]"
>
<span class="text-13px text-[var(--el-text-color-secondary)]">禁言成员</span> <span class="text-13px text-[var(--el-text-color-secondary)]">禁言成员</span>
<span <span class="text-sm font-medium text-[var(--el-text-color-primary)] truncate">
class="text-sm font-medium text-[var(--el-text-color-primary)] truncate"
>
{{ memberName }} {{ memberName }}
</span> </span>
</div> </div>

View File

@ -313,22 +313,23 @@ function updateLocalResult(id: number, handleResult: number) {
<style scoped> <style scoped>
/* 自绘按钮:贴近微信小药丸样式;与 :disabled、:hover:not(:disabled) 等伪类叠加 modifier 类的组合选择器写在 class 里成本高,留 SCSS */ /* 自绘按钮:贴近微信小药丸样式;与 :disabled、:hover:not(:disabled) 等伪类叠加 modifier 类的组合选择器写在 class 里成本高,留 SCSS */
.im-group-request-list__btn { .im-group-request-list__btn {
flex-shrink: 0;
min-width: 56px;
height: 28px; height: 28px;
min-width: 56px;
padding: 0 12px; padding: 0 12px;
font-size: 13px; font-size: 13px;
border-radius: 4px;
cursor: pointer; cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 4px;
transition: transition:
background-color 0.15s, background-color 0.15s,
border-color 0.15s, border-color 0.15s,
color 0.15s; color 0.15s;
flex-shrink: 0;
} }
.im-group-request-list__btn:disabled { .im-group-request-list__btn:disabled {
opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
opacity: 0.6;
} }
.im-group-request-list__btn--primary { .im-group-request-list__btn--primary {
@ -336,6 +337,7 @@ function updateLocalResult(id: number, handleResult: number) {
background-color: var(--el-color-primary); background-color: var(--el-color-primary);
border-color: var(--el-color-primary); border-color: var(--el-color-primary);
} }
.im-group-request-list__btn--primary:hover:not(:disabled) { .im-group-request-list__btn--primary:hover:not(:disabled) {
background-color: var(--el-color-primary-light-3); background-color: var(--el-color-primary-light-3);
border-color: var(--el-color-primary-light-3); border-color: var(--el-color-primary-light-3);
@ -346,6 +348,7 @@ function updateLocalResult(id: number, handleResult: number) {
background-color: var(--el-bg-color); background-color: var(--el-bg-color);
border-color: var(--el-border-color); border-color: var(--el-border-color);
} }
.im-group-request-list__btn--ghost:hover:not(:disabled) { .im-group-request-list__btn--ghost:hover:not(:disabled) {
color: var(--el-color-primary); color: var(--el-color-primary);
border-color: var(--el-color-primary); border-color: var(--el-color-primary);

View File

@ -363,6 +363,7 @@ function handleToggle(conversation: Conversation) {
.im-conversation-picker__recent::-webkit-scrollbar { .im-conversation-picker__recent::-webkit-scrollbar {
height: 4px; height: 4px;
} }
.im-conversation-picker__recent::-webkit-scrollbar-thumb { .im-conversation-picker__recent::-webkit-scrollbar-thumb {
background-color: var(--el-border-color); background-color: var(--el-border-color);
border-radius: 2px; border-radius: 2px;

View File

@ -181,9 +181,7 @@ const disabledSet = computed(() => new Set(props.disabledIds))
const selectedSet = computed(() => new Set(props.selectedIds)) const selectedSet = computed(() => new Set(props.selectedIds))
/** 候选好友:剔除 hideIdshide 优先级最高) */ /** 候选好友:剔除 hideIdshide 优先级最高) */
const candidates = computed(() => const candidates = computed(() => props.friends.filter((friend) => !hideSet.value.has(friend.id)))
props.friends.filter((friend) => !hideSet.value.has(friend.id))
)
/** 委托 useFriendBuckets搜索规则复用左侧列表按滚动分页渲染 */ /** 委托 useFriendBuckets搜索规则复用左侧列表按滚动分页渲染 */
const { filtered } = useFriendBuckets(candidates, keyword) const { filtered } = useFriendBuckets(candidates, keyword)

View File

@ -24,7 +24,7 @@
<PagedScroller :items="shownMembers" :page-size="30" item-key="userId"> <PagedScroller :items="shownMembers" :page-size="30" item-key="userId">
<template #default="{ item }"> <template #default="{ item }">
<GroupMemberItem <GroupMemberItem
:member="(item as GroupMemberLite)" :member="item as GroupMemberLite"
:height="46" :height="46"
@click="handleToggle(item as GroupMemberLite)" @click="handleToggle(item as GroupMemberLite)"
> >
@ -86,11 +86,7 @@
<!-- grid 形态宫格预览 locked 成员右上角叠加 × 移除locked 不渲染 --> <!-- grid 形态宫格预览 locked 成员右上角叠加 × 移除locked 不渲染 -->
<div v-else class="flex flex-wrap p-2.5"> <div v-else class="flex flex-wrap p-2.5">
<GroupMemberGrid <GroupMemberGrid v-for="member in selectedMembers" :key="member.userId" :member="member">
v-for="member in selectedMembers"
:key="member.userId"
:member="member"
>
<Icon <Icon
v-if="!isLocked(member)" v-if="!isLocked(member)"
icon="ant-design:close-circle-filled" icon="ant-design:close-circle-filled"
@ -240,4 +236,3 @@ function handleToggle(member: GroupMemberLite) {
emit('update:selectedIds', next) emit('update:selectedIds', next)
} }
</script> </script>

View File

@ -71,11 +71,7 @@ import {
inviteCall, inviteCall,
noAnswerCallCheck noAnswerCallCheck
} from '@/api/im/rtc' } from '@/api/im/rtc'
import { import { ImRtcCallMediaType, ImRtcCallStage, ImConversationType } from '@/views/im/utils/constants'
ImRtcCallMediaType,
ImRtcCallStage,
ImConversationType
} from '@/views/im/utils/constants'
import { RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS } from '@/views/im/utils/config' import { RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS } from '@/views/im/utils/config'
import { getCurrentUserId } from '@/utils/auth' import { getCurrentUserId } from '@/utils/auth'
import { getSenderAvatar, getSenderDisplayName } from '@/views/im/utils/user' import { getSenderAvatar, getSenderDisplayName } from '@/views/im/utils/user'
@ -104,17 +100,15 @@ const hangingUp = ref(false)
/** 当前是否视频通话 */ /** 当前是否视频通话 */
const isVideo = computed(() => { const isVideo = computed(() => {
const t = const t =
rtcStore.call?.mediaType || rtcStore.call?.mediaType || rtcStore.incomingPayload?.mediaType || ImRtcCallMediaType.VOICE
rtcStore.incomingPayload?.mediaType ||
ImRtcCallMediaType.VOICE
return t === ImRtcCallMediaType.VIDEO return t === ImRtcCallMediaType.VIDEO
}) })
/** 当前是否群通话;决定浮动窗大小 */ /** 当前是否群通话;决定浮动窗大小 */
const isGroup = computed( const isGroup = computed(
() => () =>
(rtcStore.call?.conversationType ?? (rtcStore.call?.conversationType ?? rtcStore.incomingPayload?.conversationType) ===
rtcStore.incomingPayload?.conversationType) === ImConversationType.GROUP ImConversationType.GROUP
) )
/** 初始摄像头是否打开;群通话默认全部关闭,进入后用户主动开 */ /** 初始摄像头是否打开;群通话默认全部关闭,进入后用户主动开 */
@ -261,11 +255,7 @@ function maybeEnterRunning() {
watch( watch(
() => rtcStore.stage, () => rtcStore.stage,
async (stage) => { async (stage) => {
if ( if (stage === ImRtcCallStage.INVITING && rtcStore.call?.token && rtcStore.call?.livekitUrl) {
stage === ImRtcCallStage.INVITING &&
rtcStore.call?.token &&
rtcStore.call?.livekitUrl
) {
try { try {
await connectLiveKit(rtcStore.call.livekitUrl, rtcStore.call.token) await connectLiveKit(rtcStore.call.livekitUrl, rtcStore.call.token)
} catch (e) { } catch (e) {
@ -417,7 +407,9 @@ function handlePeerDisconnected() {
/** 通话存活期间INVITING / INCOMING / RUNNING周期性触发后端扫该 room 的超时 INVITING保持 timer 是为了 inviteCall 追加新人后也能覆盖;阈值由后端配置决定,前端只负责 trigger */ /** 通话存活期间INVITING / INCOMING / RUNNING周期性触发后端扫该 room 的超时 INVITING保持 timer 是为了 inviteCall 追加新人后也能覆盖;阈值由后端配置决定,前端只负责 trigger */
const { resume: resumeNoAnswerTimer, pause: pauseNoAnswerTimer } = useIntervalFn( const { resume: resumeNoAnswerTimer, pause: pauseNoAnswerTimer } = useIntervalFn(
triggerNoAnswerCallCheck, RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS, { immediate: false } triggerNoAnswerCallCheck,
RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS,
{ immediate: false }
) )
watch( watch(
() => rtcStore.isActive, () => rtcStore.isActive,

View File

@ -97,12 +97,14 @@ const audioRef = useMediaStreamElement<HTMLAudioElement>(() => props.participant
.tile-dot { .tile-dot {
animation: tile-dot 1.4s infinite ease-in-out both; animation: tile-dot 1.4s infinite ease-in-out both;
} }
@keyframes tile-dot { @keyframes tile-dot {
0%, 0%,
80%, 80%,
100% { 100% {
opacity: 0.3; opacity: 0.3;
} }
40% { 40% {
opacity: 1; opacity: 1;
} }

View File

@ -85,9 +85,7 @@
</div> </div>
<!-- 底部操作区麦克风 / 扬声器 / 摄像头 / (群聊共享屏幕 / 添加成员) / 挂断 --> <!-- 底部操作区麦克风 / 扬声器 / 摄像头 / (群聊共享屏幕 / 添加成员) / 挂断 -->
<div <div class="flex flex-shrink-0 gap-3 justify-around items-center pt-4 px-4 pb-5 bg-black/20">
class="flex flex-shrink-0 gap-3 justify-around items-center pt-4 px-4 pb-5 bg-black/20"
>
<div <div
class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]" class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
@click="$emit('toggle-mic')" @click="$emit('toggle-mic')"
@ -164,7 +162,9 @@
class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]" class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
@click="$emit('add-member')" @click="$emit('add-member')"
> >
<span class="flex justify-center items-center w-[52px] h-[52px] text-white rounded-full bg-white/15"> <span
class="flex justify-center items-center w-[52px] h-[52px] text-white rounded-full bg-white/15"
>
<Icon icon="ant-design:plus-outlined" :size="22" /> <Icon icon="ant-design:plus-outlined" :size="22" />
</span> </span>
<span class="text-xs text-white/70 whitespace-nowrap">添加成员</span> <span class="text-xs text-white/70 whitespace-nowrap">添加成员</span>
@ -175,7 +175,9 @@
:class="{ 'opacity-60 pointer-events-none': hangingUp }" :class="{ 'opacity-60 pointer-events-none': hangingUp }"
@click="$emit('hangup')" @click="$emit('hangup')"
> >
<span class="flex justify-center items-center w-[52px] h-[52px] text-white rounded-full bg-[#f04a4a]"> <span
class="flex justify-center items-center w-[52px] h-[52px] text-white rounded-full bg-[#f04a4a]"
>
<Icon icon="ant-design:phone-outlined" :size="22" class="rotate-[135deg]" /> <Icon icon="ant-design:phone-outlined" :size="22" class="rotate-[135deg]" />
</span> </span>
<span class="text-xs text-white/70 whitespace-nowrap">挂断</span> <span class="text-xs text-white/70 whitespace-nowrap">挂断</span>
@ -277,11 +279,13 @@ const formattedDuration = computed(() =>
.reconnect-dot { .reconnect-dot {
animation: reconnect-pulse 1s ease-in-out infinite; animation: reconnect-pulse 1s ease-in-out infinite;
} }
@keyframes reconnect-pulse { @keyframes reconnect-pulse {
0%, 0%,
100% { 100% {
opacity: 0.3; opacity: 0.3;
} }
50% { 50% {
opacity: 1; opacity: 1;
} }

View File

@ -231,7 +231,9 @@ async function handleSend() {
const results = await Promise.all(tasks) const results = await Promise.all(tasks)
const failedNames = results.filter((r) => !r.ok).map((r) => r.conversation.name || '未命名会话') const failedNames = results.filter((r) => !r.ok).map((r) => r.conversation.name || '未命名会话')
// "" // ""
conversationStore.pushRecentForwardConversationKeyList(targets.map((c) => getConversationKey(c))) conversationStore.pushRecentForwardConversationKeyList(
targets.map((c) => getConversationKey(c))
)
if (failedNames.length === 0) { if (failedNames.length === 0) {
message.success('已转发') message.success('已转发')
} else if (failedNames.length === targets.length) { } else if (failedNames.length === targets.length) {

View File

@ -101,9 +101,7 @@ function handleSendMessage() {
} }
// friendStore / // friendStore /
const friend = friendStore.getFriend(user.value.id) const friend = friendStore.getFriend(user.value.id)
const conversationName = friend const conversationName = friend ? getFriendDisplayName(friend) : user.value.nickname || ''
? getFriendDisplayName(friend)
: user.value.nickname || ''
conversationStore.openConversation( conversationStore.openConversation(
user.value.id, user.value.id,
ImConversationType.PRIVATE, ImConversationType.PRIVATE,

View File

@ -81,12 +81,12 @@ export function useLiveKitRoom() {
_room.value = r _room.value = r
r.on(RoomEvent.ParticipantConnected, (rp) => { r.on(RoomEvent.ParticipantConnected, (rp) => {
syncRemotes(r) syncRemotes(r)
const userId = parseUserId(rp.identity) const userId = parseUserId(rp.identity)
if (userId != null) { if (userId != null) {
participantConnectedHandlers.forEach((cb) => cb(userId)) participantConnectedHandlers.forEach((cb) => cb(userId))
} }
}) })
.on(RoomEvent.ParticipantDisconnected, (rp) => { .on(RoomEvent.ParticipantDisconnected, (rp) => {
syncRemotes(r) syncRemotes(r)
// 离开的参与者缓存清掉,避免下次同 sid 重连命中失效引用 // 离开的参与者缓存清掉,避免下次同 sid 重连命中失效引用

View File

@ -198,15 +198,17 @@ export const useMediaUploader = () => {
uploadProgress: 0, uploadProgress: 0,
_localFile: opts.file _localFile: opts.file
} }
void messageStore.insertMessage( void messageStore
{ .insertMessage(
type: conversation.type, {
targetId: conversation.targetId, type: conversation.type,
name: conversation.name || String(conversation.targetId), targetId: conversation.targetId,
avatar: conversation.avatar || '' name: conversation.name || String(conversation.targetId),
}, avatar: conversation.avatar || ''
placeholder },
).catch(() => undefined) placeholder
)
.catch(() => undefined)
return { clientMessageId, blobUrl } return { clientMessageId, blobUrl }
} }

View File

@ -113,14 +113,10 @@ const currentUserId = computed(() => getCurrentUserId())
const iSentIt = computed(() => props.request.fromUserId === currentUserId.value) const iSentIt = computed(() => props.request.fromUserId === currentUserId.value)
/** 是否「已拒绝」态模板里多处用到computed 一次省得到处写枚举比对 */ /** 是否「已拒绝」态模板里多处用到computed 一次省得到处写枚举比对 */
const refused = computed( const refused = computed(() => props.request.handleResult === ImFriendRequestHandleResult.REFUSED)
() => props.request.handleResult === ImFriendRequestHandleResult.REFUSED
)
/** 是否「已通过」态:转走 UserInfo 好友详情入口 */ /** 是否「已通过」态:转走 UserInfo 好友详情入口 */
const agreed = computed( const agreed = computed(() => props.request.handleResult === ImFriendRequestHandleResult.AGREED)
() => props.request.handleResult === ImFriendRequestHandleResult.AGREED
)
/** 对端的用户编号 / 昵称 / 头像 */ /** 对端的用户编号 / 昵称 / 头像 */
const peerUserId = computed(() => const peerUserId = computed(() =>

View File

@ -39,10 +39,15 @@
title="邀请好友入群" title="邀请好友入群"
@click="handleOpenInvite" @click="handleOpenInvite"
> >
<div class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--el-text-color-regular)] bg-[var(--el-fill-color-lighter)] border border-dashed border-[var(--el-border-color)] rounded-md transition-colors duration-200"> <div
class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--el-text-color-regular)] bg-[var(--el-fill-color-lighter)] border border-dashed border-[var(--el-border-color)] rounded-md transition-colors duration-200"
>
<Icon icon="ant-design:plus-outlined" /> <Icon icon="ant-design:plus-outlined" />
</div> </div>
<div class="mt-1.5 text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center">添加</div> <div
class="mt-1.5 text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center"
>添加</div
>
</div> </div>
<!-- 移出群主或管理员管理员只能移出普通成员由后端校验 --> <!-- 移出群主或管理员管理员只能移出普通成员由后端校验 -->
@ -52,10 +57,15 @@
title="移出群成员" title="移出群成员"
@click="handleOpenRemove" @click="handleOpenRemove"
> >
<div class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--el-text-color-regular)] bg-[var(--el-fill-color-lighter)] border border-dashed border-[var(--el-border-color)] rounded-md transition-colors duration-200"> <div
class="im-conversation-group-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--el-text-color-regular)] bg-[var(--el-fill-color-lighter)] border border-dashed border-[var(--el-border-color)] rounded-md transition-colors duration-200"
>
<Icon icon="ant-design:minus-outlined" /> <Icon icon="ant-design:minus-outlined" />
</div> </div>
<div class="mt-1.5 text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center">移出</div> <div
class="mt-1.5 text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center"
>移出</div
>
</div> </div>
</div> </div>
@ -87,8 +97,13 @@
<div <div
class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">群聊名称</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
<span class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate">{{ group.name }}</span> >群聊名称</span
>
<span
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate"
>{{ group.name }}</span
>
</div> </div>
</template> </template>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@ -103,8 +118,13 @@
v-else v-else
class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 transition-colors duration-150" class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 transition-colors duration-150"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">群聊名称</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
<span class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate">{{ group.name }}</span> >群聊名称</span
>
<span
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate"
>{{ group.name }}</span
>
</div> </div>
<!-- 群公告群主可改内容可能很长 > chevron 表示可展开编辑 --> <!-- 群公告群主可改内容可能很长 > chevron 表示可展开编辑 -->
@ -120,7 +140,9 @@
class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
> >
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">群公告</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>群公告</span
>
<Icon <Icon
icon="ant-design:right-outlined" icon="ant-design:right-outlined"
:size="11" :size="11"
@ -133,7 +155,9 @@
> >
{{ group.notice }} {{ group.notice }}
</span> </span>
<span v-else class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]">未设置</span> <span v-else class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]"
>未设置</span
>
</div> </div>
</template> </template>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@ -162,7 +186,9 @@
> >
{{ group.notice }} {{ group.notice }}
</span> </span>
<span v-else class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]">未设置</span> <span v-else class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]"
>未设置</span
>
</div> </div>
<!-- 备注仅自己可见保存后会替换会话列表 / 顶部群名展示历史退群群隐藏改备注走 updateGroupMember已退群会被后端拒 --> <!-- 备注仅自己可见保存后会替换会话列表 / 顶部群名展示历史退群群隐藏改备注走 updateGroupMember已退群会被后端拒 -->
@ -177,14 +203,19 @@
<div <div
class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">备注</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>备注</span
>
<span <span
v-if="group.groupRemark" v-if="group.groupRemark"
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] line-clamp-2" class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] line-clamp-2"
> >
{{ group.groupRemark }} {{ group.groupRemark }}
</span> </span>
<span v-else class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]"> <span
v-else
class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]"
>
群聊的备注仅自己可见 群聊的备注仅自己可见
</span> </span>
</div> </div>
@ -217,14 +248,18 @@
<div <div
class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">我在本群的昵称</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>我在本群的昵称</span
>
<span <span
v-if="group.remarkNickName" v-if="group.remarkNickName"
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate" class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate"
> >
{{ group.remarkNickName }} {{ group.remarkNickName }}
</span> </span>
<span v-else class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]">点击设置</span> <span v-else class="text-13px text-[var(--el-text-color-placeholder)] leading-[1.6]"
>点击设置</span
>
</div> </div>
</template> </template>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@ -249,7 +284,9 @@
class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
@click="emit('open-history')" @click="emit('open-history')"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">查找聊天内容</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>查找聊天内容</span
>
<Icon <Icon
icon="ant-design:right-outlined" icon="ant-design:right-outlined"
:size="11" :size="11"
@ -262,7 +299,9 @@
class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
@click="handleShareGroupCard" @click="handleShareGroupCard"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">分享群名片</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>分享群名片</span
>
<Icon <Icon
icon="ant-design:right-outlined" icon="ant-design:right-outlined"
:size="11" :size="11"
@ -275,17 +314,30 @@
<!-- ==================== 开关项 ==================== --> <!-- ==================== 开关项 ==================== -->
<div class="bg-[var(--el-bg-color)]"> <div class="bg-[var(--el-bg-color)]">
<div class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> <div
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">消息免打扰</span> class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"
>
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>消息免打扰</span
>
<el-switch :model-value="!!conversation?.silent" @change="onMutedChange" /> <el-switch :model-value="!!conversation?.silent" @change="onMutedChange" />
</div> </div>
<div class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> <div
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">置顶聊天</span> class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"
>
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>置顶聊天</span
>
<el-switch :model-value="!!conversation?.top" @change="onTopChange" /> <el-switch :model-value="!!conversation?.top" @change="onTopChange" />
</div> </div>
<!-- 全群禁言仅群主或管理员可操作 --> <!-- 全群禁言仅群主或管理员可操作 -->
<div v-if="isOwnerOrAdmin" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> <div
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">全群禁言</span> v-if="isOwnerOrAdmin"
class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"
>
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>全群禁言</span
>
<el-switch :model-value="!!currentMutedAll" @change="onMuteAllChange" /> <el-switch :model-value="!!currentMutedAll" @change="onMuteAllChange" />
</div> </div>
</div> </div>
@ -296,8 +348,13 @@
<div class="flex-shrink-0 h-[10px]"></div> <div class="flex-shrink-0 h-[10px]"></div>
<div class="bg-[var(--el-bg-color)]"> <div class="bg-[var(--el-bg-color)]">
<!-- 进群审批仅群主可操作开启后普通成员的申请邀请路径都需群主 / 管理员同意群主 / 管理员邀请直进 --> <!-- 进群审批仅群主可操作开启后普通成员的申请邀请路径都需群主 / 管理员同意群主 / 管理员邀请直进 -->
<div v-if="isOwner" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> <div
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">进群需要群主 / 群管理确认</span> v-if="isOwner"
class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"
>
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>进群需要群主 / 群管理确认</span
>
<el-switch :model-value="!!group.joinApproval" @change="handleJoinApprovalChange" /> <el-switch :model-value="!!group.joinApproval" @change="handleJoinApprovalChange" />
</div> </div>
<!-- 进群申请子项仅当开启审批 + 当前用户是 owner / admin 时出现点击进列表 dialog --> <!-- 进群申请子项仅当开启审批 + 当前用户是 owner / admin 时出现点击进列表 dialog -->
@ -306,7 +363,9 @@
class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
@click="handleOpenRequestList" @click="handleOpenRequestList"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">- 进群申请</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>- 进群申请</span
>
<Icon <Icon
icon="ant-design:right-outlined" icon="ant-design:right-outlined"
:size="11" :size="11"
@ -325,7 +384,9 @@
class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
@click="handleOpenAdminSet" @click="handleOpenAdminSet"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">群管理员</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>群管理员</span
>
<Icon <Icon
icon="ant-design:right-outlined" icon="ant-design:right-outlined"
:size="11" :size="11"
@ -336,7 +397,9 @@
class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-group-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
@click="handleOpenTransferOwner" @click="handleOpenTransferOwner"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">群主管理权转让</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>群主管理权转让</span
>
<Icon <Icon
icon="ant-design:right-outlined" icon="ant-design:right-outlined"
:size="11" :size="11"
@ -363,13 +426,7 @@
解散群聊 解散群聊
</el-button> </el-button>
<!-- 非群主退出群聊 --> <!-- 非群主退出群聊 -->
<el-button <el-button v-else class="w-full !h-9 text-14px" type="danger" plain @click="handleQuit">
v-else
class="w-full !h-9 text-14px"
type="danger"
plain
@click="handleQuit"
>
退出群聊 退出群聊
</el-button> </el-button>
</div> </div>
@ -399,11 +456,7 @@ import { useMessage } from '@/hooks/web/useMessage'
import { getCurrentUserId } from '@/utils/auth' import { getCurrentUserId } from '@/utils/auth'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import { import { updateGroup, muteAll, dissolveGroup } from '@/api/im/group'
updateGroup,
muteAll,
dissolveGroup
} from '@/api/im/group'
import { quitGroup, updateGroupMember } from '@/api/im/group/member' import { quitGroup, updateGroupMember } from '@/api/im/group/member'
import { useConversationStore } from '../../../../store/conversationStore' import { useConversationStore } from '../../../../store/conversationStore'
import { useGroupStore } from '../../../../store/groupStore' import { useGroupStore } from '../../../../store/groupStore'
@ -644,7 +697,11 @@ function onTopChange(value: boolean | string | number) {
if (!props.conversation) { if (!props.conversation) {
return return
} }
conversationStore.setConversationTop(props.conversation.type, props.conversation.targetId, !!value) conversationStore.setConversationTop(
props.conversation.type,
props.conversation.targetId,
!!value
)
} }
// ==================== ==================== // ==================== ====================
@ -813,13 +870,13 @@ function handleOpenTransferOwner() {
/* 「添加 / 移出」瓦片hover 时联动内部 icon-tile 走主色,跨子元素的 hover 联动无法用单元素工具类表达 */ /* 「添加 / 移出」瓦片hover 时联动内部 icon-tile 走主色,跨子元素的 hover 联动无法用单元素工具类表达 */
.im-conversation-group-side__tile-wrap:hover .im-conversation-group-side__icon-tile { .im-conversation-group-side__tile-wrap:hover .im-conversation-group-side__icon-tile {
color: var(--el-color-primary); color: var(--el-color-primary);
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9); background-color: var(--el-color-primary-light-9);
border-color: var(--el-color-primary);
} }
/* :deep 穿透 Icon 内部 svg el-icon 全局 color 在暗色模式下被主题盖过,锁 fill 到当前色 */ /* :deep 穿透 Icon 内部 svg el-icon 全局 color 在暗色模式下被主题盖过,锁 fill 到当前色 */
.im-conversation-group-side__icon-tile :deep(svg) { .im-conversation-group-side__icon-tile :deep(svg) {
fill: currentColor !important; fill: currentcolor !important;
} }
/* 相邻信息行加分隔线; 相邻兄弟选择器无法用工具类表达 */ /* 相邻信息行加分隔线; 相邻兄弟选择器无法用工具类表达 */

View File

@ -232,7 +232,8 @@ const requestText = computed(() => {
if (!isGroup.value) { if (!isGroup.value) {
return '' return ''
} }
const count = groupRequestStore.getUnhandledGroupRequestCountMap.get(props.conversation.targetId) ?? 0 const count =
groupRequestStore.getUnhandledGroupRequestCountMap.get(props.conversation.targetId) ?? 0
return count > 0 ? `[${count}条进群申请]` : '' return count > 0 ? `[${count}条进群申请]` : ''
}) })
@ -293,7 +294,6 @@ function handleContextMenu(e: MouseEvent) {
} }
) )
} }
</script> </script>
<style scoped> <style scoped>
@ -304,7 +304,7 @@ function handleContextMenu(e: MouseEvent) {
/* el-icon 的全局 color:var(--color) 在暗色模式下会渲染成白色,这里用 :deep + !important 锁定 */ /* el-icon 的全局 color:var(--color) 在暗色模式下会渲染成白色,这里用 :deep + !important 锁定 */
.conversation-item__silent :deep(svg) { .conversation-item__silent :deep(svg) {
fill: currentColor !important; fill: currentcolor !important;
} }
.conversation-item__prefix { .conversation-item__prefix {

View File

@ -32,7 +32,9 @@
:name="friend.nickname" :name="friend.nickname"
:size="50" :size="50"
/> />
<div class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center truncate"> <div
class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center truncate"
>
{{ displayName }} {{ displayName }}
</div> </div>
</div> </div>
@ -43,10 +45,15 @@
title="发起群聊" title="发起群聊"
@click="handleOpenCreateGroup" @click="handleOpenCreateGroup"
> >
<div class="im-conversation-private-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--el-text-color-regular)] bg-[var(--el-fill-color-lighter)] border border-dashed border-[var(--el-border-color)] rounded-md transition-colors duration-200"> <div
class="im-conversation-private-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--el-text-color-regular)] bg-[var(--el-fill-color-lighter)] border border-dashed border-[var(--el-border-color)] rounded-md transition-colors duration-200"
>
<Icon icon="ant-design:plus-outlined" /> <Icon icon="ant-design:plus-outlined" />
</div> </div>
<div class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center truncate">添加</div> <div
class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--el-text-color-regular)] text-center truncate"
>添加</div
>
</div> </div>
</div> </div>
@ -64,14 +71,19 @@
<div <div
class="im-conversation-private-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-private-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">备注</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>备注</span
>
<span <span
v-if="friend.displayName" v-if="friend.displayName"
class="text-13px leading-[1.6] text-[var(--el-text-color-regular)] break-all line-clamp-2" class="text-13px leading-[1.6] text-[var(--el-text-color-regular)] break-all line-clamp-2"
> >
{{ friend.displayName }} {{ friend.displayName }}
</span> </span>
<span v-else class="text-13px leading-[1.6] text-[var(--el-text-color-placeholder)]"> <span
v-else
class="text-13px leading-[1.6] text-[var(--el-text-color-placeholder)]"
>
好友备注仅自己可见 好友备注仅自己可见
</span> </span>
</div> </div>
@ -101,7 +113,9 @@
class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]" class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--el-fill-color-lighter)]"
@click="emit('open-history')" @click="emit('open-history')"
> >
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">查找聊天内容</span> <span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>查找聊天内容</span
>
<Icon <Icon
icon="ant-design:right-outlined" icon="ant-design:right-outlined"
:size="11" :size="11"
@ -114,12 +128,20 @@
<!-- 开关项 --> <!-- 开关项 -->
<div class="bg-[var(--el-bg-color)]"> <div class="bg-[var(--el-bg-color)]">
<div class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> <div
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">消息免打扰</span> class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"
>
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>消息免打扰</span
>
<el-switch :model-value="!!conversation?.silent" @change="handleMutedChange" /> <el-switch :model-value="!!conversation?.silent" @change="handleMutedChange" />
</div> </div>
<div class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> <div
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">置顶聊天</span> class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"
>
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
>置顶聊天</span
>
<el-switch :model-value="!!conversation?.top" @change="handleTopChange" /> <el-switch :model-value="!!conversation?.top" @change="handleTopChange" />
</div> </div>
</div> </div>
@ -236,7 +258,11 @@ function handleTopChange(value: boolean | string | number) {
if (!props.conversation) { if (!props.conversation) {
return return
} }
conversationStore.setConversationTop(props.conversation.type, props.conversation.targetId, !!value) conversationStore.setConversationTop(
props.conversation.type,
props.conversation.targetId,
!!value
)
} }
/** 群创建成功:跳到新群会话 + 关掉本侧抽屉,让用户专注新群 */ /** 群创建成功:跳到新群会话 + 关掉本侧抽屉,让用户专注新群 */
@ -260,13 +286,13 @@ function handleGroupCreated(groupId: number) {
/* 「+」 tile hover 时联动内部 icon-tile 走主色; 跨子元素的 hover 联动无法用单元素工具类表达 */ /* 「+」 tile hover 时联动内部 icon-tile 走主色; 跨子元素的 hover 联动无法用单元素工具类表达 */
.im-conversation-private-side__tile-wrap-clickable:hover .im-conversation-private-side__icon-tile { .im-conversation-private-side__tile-wrap-clickable:hover .im-conversation-private-side__icon-tile {
color: var(--el-color-primary); color: var(--el-color-primary);
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9); background-color: var(--el-color-primary-light-9);
border-color: var(--el-color-primary);
} }
/* :deep 穿透 Icon 内部 svg el-icon 全局 color 在暗色模式下被主题盖过,锁 fill 到当前色 */ /* :deep 穿透 Icon 内部 svg el-icon 全局 color 在暗色模式下被主题盖过,锁 fill 到当前色 */
.im-conversation-private-side__icon-tile :deep(svg) { .im-conversation-private-side__icon-tile :deep(svg) {
fill: currentColor !important; fill: currentcolor !important;
} }
/* 相邻信息行加分隔线; 相邻兄弟选择器无法用工具类表达 */ /* 相邻信息行加分隔线; 相邻兄弟选择器无法用工具类表达 */

View File

@ -342,14 +342,13 @@ onUnmounted(() => {
<style scoped> <style scoped>
/* 底部小三角:指向触发图标,仿微信 PC 气泡指针left 偏移对应表情按钮(工具栏 1st icon */ /* 底部小三角:指向触发图标,仿微信 PC 气泡指针left 偏移对应表情按钮(工具栏 1st icon */
.im-popover-arrow::after { .im-popover-arrow::after {
content: '';
position: absolute; position: absolute;
top: calc(100% - 1px); top: calc(100% - 1px);
left: 10px; left: 10px;
border-style: solid;
border-width: 6px 6px 0 6px;
border-color: var(--el-bg-color) transparent transparent transparent; border-color: var(--el-bg-color) transparent transparent transparent;
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.08)); border-style: solid;
border-width: 6px 6px 0;
content: '';
filter: drop-shadow(0 2px 2px rgb(0 0 0 / 8%));
} }
</style> </style>

View File

@ -364,7 +364,7 @@ function collectFromEditor(root: HTMLElement): { text: string; atUserIds: number
* 3. 二次防御collectFromEditor trim可能比 syncEditorState 更严格例如全 ZWSP仍空就 return * 3. 二次防御collectFromEditor trim可能比 syncEditorState 更严格例如全 ZWSP仍空就 return
* 4. 清空 + 同步状态先清 innerHTML syncEditorState placeholder / canSend 一起回归 * 4. 清空 + 同步状态先清 innerHTML syncEditorState placeholder / canSend 一起回归
* 顺序很重要先清后 sync否则 sync 看到旧内容会误判 * 顺序很重要先清后 sync否则 sync 看到旧内容会误判
* 5. 上送atUserIds 非空才传避免发空数组quote clearConversationDraft 前抓取确保引用条立即消失 * 5. 上送atUserIds 非空才传避免发空数组quote clearConversationDraft 前抓取确保引用条立即消失
*/ */
async function handleSend(options?: { receipt?: boolean }) { async function handleSend(options?: { receipt?: boolean }) {
const editor = editorRef.value const editor = editorRef.value
@ -1171,8 +1171,9 @@ async function onVideoPicked(e: Event) {
.message-input__tool:deep(svg) { .message-input__tool:deep(svg) {
font-size: 18px !important; font-size: 18px !important;
color: var(--el-text-color-regular) !important; color: var(--el-text-color-regular) !important;
fill: currentColor !important; fill: currentcolor !important;
} }
.message-input__tool:hover, .message-input__tool:hover,
.message-input__tool:hover:deep(svg) { .message-input__tool:hover:deep(svg) {
color: var(--el-color-primary) !important; color: var(--el-color-primary) !important;
@ -1180,10 +1181,10 @@ async function onVideoPicked(e: Event) {
/* 用 data-empty 而非 :empty浏览器在删空后会留下 <br>:empty 不命中data-empty 由 syncEditorState 维护 */ /* 用 data-empty 而非 :empty浏览器在删空后会留下 <br>:empty 不命中data-empty 由 syncEditorState 维护 */
.message-input__editor[data-empty]::before { .message-input__editor[data-empty]::before {
content: attr(data-placeholder); position: absolute;
color: var(--el-text-color-placeholder); color: var(--el-text-color-placeholder);
pointer-events: none; pointer-events: none;
position: absolute; content: attr(data-placeholder);
} }
/* @ token 走主色高亮contenteditable=false 让 backspace 整段删而不是逐字符 */ /* @ token 走主色高亮contenteditable=false 让 backspace 整段删而不是逐字符 */

View File

@ -54,15 +54,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { import { computed, onBeforeUnmount, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'
computed,
onBeforeUnmount,
onMounted,
onUnmounted,
ref,
useTemplateRef,
watch
} from 'vue'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { formatSeconds } from '@/utils/formatTime' import { formatSeconds } from '@/utils/formatTime'
@ -292,14 +284,14 @@ onUnmounted(() => {
<style scoped> <style scoped>
/* 底部小三角:指向触发图标,仿微信 PC 气泡指针left 偏移对应语音按钮(工具栏 4th icon */ /* 底部小三角:指向触发图标,仿微信 PC 气泡指针left 偏移对应语音按钮(工具栏 4th icon */
.im-popover-arrow::after { .im-popover-arrow::after {
content: '';
position: absolute; position: absolute;
top: calc(100% - 1px); top: calc(100% - 1px);
left: 110px; left: 110px;
border-style: solid;
border-width: 6px 6px 0 6px;
border-color: var(--el-bg-color) transparent transparent transparent; border-color: var(--el-bg-color) transparent transparent transparent;
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.08)); border-style: solid;
border-width: 6px 6px 0;
content: '';
filter: drop-shadow(0 2px 2px rgb(0 0 0 / 8%));
} }
/* 录音中的脉冲呼吸动画;@keyframes 必须 CSS 定义 */ /* 录音中的脉冲呼吸动画;@keyframes 必须 CSS 定义 */
@ -309,13 +301,15 @@ onUnmounted(() => {
@keyframes im-voice-pulse { @keyframes im-voice-pulse {
0% { 0% {
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0.6); box-shadow: 0 0 0 0 rgb(245 108 108 / 60%);
} }
70% { 70% {
box-shadow: 0 0 0 20px rgba(245, 108, 108, 0); box-shadow: 0 0 0 20px rgb(245 108 108 / 0%);
} }
100% { 100% {
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0); box-shadow: 0 0 0 0 rgb(245 108 108 / 0%);
} }
} }
</style> </style>

View File

@ -182,15 +182,15 @@ async function handleRemove(pinnedMessage: Message) {
<style scoped> <style scoped>
/* 弹出层朝上的三角箭头;走 ::before + 4 边 border 配色画,颜色跟弹出层 background 一致 */ /* 弹出层朝上的三角箭头;走 ::before + 4 边 border 配色画,颜色跟弹出层 background 一致 */
.im-group-pinned-message__list::before { .im-group-pinned-message__list::before {
content: '';
position: absolute; position: absolute;
top: -8px; top: -8px;
left: 184px; left: 184px;
width: 0; width: 0;
height: 0; height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent; border-right: 8px solid transparent;
border-bottom: 8px solid var(--el-bg-color); border-bottom: 8px solid var(--el-bg-color);
filter: drop-shadow(0 -2px 1px rgba(0, 0, 0, 0.04)); border-left: 8px solid transparent;
content: '';
filter: drop-shadow(0 -2px 1px rgb(0 0 0 / 4%));
} }
</style> </style>

View File

@ -82,6 +82,6 @@ const pendingCount = computed(() => groupRequestStore.getUnhandledGroupRequestCo
<style scoped> <style scoped>
/* :deep 穿透 Icon 子组件 DOM强制 svg 走 currentColor 应对暗色模式 el-icon 全局色覆盖 */ /* :deep 穿透 Icon 子组件 DOM强制 svg 走 currentColor 应对暗色模式 el-icon 全局色覆盖 */
.im-group-request-pending__icon :deep(svg) { .im-group-request-pending__icon :deep(svg) {
fill: currentColor !important; fill: currentcolor !important;
} }
</style> </style>

View File

@ -5,8 +5,14 @@
class="material-card cursor-pointer w-full overflow-hidden rounded-lg bg-[var(--el-bg-color)] border border-solid border-[var(--el-border-color-lighter)]" class="material-card cursor-pointer w-full overflow-hidden rounded-lg bg-[var(--el-bg-color)] border border-solid border-[var(--el-border-color-lighter)]"
@click="onClick" @click="onClick"
> >
<img v-if="payload.coverUrl" class="block w-full h-[200px] object-cover" :src="payload.coverUrl" /> <img
<div class="px-3.5 py-3 text-15px font-600 leading-[1.4] text-[var(--el-text-color-primary)] line-clamp-2"> v-if="payload.coverUrl"
class="block w-full h-[200px] object-cover"
:src="payload.coverUrl"
/>
<div
class="px-3.5 py-3 text-15px font-600 leading-[1.4] text-[var(--el-text-color-primary)] line-clamp-2"
>
{{ payload.title || '(无标题)' }} {{ payload.title || '(无标题)' }}
</div> </div>
</div> </div>
@ -19,7 +25,9 @@
> >
<div class="flex gap-2.5 items-start"> <div class="flex gap-2.5 items-start">
<div class="flex flex-1 flex-col gap-1.5 min-w-0"> <div class="flex flex-1 flex-col gap-1.5 min-w-0">
<div class="text-15px font-600 leading-[1.4] text-[var(--el-text-color-primary)] line-clamp-2 break-all"> <div
class="text-15px font-600 leading-[1.4] text-[var(--el-text-color-primary)] line-clamp-2 break-all"
>
{{ payload.title || '(无标题)' }} {{ payload.title || '(无标题)' }}
</div> </div>
<div <div
@ -35,7 +43,9 @@
:src="payload.coverUrl" :src="payload.coverUrl"
/> />
</div> </div>
<div class="flex items-center gap-1.5 mt-2.5 pt-2 border-t border-t-solid border-[var(--el-border-color-lighter)] text-12px text-[var(--el-text-color-secondary)]"> <div
class="flex items-center gap-1.5 mt-2.5 pt-2 border-t border-t-solid border-[var(--el-border-color-lighter)] text-12px text-[var(--el-text-color-secondary)]"
>
<img <img
v-if="sourceChannel?.avatar" v-if="sourceChannel?.avatar"
class="w-4 h-4 rounded-full object-cover flex-shrink-0" class="w-4 h-4 rounded-full object-cover flex-shrink-0"
@ -47,13 +57,11 @@
</div> </div>
<!-- 富文本详情全屏弹窗按需挂载destroy-on-close 关闭后释放 v-dompurify-html 解析的 DOM --> <!-- 富文本详情全屏弹窗按需挂载destroy-on-close 关闭后释放 v-dompurify-html 解析的 DOM -->
<Dialog <Dialog v-model="detailVisible" :title="payload.title || '详情'" fullscreen destroy-on-close>
v-model="detailVisible" <div
:title="payload.title || '详情'" v-loading="detailLoading"
fullscreen class="material-detail-body max-w-[720px] mx-auto px-5 pt-6 pb-20"
destroy-on-close >
>
<div v-loading="detailLoading" class="material-detail-body max-w-[720px] mx-auto px-5 pt-6 pb-20">
<div class="text-[22px] font-600 leading-[1.4] text-[var(--el-text-color-primary)] mb-5"> <div class="text-[22px] font-600 leading-[1.4] text-[var(--el-text-color-primary)] mb-5">
{{ payload.title || '' }} {{ payload.title || '' }}
</div> </div>
@ -95,9 +103,7 @@ const isChannelView = computed(
) )
/** 反序列化 content JSON 为 payload 对象 */ /** 反序列化 content JSON 为 payload 对象 */
const payload = computed<MaterialMessage>( const payload = computed<MaterialMessage>(() => parseMessage<MaterialMessage>(props.content) ?? {})
() => parseMessage<MaterialMessage>(props.content) ?? {}
)
/** 来源频道;紧凑卡底部渲染头像 + 名称 */ /** 来源频道;紧凑卡底部渲染头像 + 名称 */
const sourceChannel = computed(() => const sourceChannel = computed(() =>
@ -137,15 +143,15 @@ const onClick = async () => {
transition: box-shadow 0.15s ease; transition: box-shadow 0.15s ease;
&:hover { &:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 12px rgb(0 0 0 / 8%);
} }
} }
/* :deep 穿透 v-dompurify-html 渲染的内嵌 DOM统一控制富文本里的 img / p / hN 排版 */ /* :deep 穿透 v-dompurify-html 渲染的内嵌 DOM统一控制富文本里的 img / p / hN 排版 */
.article-content { .article-content {
:deep(img) { :deep(img) {
max-width: 100%;
height: auto; height: auto;
max-width: 100%;
} }
:deep(p) { :deep(p) {

View File

@ -355,28 +355,31 @@ onBeforeUnmount(() => {
颜色与气泡背景对应 1px 视觉吃进去省一张图片 */ 颜色与气泡背景对应 1px 视觉吃进去省一张图片 */
.message-bubble--other::before, .message-bubble--other::before,
.message-bubble--self::before { .message-bubble--self::before {
content: '';
position: absolute; position: absolute;
top: 12px; top: 12px;
width: 0; width: 0;
height: 0; height: 0;
border-style: solid; border-style: solid;
content: '';
} }
.message-bubble--other::before { .message-bubble--other::before {
left: -5px; left: -5px;
border-width: 5px 6px 5px 0;
border-color: transparent var(--el-fill-color-light) transparent transparent; border-color: transparent var(--el-fill-color-light) transparent transparent;
border-width: 5px 6px 5px 0;
} }
.message-bubble--self::before { .message-bubble--self::before {
right: -5px; right: -5px;
border-width: 5px 0 5px 6px;
border-color: transparent transparent transparent #95ec69; border-color: transparent transparent transparent #95ec69;
border-width: 5px 0 5px 6px;
} }
/* :deep 穿透 scoped 子组件 DOMel-icon 在暗色模式下全局 color 被 .el-icon{color:var(--color)} 干扰,把 voice 图标 fill 锁死 */ /* :deep 穿透 scoped 子组件 DOMel-icon 在暗色模式下全局 color 被 .el-icon{color:var(--color)} 干扰,把 voice 图标 fill 锁死 */
.message-bubble__voice-icon :deep(svg) { .message-bubble__voice-icon :deep(svg) {
fill: #606266 !important; fill: #606266 !important;
} }
.message-bubble__voice-icon.im-voice-playing :deep(svg) { .message-bubble__voice-icon.im-voice-playing :deep(svg) {
fill: #409eff !important; fill: #409eff !important;
} }
@ -391,6 +394,7 @@ onBeforeUnmount(() => {
100% { 100% {
transform: scale(1); transform: scale(1);
} }
50% { 50% {
transform: scale(1.15); transform: scale(1.15);
} }

View File

@ -714,8 +714,8 @@ function locateMessage(messageId: number) {
/* :deep 穿透 el-tag 子组件 DOM搜索区 chip 禁掉 hover 颜色过渡 / × 图标动效,避免在搜索区里有抖动感 */ /* :deep 穿透 el-tag 子组件 DOM搜索区 chip 禁掉 hover 颜色过渡 / × 图标动效,避免在搜索区里有抖动感 */
.im-message-history__chip, .im-message-history__chip,
.im-message-history__chip :deep(.el-tag__close) { .im-message-history__chip :deep(.el-tag__close) {
transition: none !important;
animation: none !important; animation: none !important;
transition: none !important;
} }
/* 「定位到聊天位置」按钮:父行 hover 才显示,走 .parent:hover .child 跨元素状态联动 */ /* 「定位到聊天位置」按钮:父行 hover 才显示,走 .parent:hover .child 跨元素状态联动 */
@ -723,16 +723,18 @@ function locateMessage(messageId: number) {
position: absolute; position: absolute;
top: 100%; top: 100%;
right: 0; right: 0;
margin-top: 4px;
display: none; display: none;
white-space: nowrap; margin-top: 4px;
color: #1989fa; color: #1989fa;
white-space: nowrap;
cursor: pointer; cursor: pointer;
transition: color 0.15s; transition: color 0.15s;
} }
.im-message-history__row:hover .im-message-history__locate { .im-message-history__row:hover .im-message-history__locate {
display: block; display: block;
} }
.im-message-history__locate:hover { .im-message-history__locate:hover {
color: #146fc7; color: #146fc7;
} }
@ -744,30 +746,35 @@ function locateMessage(messageId: number) {
color: #1989fa; color: #1989fa;
transition: color 0.15s; transition: color 0.15s;
} }
.im-message-history__tab:hover { .im-message-history__tab:hover {
color: #2f81d4; color: #2f81d4;
} }
.im-message-history__tab--active::after { .im-message-history__tab--active::after {
content: '';
position: absolute; position: absolute;
left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0;
height: 2px; height: 2px;
background: #1989fa; background: #1989fa;
border-radius: 1px; border-radius: 1px;
content: '';
} }
/* :deep 穿透 el-calendar 子组件 DOM默认偏大压一压让它能塞进 320 popover */ /* :deep 穿透 el-calendar 子组件 DOM默认偏大压一压让它能塞进 320 popover */
.im-message-history__calendar :deep(.el-calendar) { .im-message-history__calendar :deep(.el-calendar) {
--el-calendar-cell-width: 36px; --el-calendar-cell-width: 36px;
} }
.im-message-history__calendar :deep(.el-calendar__header) { .im-message-history__calendar :deep(.el-calendar__header) {
padding: 4px 8px; padding: 4px 8px;
} }
.im-message-history__calendar :deep(.el-calendar-table) { .im-message-history__calendar :deep(.el-calendar-table) {
font-size: 12px; font-size: 12px;
} }
.im-message-history__calendar :deep(.el-calendar-day) { .im-message-history__calendar :deep(.el-calendar-day) {
height: 36px; height: 36px;
padding: 4px; padding: 4px;

View File

@ -1053,6 +1053,7 @@ function handleDelete() {
.im-loading-spin { .im-loading-spin {
animation: im-loading-spin 1s linear infinite; animation: im-loading-spin 1s linear infinite;
} }
@keyframes im-loading-spin { @keyframes im-loading-spin {
to { to {
transform: rotate(360deg); transform: rotate(360deg);

View File

@ -755,9 +755,10 @@ watch(
.message-panel__header-icon, .message-panel__header-icon,
.message-panel__header-icon :deep(svg) { .message-panel__header-icon :deep(svg) {
color: var(--el-text-color-regular) !important; color: var(--el-text-color-regular) !important;
fill: currentColor !important; fill: currentcolor !important;
transition: color 0.15s; transition: color 0.15s;
} }
.message-panel__header-icon:hover, .message-panel__header-icon:hover,
.message-panel__header-icon:hover :deep(svg) { .message-panel__header-icon:hover :deep(svg) {
color: var(--el-color-primary) !important; color: var(--el-color-primary) !important;
@ -775,6 +776,7 @@ watch(
.message-panel__message-anchor { .message-panel__message-anchor {
transition: background-color 0.6s ease; transition: background-color 0.6s ease;
} }
.message-panel__message-anchor--highlight { .message-panel__message-anchor--highlight {
background-color: var(--el-color-warning-light-9); background-color: var(--el-color-warning-light-9);
} }

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