fix: todo修复

This commit is contained in:
hw
2025-11-20 10:34:21 +08:00
237 changed files with 2791 additions and 3444 deletions

View File

@@ -547,7 +547,10 @@ onMounted(async () => {
<template>
<Page auto-content-height>
<ElContainer class="absolute left-0 top-0 m-4 h-full w-full flex-1">
<ElContainer
direction="horizontal"
class="absolute left-0 top-0 m-4 h-full w-full flex-1"
>
<!-- 左侧对话列表 -->
<ConversationList
class="!bg-card"
@@ -560,7 +563,7 @@ onMounted(async () => {
/>
<!-- 右侧详情部分 -->
<ElContainer class="bg-card mx-4">
<ElContainer direction="vertical" class="bg-card mx-4 flex-1">
<ElHeader
class="!bg-card border-border flex !h-12 items-center justify-between border-b !px-4"
>
@@ -571,30 +574,30 @@ onMounted(async () => {
</span>
</div>
<div class="flex w-72 justify-end" v-if="activeConversation">
<div class="flex w-72 justify-end gap-2" v-if="activeConversation">
<ElButton
type="primary"
plain
class="mr-2 px-2"
class="!px-2"
size="small"
@click="openChatConversationUpdateForm"
>
<span v-html="activeConversation?.modelName"></span>
<IconifyIcon icon="lucide:settings" class="ml-2 size-4" />
<IconifyIcon icon="lucide:settings" class="!ml-2 size-4" />
</ElButton>
<ElButton
size="small"
class="mr-2 px-2"
class="!ml-0 !px-2"
@click="handlerMessageClear"
>
<IconifyIcon icon="lucide:trash-2" color="#787878" />
</ElButton>
<ElButton size="small" class="mr-2 px-2">
<ElButton size="small" class="!ml-0 !px-2">
<IconifyIcon icon="lucide:download" color="#787878" />
</ElButton>
<ElButton
size="small"
class="mr-2 px-2"
class="!ml-0 !px-2"
@click="handleGoTopMessage"
>
<IconifyIcon icon="lucide:arrow-up" color="#787878" />
@@ -629,7 +632,7 @@ onMounted(async () => {
</div>
</ElMain>
<ElFooter class="!bg-card flex flex-col !p-0">
<ElFooter height="auto" class="!bg-card flex flex-col !p-0">
<form
class="border-border mx-4 mb-8 mt-2 flex flex-col rounded-xl border p-2"
>
@@ -673,7 +676,8 @@ onMounted(async () => {
{{ conversationInProgress ? '进行中' : '发送' }}
</ElButton>
<ElButton
type="danger"
type="primary"
:danger="true"
@click="stopStream()"
v-if="conversationInProgress === true"
>

View File

@@ -166,232 +166,277 @@ async function getConversationGroupByCreateTime(
return groupMap;
}
/** 新建对话 */
async function handleConversationCreate() {
// 1. 创建对话
const conversationId = await createChatConversationMy({
roleId: undefined,
} as unknown as AiChatConversationApi.ChatConversation);
// 2. 刷新列表
async function createConversation() {
// 1. 新建对话
const conversationId = await createChatConversationMy(
{} as unknown as AiChatConversationApi.ChatConversation,
);
// 2. 获取对话内容
await getChatConversationList();
// 3. 选中对话
await handleConversationClick(conversationId);
// 4. 回调
emits('onConversationCreate');
}
// 3. 回调
emits('onConversationCreate', conversationId);
/** 修改对话的标题 */
async function updateConversationTitle(
conversation: AiChatConversationApi.ChatConversation,
) {
// 1. 二次确认
await prompt({
async beforeClose(scope) {
if (scope.isConfirm) {
if (scope.value) {
try {
// 2. 发起修改
await updateChatConversationMy({
id: conversation.id,
title: scope.value,
} as AiChatConversationApi.ChatConversation);
ElMessage.success('重命名成功');
// 3. 刷新列表
await getChatConversationList();
// 4. 过滤当前切换的
const filterConversationList = conversationList.value.filter(
(item) => {
return item.id === conversation.id;
},
);
if (
filterConversationList.length > 0 &&
filterConversationList[0] && // tip避免切换对话
activeConversationId.value === filterConversationList[0].id!
) {
emits('onConversationClick', filterConversationList[0]);
}
} catch {
return false;
}
} else {
ElMessage.error('请输入标题');
return false;
}
}
},
component: () => {
return h(ElInput, {
placeholder: '请输入标题',
clearable: true,
modelValue: conversation.title,
});
},
content: '请输入标题',
title: '修改标题',
modelPropName: 'modelValue',
});
}
/** 删除聊天对话 */
async function deleteChatConversation(
conversation: AiChatConversationApi.ChatConversation,
) {
// 删除的二次确认
await confirm(`是否确认删除对话 - ${conversation.title}?`);
// 发起删除
await deleteChatConversationMy(conversation.id);
ElMessage.success('对话已删除');
// 刷新列表
await getChatConversationList();
// 回调
emits('onConversationDelete', conversation);
}
/** 清空未置顶的对话 */
async function handleConversationClear() {
await confirm({
title: '清空未置顶的对话',
content: h('div', {}, [
h('p', '确认清空未置顶的对话吗?'),
h('p', '清空后,未置顶的对话将被删除,无法恢复!'),
]),
});
// 清空
async function handleClearConversation() {
await confirm('确认后对话会全部清空,置顶的对话除外。');
await deleteChatConversationMyByUnpinned();
// 刷新列表
ElMessage.success($t('ui.actionMessage.operationSuccess'));
// 清空对话、对话内容
activeConversationId.value = null;
// 获取对话列表
await getChatConversationList();
// 回调
// 回调 方法
emits('onConversationClear');
}
/** 删除对话 */
async function handleConversationDelete(id: number) {
await confirm({
title: '删除对话',
content: h('div', {}, [
h('p', '确认删除该对话吗?'),
h('p', '删除后,该对话将被删除,无法恢复!'),
]),
});
// 删除
await deleteChatConversationMy(id);
// 刷新列表
await getChatConversationList();
// 回调
emits('onConversationDelete', id);
}
/** 置顶对话 */
async function handleConversationPin(conversation: any) {
// 更新
await updateChatConversationMy({
id: conversation.id,
pinned: !conversation.pinned,
} as AiChatConversationApi.ChatConversation);
// 刷新列表
/** 对话置顶 */
async function handleTop(conversation: AiChatConversationApi.ChatConversation) {
// 更新对话置顶
conversation.pinned = !conversation.pinned;
await updateChatConversationMy(conversation);
// 刷新对话
await getChatConversationList();
}
/** 编辑对话 */
async function handleConversationEdit(conversation: any) {
const title = await prompt({
title: '编辑对话',
content: '请输入对话标题',
defaultValue: conversation.title,
});
// 更新
await updateChatConversationMy({
id: conversation.id,
title,
} as AiChatConversationApi.ChatConversation);
// 刷新列表
await getChatConversationList();
// 提示
ElMessage.success($t('ui.actionMessage.operationSuccess'));
}
// ============ 角色仓库 ============
/** 打开角色仓库 */
async function handleRoleRepositoryOpen() {
/** 角色仓库抽屉 */
const handleRoleRepository = async () => {
drawerApi.open();
}
/** 监听 activeId 变化 */
watch(
() => props.activeId,
(newValue) => {
activeConversationId.value = newValue;
},
);
};
/** 监听选中的对话 */
const { activeId } = toRefs(props);
watch(activeId, async (newValue) => {
activeConversationId.value = newValue;
});
defineExpose({ createConversation });
/** 初始化 */
onMounted(async () => {
// 获取对话列表
// 获取 对话列表
await getChatConversationList();
// 设置选中的对话
if (activeId.value) {
activeConversationId.value = activeId.value;
// 默认选中
if (props.activeId) {
activeConversationId.value = props.activeId;
} else {
// 首次默认选中第一个
if (conversationList.value.length > 0 && conversationList.value[0]) {
activeConversationId.value = conversationList.value[0].id;
// 回调 onConversationClick
emits('onConversationClick', conversationList.value[0]);
}
}
});
defineExpose({ getChatConversationList });
</script>
<template>
<ElAside
class="bg-card relative flex h-full flex-col overflow-hidden border-r border-gray-200"
width="280px"
class="relative flex h-full flex-col justify-between overflow-hidden p-4"
>
<Drawer />
<!-- 头部 -->
<div class="flex flex-col p-4">
<div class="mb-4 flex flex-row items-center justify-between">
<div class="text-lg font-bold">对话</div>
<div class="flex flex-row">
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
text
@click="handleConversationCreate"
>
<IconifyIcon icon="lucide:plus" />
</ElButton>
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
text
@click="handleConversationClear"
>
<IconifyIcon icon="lucide:trash" />
</ElButton>
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
text
@click="handleRoleRepositoryOpen"
>
<IconifyIcon icon="lucide:user" />
</ElButton>
</div>
</div>
<!-- 左顶部对话 -->
<div class="flex h-full flex-col">
<ElButton class="h-9 w-full" type="primary" @click="createConversation">
<IconifyIcon icon="lucide:plus" class="mr-1" />
新建对话
</ElButton>
<ElInput
v-model="searchName"
placeholder="搜索对话"
@keyup.enter="searchConversation"
size="large"
class="search-input mt-4"
placeholder="搜索历史记录"
@keyup="searchConversation"
>
<template #suffix>
<template #prefix>
<IconifyIcon icon="lucide:search" />
</template>
</ElInput>
</div>
<!-- 对话列表 -->
<div class="flex-1 overflow-y-auto px-4">
<div v-if="loading" class="flex h-full items-center justify-center">
<div class="text-sm text-gray-400">加载中...</div>
</div>
<div v-else-if="Object.keys(conversationMap).length === 0">
<ElEmpty description="暂无对话" />
</div>
<div v-else>
<!-- 左中间对话列表 -->
<div class="mt-2 flex-1 overflow-auto">
<!-- 情况一加载中 -->
<ElEmpty v-if="loading" description="." v-loading="loading" />
<!-- 情况二按照 group 分组 -->
<div
v-for="(conversations, groupName) in conversationMap"
:key="groupName"
v-for="conversationKey in Object.keys(conversationMap)"
:key="conversationKey"
>
<div
v-if="conversations.length > 0"
class="mb-2 mt-4 text-xs text-gray-400"
v-if="conversationMap[conversationKey].length > 0"
class="classify-title pt-2"
>
{{ groupName }}
<p class="mx-1">
{{ conversationKey }}
</p>
</div>
<div
v-for="conversation in conversations"
v-for="conversation in conversationMap[conversationKey]"
:key="conversation.id"
class="group relative mb-2 cursor-pointer rounded-lg p-2 transition-all hover:bg-gray-100"
:class="{
'bg-gray-100': activeConversationId === conversation.id,
}"
@click="handleConversationClick(conversation.id)"
@mouseenter="hoverConversationId = conversation.id"
@mouseleave="hoverConversationId = null"
@mouseover="hoverConversationId = conversation.id"
@mouseout="hoverConversationId = null"
class="mt-1"
>
<div class="flex items-center">
<ElAvatar
v-if="conversation.roleAvatar"
:src="conversation.roleAvatar"
:size="28"
/>
<SvgGptIcon v-else class="size-7" />
<div class="ml-2 flex-1 overflow-hidden">
<div class="truncate text-sm font-medium">
<div
class="mb-2 flex cursor-pointer flex-row items-center justify-between rounded-lg px-2 leading-10 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
:class="[
conversation.id === activeConversationId
? 'bg-primary/10 dark:bg-primary/20'
: '',
]"
>
<div class="flex items-center">
<ElAvatar
v-if="conversation.roleAvatar"
:src="conversation.roleAvatar"
:size="28"
/>
<SvgGptIcon v-else class="size-6" />
<span
class="max-w-32 overflow-hidden text-ellipsis whitespace-nowrap p-2 text-sm font-normal"
>
{{ conversation.title }}
</div>
</span>
</div>
<div
v-if="hoverConversationId === conversation.id"
class="flex flex-row"
v-show="hoverConversationId === conversation.id"
class="relative right-0.5 flex items-center text-gray-400"
>
<!-- TODO @AI三个按钮之间间隙太大了 -->
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
text
@click.stop="handleConversationPin(conversation)"
class="mr-0 px-1"
link
@click.stop="handleTop(conversation)"
>
<IconifyIcon
:icon="
conversation.pinned ? 'lucide:pin-off' : 'lucide:pin'
"
v-if="!conversation.pinned"
icon="lucide:arrow-up-to-line"
/>
<IconifyIcon
v-if="conversation.pinned"
icon="lucide:arrow-down-from-line"
/>
</ElButton>
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
text
@click.stop="handleConversationEdit(conversation)"
class="mr-0 px-1"
link
@click.stop="updateConversationTitle(conversation)"
>
<IconifyIcon icon="lucide:edit" />
</ElButton>
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
text
@click.stop="handleConversationDelete(conversation.id)"
class="mr-0 px-1"
link
@click.stop="deleteChatConversation(conversation)"
>
<IconifyIcon icon="lucide:trash" />
<IconifyIcon icon="lucide:trash-2" />
</ElButton>
</div>
</div>
</div>
</div>
</div>
<!-- 底部占位 -->
<div class="h-12 w-full"></div>
</div>
<!-- 左底部工具栏 -->
<div
class="bg-card absolute bottom-1 left-0 right-0 mb-4 flex items-center justify-between px-5 leading-9 text-gray-400 shadow-sm"
>
<div
class="flex cursor-pointer items-center text-gray-400"
@click="handleRoleRepository"
>
<IconifyIcon icon="lucide:user" />
<span class="ml-1">角色仓库</span>
</div>
<div
class="flex cursor-pointer items-center text-gray-400"
@click="handleClearConversation"
>
<IconifyIcon icon="lucide:trash" />
<span class="ml-1">清空未置顶对话</span>
</div>
</div>
</ElAside>
</template>

View File

@@ -18,11 +18,9 @@ async function handlerPromptClick(prompt: any) {
</script>
<template>
<div class="relative flex h-full w-full flex-row justify-center">
<!-- center-container -->
<div class="flex flex-col justify-center">
<!-- title -->
<div class="text-center text-3xl font-bold">芋道 AI</div>
<!-- role-list -->
<div class="mt-5 flex w-96 flex-wrap items-center justify-center">
<div

View File

@@ -37,7 +37,7 @@ const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit']);
const { copy } = useClipboard(); // 初始化 copy 到粘贴板
const userStore = useUserStore();
// 判断"消息列表"滚动的位置(用于判断是否需要滚动到消息最下方)
// 判断消息列表滚动的位置(用于判断是否需要滚动到消息最下方)
const messageContainer: any = ref(null);
const isScrolling = ref(false); // 用于判断用户是否在滚动
@@ -88,6 +88,7 @@ async function copyContent(content: string) {
await copy(content);
ElMessage.success('复制成功!');
}
/** 删除 */
async function handleDelete(id: number) {
// 删除 message
@@ -153,7 +154,7 @@ onMounted(async () => {
</div>
<div class="mt-2 flex flex-row">
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
class="!ml-1 flex items-center bg-transparent !px-1.5 hover:bg-gray-100"
text
@click="copyContent(item.content)"
>
@@ -161,7 +162,7 @@ onMounted(async () => {
</ElButton>
<ElButton
v-if="item.id > 0"
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
class="!ml-1 flex items-center bg-transparent !px-1.5 hover:bg-gray-100"
text
@click="handleDelete(item.id)"
>
@@ -196,28 +197,28 @@ onMounted(async () => {
</div>
<div class="mt-2 flex flex-row-reverse">
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
class="!ml-1 flex items-center bg-transparent !px-1.5 hover:bg-gray-100"
text
@click="copyContent(item.content)"
>
<IconifyIcon icon="lucide:copy" />
</ElButton>
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
class="!ml-1 flex items-center bg-transparent !px-1.5 hover:bg-gray-100"
text
@click="handleDelete(item.id)"
>
<IconifyIcon icon="lucide:trash" />
</ElButton>
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
class="!ml-1 flex items-center bg-transparent !px-1.5 hover:bg-gray-100"
text
@click="handleRefresh(item)"
>
<IconifyIcon icon="lucide:refresh-cw" />
</ElButton>
<ElButton
class="flex items-center bg-transparent px-1.5 hover:bg-gray-100"
class="!ml-1 flex items-center bg-transparent !px-1.5 hover:bg-gray-100"
text
@click="handleEdit(item)"
>

View File

@@ -24,7 +24,7 @@ async function handleCategoryClick(category: string) {
</script>
<template>
<div class="flex flex-wrap items-center">
<div class="mx-0 flex flex-wrap items-center">
<div
class="mr-2 flex flex-row"
v-for="category in categoryList"

View File

@@ -63,59 +63,59 @@ async function handleTabsScroll() {
<template>
<div
class="relative flex h-full flex-wrap content-start items-start overflow-auto px-6 pb-36"
class="relative flex h-full flex-wrap content-start items-start overflow-auto pb-36"
ref="tabsRef"
@scroll="handleTabsScroll"
>
<div class="mb-5 mr-5 inline-block" v-for="role in roleList" :key="role.id">
<div class="mb-3 mr-3 inline-block" v-for="role in roleList" :key="role.id">
<ElCard
class="relative rounded-lg"
body-style="position: relative; display: flex; flex-direction: row; justify-content: flex-start; width: 240px; max-width: 240px; padding: 15px 15px 10px;"
body-style="position: relative; display: flex; flex-direction: column; justify-content: flex-start; width: 240px; max-width: 240px; padding: 15px;"
>
<!-- 更多操作 -->
<div v-if="showMore" class="absolute right-2 top-0">
<ElDropdown>
<ElButton link>
<IconifyIcon icon="lucide:ellipsis-vertical" />
<!-- 头部头像名称 -->
<div class="flex items-center justify-between">
<div class="flex min-w-0 flex-1 items-center">
<ElAvatar
:src="role.avatar"
class="h-8 w-8 flex-shrink-0 overflow-hidden"
/>
<div class="ml-2 truncate text-base font-medium">
{{ role.name }}
</div>
</div>
</div>
<!-- 描述信息 -->
<div
class="mt-2 line-clamp-2 h-10 overflow-hidden text-sm text-gray-600"
>
{{ role.description }}
</div>
<!-- 底部操作按钮 -->
<div class="flex items-center justify-end gap-2">
<ElDropdown v-if="showMore">
<ElButton size="small">
<IconifyIcon icon="lucide:ellipsis" />
</ElButton>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem @click="handleMoreClick('edit', role)">
<div class="flex items-center">
<IconifyIcon icon="lucide:edit" color="#787878" />
<span class="text-primary">编辑</span>
</div>
</ElDropdownItem>
<ElDropdownItem @click="handleMoreClick('delete', role)">
<div class="flex items-center">
<IconifyIcon icon="lucide:trash" color="red" />
<span class="text-red-500">删除</span>
<span class="ml-2 text-red-500">删除</span>
</div>
</ElDropdownItem>
<ElDropdownItem @click="handleMoreClick('edit', role)">
<div class="flex items-center">
<IconifyIcon icon="lucide:edit" color="#787878" />
<span class="text-primary ml-2">编辑</span>
</div>
</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
</div>
<!-- 角色信息 -->
<div>
<ElAvatar :src="role.avatar" class="h-10 w-10 overflow-hidden" />
</div>
<div class="ml-2 w-4/5">
<div class="h-20">
<div class="max-w-32 text-lg font-bold">
{{ role.name }}
</div>
<div class="mt-2 text-sm">
{{ role.description }}
</div>
</div>
<div class="mt-1 flex flex-row-reverse">
<ElButton type="primary" size="small" @click="handleUseClick(role)">
使用
</ElButton>
</div>
<ElButton type="primary" size="small" @click="handleUseClick(role)">
使用
</ElButton>
</div>
</ElCard>
</div>

View File

@@ -119,6 +119,7 @@ async function handlerCategoryClick(category: string) {
async function handlerAddRole() {
formModalApi.setData({ formType: 'my-create' }).open();
}
/** 编辑角色 */
async function handlerCardEdit(role: any) {
formModalApi.setData({ formType: 'my-update', id: role.id }).open();
@@ -187,7 +188,7 @@ onMounted(async () => {
<FormModal @success="handlerAddRoleSuccess" />
<ElMain class="relative m-0 flex-1 overflow-hidden p-0">
<div class="z-100 absolute right-0 top--1 mr-5 mt-5">
<div class="z-100 absolute right-5 top-5 flex items-center">
<!-- 搜索输入框 -->
<ElInput
v-model="search"
@@ -196,7 +197,11 @@ onMounted(async () => {
@keyup.enter="getActiveTabsRole"
>
<template #suffix>
<IconifyIcon icon="lucide:search" />
<IconifyIcon
icon="lucide:search"
class="cursor-pointer"
@click="getActiveTabsRole"
/>
</template>
</ElInput>
<ElButton
@@ -209,11 +214,10 @@ onMounted(async () => {
添加角色
</ElButton>
</div>
<!-- 标签页内容 -->
<ElTabs
v-model="activeTab"
class="relative h-full p-4"
class="relative h-full pb-4 pr-4"
@tab-click="handleTabsClick"
>
<ElTabPane
@@ -229,10 +233,8 @@ onMounted(async () => {
@on-edit="handlerCardEdit"
@on-use="handlerCardUse"
@on-page="handlerCardPage('my')"
class="mt-5"
/>
</ElTabPane>
<ElTabPane
name="public-role"
class="flex h-full flex-col overflow-y-auto"

View File

@@ -69,7 +69,7 @@ async function handleGenerateImage() {
width: width.value, // 图片宽度
height: height.value, // 图片高度
options: {},
} as unknown as AiImageApi.ImageDrawReq;
} as unknown as AiImageApi.ImageDrawReqVO;
await drawImage(form);
} finally {
// 回调

View File

@@ -116,7 +116,7 @@ async function handleGenerateImage() {
options: {
style: style.value, // 图像生成的风格
},
} as AiImageApi.ImageDrawReq;
} as AiImageApi.ImageDrawReqVO;
// 发送请求
await drawImage(form);
} finally {

View File

@@ -144,7 +144,7 @@ async function handleImageMidjourneyButtonClick(
const data = {
id: imageDetail.id,
customId: button.customId,
} as AiImageApi.ImageMidjourneyAction;
} as AiImageApi.ImageMidjourneyActionVO;
// 2. 发送 action
await midjourneyAction(data);
// 3. 刷新列表

View File

@@ -103,7 +103,7 @@ async function handleGenerateImage() {
height: imageSize.height,
version: selectVersion.value,
referImageUrl: referImageUrl.value,
} as AiImageApi.ImageMidjourneyImagineReq;
} as AiImageApi.ImageMidjourneyImagineReqVO;
await midjourneyImagine(req);
} finally {
// 回调

View File

@@ -103,7 +103,7 @@ async function handleGenerateImage() {
clipGuidancePreset: clipGuidancePreset.value, // 文本提示相匹配的图像 CLIP
stylePreset: stylePreset.value, // 风格
},
} as unknown as AiImageApi.ImageDrawReq;
} as unknown as AiImageApi.ImageDrawReqVO;
await drawImage(form);
} finally {
// 回调

View File

@@ -14,7 +14,7 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmCopyTask' });
/** 任务详情 */
function handleDetail(row: BpmProcessInstanceApi.Copy) {
function handleDetail(row: BpmProcessInstanceApi.ProcessInstanceCopyRespVO) {
const query = {
id: row.processInstanceId,
...(row.activityId && { activityId: row.activityId }),
@@ -52,7 +52,7 @@ const [Grid] = useVbenVxeGrid({
refresh: true,
search: true,
},
} as VxeTableGridOptions<BpmProcessInstanceApi.Copy>,
} as VxeTableGridOptions<BpmProcessInstanceApi.ProcessInstanceCopyRespVO>,
});
</script>

View File

@@ -49,7 +49,7 @@ const [Grid] = useVbenVxeGrid({
refresh: true,
search: true,
},
} as VxeTableGridOptions<BpmTaskApi.TaskManager>,
} as VxeTableGridOptions<BpmTaskApi.Task>,
});
</script>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallArticleCategoryApi } from '#/api/mall/promotion/articleCategory';
import type { MallArticleCategoryApi } from '#/api/mall/promotion/article/category';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
@@ -10,7 +10,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteArticleCategory,
getArticleCategoryPage,
} from '#/api/mall/promotion/articleCategory';
} from '#/api/mall/promotion/article/category';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { MallArticleCategoryApi } from '#/api/mall/promotion/articleCategory';
import type { MallArticleCategoryApi } from '#/api/mall/promotion/article/category';
import { computed, ref } from 'vue';
@@ -12,7 +12,7 @@ import {
createArticleCategory,
getArticleCategory,
updateArticleCategory,
} from '#/api/mall/promotion/articleCategory';
} from '#/api/mall/promotion/article/category';
import { $t } from '#/locales';
import { useFormSchema } from '../data';

View File

@@ -1,12 +1,12 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import type { MallArticleCategoryApi } from '#/api/mall/promotion/articleCategory';
import type { MallArticleCategoryApi } from '#/api/mall/promotion/article/category';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
import { getSimpleArticleCategoryList } from '#/api/mall/promotion/articleCategory';
import { getSimpleArticleCategoryList } from '#/api/mall/promotion/article/category';
import { getRangePickerDefaultProps } from '#/utils';
/** 关联数据 */

View File

@@ -1,13 +1,13 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallBannerApi } from '#/api/mall/market/banner';
import type { MallBannerApi } from '#/api/mall/promotion/banner';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteBanner, getBannerPage } from '#/api/mall/market/banner';
import { deleteBanner, getBannerPage } from '#/api/mall/promotion/banner';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { MallBannerApi } from '#/api/mall/market/banner';
import type { MallBannerApi } from '#/api/mall/promotion/banner';
import type { SystemUserApi } from '#/api/system/user';
import { computed, ref } from 'vue';
@@ -12,7 +13,7 @@ import {
createBanner,
getBanner,
updateBanner,
} from '#/api/mall/market/banner';
} from '#/api/mall/promotion/banner';
import { $t } from '#/locales';
import { useFormSchema } from '../data';

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Reply } from '#/views/mp/components/reply/types';
import type { Reply } from '#/views/mp/components/wx-reply/types';
import { computed, nextTick, ref } from 'vue';
@@ -10,7 +10,7 @@ import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { createAutoReply, updateAutoReply } from '#/api/mp/autoReply';
import { $t } from '#/locales';
import { ReplyType } from '#/views/mp/components/reply/types';
import { ReplyType } from '#/views/mp/components/wx-reply/types';
import { useFormSchema } from '../data';
import { MsgType } from './types';

View File

@@ -1 +0,0 @@
export { default } from './account-select.vue';

View File

@@ -1,22 +1,24 @@
// 统一导出所有模块组件
export { default as AccountSelect } from './account-select/account-select.vue';
export { default as AccountSelect } from './wx-account-select/wx-account-select.vue';
export { default as WxAccountSelect } from './wx-account-select/wx-account-select.vue';
export { default as Location } from './location/location.vue';
export { default as MaterialSelect } from './material-select/material-select.vue';
// TODO @hw还是带着 wx 前缀。。。貌似好点,我的锅!!!
export { default as Location } from './wx-location/wx-location.vue';
export * from './wx-material-select/types';
export * from './material-select/types';
export { default as MaterialSelect } from './wx-material-select/wx-material-select.vue';
export * from './msg/types';
export * from './wx-msg/types';
export { default as Music } from './music/music.vue';
export { default as Music } from './wx-music/wx-music.vue';
export { default as News } from './news/news.vue';
export { default as News } from './wx-news/wx-news.vue';
export { default as ReplySelect } from './reply/reply.vue';
export * from './wx-reply/types';
export * from './reply/types';
export { default as ReplySelect } from './wx-reply/wx-reply.vue';
export { default as VideoPlayer } from './video-play/video-play.vue';
export { default as VideoPlayer } from './wx-video-play/wx-video-play.vue';
export { default as VoicePlayer } from './voice-play/voice-play.vue';
export { default as VoicePlayer } from './wx-voice-play/wx-voice-play.vue';

View File

@@ -1 +0,0 @@
export { default } from './location.vue';

View File

@@ -1,3 +0,0 @@
export { default } from './material-select.vue';
export { MaterialType, NewsType } from './types';

View File

@@ -1,3 +0,0 @@
export { default } from './msg.vue';
export { MsgType } from './types';

View File

@@ -1 +0,0 @@
export { default } from './music.vue';

View File

@@ -1 +0,0 @@
export { default } from './news.vue';

View File

@@ -1 +0,0 @@
export { default } from './video-play.vue';

View File

@@ -1 +0,0 @@
export { default } from './voice-play.vue';

View File

@@ -0,0 +1 @@
export { default } from './wx-account-select.vue';

View File

@@ -124,7 +124,7 @@ onMounted(() => {
<ElSelect
v-model="currentId"
placeholder="请选择公众号"
class="!w-240px"
class="!w-full"
@change="onChanged"
>
<ElOption

View File

@@ -0,0 +1 @@
export { default } from './wx-location.vue';

View File

@@ -0,0 +1,3 @@
export { default } from './wx-material-select.vue';
export { MaterialType, NewsType } from './types';

View File

@@ -20,9 +20,9 @@ import {
import * as MpDraftApi from '#/api/mp/draft';
import * as MpFreePublishApi from '#/api/mp/freePublish';
import * as MpMaterialApi from '#/api/mp/material';
import News from '#/views/mp/components/news/news.vue';
import VideoPlayer from '#/views/mp/components/video-play/video-play.vue';
import VoicePlayer from '#/views/mp/components/voice-play/voice-play.vue';
import News from '#/views/mp/components/wx-news/wx-news.vue';
import VideoPlayer from '#/views/mp/components/wx-video-play/wx-video-play.vue';
import VoicePlayer from '#/views/mp/components/wx-voice-play/wx-voice-play.vue';
import { NewsType } from './types';

View File

@@ -0,0 +1,3 @@
export { default } from './wx-msg.vue';
export { MsgType } from './types';

View File

@@ -5,7 +5,7 @@ import { formatDateTime } from '@vben/utils';
import avatarWechat from '#/assets/imgs/wechat.png';
import Msg from './msg.vue';
import Msg from './wx-msg.vue';
// User 使
type PropsUser = User;

View File

@@ -1,11 +1,11 @@
<script lang="ts" setup>
import { ref } from 'vue';
import Location from '#/views/mp/components/location/location.vue';
import Music from '#/views/mp/components/music/music.vue';
import News from '#/views/mp/components/news/news.vue';
import VideoPlayer from '#/views/mp/components/video-play/video-play.vue';
import VoicePlayer from '#/views/mp/components/voice-play/voice-play.vue';
import Location from '#/views/mp/components/wx-location/wx-location.vue';
import Music from '#/views/mp/components/wx-music/wx-music.vue';
import News from '#/views/mp/components/wx-news/wx-news.vue';
import VideoPlayer from '#/views/mp/components/wx-video-play/wx-video-play.vue';
import VoicePlayer from '#/views/mp/components/wx-voice-play/wx-voice-play.vue';
import { MsgType } from '../types';
import MsgEvent from './msg-event.vue';

View File

@@ -0,0 +1 @@
export { default } from './wx-music.vue';

View File

@@ -63,5 +63,5 @@ defineExpose({
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scss */
@import url('../msg/card.scss');
@import url('../wx-msg/card.scss');
</style>

View File

@@ -0,0 +1 @@
export { default } from './wx-news.vue';

View File

@@ -120,5 +120,8 @@ defineExpose({
.material-img {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -1,3 +1,3 @@
export { default } from './reply.vue';
export { createEmptyReply, type Reply, ReplyType } from './types';
export { default } from './wx-reply.vue';

View File

@@ -18,7 +18,7 @@ import {
} from 'element-plus';
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
import MaterialSelect from '#/views/mp/components/material-select/material-select.vue';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
const props = defineProps<{
modelValue: Reply;

View File

@@ -20,7 +20,7 @@ import {
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
// import { getAccessToken } from '@/utils/auth'
import MaterialSelect from '#/views/mp/components/material-select/material-select.vue';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
//

View File

@@ -7,10 +7,10 @@ import { IconifyIcon } from '@vben/icons';
import { ElButton, ElCol, ElDialog, ElRow } from 'element-plus';
import MaterialSelect from '#/views/mp/components/material-select/material-select.vue';
import News from '#/views/mp/components/news/news.vue';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
import News from '#/views/mp/components/wx-news/wx-news.vue';
import { NewsType } from '../material-select/types';
import { NewsType } from '../wx-material-select/types';
const props = defineProps<{
modelValue: Reply;

View File

@@ -19,8 +19,8 @@ import {
} from 'element-plus';
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
import MaterialSelect from '#/views/mp/components/material-select/material-select.vue';
import VideoPlayer from '#/views/mp/components/video-play/video-play.vue';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
import VideoPlayer from '#/views/mp/components/wx-video-play/wx-video-play.vue';
const props = defineProps<{
modelValue: Reply;

View File

@@ -18,8 +18,8 @@ import {
} from 'element-plus';
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
import MaterialSelect from '#/views/mp/components/material-select/material-select.vue';
import VoicePlayer from '#/views/mp/components/voice-play/voice-play.vue';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
import VoicePlayer from '#/views/mp/components/wx-voice-play/wx-voice-play.vue';
//

View File

@@ -16,7 +16,7 @@ import { IconifyIcon } from '@vben/icons';
import { ElRow, ElTabPane, ElTabs } from 'element-plus';
import { NewsType } from '../material-select/types';
import { NewsType } from '../wx-material-select/types';
import TabImage from './tab-image.vue';
import TabMusic from './tab-music.vue';
import TabNews from './tab-news.vue';

View File

@@ -0,0 +1 @@
export { default } from './wx-video-play.vue';

View File

@@ -0,0 +1 @@
export { default } from './wx-voice-play.vue';

View File

@@ -11,7 +11,7 @@ import { useAccessStore } from '@vben/stores';
import { ElButton, ElDialog, ElImage, ElMessage, ElUpload } from 'element-plus';
import { UploadType, useBeforeUpload } from '#/utils/useUpload';
import MaterialSelect from '#/views/mp/components/material-select/material-select.vue';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
// 设置上传的请求头部

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { Article } from './types';
import News from '#/views/mp/components/news/news.vue';
import News from '#/views/mp/components/wx-news/wx-news.vue';
defineOptions({ name: 'DraftTableCell' });

View File

@@ -14,9 +14,9 @@ import {
ElSelect,
} from 'element-plus';
import MaterialSelect from '#/views/mp/components/material-select/material-select.vue';
import News from '#/views/mp/components/news/news.vue';
import ReplySelect from '#/views/mp/components/reply/reply.vue';
import MaterialSelect from '#/views/mp/components/wx-material-select/wx-material-select.vue';
import News from '#/views/mp/components/wx-news/wx-news.vue';
import ReplySelect from '#/views/mp/components/wx-reply/wx-reply.vue';
import menuOptions from './menuOptions';

View File

@@ -2,7 +2,7 @@ import type { MpStatisticsApi } from '#/api/mp/statistics';
/** 用户增减数据图表配置项 */
export function userSummaryOption(
data: MpStatisticsApi.UserSummary[],
data: MpStatisticsApi.StatisticsUserSummaryRespVO[],
dates: string[],
): any {
return {
@@ -41,7 +41,7 @@ export function userSummaryOption(
/** 累计用户数据图表配置项 */
export function userCumulateOption(
data: MpStatisticsApi.UserCumulate[],
data: MpStatisticsApi.StatisticsUserCumulateRespVO[],
dates: string[],
): any {
return {
@@ -71,7 +71,7 @@ export function userCumulateOption(
/** 消息发送概况数据图表配置项 */
export function upstreamMessageOption(
data: MpStatisticsApi.UpstreamMessage[],
data: MpStatisticsApi.StatisticsUpstreamMessageRespVO[],
dates: string[],
): any {
return {
@@ -111,7 +111,7 @@ export function upstreamMessageOption(
/** 接口分析况数据图表配置项 */
export function interfaceSummaryOption(
data: MpStatisticsApi.InterfaceSummary[],
data: MpStatisticsApi.StatisticsInterfaceSummaryRespVO[],
dates: string[],
): any {
return {

View File

@@ -1,29 +1,14 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { MpAccountApi } from '#/api/mp/account';
import { beginOfDay, endOfDay, formatDateTime } from '@vben/utils';
import { getSimpleAccountList } from '#/api/mp/account';
/** 关联数据 */
let accountList: MpAccountApi.AccountSimple[] = [];
getSimpleAccountList().then((data) => (accountList = data));
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'accountId',
label: '公众号',
component: 'ApiSelect',
componentProps: {
options: accountList.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择公众号',
},
defaultValue: accountList[0]?.id,
component: 'Input',
},
{
fieldName: 'dateRange',
@@ -36,7 +21,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
defaultValue: [
formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))),
formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))),
] as [Date, Date],
],
},
];
}

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import { ref } from 'vue';
import { ContentWrap, Page } from '@vben/common-ui';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
@@ -16,6 +16,7 @@ import {
getUserCumulate,
getUserSummary,
} from '#/api/mp/statistics';
import { WxAccountSelect } from '#/views/mp/components';
import {
interfaceSummaryOption,
@@ -95,6 +96,12 @@ async function getSummary(values: Record<string, any>) {
);
}
/** 公众号变化时查询数据 */
function handleAccountChange(accountId: number) {
queryFormApi.setValues({ accountId });
queryFormApi.submitForm();
}
const [QueryForm, queryFormApi] = useVbenForm({
commonConfig: {
componentProps: {
@@ -106,17 +113,16 @@ const [QueryForm, queryFormApi] = useVbenForm({
wrapperClass: 'grid-cols-1 md:grid-cols-2',
handleSubmit: getSummary,
});
/** 初始化 */
onMounted(() => {
queryFormApi.submitForm();
});
</script>
<template>
<Page auto-content-height>
<ContentWrap class="h-full w-full">
<QueryForm />
<QueryForm>
<template #accountId>
<WxAccountSelect @change="handleAccountChange" />
</template>
</QueryForm>
<div class="flex h-1/3 w-full gap-4">
<ElCard class="h-full w-1/2">

View File

@@ -1,12 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import type { MpAccountApi } from '#/api/mp/account';
import { getSimpleAccountList } from '#/api/mp/account';
/** 关联数据 */
let accountList: MpAccountApi.AccountSimple[] = [];
getSimpleAccountList().then((data) => (accountList = data));
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@@ -46,15 +39,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
{
fieldName: 'accountId',
label: '公众号',
component: 'ApiSelect',
componentProps: {
options: accountList.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择公众号',
},
defaultValue: accountList[0]?.id,
component: 'Input',
},
];
}

View File

@@ -9,6 +9,7 @@ import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteTag, getTagPage, syncTag } from '#/api/mp/tag';
import { $t } from '#/locales';
import { WxAccountSelect } from '#/views/mp/components';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -23,6 +24,12 @@ function handleRefresh() {
gridApi.query();
}
/** 公众号变化时查询数据 */
function handleAccountChange(accountId: number) {
gridApi.formApi.setValues({ accountId });
gridApi.formApi.submitForm();
}
/** 创建标签 */
async function handleCreate() {
const formValues = await gridApi.formApi.getValues();
@@ -99,6 +106,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
},
},
autoLoad: false,
},
rowConfig: {
keyField: 'id',
@@ -116,6 +124,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<Grid table-title="公众号标签列表">
<template #form-accountId>
<WxAccountSelect @change="handleAccountChange" />
</template>
<template #toolbar-tools>
<TableAction
:actions="[

View File

@@ -1,11 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MpAccountApi } from '#/api/mp/account';
import { getSimpleAccountList } from '#/api/mp/account';
let accountList: MpAccountApi.AccountSimple[] = [];
getSimpleAccountList().then((data) => (accountList = data));
/** 修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@@ -43,15 +37,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
{
fieldName: 'accountId',
label: '公众号',
component: 'Select',
componentProps: {
options: accountList.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择公众号',
},
defaultValue: accountList[0]?.id,
component: 'Input',
},
{
fieldName: 'openid',

View File

@@ -9,6 +9,7 @@ import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getUserPage, syncUser } from '#/api/mp/user';
import { $t } from '#/locales';
import { WxAccountSelect } from '#/views/mp/components';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -25,6 +26,12 @@ function handleRefresh() {
gridApi.query();
}
/** 公众号变化时查询数据 */
function handleAccountChange(accountId: number) {
gridApi.formApi.setValues({ accountId });
gridApi.formApi.submitForm();
}
/** 编辑用户 */
function handleEdit(row: MpUserApi.User) {
formModalApi.setData({ id: row.id }).open();
@@ -72,6 +79,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
},
},
autoLoad: false,
},
rowConfig: {
keyField: 'id',
@@ -94,6 +102,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
<FormModal @success="handleRefresh" />
<Grid table-title="粉丝列表">
<template #form-accountId>
<WxAccountSelect @change="handleAccountChange" />
</template>
<template #toolbar-tools>
<TableAction
:actions="[