Pre Merge pull request !159 from xingyu/dev

pull/159/MERGE
xingyu 2025-06-27 14:14:26 +00:00 committed by Gitee
commit 9e8cd2020e
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
11 changed files with 160 additions and 110 deletions

View File

@ -305,7 +305,7 @@ onMounted(async () => {
<template> <template>
<Layout.Sider <Layout.Sider
width="280px" width="280px"
class="!bg-primary-foreground conversation-container relative flex h-full flex-col justify-between overflow-hidden p-4" class="conversation-container relative flex h-full flex-col justify-between overflow-hidden p-4"
> >
<Drawer /> <Drawer />
<!-- 左顶部对话 --> <!-- 左顶部对话 -->
@ -358,7 +358,9 @@ onMounted(async () => {
<div <div
class="conversation flex cursor-pointer flex-row items-center justify-between rounded-lg px-2 leading-10" class="conversation flex cursor-pointer flex-row items-center justify-between rounded-lg px-2 leading-10"
:class="[ :class="[
conversation.id === activeConversationId ? 'bg-gray-100' : '', conversation.id === activeConversationId
? 'bg-primary-200'
: '',
]" ]"
> >
<div class="title-wrapper flex items-center"> <div class="title-wrapper flex items-center">
@ -418,7 +420,7 @@ onMounted(async () => {
<!-- 左底部工具栏 --> <!-- 左底部工具栏 -->
<div <div
class="absolute bottom-1 left-0 right-0 mb-4 flex items-center justify-between bg-gray-50 px-5 leading-9 text-gray-400 shadow-sm" 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 <div
class="flex cursor-pointer items-center text-gray-400" class="flex cursor-pointer items-center text-gray-400"

View File

@ -79,7 +79,7 @@ async function handleTabsScroll() {
}" }"
> >
<!-- 更多操作 --> <!-- 更多操作 -->
<div v-if="showMore" class="absolute right-3 top-0"> <div v-if="showMore" class="absolute right-2 top-0">
<Dropdown> <Dropdown>
<Button type="link"> <Button type="link">
<IconifyIcon icon="lucide:ellipsis-vertical" /> <IconifyIcon icon="lucide:ellipsis-vertical" />
@ -89,7 +89,7 @@ async function handleTabsScroll() {
<Menu.Item @click="handleMoreClick(['edit', role])"> <Menu.Item @click="handleMoreClick(['edit', role])">
<div class="flex items-center"> <div class="flex items-center">
<IconifyIcon icon="lucide:edit" color="#787878" /> <IconifyIcon icon="lucide:edit" color="#787878" />
<span>编辑</span> <span class="text-primary">编辑</span>
</div> </div>
</Menu.Item> </Menu.Item>
<Menu.Item @click="handleMoreClick(['delete', role])"> <Menu.Item @click="handleMoreClick(['delete', role])">
@ -108,12 +108,12 @@ async function handleTabsScroll() {
<Avatar :src="role.avatar" class="h-10 w-10 overflow-hidden" /> <Avatar :src="role.avatar" class="h-10 w-10 overflow-hidden" />
</div> </div>
<div class="ml-2 w-full"> <div class="ml-2 w-4/5">
<div class="h-20"> <div class="h-20">
<div class="max-w-36 text-lg font-bold text-gray-600"> <div class="max-w-32 text-lg font-bold">
{{ role.name }} {{ role.name }}
</div> </div>
<div class="mt-2 text-sm text-gray-400"> <div class="mt-2 text-sm">
{{ role.description }} {{ role.description }}
</div> </div>
</div> </div>

View File

@ -174,7 +174,7 @@ onMounted(async () => {
<template> <template>
<Drawer> <Drawer>
<Layout <Layout
class="absolute inset-0 flex h-full w-full flex-col overflow-hidden bg-white" class="bg-card absolute inset-0 flex h-full w-full flex-col overflow-hidden"
> >
<FormModal @success="handlerAddRoleSuccess" /> <FormModal @success="handlerAddRoleSuccess" />

View File

@ -495,6 +495,7 @@ onMounted(async () => {
<Layout class="absolute left-0 top-0 m-4 h-full w-full flex-1"> <Layout class="absolute left-0 top-0 m-4 h-full w-full flex-1">
<!-- 左侧对话列表 --> <!-- 左侧对话列表 -->
<ConversationList <ConversationList
class="!bg-card"
:active-id="activeConversationId as any" :active-id="activeConversationId as any"
ref="conversationListRef" ref="conversationListRef"
@on-conversation-create="handleConversationCreateSuccess" @on-conversation-create="handleConversationCreateSuccess"
@ -504,9 +505,9 @@ onMounted(async () => {
/> />
<!-- 右侧详情部分 --> <!-- 右侧详情部分 -->
<Layout class="mx-4 bg-white"> <Layout class="bg-card mx-4">
<Layout.Header <Layout.Header
class="flex items-center justify-between !bg-gray-50 shadow-none" class="!bg-card border-border flex items-center justify-between border-b"
> >
<div class="text-lg font-bold"> <div class="text-lg font-bold">
{{ activeConversation?.title ? activeConversation?.title : '对话' }} {{ activeConversation?.title ? activeConversation?.title : '对话' }}
@ -565,9 +566,9 @@ onMounted(async () => {
</div> </div>
</Layout.Content> </Layout.Content>
<Layout.Footer class="m-0 flex flex-col !bg-white p-0"> <Layout.Footer class="!bg-card m-0 flex flex-col p-0">
<form <form
class="my-5 mb-5 mt-2 flex flex-col rounded-xl border border-gray-200 px-2 py-2.5" class="border-border my-5 mb-5 mt-2 flex flex-col rounded-xl border px-2 py-2.5"
> >
<textarea <textarea
class="box-border h-24 resize-none overflow-auto border-none px-0 py-1 focus:outline-none" class="box-border h-24 resize-none overflow-auto border-none px-0 py-1 focus:outline-none"

View File

@ -90,9 +90,9 @@ onMounted(async () => {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="bg-card absolute inset-0 flex h-full w-full flex-row"> <div class="absolute inset-0 m-4 flex h-full w-full flex-row">
<div class="left-0 flex w-96 flex-col p-4"> <div class="bg-card left-0 mr-4 flex w-96 flex-col rounded-lg p-4">
<div class="segmented flex justify-center"> <div class="flex justify-center">
<Segmented <Segmented
v-model:value="selectPlatform" v-model:value="selectPlatform"
:options="platformOptions" :options="platformOptions"
@ -125,7 +125,7 @@ onMounted(async () => {
/> />
</div> </div>
</div> </div>
<div class="bg-card ml-4 flex-1"> <div class="bg-card flex-1">
<ImageList ref="imageListRef" @on-regeneration="handleRegeneration" /> <ImageList ref="imageListRef" @on-regeneration="handleRegeneration" />
</div> </div>
</div> </div>

View File

@ -123,8 +123,10 @@ function submit() {
<template> <template>
<DefineTab v-slot="{ active, text, itemClick }"> <DefineTab v-slot="{ active, text, itemClick }">
<span <span
:class="active ? 'text-black shadow-md' : 'hover:bg-gray-200'" :class="
class="relative z-10 inline-block w-1/2 cursor-pointer rounded-full text-center leading-7 text-gray-400 hover:text-black" active ? 'bg-primary-600 text-white shadow-md' : 'hover:bg-primary-200'
"
class="relative z-10 inline-block w-1/2 cursor-pointer rounded-full text-center leading-7 hover:text-black"
@click="itemClick" @click="itemClick"
> >
{{ text }} {{ text }}
@ -136,7 +138,7 @@ function submit() {
<span>{{ label }}</span> <span>{{ label }}</span>
<span <span
v-if="hint" v-if="hint"
class="flex cursor-pointer select-none items-center text-xs text-purple-500" class="text-primary-500 flex cursor-pointer select-none items-center text-xs"
@click="hintClick" @click="hintClick"
> >
<IconifyIcon icon="lucide:circle-help" /> <IconifyIcon icon="lucide:circle-help" />
@ -145,14 +147,14 @@ function submit() {
</h3> </h3>
</DefineLabel> </DefineLabel>
<div class="flex flex-col" v-bind="$attrs"> <div class="flex flex-col" v-bind="$attrs">
<div class="flex w-full justify-center bg-gray-50 pt-2"> <div class="bg-card flex w-full justify-center pt-2">
<div class="z-10 w-72 rounded-full bg-gray-200 p-1"> <div class="bg-card z-10 w-72 rounded-full p-1">
<div <div
:class=" :class="
selectedTab === AiWriteTypeEnum.REPLY && selectedTab === AiWriteTypeEnum.REPLY &&
'after:translate-x-[100%] after:transform' 'after:translate-x-[100%] after:transform'
" "
class="relative flex items-center after:absolute after:left-0 after:top-0 after:block after:h-7 after:w-1/2 after:rounded-full after:bg-white after:transition-transform after:content-['']" class="after:bg-card relative flex items-center after:absolute after:left-0 after:top-0 after:block after:h-7 after:w-1/2 after:rounded-full after:transition-transform after:content-['']"
> >
<ReuseTab <ReuseTab
v-for="tab in tabs" v-for="tab in tabs"
@ -166,7 +168,7 @@ function submit() {
</div> </div>
</div> </div>
<div <div
class="box-border h-full w-96 flex-grow overflow-y-auto bg-gray-50 px-7 pb-2 lg:block" class="bg-card box-border h-full w-96 flex-grow overflow-y-auto px-7 pb-2 lg:block"
> >
<div> <div>
<template v-if="selectedTab === 1"> <template v-if="selectedTab === 1">
@ -233,11 +235,7 @@ function submit() {
<Button :disabled="isWriting" class="mr-2" @click="reset"> <Button :disabled="isWriting" class="mr-2" @click="reset">
重置 重置
</Button> </Button>
<Button <Button type="primary" :loading="isWriting" @click="submit">
:loading="isWriting"
class="bg-purple-500 text-white"
@click="submit"
>
生成 生成
</Button> </Button>
</div> </div>

View File

@ -54,22 +54,18 @@ watch(copied, (val) => {
}); });
</script> </script>
<template> <template>
<Card class="my-card flex h-full flex-col"> <Card class="flex h-full flex-col">
<template #title> <template #title>
<h3 class="m-0 flex shrink-0 items-center justify-between px-7"> <h3 class="m-0 flex shrink-0 items-center justify-between px-7">
<span>预览</span> <span>预览</span>
<!-- 展示在右上角 --> <!-- 展示在右上角 -->
<Button <Button
class="flex bg-purple-500 text-white" type="primary"
v-show="showCopy" v-show="showCopy"
@click="copyContent" @click="copyContent"
size="small" size="small"
> >
<template #icon> <IconifyIcon icon="lucide:copy" />
<div class="flex items-center justify-center">
<IconifyIcon icon="lucide:copy" />
</div>
</template>
复制 复制
</Button> </Button>
</h3> </h3>
@ -79,7 +75,7 @@ watch(copied, (val) => {
class="hide-scroll-bar box-border h-full overflow-y-auto" class="hide-scroll-bar box-border h-full overflow-y-auto"
> >
<div <div
class="relative box-border min-h-full w-full flex-grow bg-white p-3 sm:p-7" class="bg-card relative box-border min-h-full w-full flex-grow p-3 sm:p-7"
> >
<!-- 终止生成内容的按钮 --> <!-- 终止生成内容的按钮 -->
<Button <Button

View File

@ -21,8 +21,10 @@ const emits = defineEmits<{
<span <span
v-for="tag in props.tags" v-for="tag in props.tags"
:key="tag.value" :key="tag.value"
class="mb-2 cursor-pointer rounded border-2 border-solid border-gray-200 bg-gray-200 px-1 text-xs leading-6" class="bg-card border-card-100 mb-2 cursor-pointer rounded border-2 border-solid px-1 text-xs leading-6"
:class="modelValue === tag.value && '!border-purple-500 !text-purple-500'" :class="
modelValue === tag.value && '!border-primary-500 !text-primary-500'
"
@click="emits('update:modelValue', tag.value)" @click="emits('update:modelValue', tag.value)"
> >
{{ tag.label }} {{ tag.label }}

View File

@ -66,10 +66,10 @@ function reset() {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="absolute bottom-0 left-0 right-0 top-0 flex"> <div class="absolute bottom-0 left-0 right-0 top-0 m-4 flex">
<Left <Left
:is-writing="isWriting" :is-writing="isWriting"
class="h-full" class="mr-4 h-full rounded-lg"
@submit="submit" @submit="submit"
@reset="reset" @reset="reset"
@example="handleExampleClick" @example="handleExampleClick"

View File

@ -8,13 +8,20 @@ import { useRoute, useRouter } from 'vue-router';
import { confirm, Page, useVbenModal } from '@vben/common-ui'; import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { useTabs } from '@vben/hooks'; import { useTabs } from '@vben/hooks';
import { Button, Card, message, Tabs } from 'ant-design-vue'; import { Card, message, Tabs } from 'ant-design-vue';
import { getCustomer, updateCustomerDealStatus } from '#/api/crm/customer'; import {
getCustomer,
lockCustomer,
putCustomerPool,
receiveCustomer,
updateCustomerDealStatus,
} from '#/api/crm/customer';
import { getOperateLogPage } from '#/api/crm/operateLog'; import { getOperateLogPage } from '#/api/crm/operateLog';
import { BizTypeEnum } from '#/api/crm/permission'; import { BizTypeEnum } from '#/api/crm/permission';
import { useDescription } from '#/components/description'; import { useDescription } from '#/components/description';
import { AsyncOperateLog } from '#/components/operate-log'; import { AsyncOperateLog } from '#/components/operate-log';
import { ACTION_ICON, TableAction } from '#/components/table-action';
import { BusinessDetailsList } from '#/views/crm/business'; import { BusinessDetailsList } from '#/views/crm/business';
import { ContactDetailsList } from '#/views/crm/contact'; import { ContactDetailsList } from '#/views/crm/contact';
import { ContractDetailsList } from '#/views/crm/contract'; import { ContractDetailsList } from '#/views/crm/contract';
@ -99,18 +106,45 @@ function handleTransfer() {
} }
/** 锁定客户 */ /** 锁定客户 */
function handleLock() { function handleLock(lockStatus: boolean): Promise<boolean | undefined> {
transferModalApi.setData({ id: customerId.value }).open(); return new Promise((resolve, reject) => {
} confirm({
content: `确定锁定客户【${customer.value.name}】吗?`,
/** 解锁客户 */ })
function handleUnlock() { .then(async () => {
transferModalApi.setData({ id: customerId.value }).open(); const res = await lockCustomer(customerId.value, lockStatus);
if (res) {
message.success(lockStatus ? '锁定客户成功' : '解锁客户成功');
resolve(true);
} else {
reject(new Error(lockStatus ? '锁定客户失败' : '解锁客户失败'));
}
})
.catch(() => {
reject(new Error('取消操作'));
});
});
} }
/** 领取客户 */ /** 领取客户 */
function handleReceive() { function handleReceive(): Promise<boolean | undefined> {
transferModalApi.setData({ id: customerId.value }).open(); return new Promise((resolve, reject) => {
confirm({
content: `确定领取客户【${customer.value.name}】吗?`,
})
.then(async () => {
const res = await receiveCustomer([customerId.value]);
if (res) {
message.success('领取客户成功');
resolve(true);
} else {
reject(new Error('领取客户失败'));
}
})
.catch(() => {
reject(new Error('取消操作'));
});
});
} }
/** 分配客户 */ /** 分配客户 */
@ -119,8 +153,24 @@ function handleDistributeForm() {
} }
/** 客户放入公海 */ /** 客户放入公海 */
function handlePutPool() { function handlePutPool(): Promise<boolean | undefined> {
transferModalApi.setData({ id: customerId.value }).open(); return new Promise((resolve, reject) => {
confirm({
content: `确定将客户【${customer.value.name}】放入公海吗?`,
})
.then(async () => {
const res = await putCustomerPool(customerId.value);
if (res) {
message.success('放入公海成功');
resolve(true);
} else {
reject(new Error('放入公海失败'));
}
})
.catch(() => {
reject(new Error('取消操作'));
});
});
} }
/** 更新成交状态操作 */ /** 更新成交状态操作 */
@ -161,61 +211,62 @@ onMounted(() => {
<TransferModal @success="loadCustomerDetail" /> <TransferModal @success="loadCustomerDetail" />
<DistributeModal @success="loadCustomerDetail" /> <DistributeModal @success="loadCustomerDetail" />
<template #extra> <template #extra>
<div class="flex items-center gap-2"> <TableAction
<Button :actions="[
v-if="permissionListRef?.validateWrite" {
type="primary" label: $t('ui.actionTitle.edit'),
@click="handleEdit" type: 'primary',
v-access:code="['crm:customer:update']" icon: ACTION_ICON.EDIT,
> auth: ['crm:customer:update'],
{{ $t('ui.actionTitle.edit') }} ifShow: permissionListRef?.validateWrite,
</Button> onClick: handleEdit,
<Button },
v-if="permissionListRef?.validateOwnerUser" {
type="primary" label: '转移',
@click="handleTransfer" type: 'primary',
> ifShow: permissionListRef?.validateOwnerUser,
转移 onClick: handleTransfer,
</Button> },
<Button {
v-if="permissionListRef?.validateWrite" label: '更改成交状态',
@click="handleUpdateDealStatus" type: 'default',
> ifShow: permissionListRef?.validateWrite,
更改成交状态 onClick: handleUpdateDealStatus,
</Button> },
<Button {
v-if="customer.lockStatus && permissionListRef?.validateOwnerUser" label: '锁定',
@click="handleUnlock" type: 'default',
> ifShow:
解锁 !customer.lockStatus && permissionListRef?.validateOwnerUser,
</Button> onClick: handleLock.bind(null, true),
<Button },
v-if="!customer.lockStatus && permissionListRef?.validateOwnerUser" {
@click="handleLock" label: '解锁',
> type: 'default',
锁定 ifShow: customer.lockStatus && permissionListRef?.validateOwnerUser,
</Button> onClick: handleLock.bind(null, false),
<Button },
v-if="!customer.ownerUserId" {
type="primary" label: '领取',
@click="handleReceive" type: 'primary',
> ifShow: !customer.ownerUserId,
领取 onClick: handleReceive,
</Button> },
<Button {
v-if="!customer.ownerUserId" label: '分配',
type="primary" type: 'default',
@click="handleDistributeForm" ifShow: !customer.ownerUserId,
> onClick: handleDistributeForm,
分配 },
</Button> {
<Button label: '放入公海',
v-if="customer.ownerUserId && permissionListRef?.validateOwnerUser" type: 'default',
@click="handlePutPool" ifShow:
> !!customer.ownerUserId && permissionListRef?.validateOwnerUser,
放入公海 onClick: handlePutPool,
</Button> },
</div> ]"
/>
</template> </template>
<Card class="min-h-[10%]"> <Card class="min-h-[10%]">
<Description :data="customer" /> <Description :data="customer" />

View File

@ -78,7 +78,7 @@ export function useFormSchema(): VbenFormSchema[] {
options: contracts.map((item) => ({ options: contracts.map((item) => ({
label: item.name, label: item.name,
value: item.id, value: item.id,
disabled: item.auditStatus === 20, disabled: item.auditStatus !== 20,
})), })),
placeholder: '请选择合同', placeholder: '请选择合同',
} as any; } as any;