Merge branch 'master' of https://gitea.zianxin.com/zax/xrhome-pc
# Conflicts: # .env.local
This commit is contained in:
commit
c553dbd5f8
44 files changed
+788
-190
No files matched your search
Generated
+29
@@ -24,6 +24,7 @@
|
||||
"benz-amr-recorder": "^1.1.5",
|
||||
"bpmn-js-token-simulation": "^0.36.0",
|
||||
"camunda-bpmn-moddle": "^7.0.1",
|
||||
"compressorjs": "^1.2.1",
|
||||
"cropperjs": "^1.6.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.10",
|
||||
@@ -7298,6 +7299,12 @@
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/blueimp-canvas-to-blob": {
|
||||
"version": "3.29.0",
|
||||
"resolved": "https://registry.npmmirror.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
|
||||
"integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz",
|
||||
@@ -8020,6 +8027,16 @@
|
||||
"resolved": "https://registry.npmmirror.com/component-event/-/component-event-0.2.1.tgz",
|
||||
"integrity": "sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw=="
|
||||
},
|
||||
"node_modules/compressorjs": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/compressorjs/-/compressorjs-1.2.1.tgz",
|
||||
"integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"blueimp-canvas-to-blob": "^3.29.0",
|
||||
"is-blob": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
@@ -11515,6 +11532,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-blob": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-blob/-/is-blob-2.1.0.tgz",
|
||||
"integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-boolean-object": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
||||
|
||||
+2
-1
@@ -40,6 +40,7 @@
|
||||
"benz-amr-recorder": "^1.1.5",
|
||||
"bpmn-js-token-simulation": "^0.36.0",
|
||||
"camunda-bpmn-moddle": "^7.0.1",
|
||||
"compressorjs": "^1.2.1",
|
||||
"cropperjs": "^1.6.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.10",
|
||||
@@ -152,4 +153,4 @@
|
||||
"node": ">= 16.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import request from '@/config/axios'
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
export interface Property {
|
||||
propertyId?: number // 属性编号
|
||||
@@ -27,6 +26,7 @@ export interface Sku {
|
||||
cabinetChannel?: string // 货道或柜门编号
|
||||
cabinetType?: string // 通道类型
|
||||
purchaseBizType?: string
|
||||
deviceId?: number
|
||||
}
|
||||
|
||||
export interface GiveCouponTemplate {
|
||||
@@ -63,6 +63,7 @@ export interface Spu {
|
||||
createTime?: Date // 商品创建时间
|
||||
status?: number // 商品状态
|
||||
types?: number // 商品类型
|
||||
deviceId?: number // 设备号
|
||||
}
|
||||
|
||||
// 获得 Spu 列表
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// request.get 返回的是 res.data,所以这里直接定义 data 的结构
|
||||
export interface GetStatisticsDetailRes {
|
||||
code: number;
|
||||
data: {
|
||||
list: {
|
||||
id: number;
|
||||
userId: number;
|
||||
orderId: number;
|
||||
spuId: number;
|
||||
spuName: string;
|
||||
skuId: number;
|
||||
deviceId: string;
|
||||
barCode: string;
|
||||
cabinetChannel: string;
|
||||
picUrl: string;
|
||||
count: number;
|
||||
price: number;
|
||||
discountPrice: number;
|
||||
payPrice: number;
|
||||
orderPartPrice: number;
|
||||
orderDividePrice: number;
|
||||
afterSaleStatus: number;
|
||||
no: string;
|
||||
createTime: string; // 修改为正确的类型
|
||||
}[];
|
||||
total: number;
|
||||
};
|
||||
list: {
|
||||
id: number;
|
||||
userId: number;
|
||||
orderId: number;
|
||||
spuId: number;
|
||||
spuName: string;
|
||||
skuId: number;
|
||||
deviceId: string;
|
||||
barCode: string;
|
||||
cabinetChannel: string;
|
||||
picUrl: string;
|
||||
count: number;
|
||||
price: number;
|
||||
discountPrice: number;
|
||||
payPrice: number;
|
||||
orderPartPrice: number;
|
||||
orderDividePrice: number;
|
||||
afterSaleStatus: number;
|
||||
no: string;
|
||||
createTime: string;
|
||||
}[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
// 查询参数接口
|
||||
|
||||
@@ -535,6 +535,7 @@ const inputChange = () => {
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
title="cron规则生成器"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="sc-cron">
|
||||
<el-tabs>
|
||||
|
||||
@@ -60,7 +60,7 @@ const dialogStyle = computed(() => {
|
||||
<template>
|
||||
<ElDialog
|
||||
v-bind="getBindValue"
|
||||
:close-on-click-modal="true"
|
||||
:close-on-click-modal="false"
|
||||
:fullscreen="isFullscreen"
|
||||
:width="width"
|
||||
destroy-on-close
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索">
|
||||
<ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索" :close-on-click-modal="false">
|
||||
<el-select
|
||||
filterable
|
||||
:reserve-keyword="false"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<span class="node-fixed-name" title="结束">结束</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog title="审批信息" v-model="dialogVisible" width="1000px" append-to-body>
|
||||
<el-dialog title="审批信息" v-model="dialogVisible" width="1000px" append-to-body :close-on-click-modal="false">
|
||||
<el-row>
|
||||
<el-table
|
||||
:data="processInstanceInfos"
|
||||
@@ -49,7 +49,7 @@
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<el-table-column align="center" label="耗时" prop="durationInMillis" width="100">
|
||||
<template #default="scope">
|
||||
{{ formatPast2(scope.row.durationInMillis) }}
|
||||
@@ -83,7 +83,7 @@ const dialogVisible = ref(false) // 弹窗可见性
|
||||
const processInstanceInfos = ref<any[]>([]) // 流程的审批信息
|
||||
|
||||
const nodeClick = () => {
|
||||
if (readonly) {
|
||||
if (readonly) {
|
||||
if(processInstance && processInstance.value){
|
||||
processInstanceInfos.value = [
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
v-model="dialogVisible"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-row>
|
||||
<el-table :data="selectTasks" size="small" border header-cell-class-name="table-header-gray">
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
<template>
|
||||
<div class="upload-box">
|
||||
<el-upload
|
||||
:id="uuid"
|
||||
:accept="fileType.join(',')"
|
||||
:action="uploadUrl"
|
||||
:before-upload="beforeUpload"
|
||||
:class="['upload', drag ? 'no-border' : '']"
|
||||
:disabled="disabled"
|
||||
:drag="drag"
|
||||
:http-request="httpRequest"
|
||||
:multiple="false"
|
||||
:on-error="uploadError"
|
||||
:on-success="uploadSuccess"
|
||||
:show-file-list="false"
|
||||
>
|
||||
<el-upload :id="uuid" :accept="fileType.join(',')" :action="uploadUrl" :before-upload="beforeUpload"
|
||||
:class="['upload', drag ? 'no-border' : '']" :disabled="disabled" :drag="drag" :http-request="httpRequest"
|
||||
:multiple="false" :on-error="uploadError" :on-success="uploadSuccess" :show-file-list="false">
|
||||
<template v-if="modelValue">
|
||||
<img :src="modelValue" class="upload-image" />
|
||||
<div class="upload-handle" @click.stop>
|
||||
@@ -52,7 +41,7 @@ import type { UploadProps } from 'element-plus'
|
||||
import { generateUUID } from '@/utils'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
import { compressImage, useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
|
||||
defineOptions({ name: 'UploadImg' })
|
||||
|
||||
@@ -106,15 +95,27 @@ const editImg = () => {
|
||||
dom && dom.dispatchEvent(new MouseEvent('click'))
|
||||
}
|
||||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
|
||||
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
|
||||
const imgType = props.fileType
|
||||
if (!imgType.includes(rawFile.type as FileTypes))
|
||||
message.notifyWarning('上传图片不符合所需的格式!')
|
||||
if (!imgSize) message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M!`)
|
||||
return imgType.includes(rawFile.type as FileTypes) && imgSize
|
||||
}
|
||||
|
||||
if (!imgType.includes(rawFile.type as FileTypes)) {
|
||||
message.notifyWarning('上传图片不符合所需的格式!')
|
||||
return false
|
||||
}
|
||||
if (!imgSize) {
|
||||
message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M!`)
|
||||
return false
|
||||
}
|
||||
|
||||
// ===== 新增:压缩图片 =====
|
||||
try {
|
||||
const compressed = await compressImage(rawFile, 0.8)
|
||||
return compressed
|
||||
} catch {
|
||||
return rawFile // 失败则用原图
|
||||
}
|
||||
}
|
||||
// 图片上传成功提示
|
||||
const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
|
||||
message.success('上传成功')
|
||||
@@ -129,6 +130,7 @@ const uploadError = () => {
|
||||
<style lang="scss" scoped>
|
||||
.is-error {
|
||||
.upload {
|
||||
|
||||
:deep(.el-upload),
|
||||
:deep(.el-upload-dragger) {
|
||||
border: 1px dashed var(--el-color-danger) !important;
|
||||
@@ -141,6 +143,7 @@ const uploadError = () => {
|
||||
}
|
||||
|
||||
:deep(.disabled) {
|
||||
|
||||
.el-upload,
|
||||
.el-upload-dragger {
|
||||
cursor: not-allowed !important;
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
<template>
|
||||
<div class="upload-box">
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
:accept="fileType.join(',')"
|
||||
:action="uploadUrl"
|
||||
:before-upload="beforeUpload"
|
||||
:class="['upload', drag ? 'no-border' : '']"
|
||||
:disabled="disabled"
|
||||
:drag="drag"
|
||||
:http-request="httpRequest"
|
||||
:limit="limit"
|
||||
:multiple="true"
|
||||
:on-error="uploadError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="uploadSuccess"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-upload v-model:file-list="fileList" :accept="fileType.join(',')" :action="uploadUrl"
|
||||
:before-upload="beforeUpload" :class="['upload', drag ? 'no-border' : '']" :disabled="disabled" :drag="drag"
|
||||
:http-request="httpRequest" :limit="limit" :multiple="true" :on-error="uploadError" :on-exceed="handleExceed"
|
||||
:on-success="uploadSuccess" list-type="picture-card">
|
||||
<div class="upload-empty">
|
||||
<slot name="empty">
|
||||
<Icon icon="ep:plus" />
|
||||
@@ -47,7 +35,7 @@ import { ElNotification } from 'element-plus'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
import { compressImage, useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
|
||||
defineOptions({ name: 'UploadImgs' })
|
||||
|
||||
@@ -93,23 +81,36 @@ const uploadList = ref<UploadUserFile[]>([])
|
||||
* @description 文件上传之前判断
|
||||
* @param rawFile 上传的文件
|
||||
* */
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
|
||||
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
|
||||
const imgType = props.fileType
|
||||
if (!imgType.includes(rawFile.type as FileTypes))
|
||||
|
||||
if (!imgType.includes(rawFile.type as FileTypes)) {
|
||||
ElNotification({
|
||||
title: '温馨提示',
|
||||
message: '上传图片不符合所需的格式!',
|
||||
type: 'warning'
|
||||
})
|
||||
if (!imgSize)
|
||||
return false
|
||||
}
|
||||
if (!imgSize) {
|
||||
ElNotification({
|
||||
title: '温馨提示',
|
||||
message: `上传图片大小不能超过 ${props.fileSize}M!`,
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
uploadNumber.value++
|
||||
return imgType.includes(rawFile.type as FileTypes) && imgSize
|
||||
|
||||
// ===== 新增:压缩图片 =====
|
||||
try {
|
||||
const compressed = await compressImage(rawFile, 0.8)
|
||||
return compressed
|
||||
} catch {
|
||||
return rawFile // 失败则用原图
|
||||
}
|
||||
}
|
||||
|
||||
// 图片上传成功
|
||||
@@ -186,6 +187,7 @@ const handleExceed = () => {
|
||||
<style lang="scss" scoped>
|
||||
.is-error {
|
||||
.upload {
|
||||
|
||||
:deep(.el-upload--picture-card),
|
||||
:deep(.el-upload-dragger) {
|
||||
border: 1px dashed var(--el-color-danger) !important;
|
||||
@@ -198,6 +200,7 @@ const handleExceed = () => {
|
||||
}
|
||||
|
||||
:deep(.disabled) {
|
||||
|
||||
.el-upload--picture-card,
|
||||
.el-upload-dragger {
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as FileApi from '@/api/infra/file'
|
||||
import CryptoJS from 'crypto-js'
|
||||
import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
||||
import axios from 'axios'
|
||||
import Compressor from 'compressorjs'
|
||||
|
||||
/**
|
||||
* 获得上传 URL
|
||||
@@ -104,3 +105,29 @@ enum UPLOAD_TYPE {
|
||||
// 客户端发送到后端上传
|
||||
SERVER = 'server'
|
||||
}
|
||||
|
||||
|
||||
export const compressImage = (file: File, quality = 0.6): Promise<File> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
new Compressor(file, {
|
||||
quality,
|
||||
maxWidth: 1920,
|
||||
maxHeight: 1920,
|
||||
mimeType: 'image/jpeg',
|
||||
convertSize: 5 * 1024 * 1024,
|
||||
checkOrientation: true,
|
||||
success(result) {
|
||||
const compressedFile = new File([result], file.name, {
|
||||
type: result.type,
|
||||
lastModified: Date.now()
|
||||
})
|
||||
console.log(`压缩: ${(file.size / 1024).toFixed(2)}KB → ${(compressedFile.size / 1024).toFixed(2)}KB`)
|
||||
resolve(compressedFile)
|
||||
},
|
||||
error(err) {
|
||||
console.error('压缩失败:', err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
</defs>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<el-dialog :title="dialogTitle || '审批记录'" v-model="dialogVisible" width="1000px">
|
||||
<el-dialog :title="dialogTitle || '审批记录'" v-model="dialogVisible" width="1000px" :close-on-click-modal="false">
|
||||
<el-row>
|
||||
<el-table
|
||||
:data="selectTasks"
|
||||
|
||||
@@ -195,6 +195,7 @@
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
:model="listenerFieldForm"
|
||||
|
||||
@@ -239,6 +239,7 @@
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
:model="listenerFieldForm"
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form :model="propertyForm" label-width="80px" ref="attributeFormRef">
|
||||
<el-form-item label="属性名:" prop="name">
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
width="600px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form :model="varialbeFormData" label-width="80px" ref="varialbeFormRef">
|
||||
<el-form-item label="源:" prop="source">
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import type { App } from 'vue'
|
||||
// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题
|
||||
import { ElLoading, ElScrollbar, ElButton } from 'element-plus'
|
||||
import { ElLoading, ElScrollbar, ElButton, ElConfigProvider } from 'element-plus'
|
||||
|
||||
const plugins = [ElLoading]
|
||||
|
||||
const components = [ElScrollbar, ElButton]
|
||||
|
||||
// 设置全局配置
|
||||
export const elementPlusConfig = {
|
||||
button: {
|
||||
autoInsertSpace: true
|
||||
}
|
||||
}
|
||||
|
||||
export const setupElementPlus = (app: App<Element>) => {
|
||||
plugins.forEach((plugin) => {
|
||||
app.use(plugin)
|
||||
|
||||
@@ -264,10 +264,32 @@ const handleLogin = async (params: any) => {
|
||||
}
|
||||
const loginDataLoginForm = { ...loginData.loginForm }
|
||||
loginDataLoginForm.captchaVerification = params.captchaVerification
|
||||
|
||||
// 打印登录信息
|
||||
console.log('========== 用户登录信息 ==========')
|
||||
console.log('登录表单数据:', {
|
||||
username: loginDataLoginForm.username,
|
||||
tenantName: loginDataLoginForm.tenantName,
|
||||
rememberMe: loginDataLoginForm.rememberMe,
|
||||
captchaVerification: loginDataLoginForm.captchaVerification ? '已填写' : '未填写'
|
||||
})
|
||||
|
||||
const res = await LoginApi.login(loginDataLoginForm)
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
// 打印登录返回的Token信息
|
||||
console.log('登录返回Token信息:', {
|
||||
id: res.id,
|
||||
userId: res.userId,
|
||||
userType: res.userType,
|
||||
clientId: res.clientId,
|
||||
expiresTime: res.expiresTime ? new Date(res.expiresTime).toLocaleString() : '无',
|
||||
accessToken: res.accessToken ? `${res.accessToken.substring(0, 20)}...` : '无',
|
||||
refreshToken: res.refreshToken ? `${res.refreshToken.substring(0, 20)}...` : '无'
|
||||
})
|
||||
|
||||
loading.value = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
@@ -279,6 +301,30 @@ const handleLogin = async (params: any) => {
|
||||
authUtil.removeLoginForm()
|
||||
}
|
||||
authUtil.setToken(res)
|
||||
|
||||
// 获取用户详细信息并打印
|
||||
try {
|
||||
const userInfo = await LoginApi.getInfo()
|
||||
console.log('用户详细信息:', {
|
||||
用户ID: userInfo.user?.id,
|
||||
用户名: userInfo.user?.username,
|
||||
昵称: userInfo.user?.nickname,
|
||||
头像: userInfo.user?.avatar,
|
||||
部门ID: userInfo.user?.deptId,
|
||||
邮箱: userInfo.user?.email,
|
||||
手机号: userInfo.user?.mobile,
|
||||
性别: userInfo.user?.sex,
|
||||
登录IP: userInfo.user?.loginIp,
|
||||
登录时间: userInfo.user?.loginDate,
|
||||
角色: userInfo.roles,
|
||||
权限数量: userInfo.permissions?.length || 0
|
||||
})
|
||||
console.log('所有权限:', userInfo.permissions)
|
||||
console.log('菜单列表:', userInfo.menus)
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
}
|
||||
console.log('================================')
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog v-model="signDialogVisible" title="签名" width="935">
|
||||
<el-dialog v-model="signDialogVisible" title="签名" width="935" :close-on-click-modal="false">
|
||||
<div class="position-relative">
|
||||
<Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px" />
|
||||
<el-button
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="上传文件">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="uploadUrl"
|
||||
:auto-upload="false"
|
||||
:data="data"
|
||||
:disabled="formLoading"
|
||||
:limit="1"
|
||||
:on-change="handleFileChange"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
:http-request="httpRequest"
|
||||
accept=".jpg, .png, .gif"
|
||||
drag
|
||||
>
|
||||
<el-upload ref="uploadRef" v-model:file-list="fileList" :action="uploadUrl" :auto-upload="false"
|
||||
:before-upload="beforeUpload" :data="data" :disabled="formLoading" :limit="1" :on-change="handleFileChange"
|
||||
:on-error="submitFormError" :on-exceed="handleExceed" :on-success="submitFormSuccess" :http-request="httpRequest"
|
||||
accept=".jpg, .png, .gif" drag>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
||||
<template #tip>
|
||||
@@ -31,7 +19,8 @@
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
import { useUpload, compressImage } from '@/components/UploadFile/src/useUpload'
|
||||
import type { UploadProps, UploadRawFile } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'InfraFileForm' })
|
||||
|
||||
@@ -53,6 +42,25 @@ const open = async () => {
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 文件上传之前进行压缩 */
|
||||
const beforeUpload: UploadProps['beforeUpload'] = async (file: UploadRawFile) => {
|
||||
// 验证文件类型
|
||||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
message.error('仅允许上传 jpg、png、gif 格式文件!')
|
||||
return false
|
||||
}
|
||||
|
||||
// 压缩图片
|
||||
try {
|
||||
const compressed = await compressImage(file, 0.8)
|
||||
return compressed
|
||||
} catch (error) {
|
||||
message.warning('图片压缩失败,将使用原图上传')
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理上传的文件发生变化 */
|
||||
const handleFileChange = (file) => {
|
||||
data.value.path = file.name
|
||||
|
||||
@@ -70,10 +70,12 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
:disabled="selectedRows.length === 0"
|
||||
:loading="copyLoading"
|
||||
@click="handleCopy"
|
||||
v-hasPermi="['product:channel:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
<Icon icon="ep:copy-document" class="mr-5px" /> 复制
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
@@ -90,7 +92,14 @@
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:stripe="true"
|
||||
:show-overflow-tooltip="true"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名称" align="center" prop="name" />
|
||||
<el-table-column label="设备IMEI" align="center" prop="deviceId" />
|
||||
@@ -154,6 +163,8 @@ const { t } = useI18n() // 国际化
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<ChannelVO[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const selectedRows = ref<ChannelVO[]>([]) // 选中的货道
|
||||
const copyLoading = ref(false) // 复制操作的加载中
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
@@ -174,6 +185,8 @@ const getList = async () => {
|
||||
const data = await ChannelApi.getChannelPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
// 清空选中状态(因为列表数据已更新)
|
||||
selectedRows.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -191,6 +204,92 @@ const resetQuery = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 处理表格选择变化 */
|
||||
const handleSelectionChange = (selection: ChannelVO[]) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
/** 查找可用的货道号 */
|
||||
const findAvailableChannel = async (originalChannel: string, deviceId: string): Promise<string> => {
|
||||
// 获取所有货道列表用于检查
|
||||
const allChannels = await ChannelApi.getChannelPage({ pageNo: 1, pageSize: 100 })
|
||||
const existingChannels = allChannels.list
|
||||
.filter((ch: ChannelVO) => ch.deviceId === deviceId)
|
||||
.map((ch: ChannelVO) => ch.cabinetChannel)
|
||||
|
||||
// 解析原始货道号(可能是纯数字或包含其他字符)
|
||||
const match = originalChannel.match(/(\d+)$/)
|
||||
if (!match) {
|
||||
// 如果无法解析数字,直接返回原值加1
|
||||
return originalChannel + '1'
|
||||
}
|
||||
|
||||
const baseNumber = parseInt(match[1], 10)
|
||||
const prefix = originalChannel.substring(0, originalChannel.length - match[1].length)
|
||||
let newNumber = baseNumber + 1
|
||||
|
||||
// 如果新货道号已存在,继续递增
|
||||
while (existingChannels.includes(prefix + newNumber)) {
|
||||
newNumber++
|
||||
}
|
||||
|
||||
return prefix + newNumber
|
||||
}
|
||||
|
||||
/** 复制货道 */
|
||||
const handleCopy = async () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
message.warning('请至少选择一个货道进行复制')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await message.confirm(`确认要复制选中的 ${selectedRows.value.length} 个货道吗?`)
|
||||
copyLoading.value = true
|
||||
|
||||
// 遍历选中的货道,逐个复制
|
||||
for (const row of selectedRows.value) {
|
||||
try {
|
||||
// 获取货道详情
|
||||
const channelDetail = await ChannelApi.getChannel(row.id)
|
||||
|
||||
// 查找可用的货道号(在原有基础上加1,如果存在则继续加1)
|
||||
const newCabinetChannel = await findAvailableChannel(
|
||||
channelDetail.cabinetChannel,
|
||||
channelDetail.deviceId
|
||||
)
|
||||
|
||||
// 准备新货道数据,明确复制指定字段,货道号递增
|
||||
// 不复制 id 和 skuId,只复制需要的字段
|
||||
const newChannel = {
|
||||
name: channelDetail.name, // 复制名称
|
||||
maxNum: channelDetail.maxNum, // 复制最大数量
|
||||
deviceId: channelDetail.deviceId, // 复制设备IMEI
|
||||
channelLine: channelDetail.channelLine, // 复制货道行号
|
||||
cabinetChannel: newCabinetChannel // 货道号加一
|
||||
// 编号(id)和 skuId 不复制,由系统自动生成
|
||||
} as ChannelVO
|
||||
|
||||
// 创建新货道
|
||||
await ChannelApi.createChannel(newChannel)
|
||||
} catch (error) {
|
||||
console.error(`复制货道 ${row.name} 失败:`, error)
|
||||
message.error(`复制货道 "${row.name}" 失败`)
|
||||
}
|
||||
}
|
||||
|
||||
message.success(`成功复制 ${selectedRows.value.length} 个货道`)
|
||||
// 清空选中
|
||||
selectedRows.value = []
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
} finally {
|
||||
copyLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
|
||||
@@ -53,7 +53,16 @@
|
||||
<UploadImg v-model="formData.picUrl" :disabled="isDetail" height="80px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品轮播图" prop="sliderPicUrls">
|
||||
<UploadImgs v-model="formData.sliderPicUrls" :disabled="isDetail" />
|
||||
<UploadImgs v-model="formData.sliderPicUrls!" :disabled="isDetail" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备号">
|
||||
<el-input
|
||||
v-model="formData.deviceId"
|
||||
class="w-80!"
|
||||
clearable
|
||||
placeholder="请输入设备号"
|
||||
type="number"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
@@ -87,7 +96,8 @@ const formData = reactive<Spu>({
|
||||
picUrl: '', // 商品封面图
|
||||
sliderPicUrls: [], // 商品轮播图
|
||||
introduction: '', // 商品简介
|
||||
brandId: undefined // 商品品牌
|
||||
brandId: undefined, // 商品品牌
|
||||
deviceId: undefined // 设备号
|
||||
})
|
||||
const rules = reactive({
|
||||
name: [required],
|
||||
|
||||
@@ -109,7 +109,8 @@ const formData = ref<ProductSpuApi.Spu>({
|
||||
description: '', // 商品详情
|
||||
sort: 0, // 商品排序
|
||||
giveIntegral: 0, // 赠送积分
|
||||
virtualSalesCount: 0 // 虚拟销量
|
||||
virtualSalesCount: 0, // 虚拟销量
|
||||
deviceId: undefined // 设备号
|
||||
})
|
||||
|
||||
/** 获得详情 */
|
||||
@@ -172,7 +173,11 @@ const submitForm = async () => {
|
||||
const newSliderPicUrls: any[] = []
|
||||
deepCopyFormData.sliderPicUrls!.forEach((item: any) => {
|
||||
// 如果是前端选的图
|
||||
typeof item === 'object' ? newSliderPicUrls.push(item.url) : newSliderPicUrls.push(item)
|
||||
if (typeof item === 'object') {
|
||||
newSliderPicUrls.push(item.url)
|
||||
} else {
|
||||
newSliderPicUrls.push(item)
|
||||
}
|
||||
})
|
||||
deepCopyFormData.sliderPicUrls = newSliderPicUrls
|
||||
// 校验都通过后提交表单
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<!-- 商品中心 - 商品列表 -->
|
||||
<template>
|
||||
<doc-alert title="【商品】商品 SPU 与 SKU" url="https://doc.iocoder.cn/mall/product-spu-sku/" />
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<ContentWrap class="search-wrap">
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
@@ -55,11 +53,32 @@
|
||||
v-hasPermi="['product:spu:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm(undefined)"
|
||||
@click="openForm()"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['product:spu:create']"
|
||||
plain
|
||||
type="primary"
|
||||
:disabled="selectedRows.length === 0"
|
||||
:loading="copyLoading"
|
||||
@click="handleCopy"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:copy-document" />
|
||||
复制
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['product:spu:update']"
|
||||
plain
|
||||
type="primary"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="handleModifyChannel"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:edit" />
|
||||
修改货道号
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['product:spu:export']"
|
||||
:loading="exportLoading"
|
||||
@@ -84,7 +103,12 @@
|
||||
:name="item.type"
|
||||
/>
|
||||
</el-tabs>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column type="expand">
|
||||
<template #default="{ row }">
|
||||
<el-form class="spu-table-expand" label-position="left">
|
||||
@@ -128,9 +152,45 @@
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建人" min-width="140" prop="username" />
|
||||
<el-table-column label="商品编号" min-width="140" prop="id" />
|
||||
<el-table-column label="商品信息" min-width="300">
|
||||
<el-table-column align="center" label="设备号" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<template v-if="getDeviceIdValue(row)">{{ getDeviceIdValue(row) }}</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="货道号" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.skus && row.skus.length > 0">
|
||||
<el-tag
|
||||
v-for="(sku, index) in row.skus.filter(s => s.cabinetChannel)"
|
||||
:key="index"
|
||||
size="small"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ sku.cabinetChannel }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="销售状态" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.status >= 0">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="上架"
|
||||
inactive-text="下架"
|
||||
inline-prompt
|
||||
@change="handleStatusChange(row)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag type="info">回收站</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品信息" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div class="flex">
|
||||
<el-image
|
||||
@@ -149,46 +209,14 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="货道号" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.skus && row.skus.length > 0">
|
||||
<el-tag
|
||||
v-for="(sku, index) in row.skus.filter(s => s.cabinetChannel)"
|
||||
:key="index"
|
||||
size="small"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ sku.cabinetChannel }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="价格" min-width="160" prop="price">
|
||||
<el-table-column align="center" label="库存" min-width="80" prop="stock" />
|
||||
<el-table-column align="center" label="价格" min-width="100" prop="price">
|
||||
<template #default="{ row }"> ¥ {{ fenToYuan(row.price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
|
||||
<el-table-column align="center" label="库存" min-width="90" prop="stock" />
|
||||
<el-table-column align="center" label="排序" min-width="70" prop="sort" />
|
||||
<el-table-column align="center" label="销售状态" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.status >= 0">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
active-text="上架"
|
||||
inactive-text="下架"
|
||||
inline-prompt
|
||||
@change="handleStatusChange(row)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag type="info">回收站</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="排序" min-width="80" prop="sort" />
|
||||
<el-table-column label="创建人" min-width="100" prop="username" />
|
||||
<el-table-column label="商品编号" min-width="100" prop="id" />
|
||||
<el-table-column label="销量" min-width="80" prop="salesCount" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
@@ -246,6 +274,23 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 修改货道号弹窗 -->
|
||||
<el-dialog v-model="channelDialogVisible" title="修改货道号" width="500px">
|
||||
<el-form ref="channelFormRef" :model="channelForm" label-width="100px">
|
||||
<el-form-item label="货道号" prop="cabinetChannel" :rules="[{ required: true, message: '请输入货道号', trigger: 'blur' }]">
|
||||
<el-input
|
||||
v-model="channelForm.cabinetChannel"
|
||||
placeholder="请输入货道号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="channelDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="channelLoading" @click="handleConfirmModifyChannel">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { TabsPaneContext } from 'element-plus'
|
||||
@@ -269,6 +314,14 @@ const loading = ref(false) // 列表的加载中
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref<ProductSpuApi.Spu[]>([]) // 列表的数据
|
||||
const selectedRows = ref<ProductSpuApi.Spu[]>([]) // 选中的商品
|
||||
const copyLoading = ref(false) // 复制操作的加载中
|
||||
const channelDialogVisible = ref(false) // 修改货道号弹窗显示状态
|
||||
const channelLoading = ref(false) // 修改货道号加载中
|
||||
const channelFormRef = ref() // 货道号表单引用
|
||||
const channelForm = ref({
|
||||
cabinetChannel: '' // 货道号
|
||||
})
|
||||
// tabs 数据
|
||||
const tabsData = ref([
|
||||
{
|
||||
@@ -309,14 +362,70 @@ const queryParams = ref({
|
||||
}) // 查询参数
|
||||
const queryFormRef = ref() // 搜索的表单Ref
|
||||
|
||||
const normalizeNumericString = (value?: number | string | null) => {
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
const getDeviceIdValue = (spu: ProductSpuApi.Spu) => {
|
||||
if (spu.deviceId !== undefined && spu.deviceId !== null) {
|
||||
return normalizeNumericString(spu.deviceId)
|
||||
}
|
||||
const skuDevice = spu.skus?.find((sku) => sku.deviceId !== undefined && sku.deviceId !== null)
|
||||
return normalizeNumericString(skuDevice?.deviceId ?? '')
|
||||
}
|
||||
|
||||
const getCabinetChannelValue = (spu: ProductSpuApi.Spu) => {
|
||||
const channels =
|
||||
spu.skus
|
||||
?.map((sku) => sku.cabinetChannel)
|
||||
.filter((channel): channel is string => !!channel) ?? []
|
||||
if (channels.length === 0) {
|
||||
return ''
|
||||
}
|
||||
const sortedChannels = channels
|
||||
.map((channel) => normalizeNumericString(channel))
|
||||
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }))
|
||||
return sortedChannels[0]
|
||||
}
|
||||
|
||||
const compareByDeviceAndCabinet = (a: ProductSpuApi.Spu, b: ProductSpuApi.Spu) => {
|
||||
const deviceA = getDeviceIdValue(a)
|
||||
const deviceB = getDeviceIdValue(b)
|
||||
const deviceCompare = deviceA.localeCompare(deviceB, undefined, { numeric: true, sensitivity: 'base' })
|
||||
if (deviceCompare !== 0) {
|
||||
return deviceCompare
|
||||
}
|
||||
const cabinetA = getCabinetChannelValue(a)
|
||||
const cabinetB = getCabinetChannelValue(b)
|
||||
const cabinetCompare = cabinetA.localeCompare(cabinetB, undefined, { numeric: true, sensitivity: 'base' })
|
||||
if (cabinetCompare !== 0) {
|
||||
return cabinetCompare
|
||||
}
|
||||
return ((a.id ?? 0) as number) - ((b.id ?? 0) as number)
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProductSpuApi.getSpuPage(queryParams.value)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
const sortedList = [...data.list].sort(compareByDeviceAndCabinet)
|
||||
// 过滤掉没有设备号的商品
|
||||
const filteredList = sortedList.filter((item) => !!getDeviceIdValue(item))
|
||||
list.value = filteredList
|
||||
total.value = filteredList.length
|
||||
console.log('查询列表',data)
|
||||
const cabinetChannels = filteredList
|
||||
.flatMap((item) => item.skus ?? [])
|
||||
.map((sku) => sku.cabinetChannel)
|
||||
.filter((channel): channel is string => !!channel)
|
||||
.map((channel) => normalizeNumericString(channel))
|
||||
console.log('货道号数据:', cabinetChannels)
|
||||
// 清空选中状态(因为列表数据已更新)
|
||||
selectedRows.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -325,6 +434,7 @@ const getList = async () => {
|
||||
/** 切换 Tab */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.value.tabType = tab.paneName as number
|
||||
selectedRows.value = [] // 清空选中
|
||||
getList()
|
||||
}
|
||||
|
||||
@@ -418,6 +528,132 @@ const openForm = (id?: number) => {
|
||||
push({ name: 'ProductPickupAdd' });
|
||||
}
|
||||
|
||||
/** 处理表格选择变化 */
|
||||
const handleSelectionChange = (selection: ProductSpuApi.Spu[]) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
/** 复制商品 */
|
||||
const handleCopy = async () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
message.warning('请至少选择一个商品进行复制')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await message.confirm(`确认要复制选中的 ${selectedRows.value.length} 个商品吗?`)
|
||||
copyLoading.value = true
|
||||
|
||||
// 遍历选中的商品,逐个复制
|
||||
for (const row of selectedRows.value) {
|
||||
try {
|
||||
// 获取商品详情
|
||||
const productDetail = await ProductSpuApi.getSpu(row.id!)
|
||||
|
||||
// 准备新商品数据,复制所有内容,但不传递商品编号(id)和SKU的id,后端会自动生成
|
||||
const { id, createTime, skus, ...spuData } = productDetail
|
||||
const newProduct: ProductSpuApi.Spu = {
|
||||
...spuData, // 复制所有SPU字段(除了id和createTime)
|
||||
// 复制SKU时不传递id和spuId,其他所有数据保持原样(包括name字段)
|
||||
skus: skus?.map(sku => {
|
||||
const { id: skuId, spuId, ...skuData } = sku
|
||||
return {
|
||||
...skuData, // 复制SKU的所有业务字段(包括name),排除id、spuId
|
||||
name: sku.name || productDetail.name // 确保name字段被复制,如果没有则使用SPU的name
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 在控制台打印新增的商品数据
|
||||
console.log('复制新增的商品数据:', newProduct)
|
||||
|
||||
// 创建新商品
|
||||
await ProductSpuApi.createSpu(newProduct)
|
||||
} catch (error) {
|
||||
console.error(`复制商品 ${row.name} 失败:`, error)
|
||||
message.error(`复制商品 "${row.name}" 失败`)
|
||||
}
|
||||
}
|
||||
|
||||
message.success(`成功复制 ${selectedRows.value.length} 个商品`)
|
||||
// 清空选中
|
||||
selectedRows.value = []
|
||||
// 刷新列表
|
||||
await getTabsCount()
|
||||
await getList()
|
||||
} catch {
|
||||
// 用户取消操作
|
||||
} finally {
|
||||
copyLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改货道号 */
|
||||
const handleModifyChannel = () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
message.warning('请至少选择一个商品进行修改货道号')
|
||||
return
|
||||
}
|
||||
// 重置表单
|
||||
channelForm.value.cabinetChannel = ''
|
||||
channelDialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 确认修改货道号 */
|
||||
const handleConfirmModifyChannel = async () => {
|
||||
if (!channelFormRef.value) return
|
||||
|
||||
try {
|
||||
// 表单验证
|
||||
await channelFormRef.value.validate()
|
||||
|
||||
if (!channelForm.value.cabinetChannel) {
|
||||
message.warning('请输入货道号')
|
||||
return
|
||||
}
|
||||
|
||||
channelLoading.value = true
|
||||
|
||||
// 遍历选中的商品,逐个修改货道号
|
||||
for (const row of selectedRows.value) {
|
||||
try {
|
||||
// 获取商品详情
|
||||
const productDetail = await ProductSpuApi.getSpu(row.id!)
|
||||
|
||||
// 更新 skus 中的货道号,name 使用 spu 的 name,其他参数保持不变
|
||||
const updatedSkus = productDetail.skus?.map(sku => ({
|
||||
...sku,
|
||||
name: productDetail.name, // 使用外层 spu 的 name
|
||||
cabinetChannel: channelForm.value.cabinetChannel
|
||||
})) || []
|
||||
|
||||
// 准备更新数据,只修改 skus 的货道号,其他参数保持原样
|
||||
const updateData: ProductSpuApi.Spu = {
|
||||
...productDetail,
|
||||
skus: updatedSkus
|
||||
}
|
||||
|
||||
// 调用更新接口
|
||||
await ProductSpuApi.updateSpu(updateData)
|
||||
} catch (error) {
|
||||
console.error(`修改商品 ${row.name} 货道号失败:`, error)
|
||||
message.error(`修改商品 "${row.name}" 货道号失败`)
|
||||
}
|
||||
}
|
||||
|
||||
message.success(`成功修改 ${selectedRows.value.length} 个商品的货道号`)
|
||||
// 关闭弹窗
|
||||
channelDialogVisible.value = false
|
||||
// 清空选中
|
||||
selectedRows.value = []
|
||||
} catch (error) {
|
||||
console.error('修改货道号失败:', error)
|
||||
} finally {
|
||||
channelLoading.value = false
|
||||
await getList()
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看商品详情 */
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'ProductPickupDetail', params: { id } })
|
||||
@@ -452,8 +688,12 @@ onActivated(() => {
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 解析路由的 categoryId
|
||||
if (route.query.categoryId) {
|
||||
queryParams.value.categoryId = route.query.categoryId
|
||||
const categoryId = route.query.categoryId
|
||||
if (categoryId) {
|
||||
const id = Array.isArray(categoryId) ? Number(categoryId[0]) : Number(categoryId)
|
||||
if (!isNaN(id)) {
|
||||
queryParams.value.categoryId = id as any
|
||||
}
|
||||
}
|
||||
// 获得商品信息
|
||||
await getTabsCount()
|
||||
@@ -464,6 +704,15 @@ onMounted(async () => {
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.search-wrap {
|
||||
position: sticky;
|
||||
// top: 20px;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.spu-table-expand {
|
||||
padding-left: 42px;
|
||||
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
<el-form-item v-if="formData.adType === 'link'" label="广告图链接" prop="adUrl">
|
||||
<el-input v-model="formData.adUrl" placeholder="请输入广告图链接" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="false" label="访问商品地址" prop="productUrl">
|
||||
<!-- <el-form-item v-if="false" label="访问商品地址" prop="productUrl">
|
||||
<el-input v-model="formData.productUrl" placeholder="请输入访问商品地址" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="true" label="商品地址" prop="productUrl">
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item v-if="true" label="商品地址" prop="productUrl">
|
||||
<el-input v-model="formData.productUrl" placeholder="请输入访问商品地址" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="true" label="文件地址" prop="fileUrl">
|
||||
<el-input v-model="formData.fileUrl" placeholder="请输入文件地址" />
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
@@ -67,7 +67,7 @@ const formData = ref({
|
||||
})
|
||||
const formRules = reactive({
|
||||
deviceId: [{ required: true, message: '设备IMEI不能为空', trigger: 'blur' }],
|
||||
productUrl: [{ required: true, message: '访问商品地址不能为空', trigger: 'blur' }],
|
||||
// productUrl: [{ required: true, message: '访问商品地址不能为空', trigger: 'blur' }],
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ type="success" plain @click="handleExport" :loading="exportLoading"
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="设备名称" align="center" prop="name" />
|
||||
<el-table-column label="设备IMEI" align="center" prop="deviceId" />
|
||||
<el-table-column label="访问商品地址" width="250" align="center" prop="productUrl" />
|
||||
<!-- <el-table-column label="访问商品地址" width="250" align="center" prop="productUrl" /> -->
|
||||
<el-table-column label="广告" align="center" prop="fileUrl">
|
||||
<template #default="{ row }">
|
||||
<el-image :src="row.fileUrl" class="mr-10px h-30px w-30px" @click="imagePreview(row.fileUrl)" />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
title="选择商品类型"
|
||||
width="600px"
|
||||
:before-close="handleDialogClose"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="product-type-container">
|
||||
<div class="type-card-list">
|
||||
|
||||
@@ -187,9 +187,9 @@
|
||||
<el-table-column label="销量" prop="salesCount" />
|
||||
<!-- 展开详情表格中的创建时间列 -->
|
||||
<el-table-column label="创建时间" prop="createTime" width="180">
|
||||
<template #default="{ row }">
|
||||
<template #default="{ row: sourceRow }">
|
||||
<span>{{
|
||||
row.createTime ? dateFormatter(null, null, new Date(row.createTime)) : ''
|
||||
sourceRow.createTime ? formatDate(new Date(sourceRow.createTime)) : ''
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -274,7 +274,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { SummaryApi, SummaryVO } from '@/api/product/summary'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
@@ -295,7 +295,16 @@ const total = ref(0) // 列表的总页数
|
||||
const showProductTypeDialog = ref(false) // 商品类型选择弹窗显示状态
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref({
|
||||
const queryParams = ref<{
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
tabType: number
|
||||
name: string
|
||||
categoryId: number | undefined
|
||||
createTime: any
|
||||
types: number
|
||||
barCode: string | undefined
|
||||
}>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0,
|
||||
@@ -325,8 +334,8 @@ const fetchAllProducts = async () => {
|
||||
// console.log('获取商品数据,参数:', largePageParams)
|
||||
|
||||
const [mallResponse, containerResponse] = await Promise.all([
|
||||
ProductSpuApi.getSpuPage({ ...largePageParams, types: 0 }), // 商城商品
|
||||
ProductSpuApi.getSpuPage({ ...largePageParams, types: 1 }) // 提货柜商品
|
||||
ProductSpuApi.getSpuPage({ ...largePageParams, types: 0 } as any), // 商城商品
|
||||
ProductSpuApi.getSpuPage({ ...largePageParams, types: 1 } as any) // 提货柜商品
|
||||
])
|
||||
|
||||
// console.log('商城商品响应:', mallResponse)
|
||||
@@ -345,7 +354,7 @@ const fetchAllProducts = async () => {
|
||||
/**
|
||||
* 获取商品详细信息 - 优化版本
|
||||
*/
|
||||
const fetchProductDetails = async (products) => {
|
||||
const fetchProductDetails = async (products: any[]) => {
|
||||
if (!products || products.length === 0) {
|
||||
// console.log('没有商品数据需要获取详情')
|
||||
return []
|
||||
@@ -388,7 +397,7 @@ const getList = async () => {
|
||||
// console.log('获取到的详细商品数据:', detailedProducts)
|
||||
|
||||
// Step 3: 获取汇总数据(如果需要)
|
||||
let summaryData = []
|
||||
let summaryData: any[] = []
|
||||
try {
|
||||
const summaryResponse = await SummaryApi.getSummaryPage({
|
||||
pageNo: 1,
|
||||
@@ -424,7 +433,9 @@ const getList = async () => {
|
||||
}
|
||||
|
||||
// Step 4: 按barCode分组合并商品
|
||||
const searchBarCode = queryParams.value.barCode?.trim() || null
|
||||
const searchBarCode = (queryParams.value.barCode && typeof queryParams.value.barCode === 'string')
|
||||
? queryParams.value.barCode.trim() || null
|
||||
: null
|
||||
const allGroupedProducts = groupProductsByBarCode(detailedProducts, searchBarCode)
|
||||
console.log('分组后的商品数据:', allGroupedProducts)
|
||||
|
||||
@@ -437,7 +448,43 @@ const getList = async () => {
|
||||
const combinedProducts = [...allGroupedProducts, ...uniqueSummaryData]
|
||||
console.log('合并后的最终数据:', combinedProducts)
|
||||
|
||||
// Step 6: 手动分页处理
|
||||
// Step 6: 排序处理 - 先按商品来源排序(提货柜商品在前,商城商品在后),再按SKU条形码从小到大排序
|
||||
combinedProducts.sort((a, b) => {
|
||||
// 获取商品来源类型
|
||||
const getSourceType = (item) => {
|
||||
const sourceType = item.sources?.[0]?.type || ''
|
||||
if (sourceType.includes('提货柜')) return 0 // 提货柜商品排在前面
|
||||
if (sourceType.includes('商城')) return 1 // 商城商品排在中间
|
||||
if (sourceType.includes('汇总')) return 2 // 汇总商品排在最后
|
||||
return 2 // 其他类型排在最后
|
||||
}
|
||||
|
||||
const sourceA = getSourceType(a)
|
||||
const sourceB = getSourceType(b)
|
||||
|
||||
// 先按商品来源排序
|
||||
if (sourceA !== sourceB) {
|
||||
return sourceA - sourceB
|
||||
}
|
||||
|
||||
// 如果来源相同,按SKU条形码从小到大排序
|
||||
const barCodeA = a.barCode || ''
|
||||
const barCodeB = b.barCode || ''
|
||||
|
||||
// 尝试将条形码转换为数字进行比较,如果转换失败则按字符串比较
|
||||
const numA = Number(barCodeA)
|
||||
const numB = Number(barCodeB)
|
||||
|
||||
if (!isNaN(numA) && !isNaN(numB) && barCodeA === String(numA) && barCodeB === String(numB)) {
|
||||
// 如果都是纯数字,按数字大小排序
|
||||
return numA - numB
|
||||
} else {
|
||||
// 否则按字符串排序
|
||||
return barCodeA.localeCompare(barCodeB, 'zh-CN', { numeric: true })
|
||||
}
|
||||
})
|
||||
|
||||
// Step 7: 手动分页处理
|
||||
const { pageNo, pageSize } = queryParams.value
|
||||
const startIndex = (pageNo - 1) * pageSize
|
||||
const endIndex = startIndex + pageSize
|
||||
@@ -446,9 +493,9 @@ const getList = async () => {
|
||||
total.value = combinedProducts.length
|
||||
|
||||
console.log(`分页处理: 总数=${total.value}, 当前页=${pageNo}, 每页=${pageSize}, 展示=${list.value.length}条`)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('获取商品数据失败:', error)
|
||||
message.error('获取商品数据失败: ' + (error.message || '未知错误'))
|
||||
message.error('获取商品数据失败: ' + (error?.message || '未知错误'))
|
||||
|
||||
// 设置默认值避免页面报错
|
||||
list.value = []
|
||||
@@ -461,10 +508,10 @@ const getList = async () => {
|
||||
/**
|
||||
* 按barCode分组合并商品 - 优化版本
|
||||
*/
|
||||
const groupProductsByBarCode = (products, searchBarCode = null) => {
|
||||
const groupProductsByBarCode = (products: any[], searchBarCode: string | null = null) => {
|
||||
const groupedMap = new Map()
|
||||
|
||||
const addToGroup = (key, productData, skuData = null) => {
|
||||
const addToGroup = (key: string, productData: any, skuData: any = null) => {
|
||||
if (!groupedMap.has(key)) {
|
||||
groupedMap.set(key, {
|
||||
id: productData.id,
|
||||
@@ -566,7 +613,7 @@ const handleDelete = async (id: number) => {
|
||||
deletingIds.value.add(id)
|
||||
|
||||
await SummaryApi.deleteSummary(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
message.success('删除成功')
|
||||
|
||||
// 检查分页
|
||||
const currentPageItemCount = list.value.length
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
<el-dialog v-model="mapDialogVisible" title="获取经纬度" append-to-body>
|
||||
<el-dialog v-model="mapDialogVisible" title="获取经纬度" append-to-body :close-on-click-modal="false">
|
||||
<IFrame class="h-609px" :src="tencentLbsUrl" />
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
|
||||
@@ -168,22 +168,59 @@ const queryParams = reactive<StatisticsDetailPageReqVO>({
|
||||
const getAllData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 先获取第一页数据,了解总数
|
||||
const firstPageData = await SalesStatisticsDetailsApi.getStatisticsDetailPage({
|
||||
...queryParams,
|
||||
pageNo: 1,
|
||||
pageSize: 1000
|
||||
})
|
||||
|
||||
if (firstPageData && firstPageData.list) {
|
||||
const sortedData = firstPageData.list.sort((a: any, b: any) => {
|
||||
console.log('API 返回的完整数据:', firstPageData)
|
||||
|
||||
const responseData = firstPageData as any
|
||||
if (responseData && responseData.list) {
|
||||
let allItems = [...responseData.list]
|
||||
const totalCount = responseData.total || 0
|
||||
const pageSize = 10
|
||||
|
||||
// 如果总数超过第一页的数量,需要继续获取剩余数据
|
||||
if (totalCount > pageSize) {
|
||||
const totalPages = Math.ceil(totalCount / pageSize)
|
||||
console.log(`总数据 ${totalCount} 条,需要获取 ${totalPages} 页`)
|
||||
|
||||
// 并发获取剩余页面的数据
|
||||
const promises: Promise<any>[] = []
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
promises.push(
|
||||
SalesStatisticsDetailsApi.getStatisticsDetailPage({
|
||||
...queryParams,
|
||||
pageNo: page,
|
||||
pageSize: pageSize
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const remainingPages = await Promise.all(promises)
|
||||
remainingPages.forEach((pageData: any) => {
|
||||
if (pageData && pageData.list) {
|
||||
allItems = allItems.concat(pageData.list)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 打印 createTime 参数
|
||||
console.log('createTime 数据:', allItems.map((item: any) => item.createTime))
|
||||
console.log(`实际获取数据条数: ${allItems.length},总数据: ${totalCount}`)
|
||||
|
||||
const sortedData = allItems.sort((a: any, b: any) => {
|
||||
return new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
|
||||
})
|
||||
|
||||
allData.value = sortedData
|
||||
total.value = firstPageData.total || 0
|
||||
total.value = totalCount
|
||||
updateCurrentPageData()
|
||||
|
||||
console.log("销售订单", firstPageData)
|
||||
} else {
|
||||
console.warn('数据格式异常,未找到 list 数据', firstPageData)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error)
|
||||
@@ -194,10 +231,18 @@ const getAllData = async () => {
|
||||
}
|
||||
|
||||
const updateCurrentPageData = () => {
|
||||
const startIndex = (queryParams.pageNo - 1) * queryParams.pageSize
|
||||
const endIndex = startIndex + queryParams.pageSize
|
||||
const pageNo = queryParams.pageNo ?? 1
|
||||
const pageSize = queryParams.pageSize ?? 10
|
||||
const startIndex = (pageNo - 1) * pageSize
|
||||
const endIndex = startIndex + pageSize
|
||||
|
||||
if (queryParams.no || queryParams.deviceId || queryParams.barCode || queryParams.createTime) {
|
||||
// 检查是否有搜索条件
|
||||
const hasSearchCondition = (queryParams.no && queryParams.no.trim()) ||
|
||||
(queryParams.deviceId && queryParams.deviceId.trim()) ||
|
||||
(queryParams.barCode && queryParams.barCode.trim()) ||
|
||||
(queryParams.createTime && Array.isArray(queryParams.createTime) && queryParams.createTime.length === 2)
|
||||
|
||||
if (hasSearchCondition) {
|
||||
handleQuery()
|
||||
} else {
|
||||
list.value = allData.value.slice(startIndex, endIndex)
|
||||
@@ -220,13 +265,13 @@ const handleQuery = () => {
|
||||
|
||||
if (queryParams.deviceId && queryParams.deviceId.trim()) {
|
||||
filteredData = filteredData.filter((item: any) =>
|
||||
item.deviceId && String(item.deviceId).toLowerCase().includes(queryParams.deviceId.toLowerCase())
|
||||
item.deviceId && String(item.deviceId).toLowerCase().includes(queryParams.deviceId!.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (queryParams.barCode && queryParams.barCode.trim()) {
|
||||
filteredData = filteredData.filter((item: any) =>
|
||||
item.barCode && String(item.barCode).toLowerCase().includes(queryParams.barCode.toLowerCase())
|
||||
item.barCode && String(item.barCode).toLowerCase().includes(queryParams.barCode!.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -244,8 +289,10 @@ const handleQuery = () => {
|
||||
})
|
||||
|
||||
total.value = sortedData.length
|
||||
const startIndex = (queryParams.pageNo - 1) * queryParams.pageSize
|
||||
const endIndex = startIndex + queryParams.pageSize
|
||||
const pageNo = queryParams.pageNo ?? 1
|
||||
const pageSize = queryParams.pageSize ?? 10
|
||||
const startIndex = (pageNo - 1) * pageSize
|
||||
const endIndex = startIndex + pageSize
|
||||
list.value = sortedData.slice(startIndex, endIndex)
|
||||
}
|
||||
|
||||
@@ -280,7 +327,9 @@ const handleExport = async () => {
|
||||
|
||||
const formatTime = (timestamp: number | string) => {
|
||||
if (!timestamp) return '-'
|
||||
const date = new Date(Number(timestamp))
|
||||
// 如果是字符串格式的日期,直接使用;如果是数字时间戳,需要转换
|
||||
const date = typeof timestamp === 'string' ? new Date(timestamp) : new Date(Number(timestamp))
|
||||
if (isNaN(date.getTime())) return '-'
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
v-model="showDialog"
|
||||
width="800px"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<ReplyForm v-model="replyForm" v-model:reply="reply" :msg-type="msgType" ref="formRef" />
|
||||
<template #footer>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
width="90%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<WxMaterialSelect
|
||||
type="image"
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
width="80%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<WxMaterialSelect
|
||||
type="image"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-dialog title="选择图文" v-model="showDialog" width="90%" append-to-body destroy-on-close>
|
||||
<el-dialog title="选择图文" v-model="showDialog" width="90%" append-to-body destroy-on-close :close-on-click-modal="false">
|
||||
<WxMaterialSelect
|
||||
type="news"
|
||||
:account-id="reply.accountId"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
width="90%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<WxMaterialSelect
|
||||
type="video"
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
width="90%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<WxMaterialSelect
|
||||
type="voice"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 弹窗播放 -->
|
||||
<el-dialog v-model="dialogVideo" title="视频播放" append-to-body>
|
||||
<el-dialog v-model="dialogVideo" title="视频播放" append-to-body :close-on-click-modal="false">
|
||||
<video-player
|
||||
v-if="dialogVideo"
|
||||
class="video-player vjs-big-play-centered"
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
width="80%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<WxMaterialSelect
|
||||
type="image"
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
v-model="showDialog"
|
||||
:before-close="onBeforeDialogClose"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<NewsForm v-model="newsList" v-loading="isSubmitting" :is-creating="isCreating" />
|
||||
<template #footer>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog title="新建视频" v-model="showDialog" width="600px">
|
||||
<el-dialog title="新建视频" v-model="showDialog" width="600px" :close-on-click-modal="false">
|
||||
<el-upload
|
||||
:action="UPLOAD_URL"
|
||||
:headers="HEADERS"
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-dialog title="选择图文" v-model="showNewsDialog" width="80%" destroy-on-close>
|
||||
<el-dialog title="选择图文" v-model="showNewsDialog" width="80%" destroy-on-close :close-on-click-modal="false">
|
||||
<WxMaterialSelect
|
||||
type="news"
|
||||
:account-id="props.accountId"
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
@click="messageBox.show = true"
|
||||
width="50%"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<WxMsg :user-id="messageBox.userId" />
|
||||
</el-dialog>
|
||||
|
||||
Reference in new issue
Block a user