1.加入ai对话框 模块日报功能
This commit is contained in:
4
.env.dev
4
.env.dev
@@ -4,8 +4,8 @@ NODE_ENV=production
|
||||
VITE_DEV=true
|
||||
|
||||
# 请求路径
|
||||
VITE_BASE_URL='http://localhost:48080'
|
||||
#VITE_BASE_URL='http://118.253.178.8:48080'
|
||||
#VITE_BASE_URL='http://localhost:48080'
|
||||
VITE_BASE_URL='http://118.253.178.8:48080'
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
|
||||
VITE_UPLOAD_TYPE=server
|
||||
|
||||
@@ -4,8 +4,8 @@ NODE_ENV=development
|
||||
VITE_DEV=true
|
||||
|
||||
# 请求路径
|
||||
#VITE_BASE_URL='http://118.253.178.8:48080'
|
||||
VITE_BASE_URL='http://localhost:48080'
|
||||
VITE_BASE_URL='http://118.253.178.8:48080'
|
||||
#VITE_BASE_URL='http://localhost:48080'
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
||||
VITE_UPLOAD_TYPE=server
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface TenantVO {
|
||||
accountCount: number
|
||||
websites: string[]
|
||||
createTime: Date
|
||||
tenantPrompt?: string
|
||||
}
|
||||
|
||||
export interface TenantPageReqVO extends PageParam {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
BarChart,
|
||||
FunnelChart,
|
||||
GaugeChart,
|
||||
HeatmapChart,
|
||||
LineChart,
|
||||
MapChart,
|
||||
PictorialBarChart,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
|
||||
import {
|
||||
AriaComponent,
|
||||
CalendarComponent,
|
||||
DataZoomComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
@@ -37,6 +39,7 @@ echarts.use([
|
||||
AriaComponent,
|
||||
ParallelComponent,
|
||||
VisualMapComponent,
|
||||
CalendarComponent,
|
||||
BarChart,
|
||||
LineChart,
|
||||
PieChart,
|
||||
@@ -45,7 +48,8 @@ echarts.use([
|
||||
PictorialBarChart,
|
||||
RadarChart,
|
||||
GaugeChart,
|
||||
FunnelChart
|
||||
FunnelChart,
|
||||
HeatmapChart
|
||||
])
|
||||
|
||||
export default echarts
|
||||
|
||||
@@ -792,7 +792,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
name: 'SupplierPerformance',
|
||||
meta: {
|
||||
title: '供应商详情',
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true
|
||||
},
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
class="w-full"
|
||||
/>
|
||||
</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-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
@@ -103,13 +106,15 @@ const formData = ref({
|
||||
expireTime: undefined,
|
||||
websites: [],
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
tenantPrompt: '',
|
||||
// 新增专属
|
||||
username: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const formRules = reactive({
|
||||
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' }],
|
||||
status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }],
|
||||
accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }],
|
||||
@@ -178,6 +183,7 @@ const resetForm = () => {
|
||||
expireTime: undefined,
|
||||
websites: [],
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
tenantPrompt: '',
|
||||
username: undefined,
|
||||
password: undefined
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="supplier-performance-page">
|
||||
<div ref="pageRootRef" class="supplier-performance-page">
|
||||
<!-- 查询条件区域:与主页一致 -->
|
||||
<el-card class="query-card" shadow="never">
|
||||
<div class="query-form">
|
||||
@@ -265,7 +265,7 @@
|
||||
</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">
|
||||
<span>检测指标</span>
|
||||
<span>实际结果</span>
|
||||
@@ -313,10 +313,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表形式(不需要悬浮卡片) -->
|
||||
<!-- 列表形式:每个字段独立一列铺开展示 -->
|
||||
<div v-show="displayMode === 'table'" class="table-wrap">
|
||||
<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 }">
|
||||
<div class="table-title">
|
||||
<div class="table-title-main">{{ String((row as any)?.title ?? (row as any)?.name ?? '-') }}</div>
|
||||
@@ -333,24 +333,21 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="核心指标" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="table-core">
|
||||
<div class="core-name">{{ getTitleName(row) || '-' }}</div>
|
||||
<div class="core-value">{{ getTitleValue(row) || '-' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-table-column label="核心指标名称" min-width="120" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ getTitleName(row) || '-' }}</template>
|
||||
</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 }">
|
||||
<div class="metric-cell">
|
||||
<div
|
||||
v-for="(item, mi) in getTableData(row)"
|
||||
:key="mi"
|
||||
class="metric-cell-row"
|
||||
v-for="(item, mi) in getMetricItemAt(row, ci)"
|
||||
:key="item.metricName + '-' + mi"
|
||||
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 }}
|
||||
<span v-if="item.arrow" class="trend-arrow" :class="item.arrowClass">{{ item.arrow }}</span>
|
||||
<template v-if="item.labelPairs && item.labelPairs.length > 0">
|
||||
@@ -364,11 +361,16 @@
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="metric-cell-ref">{{ item.paramValue }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -384,7 +386,9 @@
|
||||
</template>
|
||||
|
||||
<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 { ElMessage } from 'element-plus'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -410,10 +414,24 @@ const SUPPLIER_PERFORMANCE_MODULE_KEY = `${SUPPLIER_PERFORMANCE_COMPONENT}:main`
|
||||
const REPORT_ID = 6
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const { openWithScreenshot, setPageLoading } = useAiAssistant()
|
||||
const { openWithScreenshot, setPageLoading, setPageModuleName, setPageModuleCode, setScreenshotTarget } =
|
||||
useAiAssistant()
|
||||
const pageRootRef = ref<HTMLElement | null>(null)
|
||||
const loading = ref(false)
|
||||
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 promptEditVisible = ref(false)
|
||||
@@ -436,7 +454,11 @@ function onCardAiAnalysis(e: MouseEvent) {
|
||||
const card = (e.currentTarget as HTMLElement).closest('.supplier-card')
|
||||
if (card instanceof HTMLElement) {
|
||||
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 username = computed(() => {
|
||||
return userStore.user?.username || ''
|
||||
})
|
||||
|
||||
// 查询参数(与主页一致)
|
||||
const queryParams = reactive({
|
||||
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 []
|
||||
return list.map((item: any) => {
|
||||
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 }
|
||||
}).filter((o) => o.label && o.value)
|
||||
}
|
||||
|
||||
// 拉取下拉选项
|
||||
async function fetchBrandOptions() {
|
||||
try {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'pinpai' })
|
||||
brandOptions.value = mapPinpaiToOptions(resolveTableList(res))
|
||||
} catch (_) {
|
||||
brandOptions.value = []
|
||||
}
|
||||
}
|
||||
// 拉取下拉选项(通用)
|
||||
const OPTION_TABLE_MAP: Array<{
|
||||
tableName: string
|
||||
target: ReturnType<typeof ref<{ label: string; value: string }[]>>
|
||||
mapper: (list: any) => { label: string; value: string }[]
|
||||
}> = [
|
||||
{ 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 {
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName: 'jijie' })
|
||||
seasonOptions.value = mapJijieToOptions(resolveTableList(res))
|
||||
} catch (_) {
|
||||
seasonOptions.value = []
|
||||
const res = await ReportApi.executeTable({ reportId: REPORT_ID, tableName })
|
||||
target.value = mapper(resolveTableList(res))
|
||||
} catch {
|
||||
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
|
||||
@@ -721,6 +723,9 @@ const columnsSorted = computed(() => {
|
||||
return cols.sort((a, b) => (a.order ?? 999) - (b.order ?? 999))
|
||||
})
|
||||
|
||||
/** 列表表格:检测指标列(排除第一列 title) */
|
||||
const metricColumns = computed(() => columnsSorted.value.slice(1))
|
||||
|
||||
/** 从 rows 中收集所有 label/tag(按逗号拆分)去重后作为筛选项,首项为「全部」 */
|
||||
const labelOpts = computed(() => {
|
||||
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' }))
|
||||
}
|
||||
|
||||
function formatVal(val: unknown, key: string): string {
|
||||
function formatVal(val: unknown): string {
|
||||
if (val == null) return '-'
|
||||
// 后端 value1/value2/value3 多为字符串(含 %),直接展示;数值则按千分位
|
||||
if (typeof val === 'number') return val.toLocaleString('zh-CN')
|
||||
return String(val)
|
||||
}
|
||||
@@ -824,16 +828,16 @@ function getTitleValue(row: any): string {
|
||||
return v != null ? String(v).trim() : '-'
|
||||
}
|
||||
|
||||
/** 获取数据列(排除第一列 title):按接口列配置全部展示,不要求 date 非空 */
|
||||
function getColumnsWithDate(row: any): ColumnCfg[] {
|
||||
/** 获取数据列(排除第一列 title) */
|
||||
function getColumnsWithDate(): ColumnCfg[] {
|
||||
return columnsSorted.value.slice(1)
|
||||
}
|
||||
|
||||
/** 生成表格数据:按列配置渲染,实际结果取 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 }> }> {
|
||||
const colsWithDate = getColumnsWithDate(row)
|
||||
const colsWithDate = getColumnsWithDate()
|
||||
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 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[] } {
|
||||
if (Array.isArray(raw)) {
|
||||
// 二维数组 [[列], [list]]
|
||||
@@ -915,8 +926,6 @@ function parseResponseData(raw: unknown): { columns: ColumnCfg[]; list: any[] }
|
||||
const handleQuery = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 确保 username 有值:优先使用 computed,如果为空则直接从 userStore 获取
|
||||
const currentUsername = username.value || userStore.user?.username || ''
|
||||
const res = await ReportApi.getCategoryPerformance({
|
||||
reportId: REPORT_ID,
|
||||
name: 'YDY_AI_GET_GHSZX',
|
||||
@@ -927,7 +936,7 @@ const handleQuery = async () => {
|
||||
dalei: arrToQuery(queryParams.category),
|
||||
jj: arrToQuery(queryParams.season),
|
||||
p: '123',
|
||||
username: currentUsername,
|
||||
username: userStore.user?.username || '',
|
||||
ghsdm: supplierCode.value || undefined
|
||||
})
|
||||
const raw = (res as any)?.data ?? res
|
||||
@@ -961,28 +970,9 @@ const handleReset = () => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadPromptMap()
|
||||
// 等待下一个 tick,确保路由参数完全加载
|
||||
await nextTick()
|
||||
|
||||
// 先初始化路由参数
|
||||
initQueryParamsFromRoute()
|
||||
|
||||
// 并行获取下拉选项数据(这些不依赖路由参数,用于填充下拉框)
|
||||
// 注意:这些调用会立即执行,但它们是获取下拉选项数据,不是主查询
|
||||
await Promise.all([
|
||||
fetchBrandOptions(),
|
||||
fetchSeasonOptions(),
|
||||
fetchCategoryOptions(),
|
||||
fetchLineOptions(),
|
||||
fetchStoreOptions()
|
||||
])
|
||||
|
||||
// 设置默认值
|
||||
await Promise.all([loadPromptMap(), fetchAllOptions()])
|
||||
applyQueryDefaults()
|
||||
|
||||
// 最后执行主查询(此时路由参数已经读取完成,包括 code 参数)
|
||||
// 主查询会使用 supplierCode.value(从路由参数中读取的 code)作为 ghsdm 参数
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
@@ -1208,44 +1198,9 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.table-core {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.core-name {
|
||||
.table-metric-actual,
|
||||
.table-metric-ref {
|
||||
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;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -1272,21 +1227,18 @@ onMounted(async () => {
|
||||
font-weight: 700;
|
||||
display: inline-block;
|
||||
|
||||
&.badge-danger,
|
||||
&.label-trend-up {
|
||||
background: #fee2e2;
|
||||
color: #ef4444;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
&.badge-warn,
|
||||
&.label-trend-flat {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
border: 1px solid #fde68a;
|
||||
}
|
||||
|
||||
&.badge-success,
|
||||
&.label-trend-down {
|
||||
background: #dcfce7;
|
||||
color: #22c55e;
|
||||
@@ -1295,12 +1247,22 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.metric-cell-ref {
|
||||
font-size: 12px;
|
||||
.table-metric-actual {
|
||||
&.label-trend-up {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
&.label-trend-down {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
&.label-trend-flat {
|
||||
color: #d97706;
|
||||
}
|
||||
}
|
||||
|
||||
.table-metric-ref {
|
||||
color: var(--el-text-color-secondary);
|
||||
text-align: right;
|
||||
line-height: 1.4;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.empty {
|
||||
|
||||
@@ -553,7 +553,7 @@
|
||||
row-class-name="product-list-row-clickable"
|
||||
@row-click="handleProductRowClick"
|
||||
>
|
||||
<el-table-column prop="productInfo" label="商品信息" width="280">
|
||||
<el-table-column prop="productInfo" label="商品信息" width="200" fixed="left">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
placement="right"
|
||||
@@ -564,25 +564,19 @@
|
||||
@show="handleProductHoverShow(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="product-info">
|
||||
<div class="product-info product-info-compact">
|
||||
<div class="product-image">
|
||||
<img v-if="row.imageUrl" :src="row.imageUrl" alt="商品图片" class="product-img" />
|
||||
<el-icon v-else><Picture /></el-icon>
|
||||
</div>
|
||||
<div class="product-details">
|
||||
<div class="product-code">
|
||||
款号:
|
||||
<span
|
||||
<div class="product-name">{{ row.name || '-' }}</div>
|
||||
<div
|
||||
class="product-code-link"
|
||||
@click.stop="handleProductCodeClick(row)"
|
||||
>
|
||||
{{ getProductCode(row) }}
|
||||
</span>
|
||||
</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>
|
||||
</template>
|
||||
@@ -718,13 +712,37 @@
|
||||
</el-popover>
|
||||
</template>
|
||||
</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 }">
|
||||
<div>{{ row.category }}</div>
|
||||
<span
|
||||
class="product-code-link"
|
||||
@click.stop="handleProductCodeClick(row)"
|
||||
>
|
||||
{{ getProductCode(row) }}
|
||||
</span>
|
||||
</template>
|
||||
</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">
|
||||
<template #default="{ row }">
|
||||
<div class="font-bold">{{ row.lsRaw || '-' }}</div>
|
||||
@@ -3586,6 +3604,17 @@ onDeactivated(() => {
|
||||
gap: 12px;
|
||||
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 {
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
|
||||
Reference in New Issue
Block a user