Merge remote-tracking branch 'yudao/dev' into dev

This commit is contained in:
jason
2025-11-28 09:13:09 +08:00
79 changed files with 2000 additions and 2056 deletions

View File

@@ -16,12 +16,6 @@ export namespace MpAccountApi {
remark?: string;
createTime?: Date;
}
// TODO @dylan这个直接使用 Account简化一点
export interface AccountSimple {
id: number;
name: string;
}
}
/** 查询公众号账号列表 */
@@ -41,7 +35,7 @@ export function getAccount(id: number) {
/** 查询公众号账号列表 */
export function getSimpleAccountList() {
return requestClient.get<MpAccountApi.AccountSimple[]>(
return requestClient.get<MpAccountApi.Account[]>(
'/mp/account/list-all-simple',
);
}

View File

@@ -2,6 +2,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
// TODO @dylan这个类的代码应该和对应的 antd 是一致的。调整下~看看相关的 vue 是不是也要调整掉。
/** 消息类型枚举 */
export enum MessageType {
IMAGE = 'image', // 图片消息

View File

@@ -145,8 +145,8 @@ export function useApiSelect(option: ApiSelectProps) {
}
return {
label: label,
value: value,
label,
value,
};
});
return;

View File

@@ -202,10 +202,10 @@ export async function useFormCreateDesigner(designer: Ref) {
value: 'id',
options: [
{ label: '部门编号', value: 'id' },
{ label: '部门名称', value: 'name' }
]
}
]
{ label: '部门名称', value: 'name' },
],
},
],
});
const dictSelectRule = useDictSelectRule();
const apiSelectRule0 = useSelectRule({

View File

@@ -1,65 +1,102 @@
<script setup lang="ts">
import type { BasicOption } from '@vben/types';
import type { Recordable } from '@vben/types';
import type { VbenFormSchema } from '#/adapter/form';
import type { SystemUserProfileApi } from '#/api/system/user/profile';
import { computed, onMounted, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import { ProfileBaseSetting } from '@vben/common-ui';
import { ProfileBaseSetting, z } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { getUserInfoApi } from '#/api';
import { ElMessage } from 'element-plus';
import { updateUserProfile } from '#/api/system/user/profile';
import { $t } from '#/locales';
const props = defineProps<{
profile?: SystemUserProfileApi.UserProfileRespVO;
}>();
const emit = defineEmits<{
(e: 'success'): void;
}>();
const profileBaseSettingRef = ref();
const MOCK_ROLES_OPTIONS: BasicOption[] = [
{
label: '管理员',
value: 'super',
},
{
label: '用户',
value: 'user',
},
{
label: '测试',
value: 'test',
},
];
const formSchema = computed((): VbenFormSchema[] => {
return [
{
fieldName: 'realName',
label: '用户昵称',
fieldName: 'nickname',
component: 'Input',
label: '姓名',
},
{
fieldName: 'username',
component: 'Input',
label: '用户名',
},
{
fieldName: 'roles',
component: 'Select',
componentProps: {
mode: 'tags',
options: MOCK_ROLES_OPTIONS,
placeholder: '请输入用户昵称',
},
label: '角色',
rules: 'required',
},
{
fieldName: 'introduction',
component: 'Textarea',
label: '个人简介',
label: '用户手机',
fieldName: 'mobile',
component: 'Input',
componentProps: {
placeholder: '请输入用户手机',
},
rules: z.string(),
},
{
label: '用户邮箱',
fieldName: 'email',
component: 'Input',
componentProps: {
placeholder: '请输入用户邮箱',
},
rules: z.string().email('请输入正确的邮箱'),
},
{
label: '用户性别',
fieldName: 'sex',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: z.number(),
},
];
});
onMounted(async () => {
const data = await getUserInfoApi();
profileBaseSettingRef.value.getFormApi().setValues(data);
});
async function handleSubmit(values: Recordable<any>) {
try {
profileBaseSettingRef.value.getFormApi().setLoading(true);
// 提交表单
await updateUserProfile(values as SystemUserProfileApi.UpdateProfileReqVO);
// 关闭并提示
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} catch (error) {
console.error(error);
} finally {
profileBaseSettingRef.value.getFormApi().setLoading(false);
}
}
/** 监听 profile 变化 */
watch(
() => props.profile,
(newProfile) => {
if (newProfile) {
profileBaseSettingRef.value.getFormApi().setValues(newProfile);
}
},
{ immediate: true },
);
</script>
<template>
<ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
<ProfileBaseSetting
ref="profileBaseSettingRef"
:form-schema="formSchema"
@submit="handleSubmit"
/>
</template>

View File

@@ -274,9 +274,9 @@ onMounted(async () => {
</template>
<template #bottom>
<div class="border-border bg-muted mt-2 rounded border p-2">
<div class="text-muted-foreground flex justify-between text-sm">
<span class="text-foreground font-medium">合计</span>
<div class="mt-2 rounded border border-border bg-muted p-2">
<div class="flex justify-between text-sm text-muted-foreground">
<span class="font-medium text-foreground">合计</span>
<div class="flex space-x-4">
<span>数量{{ erpCountInputFormatter(summaries.count) }}</span>
<span>

View File

@@ -294,9 +294,9 @@ onMounted(async () => {
</template>
<template #bottom>
<div class="border-border bg-muted mt-2 rounded border p-2">
<div class="text-muted-foreground flex justify-between text-sm">
<span class="text-foreground font-medium">合计</span>
<div class="mt-2 rounded border border-border bg-muted p-2">
<div class="flex justify-between text-sm text-muted-foreground">
<span class="font-medium text-foreground">合计</span>
<div class="flex space-x-4">
<span>数量{{ erpCountInputFormatter(summaries.count) }}</span>
<span>

View File

@@ -273,8 +273,8 @@ onMounted(async () => {
<template #bottom>
<div class="mt-2 rounded border border-border bg-muted p-2">
<div class="text-muted-foreground flex justify-between text-sm">
<span class="text-foreground font-medium">合计</span>
<div class="flex justify-between text-sm text-muted-foreground">
<span class="font-medium text-foreground">合计</span>
<div class="flex space-x-4">
<span>数量{{ erpCountInputFormatter(summaries.count) }}</span>
<span>

View File

@@ -1,7 +1,7 @@
export { default as WxAccountSelect } from './wx-account-select/wx-account-select.vue';
export { default as WxLocation } from './wx-location/wx-location.vue';
export { default as WxMaterialSelect } from './wx-material-select/wx-material-select.vue';
export { default as WxMsg } from './wx-msg/msg.vue';
export { default as WxMsg } from './wx-msg/msg.vue'; // TODO @hw、@dylan貌似和 antd 不同。antd 这里是 export { default as WxMsg } from './wx-msg/wx-msg.vue'; 看看哪个是对的
export { default as WxMusic } from './wx-music/wx-music.vue';
export { default as WxNews } from './wx-news/wx-news.vue';
export { default as WxReply } from './wx-reply/wx-reply.vue';

View File

@@ -10,9 +10,11 @@ import { ElCol, ElLink, ElMessage, ElRow } from 'element-plus';
import { getTradeConfig } from '#/api/mall/trade/config';
/** 微信消息 - 定位 */
defineOptions({ name: 'Location' });
defineOptions({ name: 'WxLocation' });
const props = defineProps<WxLocationProps>();
const props = withDefaults(defineProps<WxLocationProps>(), {
qqMapKey: '', // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
});
const fetchedQqMapKey = ref('');
const resolvedQqMapKey = computed(

View File

@@ -46,13 +46,15 @@ function getNickname(sendFrom: number) {
</div>
<div class="relative mx-2 flex-1 rounded-[5px] border border-[#dedede]">
<span
class="pointer-events-none absolute -left-2 top-[10px] h-0 w-0 border-y-[8px] border-r-[8px] border-y-transparent border-r-[#dedede]"
v-if="item.sendFrom === SendFrom.MpBot"
class="pointer-events-none absolute -left-2 top-[10px] h-0 w-0 border-y-[8px] border-r-[8px] border-y-transparent border-r-[transparent]"
:class="{
'-right-2 left-auto border-l-[8px] border-r-0 border-l-[#dedede]':
item.sendFrom === SendFrom.MpBot,
}"
></span>
<span
v-if="item.sendFrom === SendFrom.User"
class="pointer-events-none absolute -left-[7px] top-[10px] h-0 w-0 border-y-[8px] border-r-[8px] border-y-transparent border-r-[#f8f8f8]"
:class="{
'-right-[7px] left-auto border-l-[8px] border-r-0 border-l-[#f8f8f8]':

View File

@@ -18,7 +18,7 @@ defineExpose({
<template>
<div class="mx-auto flex w-full flex-col gap-[10px] bg-white">
<div v-for="(article, index) in articles" :key="index" class="news-div">
<div v-for="(article, index) in articles" :key="index">
<!-- 头条 -->
<a v-if="index === 0" :href="article.url" target="_blank">
<div class="mx-auto w-full">
@@ -26,11 +26,10 @@ defineExpose({
<img
:src="article.picUrl"
:preview="false"
class="flex w-[100%] items-center justify-center object-cover"
class="w-[100px] object-cover"
/>
<div
class="absolute bottom-0 left-0 ml-[10px] inline-block w-[98%] whitespace-normal p-[1%] text-base text-white"
style="box-sizing: unset !important"
>
<span>{{ article.title }}</span>
</div>

View File

@@ -78,8 +78,6 @@ function onDelete() {
/** 选择素材 */
function selectMaterial(item: any) {
showDialog.value = false;
// reply.value.type = 'image'
reply.value.mediaId = item.mediaId;
reply.value.url = item.url;
reply.value.name = item.name;

View File

@@ -8,8 +8,7 @@ import { IconifyIcon } from '@vben/icons';
import { ElButton, ElCol, ElDialog, ElRow } from 'element-plus';
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 { WxMaterialSelect, WxNews } from '#/views/mp/components';
defineOptions({ name: 'TabNews' });
@@ -48,7 +47,7 @@ function onDelete() {
class="mx-auto mb-[10px] w-[280px] border border-[#eaeaea] p-[10px]"
v-if="reply.articles && reply.articles.length > 0"
>
<News :articles="reply.articles" />
<WxNews :articles="reply.articles" />
<ElCol class="pt-[10px] text-center">
<ElButton type="danger" circle @click="onDelete">
<IconifyIcon icon="lucide:trash-2" />
@@ -78,7 +77,7 @@ function onDelete() {
append-to-body
destroy-on-close
>
<MaterialSelect
<WxMaterialSelect
type="news"
:account-id="reply.accountId"
:news-type="newsType"

View File

@@ -3,8 +3,7 @@ import { ref } from 'vue';
import { IconifyIcon } from '@vben/icons';
// 因为微信语音是 amr 格式,所以需要用到 amr 解码器https://www.npmjs.com/package/benz-amr-recorder
import BenzAMRRecorder from 'benz-amr-recorder';
import BenzAMRRecorder from 'benz-amr-recorder'; // 因为微信语音是 amr 格式,所以需要用到 amr 解码器https://www.npmjs.com/package/benz-amr-recorder
import { ElTag } from 'element-plus';
/** 微信消息 - 语音 */
@@ -64,23 +63,24 @@ function amrStop() {
playing.value = false;
amr.value.stop();
}
// TODO dylan下面样式有点问题
</script>
<template>
<div
class="flex min-h-[50px] min-w-[120px] flex-col items-center justify-center rounded-[10px] bg-[#eaeaea] px-3 py-2"
class="flex h-[50px] w-[120px] cursor-pointer items-center justify-center rounded-[10px] bg-[#eaeaea] p-[5px]"
@click="playVoice"
>
<div class="flex items-center">
<el-icon>
<IconifyIcon
v-if="playing !== true"
icon="lucide:circle-play"
:size="32"
/>
<IconifyIcon v-else icon="lucide:circle-pause" :size="32" />
<span class="ml-2 text-xs" v-if="duration">{{ duration }} </span>
</div>
<span v-if="duration" class="ml-[5px] text-[11px]">
{{ duration }}
</span>
</el-icon>
<div v-if="content">
<ElTag type="success" size="small">语音识别</ElTag>
{{ content }}

View File

@@ -6,7 +6,7 @@ import { formatDateTime } from '@vben/utils';
import { getSimpleAccountList } from '#/api/mp/account';
let accountList: MpAccountApi.AccountSimple[] = [];
let accountList: MpAccountApi.Account[] = [];
getSimpleAccountList().then((data) => (accountList = data));
/** 搜索表单配置 */