fix: 合并冲突

pull/273/head
hw 2025-11-24 17:52:03 +08:00
parent 241c70ebb0
commit d29e53ff15
222 changed files with 3331 additions and 1115 deletions

View File

@ -4,7 +4,8 @@
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
"forgetPassword": "Forget Password",
"profile": "Profile"
},
"dashboard": {
"title": "Dashboard",

View File

@ -4,7 +4,8 @@
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
"forgetPassword": "忘记密码",
"profile": "个人中心"
},
"dashboard": {
"title": "概览",

View File

@ -90,7 +90,7 @@ export const useMallKefuStore = defineStore('mall-kefu', {
},
conversationSort() {
// 按置顶属性和最后消息时间排序
this.conversationList.sort((a, b) => {
this.conversationList.toSorted((a, b) => {
// 按照置顶排序,置顶的会在前面
if (a.adminPinned !== b.adminPinned) {
return a.adminPinned ? -1 : 1;

View File

@ -0,0 +1,102 @@
<script setup lang="ts">
import type { Recordable } from '@vben/types';
import type { VbenFormSchema } from '#/adapter/form';
import type { SystemUserProfileApi } from '#/api/system/user/profile';
import { computed, ref, watch } from 'vue';
import { ProfileBaseSetting, z } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { message } from 'ant-design-vue';
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 formSchema = computed((): VbenFormSchema[] => {
return [
{
label: '用户昵称',
fieldName: 'nickname',
component: 'Input',
componentProps: {
placeholder: '请输入用户昵称',
},
rules: 'required',
},
{
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(),
},
];
});
async function handleSubmit(values: Recordable<any>) {
try {
profileBaseSettingRef.value.getFormApi().setLoading(true);
//
await updateUserProfile(values as SystemUserProfileApi.UpdateProfileReqVO);
//
emit('success');
message.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"
@submit="handleSubmit"
/>
</template>

View File

@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ProfileNotificationSetting } from '@vben/common-ui';
const formSchema = computed(() => {
return [
{
value: true,
fieldName: 'accountPassword',
label: '账户密码',
description: '其他用户的消息将以站内信的形式通知',
},
{
value: true,
fieldName: 'systemMessage',
label: '系统消息',
description: '系统消息将以站内信的形式通知',
},
{
value: true,
fieldName: 'todoTask',
label: '待办任务',
description: '待办任务将以站内信的形式通知',
},
];
});
</script>
<template>
<ProfileNotificationSetting :form-schema="formSchema" />
</template>

View File

@ -0,0 +1,66 @@
<script setup lang="ts">
import type { VbenFormSchema } from '#/adapter/form';
import { computed, ref } from 'vue';
import { ProfilePasswordSetting, z } from '@vben/common-ui';
import { message } from 'ant-design-vue';
const profilePasswordSettingRef = ref();
const formSchema = computed((): VbenFormSchema[] => {
return [
{
fieldName: 'oldPassword',
label: '旧密码',
component: 'VbenInputPassword',
componentProps: {
placeholder: '请输入旧密码',
},
},
{
fieldName: 'newPassword',
label: '新密码',
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: '请输入新密码',
},
},
{
fieldName: 'confirmPassword',
label: '确认密码',
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: '请再次输入新密码',
},
dependencies: {
rules(values) {
const { newPassword } = values;
return z
.string({ required_error: '请再次输入新密码' })
.min(1, { message: '请再次输入新密码' })
.refine((value) => value === newPassword, {
message: '两次输入的密码不一致',
});
},
triggerFields: ['newPassword'],
},
},
];
});
function handleSubmit() {
message.success('密码修改成功');
}
</script>
<template>
<ProfilePasswordSetting
ref="profilePasswordSettingRef"
class="w-1/3"
:form-schema="formSchema"
@submit="handleSubmit"
/>
</template>

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ProfileSecuritySetting } from '@vben/common-ui';
const formSchema = computed(() => {
return [
{
value: true,
fieldName: 'accountPassword',
label: '账户密码',
description: '当前密码强度:强',
},
{
value: true,
fieldName: 'securityPhone',
label: '密保手机',
description: '已绑定手机138****8293',
},
{
value: true,
fieldName: 'securityQuestion',
label: '密保问题',
description: '未设置密保问题,密保问题可有效保护账户安全',
},
{
value: true,
fieldName: 'securityEmail',
label: '备用邮箱',
description: '已绑定邮箱ant***sign.com',
},
{
value: false,
fieldName: 'securityMfa',
label: 'MFA 设备',
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
},
];
});
</script>
<template>
<ProfileSecuritySetting :form-schema="formSchema" />
</template>

View File

@ -403,8 +403,8 @@ async function doSendMessageStream(userMessage: AiChatMessageApi.ChatMessage) {
const lastMessage =
activeMessageList.value[activeMessageList.value.length - 1];
//
lastMessage.reasoningContent =
(lastMessage.reasoningContent || '') +
lastMessage!.reasoningContent =
(lastMessage!.reasoningContent || '') +
data.receive.reasoningContent;
}
@ -554,7 +554,7 @@ onMounted(async () => {
<!-- 右侧详情部分 -->
<Layout class="bg-card mx-4">
<Layout.Header
class="!bg-card border-border flex !h-12 items-center justify-between border-b !px-4"
class="border-border !bg-card flex !h-12 items-center justify-between border-b !px-4"
>
<div class="text-lg font-bold">
{{ activeConversation?.title ? activeConversation?.title : '对话' }}

View File

@ -90,7 +90,7 @@ async function getChatConversationList() {
// 1.1
conversationList.value = await getChatConversationMyList();
// 1.2
conversationList.value.sort((a, b) => {
conversationList.value.toSorted((a, b) => {
return Number(b.createTime) - Number(a.createTime);
});
// 1.3

View File

@ -138,14 +138,14 @@ async function uploadFile(fileItem: FileItem) {
fileItem.progress = 100;
//
console.log('上传响应:', response);
console.warn('上传响应:', response);
// { url: '...' } { data: '...' }
const fileUrl =
(response as any)?.url || (response as any)?.data || response;
fileItem.url = fileUrl;
console.log('提取的文件 URL:', fileUrl);
console.warn('提取的文件 URL:', fileUrl);
// URL
if (fileUrl && typeof fileUrl === 'string') {

View File

@ -13,7 +13,6 @@ import {
getChatRole,
updateChatRole,
} from '#/api/ai/model/chatRole';
import {} from '#/api/bpm/model';
import { $t } from '#/locales';
import { useFormSchema } from '../data';

View File

@ -9,7 +9,6 @@ import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createModel, getModel, updateModel } from '#/api/ai/model/model';
import {} from '#/api/bpm/model';
import { $t } from '#/locales';
import { useFormSchema } from '../data';

View File

@ -17,7 +17,7 @@ const currentSong = ref({}); // 当前音乐
const mySongList = ref<Recordable<any>[]>([]);
const squareSongList = ref<Recordable<any>[]>([]);
function generateMusic(formData: Recordable<any>) {
function generateMusic(_formData: Recordable<any>) {
loading.value = true;
setTimeout(() => {
mySongList.value = Array.from({ length: 20 }, (_, index) => {

View File

@ -21,7 +21,7 @@ const emits = defineEmits<{
<span
v-for="tag in props.tags"
:key="tag.value"
class="bg-card border-card-100 mb-2 cursor-pointer rounded border-2 border-solid px-1 text-xs leading-6"
class="border-card-100 bg-card mb-2 cursor-pointer rounded border-2 border-solid px-1 text-xs leading-6"
:class="
modelValue === tag.value && '!border-primary-500 !text-primary-500'
"

View File

@ -62,16 +62,16 @@ const resetElement = () => {
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
//
boundaryEventType.value = elExtensionElements.value.values?.filter(
boundaryEventType.value = elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:BoundaryEventType`,
)?.[0];
);
if (boundaryEventType.value && boundaryEventType.value.value === 1) {
timeoutHandlerEnable.value = true;
configExtensions.value.push(boundaryEventType.value);
}
//
timeoutHandlerType.value = elExtensionElements.value.values?.filter(
timeoutHandlerType.value = elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:TimeoutHandlerType`,
)?.[0];
if (timeoutHandlerType.value) {

View File

@ -112,7 +112,7 @@ const resetCustomConfigList = () => {
//
approveType.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:ApproveType`,
)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:ApproveType`, {
@ -121,7 +121,7 @@ const resetCustomConfigList = () => {
//
assignStartUserHandlerTypeEl.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:AssignStartUserHandlerType`,
)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, {
@ -131,13 +131,13 @@ const resetCustomConfigList = () => {
//
rejectHandlerTypeEl.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:RejectHandlerType`,
)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 });
rejectHandlerType.value = rejectHandlerTypeEl.value.value;
returnNodeIdEl.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:RejectReturnTaskId`,
)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, {
@ -147,7 +147,7 @@ const resetCustomConfigList = () => {
//
assignEmptyHandlerTypeEl.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:AssignEmptyHandlerType`,
)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, {
@ -155,7 +155,7 @@ const resetCustomConfigList = () => {
});
assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value;
assignEmptyUserIdsEl.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:AssignEmptyUserIds`,
)?.[0] ||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, {
@ -172,7 +172,7 @@ const resetCustomConfigList = () => {
});
//
buttonsSettingEl.value = elExtensionElements.value.values?.filter(
buttonsSettingEl.value = elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:ButtonsSetting`,
);
if (buttonsSettingEl.value.length === 0) {
@ -189,7 +189,7 @@ const resetCustomConfigList = () => {
//
if (formType.value === BpmModelFormType.NORMAL) {
const fieldsPermissionList = elExtensionElements.value.values?.filter(
const fieldsPermissionList = elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:FieldsPermission`,
);
fieldsPermissionEl.value = [];
@ -206,21 +206,21 @@ const resetCustomConfigList = () => {
//
signEnable.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:SignEnable`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false });
//
reasonRequire.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) => ex.$type === `${prefix}:ReasonRequire`,
)?.[0] ||
) ||
bpmnInstances().moddle.create(`${prefix}:ReasonRequire`, { value: false });
// 便
otherExtensions.value =
elExtensionElements.value.values?.filter(
elExtensionElements.value.values?.find(
(ex: any) =>
ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
ex.$type !== `${prefix}:RejectHandlerType` &&

View File

@ -118,10 +118,10 @@ const resetTaskForm = () => {
const extensionElements =
businessObject?.extensionElements ??
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
userTaskForm.value.candidateStrategy = extensionElements.values?.find(
(ex: any) => ex.$type === `${prefix}:CandidateStrategy`,
)?.[0]?.value;
const candidateParamStr = extensionElements.values?.filter(
const candidateParamStr = extensionElements.values?.find(
(ex: any) => ex.$type === `${prefix}:CandidateParam`,
)?.[0]?.value;
if (candidateParamStr && candidateParamStr.length > 0) {

View File

@ -112,7 +112,7 @@ function setDuration(type, val) {
// ISO 8601
let d = isoDuration.value;
if (d.includes(type)) {
d = d.replace(new RegExp(`\\d+${type}`), val + type);
d = d.replace(new RegExp(String.raw`\d+${type}`), val + type);
} else {
d += val + type;
}

View File

@ -82,10 +82,12 @@ export function updateElementExtensions(element, extensionList) {
}
// 创建一个id
export function uuid(length = 8, chars?) {
export function uuid(
length = 8,
charsString = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
) {
let result = '';
const charsString =
chars || '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (let i = length; i > 0; --i) {
result += charsString[Math.floor(Math.random() * charsString.length)];
}

View File

@ -6,7 +6,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -54,7 +54,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -102,7 +102,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -150,7 +150,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -198,7 +198,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -246,7 +246,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -294,7 +294,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -342,7 +342,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,

View File

@ -24,7 +24,7 @@ onMounted(() => {
{ name: '定制', value: 310 },
{ name: '技术支持', value: 274 },
{ name: '远程', value: 400 },
].sort((a, b) => {
].toSorted((a, b) => {
return a.value - b.value;
}),
name: '商业占比',

View File

@ -128,6 +128,7 @@ function handleOpenSaleOut() {
function handleAddSaleOut(rows: ErpSaleOutApi.SaleOut[]) {
rows.forEach((row) => {
// TODO
const newItem: ErpFinanceReceiptApi.FinanceReceiptItem = {
bizId: row.id,
bizType: ErpBizType.SALE_OUT,
@ -153,6 +154,7 @@ function handleOpenSaleReturn() {
}
function handleAddSaleReturn(rows: ErpSaleReturnApi.SaleReturn[]) {
// TODO
rows.forEach((row) => {
const newItem: ErpFinanceReceiptApi.FinanceReceiptItem = {
bizId: row.id,

View File

@ -151,6 +151,7 @@ async function handleWarehouseChange(row: ErpPurchaseInApi.PurchaseInItem) {
/** 处理行数据变更 */
function handleRowChange(row: any) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index === -1) {
tableData.value.push(row);

View File

@ -142,6 +142,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -131,6 +131,7 @@ watch(
/** 处理删除 */
function handleDelete(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);
@ -153,6 +154,7 @@ async function handleWarehouseChange(
/** 处理行数据变更 */
function handleRowChange(row: any) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index === -1) {
tableData.value.push(row);

View File

@ -142,6 +142,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpSaleOrderApi.SaleOrderItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -131,6 +131,7 @@ watch(
/** 处理删除 */
function handleDelete(row: ErpSaleOutApi.SaleOutItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -131,6 +131,7 @@ watch(
/** 处理删除 */
function handleDelete(row: ErpSaleReturnApi.SaleReturnItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -106,6 +106,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpStockCheckApi.StockCheckItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -98,6 +98,7 @@ function handleAdd() {
totalPrice: undefined,
remark: undefined,
};
// TODO
tableData.value.push(newRow);
//
emit('update:items', [...tableData.value]);
@ -105,6 +106,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpStockInApi.StockInItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -106,6 +106,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpStockMoveApi.StockMoveItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -105,6 +105,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpStockOutApi.StockOutItem) {
// TODO
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -47,7 +47,7 @@ const { status, data, send, close, open } = useWebSocket(server.value, {
const messageList = ref(
[] as { text: string; time: number; type?: string; userId?: string }[],
); //
const messageReverseList = computed(() => [...messageList.value].reverse());
const messageReverseList = computed(() => [...messageList.value].toReversed());
watchEffect(() => {
if (!data.value) {
return;

View File

@ -553,17 +553,17 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
.toolbar-wrapper {
padding: 16px;
background-color: hsl(var(--card) / 0.9);
background-color: hsl(var(--card) / 90%);
border: 1px solid hsl(var(--border) / 60%);
border-radius: 8px;
border: 1px solid hsl(var(--border) / 0.6);
}
.chart-container,
.table-container {
padding: 16px;
background-color: hsl(var(--card));
border: 1px solid hsl(var(--border) / 60%);
border-radius: 8px;
border: 1px solid hsl(var(--border) / 0.6);
}
}
</style>

View File

@ -22,8 +22,7 @@ import {
import { getLatestDeviceProperties } from '#/api/iot/device/device';
import DeviceDetailsThingModelPropertyHistory
from './device-details-thing-model-property-history.vue';
import DeviceDetailsThingModelPropertyHistory from './device-details-thing-model-property-history.vue';
const props = defineProps<{ deviceId: number }>();

View File

@ -176,7 +176,7 @@ function getDeviceTypeColor(deviceType: number) {
}
//
function getStatusInfo(state: number | string | null | undefined) {
function getStatusInfo(state: null | number | string | undefined) {
const parsedState = Number(state);
const hasNumericState = Number.isFinite(parsedState);
const fallback = hasNumericState
@ -396,21 +396,21 @@ defineExpose({
.device-card {
height: 100%;
overflow: hidden;
background: hsl(var(--card) / 0.95);
border: 1px solid hsl(var(--border) / 0.6);
background: hsl(var(--card) / 95%);
border: 1px solid hsl(var(--border) / 60%);
border-radius: 8px;
box-shadow:
0 1px 2px 0 hsl(var(--foreground) / 0.04),
0 1px 6px -1px hsl(var(--foreground) / 0.05),
0 2px 4px 0 hsl(var(--foreground) / 0.05);
0 1px 2px 0 hsl(var(--foreground) / 4%),
0 1px 6px -1px hsl(var(--foreground) / 5%),
0 2px 4px 0 hsl(var(--foreground) / 5%);
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: hsl(var(--border));
box-shadow:
0 1px 2px -2px hsl(var(--foreground) / 0.12),
0 3px 6px 0 hsl(var(--foreground) / 0.1),
0 5px 12px 4px hsl(var(--foreground) / 0.08);
0 1px 2px -2px hsl(var(--foreground) / 12%),
0 3px 6px 0 hsl(var(--foreground) / 10%),
0 5px 12px 4px hsl(var(--foreground) / 8%);
transform: translateY(-4px);
}
@ -473,7 +473,7 @@ defineExpose({
font-size: 16px;
font-weight: 600;
line-height: 24px;
color: hsl(var(--foreground) / 0.9);
color: hsl(var(--foreground) / 90%);
white-space: nowrap;
}
@ -496,7 +496,7 @@ defineExpose({
.label {
flex-shrink: 0;
font-size: 13px;
color: hsl(var(--foreground) / 0.6);
color: hsl(var(--foreground) / 60%);
}
.value {
@ -505,7 +505,7 @@ defineExpose({
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px;
color: hsl(var(--foreground) / 0.85);
color: hsl(var(--foreground) / 85%);
text-align: right;
white-space: nowrap;
@ -515,7 +515,7 @@ defineExpose({
transition: color 0.2s;
&:hover {
color: hsl(var(--primary) / 0.85);
color: hsl(var(--primary) / 85%);
}
}
@ -524,7 +524,7 @@ defineExpose({
'SF Mono', Monaco, Inconsolata, 'Fira Code', Consolas, monospace;
font-size: 12px;
font-weight: 500;
color: hsl(var(--foreground) / 0.6);
color: hsl(var(--foreground) / 60%);
}
}
}
@ -537,7 +537,7 @@ defineExpose({
display: flex;
gap: 8px;
padding-top: 12px;
border-top: 1px solid hsl(var(--border) / 0.4);
border-top: 1px solid hsl(var(--border) / 40%);
.action-btn {
display: flex;
@ -561,8 +561,8 @@ defineExpose({
&.btn-edit {
color: hsl(var(--primary));
background: hsl(var(--primary) / 0.12);
border-color: hsl(var(--primary) / 0.25);
background: hsl(var(--primary) / 12%);
border-color: hsl(var(--primary) / 25%);
&:hover {
color: hsl(var(--primary-foreground));
@ -573,8 +573,8 @@ defineExpose({
&.btn-view {
color: hsl(var(--warning));
background: hsl(var(--warning) / 0.12);
border-color: hsl(var(--warning) / 0.25);
background: hsl(var(--warning) / 12%);
border-color: hsl(var(--warning) / 25%);
&:hover {
color: #fff;
@ -590,11 +590,7 @@ defineExpose({
hsl(var(--accent)) 40%,
hsl(var(--card)) 60%
);
border-color: color-mix(
in srgb,
hsl(var(--accent)) 55%,
transparent
);
border-color: color-mix(in srgb, hsl(var(--accent)) 55%, transparent);
&:hover {
color: hsl(var(--accent-foreground));
@ -607,8 +603,8 @@ defineExpose({
flex: 0 0 32px;
padding: 4px;
color: hsl(var(--destructive));
background: hsl(var(--destructive) / 0.12);
border-color: hsl(var(--destructive) / 0.3);
background: hsl(var(--destructive) / 12%);
border-color: hsl(var(--destructive) / 30%);
&:hover {
color: hsl(var(--destructive-foreground));

View File

@ -1,12 +1,16 @@
<script setup lang="ts">
import {ComparisonCard, Page} from '@vben/common-ui';
// TODO @
import type { StatsData } from './data';
import {Col, Row} from 'ant-design-vue';
import {onMounted, ref} from 'vue';
import { onMounted, ref } from 'vue';
import {getStatisticsSummary} from '#/api/iot/statistics';
import { ComparisonCard, Page } from '@vben/common-ui';
import {defaultStatsData, type StatsData} from './data';
import { Col, Row } from 'ant-design-vue';
import { getStatisticsSummary } from '#/api/iot/statistics';
import { defaultStatsData } from './data';
import DeviceCountCard from './modules/device-count-card.vue';
import DeviceStateCountCard from './modules/device-state-count-card.vue';
import MessageTrendCard from './modules/message-trend-card.vue';

View File

@ -140,9 +140,9 @@ onMounted(() => {
<span class="text-base font-medium text-gray-600">消息量统计</span>
<div class="flex flex-wrap items-center gap-4">
<div class="flex items-center gap-3">
<span class="whitespace-nowrap text-sm text-gray-500"
>时间范围</span
>
<span class="whitespace-nowrap text-sm text-gray-500">
时间范围
</span>
<ShortcutDateRangePicker @change="handleDateRangeChange" />
</div>
<div class="flex items-center gap-2">
@ -178,4 +178,4 @@ onMounted(() => {
<EchartsUI ref="messageChartRef" class="h-[300px] w-full" />
</div>
</Card>
</template>
</template>

View File

@ -1,6 +1,6 @@
export {default as HttpConfigForm} from './http-config-form.vue';
export {default as KafkaMqConfigForm} from './kafka-mq-config-form.vue';
export {default as MqttConfigForm} from './mqtt-config-form.vue';
export {default as RabbitMqConfigForm} from './rabbit-mq-config-form.vue';
export {default as RedisStreamConfigForm} from './redis-stream-config-form.vue';
export {default as RocketMqConfigForm} from './rocket-mq-config-form.vue';
export { default as HttpConfigForm } from './http-config-form.vue';
export { default as KafkaMqConfigForm } from './kafka-mq-config-form.vue';
export { default as MqttConfigForm } from './mqtt-config-form.vue';
export { default as RabbitMqConfigForm } from './rabbit-mq-config-form.vue';
export { default as RedisStreamConfigForm } from './redis-stream-config-form.vue';
export { default as RocketMqConfigForm } from './rocket-mq-config-form.vue';

View File

@ -135,7 +135,8 @@ function handleDeviceChange(_: any) {
/**
* 处理属性变化事件
* @param propertyInfo 属性信息对象
* @param propertyInfo.config 属性配置
* @param propertyInfo.type 属性类型
*/
function handlePropertyChange(propertyInfo: { config: any; type: string }) {
propertyType.value = propertyInfo.type;

View File

@ -108,10 +108,10 @@ function updateCondition(index: number, condition: TriggerCondition) {
>
<!-- 条件配置 -->
<div
class="rounded-3px border-border bg-fill-color-blank border shadow-sm"
class="rounded-3px bg-fill-color-blank border-border border shadow-sm"
>
<div
class="rounded-t-1 border-border bg-fill-color-blank flex items-center justify-between border-b p-3"
class="rounded-t-1 bg-fill-color-blank border-border flex items-center justify-between border-b p-3"
>
<div class="flex items-center gap-2">
<div

View File

@ -507,7 +507,7 @@ watch(
{{ JSON_PARAMS_INPUT_CONSTANTS.COMPLETE_JSON_FORMAT }}
</div>
<pre
class="bg-card border-l-3px border-primary text-primary overflow-x-auto rounded-lg p-3 text-sm"
class="border-l-3px border-primary bg-card text-primary overflow-x-auto rounded-lg p-3 text-sm"
>
<code>{{ generateExampleJson() }}</code>
</pre>

View File

@ -275,7 +275,7 @@ function onActionTypeChange(action: Action, type: any) {
action.type ===
IotRuleSceneActionTypeEnum.ALERT_TRIGGER.toString()
"
class="border-border bg-fill-color-blank rounded-lg border p-4"
class="bg-fill-color-blank border-border rounded-lg border p-4"
>
<div class="mb-2 flex items-center gap-2">
<IconifyIcon icon="ep:warning" class="text-warning text-base" />

View File

@ -8,7 +8,8 @@ import { IconifyIcon } from '@vben/icons';
import { useVModel } from '@vueuse/core';
import { Card, Col, Form, Input, Radio, Row } from 'ant-design-vue';
import { DictTag } from "#/components/dict-tag";
import { DictTag } from '#/components/dict-tag';
/** 基础信息配置组件 */
defineOptions({ name: 'BasicInfoSection' });

View File

@ -7,7 +7,7 @@ import { DICT_TYPE } from '@vben/constants';
import { Select } from 'ant-design-vue';
import { getSimpleProductList } from '#/api/iot/product/product';
import { DictTag } from "#/components/dict-tag";
import { DictTag } from '#/components/dict-tag';
/** 产品选择器组件 */
defineOptions({ name: 'ProductSelector' });
@ -78,7 +78,7 @@ onMounted(() => {
{{ product.productKey }}
</div>
</div>
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
</div>
</Select.Option>
</Select>

View File

@ -41,6 +41,7 @@ const emit = defineEmits<{
(e: 'change', value: { config: any; type: string }): void;
}>();
// TODO
/** 属性选择器内部使用的统一数据结构 */
interface PropertySelectorItem {
identifier: string;

View File

@ -1,4 +1,4 @@
export {default as ThingModelArrayDataSpecs} from './thing-model-array-data-specs.vue';
export {default as ThingModelEnumDataSpecs} from './thing-model-enum-data-specs.vue';
export {default as ThingModelNumberDataSpecs} from './thing-model-number-data-specs.vue';
export {default as ThingModelStructDataSpecs} from './thing-model-struct-data-specs.vue';
export { default as ThingModelArrayDataSpecs } from './thing-model-array-data-specs.vue';
export { default as ThingModelEnumDataSpecs } from './thing-model-enum-data-specs.vue';
export { default as ThingModelNumberDataSpecs } from './thing-model-number-data-specs.vue';
export { default as ThingModelStructDataSpecs } from './thing-model-struct-data-specs.vue';

View File

@ -126,7 +126,8 @@ function validateSku() {
/**
* 选择时触发
*
* @param records 传递过来的选中的 sku 是一个数组
* @param {object} param0 参数对象
* @param {MallSpuApi.Sku[]} param0.records 传递过来的选中的 sku 是一个数组
*/
function handleSelectionChange({ records }: { records: MallSpuApi.Sku[] }) {
emit('selectionChange', records);

View File

@ -111,6 +111,7 @@ function emitActivityChange() {
>
<Tooltip :title="activity.name">
<div class="relative h-full w-full">
<!-- TODO @芋艿 -->
<Image
:src="activity.picUrl"
class="h-full w-full rounded-lg object-cover"

View File

@ -99,8 +99,8 @@ function handleAppLinkSelected(appLink: AppLink) {
/**
* 处理右侧链接列表滚动
*
* @param {object} param0 滚动事件参数
* @param {number} param0.scrollTop 滚动条的位置
* @param {Event} event 滚动事件
* @param {number} event.target.scrollTop 滚动条的位置
*/
function handleScroll(event: Event) {
const scrollTop = (event.target as HTMLDivElement).scrollTop;

View File

@ -202,7 +202,7 @@ function eachCube(callback: (x: number, y: number, cube: Cube) => void) {
<td
v-for="(cube, col) in rowCubes"
:key="col"
class="active:bg-primary-200 hover:bg-primary-100 box-border cursor-pointer border text-center align-middle"
class="hover:bg-primary-100 active:bg-primary-200 box-border cursor-pointer border text-center align-middle"
:class="[{ active: cube.active }]"
:style="{
width: `${cubeSize}px`,
@ -219,7 +219,7 @@ function eachCube(callback: (x: number, y: number, cube: Cube) => void) {
<div
v-for="(hotArea, index) in hotAreas"
:key="index"
class="bg-primary-200 border-primary absolute box-border flex items-center justify-center border"
class="border-primary bg-primary-200 absolute box-border flex items-center justify-center border"
:style="{
top: `${cubeSize * hotArea.top}px`,
left: `${cubeSize * hotArea.left}px`,

View File

@ -57,10 +57,10 @@ export function isContains(hotArea: Rect, point: Point): boolean {
*/
export function createRect(a: Point, b: Point): Rect {
// 计算矩形的范围
let [left, left2] = [a.x, b.x].sort();
let [left, left2] = [a.x, b.x].toSorted();
left = left ?? 0;
left2 = left2 ?? 0;
let [top, top2] = [a.y, b.y].sort();
let [top, top2] = [a.y, b.y].toSorted();
top = top ?? 0;
top2 = top2 ?? 0;
const right = left2 + 1;

View File

@ -91,7 +91,7 @@ function pushMessage(message: any) {
/** 按照时间倒序,获取消息列表 */
const getMessageList0 = computed(() => {
// 使
return [...messageList.value].sort(
return [...messageList.value].toSorted(
(a: any, b: any) => a.createTime - b.createTime,
);
});

View File

@ -12,7 +12,9 @@ import { getRangePickerDefaultProps } from '#/utils';
/** 关联数据 */
const userStore = useUserStore();
const pickUpStoreList = ref<MallDeliveryPickUpStoreApi.DeliveryPickUpStore[]>([]);
const pickUpStoreList = ref<MallDeliveryPickUpStoreApi.DeliveryPickUpStore[]>(
[],
);
getSimpleDeliveryPickUpStoreList().then((res) => {
pickUpStoreList.value = res;
// 移除自己无法核销的门店

View File

@ -113,7 +113,8 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>();
const data =
modalApi.getData<MallDeliveryPickUpStoreApi.DeliveryPickUpStore>();
if (!data || !data.id) {
//
await initTencentLbsMap();

View File

@ -111,20 +111,22 @@ const [Modal, modalApi] = useVbenModal({
if (data.row?.id) {
//
const rowData = data.row;
const formValues: any = { ...rowData };
formValues.reply = {
type: rowData.responseMessageType,
accountId: data.accountId || -1,
content: rowData.responseContent,
mediaId: rowData.responseMediaId,
url: rowData.responseMediaUrl,
title: rowData.responseTitle,
description: rowData.responseDescription,
thumbMediaId: rowData.responseThumbMediaId,
thumbMediaUrl: rowData.responseThumbMediaUrl,
articles: rowData.responseArticles,
musicUrl: rowData.responseMusicUrl,
hqMusicUrl: rowData.responseHqMusicUrl,
const formValues: any = {
...rowData,
reply: {
type: rowData.responseMessageType,
accountId: data.accountId || -1,
content: rowData.responseContent,
mediaId: rowData.responseMediaId,
url: rowData.responseMediaUrl,
title: rowData.responseTitle,
description: rowData.responseDescription,
thumbMediaId: rowData.responseThumbMediaId,
thumbMediaUrl: rowData.responseThumbMediaUrl,
articles: rowData.responseArticles,
musicUrl: rowData.responseMusicUrl,
hqMusicUrl: rowData.responseHqMusicUrl,
},
};
await formApi.setValues(formValues);
} else {

View File

@ -106,7 +106,7 @@ async function getPage(page: any, params: any = null) {
const scrollHeight = msgDivRef.value?.scrollHeight ?? 0;
//
const data = dataTemp.list.reverse();
const data = dataTemp.list.toReversed();
list.value = [...data, ...list.value];
loading.value = false;
if (data.length < queryParams.pageSize || data.length === 0) {

View File

@ -72,12 +72,12 @@ async function handleDelete(row: MpDraftApi.DraftArticle) {
return;
}
const hideLoading = message.loading({
content: '删除中...',
content: $t('ui.actionMessage.deleting'),
duration: 0,
});
try {
await deleteDraft(accountId, row.mediaId);
message.success('删除成功');
message.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
hideLoading();

View File

@ -0,0 +1,59 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import type { MpAccountApi } from '#/api/mp/account';
import { 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: 'Select',
componentProps: {
options: accountList.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择公众号',
clearable: true,
},
},
];
}
/** 表格列配置 */
export function useGridColumns(): VxeGridPropTypes.Columns {
return [
{
field: 'cover',
title: '图片',
width: 360,
slots: { default: 'cover' },
},
{
field: 'title',
title: '标题',
slots: { default: 'title' },
},
{
field: 'updateTime',
title: '修改时间',
formatter: ({ row }) => {
return formatDateTime(row.updateTime * 1000);
},
},
{
title: '操作',
width: 120,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@ -1,29 +1,173 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MpFreePublishApi } from '#/api/mp/freePublish';
import { DocAlert, Page } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
import { Image, message, Typography } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteFreePublish, getFreePublishPage } from '#/api/mp/freePublish';
import { $t } from '#/locales';
import { WxAccountSelect } from '#/views/mp/components';
import { useGridColumns, useGridFormSchema } from './data';
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 公众号变化时查询数据 */
function handleAccountChange(accountId: number) {
gridApi.formApi.setValues({ accountId });
gridApi.formApi.submitForm();
}
/** 删除文章 */
async function handleDelete(row: MpFreePublishApi.FreePublish) {
const formValues = await gridApi.formApi.getValues();
const accountId = formValues.accountId;
if (!accountId) {
message.warning('请先选择公众号');
return;
}
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
});
try {
await deleteFreePublish(accountId, row.articleId!);
message.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
if (!formValues.accountId) {
return {
list: [],
total: 0,
};
}
const res = await getFreePublishPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
// thumbUrl picUrl wx-news
res.list.forEach((record: any) => {
const newsList = record.content?.newsItem;
if (newsList) {
newsList.forEach((item: any) => {
item.picUrl = item.thumbUrl || item.picUrl;
});
}
});
// TODO @jaweArticle
return {
list: res.list as unknown as Article[],
total: res.total,
};
},
},
},
rowConfig: {
keyField: 'articleId',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<Article>,
});
</script>
<template>
<Page>
<DocAlert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
<Button
danger
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
>
该功能支持 Vue3 + element-plus 版本
</Button>
<br />
<Button
type="link"
target="_blank"
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/mp/freePublish/index"
>
可参考
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/mp/freePublish/index
代码pull request 贡献给我们
</Button>
<Page auto-content-height>
<template #doc>
<DocAlert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
</template>
<Grid table-title="">
<template #form-accountId>
<WxAccountSelect @change="handleAccountChange" />
</template>
<template #cover="{ row }">
<div
v-if="row.content?.newsItem && row.content.newsItem.length > 0"
class="flex flex-col items-center justify-center gap-1"
>
<Image
v-for="(item, index) in row.content.newsItem"
:key="index"
:src="item.picUrl || item.thumbUrl"
class="h-36 !w-[300px] rounded object-cover"
:alt="`文章 ${index + 1} 封面图`"
/>
</div>
<span v-else class="text-gray-400">-</span>
</template>
<template #title="{ row }">
<div
v-if="row.content?.newsItem && row.content.newsItem.length > 0"
class="space-y-1"
>
<div
v-for="(item, index) in row.content.newsItem"
:key="index"
class="flex h-36 items-center justify-center"
>
<Typography.Link :href="(item as any).url" target="_blank">
{{ item.title }}
</Typography.Link>
</div>
</div>
<span v-else class="text-gray-400">-</span>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '删除',
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mp:free-publish:delete'],
popConfirm: {
title: '是否确认删除此数据?',
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>
<style lang="scss" scoped>
:deep(.vxe-table--body-wrapper) {
.vxe-table--body {
.vxe-body--column {
.vxe-cell {
height: auto !important;
padding: 0;
}
}
}
}
</style>

View File

@ -134,20 +134,20 @@ function menuListToFrontend(list: any[]) {
list.forEach((item: RawMenu) => {
const menu: any = {
...item,
};
menu.reply = {
type: item.replyMessageType,
accountId: item.accountId,
content: item.replyContent,
mediaId: item.replyMediaId,
url: item.replyMediaUrl,
title: item.replyTitle,
description: item.replyDescription,
thumbMediaId: item.replyThumbMediaId,
thumbMediaUrl: item.replyThumbMediaUrl,
articles: item.replyArticles,
musicUrl: item.replyMusicUrl,
hqMusicUrl: item.replyHqMusicUrl,
reply: {
type: item.replyMessageType,
accountId: item.accountId,
content: item.replyContent,
mediaId: item.replyMediaId,
url: item.replyMediaUrl,
title: item.replyTitle,
description: item.replyDescription,
thumbMediaId: item.replyThumbMediaId,
thumbMediaUrl: item.replyThumbMediaUrl,
articles: item.replyArticles,
musicUrl: item.replyMusicUrl,
hqMusicUrl: item.replyHqMusicUrl,
},
};
result.push(menu as RawMenu);
});
@ -275,23 +275,22 @@ function menuListToBackend() {
/** 将前端的 menu转换成后端接收的 menu */
// TODO: @ API
function menuToBackend(menu: any) {
const result = {
return {
...menu,
children: undefined, //
reply: undefined, //
replyMessageType: menu.reply.type,
replyContent: menu.reply.content,
replyMediaId: menu.reply.mediaId,
replyMediaUrl: menu.reply.url,
replyTitle: menu.reply.title,
replyDescription: menu.reply.description,
replyThumbMediaId: menu.reply.thumbMediaId,
replyThumbMediaUrl: menu.reply.thumbMediaUrl,
replyArticles: menu.reply.articles,
replyMusicUrl: menu.reply.musicUrl,
replyHqMusicUrl: menu.reply.hqMusicUrl,
};
result.replyMessageType = menu.reply.type;
result.replyContent = menu.reply.content;
result.replyMediaId = menu.reply.mediaId;
result.replyMediaUrl = menu.reply.url;
result.replyTitle = menu.reply.title;
result.replyDescription = menu.reply.description;
result.replyThumbMediaId = menu.reply.thumbMediaId;
result.replyThumbMediaUrl = menu.reply.thumbMediaUrl;
result.replyArticles = menu.reply.articles;
result.replyMusicUrl = menu.reply.musicUrl;
result.replyHqMusicUrl = menu.reply.hqMusicUrl;
return result;
}
</script>

View File

@ -258,9 +258,7 @@ export function useAppFormSchema(): VbenFormSchema[] {
/** 渠道新增/修改的表单 */
export function useChannelFormSchema(formType: string = ''): VbenFormSchema[] {
const schema: VbenFormSchema[] = [];
// 添加通用字段
schema.push(
const schema: VbenFormSchema[] = [
{
component: 'Input',
fieldName: 'id',
@ -309,7 +307,8 @@ export function useChannelFormSchema(formType: string = ''): VbenFormSchema[] {
optionType: 'button',
},
},
);
];
// 添加通用字段
// 根据类型添加特定字段
if (formType.includes('alipay_')) {
schema.push(

View File

@ -4,7 +4,8 @@
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
"forgetPassword": "Forget Password",
"profile": "Profile"
},
"dashboard": {
"title": "Dashboard",

View File

@ -4,7 +4,8 @@
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
"forgetPassword": "忘记密码",
"profile": "个人中心"
},
"dashboard": {
"title": "概览",

View File

@ -90,8 +90,7 @@ const routes: RouteRecordRaw[] = [
title: '客户统计',
activePath: '/crm/statistics/customer',
},
component: () =>
import('#/views/crm/statistics/customer/index.vue'),
component: () => import('#/views/crm/statistics/customer/index.vue'),
},
{
path: 'statistics/funnel',
@ -109,8 +108,7 @@ const routes: RouteRecordRaw[] = [
title: '员工业绩',
activePath: '/crm/statistics/performance',
},
component: () =>
import('#/views/crm/statistics/performance/index.vue'),
component: () => import('#/views/crm/statistics/performance/index.vue'),
},
{
path: 'statistics/portrait',
@ -119,8 +117,7 @@ const routes: RouteRecordRaw[] = [
title: '客户画像',
activePath: '/crm/statistics/portrait',
},
component: () =>
import('#/views/crm/statistics/portrait/index.vue'),
component: () => import('#/views/crm/statistics/portrait/index.vue'),
},
{
path: 'statistics/rank',

View File

@ -80,7 +80,7 @@ export const useMallKefuStore = defineStore('mall-kefu', {
},
conversationSort() {
// 按置顶属性和最后消息时间排序
this.conversationList.sort((a, b) => {
this.conversationList.toSorted((a, b) => {
// 按照置顶排序,置顶的会在前面
if (a.adminPinned !== b.adminPinned) {
return a.adminPinned ? -1 : 1;

View File

@ -0,0 +1,65 @@
<script setup lang="ts">
import type { BasicOption } from '@vben/types';
import type { VbenFormSchema } from '#/adapter/form';
import { computed, onMounted, ref } from 'vue';
import { ProfileBaseSetting } from '@vben/common-ui';
import { getUserInfoApi } from '#/api';
const profileBaseSettingRef = ref();
const MOCK_ROLES_OPTIONS: BasicOption[] = [
{
label: '管理员',
value: 'super',
},
{
label: '用户',
value: 'user',
},
{
label: '测试',
value: 'test',
},
];
const formSchema = computed((): VbenFormSchema[] => {
return [
{
fieldName: 'realName',
component: 'Input',
label: '姓名',
},
{
fieldName: 'username',
component: 'Input',
label: '用户名',
},
{
fieldName: 'roles',
component: 'Select',
componentProps: {
mode: 'tags',
options: MOCK_ROLES_OPTIONS,
},
label: '角色',
},
{
fieldName: 'introduction',
component: 'Textarea',
label: '个人简介',
},
];
});
onMounted(async () => {
const data = await getUserInfoApi();
profileBaseSettingRef.value.getFormApi().setValues(data);
});
</script>
<template>
<ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
</template>

View File

@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ProfileNotificationSetting } from '@vben/common-ui';
const formSchema = computed(() => {
return [
{
value: true,
fieldName: 'accountPassword',
label: '账户密码',
description: '其他用户的消息将以站内信的形式通知',
},
{
value: true,
fieldName: 'systemMessage',
label: '系统消息',
description: '系统消息将以站内信的形式通知',
},
{
value: true,
fieldName: 'todoTask',
label: '待办任务',
description: '待办任务将以站内信的形式通知',
},
];
});
</script>
<template>
<ProfileNotificationSetting :form-schema="formSchema" />
</template>

View File

@ -0,0 +1,66 @@
<script setup lang="ts">
import type { VbenFormSchema } from '#/adapter/form';
import { computed, ref } from 'vue';
import { ProfilePasswordSetting, z } from '@vben/common-ui';
import { ElMessage } from 'element-plus';
const profilePasswordSettingRef = ref();
const formSchema = computed((): VbenFormSchema[] => {
return [
{
fieldName: 'oldPassword',
label: '旧密码',
component: 'VbenInputPassword',
componentProps: {
placeholder: '请输入旧密码',
},
},
{
fieldName: 'newPassword',
label: '新密码',
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: '请输入新密码',
},
},
{
fieldName: 'confirmPassword',
label: '确认密码',
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: '请再次输入新密码',
},
dependencies: {
rules(values) {
const { newPassword } = values;
return z
.string({ required_error: '请再次输入新密码' })
.min(1, { message: '请再次输入新密码' })
.refine((value) => value === newPassword, {
message: '两次输入的密码不一致',
});
},
triggerFields: ['newPassword'],
},
},
];
});
function handleSubmit() {
ElMessage.success('密码修改成功');
}
</script>
<template>
<ProfilePasswordSetting
ref="profilePasswordSettingRef"
class="w-1/3"
:form-schema="formSchema"
@submit="handleSubmit"
/>
</template>

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ProfileSecuritySetting } from '@vben/common-ui';
const formSchema = computed(() => {
return [
{
value: true,
fieldName: 'accountPassword',
label: '账户密码',
description: '当前密码强度:强',
},
{
value: true,
fieldName: 'securityPhone',
label: '密保手机',
description: '已绑定手机138****8293',
},
{
value: true,
fieldName: 'securityQuestion',
label: '密保问题',
description: '未设置密保问题,密保问题可有效保护账户安全',
},
{
value: true,
fieldName: 'securityEmail',
label: '备用邮箱',
description: '已绑定邮箱ant***sign.com',
},
{
value: false,
fieldName: 'securityMfa',
label: 'MFA 设备',
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
},
];
});
</script>
<template>
<ProfileSecuritySetting :form-schema="formSchema" />
</template>

View File

@ -411,8 +411,8 @@ async function doSendMessageStream(userMessage: AiChatMessageApi.ChatMessage) {
const lastMessage =
activeMessageList.value[activeMessageList.value.length - 1];
//
lastMessage.reasoningContent =
(lastMessage.reasoningContent || '') +
lastMessage!.reasoningContent =
(lastMessage!.reasoningContent || '') +
data.receive.reasoningContent;
}
@ -565,7 +565,7 @@ onMounted(async () => {
<!-- 右侧详情部分 -->
<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"
class="border-border !bg-card flex !h-12 items-center justify-between border-b !px-4"
>
<div class="text-lg font-bold">
{{ activeConversation?.title ? activeConversation?.title : '对话' }}

View File

@ -97,7 +97,7 @@ async function getChatConversationList() {
// 1.1
conversationList.value = await getChatConversationMyList();
// 1.2
conversationList.value.sort((a, b) => {
conversationList.value.toSorted((a, b) => {
return Number(b.createTime) - Number(a.createTime);
});
// 1.3

View File

@ -138,14 +138,14 @@ async function uploadFile(fileItem: FileItem) {
fileItem.progress = 100;
//
console.log('上传响应:', response);
console.warn('上传响应:', response);
// { url: '...' } { data: '...' }
const fileUrl =
(response as any)?.url || (response as any)?.data || response;
fileItem.url = fileUrl;
console.log('提取的文件 URL:', fileUrl);
console.warn('提取的文件 URL:', fileUrl);
// URL
if (fileUrl && typeof fileUrl === 'string') {

View File

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

View File

@ -13,7 +13,6 @@ import {
getChatRole,
updateChatRole,
} from '#/api/ai/model/chatRole';
import {} from '#/api/bpm/model';
import { $t } from '#/locales';
import { useFormSchema } from '../data';

View File

@ -9,7 +9,6 @@ import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { createModel, getModel, updateModel } from '#/api/ai/model/model';
import {} from '#/api/bpm/model';
import { $t } from '#/locales';
import { useFormSchema } from '../data';

View File

@ -17,7 +17,7 @@ const currentSong = ref({}); // 当前音乐
const mySongList = ref<Recordable<any>[]>([]);
const squareSongList = ref<Recordable<any>[]>([]);
function generateMusic(formData: Recordable<any>) {
function generateMusic(_formData: Recordable<any>) {
loading.value = true;
setTimeout(() => {
mySongList.value = Array.from({ length: 20 }, (_, index) => {

View File

@ -21,7 +21,7 @@ const emits = defineEmits<{
<span
v-for="tag in props.tags"
:key="tag.value"
class="bg-card border-card-100 mb-2 cursor-pointer rounded border-2 border-solid px-1 text-xs leading-6"
class="border-card-100 bg-card mb-2 cursor-pointer rounded border-2 border-solid px-1 text-xs leading-6"
:class="
modelValue === tag.value && '!border-primary-500 !text-primary-500'
"

View File

@ -6,7 +6,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -54,7 +54,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -102,7 +102,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -150,7 +150,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -198,7 +198,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -246,7 +246,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -294,7 +294,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,
@ -342,7 +342,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
return {
dataset: {
dimensions: ['nickname', 'count'],
source: cloneDeep(res).reverse(),
source: cloneDeep(res).toReversed(),
},
grid: {
left: 20,

View File

@ -24,7 +24,7 @@ onMounted(() => {
{ name: '定制', value: 310 },
{ name: '技术支持', value: 274 },
{ name: '远程', value: 400 },
].sort((a, b) => {
].toSorted((a, b) => {
return a.value - b.value;
}),
name: '商业占比',

View File

@ -128,6 +128,7 @@ const handleOpenPurchaseIn = () => {
const handleAddPurchaseIn = (rows: ErpPurchaseInApi.PurchaseIn[]) => {
rows.forEach((row) => {
// TODO @
const newItem: ErpFinancePaymentApi.FinancePaymentItem = {
bizId: row.id,
bizType: ErpBizType.PURCHASE_IN,

View File

@ -127,6 +127,7 @@ function handleOpenSaleOut() {
}
function handleAddSaleOut(rows: ErpSaleOutApi.SaleOut[]) {
// TODO @
rows.forEach((row) => {
const newItem: ErpFinanceReceiptApi.FinanceReceiptItem = {
bizId: row.id,

View File

@ -131,6 +131,7 @@ watch(
/** 处理删除 */
function handleDelete(row: ErpPurchaseInApi.PurchaseInItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -142,6 +142,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);
@ -169,6 +170,7 @@ async function handleProductChange(productId: any, row: any) {
/** 处理行数据变更 */
function handleRowChange(row: any) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index === -1) {
tableData.value.push(row);

View File

@ -131,6 +131,7 @@ watch(
/** 处理删除 */
function handleDelete(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -142,6 +142,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpSaleOrderApi.SaleOrderItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -131,6 +131,7 @@ watch(
/** 处理删除 */
function handleDelete(row: ErpSaleOutApi.SaleOutItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -131,6 +131,7 @@ watch(
/** 处理删除 */
function handleDelete(row: ErpSaleReturnApi.SaleReturnItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -106,6 +106,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpStockCheckApi.StockCheckItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -86,6 +86,7 @@ watch(
/** 处理新增 */
function handleAdd() {
// TODO @
const newRow = {
id: undefined,
warehouseId: undefined,

View File

@ -105,6 +105,7 @@ function handleAdd() {
/** 处理删除 */
function handleDelete(row: ErpStockOutApi.StockOutItem) {
// TODO @
const index = tableData.value.findIndex((item) => item.seq === row.seq);
if (index !== -1) {
tableData.value.splice(index, 1);

View File

@ -47,7 +47,7 @@ const { status, data, send, close, open } = useWebSocket(server.value, {
const messageList = ref(
[] as { text: string; time: number; type?: string; userId?: string }[],
); //
const messageReverseList = computed(() => [...messageList.value].reverse());
const messageReverseList = computed(() => [...messageList.value].toReversed());
watchEffect(() => {
if (!data.value) {
return;

View File

@ -111,6 +111,7 @@ function emitActivityChange() {
>
<ElTooltip :content="activity.name">
<div class="relative h-full w-full">
<!-- TODO @芋艿 -->
<ElImage
:src="activity.picUrl"
class="h-full w-full rounded-lg object-cover"

View File

@ -57,10 +57,10 @@ export function isContains(hotArea: Rect, point: Point): boolean {
*/
export function createRect(a: Point, b: Point): Rect {
// 计算矩形的范围
let [left, left2] = [a.x, b.x].sort();
let [left, left2] = [a.x, b.x].toSorted();
left = left ?? 0;
left2 = left2 ?? 0;
let [top, top2] = [a.y, b.y].sort();
let [top, top2] = [a.y, b.y].toSorted();
top = top ?? 0;
top2 = top2 ?? 0;
const right = left2 + 1;

View File

@ -97,7 +97,7 @@ function pushMessage(message: any) {
/** 按照时间倒序,获取消息列表 */
const getMessageList0 = computed(() => {
// 使
return [...messageList.value].sort(
return [...messageList.value].toSorted(
(a: any, b: any) => a.createTime - b.createTime,
);
});

View File

@ -110,6 +110,7 @@ function emitActivityChange() {
>
<ElTooltip :content="activity.name">
<div class="relative h-full w-full">
<!-- TODO @芋艿 -->
<ElImage
:src="activity.picUrl"
class="h-full w-full rounded-lg object-cover"

View File

@ -111,20 +111,22 @@ const [Modal, modalApi] = useVbenModal({
if (data.row?.id) {
//
const rowData = data.row;
const formValues: any = { ...rowData };
formValues.reply = {
type: rowData.responseMessageType,
accountId: data.accountId || -1,
content: rowData.responseContent,
mediaId: rowData.responseMediaId,
url: rowData.responseMediaUrl,
title: rowData.responseTitle,
description: rowData.responseDescription,
thumbMediaId: rowData.responseThumbMediaId,
thumbMediaUrl: rowData.responseThumbMediaUrl,
articles: rowData.responseArticles,
musicUrl: rowData.responseMusicUrl,
hqMusicUrl: rowData.responseHqMusicUrl,
const formValues: any = {
...rowData,
reply: {
type: rowData.responseMessageType,
accountId: data.accountId || -1,
content: rowData.responseContent,
mediaId: rowData.responseMediaId,
url: rowData.responseMediaUrl,
title: rowData.responseTitle,
description: rowData.responseDescription,
thumbMediaId: rowData.responseThumbMediaId,
thumbMediaUrl: rowData.responseThumbMediaUrl,
articles: rowData.responseArticles,
musicUrl: rowData.responseMusicUrl,
hqMusicUrl: rowData.responseHqMusicUrl,
},
};
await formApi.setValues(formValues);
} else {

View File

@ -106,7 +106,7 @@ async function getPage(page: any, params: any = null) {
const scrollHeight = msgDivRef.value?.scrollHeight ?? 0;
//
const data = dataTemp.list.reverse();
const data = dataTemp.list.toReversed();
list.value = [...data, ...list.value];
loading.value = false;
if (data.length < queryParams.pageSize || data.length === 0) {

View File

@ -72,11 +72,11 @@ async function handleDelete(row: MpDraftApi.DraftArticle) {
return;
}
const hideLoading = ElLoading.service({
text: '删除中...',
text: $t('ui.actionMessage.deleting'),
});
try {
await deleteDraft(accountId, row.mediaId);
ElMessage.success('删除成功');
ElMessage.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
hideLoading.close();

View File

@ -0,0 +1,59 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import type { MpAccountApi } from '#/api/mp/account';
import { 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: 'Select',
componentProps: {
options: accountList.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择公众号',
clearable: true,
},
},
];
}
/** 表格列配置 */
export function useGridColumns(): VxeGridPropTypes.Columns {
return [
{
field: 'cover',
title: '图片',
width: 360,
slots: { default: 'cover' },
},
{
field: 'title',
title: '标题',
slots: { default: 'title' },
},
{
field: 'updateTime',
title: '修改时间',
formatter: ({ row }) => {
return formatDateTime(row.updateTime * 1000);
},
},
{
title: '操作',
width: 120,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

Some files were not shown because too many files have changed in this diff Show More