1.加入ai对话框 模块日报功能

This commit is contained in:
2026-03-25 08:40:33 +08:00
parent bb5ab23430
commit e762188322
8 changed files with 184 additions and 183 deletions

View File

@@ -4,8 +4,8 @@ NODE_ENV=production
VITE_DEV=true VITE_DEV=true
# 请求路径 # 请求路径
VITE_BASE_URL='http://localhost:48080' #VITE_BASE_URL='http://localhost:48080'
#VITE_BASE_URL='http://118.253.178.8:48080' VITE_BASE_URL='http://118.253.178.8:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务 # 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server VITE_UPLOAD_TYPE=server

View File

@@ -4,8 +4,8 @@ NODE_ENV=development
VITE_DEV=true VITE_DEV=true
# 请求路径 # 请求路径
#VITE_BASE_URL='http://118.253.178.8:48080' VITE_BASE_URL='http://118.253.178.8:48080'
VITE_BASE_URL='http://localhost:48080' #VITE_BASE_URL='http://localhost:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务 # 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=server VITE_UPLOAD_TYPE=server

View File

@@ -14,6 +14,7 @@ export interface TenantVO {
accountCount: number accountCount: number
websites: string[] websites: string[]
createTime: Date createTime: Date
tenantPrompt?: string
} }
export interface TenantPageReqVO extends PageParam { export interface TenantPageReqVO extends PageParam {

View File

@@ -4,6 +4,7 @@ import {
BarChart, BarChart,
FunnelChart, FunnelChart,
GaugeChart, GaugeChart,
HeatmapChart,
LineChart, LineChart,
MapChart, MapChart,
PictorialBarChart, PictorialBarChart,
@@ -13,6 +14,7 @@ import {
import { import {
AriaComponent, AriaComponent,
CalendarComponent,
DataZoomComponent, DataZoomComponent,
GridComponent, GridComponent,
LegendComponent, LegendComponent,
@@ -37,6 +39,7 @@ echarts.use([
AriaComponent, AriaComponent,
ParallelComponent, ParallelComponent,
VisualMapComponent, VisualMapComponent,
CalendarComponent,
BarChart, BarChart,
LineChart, LineChart,
PieChart, PieChart,
@@ -45,7 +48,8 @@ echarts.use([
PictorialBarChart, PictorialBarChart,
RadarChart, RadarChart,
GaugeChart, GaugeChart,
FunnelChart FunnelChart,
HeatmapChart
]) ])
export default echarts export default echarts

View File

@@ -792,7 +792,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
name: 'SupplierPerformance', name: 'SupplierPerformance',
meta: { meta: {
title: '供应商详情', title: '供应商详情',
noCache: true,
hidden: true, hidden: true,
canTo: true canTo: true
}, },

View File

@@ -61,6 +61,9 @@
class="w-full" class="w-full"
/> />
</el-form-item> </el-form-item>
<el-form-item label="租户提示词" prop="tenantPrompt">
<el-input v-model="formData.tenantPrompt" type="textarea" :rows="3" placeholder="请输入租户提示词" />
</el-form-item>
<el-form-item label="租户状态" prop="status"> <el-form-item label="租户状态" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio <el-radio
@@ -103,13 +106,15 @@ const formData = ref({
expireTime: undefined, expireTime: undefined,
websites: [], websites: [],
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
tenantPrompt: '',
// 新增专属 // 新增专属
username: undefined, username: undefined,
password: undefined password: undefined
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }], name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }],
packageId: [{ required: true, message: '租户套<EFBFBD><EFBFBD><EFBFBD>不能为空', trigger: 'blur' }], packageId: [{ required: true, message: '租户套不能为空', trigger: 'blur' }],
contactName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }], contactName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }], status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }],
accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }], accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }],
@@ -178,6 +183,7 @@ const resetForm = () => {
expireTime: undefined, expireTime: undefined,
websites: [], websites: [],
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
tenantPrompt: '',
username: undefined, username: undefined,
password: undefined password: undefined
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="supplier-performance-page"> <div ref="pageRootRef" class="supplier-performance-page">
<!-- 查询条件区域与主页一致 --> <!-- 查询条件区域与主页一致 -->
<el-card class="query-card" shadow="never"> <el-card class="query-card" shadow="never">
<div class="query-form"> <div class="query-form">
@@ -265,7 +265,7 @@
</div> </div>
<!-- 指标表格三列网格布局 --> <!-- 指标表格三列网格布局 -->
<div v-if="getColumnsWithDate(row).length > 0" class="metric-section"> <div v-if="getColumnsWithDate().length > 0" class="metric-section">
<div class="grid-layout metric-header"> <div class="grid-layout metric-header">
<span>检测指标</span> <span>检测指标</span>
<span>实际结果</span> <span>实际结果</span>
@@ -313,10 +313,10 @@
</div> </div>
</div> </div>
<!-- 列表形式不需要悬浮卡片 --> <!-- 列表形式每个字段独立一列铺开展示 -->
<div v-show="displayMode === 'table'" class="table-wrap"> <div v-show="displayMode === 'table'" class="table-wrap">
<el-table :data="visibleRows" v-loading="loading" border stripe style="width: 100%"> <el-table :data="visibleRows" v-loading="loading" border stripe style="width: 100%">
<el-table-column label="供应商" min-width="220" show-overflow-tooltip> <el-table-column label="供应商" min-width="180" fixed="left" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<div class="table-title"> <div class="table-title">
<div class="table-title-main">{{ String((row as any)?.title ?? (row as any)?.name ?? '-') }}</div> <div class="table-title-main">{{ String((row as any)?.title ?? (row as any)?.name ?? '-') }}</div>
@@ -333,24 +333,21 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="核心指标" width="160" align="center"> <el-table-column label="核心指标名称" min-width="120" align="center" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">{{ getTitleName(row) || '-' }}</template>
<div class="table-core">
<div class="core-name">{{ getTitleName(row) || '-' }}</div>
<div class="core-value">{{ getTitleValue(row) || '-' }}</div>
</div>
</template>
</el-table-column> </el-table-column>
<el-table-column label="检测指标明细" min-width="420"> <el-table-column label="核心指标值" min-width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">{{ getTitleValue(row) || '-' }}</template>
</el-table-column>
<template v-for="(col, ci) in metricColumns" :key="col.key || ci">
<el-table-column :label="col.title + '(实际)'" min-width="110" align="center" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<div class="metric-cell">
<div <div
v-for="(item, mi) in getTableData(row)" v-for="(item, mi) in getMetricItemAt(row, ci)"
:key="mi" :key="item.metricName + '-' + mi"
class="metric-cell-row" class="table-metric-actual"
:class="item.valueColorClass"
> >
<div class="metric-cell-name">{{ item.metricName }}</div>
<div class="metric-cell-actual" :class="item.valueColorClass">
{{ item.actualValue }} {{ item.actualValue }}
<span v-if="item.arrow" class="trend-arrow" :class="item.arrowClass">{{ item.arrow }}</span> <span v-if="item.arrow" class="trend-arrow" :class="item.arrowClass">{{ item.arrow }}</span>
<template v-if="item.labelPairs && item.labelPairs.length > 0"> <template v-if="item.labelPairs && item.labelPairs.length > 0">
@@ -364,11 +361,16 @@
</span> </span>
</template> </template>
</div> </div>
<div class="metric-cell-ref">{{ item.paramValue }}</div> </template>
</div> </el-table-column>
<el-table-column :label="col.title + '(基准)'" min-width="100" align="center" show-overflow-tooltip>
<template #default="{ row }">
<div v-for="(item, mi) in getMetricItemAt(row, ci)" :key="item.metricName + '-' + mi" class="table-metric-ref">
{{ item.paramValue }}
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
</template>
</el-table> </el-table>
</div> </div>
</el-card> </el-card>
@@ -384,7 +386,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
defineOptions({ name: 'SupplierPerformance' })
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@@ -410,10 +414,24 @@ const SUPPLIER_PERFORMANCE_MODULE_KEY = `${SUPPLIER_PERFORMANCE_COMPONENT}:main`
const REPORT_ID = 6 const REPORT_ID = 6
const route = useRoute() const route = useRoute()
const userStore = useUserStore() const userStore = useUserStore()
const { openWithScreenshot, setPageLoading } = useAiAssistant() const { openWithScreenshot, setPageLoading, setPageModuleName, setPageModuleCode, setScreenshotTarget } =
useAiAssistant()
const pageRootRef = ref<HTMLElement | null>(null)
const loading = ref(false) const loading = ref(false)
watch(loading, (v) => setPageLoading(v), { immediate: true }) watch(loading, (v) => setPageLoading(v), { immediate: true })
onUnmounted(() => setPageLoading(false)) onMounted(() => {
setPageModuleName('供应商表现')
setPageModuleCode(SUPPLIER_PERFORMANCE_MODULE_KEY)
nextTick(() => {
if (pageRootRef.value) setScreenshotTarget(pageRootRef.value)
})
})
onUnmounted(() => {
setPageLoading(false)
setPageModuleName(null)
setPageModuleCode(null)
setScreenshotTarget(null)
})
const promptMap = ref<Record<string, string>>({}) const promptMap = ref<Record<string, string>>({})
const promptEditVisible = ref(false) const promptEditVisible = ref(false)
@@ -436,7 +454,11 @@ function onCardAiAnalysis(e: MouseEvent) {
const card = (e.currentTarget as HTMLElement).closest('.supplier-card') const card = (e.currentTarget as HTMLElement).closest('.supplier-card')
if (card instanceof HTMLElement) { if (card instanceof HTMLElement) {
const prompt = promptMap.value[SUPPLIER_PERFORMANCE_MODULE_KEY] const prompt = promptMap.value[SUPPLIER_PERFORMANCE_MODULE_KEY]
openWithScreenshot(card, { moduleName: '供应商表现', prompt }) openWithScreenshot(card, {
moduleName: '供应商表现',
moduleCode: SUPPLIER_PERFORMANCE_MODULE_KEY,
prompt
})
} }
} }
@@ -447,10 +469,6 @@ const rows = ref<any[]>([])
/** 标签分类筛选(与主页供应商表现一致) */ /** 标签分类筛选(与主页供应商表现一致) */
const labelFilter = ref<string>('all') const labelFilter = ref<string>('all')
const username = computed(() => {
return userStore.user?.username || ''
})
// 查询参数(与主页一致) // 查询参数(与主页一致)
const queryParams = reactive({ const queryParams = reactive({
rq: dayjs().subtract(6, 'day').format('YYYY-MM-DD'), rq: dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
@@ -657,51 +675,35 @@ function mapKehuToOptions(list: any): { label: string; value: string }[] {
if (!Array.isArray(list)) return [] if (!Array.isArray(list)) return []
return list.map((item: any) => { return list.map((item: any) => {
const label = item?.khmc ?? item?.CKMC ?? item?.KHMC ?? item?.DLMC ?? item?.label ?? item?.name ?? '' const label = item?.khmc ?? item?.CKMC ?? item?.KHMC ?? item?.DLMC ?? item?.label ?? item?.name ?? ''
const value = item?.khdm != null ? String(item.khdm) : item?.CKDM != null ? String(item.CKDM) : item?.KHDM != null ? String(item.KHDM) : item?.DLDM != null ? String(item.DLDM) : item?.value != null ? String(item.value) : item?.code != null ? String(item.code) : '' const rawVal = item?.khdm ?? item?.CKDM ?? item?.KHDM ?? item?.DLDM ?? item?.value ?? item?.code
const value = rawVal != null ? String(rawVal) : ''
return { label, value } return { label, value }
}).filter((o) => o.label && o.value) }).filter((o) => o.label && o.value)
} }
// 拉取下拉选项 // 拉取下拉选项(通用)
async function fetchBrandOptions() { const OPTION_TABLE_MAP: Array<{
try { tableName: string
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'pinpai' }) target: ReturnType<typeof ref<{ label: string; value: string }[]>>
brandOptions.value = mapPinpaiToOptions(resolveTableList(res)) mapper: (list: any) => { label: string; value: string }[]
} catch (_) { }> = [
brandOptions.value = [] { tableName: 'pinpai', target: brandOptions, mapper: mapPinpaiToOptions },
} { tableName: 'jijie', target: seasonOptions, mapper: mapJijieToOptions },
} { tableName: 'dalei', target: categoryOptions, mapper: mapDaleiToOptions },
{ tableName: 'kehu', target: storeOptions, mapper: mapKehuToOptions }
]
async function fetchSeasonOptions() { async function fetchAllOptions() {
await Promise.all(
OPTION_TABLE_MAP.map(async ({ tableName, target, mapper }) => {
try { try {
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'jijie' }) const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName })
seasonOptions.value = mapJijieToOptions(resolveTableList(res)) target.value = mapper(resolveTableList(res))
} catch (_) { } catch {
seasonOptions.value = [] target.value = []
} }
} })
)
async function fetchCategoryOptions() {
try {
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'dalei' })
categoryOptions.value = mapDaleiToOptions(resolveTableList(res))
} catch (_) {
categoryOptions.value = []
}
}
async function fetchStoreOptions() {
try {
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'kehu' })
storeOptions.value = mapKehuToOptions(resolveTableList(res))
} catch (_) {
storeOptions.value = []
}
}
/** 线路选项:暂不查后端,使用空默认值 */
function fetchLineOptions() {
lineOptions.value = []
} }
// 查询条件默认值:门店默认全选,品牌有数据且含 1 则默认 1 // 查询条件默认值:门店默认全选,品牌有数据且含 1 则默认 1
@@ -721,6 +723,9 @@ const columnsSorted = computed(() => {
return cols.sort((a, b) => (a.order ?? 999) - (b.order ?? 999)) return cols.sort((a, b) => (a.order ?? 999) - (b.order ?? 999))
}) })
/** 列表表格:检测指标列(排除第一列 title */
const metricColumns = computed(() => columnsSorted.value.slice(1))
/** 从 rows 中收集所有 label/tag按逗号拆分去重后作为筛选项首项为「全部」 */ /** 从 rows 中收集所有 label/tag按逗号拆分去重后作为筛选项首项为「全部」 */
const labelOpts = computed(() => { const labelOpts = computed(() => {
const labels = new Set<string>() const labels = new Set<string>()
@@ -770,9 +775,8 @@ function parseLabelColorPairs(labelStr: unknown, colorStr: unknown): { label: st
return labels.map((label, i) => ({ label, color: colors[i] ?? 'flat' })) return labels.map((label, i) => ({ label, color: colors[i] ?? 'flat' }))
} }
function formatVal(val: unknown, key: string): string { function formatVal(val: unknown): string {
if (val == null) return '-' if (val == null) return '-'
// 后端 value1/value2/value3 多为字符串(含 %),直接展示;数值则按千分位
if (typeof val === 'number') return val.toLocaleString('zh-CN') if (typeof val === 'number') return val.toLocaleString('zh-CN')
return String(val) return String(val)
} }
@@ -824,16 +828,16 @@ function getTitleValue(row: any): string {
return v != null ? String(v).trim() : '-' return v != null ? String(v).trim() : '-'
} }
/** 获取数据列(排除第一列 title:按接口列配置全部展示,不要求 date 非空 */ /** 获取数据列(排除第一列 title */
function getColumnsWithDate(row: any): ColumnCfg[] { function getColumnsWithDate(): ColumnCfg[] {
return columnsSorted.value.slice(1) return columnsSorted.value.slice(1)
} }
/** 生成表格数据:按列配置渲染,实际结果取 row[key],基准参考取 row[key+date] 或 '-' */ /** 生成表格数据:按列配置渲染,实际结果取 row[key],基准参考取 row[key+date] 或 '-' */
function getTableData(row: any): Array<{ metricName: string; actualValue: string; paramValue: string; valueColorClass?: string; arrow?: string; arrowClass?: string; labelPairs?: Array<{ label: string; color: string }> }> { function getTableData(row: any): Array<{ metricName: string; actualValue: string; paramValue: string; valueColorClass?: string; arrow?: string; arrowClass?: string; labelPairs?: Array<{ label: string; color: string }> }> {
const colsWithDate = getColumnsWithDate(row) const colsWithDate = getColumnsWithDate()
return colsWithDate.map((col) => { return colsWithDate.map((col) => {
const actualVal = formatVal((row as any)?.[col.key], col.key) const actualVal = formatVal((row as any)?.[col.key])
const dateVal = (row as any)?.[col.key + 'date'] const dateVal = (row as any)?.[col.key + 'date']
const paramVal = dateVal != null && String(dateVal).trim() !== '' ? String(dateVal).trim() : '-' const paramVal = dateVal != null && String(dateVal).trim() !== '' ? String(dateVal).trim() : '-'
@@ -882,6 +886,13 @@ function getTableData(row: any): Array<{ metricName: string; actualValue: string
}) })
} }
/** 获取某行第 ci 个指标项(用于列表表格按列展示),返回数组供 v-for 使用 */
function getMetricItemAt(row: any, ci: number) {
const data = getTableData(row)
const item = data[ci]
return item ? [item] : []
}
function parseResponseData(raw: unknown): { columns: ColumnCfg[]; list: any[] } { function parseResponseData(raw: unknown): { columns: ColumnCfg[]; list: any[] } {
if (Array.isArray(raw)) { if (Array.isArray(raw)) {
// 二维数组 [[列], [list]] // 二维数组 [[列], [list]]
@@ -915,8 +926,6 @@ function parseResponseData(raw: unknown): { columns: ColumnCfg[]; list: any[] }
const handleQuery = async () => { const handleQuery = async () => {
loading.value = true loading.value = true
try { try {
// 确保 username 有值:优先使用 computed如果为空则直接从 userStore 获取
const currentUsername = username.value || userStore.user?.username || ''
const res = await ReportApi.getCategoryPerformance({ const res = await ReportApi.getCategoryPerformance({
reportId: REPORT_ID, reportId: REPORT_ID,
name: 'YDY_AI_GET_GHSZX', name: 'YDY_AI_GET_GHSZX',
@@ -927,7 +936,7 @@ const handleQuery = async () => {
dalei: arrToQuery(queryParams.category), dalei: arrToQuery(queryParams.category),
jj: arrToQuery(queryParams.season), jj: arrToQuery(queryParams.season),
p: '123', p: '123',
username: currentUsername, username: userStore.user?.username || '',
ghsdm: supplierCode.value || undefined ghsdm: supplierCode.value || undefined
}) })
const raw = (res as any)?.data ?? res const raw = (res as any)?.data ?? res
@@ -961,28 +970,9 @@ const handleReset = () => {
} }
onMounted(async () => { onMounted(async () => {
await loadPromptMap()
// 等待下一个 tick确保路由参数完全加载
await nextTick()
// 先初始化路由参数
initQueryParamsFromRoute() initQueryParamsFromRoute()
await Promise.all([loadPromptMap(), fetchAllOptions()])
// 并行获取下拉选项数据(这些不依赖路由参数,用于填充下拉框)
// 注意:这些调用会立即执行,但它们是获取下拉选项数据,不是主查询
await Promise.all([
fetchBrandOptions(),
fetchSeasonOptions(),
fetchCategoryOptions(),
fetchLineOptions(),
fetchStoreOptions()
])
// 设置默认值
applyQueryDefaults() applyQueryDefaults()
// 最后执行主查询(此时路由参数已经读取完成,包括 code 参数)
// 主查询会使用 supplierCode.value从路由参数中读取的 code作为 ghsdm 参数
handleQuery() handleQuery()
}) })
</script> </script>
@@ -1208,44 +1198,9 @@ onMounted(async () => {
} }
} }
.table-core { .table-metric-actual,
display: flex; .table-metric-ref {
flex-direction: column;
gap: 4px;
.core-name {
font-size: 12px; font-size: 12px;
color: var(--el-text-color-secondary);
}
.core-value {
font-weight: 700;
color: var(--el-text-color-primary);
}
}
.metric-cell {
display: flex;
flex-direction: column;
gap: 10px;
padding: 2px 0;
}
.metric-cell-row {
display: grid;
grid-template-columns: 160px 1fr 120px;
gap: 12px;
align-items: start;
}
.metric-cell-name {
font-size: 12px;
color: var(--el-text-color-regular);
}
.metric-cell-actual {
font-size: 12px;
color: var(--el-text-color-primary);
line-height: 1.4; line-height: 1.4;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -1272,21 +1227,18 @@ onMounted(async () => {
font-weight: 700; font-weight: 700;
display: inline-block; display: inline-block;
&.badge-danger,
&.label-trend-up { &.label-trend-up {
background: #fee2e2; background: #fee2e2;
color: #ef4444; color: #ef4444;
border: 1px solid #fecaca; border: 1px solid #fecaca;
} }
&.badge-warn,
&.label-trend-flat { &.label-trend-flat {
background: #fef3c7; background: #fef3c7;
color: #d97706; color: #d97706;
border: 1px solid #fde68a; border: 1px solid #fde68a;
} }
&.badge-success,
&.label-trend-down { &.label-trend-down {
background: #dcfce7; background: #dcfce7;
color: #22c55e; color: #22c55e;
@@ -1295,12 +1247,22 @@ onMounted(async () => {
} }
} }
.metric-cell-ref { .table-metric-actual {
font-size: 12px; &.label-trend-up {
color: #ef4444;
}
&.label-trend-down {
color: #22c55e;
}
&.label-trend-flat {
color: #d97706;
}
}
.table-metric-ref {
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
text-align: right;
line-height: 1.4;
white-space: pre-line;
} }
.empty { .empty {

View File

@@ -553,7 +553,7 @@
row-class-name="product-list-row-clickable" row-class-name="product-list-row-clickable"
@row-click="handleProductRowClick" @row-click="handleProductRowClick"
> >
<el-table-column prop="productInfo" label="商品信息" width="280"> <el-table-column prop="productInfo" label="商品信息" width="200" fixed="left">
<template #default="{ row }"> <template #default="{ row }">
<el-popover <el-popover
placement="right" placement="right"
@@ -564,25 +564,19 @@
@show="handleProductHoverShow(row)" @show="handleProductHoverShow(row)"
> >
<template #reference> <template #reference>
<div class="product-info"> <div class="product-info product-info-compact">
<div class="product-image"> <div class="product-image">
<img v-if="row.imageUrl" :src="row.imageUrl" alt="商品图片" class="product-img" /> <img v-if="row.imageUrl" :src="row.imageUrl" alt="商品图片" class="product-img" />
<el-icon v-else><Picture /></el-icon> <el-icon v-else><Picture /></el-icon>
</div> </div>
<div class="product-details"> <div class="product-details">
<div class="product-code"> <div class="product-name">{{ row.name || '-' }}</div>
款号: <div
<span
class="product-code-link" class="product-code-link"
@click.stop="handleProductCodeClick(row)" @click.stop="handleProductCodeClick(row)"
> >
{{ getProductCode(row) }} {{ getProductCode(row) }}
</span>
</div> </div>
<div class="product-code">条码: {{ row.barcode || '-' }}</div>
<div class="product-code">颜色: {{ row.color }}</div>
<div class="product-code">进价: ¥{{ formatNumber(row.purchasePrice || 0) }}</div>
<div class="product-code">售价: ¥{{ formatNumber(row.sellingPrice || 0) }}</div>
</div> </div>
</div> </div>
</template> </template>
@@ -718,13 +712,37 @@
</el-popover> </el-popover>
</template> </template>
</el-table-column> </el-table-column>
<!-- 第二列商品属性 --> <!-- 商品属性拆开列展示 -->
<el-table-column prop="category" label="商品属性" width="200" show-overflow-tooltip> <el-table-column prop="code" label="款号" min-width="100" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<div>{{ row.category }}</div> <span
class="product-code-link"
@click.stop="handleProductCodeClick(row)"
>
{{ getProductCode(row) }}
</span>
</template> </template>
</el-table-column> </el-table-column>
<!-- 第三列销售数据 --> <el-table-column prop="barcode" label="条码" min-width="120" show-overflow-tooltip>
<template #default="{ row }">{{ row.barcode || '-' }}</template>
</el-table-column>
<el-table-column prop="color" label="颜色" min-width="80" show-overflow-tooltip>
<template #default="{ row }">{{ row.color || '-' }}</template>
</el-table-column>
<el-table-column prop="category" label="类目" min-width="120" show-overflow-tooltip>
<template #default="{ row }">{{ row.category || '-' }}</template>
</el-table-column>
<el-table-column prop="purchasePrice" label="进价" min-width="90" align="right">
<template #default="{ row }">
{{ row.purchasePrice != null ? '¥' + formatNumber(row.purchasePrice) : '-' }}
</template>
</el-table-column>
<el-table-column prop="sellingPrice" label="售价" min-width="90" align="right">
<template #default="{ row }">
{{ row.sellingPrice != null ? '¥' + formatNumber(row.sellingPrice) : '-' }}
</template>
</el-table-column>
<!-- 销售数据 -->
<el-table-column prop="ls" label="销售数据" align="right" width="150"> <el-table-column prop="ls" label="销售数据" align="right" width="150">
<template #default="{ row }"> <template #default="{ row }">
<div class="font-bold">{{ row.lsRaw || '-' }}</div> <div class="font-bold">{{ row.lsRaw || '-' }}</div>
@@ -3586,6 +3604,17 @@ onDeactivated(() => {
gap: 12px; gap: 12px;
align-items: flex-start; align-items: flex-start;
&.product-info-compact .product-details {
.product-name {
font-size: 13px;
font-weight: 600;
margin-bottom: 2px;
}
.product-code-link {
font-size: 12px;
}
}
.product-image { .product-image {
width: 48px; width: 48px;
height: 56px; height: 56px;