style: 使用 Prettier 格式化源码
parent
3208a76868
commit
18ed1cdfed
|
|
@ -39,4 +39,4 @@ export const ProcessExpressionApi = {
|
|||
exportProcessExpression: async (params) => {
|
||||
return await request.download({ url: `/bpm/process-expression/export-excel`, params })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
|
||||
export const updateBpmSimpleModel = async (data) => {
|
||||
return await request.post({
|
||||
url: '/bpm/model/simple/update',
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
||||
|
|
@ -65,4 +69,3 @@ export const blockFriend = (friendUserId: number | string) => {
|
|||
export const unblockFriend = (friendUserId: number | string) => {
|
||||
return request.put<boolean>({ url: '/im/friend/unblock', params: { friendUserId } })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,12 +42,18 @@ export const getStatisticsOverview = (): Promise<ImStatisticsOverviewVO> => {
|
|||
|
||||
// 获得消息趋势(私聊 + 群聊双线)
|
||||
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> => {
|
||||
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 天)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ export const deleteDataSourceConfig = (id: 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(',') }
|
||||
})
|
||||
}
|
||||
|
||||
// 查询数据源配置详情
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
/** 学生课程信息 */
|
||||
export interface Demo03Course {
|
||||
id: number; // 编号
|
||||
studentId?: number; // 学生编号
|
||||
name?: string; // 名字
|
||||
score?: number; // 分数
|
||||
id: number // 编号
|
||||
studentId?: number // 学生编号
|
||||
name?: string // 名字
|
||||
score?: number // 分数
|
||||
}
|
||||
|
||||
/** 学生班级信息 */
|
||||
export interface Demo03Grade {
|
||||
id: number; // 编号
|
||||
studentId?: number; // 学生编号
|
||||
name?: string; // 名字
|
||||
teacher?: string; // 班主任
|
||||
id: number // 编号
|
||||
studentId?: number // 学生编号
|
||||
name?: string // 名字
|
||||
teacher?: string // 班主任
|
||||
}
|
||||
|
||||
/** 学生信息 */
|
||||
export interface Demo03Student {
|
||||
id: number; // 编号
|
||||
name?: string; // 名字
|
||||
sex?: number; // 性别
|
||||
birthday?: string | Dayjs; // 出生日期
|
||||
description?: string; // 简介
|
||||
id: number // 编号
|
||||
name?: string // 名字
|
||||
sex?: number // 性别
|
||||
birthday?: string | Dayjs // 出生日期
|
||||
description?: string // 简介
|
||||
}
|
||||
|
||||
// 学生 API
|
||||
|
|
@ -55,7 +55,9 @@ export const Demo03StudentApi = {
|
|||
|
||||
/** 批量删除学生 */
|
||||
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
|
||||
|
|
@ -63,7 +65,7 @@ export const Demo03StudentApi = {
|
|||
return await request.download({ url: `/infra/demo03-student-erp/export-excel`, params })
|
||||
},
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
// 获得学生课程分页
|
||||
getDemo03CoursePage: async (params) => {
|
||||
|
|
@ -86,7 +88,9 @@ export const Demo03StudentApi = {
|
|||
|
||||
/** 批量删除学生课程 */
|
||||
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 })
|
||||
},
|
||||
|
||||
// ==================== 子表(学生班级) ====================
|
||||
// ==================== 子表(学生班级) ====================
|
||||
|
||||
// 获得学生班级分页
|
||||
getDemo03GradePage: async (params) => {
|
||||
|
|
@ -117,11 +121,13 @@ export const Demo03StudentApi = {
|
|||
|
||||
/** 批量删除学生班级 */
|
||||
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) => {
|
||||
return await request.get({ url: `/infra/demo03-student-erp/demo03-grade/get?id=` + id })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
/** 学生课程信息 */
|
||||
export interface Demo03Course {
|
||||
id: number; // 编号
|
||||
studentId?: number; // 学生编号
|
||||
name?: string; // 名字
|
||||
score?: number; // 分数
|
||||
id: number // 编号
|
||||
studentId?: number // 学生编号
|
||||
name?: string // 名字
|
||||
score?: number // 分数
|
||||
}
|
||||
|
||||
/** 学生班级信息 */
|
||||
export interface Demo03Grade {
|
||||
id: number; // 编号
|
||||
studentId?: number; // 学生编号
|
||||
name?: string; // 名字
|
||||
teacher?: string; // 班主任
|
||||
id: number // 编号
|
||||
studentId?: number // 学生编号
|
||||
name?: string // 名字
|
||||
teacher?: string // 班主任
|
||||
}
|
||||
|
||||
/** 学生信息 */
|
||||
export interface Demo03Student {
|
||||
id: number; // 编号
|
||||
name?: string; // 名字
|
||||
sex?: number; // 性别
|
||||
birthday?: string | Dayjs; // 出生日期
|
||||
description?: string; // 简介
|
||||
id: number // 编号
|
||||
name?: string // 名字
|
||||
sex?: number // 性别
|
||||
birthday?: string | Dayjs // 出生日期
|
||||
description?: string // 简介
|
||||
demo03courses?: Demo03Course[]
|
||||
demo03grade?: Demo03Grade
|
||||
}
|
||||
|
|
@ -57,7 +57,9 @@ export const Demo03StudentApi = {
|
|||
|
||||
/** 批量删除学生 */
|
||||
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
|
||||
|
|
@ -65,17 +67,21 @@ export const Demo03StudentApi = {
|
|||
return await request.download({ url: `/infra/demo03-student-inner/export-excel`, params })
|
||||
},
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
// 获得学生课程列表
|
||||
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) => {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import request from '@/config/axios'
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
/** 学生课程信息 */
|
||||
export interface Demo03Course {
|
||||
id: number; // 编号
|
||||
studentId?: number; // 学生编号
|
||||
name?: string; // 名字
|
||||
score?: number; // 分数
|
||||
id: number // 编号
|
||||
studentId?: number // 学生编号
|
||||
name?: string // 名字
|
||||
score?: number // 分数
|
||||
}
|
||||
|
||||
/** 学生班级信息 */
|
||||
export interface Demo03Grade {
|
||||
id: number; // 编号
|
||||
studentId?: number; // 学生编号
|
||||
name?: string; // 名字
|
||||
teacher?: string; // 班主任
|
||||
id: number // 编号
|
||||
studentId?: number // 学生编号
|
||||
name?: string // 名字
|
||||
teacher?: string // 班主任
|
||||
}
|
||||
|
||||
/** 学生信息 */
|
||||
export interface Demo03Student {
|
||||
id: number; // 编号
|
||||
name?: string; // 名字
|
||||
sex?: number; // 性别
|
||||
birthday?: string | Dayjs; // 出生日期
|
||||
description?: string; // 简介
|
||||
id: number // 编号
|
||||
name?: string // 名字
|
||||
sex?: number // 性别
|
||||
birthday?: string | Dayjs // 出生日期
|
||||
description?: string // 简介
|
||||
demo03courses?: Demo03Course[]
|
||||
demo03grade?: Demo03Grade
|
||||
}
|
||||
|
|
@ -57,7 +57,9 @@ export const Demo03StudentApi = {
|
|||
|
||||
/** 批量删除学生 */
|
||||
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
|
||||
|
|
@ -65,17 +67,21 @@ export const Demo03StudentApi = {
|
|||
return await request.download({ url: `/infra/demo03-student-normal/export-excel`, params })
|
||||
},
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
// 获得学生课程列表
|
||||
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) => {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ export const deleteMailAccount = async (id: 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(',') }
|
||||
})
|
||||
}
|
||||
|
||||
// 获得邮箱账号精简列表
|
||||
|
|
|
|||
|
|
@ -48,5 +48,8 @@ export const deleteOAuth2Client = (id: number) => {
|
|||
|
||||
// 批量删除 OAuth2 客户端
|
||||
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(',') }
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ export const deleteTenantPackage = (id: 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(',') }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取租户套餐精简信息列表
|
||||
|
|
|
|||
|
|
@ -17,7 +17,12 @@
|
|||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="高度" prop="height">
|
||||
<el-input-number class="!w-50% mr-10px" controls-position="right" v-model="formData.height" /> px
|
||||
<el-input-number
|
||||
class="!w-50% mr-10px"
|
||||
controls-position="right"
|
||||
v-model="formData.height"
|
||||
/>
|
||||
px
|
||||
</el-form-item>
|
||||
<el-form-item label="指示器" prop="indicator">
|
||||
<el-radio-group v-model="formData.indicator">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
|
||||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||
|
||||
/** 积分商城属性 */
|
||||
export interface PromotionPointProperty {
|
||||
|
|
|
|||
|
|
@ -31,13 +31,7 @@
|
|||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="高度" prop="height" label-width="70px">
|
||||
<el-slider
|
||||
v-model="formData.height"
|
||||
:max="200"
|
||||
:min="20"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
<el-slider v-model="formData.height" :max="200" :min="20" show-input input-size="small" />
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="主标题" class="property-group" shadow="never">
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ interface AreaVO {
|
|||
|
||||
interface Props {
|
||||
modelValue?: number[] | string[]
|
||||
level?: typeof AreaLevelEnum[keyof typeof AreaLevelEnum]
|
||||
level?: (typeof AreaLevelEnum)[keyof typeof AreaLevelEnum]
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
clearable?: boolean
|
||||
|
|
|
|||
|
|
@ -155,32 +155,32 @@ const hasValidPresetValue = (): boolean => {
|
|||
// 设置默认值(当前用户部门)
|
||||
const setDefaultValue = () => {
|
||||
console.log('[DeptSelect] setDefaultValue called, defaultCurrentDept:', props.defaultCurrentDept)
|
||||
|
||||
|
||||
// 仅当 defaultCurrentDept 为 true 时处理
|
||||
if (!props.defaultCurrentDept) {
|
||||
console.log('[DeptSelect] defaultCurrentDept is false, skip')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 检查是否已有预设值(预设值优先级高于默认当前部门)
|
||||
if (hasValidPresetValue()) {
|
||||
console.log('[DeptSelect] has preset value, skip:', props.modelValue)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 获取当前用户的部门 ID
|
||||
const userStore = useUserStoreWithOut()
|
||||
const user = userStore.getUser
|
||||
const deptId = user?.deptId
|
||||
|
||||
|
||||
console.log('[DeptSelect] current user:', user, 'deptId:', deptId)
|
||||
|
||||
|
||||
// 处理 deptId 为空或 0 的边界情况
|
||||
if (!deptId || deptId === 0) {
|
||||
console.log('[DeptSelect] deptId is invalid, skip')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 根据多选模式决定默认值格式
|
||||
const defaultValue = props.multiple ? [deptId] : deptId
|
||||
console.log('[DeptSelect] setting default value:', defaultValue)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,14 @@ export const useFormCreateDesigner = async (designer: Ref) => {
|
|||
designer.value?.removeMenuItem('fcEditor')
|
||||
const iframeRule = useIframeRule()
|
||||
const areaSelectRule = useAreaSelectRule()
|
||||
const components = [editorRule, uploadFileRule, uploadImgRule, uploadImgsRule, iframeRule, areaSelectRule]
|
||||
const components = [
|
||||
editorRule,
|
||||
uploadFileRule,
|
||||
uploadImgRule,
|
||||
uploadImgsRule,
|
||||
iframeRule,
|
||||
areaSelectRule
|
||||
]
|
||||
components.forEach((component) => {
|
||||
// 插入组件规则
|
||||
designer.value?.addComponent(component)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const localeProps = (t, prefix, rules) => {
|
|||
|
||||
/**
|
||||
* 解析表单组件的 field, title 等字段(递归,如果组件包含子组件)
|
||||
*
|
||||
*
|
||||
* @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule
|
||||
* @param fields 解析后表单组件字段
|
||||
* @param parentTitle 如果是子表单,子表单的标题,默认为空
|
||||
|
|
|
|||
|
|
@ -125,7 +125,12 @@ watch(
|
|||
|
||||
<template>
|
||||
<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>
|
||||
<ElPopover
|
||||
:popper-options="{
|
||||
|
|
|
|||
|
|
@ -5,43 +5,43 @@ export interface JsonEditorProps {
|
|||
* JSON数据,支持双向绑定
|
||||
*/
|
||||
modelValue: any
|
||||
|
||||
|
||||
/**
|
||||
* 编辑器模式
|
||||
* @default 'tree'
|
||||
*/
|
||||
mode?: JSONEditorMode
|
||||
|
||||
|
||||
/**
|
||||
* 编辑器高度
|
||||
* @default '400px'
|
||||
*/
|
||||
height?: string
|
||||
|
||||
|
||||
/**
|
||||
* 是否显示模式选择下拉菜单
|
||||
* @default false
|
||||
*/
|
||||
showModeSelection?: boolean
|
||||
|
||||
|
||||
/**
|
||||
* 是否显示导航栏
|
||||
* @default false
|
||||
*/
|
||||
showNavigationBar?: boolean
|
||||
|
||||
|
||||
/**
|
||||
* 是否显示状态栏
|
||||
* @default true
|
||||
*/
|
||||
showStatusBar?: boolean
|
||||
|
||||
|
||||
/**
|
||||
* 是否显示主菜单栏
|
||||
* @default true
|
||||
*/
|
||||
showMainMenuBar?: boolean
|
||||
|
||||
|
||||
/**
|
||||
* JSONEditor配置选项
|
||||
* @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: 'change', value: any): void
|
||||
|
||||
|
||||
/**
|
||||
* 验证错误时触发
|
||||
*/
|
||||
|
|
@ -77,4 +77,4 @@ export interface JsonEditorExpose {
|
|||
* 获取原始的JSONEditor实例
|
||||
*/
|
||||
getEditor: () => any
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,13 +50,13 @@ const props = defineProps({
|
|||
modelFormId: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: undefined,
|
||||
default: undefined
|
||||
},
|
||||
// 表单类型
|
||||
// 表单类型
|
||||
modelFormType: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: BpmModelFormType.NORMAL,
|
||||
default: BpmModelFormType.NORMAL
|
||||
},
|
||||
// 可发起流程的人员编号
|
||||
startUserIds: {
|
||||
|
|
@ -73,30 +73,30 @@ const props = defineProps({
|
|||
const processData = inject('processData') as Ref
|
||||
const loading = ref(false)
|
||||
const formFields = ref<string[]>([])
|
||||
const formType = ref(props.modelFormType);
|
||||
const formType = ref(props.modelFormType)
|
||||
|
||||
// 监听 modelFormType 变化
|
||||
watch(
|
||||
() => props.modelFormType,
|
||||
(newVal) => {
|
||||
formType.value = newVal;
|
||||
},
|
||||
);
|
||||
formType.value = newVal
|
||||
}
|
||||
)
|
||||
|
||||
// 监听 modelFormId 变化
|
||||
watch(
|
||||
() => props.modelFormId,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
const form = await getForm(newVal);
|
||||
formFields.value = form?.fields;
|
||||
const form = await getForm(newVal)
|
||||
formFields.value = form?.fields
|
||||
} else {
|
||||
// 如果 modelFormId 为空,清空表单字段
|
||||
formFields.value = [];
|
||||
formFields.value = []
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
|
||||
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
|
||||
|
|
@ -118,7 +118,6 @@ provide('startDeptIds', props.startDeptIds)
|
|||
provide('tasks', [])
|
||||
provide('processInstance', {})
|
||||
|
||||
|
||||
const message = useMessage() // 国际化
|
||||
const processNodeTree = ref<SimpleFlowNode | undefined>()
|
||||
provide('processNodeTree', processNodeTree)
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ import SimpleProcessDesigner from './SimpleProcessDesigner.vue'
|
|||
import SimpleProcessViewer from './SimpleProcessViewer.vue'
|
||||
import '../theme/simple-process-designer.scss'
|
||||
|
||||
export { SimpleProcessDesigner, SimpleProcessViewer}
|
||||
export { SimpleProcessDesigner, SimpleProcessViewer }
|
||||
|
|
|
|||
|
|
@ -295,10 +295,20 @@
|
|||
/>
|
||||
</el-select>
|
||||
</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-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-option
|
||||
v-for="(field, fIdx) in digitalFormFieldOptions"
|
||||
|
|
@ -308,7 +318,12 @@
|
|||
/>
|
||||
</el-select>
|
||||
</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-option
|
||||
v-for="(field, fIdx) in multiFormFieldOptions"
|
||||
|
|
@ -519,7 +534,9 @@ const showChildProcessNodeConfig = (node: SimpleFlowNode) => {
|
|||
configForm.value.outVariables = node.childProcessSetting.outVariables
|
||||
// 6. 发起人设置
|
||||
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 ?? ''
|
||||
// 7. 超时设置
|
||||
configForm.value.timeoutEnable = node.childProcessSetting.timeoutSetting.enable ?? false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
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) {
|
||||
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) {
|
||||
return '其它情况'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@
|
|||
}
|
||||
|
||||
.router {
|
||||
color: #ca3a31
|
||||
color: #ca3a31;
|
||||
}
|
||||
|
||||
.transactor {
|
||||
|
|
@ -303,7 +303,7 @@
|
|||
}
|
||||
|
||||
&.router-node {
|
||||
color: #ca3a31
|
||||
color: #ca3a31;
|
||||
}
|
||||
|
||||
&.transactor-task {
|
||||
|
|
@ -762,14 +762,15 @@
|
|||
|
||||
// iconfont 样式
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4495938 */
|
||||
src: url('iconfont.woff2?t=1737639517142') format('woff2'),
|
||||
url('iconfont.woff?t=1737639517142') format('woff'),
|
||||
url('iconfont.ttf?t=1737639517142') format('truetype');
|
||||
font-family: 'iconfont'; /* Project id 4495938 */
|
||||
src:
|
||||
url('iconfont.woff2?t=1737639517142') format('woff2'),
|
||||
url('iconfont.woff?t=1737639517142') format('woff'),
|
||||
url('iconfont.ttf?t=1737639517142') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-family: 'iconfont' !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
|
@ -777,49 +778,49 @@
|
|||
}
|
||||
|
||||
.icon-trigger:before {
|
||||
content: "\e6d3";
|
||||
content: '\e6d3';
|
||||
}
|
||||
|
||||
.icon-router:before {
|
||||
content: "\e6b2";
|
||||
content: '\e6b2';
|
||||
}
|
||||
|
||||
.icon-delay:before {
|
||||
content: "\e600";
|
||||
content: '\e600';
|
||||
}
|
||||
|
||||
.icon-start-user:before {
|
||||
content: "\e679";
|
||||
content: '\e679';
|
||||
}
|
||||
|
||||
.icon-inclusive:before {
|
||||
content: "\e602";
|
||||
content: '\e602';
|
||||
}
|
||||
|
||||
.icon-copy:before {
|
||||
content: "\e7eb";
|
||||
content: '\e7eb';
|
||||
}
|
||||
|
||||
.icon-transactor:before {
|
||||
content: "\e61c";
|
||||
content: '\e61c';
|
||||
}
|
||||
|
||||
.icon-exclusive:before {
|
||||
content: "\e717";
|
||||
content: '\e717';
|
||||
}
|
||||
|
||||
.icon-approve:before {
|
||||
content: "\e715";
|
||||
content: '\e715';
|
||||
}
|
||||
|
||||
.icon-parallel:before {
|
||||
content: "\e688";
|
||||
content: '\e688';
|
||||
}
|
||||
|
||||
.icon-async-child-process:before {
|
||||
content: "\e6f2";
|
||||
content: '\e6f2';
|
||||
}
|
||||
|
||||
.icon-child-process:before {
|
||||
content: "\e6c1";
|
||||
content: '\e6c1';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
* Verify 验证码组件
|
||||
* @description 分发验证码使用
|
||||
* */
|
||||
import {VerifyPictureWord, VerifyPoints, VerifySlide} from './Verify'
|
||||
import { VerifyPictureWord, VerifyPoints, VerifySlide } from './Verify'
|
||||
import { computed, ref, toRefs, watchEffect } from 'vue'
|
||||
|
||||
export default {
|
||||
|
|
@ -443,4 +443,4 @@ export default {
|
|||
content: ' ';
|
||||
inset: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ import VerifySlide from './VerifySlide.vue'
|
|||
import VerifyPoints from './VerifyPoints.vue'
|
||||
import VerifyPictureWord from './VerifyPictureWord.vue'
|
||||
|
||||
export { VerifySlide, VerifyPoints, VerifyPictureWord }
|
||||
export { VerifySlide, VerifyPoints, VerifyPictureWord }
|
||||
|
|
|
|||
|
|
@ -1254,11 +1254,11 @@
|
|||
"allowedIn": ["bpmn:StartEvent", "bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Integer",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,20 +23,20 @@
|
|||
|
||||
export default function customTranslate(translations) {
|
||||
return function (template, replacements) {
|
||||
replacements = replacements || {};
|
||||
replacements = replacements || {}
|
||||
// 将模板和翻译字典的键统一转换为小写进行匹配
|
||||
const lowerTemplate = template.toLowerCase();
|
||||
const translation = Object.keys(translations).find(key => key.toLowerCase() === lowerTemplate);
|
||||
const lowerTemplate = template.toLowerCase()
|
||||
const translation = Object.keys(translations).find((key) => key.toLowerCase() === lowerTemplate)
|
||||
|
||||
// 如果找到匹配的翻译,使用翻译后的模板
|
||||
if (translation) {
|
||||
template = translations[translation];
|
||||
template = translations[translation]
|
||||
}
|
||||
|
||||
// 替换模板中的占位符
|
||||
return template.replace(/{([^}]+)}/g, function (_, key) {
|
||||
// 如果替换值存在,返回替换值;否则返回原始占位符
|
||||
return replacements[key] !== undefined ? replacements[key] : `{${key}}`;
|
||||
});
|
||||
};
|
||||
}
|
||||
return replacements[key] !== undefined ? replacements[key] : `{${key}}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
@use './process-panel.scss';
|
||||
|
||||
$success-color: #4eb819;
|
||||
$primary-color: #409EFF;
|
||||
$danger-color: #F56C6C;
|
||||
$primary-color: #409eff;
|
||||
$danger-color: #f56c6c;
|
||||
$cancel-color: #909399;
|
||||
|
||||
.process-viewer {
|
||||
position: relative;
|
||||
border: 1px solid #EFEFEF;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
|
||||
border: 1px solid #efefef;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+')
|
||||
repeat !important;
|
||||
|
||||
.success-arrow {
|
||||
fill: $success-color;
|
||||
|
|
@ -23,7 +24,7 @@ $cancel-color: #909399;
|
|||
|
||||
.success.djs-connection {
|
||||
.djs-visual path {
|
||||
stroke: $success-color!important;
|
||||
stroke: $success-color !important;
|
||||
//marker-end: url(#sequenceflow-end-white-success)!important;
|
||||
}
|
||||
}
|
||||
|
|
@ -36,82 +37,84 @@ $cancel-color: #909399;
|
|||
|
||||
.success.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $success-color !important;
|
||||
fill: $success-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $success-color!important;
|
||||
stroke: $success-color !important;
|
||||
}
|
||||
|
||||
.djs-visual path:nth-child(2) {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
stroke: $success-color !important;
|
||||
fill: $success-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $success-color!important;
|
||||
fill: $success-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $success-color !important;
|
||||
fill: $success-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.primary.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $primary-color !important;
|
||||
fill: $primary-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $primary-color!important;
|
||||
stroke: $primary-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $primary-color!important;
|
||||
fill: $primary-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $primary-color !important;
|
||||
fill: $primary-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.danger.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $danger-color !important;
|
||||
fill: $danger-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $danger-color!important;
|
||||
stroke: $danger-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $danger-color!important;
|
||||
fill: $danger-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $danger-color !important;
|
||||
fill: $danger-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel.djs-shape {
|
||||
.djs-visual rect {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $cancel-color !important;
|
||||
fill: $cancel-color !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
|
||||
.djs-visual polygon {
|
||||
stroke: $cancel-color!important;
|
||||
stroke: $cancel-color !important;
|
||||
}
|
||||
|
||||
.djs-visual circle {
|
||||
stroke: $cancel-color!important;
|
||||
fill: $cancel-color!important;
|
||||
fill-opacity: 0.15!important;
|
||||
stroke: $cancel-color !important;
|
||||
fill: $cancel-color !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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,8 +167,8 @@ service.interceptors.response.use(
|
|||
cb()
|
||||
})
|
||||
requestList = []
|
||||
if ((config!.headers || {}).isEncrypt){
|
||||
(config!.headers || {}).isEncrypted = true
|
||||
if ((config!.headers || {}).isEncrypt) {
|
||||
;(config!.headers || {}).isEncrypted = true
|
||||
}
|
||||
return service(config)
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
|||
const id = domSymbol.toString()
|
||||
const appStore = useAppStore()
|
||||
let watermarkStr = ''
|
||||
|
||||
|
||||
const clear = () => {
|
||||
const domId = document.getElementById(id)
|
||||
if (domId) {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ onMounted(() => {
|
|||
watch(
|
||||
() => collapse.value,
|
||||
(collapse: boolean) => {
|
||||
if (getLayoutRenderMode(unref(layout)) === 'topLeft' || getLayoutRenderMode(unref(layout)) === 'cutMenu') {
|
||||
if (
|
||||
getLayoutRenderMode(unref(layout)) === 'topLeft' ||
|
||||
getLayoutRenderMode(unref(layout)) === 'cutMenu'
|
||||
) {
|
||||
show.value = true
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ import { hasOneShowingChild } from './helper'
|
|||
import { isUrl } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { createRouteLocation, resolveDynamicPath } from '@/utils/routeParams'
|
||||
import {
|
||||
isHeaderNavLayout,
|
||||
isHorizontalMenuLayout,
|
||||
isTwoColumnLayout
|
||||
} from '@/utils/layout'
|
||||
import { isHeaderNavLayout, isHorizontalMenuLayout, isTwoColumnLayout } from '@/utils/layout'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
import {
|
||||
|
|
@ -69,8 +65,9 @@ export default defineComponent({
|
|||
|
||||
const menuRef = ref<InstanceType<typeof ElMenu>>()
|
||||
|
||||
const menuMode = computed((): 'vertical' | 'horizontal' =>
|
||||
props.mode || (isHorizontalMenuLayout(unref(layout)) ? 'horizontal' : 'vertical')
|
||||
const menuMode = computed(
|
||||
(): 'vertical' | 'horizontal' =>
|
||||
props.mode || (isHorizontalMenuLayout(unref(layout)) ? 'horizontal' : 'vertical')
|
||||
)
|
||||
|
||||
const collapse = computed(() => appStore.getCollapse)
|
||||
|
|
@ -102,7 +99,8 @@ export default defineComponent({
|
|||
|
||||
const routers = computed(() => {
|
||||
const sourceRouters =
|
||||
props.menus || (props.split ? permissionStore.getMenuTabRouters : permissionStore.getRouters)
|
||||
props.menus ||
|
||||
(props.split ? permissionStore.getMenuTabRouters : permissionStore.getRouters)
|
||||
if (!props.rootOnly) {
|
||||
return sourceRouters
|
||||
}
|
||||
|
|
@ -113,7 +111,10 @@ export default defineComponent({
|
|||
const { meta, path } = unref(currentRoute)
|
||||
const currentPath = (meta.activeMenu as string) || path
|
||||
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 (meta.activeMenu) {
|
||||
|
|
@ -134,7 +135,10 @@ export default defineComponent({
|
|||
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)
|
||||
if (!firstChild) {
|
||||
return undefined
|
||||
|
|
@ -218,7 +222,8 @@ export default defineComponent({
|
|||
|
||||
const setSplitMenus = (targetPath: string) => {
|
||||
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 children = rootRoute?.children?.length
|
||||
? cloneDeep(rootRoute.children).map((child) => {
|
||||
|
|
@ -386,7 +391,8 @@ export default defineComponent({
|
|||
}
|
||||
const targetPath =
|
||||
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
|
||||
|
||||
if (targetPath) {
|
||||
|
|
@ -541,9 +547,13 @@ export default defineComponent({
|
|||
'h-[100%] overflow-hidden flex-col bg-[var(--left-menu-bg-color)]',
|
||||
{
|
||||
'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)]':
|
||||
!unref(collapse) && !isTwoColumnLayout(unref(layout)) && unref(menuMode) !== 'horizontal'
|
||||
!unref(collapse) &&
|
||||
!isTwoColumnLayout(unref(layout)) &&
|
||||
unref(menuMode) !== 'horizontal'
|
||||
}
|
||||
]}
|
||||
style={{
|
||||
|
|
@ -642,13 +652,12 @@ $prefix-cls: #{$namespace}-menu;
|
|||
height: calc(var(--top-tool-height)) !important;
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(.#{$elNamespace}-menu--horizontal) {
|
||||
height: calc(var(--top-tool-height));
|
||||
border-bottom: none;
|
||||
min-width: 100%;
|
||||
border-bottom: none;
|
||||
// 重新设置底部高亮颜色
|
||||
& > .#{$elNamespace}-sub-menu.is-active {
|
||||
.#{$elNamespace}-sub-menu__title {
|
||||
|
|
|
|||
|
|
@ -33,14 +33,12 @@ export const useRenderMenuItem = () =>
|
|||
|
||||
if (
|
||||
!children.length ||
|
||||
oneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||
!meta?.alwaysShow
|
||||
(oneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||
!meta?.alwaysShow)
|
||||
) {
|
||||
return (
|
||||
<ElMenuItem
|
||||
index={resolvedOnlyOneChildPath}
|
||||
>
|
||||
<ElMenuItem index={resolvedOnlyOneChildPath}>
|
||||
{{
|
||||
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -58,9 +58,9 @@ export const getRootMenuRoute = (
|
|||
}
|
||||
}
|
||||
|
||||
export const getRootMenuActivePath = (
|
||||
routes: AppRouteRecordRaw[],
|
||||
targetPath: string
|
||||
): string => {
|
||||
return getRootMenuRoute(routes, targetPath)?.fullPath || getRootPath(normalizeMenuTargetPath(targetPath))
|
||||
export const getRootMenuActivePath = (routes: AppRouteRecordRaw[], targetPath: string): string => {
|
||||
return (
|
||||
getRootMenuRoute(routes, targetPath)?.fullPath ||
|
||||
getRootPath(normalizeMenuTargetPath(targetPath))
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,7 @@ import { useDesign } from '@/hooks/web/useDesign'
|
|||
import { isUrl } from '@/utils/is'
|
||||
import { createRouteLocation } from '@/utils/routeParams'
|
||||
import { getRootPath, isHeaderMixedNavLayout, isTwoColumnLayout } from '@/utils/layout'
|
||||
import {
|
||||
getRootMenuRoute,
|
||||
normalizeMenuTargetPath
|
||||
} from '@/layout/components/Menu/src/menuRoute'
|
||||
import { getRootMenuRoute, normalizeMenuTargetPath } from '@/layout/components/Menu/src/menuRoute'
|
||||
|
||||
const { getPrefixCls, variables } = useDesign()
|
||||
|
||||
|
|
@ -58,7 +55,9 @@ export default defineComponent({
|
|||
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)
|
||||
|
||||
|
|
@ -125,7 +124,9 @@ export default defineComponent({
|
|||
const isSameMenuRouters = (left: AppRouteRecordRaw[], right: AppRouteRecordRaw[]): boolean => {
|
||||
return (
|
||||
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)
|
||||
if (children.length) {
|
||||
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)) {
|
||||
setExtraMenuRouters(buildExtraMenuRouters(children, unref(tabActive)))
|
||||
|
|
|
|||
|
|
@ -171,7 +171,8 @@ const moveToTarget = (currentTag: RouteLocationNormalizedLoaded) => {
|
|||
} else {
|
||||
// find preTag and nextTag
|
||||
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
|
||||
const tgsRefs = document.getElementsByClassName(`${prefixCls}__item`)
|
||||
|
|
@ -376,10 +377,10 @@ watch(
|
|||
<Icon
|
||||
v-if="
|
||||
tagsViewIcon &&
|
||||
(item?.meta?.icon ||
|
||||
(item?.matched &&
|
||||
item.matched[0] &&
|
||||
item.matched[item.matched.length - 1].meta?.icon))
|
||||
(item?.meta?.icon ||
|
||||
(item?.matched &&
|
||||
item.matched[0] &&
|
||||
item.matched[item.matched.length - 1].meta?.icon))
|
||||
"
|
||||
:icon="item?.meta?.icon || item.matched[item.matched.length - 1].meta.icon"
|
||||
:size="12"
|
||||
|
|
@ -387,7 +388,7 @@ watch(
|
|||
/>
|
||||
{{
|
||||
t(item?.meta?.title as string) +
|
||||
(item?.meta?.titleSuffix ? ` (${item?.meta?.titleSuffix})` : '')
|
||||
(item?.meta?.titleSuffix ? ` (${item?.meta?.titleSuffix})` : '')
|
||||
}}
|
||||
<Icon
|
||||
:class="`${prefixCls}__item--close`"
|
||||
|
|
|
|||
|
|
@ -94,7 +94,11 @@ export default defineComponent({
|
|||
) : undefined}
|
||||
<div class="h-full flex items-center">
|
||||
{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" />
|
||||
</div>
|
||||
{screenfull.value ? (
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ const handleLock = async () => {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
:global(.v-lock-dialog) {
|
||||
@media (max-width: 767px) {
|
||||
@media (width <= 767px) {
|
||||
max-width: calc(100vw - 16px);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ $error-color: #ed6f6f;
|
|||
font-size: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $screen-lg) {
|
||||
span:not(.meridiem) {
|
||||
font-size: 220px;
|
||||
|
|
@ -216,6 +217,7 @@ $error-color: #ed6f6f;
|
|||
font-size: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $screen-2xl) {
|
||||
span:not(.meridiem) {
|
||||
font-size: 320px;
|
||||
|
|
@ -230,7 +232,7 @@ $error-color: #ed6f6f;
|
|||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
backdrop-filter: blur(8px);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ import AppView from './AppView.vue'
|
|||
import ToolHeader from './ToolHeader.vue'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import {
|
||||
isHeaderMixedNavLayout,
|
||||
isMixedNavLayout,
|
||||
isTwoColumnLayout
|
||||
} from '@/utils/layout'
|
||||
import { isHeaderMixedNavLayout, isMixedNavLayout, isTwoColumnLayout } from '@/utils/layout'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
|
@ -251,7 +247,8 @@ export const useRenderLayout = () => {
|
|||
const renderCutMenu = () => {
|
||||
const showHeaderMenu = isHeaderMixedNavLayout(layout.value)
|
||||
const fixedTwoColumnMenu = fixedMenu.value || isTwoColumnLayout(layout.value)
|
||||
const showTwoColumnExtraMenu = fixedTwoColumnMenu && permissionStore.getMenuTabRouters.length > 0
|
||||
const showTwoColumnExtraMenu =
|
||||
fixedTwoColumnMenu && permissionStore.getMenuTabRouters.length > 0
|
||||
return (
|
||||
<>
|
||||
<div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom">
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ export const PREDEFINE_COLORS = [
|
|||
'#711f57'
|
||||
]
|
||||
|
||||
|
||||
/**
|
||||
* Mixes two colors.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -172,43 +172,43 @@ export const once = function (el: HTMLElement, event: string, fn: EventListener)
|
|||
export const getStyle =
|
||||
ieVersion < 9
|
||||
? function (element: Element | any, styleName: string) {
|
||||
if (isServer) return
|
||||
if (!element || !styleName) return null
|
||||
styleName = camelCase(styleName)
|
||||
if (styleName === 'float') {
|
||||
styleName = 'styleFloat'
|
||||
}
|
||||
try {
|
||||
switch (styleName) {
|
||||
case 'opacity':
|
||||
try {
|
||||
return element.filters.item('alpha').opacity / 100
|
||||
} catch (e) {
|
||||
return 1.0
|
||||
}
|
||||
default:
|
||||
return element.style[styleName] || element.currentStyle
|
||||
? element.currentStyle[styleName]
|
||||
: null
|
||||
if (isServer) return
|
||||
if (!element || !styleName) return null
|
||||
styleName = camelCase(styleName)
|
||||
if (styleName === 'float') {
|
||||
styleName = 'styleFloat'
|
||||
}
|
||||
try {
|
||||
switch (styleName) {
|
||||
case 'opacity':
|
||||
try {
|
||||
return element.filters.item('alpha').opacity / 100
|
||||
} catch (e) {
|
||||
return 1.0
|
||||
}
|
||||
default:
|
||||
return element.style[styleName] || element.currentStyle
|
||||
? element.currentStyle[styleName]
|
||||
: null
|
||||
}
|
||||
} catch (e) {
|
||||
return element.style[styleName]
|
||||
}
|
||||
} catch (e) {
|
||||
return element.style[styleName]
|
||||
}
|
||||
}
|
||||
: function (element: Element | any, styleName: string) {
|
||||
if (isServer) return
|
||||
if (!element || !styleName) return null
|
||||
styleName = camelCase(styleName)
|
||||
if (styleName === 'float') {
|
||||
styleName = 'cssFloat'
|
||||
if (isServer) return
|
||||
if (!element || !styleName) return null
|
||||
styleName = camelCase(styleName)
|
||||
if (styleName === 'float') {
|
||||
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 */
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,9 @@ export function formatPast2(ms: number): string {
|
|||
*/
|
||||
export function formatSeconds(seconds: number): string {
|
||||
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')
|
||||
return `${mm}:${ss}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,10 +215,10 @@ export const generateUUID = () => {
|
|||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
let random = Math.random() * 16
|
||||
if (timestamp > 0) {
|
||||
random = (timestamp + random) % 16 | 0
|
||||
random = ((timestamp + random) % 16) | 0
|
||||
timestamp = Math.floor(timestamp / 16)
|
||||
} else {
|
||||
random = (performanceNow + random) % 16 | 0
|
||||
random = ((performanceNow + random) % 16) | 0
|
||||
performanceNow = Math.floor(performanceNow / 16)
|
||||
}
|
||||
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export const isClient = !isServer
|
|||
export const isUrl = (path: string): boolean => {
|
||||
// fix:修复hash路由无法跳转的问题
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ export const isHeaderNavLayout = (layout?: LayoutType | string | null): boolean
|
|||
|
||||
export const isHorizontalMenuLayout = (layout?: LayoutType | string | null): boolean => {
|
||||
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 => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import {hasPermission} from "@/directives/permission/hasPermi";
|
||||
|
||||
import { hasPermission } from '@/directives/permission/hasPermi'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ export const parseQueryString = (queryString = ''): Record<string, any> => {
|
|||
return qs.parse(queryString.replace(/^\?/, '')) as Record<string, any>
|
||||
}
|
||||
|
||||
export const splitRoutePath = (
|
||||
rawPath: string | null | undefined
|
||||
): ParsedRouteLocation => {
|
||||
export const splitRoutePath = (rawPath: string | null | undefined): ParsedRouteLocation => {
|
||||
if (!rawPath) {
|
||||
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 => {
|
||||
if (Array.isArray(value)) {
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ const iconHouse = useIcon({ icon: 'ep:house' })
|
|||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||
const formLogin = ref()
|
||||
const {validForm} = useFormValid(formLogin)
|
||||
const { validForm } = useFormValid(formLogin)
|
||||
const { handleBackLogin, getLoginState } = useLoginState()
|
||||
const { currentRoute, push } = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
|
|
|||
|
|
@ -6,4 +6,12 @@ import QrCodeForm from './QrCodeForm.vue'
|
|||
import SSOLoginVue from './SSOLogin.vue'
|
||||
import ForgetPasswordForm from './ForgetPasswordForm.vue'
|
||||
|
||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue, ForgetPasswordForm }
|
||||
export {
|
||||
LoginForm,
|
||||
MobileForm,
|
||||
LoginFormTitle,
|
||||
RegisterForm,
|
||||
QrCodeForm,
|
||||
SSOLoginVue,
|
||||
ForgetPasswordForm
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<el-card
|
||||
body-class=""
|
||||
class="!w-80 !h-auto !rounded-10px !relative !flex !flex-col"
|
||||
>
|
||||
<el-card body-class="" class="!w-80 !h-auto !rounded-10px !relative !flex !flex-col">
|
||||
<div class="!flex !flex-row !justify-between">
|
||||
<div>
|
||||
<el-button type="primary" text bg v-if="detail?.status === AiImageStatusEnum.IN_PROGRESS">
|
||||
|
|
|
|||
|
|
@ -37,7 +37,11 @@
|
|||
</div>
|
||||
<el-space wrap class="mt-15px">
|
||||
<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"
|
||||
:key="model.key"
|
||||
>
|
||||
|
|
@ -52,7 +56,11 @@
|
|||
</div>
|
||||
<el-space wrap class="mt-15px">
|
||||
<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"
|
||||
:key="imageStyle.key"
|
||||
>
|
||||
|
|
@ -73,7 +81,11 @@
|
|||
@click="handleSizeClick(imageSize)"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -229,4 +241,3 @@ const settingValues = async (detail: ImageVO) => {
|
|||
/** 暴露组件方法 */
|
||||
defineExpose({ settingValues })
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,11 @@
|
|||
@click="handleSizeClick(imageSize)"
|
||||
>
|
||||
<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>
|
||||
|
|
@ -57,7 +61,11 @@
|
|||
</div>
|
||||
<el-space wrap class="mt-15px">
|
||||
<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"
|
||||
:key="model.key"
|
||||
>
|
||||
|
|
@ -71,12 +79,7 @@
|
|||
<el-text tag="b">版本</el-text>
|
||||
</div>
|
||||
<el-space wrap class="mt-20px w-full">
|
||||
<el-select
|
||||
v-model="selectVersion"
|
||||
class="!w-350px"
|
||||
clearable
|
||||
placeholder="请选择版本"
|
||||
>
|
||||
<el-select v-model="selectVersion" class="!w-350px" clearable placeholder="请选择版本">
|
||||
<el-option
|
||||
v-for="item in versionList"
|
||||
:key="item.value"
|
||||
|
|
@ -233,4 +236,3 @@ const settingValues = async (detail: ImageVO) => {
|
|||
/** 暴露组件方法 */
|
||||
defineExpose({ settingValues })
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -254,4 +254,3 @@ const settingValues = async (detail: ImageVO) => {
|
|||
/** 暴露组件方法 */
|
||||
defineExpose({ settingValues })
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,19 @@
|
|||
:suffix-icon="Search"
|
||||
@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 界面一致?(只有卡片,没有操作);因为看着更有相框的感觉~~~ -->
|
||||
<div v-for="item in list" :key="item.id" class="relative overflow-hidden bg-gray-100 cursor-pointer transition-transform duration-300 hover:scale-105">
|
||||
<img :src="item.picUrl" class="w-full h-auto block transition-transform duration-300 hover:scale-110" />
|
||||
<div
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="relative overflow-hidden bg-gray-100 cursor-pointer transition-transform duration-300 hover:scale-105"
|
||||
>
|
||||
<img
|
||||
:src="item.picUrl"
|
||||
class="w-full h-auto block transition-transform duration-300 hover:scale-110"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO @fan:缺少翻页 -->
|
||||
|
|
@ -64,4 +73,3 @@ onMounted(async () => {
|
|||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,20 @@
|
|||
</el-radio>
|
||||
</el-radio-group>
|
||||
</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-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
|
||||
</el-select>
|
||||
</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
|
||||
v-model="modelData.formCustomCreatePath"
|
||||
placeholder="请输入表单提交路由"
|
||||
|
|
@ -31,7 +39,11 @@
|
|||
<Icon icon="ep:question" class="ml-5px" />
|
||||
</el-tooltip>
|
||||
</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
|
||||
v-model="modelData.formCustomViewPath"
|
||||
placeholder="请输入表单查看的组件地址"
|
||||
|
|
@ -48,7 +60,11 @@
|
|||
</el-form-item>
|
||||
<!-- 表单预览 -->
|
||||
<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"
|
||||
>
|
||||
<div class="flex items-center mb-15px">
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ onBeforeUnmount(() => {
|
|||
/>
|
||||
</div>
|
||||
<!-- TODO @unocss 简化 style -->
|
||||
<div style=" margin: 10px;border: 1px solid #ccc">
|
||||
<div style="margin: 10px; border: 1px solid #ccc">
|
||||
<Toolbar
|
||||
style="border-bottom: 1px solid #ccc"
|
||||
:editor="editorRef"
|
||||
|
|
@ -106,7 +106,7 @@ onBeforeUnmount(() => {
|
|||
@insert-mention="insertMention"
|
||||
/>
|
||||
</div>
|
||||
<div style=" float: right;margin-right: 10px">
|
||||
<div style="float: right; margin-right: 10px">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确 定</el-button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,11 +15,10 @@ function parseHtml(
|
|||
): SlateElement {
|
||||
// TS 语法
|
||||
|
||||
|
||||
// 生成“流程记录”元素(按照此前约定的数据结构)
|
||||
const processRecord = {
|
||||
type: 'process-record',
|
||||
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!!
|
||||
children: [{ text: '' }] // void node 必须有 children ,其中有一个空字符串,重要!!!
|
||||
}
|
||||
|
||||
return processRecord
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ onActivated(() => {
|
|||
.el-form--inline .el-form-item {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ const submitForm = async () => {
|
|||
if (!fApi.value || !props.selectProcessDefinition) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 流程表单校验
|
||||
await fApi.value.validate()
|
||||
|
|
|
|||
|
|
@ -252,11 +252,7 @@ const flattenAreaTree = (list: AreaNode[] = [], map: Map<string, string> = new M
|
|||
return map
|
||||
}
|
||||
|
||||
const mapValueWithLabelMap = (
|
||||
value: unknown,
|
||||
labelMap: Map<string, string>,
|
||||
separator = ', '
|
||||
) => {
|
||||
const mapValueWithLabelMap = (value: unknown, labelMap: Map<string, string>, separator = ', ') => {
|
||||
const values = toValueArray(value)
|
||||
const labels = values
|
||||
.map((item) => escapeHtml(labelMap.get(String(item)) ?? String(item)))
|
||||
|
|
@ -300,11 +296,7 @@ const loadPrintLookupMaps = async (formFieldsObj: FormFieldRule[]) => {
|
|||
} satisfies PrintLookupMaps
|
||||
}
|
||||
|
||||
const formatPrintField = (
|
||||
rule: FormFieldRule,
|
||||
value: unknown,
|
||||
lookupMaps: PrintLookupMaps
|
||||
) => {
|
||||
const formatPrintField = (rule: FormFieldRule, value: unknown, lookupMaps: PrintLookupMaps) => {
|
||||
const type = String(rule.type ?? '')
|
||||
|
||||
switch (type) {
|
||||
|
|
@ -356,7 +348,9 @@ const formatPrintField = (
|
|||
return renderFileListHtml(value)
|
||||
case 'IframeComponent': {
|
||||
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 ?? '')
|
||||
return iframeUrl ? createFileLinkHtml(iframeUrl) : ''
|
||||
}
|
||||
|
|
@ -395,10 +389,10 @@ const initPrintDataMap = () => {
|
|||
printDataMap.value['processNum'] = String(printData.value.processInstance.id ?? '')
|
||||
printDataMap.value['startTime'] = formatDate(printData.value.processInstance.startTime)
|
||||
printDataMap.value['endTime'] = formatDate(printData.value.processInstance.endTime)
|
||||
printDataMap.value['processStatus'] = String(getDictLabel(
|
||||
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||
printData.value.processInstance.status
|
||||
) ?? '')
|
||||
printDataMap.value['processStatus'] = String(
|
||||
getDictLabel(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, printData.value.processInstance.status) ??
|
||||
''
|
||||
)
|
||||
printDataMap.value['printUser'] = userName.value
|
||||
printDataMap.value['printTime'] = printTime.value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,12 @@
|
|||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
</template>
|
||||
</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="操作" fixed="right" width="80">
|
||||
<template #default="scope">
|
||||
|
|
|
|||
|
|
@ -77,8 +77,7 @@ const getList = async () => {
|
|||
const currentUserId = getCurrentUserId()
|
||||
const permission = list.value.find(
|
||||
(item) =>
|
||||
item.userId === currentUserId &&
|
||||
item.level === PermissionApi.PermissionLevelEnum.OWNER
|
||||
item.userId === currentUserId && item.level === PermissionApi.PermissionLevelEnum.OWNER
|
||||
)
|
||||
if (permission) {
|
||||
formData.value.ownerUserId = currentUserId
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
class="!w-240px"
|
||||
node-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
@change="(queryParams.userId = undefined), handleQuery()"
|
||||
@change="((queryParams.userId = undefined), handleQuery())"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="员工" prop="userId">
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
class="!w-240px"
|
||||
node-key="id"
|
||||
placeholder="请选择归属部门"
|
||||
@change="(queryParams.userId = undefined), handleQuery()"
|
||||
@change="((queryParams.userId = undefined), handleQuery())"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="员工" prop="userId">
|
||||
|
|
|
|||
|
|
@ -11,10 +11,7 @@
|
|||
:index="idx"
|
||||
:key="resolveItemKey(item, idx)"
|
||||
></slot>
|
||||
<div
|
||||
v-if="showFooter"
|
||||
class="py-3 text-xs text-center text-[var(--el-text-color-secondary)]"
|
||||
>
|
||||
<div v-if="showFooter" class="py-3 text-xs text-center text-[var(--el-text-color-secondary)]">
|
||||
已到底部
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
title="拖拽调整宽度"
|
||||
@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>
|
||||
</aside>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -42,11 +42,7 @@ import { computed } from 'vue'
|
|||
|
||||
import UserAvatar from '../user/UserAvatar.vue'
|
||||
import { isPrivateConversation } from '@/views/im/utils/constants'
|
||||
import {
|
||||
getCardLabelInfo,
|
||||
type CardMessage,
|
||||
type CardTarget
|
||||
} from '@/views/im/utils/message'
|
||||
import { getCardLabelInfo, type CardMessage, type CardTarget } from '@/views/im/utils/message'
|
||||
|
||||
defineOptions({ name: 'ImCardBubble' })
|
||||
|
||||
|
|
|
|||
|
|
@ -168,9 +168,7 @@ const message = useMessage()
|
|||
const currentUserId = computed(() => getCurrentUserId())
|
||||
|
||||
/** 搜索结果过滤掉自己;用 v-if 而非 v-show,避免 DOM 占位 + 头像无效请求 */
|
||||
const visibleUsers = computed(() =>
|
||||
users.value.filter((user) => user.id !== currentUserId.value)
|
||||
)
|
||||
const visibleUsers = computed(() => users.value.filter((user) => user.id !== currentUserId.value))
|
||||
const keyword = ref('')
|
||||
const users = ref<UserVO[]>([])
|
||||
const searched = ref(false)
|
||||
|
|
|
|||
|
|
@ -17,11 +17,7 @@ import { computed, ref, watch } from 'vue'
|
|||
import UserAvatar from '../user/UserAvatar.vue'
|
||||
import { useFriendStore } from '../../store/friendStore'
|
||||
import { useGroupStore } from '../../store/groupStore'
|
||||
import {
|
||||
buildGroupAvatar,
|
||||
getCachedGroupAvatar,
|
||||
setCachedGroupAvatar
|
||||
} from '../../../utils/group'
|
||||
import { buildGroupAvatar, getCachedGroupAvatar, setCachedGroupAvatar } from '../../../utils/group'
|
||||
import { getMemberDisplayName } from '../../../utils/user'
|
||||
import type { GroupMember } from '../../types'
|
||||
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@ onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
|
|||
function handleChat(group: GroupLite) {
|
||||
const cached = groupStore.getGroup(group.id)
|
||||
// cached 命中走 getGroupDisplayName 让群备注优先(与 contact / 会话列表的展示名一致);缺 cached 时回落 showGroupName / 原群名
|
||||
const displayName = cached
|
||||
? getGroupDisplayName(cached)
|
||||
: group.showGroupName || group.name || ''
|
||||
const displayName = cached ? getGroupDisplayName(cached) : group.showGroupName || group.name || ''
|
||||
// 打开或新建会话
|
||||
conversationStore.openConversation(
|
||||
group.id,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,9 @@
|
|||
<el-dialog v-model="visible" title="设置禁言" width="560px" :close-on-click-modal="false">
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- 成员信息卡:和 FriendAddDialog 的 user 卡保持一致的浅色背景 -->
|
||||
<div
|
||||
class="flex items-center gap-2 px-3 py-2.5 rounded-md bg-[var(--el-fill-color-light)]"
|
||||
>
|
||||
<div 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-sm font-medium text-[var(--el-text-color-primary)] truncate"
|
||||
>
|
||||
<span class="text-sm font-medium text-[var(--el-text-color-primary)] truncate">
|
||||
{{ memberName }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -313,22 +313,23 @@ function updateLocalResult(id: number, handleResult: number) {
|
|||
<style scoped>
|
||||
/* 自绘按钮:贴近微信小药丸样式;与 :disabled、:hover:not(:disabled) 等伪类叠加 modifier 类的组合选择器写在 class 里成本高,留 SCSS */
|
||||
.im-group-request-list__btn {
|
||||
flex-shrink: 0;
|
||||
min-width: 56px;
|
||||
height: 28px;
|
||||
min-width: 56px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
transition:
|
||||
background-color 0.15s,
|
||||
border-color 0.15s,
|
||||
color 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.im-group-request-list__btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.im-group-request-list__btn--primary {
|
||||
|
|
@ -336,6 +337,7 @@ function updateLocalResult(id: number, handleResult: number) {
|
|||
background-color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.im-group-request-list__btn--primary:hover:not(:disabled) {
|
||||
background-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);
|
||||
border-color: var(--el-border-color);
|
||||
}
|
||||
|
||||
.im-group-request-list__btn--ghost:hover:not(:disabled) {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
|
|
|
|||
|
|
@ -363,6 +363,7 @@ function handleToggle(conversation: Conversation) {
|
|||
.im-conversation-picker__recent::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.im-conversation-picker__recent::-webkit-scrollbar-thumb {
|
||||
background-color: var(--el-border-color);
|
||||
border-radius: 2px;
|
||||
|
|
|
|||
|
|
@ -181,9 +181,7 @@ const disabledSet = computed(() => new Set(props.disabledIds))
|
|||
const selectedSet = computed(() => new Set(props.selectedIds))
|
||||
|
||||
/** 候选好友:剔除 hideIds(hide 优先级最高) */
|
||||
const candidates = computed(() =>
|
||||
props.friends.filter((friend) => !hideSet.value.has(friend.id))
|
||||
)
|
||||
const candidates = computed(() => props.friends.filter((friend) => !hideSet.value.has(friend.id)))
|
||||
|
||||
/** 委托 useFriendBuckets:搜索规则复用,左侧列表按滚动分页渲染 */
|
||||
const { filtered } = useFriendBuckets(candidates, keyword)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<PagedScroller :items="shownMembers" :page-size="30" item-key="userId">
|
||||
<template #default="{ item }">
|
||||
<GroupMemberItem
|
||||
:member="(item as GroupMemberLite)"
|
||||
:member="item as GroupMemberLite"
|
||||
:height="46"
|
||||
@click="handleToggle(item as GroupMemberLite)"
|
||||
>
|
||||
|
|
@ -86,11 +86,7 @@
|
|||
|
||||
<!-- grid 形态:宫格预览;非 locked 成员右上角叠加 × 移除(locked 不渲染) -->
|
||||
<div v-else class="flex flex-wrap p-2.5">
|
||||
<GroupMemberGrid
|
||||
v-for="member in selectedMembers"
|
||||
:key="member.userId"
|
||||
:member="member"
|
||||
>
|
||||
<GroupMemberGrid v-for="member in selectedMembers" :key="member.userId" :member="member">
|
||||
<Icon
|
||||
v-if="!isLocked(member)"
|
||||
icon="ant-design:close-circle-filled"
|
||||
|
|
@ -240,4 +236,3 @@ function handleToggle(member: GroupMemberLite) {
|
|||
emit('update:selectedIds', next)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -71,11 +71,7 @@ import {
|
|||
inviteCall,
|
||||
noAnswerCallCheck
|
||||
} from '@/api/im/rtc'
|
||||
import {
|
||||
ImRtcCallMediaType,
|
||||
ImRtcCallStage,
|
||||
ImConversationType
|
||||
} from '@/views/im/utils/constants'
|
||||
import { ImRtcCallMediaType, ImRtcCallStage, ImConversationType } from '@/views/im/utils/constants'
|
||||
import { RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS } from '@/views/im/utils/config'
|
||||
import { getCurrentUserId } from '@/utils/auth'
|
||||
import { getSenderAvatar, getSenderDisplayName } from '@/views/im/utils/user'
|
||||
|
|
@ -104,17 +100,15 @@ const hangingUp = ref(false)
|
|||
/** 当前是否视频通话 */
|
||||
const isVideo = computed(() => {
|
||||
const t =
|
||||
rtcStore.call?.mediaType ||
|
||||
rtcStore.incomingPayload?.mediaType ||
|
||||
ImRtcCallMediaType.VOICE
|
||||
rtcStore.call?.mediaType || rtcStore.incomingPayload?.mediaType || ImRtcCallMediaType.VOICE
|
||||
return t === ImRtcCallMediaType.VIDEO
|
||||
})
|
||||
|
||||
/** 当前是否群通话;决定浮动窗大小 */
|
||||
const isGroup = computed(
|
||||
() =>
|
||||
(rtcStore.call?.conversationType ??
|
||||
rtcStore.incomingPayload?.conversationType) === ImConversationType.GROUP
|
||||
(rtcStore.call?.conversationType ?? rtcStore.incomingPayload?.conversationType) ===
|
||||
ImConversationType.GROUP
|
||||
)
|
||||
|
||||
/** 初始摄像头是否打开;群通话默认全部关闭,进入后用户主动开 */
|
||||
|
|
@ -261,11 +255,7 @@ function maybeEnterRunning() {
|
|||
watch(
|
||||
() => rtcStore.stage,
|
||||
async (stage) => {
|
||||
if (
|
||||
stage === ImRtcCallStage.INVITING &&
|
||||
rtcStore.call?.token &&
|
||||
rtcStore.call?.livekitUrl
|
||||
) {
|
||||
if (stage === ImRtcCallStage.INVITING && rtcStore.call?.token && rtcStore.call?.livekitUrl) {
|
||||
try {
|
||||
await connectLiveKit(rtcStore.call.livekitUrl, rtcStore.call.token)
|
||||
} catch (e) {
|
||||
|
|
@ -417,7 +407,9 @@ function handlePeerDisconnected() {
|
|||
|
||||
/** 通话存活期间(INVITING / INCOMING / RUNNING)周期性触发后端扫该 room 的超时 INVITING;保持 timer 是为了 inviteCall 追加新人后也能覆盖;阈值由后端配置决定,前端只负责 trigger */
|
||||
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(
|
||||
() => rtcStore.isActive,
|
||||
|
|
|
|||
|
|
@ -97,12 +97,14 @@ const audioRef = useMediaStreamElement<HTMLAudioElement>(() => props.participant
|
|||
.tile-dot {
|
||||
animation: tile-dot 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
@keyframes tile-dot {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
40% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,9 +85,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 底部操作区:麦克风 / 扬声器 / 摄像头 / (群聊:共享屏幕 / 添加成员) / 挂断 -->
|
||||
<div
|
||||
class="flex flex-shrink-0 gap-3 justify-around items-center pt-4 px-4 pb-5 bg-black/20"
|
||||
>
|
||||
<div class="flex flex-shrink-0 gap-3 justify-around items-center pt-4 px-4 pb-5 bg-black/20">
|
||||
<div
|
||||
class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
|
||||
@click="$emit('toggle-mic')"
|
||||
|
|
@ -164,7 +162,9 @@
|
|||
class="flex flex-col gap-2 items-center cursor-pointer select-none min-w-[64px]"
|
||||
@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" />
|
||||
</span>
|
||||
<span class="text-xs text-white/70 whitespace-nowrap">添加成员</span>
|
||||
|
|
@ -175,7 +175,9 @@
|
|||
:class="{ 'opacity-60 pointer-events-none': hangingUp }"
|
||||
@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]" />
|
||||
</span>
|
||||
<span class="text-xs text-white/70 whitespace-nowrap">挂断</span>
|
||||
|
|
@ -277,11 +279,13 @@ const formattedDuration = computed(() =>
|
|||
.reconnect-dot {
|
||||
animation: reconnect-pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes reconnect-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,7 +231,9 @@ async function handleSend() {
|
|||
const results = await Promise.all(tasks)
|
||||
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) {
|
||||
message.success('已转发')
|
||||
} else if (failedNames.length === targets.length) {
|
||||
|
|
|
|||
|
|
@ -101,9 +101,7 @@ function handleSendMessage() {
|
|||
}
|
||||
// 取 friendStore 里的最新备注 / 免打扰,避免新建会话用过期数据
|
||||
const friend = friendStore.getFriend(user.value.id)
|
||||
const conversationName = friend
|
||||
? getFriendDisplayName(friend)
|
||||
: user.value.nickname || ''
|
||||
const conversationName = friend ? getFriendDisplayName(friend) : user.value.nickname || ''
|
||||
conversationStore.openConversation(
|
||||
user.value.id,
|
||||
ImConversationType.PRIVATE,
|
||||
|
|
|
|||
|
|
@ -81,12 +81,12 @@ export function useLiveKitRoom() {
|
|||
_room.value = r
|
||||
|
||||
r.on(RoomEvent.ParticipantConnected, (rp) => {
|
||||
syncRemotes(r)
|
||||
const userId = parseUserId(rp.identity)
|
||||
if (userId != null) {
|
||||
participantConnectedHandlers.forEach((cb) => cb(userId))
|
||||
}
|
||||
})
|
||||
syncRemotes(r)
|
||||
const userId = parseUserId(rp.identity)
|
||||
if (userId != null) {
|
||||
participantConnectedHandlers.forEach((cb) => cb(userId))
|
||||
}
|
||||
})
|
||||
.on(RoomEvent.ParticipantDisconnected, (rp) => {
|
||||
syncRemotes(r)
|
||||
// 离开的参与者缓存清掉,避免下次同 sid 重连命中失效引用
|
||||
|
|
|
|||
|
|
@ -198,15 +198,17 @@ export const useMediaUploader = () => {
|
|||
uploadProgress: 0,
|
||||
_localFile: opts.file
|
||||
}
|
||||
void messageStore.insertMessage(
|
||||
{
|
||||
type: conversation.type,
|
||||
targetId: conversation.targetId,
|
||||
name: conversation.name || String(conversation.targetId),
|
||||
avatar: conversation.avatar || ''
|
||||
},
|
||||
placeholder
|
||||
).catch(() => undefined)
|
||||
void messageStore
|
||||
.insertMessage(
|
||||
{
|
||||
type: conversation.type,
|
||||
targetId: conversation.targetId,
|
||||
name: conversation.name || String(conversation.targetId),
|
||||
avatar: conversation.avatar || ''
|
||||
},
|
||||
placeholder
|
||||
)
|
||||
.catch(() => undefined)
|
||||
return { clientMessageId, blobUrl }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,14 +113,10 @@ const currentUserId = computed(() => getCurrentUserId())
|
|||
const iSentIt = computed(() => props.request.fromUserId === currentUserId.value)
|
||||
|
||||
/** 是否「已拒绝」态:模板里多处用到,computed 一次省得到处写枚举比对 */
|
||||
const refused = computed(
|
||||
() => props.request.handleResult === ImFriendRequestHandleResult.REFUSED
|
||||
)
|
||||
const refused = computed(() => props.request.handleResult === ImFriendRequestHandleResult.REFUSED)
|
||||
|
||||
/** 是否「已通过」态:转走 UserInfo 好友详情入口 */
|
||||
const agreed = computed(
|
||||
() => props.request.handleResult === ImFriendRequestHandleResult.AGREED
|
||||
)
|
||||
const agreed = computed(() => props.request.handleResult === ImFriendRequestHandleResult.AGREED)
|
||||
|
||||
/** 对端的用户编号 / 昵称 / 头像 */
|
||||
const peerUserId = computed(() =>
|
||||
|
|
|
|||
|
|
@ -39,10 +39,15 @@
|
|||
title="邀请好友入群"
|
||||
@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" />
|
||||
</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>
|
||||
|
||||
<!-- 移出(群主或管理员;管理员只能移出普通成员,由后端校验) -->
|
||||
|
|
@ -52,10 +57,15 @@
|
|||
title="移出群成员"
|
||||
@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" />
|
||||
</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>
|
||||
|
||||
|
|
@ -87,8 +97,13 @@
|
|||
<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)]"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">群聊名称</span>
|
||||
<span class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate">{{ group.name }}</span>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>群聊名称</span
|
||||
>
|
||||
<span
|
||||
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate"
|
||||
>{{ group.name }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col gap-2">
|
||||
|
|
@ -103,8 +118,13 @@
|
|||
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"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">群聊名称</span>
|
||||
<span class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate">{{ group.name }}</span>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>群聊名称</span
|
||||
>
|
||||
<span
|
||||
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate"
|
||||
>{{ group.name }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 群公告(群主可改):内容可能很长,加 > 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)]"
|
||||
>
|
||||
<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="ant-design:right-outlined"
|
||||
:size="11"
|
||||
|
|
@ -133,7 +155,9 @@
|
|||
>
|
||||
{{ group.notice }}
|
||||
</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>
|
||||
</template>
|
||||
<div class="flex flex-col gap-2">
|
||||
|
|
@ -162,7 +186,9 @@
|
|||
>
|
||||
{{ group.notice }}
|
||||
</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>
|
||||
|
||||
<!-- 备注(仅自己可见;保存后会替换会话列表 / 顶部群名展示);历史退群群隐藏:改备注走 updateGroupMember,已退群会被后端拒 -->
|
||||
|
|
@ -177,14 +203,19 @@
|
|||
<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)]"
|
||||
>
|
||||
<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
|
||||
v-if="group.groupRemark"
|
||||
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] line-clamp-2"
|
||||
>
|
||||
{{ group.groupRemark }}
|
||||
</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>
|
||||
</div>
|
||||
|
|
@ -217,14 +248,18 @@
|
|||
<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)]"
|
||||
>
|
||||
<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
|
||||
v-if="group.remarkNickName"
|
||||
class="text-13px text-[var(--el-text-color-regular)] break-all leading-[1.6] truncate"
|
||||
>
|
||||
{{ group.remarkNickName }}
|
||||
</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>
|
||||
</template>
|
||||
<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)]"
|
||||
@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="ant-design:right-outlined"
|
||||
: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)]"
|
||||
@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="ant-design:right-outlined"
|
||||
:size="11"
|
||||
|
|
@ -275,17 +314,30 @@
|
|||
|
||||
<!-- ==================== 开关项 ==================== -->
|
||||
<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">
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">消息免打扰</span>
|
||||
<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"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>消息免打扰</span
|
||||
>
|
||||
<el-switch :model-value="!!conversation?.silent" @change="onMutedChange" />
|
||||
</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">
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">置顶聊天</span>
|
||||
<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"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>置顶聊天</span
|
||||
>
|
||||
<el-switch :model-value="!!conversation?.top" @change="onTopChange" />
|
||||
</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">
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">全群禁言</span>
|
||||
<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"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>全群禁言</span
|
||||
>
|
||||
<el-switch :model-value="!!currentMutedAll" @change="onMuteAllChange" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -296,8 +348,13 @@
|
|||
<div class="flex-shrink-0 h-[10px]"></div>
|
||||
<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">
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">进群需要群主 / 群管理确认</span>
|
||||
<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"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>进群需要群主 / 群管理确认</span
|
||||
>
|
||||
<el-switch :model-value="!!group.joinApproval" @change="handleJoinApprovalChange" />
|
||||
</div>
|
||||
<!-- 进群申请子项:仅当开启审批 + 当前用户是 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)]"
|
||||
@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="ant-design:right-outlined"
|
||||
: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)]"
|
||||
@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="ant-design:right-outlined"
|
||||
: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)]"
|
||||
@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="ant-design:right-outlined"
|
||||
:size="11"
|
||||
|
|
@ -363,13 +426,7 @@
|
|||
解散群聊
|
||||
</el-button>
|
||||
<!-- 非群主:退出群聊 -->
|
||||
<el-button
|
||||
v-else
|
||||
class="w-full !h-9 text-14px"
|
||||
type="danger"
|
||||
plain
|
||||
@click="handleQuit"
|
||||
>
|
||||
<el-button v-else class="w-full !h-9 text-14px" type="danger" plain @click="handleQuit">
|
||||
退出群聊
|
||||
</el-button>
|
||||
</div>
|
||||
|
|
@ -399,11 +456,7 @@ import { useMessage } from '@/hooks/web/useMessage'
|
|||
|
||||
import { getCurrentUserId } from '@/utils/auth'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import {
|
||||
updateGroup,
|
||||
muteAll,
|
||||
dissolveGroup
|
||||
} from '@/api/im/group'
|
||||
import { updateGroup, muteAll, dissolveGroup } from '@/api/im/group'
|
||||
import { quitGroup, updateGroupMember } from '@/api/im/group/member'
|
||||
import { useConversationStore } from '../../../../store/conversationStore'
|
||||
import { useGroupStore } from '../../../../store/groupStore'
|
||||
|
|
@ -644,7 +697,11 @@ function onTopChange(value: boolean | string | number) {
|
|||
if (!props.conversation) {
|
||||
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 联动无法用单元素工具类表达 */
|
||||
.im-conversation-group-side__tile-wrap:hover .im-conversation-group-side__icon-tile {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
/* :deep 穿透 Icon 内部 svg; el-icon 全局 color 在暗色模式下被主题盖过,锁 fill 到当前色 */
|
||||
.im-conversation-group-side__icon-tile :deep(svg) {
|
||||
fill: currentColor !important;
|
||||
fill: currentcolor !important;
|
||||
}
|
||||
|
||||
/* 相邻信息行加分隔线; 相邻兄弟选择器无法用工具类表达 */
|
||||
|
|
|
|||
|
|
@ -232,7 +232,8 @@ const requestText = computed(() => {
|
|||
if (!isGroup.value) {
|
||||
return ''
|
||||
}
|
||||
const count = groupRequestStore.getUnhandledGroupRequestCountMap.get(props.conversation.targetId) ?? 0
|
||||
const count =
|
||||
groupRequestStore.getUnhandledGroupRequestCountMap.get(props.conversation.targetId) ?? 0
|
||||
return count > 0 ? `[${count}条进群申请]` : ''
|
||||
})
|
||||
|
||||
|
|
@ -293,7 +294,6 @@ function handleContextMenu(e: MouseEvent) {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -304,7 +304,7 @@ function handleContextMenu(e: MouseEvent) {
|
|||
|
||||
/* el-icon 的全局 color:var(--color) 在暗色模式下会渲染成白色,这里用 :deep + !important 锁定 */
|
||||
.conversation-item__silent :deep(svg) {
|
||||
fill: currentColor !important;
|
||||
fill: currentcolor !important;
|
||||
}
|
||||
|
||||
.conversation-item__prefix {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@
|
|||
:name="friend.nickname"
|
||||
: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 }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -43,10 +45,15 @@
|
|||
title="发起群聊"
|
||||
@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" />
|
||||
</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>
|
||||
|
||||
|
|
@ -64,14 +71,19 @@
|
|||
<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)]"
|
||||
>
|
||||
<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
|
||||
v-if="friend.displayName"
|
||||
class="text-13px leading-[1.6] text-[var(--el-text-color-regular)] break-all line-clamp-2"
|
||||
>
|
||||
{{ friend.displayName }}
|
||||
</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>
|
||||
</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)]"
|
||||
@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="ant-design:right-outlined"
|
||||
:size="11"
|
||||
|
|
@ -114,12 +128,20 @@
|
|||
|
||||
<!-- 开关项 -->
|
||||
<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">
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">消息免打扰</span>
|
||||
<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"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>消息免打扰</span
|
||||
>
|
||||
<el-switch :model-value="!!conversation?.silent" @change="handleMutedChange" />
|
||||
</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">
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]">置顶聊天</span>
|
||||
<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"
|
||||
>
|
||||
<span class="flex-shrink-0 text-14px text-[var(--el-text-color-primary)]"
|
||||
>置顶聊天</span
|
||||
>
|
||||
<el-switch :model-value="!!conversation?.top" @change="handleTopChange" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -236,7 +258,11 @@ function handleTopChange(value: boolean | string | number) {
|
|||
if (!props.conversation) {
|
||||
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 联动无法用单元素工具类表达 */
|
||||
.im-conversation-private-side__tile-wrap-clickable:hover .im-conversation-private-side__icon-tile {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
/* :deep 穿透 Icon 内部 svg; el-icon 全局 color 在暗色模式下被主题盖过,锁 fill 到当前色 */
|
||||
.im-conversation-private-side__icon-tile :deep(svg) {
|
||||
fill: currentColor !important;
|
||||
fill: currentcolor !important;
|
||||
}
|
||||
|
||||
/* 相邻信息行加分隔线; 相邻兄弟选择器无法用工具类表达 */
|
||||
|
|
|
|||
|
|
@ -342,14 +342,13 @@ onUnmounted(() => {
|
|||
<style scoped>
|
||||
/* 底部小三角:指向触发图标,仿微信 PC 气泡指针;left 偏移对应表情按钮(工具栏 1st icon) */
|
||||
.im-popover-arrow::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: calc(100% - 1px);
|
||||
left: 10px;
|
||||
border-style: solid;
|
||||
border-width: 6px 6px 0 6px;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -364,7 +364,7 @@ function collectFromEditor(root: HTMLElement): { text: string; atUserIds: number
|
|||
* 3. 二次防御:collectFromEditor 走 trim,可能比 syncEditorState 更严格(例如全 ZWSP),仍空就 return
|
||||
* 4. 清空 + 同步状态:先清 innerHTML 再 syncEditorState 让 placeholder / canSend 一起回归
|
||||
* (顺序很重要:先清后 sync,否则 sync 看到旧内容会误判)
|
||||
* 5. 上送:atUserIds 非空才传,避免发空数组;quote 由 clearConversationDraft 前抓取,确保引用条立即消失
|
||||
* 5. 上送:atUserIds 非空才传,避免发空数组;quote 由 clearConversationDraft 前抓取,确保引用条立即消失
|
||||
*/
|
||||
async function handleSend(options?: { receipt?: boolean }) {
|
||||
const editor = editorRef.value
|
||||
|
|
@ -1171,8 +1171,9 @@ async function onVideoPicked(e: Event) {
|
|||
.message-input__tool:deep(svg) {
|
||||
font-size: 18px !important;
|
||||
color: var(--el-text-color-regular) !important;
|
||||
fill: currentColor !important;
|
||||
fill: currentcolor !important;
|
||||
}
|
||||
|
||||
.message-input__tool:hover,
|
||||
.message-input__tool:hover:deep(svg) {
|
||||
color: var(--el-color-primary) !important;
|
||||
|
|
@ -1180,10 +1181,10 @@ async function onVideoPicked(e: Event) {
|
|||
|
||||
/* 用 data-empty 而非 :empty:浏览器在删空后会留下 <br>,:empty 不命中;data-empty 由 syncEditorState 维护 */
|
||||
.message-input__editor[data-empty]::before {
|
||||
content: attr(data-placeholder);
|
||||
position: absolute;
|
||||
color: var(--el-text-color-placeholder);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
content: attr(data-placeholder);
|
||||
}
|
||||
|
||||
/* @ token 走主色高亮;contenteditable=false 让 backspace 整段删而不是逐字符 */
|
||||
|
|
|
|||
|
|
@ -54,15 +54,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
computed,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
useTemplateRef,
|
||||
watch
|
||||
} from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { formatSeconds } from '@/utils/formatTime'
|
||||
|
||||
|
|
@ -292,14 +284,14 @@ onUnmounted(() => {
|
|||
<style scoped>
|
||||
/* 底部小三角:指向触发图标,仿微信 PC 气泡指针;left 偏移对应语音按钮(工具栏 4th icon) */
|
||||
.im-popover-arrow::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: calc(100% - 1px);
|
||||
left: 110px;
|
||||
border-style: solid;
|
||||
border-width: 6px 6px 0 6px;
|
||||
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 定义 */
|
||||
|
|
@ -309,13 +301,15 @@ onUnmounted(() => {
|
|||
|
||||
@keyframes im-voice-pulse {
|
||||
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% {
|
||||
box-shadow: 0 0 0 20px rgba(245, 108, 108, 0);
|
||||
box-shadow: 0 0 0 20px rgb(245 108 108 / 0%);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(245, 108, 108, 0);
|
||||
box-shadow: 0 0 0 0 rgb(245 108 108 / 0%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -182,15 +182,15 @@ async function handleRemove(pinnedMessage: Message) {
|
|||
<style scoped>
|
||||
/* 弹出层朝上的三角箭头;走 ::before + 4 边 border 配色画,颜色跟弹出层 background 一致 */
|
||||
.im-group-pinned-message__list::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 184px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,6 @@ const pendingCount = computed(() => groupRequestStore.getUnhandledGroupRequestCo
|
|||
<style scoped>
|
||||
/* :deep 穿透 Icon 子组件 DOM;强制 svg 走 currentColor 应对暗色模式 el-icon 全局色覆盖 */
|
||||
.im-group-request-pending__icon :deep(svg) {
|
||||
fill: currentColor !important;
|
||||
fill: currentcolor !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -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)]"
|
||||
@click="onClick"
|
||||
>
|
||||
<img 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">
|
||||
<img
|
||||
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 || '(无标题)' }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -19,7 +25,9 @@
|
|||
>
|
||||
<div class="flex gap-2.5 items-start">
|
||||
<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 || '(无标题)' }}
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -35,7 +43,9 @@
|
|||
:src="payload.coverUrl"
|
||||
/>
|
||||
</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
|
||||
v-if="sourceChannel?.avatar"
|
||||
class="w-4 h-4 rounded-full object-cover flex-shrink-0"
|
||||
|
|
@ -47,13 +57,11 @@
|
|||
</div>
|
||||
|
||||
<!-- 富文本详情:全屏弹窗按需挂载,destroy-on-close 关闭后释放 v-dompurify-html 解析的 DOM -->
|
||||
<Dialog
|
||||
v-model="detailVisible"
|
||||
:title="payload.title || '详情'"
|
||||
fullscreen
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-loading="detailLoading" class="material-detail-body max-w-[720px] mx-auto px-5 pt-6 pb-20">
|
||||
<Dialog v-model="detailVisible" :title="payload.title || '详情'" fullscreen 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">
|
||||
{{ payload.title || '' }}
|
||||
</div>
|
||||
|
|
@ -95,9 +103,7 @@ const isChannelView = computed(
|
|||
)
|
||||
|
||||
/** 反序列化 content JSON 为 payload 对象 */
|
||||
const payload = computed<MaterialMessage>(
|
||||
() => parseMessage<MaterialMessage>(props.content) ?? {}
|
||||
)
|
||||
const payload = computed<MaterialMessage>(() => parseMessage<MaterialMessage>(props.content) ?? {})
|
||||
|
||||
/** 来源频道;紧凑卡底部渲染头像 + 名称 */
|
||||
const sourceChannel = computed(() =>
|
||||
|
|
@ -137,15 +143,15 @@ const onClick = async () => {
|
|||
transition: box-shadow 0.15s ease;
|
||||
|
||||
&: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 排版 */
|
||||
.article-content {
|
||||
:deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:deep(p) {
|
||||
|
|
|
|||
|
|
@ -355,28 +355,31 @@ onBeforeUnmount(() => {
|
|||
颜色与气泡背景对应,留 1px 视觉吃进去,省一张图片 */
|
||||
.message-bubble--other::before,
|
||||
.message-bubble--self::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.message-bubble--other::before {
|
||||
left: -5px;
|
||||
border-width: 5px 6px 5px 0;
|
||||
border-color: transparent var(--el-fill-color-light) transparent transparent;
|
||||
border-width: 5px 6px 5px 0;
|
||||
}
|
||||
|
||||
.message-bubble--self::before {
|
||||
right: -5px;
|
||||
border-width: 5px 0 5px 6px;
|
||||
border-color: transparent transparent transparent #95ec69;
|
||||
border-width: 5px 0 5px 6px;
|
||||
}
|
||||
|
||||
/* :deep 穿透 scoped 子组件 DOM;el-icon 在暗色模式下全局 color 被 .el-icon{color:var(--color)} 干扰,把 voice 图标 fill 锁死 */
|
||||
.message-bubble__voice-icon :deep(svg) {
|
||||
fill: #606266 !important;
|
||||
}
|
||||
|
||||
.message-bubble__voice-icon.im-voice-playing :deep(svg) {
|
||||
fill: #409eff !important;
|
||||
}
|
||||
|
|
@ -391,6 +394,7 @@ onBeforeUnmount(() => {
|
|||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -714,8 +714,8 @@ function locateMessage(messageId: number) {
|
|||
/* :deep 穿透 el-tag 子组件 DOM;搜索区 chip 禁掉 hover 颜色过渡 / × 图标动效,避免在搜索区里有抖动感 */
|
||||
.im-message-history__chip,
|
||||
.im-message-history__chip :deep(.el-tag__close) {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* 「定位到聊天位置」按钮:父行 hover 才显示,走 .parent:hover .child 跨元素状态联动 */
|
||||
|
|
@ -723,16 +723,18 @@ function locateMessage(messageId: number) {
|
|||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 4px;
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
margin-top: 4px;
|
||||
color: #1989fa;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.im-message-history__row:hover .im-message-history__locate {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.im-message-history__locate:hover {
|
||||
color: #146fc7;
|
||||
}
|
||||
|
|
@ -744,30 +746,35 @@ function locateMessage(messageId: number) {
|
|||
color: #1989fa;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.im-message-history__tab:hover {
|
||||
color: #2f81d4;
|
||||
}
|
||||
|
||||
.im-message-history__tab--active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
background: #1989fa;
|
||||
border-radius: 1px;
|
||||
content: '';
|
||||
}
|
||||
|
||||
/* :deep 穿透 el-calendar 子组件 DOM;默认偏大压一压让它能塞进 320 popover */
|
||||
.im-message-history__calendar :deep(.el-calendar) {
|
||||
--el-calendar-cell-width: 36px;
|
||||
}
|
||||
|
||||
.im-message-history__calendar :deep(.el-calendar__header) {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.im-message-history__calendar :deep(.el-calendar-table) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.im-message-history__calendar :deep(.el-calendar-day) {
|
||||
height: 36px;
|
||||
padding: 4px;
|
||||
|
|
|
|||
|
|
@ -1053,6 +1053,7 @@ function handleDelete() {
|
|||
.im-loading-spin {
|
||||
animation: im-loading-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes im-loading-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
|
|
|
|||
|
|
@ -755,9 +755,10 @@ watch(
|
|||
.message-panel__header-icon,
|
||||
.message-panel__header-icon :deep(svg) {
|
||||
color: var(--el-text-color-regular) !important;
|
||||
fill: currentColor !important;
|
||||
fill: currentcolor !important;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.message-panel__header-icon:hover,
|
||||
.message-panel__header-icon:hover :deep(svg) {
|
||||
color: var(--el-color-primary) !important;
|
||||
|
|
@ -775,6 +776,7 @@ watch(
|
|||
.message-panel__message-anchor {
|
||||
transition: background-color 0.6s ease;
|
||||
}
|
||||
|
||||
.message-panel__message-anchor--highlight {
|
||||
background-color: var(--el-color-warning-light-9);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue