fix: lint
This commit is contained in:
@@ -91,8 +91,7 @@ const handleChange: NonNullable<CascaderProps['onChange']> = (value) => {
|
||||
|
||||
const path = Array.isArray(value[0]) ? value[0] : value;
|
||||
const leafValue = path.at(-1);
|
||||
const areaId =
|
||||
typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
const areaId = typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
emit('update:modelValue', Number.isNaN(areaId) ? undefined : areaId);
|
||||
};
|
||||
|
||||
|
||||
@@ -97,11 +97,7 @@ onMounted(async () => {
|
||||
>
|
||||
<IconifyIcon icon="lucide:trash" />
|
||||
</Button>
|
||||
<Button
|
||||
class="m-0 p-2"
|
||||
type="text"
|
||||
@click="handleButtonClick('more')"
|
||||
>
|
||||
<Button class="m-0 p-2" type="text" @click="handleButtonClick('more')">
|
||||
<IconifyIcon icon="lucide:ellipsis-vertical" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<!-- eslint-disable unicorn/no-nested-ternary -->
|
||||
<!-- eslint-disable prettier/prettier -->
|
||||
<script lang="ts" setup>
|
||||
// oxlint-disable unicorn/no-nested-ternary
|
||||
import { inject, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
@@ -205,7 +205,9 @@ const saveConfig = async () => {
|
||||
(option) => option.key === configForm.value.calledProcessDefinitionKey,
|
||||
);
|
||||
|
||||
currentNode.value.name = nodeName.value!;
|
||||
if (nodeName.value) {
|
||||
currentNode.value.name = nodeName.value;
|
||||
}
|
||||
if (currentNode.value.childProcessSetting) {
|
||||
// 1. 是否异步
|
||||
currentNode.value.childProcessSetting.async = configForm.value.async;
|
||||
@@ -811,10 +813,7 @@ onMounted(async () => {
|
||||
trigger: 'change',
|
||||
}"
|
||||
>
|
||||
<InputNumber
|
||||
v-model:value="multiInstanceSourceNumber"
|
||||
:min="1"
|
||||
/>
|
||||
<InputNumber v-model:value="multiInstanceSourceNumber" :min="1" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
|
||||
@@ -157,7 +157,7 @@ function deleteHttpResponseSetting(
|
||||
<IconifyIcon
|
||||
class="size-4 cursor-pointer text-red-500"
|
||||
icon="lucide:trash-2"
|
||||
@click="deleteHttpResponseSetting(setting.response!, index)"
|
||||
@click="deleteHttpResponseSetting(setting.response, index)"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
@@ -158,18 +158,20 @@ const formFieldOptions4Title = computed(() => {
|
||||
};
|
||||
});
|
||||
// 固定添加发起人 ID 字段
|
||||
cloneFormField.unshift({
|
||||
label: '流程名称',
|
||||
value: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
|
||||
});
|
||||
cloneFormField.unshift({
|
||||
label: '发起时间',
|
||||
value: ProcessVariableEnum.START_TIME,
|
||||
});
|
||||
cloneFormField.unshift({
|
||||
label: '发起人',
|
||||
value: ProcessVariableEnum.START_USER_ID,
|
||||
});
|
||||
cloneFormField.unshift(
|
||||
{
|
||||
label: '发起人',
|
||||
value: ProcessVariableEnum.START_USER_ID,
|
||||
},
|
||||
{
|
||||
label: '发起时间',
|
||||
value: ProcessVariableEnum.START_TIME,
|
||||
},
|
||||
{
|
||||
label: '流程名称',
|
||||
value: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
|
||||
},
|
||||
);
|
||||
return cloneFormField;
|
||||
});
|
||||
const formFieldOptions4Summary = computed(() => {
|
||||
|
||||
@@ -112,11 +112,7 @@ defineExpose({ validate });
|
||||
class="mb-5"
|
||||
>
|
||||
<Select v-model:value="modelData.formId" allow-clear>
|
||||
<SelectOption
|
||||
v-for="form in formList"
|
||||
:key="form.id"
|
||||
:value="form.id"
|
||||
>
|
||||
<SelectOption v-for="form in formList" :key="form.id" :value="form.id">
|
||||
{{ form.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
|
||||
@@ -142,7 +142,7 @@ function handleModelSort() {
|
||||
modelList.value.splice(
|
||||
newDraggableIndex ?? 0,
|
||||
0,
|
||||
modelList.value.splice(oldDraggableIndex ?? 0, 1)[0]!,
|
||||
modelList.value.splice(oldDraggableIndex ?? 0, 1)[0],
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -355,7 +355,8 @@ async function initNextAssigneesFormField() {
|
||||
node.candidateStrategy) ||
|
||||
// 情况二:当前节点是审批人自选
|
||||
(isEmpty(node.candidateUsers) &&
|
||||
BpmCandidateStrategyEnum.APPROVE_USER_SELECT === node.candidateStrategy)
|
||||
BpmCandidateStrategyEnum.APPROVE_USER_SELECT ===
|
||||
node.candidateStrategy)
|
||||
) {
|
||||
nextAssigneesActivityNode.value.push(node);
|
||||
}
|
||||
@@ -397,7 +398,10 @@ function validateNextAssignees() {
|
||||
}
|
||||
// 如果需要自选审批人,则校验每个节点是否都已配置审批人
|
||||
for (const item of nextAssigneesActivityNode.value) {
|
||||
if (isEmpty(item.candidateUsers) && isEmpty(approveReasonForm.nextAssignees[item.id])) {
|
||||
if (
|
||||
isEmpty(item.candidateUsers) &&
|
||||
isEmpty(approveReasonForm.nextAssignees[item.id])
|
||||
) {
|
||||
message.warning('下一个节点的审批人不能为空!');
|
||||
return false;
|
||||
}
|
||||
@@ -791,9 +795,7 @@ const imagePreviewUrl = ref('');
|
||||
|
||||
/** 判断文件是否为图片类型 */
|
||||
function isImageUrl(url: string) {
|
||||
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(
|
||||
url.split(/[?#]/)[0] || '',
|
||||
);
|
||||
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(url.split(/[?#]/)[0] || '');
|
||||
}
|
||||
|
||||
/** 处理文件预览 */
|
||||
|
||||
@@ -425,71 +425,69 @@ defineExpose({ setCustomApproveUsers, batchSetCustomApproveUsers });
|
||||
v-if="shouldShowReasonAndAttachment(task, activity.nodeType)"
|
||||
class="mt-1 w-full rounded-md bg-gray-100 p-2 text-sm text-gray-500"
|
||||
>
|
||||
<div v-if="task.reason">审批意见:{{ task.reason }}</div>
|
||||
<div
|
||||
v-if="(task.attachments?.length || 0) > 0"
|
||||
:class="{
|
||||
'mt-2 border-t border-dashed border-gray-300 pt-2':
|
||||
task.reason,
|
||||
}"
|
||||
>
|
||||
<div class="mb-1 text-xs font-semibold text-gray-400">
|
||||
附件列表:
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<template
|
||||
v-for="(
|
||||
attachment, attachmentIndex
|
||||
) in task.attachments"
|
||||
:key="attachmentIndex"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
isImageAttachment(attachment)
|
||||
? 'lucide:image'
|
||||
: 'lucide:file-text'
|
||||
"
|
||||
class="text-gray-400"
|
||||
/>
|
||||
<Image
|
||||
v-if="isImageAttachment(attachment)"
|
||||
:width="32"
|
||||
:height="32"
|
||||
class="rounded border border-solid border-gray-200 object-cover"
|
||||
:src="attachment"
|
||||
:preview="{ src: attachment }"
|
||||
/>
|
||||
<a
|
||||
v-else
|
||||
:href="attachment"
|
||||
target="_blank"
|
||||
class="max-w-[240px] truncate text-blue-500 hover:text-blue-600 hover:underline"
|
||||
:title="getAttachmentName(attachment)"
|
||||
>
|
||||
{{ getAttachmentName(attachment) }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="task.reason">审批意见:{{ task.reason }}</div>
|
||||
<div
|
||||
v-if="(task.attachments?.length || 0) > 0"
|
||||
:class="{
|
||||
'mt-2 border-t border-dashed border-gray-300 pt-2':
|
||||
task.reason,
|
||||
}"
|
||||
>
|
||||
<div class="mb-1 text-xs font-semibold text-gray-400">
|
||||
附件列表:
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<template
|
||||
v-for="(attachment, attachmentIndex) in task.attachments"
|
||||
:key="attachmentIndex"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon
|
||||
:icon="
|
||||
isImageAttachment(attachment)
|
||||
? 'lucide:image'
|
||||
: 'lucide:file-text'
|
||||
"
|
||||
class="text-gray-400"
|
||||
/>
|
||||
<Image
|
||||
v-if="isImageAttachment(attachment)"
|
||||
:width="32"
|
||||
:height="32"
|
||||
class="rounded border border-solid border-gray-200 object-cover"
|
||||
:src="attachment"
|
||||
:preview="{ src: attachment }"
|
||||
/>
|
||||
<a
|
||||
v-else
|
||||
:href="attachment"
|
||||
target="_blank"
|
||||
class="max-w-[240px] truncate text-blue-500 hover:text-blue-600 hover:underline"
|
||||
:title="getAttachmentName(attachment)"
|
||||
>
|
||||
{{ getAttachmentName(attachment) }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
task.signPicUrl &&
|
||||
activity.nodeType === BpmNodeTypeEnum.USER_TASK_NODE
|
||||
"
|
||||
class="mt-1 flex w-full items-center rounded-md bg-gray-100 p-2 text-sm text-gray-500"
|
||||
>
|
||||
签名:
|
||||
<Image
|
||||
class="ml-2"
|
||||
:width="180"
|
||||
:height="60"
|
||||
:src="task.signPicUrl"
|
||||
:preview="{ src: task.signPicUrl }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
task.signPicUrl &&
|
||||
activity.nodeType === BpmNodeTypeEnum.USER_TASK_NODE
|
||||
"
|
||||
class="mt-1 flex w-full items-center rounded-md bg-gray-100 p-2 text-sm text-gray-500"
|
||||
>
|
||||
签名:
|
||||
<Image
|
||||
class="ml-2"
|
||||
:width="180"
|
||||
:height="60"
|
||||
:src="task.signPicUrl"
|
||||
:preview="{ src: task.signPicUrl }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 情况二:遍历每个审批节点下的【候选的】task 任务 -->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CardTarget } from '../../../utils/message';
|
||||
import type { Conversation, FriendLite } from '../../types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -17,15 +18,14 @@ import {
|
||||
} from '../../../utils/constants';
|
||||
import { getConversationKey } from '../../../utils/conversation';
|
||||
import { buildDefaultGroupName } from '../../../utils/group';
|
||||
import { type CardTarget, serializeMessage } from '../../../utils/message';
|
||||
import { serializeMessage } from '../../../utils/message';
|
||||
import { getGroupDisplayName, isGroupQuit } from '../../../utils/user';
|
||||
import { useMessageSender } from '../../composables/useMessageSender';
|
||||
import { FacePicker } from '../../pages/conversation/components/input';
|
||||
import { useConversationStore } from '../../store/conversationStore';
|
||||
import { useFriendStore } from '../../store/friendStore';
|
||||
import { useGroupStore } from '../../store/groupStore';
|
||||
import { ConversationPickerPanel } from '../picker';
|
||||
import { FriendPickerPanel } from '../picker';
|
||||
import { ConversationPickerPanel, FriendPickerPanel } from '../picker';
|
||||
|
||||
defineOptions({ name: 'ImRecommendCardDialog' });
|
||||
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import type { User } from '../../types'
|
||||
import type { User } from '../../types';
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Image } from 'ant-design-vue'
|
||||
import { Image } from 'ant-design-vue';
|
||||
|
||||
import { ImFriendAddSource } from '../../../utils/constants'
|
||||
import { getAvatarBgColor, getAvatarText } from '../../../utils/user'
|
||||
import { useImUiStore } from '../../store/uiStore'
|
||||
import { ImFriendAddSource } from '../../../utils/constants';
|
||||
import { getAvatarBgColor, getAvatarText } from '../../../utils/user';
|
||||
import { useImUiStore } from '../../store/uiStore';
|
||||
|
||||
defineOptions({ name: 'ImUserAvatar', inheritAttrs: false })
|
||||
defineOptions({ name: 'ImUserAvatar', inheritAttrs: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
addSource?: number // 加好友来源;点头像弹 UserInfoCard 时透传给 FriendAddDialog(默认 1=搜索)
|
||||
addSourceExtra?: string // 加好友来源附加:addSource=2(群聊)时传群名,话术拼为「我是 XX 群的 YY」
|
||||
clickable?: boolean // 是否点击弹出 UserInfoCard;默认 true
|
||||
id?: number | string // 用户 id;传了才能点开名片
|
||||
name?: string // 用户名(色卡文字 + 名片备用)
|
||||
previewable?: boolean // 是否点头像直接放大预览;开启后忽略 clickable,不再弹名片
|
||||
previewZIndex?: number // 预览层 z-index;放在高 z-index 弹层(如 UserInfoCard)里时需手动抬高
|
||||
radius?: string // 圆角,支持 CSS 长度;默认 15% 方块小圆角(参考微信)
|
||||
size?: number // 尺寸(px),正方形;优先于 width/height
|
||||
url?: string // 头像图片 URL;为空时走色卡文字
|
||||
user?: User // 额外的用户信息,传了点击就不用现拉接口(弹名片用)
|
||||
addSource?: number; // 加好友来源;点头像弹 UserInfoCard 时透传给 FriendAddDialog(默认 1=搜索)
|
||||
addSourceExtra?: string; // 加好友来源附加:addSource=2(群聊)时传群名,话术拼为「我是 XX 群的 YY」
|
||||
clickable?: boolean; // 是否点击弹出 UserInfoCard;默认 true
|
||||
id?: number | string; // 用户 id;传了才能点开名片
|
||||
name?: string; // 用户名(色卡文字 + 名片备用)
|
||||
previewable?: boolean; // 是否点头像直接放大预览;开启后忽略 clickable,不再弹名片
|
||||
previewZIndex?: number; // 预览层 z-index;放在高 z-index 弹层(如 UserInfoCard)里时需手动抬高
|
||||
radius?: string; // 圆角,支持 CSS 长度;默认 15% 方块小圆角(参考微信)
|
||||
size?: number; // 尺寸(px),正方形;优先于 width/height
|
||||
url?: string; // 头像图片 URL;为空时走色卡文字
|
||||
user?: User; // 额外的用户信息,传了点击就不用现拉接口(弹名片用)
|
||||
}>(),
|
||||
{
|
||||
addSourceExtra: undefined,
|
||||
@@ -36,60 +36,65 @@ const props = withDefaults(
|
||||
size: 42,
|
||||
url: undefined,
|
||||
user: undefined,
|
||||
addSource: ImFriendAddSource.SEARCH
|
||||
}
|
||||
)
|
||||
addSource: ImFriendAddSource.SEARCH,
|
||||
},
|
||||
);
|
||||
|
||||
const uiStore = useImUiStore()
|
||||
const uiStore = useImUiStore();
|
||||
|
||||
const imgStyle = computed(() => ({
|
||||
width: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
borderRadius: props.radius
|
||||
}))
|
||||
borderRadius: props.radius,
|
||||
}));
|
||||
|
||||
const textStyle = computed(() => ({
|
||||
width: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
fontSize: `${Math.floor(props.size * (avatarText.value.length > 1 ? 0.34 : 0.42))}px`,
|
||||
background: textColor.value,
|
||||
borderRadius: props.radius
|
||||
}))
|
||||
borderRadius: props.radius,
|
||||
}));
|
||||
|
||||
/** 色卡首字:中文取 1 个字、英文 / 拉丁取前 2 个字母 */
|
||||
const avatarText = computed(() => getAvatarText(props.name))
|
||||
const avatarText = computed(() => getAvatarText(props.name));
|
||||
|
||||
/** 色卡底色:按昵称 charCode 哈希取调色板色 */
|
||||
const textColor = computed(() => getAvatarBgColor(props.name))
|
||||
const textColor = computed(() => getAvatarBgColor(props.name));
|
||||
|
||||
/** 头像点击:previewable 走 el-image 预览不弹名片;否则按 user / id 任一入参打开名片 */
|
||||
function handleClick(e: MouseEvent) {
|
||||
if (props.previewable) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!props.clickable) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// 情况一:有预传 user 信息:就直接用,省一次接口
|
||||
if (props.user) {
|
||||
uiStore.openUserInfoCardAtEvent(props.user, e, props.addSource, props.addSourceExtra)
|
||||
return
|
||||
uiStore.openUserInfoCardAtEvent(
|
||||
props.user,
|
||||
e,
|
||||
props.addSource,
|
||||
props.addSourceExtra,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 情况二:无预传 user 信息:打开名片,传最小必要信息(id + 昵称 + 头像),位置在鼠标右侧
|
||||
const numId = Number(props.id)
|
||||
const numId = Number(props.id);
|
||||
if (!numId || numId <= 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
uiStore.openUserInfoCardAtEvent(
|
||||
{
|
||||
id: numId,
|
||||
nickname: props.name,
|
||||
avatar: props.url
|
||||
avatar: props.url,
|
||||
},
|
||||
e,
|
||||
props.addSource,
|
||||
props.addSourceExtra
|
||||
)
|
||||
props.addSourceExtra,
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,93 +1,95 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { UserInfoRelation } from './user-info.vue';
|
||||
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth'
|
||||
import { computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { ImConversationType } from '../../../utils/constants'
|
||||
import { getFriendDisplayName } from '../../../utils/user'
|
||||
import { useConversationStore } from '../../store/conversationStore'
|
||||
import { useFriendStore } from '../../store/friendStore'
|
||||
import { useImUiStore } from '../../store/uiStore'
|
||||
import UserInfo, { type UserInfoRelation } from './user-info.vue'
|
||||
import { getCurrentUserId } from '#/views/im/utils/auth';
|
||||
|
||||
defineOptions({ name: 'ImUserInfoCard' })
|
||||
import { ImConversationType } from '../../../utils/constants';
|
||||
import { getFriendDisplayName } from '../../../utils/user';
|
||||
import { useConversationStore } from '../../store/conversationStore';
|
||||
import { useFriendStore } from '../../store/friendStore';
|
||||
import { useImUiStore } from '../../store/uiStore';
|
||||
import UserInfo from './user-info.vue';
|
||||
|
||||
const uiStore = useImUiStore()
|
||||
const conversationStore = useConversationStore()
|
||||
const friendStore = useFriendStore()
|
||||
const router = useRouter()
|
||||
defineOptions({ name: 'ImUserInfoCard' });
|
||||
|
||||
const card = computed(() => uiStore.userInfoCard)
|
||||
const user = computed(() => card.value.user)
|
||||
const uiStore = useImUiStore();
|
||||
const conversationStore = useConversationStore();
|
||||
const friendStore = useFriendStore();
|
||||
const router = useRouter();
|
||||
|
||||
const card = computed(() => uiStore.userInfoCard);
|
||||
const user = computed(() => card.value.user);
|
||||
|
||||
const isSelf = computed(() => {
|
||||
const myId = getCurrentUserId()
|
||||
return !!user.value?.id && user.value.id === myId
|
||||
})
|
||||
const myId = getCurrentUserId();
|
||||
return !!user.value?.id && user.value.id === myId;
|
||||
});
|
||||
const isActiveFriend = computed(() => {
|
||||
if (!user.value?.id || isSelf.value) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
return friendStore.isActiveFriend(user.value.id)
|
||||
})
|
||||
return friendStore.isActiveFriend(user.value.id);
|
||||
});
|
||||
const relation = computed<UserInfoRelation>(() => {
|
||||
if (!user.value) {
|
||||
return 'readonly'
|
||||
return 'readonly';
|
||||
}
|
||||
if (isSelf.value) {
|
||||
return 'self'
|
||||
return 'self';
|
||||
}
|
||||
if (isActiveFriend.value) {
|
||||
return 'friend'
|
||||
return 'friend';
|
||||
}
|
||||
return 'stranger'
|
||||
})
|
||||
return 'stranger';
|
||||
});
|
||||
|
||||
const remark = computed(() => {
|
||||
if (!isActiveFriend.value || !user.value?.id) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
return friendStore.getFriend(user.value.id)?.displayName || ''
|
||||
})
|
||||
return friendStore.getFriend(user.value.id)?.displayName || '';
|
||||
});
|
||||
|
||||
/** 关闭名片:点击遮罩 / Esc / UserInfo 抛上来的删除成功事件 */
|
||||
function handleClose() {
|
||||
uiStore.closeUserInfoCard()
|
||||
uiStore.closeUserInfoCard();
|
||||
}
|
||||
|
||||
/** 键盘事件:Esc 键关闭名片 */
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && card.value.show) {
|
||||
handleClose()
|
||||
handleClose();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('keydown', handleKeydown))
|
||||
onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
|
||||
onMounted(() => window.addEventListener('keydown', handleKeydown));
|
||||
onUnmounted(() => window.removeEventListener('keydown', handleKeydown));
|
||||
|
||||
/** 发消息:UserInfo 抛上来的"发消息",由本组件接力做 openConversation + 跳消息 Tab + 关浮层 */
|
||||
function handleSendMessage() {
|
||||
if (!user.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// 取 friendStore 里的最新备注 / 免打扰,避免新建会话用过期数据
|
||||
const friend = friendStore.getFriend(user.value.id)
|
||||
const friend = friendStore.getFriend(user.value.id);
|
||||
const conversationName = friend
|
||||
? getFriendDisplayName(friend)
|
||||
: user.value.nickname || ''
|
||||
: user.value.nickname || '';
|
||||
conversationStore.openConversation(
|
||||
user.value.id,
|
||||
ImConversationType.PRIVATE,
|
||||
conversationName,
|
||||
user.value.avatar || '',
|
||||
{ silent: !!friend?.silent }
|
||||
)
|
||||
{ silent: !!friend?.silent },
|
||||
);
|
||||
// 跳转会话 Tab(如果不在的话),并且不管当前路由是啥都先切到会话列表页
|
||||
if (router.currentRoute.value.name !== 'ImHomeConversation') {
|
||||
router.push({ name: 'ImHomeConversation' })
|
||||
router.push({ name: 'ImHomeConversation' });
|
||||
}
|
||||
uiStore.closeUserInfoCard()
|
||||
uiStore.closeUserInfoCard();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -99,10 +101,14 @@ function handleSendMessage() {
|
||||
- 关系态由 isSelf / isActiveFriend 派生 relation prop 透到 UserInfo;删除 / 加好友 / 备注落库都在 UserInfo 内闭环
|
||||
-->
|
||||
<teleport to="body">
|
||||
<div v-if="card.show" class="fixed inset-0 z-9998" @click.self="handleClose">
|
||||
<div
|
||||
v-if="card.show"
|
||||
class="fixed inset-0 z-9998"
|
||||
@click.self="handleClose"
|
||||
>
|
||||
<div
|
||||
class="fixed w-80 p-4 bg-[var(--ant-color-bg-elevated)] rounded-md shadow-xl"
|
||||
:style="{ left: `${card.position.x }px`, top: `${card.position.y }px` }"
|
||||
:style="{ left: `${card.position.x}px`, top: `${card.position.y}px` }"
|
||||
@click.stop
|
||||
>
|
||||
<UserInfo
|
||||
|
||||
@@ -1,59 +1,68 @@
|
||||
<script lang="ts" setup>
|
||||
import type { User } from '../../types'
|
||||
import type { User } from '../../types';
|
||||
|
||||
import { computed, h, nextTick, ref, watch } from 'vue'
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { confirm } from '@vben/common-ui'
|
||||
import { DICT_TYPE } from '@vben/constants'
|
||||
import { getDictLabel } from '@vben/hooks'
|
||||
import { IconifyIcon as Icon } from '@vben/icons'
|
||||
import { computed, h, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { Button, Checkbox, Dropdown, Input, Menu, message } from 'ant-design-vue'
|
||||
import { confirm } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
import { IconifyIcon as Icon } from '@vben/icons';
|
||||
|
||||
import { getSimpleUser, type SystemUserApi } from '#/api/system/user'
|
||||
import { formatDate } from '#/views/im/utils/time'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
Input,
|
||||
Menu,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { ImFriendAddSource } from '../../../utils/constants'
|
||||
import { toUserCardTarget } from '../../../utils/message'
|
||||
import { getGenderColor, getGenderIcon } from '../../../utils/user'
|
||||
import { useFriendStore } from '../../store/friendStore'
|
||||
import { FriendAddDialog } from '../friend'
|
||||
import RecommendCardDialog from './recommend-card-dialog.vue'
|
||||
import UserAvatar from './user-avatar.vue'
|
||||
import { getSimpleUser } from '#/api/system/user';
|
||||
import { formatDate } from '#/views/im/utils/time';
|
||||
|
||||
defineOptions({ name: 'ImUserInfo' })
|
||||
import { ImFriendAddSource } from '../../../utils/constants';
|
||||
import { toUserCardTarget } from '../../../utils/message';
|
||||
import { getGenderColor, getGenderIcon } from '../../../utils/user';
|
||||
import { useFriendStore } from '../../store/friendStore';
|
||||
import { FriendAddDialog } from '../friend';
|
||||
import RecommendCardDialog from './recommend-card-dialog.vue';
|
||||
import UserAvatar from './user-avatar.vue';
|
||||
|
||||
defineOptions({ name: 'ImUserInfo' });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 加好友来源;默认 SEARCH;参见 ImFriendAddSource */
|
||||
addSource?: number
|
||||
addSource?: number;
|
||||
/** 来源附带信息:addSource=GROUP(群聊)时传群名,用于「我是 XX 群的 YY」预填话术 */
|
||||
addSourceExtra?: string
|
||||
addSourceExtra?: string;
|
||||
/** 备注:仅 relation=friend 时有效;空串 → 显示"添加备注"占位 */
|
||||
displayName?: string
|
||||
displayName?: string;
|
||||
/** UserAvatar 预览层 z-index;放在高 z-index 浮层(如 UserInfoCard)里需手动抬高 */
|
||||
previewZIndex?: number
|
||||
relation?: UserInfoRelation
|
||||
previewZIndex?: number;
|
||||
relation?: UserInfoRelation;
|
||||
/** 起手用户:至少要有 id;性别 / 部门由组件按 id 懒拉合并补齐 */
|
||||
user: null | User
|
||||
user: null | User;
|
||||
}>(),
|
||||
{
|
||||
addSource: ImFriendAddSource.SEARCH,
|
||||
addSourceExtra: '',
|
||||
displayName: '',
|
||||
previewZIndex: 2000,
|
||||
relation: 'readonly'
|
||||
}
|
||||
)
|
||||
relation: 'readonly',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** 用户点"发消息":导航 / 关浮层等场景相关动作由父级承担(比如 UserInfoCard 要 close) */
|
||||
chat: [user: User]
|
||||
chat: [user: User];
|
||||
/** 删除联系人成功后通知父级(confirm + 调 store 都在本组件内做完):父级关浮层 / 清选中等 */
|
||||
deleted: [user: User]
|
||||
deleted: [user: User];
|
||||
/** 备注落库成功后通知父级,用于同步父级持有的本地 FriendLite / Friend 副本(如 contact 页的 selection) */
|
||||
saved: [value: string]
|
||||
}>()
|
||||
saved: [value: string];
|
||||
}>();
|
||||
|
||||
/**
|
||||
* 关系态:决定备注行 / 右上 "..." 菜单 / 底部动作区的内容
|
||||
@@ -62,31 +71,35 @@ const emit = defineEmits<{
|
||||
* - self: 单按钮"不能和自己聊天" disabled
|
||||
* - readonly: 不渲染备注 / 菜单 / 动作区,仅展示头部信息
|
||||
*/
|
||||
export type UserInfoRelation = 'friend' | 'readonly' | 'self' | 'stranger'
|
||||
export type UserInfoRelation = 'friend' | 'readonly' | 'self' | 'stranger';
|
||||
|
||||
const friendStore = useFriendStore()
|
||||
const friendStore = useFriendStore();
|
||||
|
||||
const full = ref<null | User>(props.user) // 起手 user + getSimpleUser 合并后的完整对象(性别 / 部门补齐用)
|
||||
const full = ref<null | User>(props.user); // 起手 user + getSimpleUser 合并后的完整对象(性别 / 部门补齐用)
|
||||
|
||||
/** 主标题:备注优先(好友场景),其次原昵称 */
|
||||
const headerName = computed(() => props.displayName || full.value?.nickname || '')
|
||||
const headerName = computed(
|
||||
() => props.displayName || full.value?.nickname || '',
|
||||
);
|
||||
|
||||
const deptText = computed(() => full.value?.deptName || '-')
|
||||
const deptText = computed(() => full.value?.deptName || '-');
|
||||
|
||||
const genderIcon = computed(() => getGenderIcon(full.value?.sex))
|
||||
const genderColor = computed(() => getGenderColor(full.value?.sex))
|
||||
const genderIcon = computed(() => getGenderIcon(full.value?.sex));
|
||||
const genderColor = computed(() => getGenderColor(full.value?.sex));
|
||||
|
||||
/** 好友关系记录:来源 / 添加时间 / 是否拉黑从这里取(仅 friend 态下才有意义) */
|
||||
const friendInfo = computed(() =>
|
||||
props.user?.id ? friendStore.getFriend(props.user.id) : undefined
|
||||
)
|
||||
props.user?.id ? friendStore.getFriend(props.user.id) : undefined,
|
||||
);
|
||||
|
||||
/** 是否已拉黑:菜单项「加入黑名单 / 移出黑名单」按这个切换 */
|
||||
const isBlocked = computed(() => !!friendInfo.value?.blocked)
|
||||
const isBlocked = computed(() => !!friendInfo.value?.blocked);
|
||||
|
||||
const editingRemark = ref(false) // 备注内联编辑:editingRemark 控制输入态;user 切换时由下面的 watch 复位避免脏态泄漏
|
||||
const remarkInput = ref('')
|
||||
const remarkInputRef = ref<null | { focus: () => void; select?: () => void }>(null)
|
||||
const editingRemark = ref(false); // 备注内联编辑:editingRemark 控制输入态;user 切换时由下面的 watch 复位避免脏态泄漏
|
||||
const remarkInput = ref('');
|
||||
const remarkInputRef = ref<null | { focus: () => void; select?: () => void }>(
|
||||
null,
|
||||
);
|
||||
|
||||
/**
|
||||
* user.id 变化的统一处理:
|
||||
@@ -97,30 +110,30 @@ const remarkInputRef = ref<null | { focus: () => void; select?: () => void }>(nu
|
||||
watch(
|
||||
() => props.user?.id,
|
||||
async (id) => {
|
||||
full.value = props.user
|
||||
editingRemark.value = false
|
||||
full.value = props.user;
|
||||
editingRemark.value = false;
|
||||
if (!id) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const data = (await getSimpleUser(id)) as User
|
||||
const data = (await getSimpleUser(id)) as User;
|
||||
if (props.user?.id !== id) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
full.value = { ...props.user, ...data }
|
||||
full.value = { ...props.user, ...data };
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 备注行点击:进编辑态 + 把当前备注灌进输入框,下一帧把焦点 / 全选交给 el-input */
|
||||
async function handleRowClick() {
|
||||
if (editingRemark.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
remarkInput.value = props.displayName || ''
|
||||
editingRemark.value = true
|
||||
await nextTick()
|
||||
remarkInputRef.value?.focus()
|
||||
remarkInputRef.value?.select?.()
|
||||
remarkInput.value = props.displayName || '';
|
||||
editingRemark.value = true;
|
||||
await nextTick();
|
||||
remarkInputRef.value?.focus();
|
||||
remarkInputRef.value?.select?.();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,61 +144,61 @@ async function handleRowClick() {
|
||||
*/
|
||||
async function saveRemark() {
|
||||
if (!editingRemark.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const userId = props.user?.id
|
||||
const userId = props.user?.id;
|
||||
if (!userId) {
|
||||
editingRemark.value = false
|
||||
return
|
||||
editingRemark.value = false;
|
||||
return;
|
||||
}
|
||||
const next = remarkInput.value.trim()
|
||||
editingRemark.value = false
|
||||
const next = remarkInput.value.trim();
|
||||
editingRemark.value = false;
|
||||
if (next === (props.displayName || '')) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
await friendStore.setFriendDisplayName(userId, next)
|
||||
message.success('已更新备注')
|
||||
emit('saved', next)
|
||||
await friendStore.setFriendDisplayName(userId, next);
|
||||
message.success('已更新备注');
|
||||
emit('saved', next);
|
||||
}
|
||||
|
||||
function cancelEditRemark() {
|
||||
editingRemark.value = false
|
||||
editingRemark.value = false;
|
||||
}
|
||||
|
||||
/** 发消息:导航 / 关浮层这些"业务侧"动作交给父级,本组件只负责通知 */
|
||||
function handleChat() {
|
||||
if (!props.user) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
emit('chat', props.user)
|
||||
emit('chat', props.user);
|
||||
}
|
||||
|
||||
/** 占位提示:语音 / 视频聊天能力尚未接入,先以"开发中"友好提示 */
|
||||
function handleComingSoon(featureName: string) {
|
||||
message.info(`${featureName} 功能开发中`)
|
||||
message.info(`${featureName} 功能开发中`);
|
||||
}
|
||||
|
||||
// ==================== 添加好友 / 删除好友 ====================
|
||||
|
||||
const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>() // 加好友弹窗 ref:handleAddFriend 调 open({ presetUser, addSource, addSourceExtra }) 触发
|
||||
const friendAddDialogRef = ref<InstanceType<typeof FriendAddDialog>>(); // 加好友弹窗 ref:handleAddFriend 调 open({ presetUser, addSource, addSourceExtra }) 触发
|
||||
|
||||
const recommendDialogRef = ref<InstanceType<typeof RecommendCardDialog>>() // 推荐名片弹窗 ref:handleRecommend 调用 open({ target }) 打开
|
||||
const recommendDialogRef = ref<InstanceType<typeof RecommendCardDialog>>(); // 推荐名片弹窗 ref:handleRecommend 调用 open({ target }) 打开
|
||||
/** 把他推荐给朋友:弹 RecommendCardDialog 选目标会话 */
|
||||
function handleRecommend() {
|
||||
if (!props.user?.id) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const target = toUserCardTarget(full.value)
|
||||
const target = toUserCardTarget(full.value);
|
||||
if (!target) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
recommendDialogRef.value?.open({ target })
|
||||
recommendDialogRef.value?.open({ target });
|
||||
}
|
||||
|
||||
/** 加为好友:弹 FriendAddDialog(带预填用户),让用户填申请理由 + 备注后再发申请 */
|
||||
function handleAddFriend() {
|
||||
if (!props.user?.id) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const presetUser: SystemUserApi.UserSimple = {
|
||||
id: props.user.id,
|
||||
@@ -193,46 +206,49 @@ function handleAddFriend() {
|
||||
avatar: props.user.avatar,
|
||||
sex: props.user.sex,
|
||||
deptId: props.user.deptId,
|
||||
deptName: props.user.deptName
|
||||
} as SystemUserApi.UserSimple
|
||||
deptName: props.user.deptName,
|
||||
} as SystemUserApi.UserSimple;
|
||||
friendAddDialogRef.value?.open({
|
||||
presetUser,
|
||||
addSource: props.addSource,
|
||||
addSourceExtra: props.addSourceExtra
|
||||
})
|
||||
addSourceExtra: props.addSourceExtra,
|
||||
});
|
||||
}
|
||||
|
||||
/** 加入黑名单:confirm → friendStore.blockFriend;后端 FRIEND_BLOCK 推到时由 dispatcher 同步多端 */
|
||||
async function handleBlock() {
|
||||
if (!props.user?.id) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const target = props.user
|
||||
const target = props.user;
|
||||
try {
|
||||
await confirm(`确定将「${target.nickname || ''}」加入黑名单吗?`, '加入黑名单')
|
||||
await confirm(
|
||||
`确定将「${target.nickname || ''}」加入黑名单吗?`,
|
||||
'加入黑名单',
|
||||
);
|
||||
} catch {
|
||||
return
|
||||
return;
|
||||
}
|
||||
await friendStore.blockFriend(target.id)
|
||||
message.success('已加入黑名单')
|
||||
await friendStore.blockFriend(target.id);
|
||||
message.success('已加入黑名单');
|
||||
}
|
||||
|
||||
/** 移出黑名单:操作温和不弹 confirm;后端 FRIEND_UNBLOCK 推到时由 dispatcher 同步多端 */
|
||||
async function handleUnblock() {
|
||||
if (!props.user?.id) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
await friendStore.unblockFriend(props.user.id)
|
||||
message.success('已移出黑名单')
|
||||
await friendStore.unblockFriend(props.user.id);
|
||||
message.success('已移出黑名单');
|
||||
}
|
||||
|
||||
/** 删除联系人:弹自定义确认(含「同时清空聊天记录」选项)→ friendStore.deleteFriend → 通知父级关浮层 / 清选中 */
|
||||
async function handleDeleteFriend() {
|
||||
if (!props.user?.id) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const target = props.user
|
||||
const clearConversation = ref(true)
|
||||
const target = props.user;
|
||||
const clearConversation = ref(true);
|
||||
try {
|
||||
await confirm({
|
||||
cancelText: '取消',
|
||||
@@ -244,21 +260,21 @@ async function handleDeleteFriend() {
|
||||
{
|
||||
checked: clearConversation.value,
|
||||
'onUpdate:checked': (value: boolean) => {
|
||||
clearConversation.value = value
|
||||
}
|
||||
clearConversation.value = value;
|
||||
},
|
||||
},
|
||||
() => '同时清空聊天记录'
|
||||
)
|
||||
() => '同时清空聊天记录',
|
||||
),
|
||||
]),
|
||||
icon: 'warning',
|
||||
title: '删除联系人'
|
||||
})
|
||||
title: '删除联系人',
|
||||
});
|
||||
} catch {
|
||||
return
|
||||
return;
|
||||
}
|
||||
await friendStore.deleteFriend(target.id, clearConversation.value)
|
||||
message.success('已删除好友')
|
||||
emit('deleted', target)
|
||||
await friendStore.deleteFriend(target.id, clearConversation.value);
|
||||
message.success('已删除好友');
|
||||
emit('deleted', target);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -297,19 +313,31 @@ async function handleDeleteFriend() {
|
||||
class="flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-2 space-y-1 text-13px text-[var(--ant-color-text-secondary)]">
|
||||
<div
|
||||
class="mt-2 space-y-1 text-13px text-[var(--ant-color-text-secondary)]"
|
||||
>
|
||||
<!-- 仅当备注已设时展示昵称副行;未设置时主标题就是 nickname,避免重复 -->
|
||||
<div v-if="displayName" class="truncate">昵称:{{ full?.nickname }}</div>
|
||||
<div v-if="displayName" class="truncate">
|
||||
昵称:{{ full?.nickname }}
|
||||
</div>
|
||||
<div class="truncate">部门:{{ deptText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右上 "..." 菜单:仅 friend 态展示;含「加入/移出黑名单」 + 「删除联系人」 -->
|
||||
<div v-if="relation === 'friend'" class="flex-shrink-0">
|
||||
<Dropdown :trigger="['click']" placement="bottomRight" overlay-class-name="im-user-info__more-menu">
|
||||
<Dropdown
|
||||
:trigger="['click']"
|
||||
placement="bottomRight"
|
||||
overlay-class-name="im-user-info__more-menu"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center w-7 h-7 rounded cursor-pointer hover:bg-[var(--ant-color-fill-secondary)]"
|
||||
>
|
||||
<Icon icon="ep:more-filled" :size="18" class="text-[var(--ant-color-text-secondary)]" />
|
||||
<Icon
|
||||
icon="ep:more-filled"
|
||||
:size="18"
|
||||
class="text-[var(--ant-color-text-secondary)]"
|
||||
/>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<Menu>
|
||||
@@ -317,9 +345,11 @@ async function handleDeleteFriend() {
|
||||
<Menu.Item @click="handleRecommend">把他推荐给朋友</Menu.Item>
|
||||
<!-- 拉黑 / 移出黑名单:按 friendInfo.blocked 切换文案 -->
|
||||
<Menu.Item v-if="!isBlocked" divided @click="handleBlock">
|
||||
加入黑名单
|
||||
</Menu.Item>
|
||||
<Menu.Item v-else divided @click="handleUnblock">移出黑名单</Menu.Item>
|
||||
加入黑名单
|
||||
</Menu.Item>
|
||||
<Menu.Item v-else divided @click="handleUnblock">
|
||||
移出黑名单
|
||||
</Menu.Item>
|
||||
<Menu.Item divided @click="handleDeleteFriend">
|
||||
<span class="text-[var(--ant-color-error)]">删除联系人</span>
|
||||
</Menu.Item>
|
||||
@@ -341,7 +371,11 @@ async function handleDeleteFriend() {
|
||||
"
|
||||
@click="handleRowClick"
|
||||
>
|
||||
<span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">备注</span>
|
||||
<span
|
||||
class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]"
|
||||
>
|
||||
备注
|
||||
</span>
|
||||
<Input
|
||||
v-if="editingRemark"
|
||||
ref="remarkInputRef"
|
||||
@@ -378,14 +412,30 @@ async function handleDeleteFriend() {
|
||||
<!-- ==================== 更多信息:来源 / 添加时间,对齐微信「朋友资料」分块 ==================== -->
|
||||
<template v-if="friendInfo?.addSource || friendInfo?.addTime">
|
||||
<div class="my-4 h-px bg-[var(--ant-color-border-secondary)]"></div>
|
||||
<div v-if="friendInfo?.addSource" class="flex gap-5 items-center px-1.5 py-1.5 text-sm">
|
||||
<span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">来源</span>
|
||||
<div
|
||||
v-if="friendInfo?.addSource"
|
||||
class="flex gap-5 items-center px-1.5 py-1.5 text-sm"
|
||||
>
|
||||
<span
|
||||
class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]"
|
||||
>
|
||||
来源
|
||||
</span>
|
||||
<span class="flex-1 min-w-0 truncate text-[var(--ant-color-text)]">
|
||||
{{ getDictLabel(DICT_TYPE.IM_FRIEND_ADD_SOURCE, friendInfo.addSource) }}
|
||||
{{
|
||||
getDictLabel(DICT_TYPE.IM_FRIEND_ADD_SOURCE, friendInfo.addSource)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="friendInfo?.addTime" class="flex gap-5 items-center px-1.5 py-1.5 text-sm">
|
||||
<span class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]">添加时间</span>
|
||||
<div
|
||||
v-if="friendInfo?.addTime"
|
||||
class="flex gap-5 items-center px-1.5 py-1.5 text-sm"
|
||||
>
|
||||
<span
|
||||
class="flex-shrink-0 w-16 whitespace-nowrap text-[var(--ant-color-text-secondary)]"
|
||||
>
|
||||
添加时间
|
||||
</span>
|
||||
<span class="flex-1 min-w-0 truncate text-[var(--ant-color-text)]">
|
||||
{{ formatDate(new Date(friendInfo.addTime), 'YYYY-MM-DD') }}
|
||||
</span>
|
||||
|
||||
@@ -104,7 +104,7 @@ function makeTemplate() {
|
||||
const rule = ref('')
|
||||
const option = ref('')
|
||||
const init = () => {
|
||||
rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}')
|
||||
rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', String.raw`\\`)}')
|
||||
option.value = formCreate.parseJson('${JSON.stringify(opt, null, 2)}')
|
||||
}
|
||||
const onSubmit = (formData) => {
|
||||
|
||||
@@ -79,7 +79,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
trigger: (values, formApi) => {
|
||||
if (values.deviceId !== undefined) {
|
||||
void formApi.setFieldValue('deviceId', undefined);
|
||||
formApi.setFieldValue('deviceId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ const canAdd = computed(() => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
// eslint-disable-next-line unicorn/no-nested-ternary
|
||||
// oxlint-disable-next-line unicorn/no-nested-ternary
|
||||
const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
|
||||
if (ids.length === 0) {
|
||||
productSpus.value = [];
|
||||
@@ -54,7 +54,7 @@ watch(
|
||||
// 只有商品发生变化时才重新查询
|
||||
if (
|
||||
productSpus.value.length === 0 ||
|
||||
productSpus.value.some((spu) => !ids.includes(spu.id!))
|
||||
productSpus.value.some((spu) => !ids.includes(spu.id))
|
||||
) {
|
||||
productSpus.value = await getSpuDetailList(ids);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ function emitSpuChange() {
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
productSpus.value.map((spu) => spu.id!),
|
||||
productSpus.value.map((spu) => spu.id),
|
||||
);
|
||||
emit('change', productSpus.value);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const canAdd = computed(() => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
// eslint-disable-next-line unicorn/no-nested-ternary
|
||||
// oxlint-disable-next-line unicorn/no-nested-ternary
|
||||
const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
|
||||
if (ids.length === 0) {
|
||||
activityList.value = [];
|
||||
@@ -55,7 +55,7 @@ watch(
|
||||
// 只有活动发生变化时才重新查询
|
||||
if (
|
||||
activityList.value.length === 0 ||
|
||||
activityList.value.some((activity) => !ids.includes(activity.id!))
|
||||
activityList.value.some((activity) => !ids.includes(activity.id))
|
||||
) {
|
||||
activityList.value = await getCombinationActivityListByIds(ids);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ function emitActivityChange() {
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
activityList.value.map((activity) => activity.id!),
|
||||
activityList.value.map((activity) => activity.id),
|
||||
);
|
||||
emit('change', activityList.value);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,10 @@ const spuList = ref<MallSpuApi.Spu[]>([]);
|
||||
watch(
|
||||
() => props.property.spuIds,
|
||||
async () => {
|
||||
spuList.value = props.property.spuIds.length > 0
|
||||
? await getSpuDetailList(props.property.spuIds)
|
||||
: [];
|
||||
spuList.value =
|
||||
props.property.spuIds.length > 0
|
||||
? await getSpuDetailList(props.property.spuIds)
|
||||
: [];
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable typescript/require-array-sort-compare
|
||||
/** 坐标点 */
|
||||
export interface Point {
|
||||
x: number;
|
||||
|
||||
@@ -45,7 +45,7 @@ const canAdd = computed(() => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
// eslint-disable-next-line unicorn/no-nested-ternary
|
||||
// oxlint-disable-next-line unicorn/no-nested-ternary
|
||||
const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
|
||||
if (ids.length === 0) {
|
||||
pointActivityList.value = [];
|
||||
@@ -54,7 +54,7 @@ watch(
|
||||
// 只有活动发生变化时才重新查询
|
||||
if (
|
||||
pointActivityList.value.length === 0 ||
|
||||
pointActivityList.value.some((activity) => !ids.includes(activity.id!))
|
||||
pointActivityList.value.some((activity) => !ids.includes(activity.id))
|
||||
) {
|
||||
pointActivityList.value = await getPointActivityListByIds(ids);
|
||||
}
|
||||
@@ -95,7 +95,7 @@ function emitActivityChange() {
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
pointActivityList.value.map((activity) => activity.id!),
|
||||
pointActivityList.value.map((activity) => activity.id),
|
||||
);
|
||||
emit('change', pointActivityList.value);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const canAdd = computed(() => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
// eslint-disable-next-line unicorn/no-nested-ternary
|
||||
// oxlint-disable-next-line unicorn/no-nested-ternary
|
||||
const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
|
||||
if (ids.length === 0) {
|
||||
activityList.value = [];
|
||||
@@ -54,7 +54,7 @@ watch(
|
||||
// 只有活动发生变化时才重新查询
|
||||
if (
|
||||
activityList.value.length === 0 ||
|
||||
activityList.value.some((activity) => !ids.includes(activity.id!))
|
||||
activityList.value.some((activity) => !ids.includes(activity.id))
|
||||
) {
|
||||
activityList.value = await getSeckillActivityListByIds(ids as number[]);
|
||||
}
|
||||
@@ -93,7 +93,7 @@ function emitActivityChange() {
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
activityList.value.map((activity) => activity.id!),
|
||||
activityList.value.map((activity) => activity.id),
|
||||
);
|
||||
emit('change', activityList.value);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库切换时清空库区,避免旧库区条件残留
|
||||
trigger: (values, formApi) => {
|
||||
if (values.locationId !== undefined) {
|
||||
void formApi.setFieldValue('locationId', undefined);
|
||||
formApi.setFieldValue('locationId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -207,7 +207,7 @@ export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库切换时清空库区
|
||||
trigger: (values, formApi) => {
|
||||
if (values.locationId !== undefined) {
|
||||
void formApi.setFieldValue('locationId', undefined);
|
||||
formApi.setFieldValue('locationId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -225,7 +225,7 @@ export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库或库区切换时清空库位
|
||||
trigger: (values, formApi) => {
|
||||
if (values.areaId !== undefined) {
|
||||
void formApi.setFieldValue('areaId', undefined);
|
||||
formApi.setFieldValue('areaId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,7 +26,9 @@ function normalizeTimestampPickerValue(value: any, valueFormat: unknown): any {
|
||||
return value;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => normalizeTimestampPickerValue(item, valueFormat));
|
||||
return value.map((item) =>
|
||||
normalizeTimestampPickerValue(item, valueFormat),
|
||||
);
|
||||
}
|
||||
return typeof value === 'number' ? String(value) : value;
|
||||
}
|
||||
|
||||
@@ -91,8 +91,7 @@ const handleChange: NonNullable<CascaderProps['onChange']> = (value) => {
|
||||
|
||||
const path = Array.isArray(value[0]) ? value[0] : value;
|
||||
const leafValue = path.at(-1);
|
||||
const areaId =
|
||||
typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
const areaId = typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
emit('update:modelValue', Number.isNaN(areaId) ? undefined : areaId);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<!-- eslint-disable unicorn/no-nested-ternary -->
|
||||
<!-- eslint-disable prettier/prettier -->
|
||||
<script lang="ts" setup>
|
||||
// oxlint-disable unicorn/no-nested-ternary
|
||||
import { inject, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
@@ -786,10 +786,7 @@ onMounted(async () => {
|
||||
},
|
||||
]"
|
||||
>
|
||||
<InputNumber
|
||||
v-model:value="multiInstanceSourceNumber"
|
||||
:min="1"
|
||||
/>
|
||||
<InputNumber v-model:value="multiInstanceSourceNumber" :min="1" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
|
||||
@@ -158,18 +158,20 @@ const formFieldOptions4Title = computed(() => {
|
||||
};
|
||||
});
|
||||
// 固定添加发起人 ID 字段
|
||||
cloneFormField.unshift({
|
||||
label: '流程名称',
|
||||
value: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
|
||||
});
|
||||
cloneFormField.unshift({
|
||||
label: '发起时间',
|
||||
value: ProcessVariableEnum.START_TIME,
|
||||
});
|
||||
cloneFormField.unshift({
|
||||
label: '发起人',
|
||||
value: ProcessVariableEnum.START_USER_ID,
|
||||
});
|
||||
cloneFormField.unshift(
|
||||
{
|
||||
label: '发起人',
|
||||
value: ProcessVariableEnum.START_USER_ID,
|
||||
},
|
||||
{
|
||||
label: '发起时间',
|
||||
value: ProcessVariableEnum.START_TIME,
|
||||
},
|
||||
{
|
||||
label: '流程名称',
|
||||
value: ProcessVariableEnum.PROCESS_DEFINITION_NAME,
|
||||
},
|
||||
);
|
||||
return cloneFormField;
|
||||
});
|
||||
const formFieldOptions4Summary = computed(() => {
|
||||
|
||||
@@ -336,7 +336,8 @@ async function initNextAssigneesFormField() {
|
||||
node.candidateStrategy) ||
|
||||
// 情况二:当前节点是审批人自选
|
||||
(isEmpty(node.candidateUsers) &&
|
||||
BpmCandidateStrategyEnum.APPROVE_USER_SELECT === node.candidateStrategy)
|
||||
BpmCandidateStrategyEnum.APPROVE_USER_SELECT ===
|
||||
node.candidateStrategy)
|
||||
) {
|
||||
nextAssigneesActivityNode.value.push(node);
|
||||
}
|
||||
@@ -378,7 +379,10 @@ function validateNextAssignees() {
|
||||
}
|
||||
// 如果需要自选审批人,则校验每个节点是否都已配置审批人
|
||||
for (const item of nextAssigneesActivityNode.value) {
|
||||
if (isEmpty(item.candidateUsers) && isEmpty(approveReasonForm.nextAssignees[item.id])) {
|
||||
if (
|
||||
isEmpty(item.candidateUsers) &&
|
||||
isEmpty(approveReasonForm.nextAssignees[item.id])
|
||||
) {
|
||||
message.warning('下一个节点的审批人不能为空!');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
trigger: (values, formApi) => {
|
||||
if (values.deviceId !== undefined) {
|
||||
void formApi.setFieldValue('deviceId', undefined);
|
||||
formApi.setFieldValue('deviceId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ const canAdd = computed(() => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
// eslint-disable-next-line unicorn/no-nested-ternary
|
||||
// oxlint-disable-next-line unicorn/no-nested-ternary
|
||||
const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
|
||||
if (ids.length === 0) {
|
||||
productSpus.value = [];
|
||||
@@ -54,7 +54,7 @@ watch(
|
||||
// 只有商品发生变化时才重新查询
|
||||
if (
|
||||
productSpus.value.length === 0 ||
|
||||
productSpus.value.some((spu) => !ids.includes(spu.id!))
|
||||
productSpus.value.some((spu) => !ids.includes(spu.id))
|
||||
) {
|
||||
productSpus.value = await getSpuDetailList(ids);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ function emitSpuChange() {
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
productSpus.value.map((spu) => spu.id!),
|
||||
productSpus.value.map((spu) => spu.id),
|
||||
);
|
||||
emit('change', productSpus.value);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const canAdd = computed(() => {
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
// eslint-disable-next-line unicorn/no-nested-ternary
|
||||
// oxlint-disable-next-line unicorn/no-nested-ternary
|
||||
const ids = Array.isArray(newValue) ? newValue : newValue ? [newValue] : [];
|
||||
if (ids.length === 0) {
|
||||
activityList.value = [];
|
||||
@@ -55,7 +55,7 @@ watch(
|
||||
// 只有活动发生变化时才重新查询
|
||||
if (
|
||||
activityList.value.length === 0 ||
|
||||
activityList.value.some((activity) => !ids.includes(activity.id!))
|
||||
activityList.value.some((activity) => !ids.includes(activity.id))
|
||||
) {
|
||||
activityList.value = await getCombinationActivityListByIds(ids);
|
||||
}
|
||||
@@ -94,7 +94,7 @@ function emitActivityChange() {
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
activityList.value.map((activity) => activity.id!),
|
||||
activityList.value.map((activity) => activity.id),
|
||||
);
|
||||
emit('change', activityList.value);
|
||||
}
|
||||
|
||||
@@ -187,7 +187,9 @@ function getTreeNode(slotProps: unknown) {
|
||||
<FormItem
|
||||
:label="getTreeNode(node).label"
|
||||
:name="getTreeNode(node).prop"
|
||||
:label-col="{ flex: getTreeNode(node).children ? '80px' : '58px' }"
|
||||
:label-col="{
|
||||
flex: getTreeNode(node).children ? '80px' : '58px',
|
||||
}"
|
||||
class="mb-0 w-full"
|
||||
>
|
||||
<Row>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable typescript/require-array-sort-compare
|
||||
/** 坐标点 */
|
||||
export interface Point {
|
||||
x: number;
|
||||
|
||||
@@ -57,7 +57,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库切换时清空库区,避免旧库区条件残留
|
||||
trigger: (values, formApi) => {
|
||||
if (values.locationId !== undefined) {
|
||||
void formApi.setFieldValue('locationId', undefined);
|
||||
formApi.setFieldValue('locationId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -207,7 +207,7 @@ export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库切换时清空库区
|
||||
trigger: (values, formApi) => {
|
||||
if (values.locationId !== undefined) {
|
||||
void formApi.setFieldValue('locationId', undefined);
|
||||
formApi.setFieldValue('locationId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -225,7 +225,7 @@ export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库或库区切换时清空库位
|
||||
trigger: (values, formApi) => {
|
||||
if (values.areaId !== undefined) {
|
||||
void formApi.setFieldValue('areaId', undefined);
|
||||
formApi.setFieldValue('areaId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -85,8 +85,7 @@ function handleChange(value?: CascaderValue | null) {
|
||||
|
||||
const path = Array.isArray(value[0]) ? value[0] : value;
|
||||
const leafValue = path.at(-1);
|
||||
const areaId =
|
||||
typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
const areaId = typeof leafValue === 'number' ? leafValue : Number(leafValue);
|
||||
emit('update:modelValue', Number.isNaN(areaId) ? undefined : areaId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<!-- eslint-disable unicorn/no-nested-ternary -->
|
||||
<!-- eslint-disable prettier/prettier -->
|
||||
<script lang="ts" setup>
|
||||
// oxlint-disable unicorn/no-nested-ternary
|
||||
import { inject, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
@@ -700,7 +700,7 @@ onMounted(() => {
|
||||
v-for="item in roleOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id!"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
@@ -738,7 +738,7 @@ onMounted(() => {
|
||||
v-for="item in postOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id!"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
@@ -752,7 +752,7 @@ onMounted(() => {
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id!"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
@@ -1035,7 +1035,7 @@ onMounted(() => {
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id!"
|
||||
:value="item.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
@@ -79,7 +79,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
},
|
||||
trigger: (values, formApi) => {
|
||||
if (values.deviceId !== undefined) {
|
||||
void formApi.setFieldValue('deviceId', undefined);
|
||||
formApi.setFieldValue('deviceId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable typescript/require-array-sort-compare
|
||||
/** 坐标点 */
|
||||
export interface Point {
|
||||
x: number;
|
||||
|
||||
@@ -57,7 +57,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库切换时清空库区,避免旧库区条件残留
|
||||
trigger: (values, formApi) => {
|
||||
if (values.locationId !== undefined) {
|
||||
void formApi.setFieldValue('locationId', undefined);
|
||||
formApi.setFieldValue('locationId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -207,7 +207,7 @@ export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库切换时清空库区
|
||||
trigger: (values, formApi) => {
|
||||
if (values.locationId !== undefined) {
|
||||
void formApi.setFieldValue('locationId', undefined);
|
||||
formApi.setFieldValue('locationId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -225,7 +225,7 @@ export function useSelectGridFormSchema(): VbenFormSchema[] {
|
||||
// 仓库或库区切换时清空库位
|
||||
trigger: (values, formApi) => {
|
||||
if (values.areaId !== undefined) {
|
||||
void formApi.setFieldValue('areaId', undefined);
|
||||
formApi.setFieldValue('areaId', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user