!159 完善商品查看详情,修改了一些问题

Merge pull request !159 from puhui999/dev-to-dev
This commit is contained in:
芋道源码 2023-06-03 12:09:43 +00:00 committed by Gitee
commit 541b23c8c1
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
19 changed files with 657 additions and 132 deletions

View File

@ -19,13 +19,13 @@ VITE_API_URL=/admin-api
VITE_BASE_PATH=/ VITE_BASE_PATH=/
# 是否删除debugger # 是否删除debugger
VITE_DROP_DEBUGGER=false VITE_DROP_DEBUGGER=true
# 是否删除console.log # 是否删除console.log
VITE_DROP_CONSOLE=false VITE_DROP_CONSOLE=false
# 是否sourcemap # 是否sourcemap
VITE_SOURCEMAP=true VITE_SOURCEMAP=false
# 输出路径 # 输出路径
VITE_OUT_DIR=dist-dev VITE_OUT_DIR=dist-dev

View File

@ -62,8 +62,9 @@
"qs": "^6.11.1", "qs": "^6.11.1",
"steady-xml": "^0.1.0", "steady-xml": "^0.1.0",
"url": "^0.11.0", "url": "^0.11.0",
"video.js": "^8.0.4", "video.js": "^8.3.0",
"vue": "3.2.47", "vue": "3.3.4",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.2.2", "vue-i18n": "9.2.2",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vue-types": "^5.0.2", "vue-types": "^5.0.2",

View File

@ -7,8 +7,7 @@ export interface Property {
valueName?: string // 属性值名称 valueName?: string // 属性值名称
} }
// TODO puhui999是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型? export interface Sku {
export interface SkuType {
id?: number // 商品 SKU 编号 id?: number // 商品 SKU 编号
spuId?: number // SPU 编号 spuId?: number // SPU 编号
properties?: Property[] // 属性数组 properties?: Property[] // 属性数组
@ -25,8 +24,7 @@ export interface SkuType {
salesCount?: number // 商品销量 salesCount?: number // 商品销量
} }
// TODO puhui999是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型? export interface Spu {
export interface SpuType {
id?: number id?: number
name?: string // 商品名称 name?: string // 商品名称
categoryId?: number | null // 商品分类 categoryId?: number | null // 商品分类
@ -39,9 +37,9 @@ export interface SpuType {
brandId?: number | null // 商品品牌编号 brandId?: number | null // 商品品牌编号
specType?: boolean // 商品规格 specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型 subCommissionType?: boolean // 分销类型
skus: SkuType[] // sku数组 skus?: Sku[] // sku数组
description?: string // 商品详情 description?: string // 商品详情
sort?: string // 商品排序 sort?: number // 商品排序
giveIntegral?: number // 赠送积分 giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量 virtualSalesCount?: number // 虚拟销量
recommendHot?: boolean // 是否热卖 recommendHot?: boolean // 是否热卖
@ -62,12 +60,12 @@ export const getTabsCount = () => {
} }
// 创建商品 Spu // 创建商品 Spu
export const createSpu = (data: SpuType) => { export const createSpu = (data: Spu) => {
return request.post({ url: '/product/spu/create', data }) return request.post({ url: '/product/spu/create', data })
} }
// 更新商品 Spu // 更新商品 Spu
export const updateSpu = (data: SpuType) => { export const updateSpu = (data: Spu) => {
return request.put({ url: '/product/spu/update', data }) return request.put({ url: '/product/spu/update', data })
} }

View File

@ -33,6 +33,11 @@ export const getDeliveryExpressTemplate = async (id: number) => {
return await request.get({ url: '/trade/delivery/express-template/get?id=' + id }) return await request.get({ url: '/trade/delivery/express-template/get?id=' + id })
} }
// 查询快递运费模板详情
export const getSimpleTemplateList = async () => {
return await request.get({ url: '/trade/delivery/express-template/list-all-simple' })
}
// 新增快递运费模板 // 新增快递运费模板
export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => { export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
return await request.post({ url: '/trade/delivery/express-template/create', data }) return await request.post({ url: '/trade/delivery/express-template/create', data })

View File

@ -1,16 +1,16 @@
<script lang="tsx"> <script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue' import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus' import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
import { componentMap } from './componentMap' import { componentMap } from './componentMap'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { getSlot } from '@/utils/tsxHelper' import { getSlot } from '@/utils/tsxHelper'
import { import {
setTextPlaceholder,
setGridProp,
setComponentProps,
setItemComponentSlots,
initModel, initModel,
setFormItemSlots setComponentProps,
setFormItemSlots,
setGridProp,
setItemComponentSlots,
setTextPlaceholder
} from './helper' } from './helper'
import { useRenderSelect } from './components/useRenderSelect' import { useRenderSelect } from './components/useRenderSelect'
import { useRenderRadio } from './components/useRenderRadio' import { useRenderRadio } from './components/useRenderRadio'
@ -196,7 +196,7 @@ export default defineComponent({
<span>{item.label}</span> <span>{item.label}</span>
<ElTooltip placement="right" raw-content> <ElTooltip placement="right" raw-content>
{{ {{
content: () => <span v-html={item.labelMessage}></span>, content: () => <span v-dompurify-html={item.labelMessage}></span>,
default: () => ( default: () => (
<Icon <Icon
icon="ep:warning" icon="ep:warning"

View File

@ -38,9 +38,10 @@ import App from './App.vue'
import './permission' import './permission'
import '@/plugins/tongji' // 百度统计 import '@/plugins/tongji' // 百度统计
import Logger from '@/utils/Logger' import Logger from '@/utils/Logger'
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
// 创建实例 // 创建实例
const setupAll = async () => { const setupAll = async () => {
const app = createApp(App) const app = createApp(App)
@ -61,6 +62,8 @@ const setupAll = async () => {
await router.isReady() await router.isReady()
app.use(VueDOMPurifyHTML)
app.mount('#app') app.mount('#app')
} }

View File

@ -379,6 +379,19 @@ const remainingRouter: AppRouteRecordRaw[] = [
title: '编辑商品', title: '编辑商品',
activeMenu: '/product/product-spu' activeMenu: '/product/product-spu'
} }
},
{
path: 'productSpuDetail/:spuId(\\d+)',
component: () => import('@/views/mall/product/spu/addForm.vue'),
name: 'productSpuDetail',
meta: {
noCache: true,
hidden: true,
canTo: true,
icon: 'ep:view',
title: '商品详情',
activeMenu: '/product/product-spu'
}
} }
] ]
} }

View File

@ -3,6 +3,7 @@ interface TreeHelperConfig {
children: string children: string
pid: string pid: string
} }
const DEFAULT_CONFIG: TreeHelperConfig = { const DEFAULT_CONFIG: TreeHelperConfig = {
id: 'id', id: 'id',
children: 'children', children: 'children',
@ -133,6 +134,7 @@ export const filter = <T = any>(
): T[] => { ): T[] => {
config = getConfig(config) config = getConfig(config)
const children = config.children as string const children = config.children as string
function listFilter(list: T[]) { function listFilter(list: T[]) {
return list return list
.map((node: any) => ({ ...node })) .map((node: any) => ({ ...node }))
@ -141,6 +143,7 @@ export const filter = <T = any>(
return func(node) || (node[children] && node[children].length) return func(node) || (node[children] && node[children].length)
}) })
} }
return listFilter(tree) return listFilter(tree)
} }
@ -264,6 +267,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children
} }
} }
} }
return tree return tree
} }
@ -302,3 +306,91 @@ export const handleTree2 = (data, id, parentId, children, rootId) => {
}) })
return treeData !== '' ? treeData : data return treeData !== '' ? treeData : data
} }
/**
*
* @param tree
* @param nodeId
* @param level ,
*/
export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => {
if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
console.warn('tree must be an array')
return false
}
// 校验是否是一级节点
if (tree.some((item) => item.id === nodeId)) {
return false
}
// 递归计数
let count = 1
// 深层次校验
function performAThoroughValidation(arr: any[]): boolean {
count += 1
for (const item of arr) {
if (item.id === nodeId) {
return true
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
if (performAThoroughValidation(item.children)) {
return true
}
}
}
return false
}
for (const item of tree) {
count = 1
if (performAThoroughValidation(item.children)) {
// 找到后对比是否是期望的层级
if (count >= level) {
return true
}
}
}
return false
}
/**
*
* @param tree
* @param nodeId id
*/
export const treeToString = (tree: any[], nodeId) => {
if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
console.warn('tree must be an array')
return ''
}
// 校验是否是一级节点
const node = tree.find((item) => item.id === nodeId)
if (typeof node !== 'undefined') {
return node.name
}
let str = ''
function performAThoroughValidation(arr) {
for (const item of arr) {
if (item.id === nodeId) {
str += `/${item.name}`
return true
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
str += `/${item.name}`
if (performAThoroughValidation(item.children)) {
return true
}
}
}
return false
}
for (const item of tree) {
str = `${item.name}`
if (performAThoroughValidation(item.children)) {
break
}
}
return str
}

View File

@ -16,20 +16,20 @@
</ContentWrap> </ContentWrap>
<!-- 弹窗表单预览 --> <!-- 弹窗表单预览 -->
<Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600"> <Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
<div ref="editor" v-if="dialogVisible"> <div v-if="dialogVisible" ref="editor">
<el-button style="float: right" @click="copy(formData)"> <el-button style="float: right" @click="copy(formData)">
{{ t('common.copy') }} {{ t('common.copy') }}
</el-button> </el-button>
<el-scrollbar height="580"> <el-scrollbar height="580">
<div> <div>
<pre><code class="hljs" v-html="highlightedCode(formData)"></code></pre> <pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts" name="InfraBuild"> <script lang="ts" name="InfraBuild" setup>
import FcDesigner from '@form-create/designer' import FcDesigner from '@form-create/designer'
import { useClipboard } from '@vueuse/core' import { useClipboard } from '@vueuse/core'
import { isString } from '@/utils/is' import { isString } from '@/utils/is'

View File

@ -46,7 +46,7 @@
{{ t('common.copy') }} {{ t('common.copy') }}
</el-button> </el-button>
<div> <div>
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre> <pre><code v-dompurify-html="highlightedCode(item)" class="hljs"></code></pre>
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>

View File

@ -5,6 +5,7 @@
<BasicInfoForm <BasicInfoForm
ref="basicInfoRef" ref="basicInfoRef"
v-model:activeName="activeName" v-model:activeName="activeName"
:is-detail="isDetail"
:propFormData="formData" :propFormData="formData"
/> />
</el-tab-pane> </el-tab-pane>
@ -12,6 +13,7 @@
<DescriptionForm <DescriptionForm
ref="descriptionRef" ref="descriptionRef"
v-model:activeName="activeName" v-model:activeName="activeName"
:is-detail="isDetail"
:propFormData="formData" :propFormData="formData"
/> />
</el-tab-pane> </el-tab-pane>
@ -19,13 +21,16 @@
<OtherSettingsForm <OtherSettingsForm
ref="otherSettingsRef" ref="otherSettingsRef"
v-model:activeName="activeName" v-model:activeName="activeName"
:is-detail="isDetail"
:propFormData="formData" :propFormData="formData"
/> />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<el-form> <el-form>
<el-form-item style="float: right"> <el-form-item style="float: right">
<el-button :loading="formLoading" type="primary" @click="submitForm">保存</el-button> <el-button v-if="!isDetail" :loading="formLoading" type="primary" @click="submitForm">
保存
</el-button>
<el-button @click="close">返回</el-button> <el-button @click="close">返回</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -42,16 +47,17 @@ import { convertToInteger, formatToFraction } from '@/utils'
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
const { push, currentRoute } = useRouter() // const { push, currentRoute } = useRouter() //
const { params } = useRoute() // const { params, name } = useRoute() //
const { delView } = useTagsViewStore() // const { delView } = useTagsViewStore() //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const activeName = ref('basicInfo') // Tag const activeName = ref('basicInfo') // Tag
const isDetail = ref(false) //
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // Ref const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // Ref
const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // Ref const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // Ref
const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // Ref const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // Ref
// spu // spu
const formData = ref<ProductSpuApi.SpuType>({ const formData = ref<ProductSpuApi.Spu>({
name: '', // name: '', //
categoryId: null, // categoryId: null, //
keyword: '', // keyword: '', //
@ -59,7 +65,7 @@ const formData = ref<ProductSpuApi.SpuType>({
picUrl: '', // picUrl: '', //
sliderPicUrls: [], // sliderPicUrls: [], //
introduction: '', // introduction: '', //
deliveryTemplateId: 1, // deliveryTemplateId: null, //
brandId: null, // brandId: null, //
specType: false, // specType: false, //
subCommissionType: false, // subCommissionType: false, //
@ -90,12 +96,15 @@ const formData = ref<ProductSpuApi.SpuType>({
/** 获得详情 */ /** 获得详情 */
const getDetail = async () => { const getDetail = async () => {
if ('productSpuDetail' === name) {
isDetail.value = true
}
const id = params.spuId as number const id = params.spuId as number
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.Spu
res.skus.forEach((item) => { res.skus!.forEach((item) => {
// //
item.price = formatToFraction(item.price) item.price = formatToFraction(item.price)
item.marketPrice = formatToFraction(item.marketPrice) item.marketPrice = formatToFraction(item.marketPrice)
@ -120,9 +129,10 @@ const submitForm = async () => {
await unref(basicInfoRef)?.validate() await unref(basicInfoRef)?.validate()
await unref(descriptionRef)?.validate() await unref(descriptionRef)?.validate()
await unref(otherSettingsRef)?.validate() await unref(otherSettingsRef)?.validate()
const deepCopyFormData = cloneDeep(unref(formData.value)) // fix: server // , server
// TODO sku const deepCopyFormData = cloneDeep(unref(formData.value))
formData.value.skus.forEach((sku) => { // sku
formData.value.skus!.forEach((sku) => {
// //
if (sku.barCode === '') { if (sku.barCode === '') {
const index = deepCopyFormData.skus.findIndex( const index = deepCopyFormData.skus.findIndex(
@ -150,7 +160,7 @@ const submitForm = async () => {
}) })
deepCopyFormData.sliderPicUrls = newSliderPicUrls deepCopyFormData.sliderPicUrls = newSliderPicUrls
// //
const data = deepCopyFormData as ProductSpuApi.SpuType const data = deepCopyFormData as ProductSpuApi.Spu
const id = params.spuId as number const id = params.spuId as number
if (!id) { if (!id) {
await ProductSpuApi.createSpu(data) await ProductSpuApi.createSpu(data)
@ -170,7 +180,6 @@ const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
push('/product/product-spu') push('/product/product-spu')
} }
/** 初始化 */ /** 初始化 */
onMounted(async () => { onMounted(async () => {
await getDetail() await getDetail()

View File

@ -1,5 +1,11 @@
<template> <template>
<el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px"> <el-form
v-if="!isDetail"
ref="productSpuBasicInfoRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="商品名称" prop="name"> <el-form-item label="商品名称" prop="name">
@ -7,7 +13,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<!-- TODO @puhui999只能选根节点 --> <!-- TODO @puhui999只能选根节点 fix: 已完善-->
<el-form-item label="商品分类" prop="categoryId"> <el-form-item label="商品分类" prop="categoryId">
<el-tree-select <el-tree-select
v-model="formData.categoryId" v-model="formData.categoryId"
@ -17,6 +23,7 @@
class="w-1/1" class="w-1/1"
node-key="id" node-key="id"
placeholder="请选择商品分类" placeholder="请选择商品分类"
@change="nodeClick"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -60,9 +67,15 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="运费模板" prop="deliveryTemplateId"> <el-form-item label="运费模板" prop="deliveryTemplateId">
<el-select v-model="formData.deliveryTemplateId" placeholder="请选择"> <el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
<el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" /> <el-option
v-for="item in deliveryTemplateList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select> </el-select>
<el-button class="ml-20px">运费模板</el-button> <!-- TODO 可能情况善品录入后选择运费发现下拉选择中没有对应的模版 这里需不需要做添加运费模版后选择的功能 -->
<!-- <el-button class="ml-20px">运费模板</el-button>-->
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -95,6 +108,9 @@
</el-col> </el-col>
<!-- 多规格添加--> <!-- 多规格添加-->
<el-col :span="24"> <el-col :span="24">
<el-form-item v-if="!formData.specType">
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
<el-form-item v-if="formData.specType" label="商品属性"> <el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button> <el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
<ProductAttributes :propertyList="propertyList" @success="generateSkus" /> <ProductAttributes :propertyList="propertyList" @success="generateSkus" />
@ -107,34 +123,89 @@
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" /> <SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item> </el-form-item>
</template> </template>
<el-form-item v-if="!formData.specType">
<SkuList :prop-form-data="formData" :propertyList="propertyList" />
</el-form-item>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" /> <ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
<!-- 详情跟表单放在一块可以共用已有功能再抽离成组件有点过度封装的感觉 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template>
<template #brandId="{ row }">
{{ brandList.find((item) => item.id === row.brandId)?.name }}
</template>
<template #deliveryTemplateId="{ row }">
{{ deliveryTemplateList.find((item) => item.id === row.deliveryTemplateId)?.name }}
</template>
<template #specType="{ row }">
{{ row.specType ? '多规格' : '单规格' }}
</template>
<template #subCommissionType="{ row }">
{{ row.subCommissionType ? '自行设置' : '默认设置' }}
</template>
<template #picUrl="{ row }">
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
</template>
<template #sliderPicUrls="{ row }">
<el-image
v-for="(item, index) in row.sliderPicUrls"
:key="index"
:src="item.url"
class="w-60px h-60px mr-10px"
@click="imagePreview(row.sliderPicUrls)"
/>
</template>
<template #skus>
<SkuList
ref="skuDetailListRef"
:is-detail="isDetail"
:prop-form-data="formData"
:propertyList="propertyList"
/>
</template>
</Descriptions>
</template> </template>
<script lang="ts" name="ProductSpuBasicInfoForm" setup> <script lang="ts" name="ProductSpuBasicInfoForm" setup>
import { PropType } from 'vue' import { PropType } from 'vue'
import { isArray } from '@/utils/is'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { defaultProps, handleTree } from '@/utils/tree' import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
import { createImageViewer } from '@/components/ImageViewer'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import type { SpuType } from '@/api/mall/product/spu'
import { UploadImg, UploadImgs } from '@/components/UploadFile' import { UploadImg, UploadImgs } from '@/components/UploadFile'
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index' import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
import { basicInfoSchema } from './spu.data'
import type { Spu } from '@/api/mall/product/spu'
import * as ProductCategoryApi from '@/api/mall/product/category' import * as ProductCategoryApi from '@/api/mall/product/category'
import { getSimpleBrandList } from '@/api/mall/product/brand' import { getSimpleBrandList } from '@/api/mall/product/brand'
import { getSimpleTemplateList } from '@/api/mall/trade/delivery/expressTemplate/index'
// ====== ======
const { allSchemas } = useCrudSchemas(basicInfoSchema)
/** 商品图预览 */
const imagePreview = (args) => {
const urlList = []
if (isArray(args)) {
args.forEach((item) => {
urlList.push(item.url)
})
} else {
urlList.push(args)
}
createImageViewer({
urlList
})
}
// ====== end ======
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<SpuType>, type: Object as PropType<Spu>,
default: () => {} default: () => {}
}, },
activeName: propTypes.string.def('') activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) //
}) })
const attributesAddFormRef = ref() // const attributesAddFormRef = ref() //
const productSpuBasicInfoRef = ref() // Ref const productSpuBasicInfoRef = ref() // Ref
@ -144,15 +215,15 @@ const skuListRef = ref() // 商品属性列表Ref
const generateSkus = (propertyList) => { const generateSkus = (propertyList) => {
skuListRef.value.generateTableData(propertyList) skuListRef.value.generateTableData(propertyList)
} }
const formData = reactive<SpuType>({ const formData = reactive<Spu>({
name: '', // name: '', //
categoryId: null, // categoryId: null, //
keyword: '', // keyword: '', //
unit: '', // unit: null, //
picUrl: '', // picUrl: '', //
sliderPicUrls: [], // sliderPicUrls: [], //
introduction: '', // introduction: '', //
deliveryTemplateId: 1, // deliveryTemplateId: null, //
brandId: null, // brandId: null, //
specType: false, // specType: false, //
subCommissionType: false, // subCommissionType: false, //
@ -185,26 +256,24 @@ watch(
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({ formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
url: item url: item
})) }))
// TODO @puhui999if return
// //
if (formData.specType) { if (!formData.specType) return
// skus propertyList // skus propertyList
const properties = [] const properties = []
formData.skus.forEach((sku) => { formData.skus.forEach((sku) => {
sku.properties.forEach(({ propertyId, propertyName, valueId, valueName }) => { sku.properties.forEach(({ propertyId, propertyName, valueId, valueName }) => {
// //
if (!properties.some((item) => item.id === propertyId)) { if (!properties.some((item) => item.id === propertyId)) {
properties.push({ id: propertyId, name: propertyName, values: [] }) properties.push({ id: propertyId, name: propertyName, values: [] })
} }
// //
const index = properties.findIndex((item) => item.id === propertyId) const index = properties.findIndex((item) => item.id === propertyId)
if (!properties[index].values.some((value) => value.id === valueId)) { if (!properties[index].values.some((value) => value.id === valueId)) {
properties[index].values.push({ id: valueId, name: valueName }) properties[index].values.push({ id: valueId, name: valueName })
} }
})
}) })
propertyList.value = properties })
} propertyList.value = properties
}, },
{ {
immediate: true immediate: true
@ -216,6 +285,11 @@ watch(
*/ */
const emit = defineEmits(['update:activeName']) const emit = defineEmits(['update:activeName'])
const validate = async () => { const validate = async () => {
// sku
if (!skuListRef.value.validateSku()) {
message.warning('商品相关价格不能低于0.01元!!')
throw new Error('商品相关价格不能低于0.01元!!')
}
// //
if (!productSpuBasicInfoRef) return if (!productSpuBasicInfoRef) return
return await unref(productSpuBasicInfoRef).validate((valid) => { return await unref(productSpuBasicInfoRef).validate((valid) => {
@ -263,12 +337,31 @@ const onChangeSpec = () => {
} }
const categoryList = ref([]) // const categoryList = ref([]) //
/**
* 选择分类时触发校验
*/
const nodeClick = () => {
if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
formData.categoryId = null
message.warning('必须选择二级及以下节点!!')
}
}
/**
* 获取分类的节点的完整结构
* @param categoryId 分类id
*/
const categoryString = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
const brandList = ref([]) // const brandList = ref([]) //
const deliveryTemplateList = ref([]) //
onMounted(async () => { onMounted(async () => {
// //
const data = await ProductCategoryApi.getCategoryList({}) const data = await ProductCategoryApi.getCategoryList({})
categoryList.value = handleTree(data, 'id', 'parentId') categoryList.value = handleTree(data, 'id', 'parentId')
// //
brandList.value = await getSimpleBrandList() brandList.value = await getSimpleBrandList()
//
deliveryTemplateList.value = await getSimpleTemplateList()
}) })
</script> </script>

View File

@ -1,28 +1,49 @@
<template> <template>
<el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px"> <el-form
v-if="!isDetail"
ref="descriptionFormRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<!--富文本编辑器组件--> <!--富文本编辑器组件-->
<el-form-item label="商品详情" prop="description"> <el-form-item label="商品详情" prop="description">
<Editor v-model:modelValue="formData.description" /> <Editor v-model:modelValue="formData.description" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<Descriptions
v-if="isDetail"
:data="formData"
:schema="allSchemas.detailSchema"
class="descriptionFormDescriptions"
>
<!-- 展示 HTML 内容 -->
<template #description="{ row }">
<div v-dompurify-html="row.description" style="width: 600px"></div>
</template>
</Descriptions>
</template> </template>
<script lang="ts" name="DescriptionForm" setup> <script lang="ts" name="DescriptionForm" setup>
import type { SpuType } from '@/api/mall/product/spu' import type { Spu } from '@/api/mall/product/spu'
import { Editor } from '@/components/Editor' import { Editor } from '@/components/Editor'
import { PropType } from 'vue' import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { descriptionSchema } from './spu.data'
const { allSchemas } = useCrudSchemas(descriptionSchema)
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<SpuType>, type: Object as PropType<Spu>,
default: () => {} default: () => {}
}, },
activeName: propTypes.string.def('') activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) //
}) })
const descriptionFormRef = ref() // Ref const descriptionFormRef = ref() // Ref
const formData = ref<SpuType>({ const formData = ref<Spu>({
description: '' // description: '' //
}) })
// //

View File

@ -1,5 +1,11 @@
<template> <template>
<el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px"> <el-form
v-if="!isDetail"
ref="otherSettingsFormRef"
:model="formData"
:rules="rules"
label-width="120px"
>
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<el-row :gutter="20"> <el-row :gutter="20">
@ -50,26 +56,53 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #recommendHot="{ row }">
{{ row.recommendHot ? '是' : '否' }}
</template>
<template #recommendBenefit="{ row }">
{{ row.recommendBenefit ? '是' : '否' }}
</template>
<template #recommendBest="{ row }">
{{ row.recommendBest ? '是' : '否' }}
</template>
<template #recommendNew="{ row }">
{{ row.recommendNew ? '是' : '否' }}
</template>
<template #recommendGood="{ row }">
{{ row.recommendGood ? '是' : '否' }}
</template>
<template #activityOrders>
<el-tag>默认</el-tag>
<el-tag class="ml-2" type="success">秒杀</el-tag>
<el-tag class="ml-2" type="info">砍价</el-tag>
<el-tag class="ml-2" type="warning">拼团</el-tag>
</template>
</Descriptions>
</template> </template>
<script lang="ts" name="OtherSettingsForm" setup> <script lang="ts" name="OtherSettingsForm" setup>
import type { SpuType } from '@/api/mall/product/spu' import type { Spu } from '@/api/mall/product/spu'
import { PropType } from 'vue' import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { otherSettingsSchema } from './spu.data'
const { allSchemas } = useCrudSchemas(otherSettingsSchema)
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<SpuType>, type: Object as PropType<Spu>,
default: () => {} default: () => {}
}, },
activeName: propTypes.string.def('') activeName: propTypes.string.def(''),
isDetail: propTypes.bool.def(false) //
}) })
const otherSettingsFormRef = ref() // Ref const otherSettingsFormRef = ref() // Ref
// //
const formData = ref<SpuType>({ const formData = ref<Spu>({
sort: 1, // sort: 1, //
giveIntegral: 1, // giveIntegral: 1, //
virtualSalesCount: 1, // virtualSalesCount: 1, //

View File

@ -90,8 +90,7 @@ const submitForm = async () => {
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
name: '', name: ''
remark: ''
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }

View File

@ -1,6 +1,7 @@
<template> <template>
<el-table <el-table
:data="isBatch ? skuList : formData.skus" v-if="!isDetail"
:data="isBatch ? skuList : formData!.skus"
border border
class="tabNumWidth" class="tabNumWidth"
max-height="500" max-height="500"
@ -11,7 +12,7 @@
<UploadImg v-model="row.picUrl" height="80px" width="100%" /> <UploadImg v-model="row.picUrl" height="80px" width="100%" />
</template> </template>
</el-table-column> </el-table-column>
<template v-if="formData.specType && !isBatch"> <template v-if="formData!.specType && !isBatch">
<!-- 根据商品属性动态添加 --> <!-- 根据商品属性动态添加 -->
<el-table-column <el-table-column
v-for="(item, index) in tableHeaders" v-for="(item, index) in tableHeaders"
@ -21,8 +22,10 @@
min-width="120" min-width="120"
> >
<template #default="{ row }"> <template #default="{ row }">
<!-- TODO puhui999展示成蓝色有点区分度哈 --> <!-- TODO puhui999展示成蓝色有点区分度哈 fix-->
{{ row.properties[index]?.valueName }} <span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }}
</span>
</template> </template>
</el-table-column> </el-table-column>
</template> </template>
@ -73,7 +76,7 @@
<el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" /> <el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
</template> </template>
</el-table-column> </el-table-column>
<template v-if="formData.subCommissionType"> <template v-if="formData!.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="168"> <el-table-column align="center" label="一级返佣(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number <el-input-number
@ -97,7 +100,7 @@
</template> </template>
</el-table-column> </el-table-column>
</template> </template>
<el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80"> <el-table-column v-if="formData?.specType" align="center" fixed="right" label="操作" width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd"> <el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
批量添加 批量添加
@ -106,27 +109,107 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-table
v-if="isDetail"
:data="formData!.skus"
border
max-height="500"
size="small"
style="width: 99%"
>
<el-table-column align="center" label="图片" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<template v-if="formData!.specType && !isBatch">
<!-- 根据商品属性动态添加 -->
<el-table-column
v-for="(item, index) in tableHeaders"
:key="index"
:label="item.label"
align="center"
min-width="80"
>
<template #default="{ row }">
<!-- TODO puhui999展示成蓝色有点区分度哈 fix-->
<span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }}
</span>
</template>
</el-table-column>
</template>
<el-table-column align="center" label="商品条码" min-width="100">
<template #default="{ row }">
{{ row.barCode }}
</template>
</el-table-column>
<el-table-column align="center" label="销售价(元)" min-width="80">
<template #default="{ row }">
{{ row.price }}
</template>
</el-table-column>
<el-table-column align="center" label="市场价(元)" min-width="80">
<template #default="{ row }">
{{ row.marketPrice }}
</template>
</el-table-column>
<el-table-column align="center" label="成本价(元)" min-width="80">
<template #default="{ row }">
{{ row.costPrice }}
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="80">
<template #default="{ row }">
{{ row.stock }}
</template>
</el-table-column>
<el-table-column align="center" label="重量(kg)" min-width="80">
<template #default="{ row }">
{{ row.weight }}
</template>
</el-table-column>
<el-table-column align="center" label="体积(m^3)" min-width="80">
<template #default="{ row }">
{{ row.volume }}
</template>
</el-table-column>
<template v-if="formData!.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="80">
<template #default="{ row }">
{{ row.subCommissionFirstPrice }}
</template>
</el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="80">
<template #default="{ row }">
{{ row.subCommissionSecondPrice }}
</template>
</el-table-column>
</template>
</el-table>
</template> </template>
<script lang="ts" name="SkuList" setup> <script lang="ts" name="SkuList" setup>
import { PropType } from 'vue' import { PropType, Ref } from 'vue'
import { copyValueToTarget } from '@/utils' import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { UploadImg } from '@/components/UploadFile' import { UploadImg } from '@/components/UploadFile'
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu' import type { Property, Sku, Spu } from '@/api/mall/product/spu'
import { createImageViewer } from '@/components/ImageViewer'
const props = defineProps({ const props = defineProps({
propFormData: { propFormData: {
type: Object as PropType<SpuType>, type: Object as PropType<Spu>,
default: () => {} default: () => {}
}, },
propertyList: { propertyList: {
type: Array, type: Array,
default: () => [] default: () => []
}, },
isBatch: propTypes.bool.def(false) // isBatch: propTypes.bool.def(false), //
isDetail: propTypes.bool.def(false) // sku
}) })
const formData = ref<SpuType>() // const formData: Ref<Spu | undefined> = ref<Spu>() //
const skuList = ref<SkuType[]>([ const skuList = ref<Sku[]>([
{ {
price: 0, // price: 0, //
marketPrice: 0, // marketPrice: 0, //
@ -140,24 +223,44 @@ const skuList = ref<SkuType[]>([
subCommissionSecondPrice: 0 // subCommissionSecondPrice: 0 //
} }
]) // ]) //
// TODO @puhui999 0.01
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
}
/** 批量添加 */ /** 批量添加 */
const batchAdd = () => { const batchAdd = () => {
formData.value.skus.forEach((item) => { formData.value!.skus!.forEach((item) => {
copyValueToTarget(item, skuList.value[0]) copyValueToTarget(item, skuList.value[0])
}) })
} }
/** 删除 sku */ /** 删除 sku */
const deleteSku = (row) => { const deleteSku = (row) => {
const index = formData.value.skus.findIndex( const index = formData.value!.skus!.findIndex(
// //
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties) (sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
) )
formData.value.skus.splice(index, 1) formData.value!.skus!.splice(index, 1)
} }
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // const tableHeaders = ref<{ prop: string; label: string }[]>([]) //
/**
* 保存时每个商品规格的表单要校验下例如说销售金额最低是 0.01 这种
*/
const validateSku = (): boolean => {
const checks = ['price', 'marketPrice', 'costPrice']
let validate = true //
for (const sku of formData.value!.skus) {
if (checks.some((check) => sku[check] < 0.01)) {
validate = false //
break
}
}
return validate
}
/** /**
* 将传进来的值赋值给 skuList * 将传进来的值赋值给 skuList
@ -185,14 +288,13 @@ const generateTableData = (propertyList: any[]) => {
valueName: v.name valueName: v.name
})) }))
) )
// TODO @puhui buildSkuListitem sku const buildSkuList = build(propertyValues)
const buildList = build(propertyValues)
// sku skus // sku skus
if (!validateData(propertyList)) { if (!validateData(propertyList)) {
// sku // sku
formData.value!.skus = [] formData.value!.skus = []
} }
for (const item of buildList) { for (const item of buildSkuList) {
const row = { const row = {
properties: Array.isArray(item) ? item : [item], // property properties: Array.isArray(item) ? item : [item], // property
price: 0, price: 0,
@ -207,13 +309,13 @@ const generateTableData = (propertyList: any[]) => {
subCommissionSecondPrice: 0 subCommissionSecondPrice: 0
} }
// sku // sku
const index = formData.value!.skus.findIndex( const index = formData.value!.skus!.findIndex(
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties) (sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
) )
if (index !== -1) { if (index !== -1) {
continue continue
} }
formData.value.skus.push(row) formData.value!.skus!.push(row)
} }
} }
@ -222,7 +324,7 @@ const generateTableData = (propertyList: any[]) => {
*/ */
const validateData = (propertyList: any[]) => { const validateData = (propertyList: any[]) => {
const skuPropertyIds = [] const skuPropertyIds = []
formData.value.skus.forEach((sku) => formData.value!.skus!.forEach((sku) =>
sku.properties sku.properties
?.map((property) => property.propertyId) ?.map((property) => property.propertyId)
.forEach((propertyId) => { .forEach((propertyId) => {
@ -263,7 +365,7 @@ watch(
() => props.propertyList, () => props.propertyList,
(propertyList) => { (propertyList) => {
// //
if (!formData.value.specType) { if (!formData.value!.specType) {
return return
} }
// 使 // 使
@ -313,5 +415,5 @@ watch(
} }
) )
// sku // sku
defineExpose({ generateTableData }) defineExpose({ generateTableData, validateSku })
</script> </script>

View File

@ -0,0 +1,105 @@
import { CrudSchema } from '@/hooks/web/useCrudSchemas'
export const basicInfoSchema = reactive<CrudSchema[]>([
{
label: '商品名称',
field: 'name'
},
{
label: '关键字',
field: 'keyword'
},
{
label: '商品简介',
field: 'introduction'
},
{
label: '商品分类',
field: 'categoryId'
},
{
label: '商品品牌',
field: 'brandId'
},
{
label: '商品封面图',
field: 'picUrl'
},
{
label: '商品轮播图',
field: 'sliderPicUrls'
},
{
label: '商品视频',
field: 'videoUrl'
},
{
label: '单位',
field: 'unit',
dictType: DICT_TYPE.PRODUCT_UNIT
},
{
label: '规格类型',
field: 'specType'
},
{
label: '分销类型',
field: 'subCommissionType'
},
{
label: '物流模版',
field: 'deliveryTemplateId'
},
{
label: '商品属性列表',
field: 'skus'
}
])
export const descriptionSchema = reactive<CrudSchema[]>([
{
label: '商品详情',
field: 'description'
}
])
export const otherSettingsSchema = reactive<CrudSchema[]>([
{
label: '商品排序',
field: 'sort'
},
{
label: '赠送积分',
field: 'giveIntegral'
},
{
label: '虚拟销量',
field: 'virtualSalesCount'
},
{
label: '是否热卖推荐',
field: 'recommendHot'
},
{
label: '是否优惠推荐',
field: 'recommendBenefit'
},
{
label: '是否精品推荐',
field: 'recommendBest'
},
{
label: '是否新品推荐',
field: 'recommendNew'
},
{
label: '是否优品推荐',
field: 'recommendGood'
},
{
label: '赠送的优惠劵',
field: 'giveCouponTemplateIds'
},
{
label: '活动显示排序',
field: 'activityOrders'
}
])

View File

@ -8,18 +8,16 @@
class="-mb-15px" class="-mb-15px"
label-width="68px" label-width="68px"
> >
<!-- TODO @puhui999品牌应该是数据下拉哈 --> <el-form-item label="商品名称" prop="name">
<el-form-item label="品牌名称" prop="name">
<el-input <el-input
v-model="queryParams.name" v-model="queryParams.name"
class="!w-240px" class="!w-240px"
clearable clearable
placeholder="请输入名称" placeholder="请输入品名称"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<!-- TODO 分类只能选择二级分类目前还没做还是先以联调通顺为主 --> <!-- TODO 分类只能选择二级分类目前还没做还是先以联调通顺为主 fixL: 已完善 -->
<!-- TODO puhui999我们要不改成支持选择一级如果选择一级后端要递归查询下子分类然后去 in -->
<el-form-item label="商品分类" prop="categoryId"> <el-form-item label="商品分类" prop="categoryId">
<el-tree-select <el-tree-select
v-model="queryParams.categoryId" v-model="queryParams.categoryId"
@ -29,6 +27,7 @@
class="w-1/1" class="w-1/1"
node-key="id" node-key="id"
placeholder="请选择商品分类" placeholder="请选择商品分类"
@change="nodeClick"
/> />
</el-form-item> </el-form-item>
<el-form-item label="创建时间" prop="createTime"> <el-form-item label="创建时间" prop="createTime">
@ -80,31 +79,60 @@
/> />
</el-tabs> </el-tabs>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<!-- TODO puhui这几个属性哈一行三个 <!-- TODO puhui这几个属性哈一行三个 fix
商品分类服装鞋包/箱包 商品分类服装鞋包/箱包
商品市场价格100.00 商品市场价格100.00
成本价0.00 成本价0.00
收藏5 收藏5
虚拟销量999 --> 虚拟销量999 -->
<el-table-column type="expand" width="30"> <el-table-column type="expand" width="30">
<template #default="{ row }"> <template #default="{ row }">
<el-form class="demo-table-expand" inline label-position="left"> <el-form class="demo-table-expand" label-position="left">
<el-form-item label="市场价:"> <el-row>
<span>{{ formatToFraction(row.marketPrice) }}</span> <el-col :span="24">
</el-form-item> <el-row>
<el-form-item label="成本价:"> <el-col :span="8">
<span>{{ formatToFraction(row.costPrice) }}</span> <el-form-item label="商品分类:">
</el-form-item> <span>{{ categoryString(row.categoryId) }}</span>
<el-form-item label="虚拟销量:"> </el-form-item>
<span>{{ row.virtualSalesCount }}</span> </el-col>
</el-form-item> <el-col :span="8">
<el-form-item label="市场价:">
<span>{{ formatToFraction(row.marketPrice) }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="成本价:">
<span>{{ formatToFraction(row.costPrice) }}</span>
</el-form-item>
</el-col>
</el-row>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-row>
<el-col :span="8">
<el-form-item label="收藏:">
<!-- TODO 没有这个属性暂时写死 5 -->
<span>5</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="虚拟销量:">
<span>{{ row.virtualSalesCount }}</span>
</el-form-item>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form> </el-form>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column key="id" align="center" label="商品编号" prop="id" /> <el-table-column key="id" align="center" label="商品编号" prop="id" />
<el-table-column label="商品图" min-width="80"> <el-table-column label="商品图" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-image :src="row.picUrl" @click="imagePreview(row.picUrl)" class="w-30px h-30px" /> <el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" /> <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
@ -143,8 +171,13 @@
</el-table-column> </el-table-column>
<el-table-column align="center" fixed="right" label="操作" min-width="200"> <el-table-column align="center" fixed="right" label="操作" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
<!-- TODO @puhui999详情可以后面点做哈 --> <!-- TODO @puhui999详情可以后面点做哈 fix-->
<el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openDetail"> <el-button
v-hasPermi="['product:spu:update']"
link
type="primary"
@click="openDetail(row.id)"
>
详情 详情
</el-button> </el-button>
<template v-if="queryParams.tabType === 4"> <template v-if="queryParams.tabType === 4">
@ -202,7 +235,7 @@ import { TabsPaneContext } from 'element-plus'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { createImageViewer } from '@/components/ImageViewer' import { createImageViewer } from '@/components/ImageViewer'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { defaultProps, handleTree } from '@/utils/tree' import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
import { ProductSpuStatusEnum } from '@/utils/constants' import { ProductSpuStatusEnum } from '@/utils/constants'
import { formatToFraction } from '@/utils' import { formatToFraction } from '@/utils'
import download from '@/utils/download' import download from '@/utils/download'
@ -256,12 +289,14 @@ const getTabsCount = async () => {
const queryParams = ref({ const queryParams = ref({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
tabType: 0 tabType: 0,
name: '',
categoryId: null
}) // }) //
const queryFormRef = ref() // Ref const queryFormRef = ref() // Ref
const handleTabClick = (tab: TabsPaneContext) => { const handleTabClick = (tab: TabsPaneContext) => {
queryParams.value.tabType = tab.paneName queryParams.value.tabType = tab.paneName as number
getList() getList()
} }
@ -372,8 +407,8 @@ const openForm = (id?: number) => {
/** /**
* 查看商品详情 * 查看商品详情
*/ */
const openDetail = () => { const openDetail = (id?: number) => {
message.alert('查看详情未完善!!!') push('/product/productSpuDetail/' + id)
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
@ -391,7 +426,7 @@ const handleExport = async () => {
} }
} }
// TODO @puhui999fix: //
watch( watch(
() => currentRoute.value, () => currentRoute.value,
() => { () => {
@ -400,6 +435,22 @@ watch(
) )
const categoryList = ref() // const categoryList = ref() //
/**
* 获取分类的节点的完整结构
* @param categoryId 分类id
*/
const categoryString = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
/**
* 校验所选是否为二级及以下节点
*/
const nodeClick = () => {
if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) {
queryParams.value.categoryId = null
message.warning('必须选择二级及以下节点!!')
}
}
/** 初始化 **/ /** 初始化 **/
onMounted(async () => { onMounted(async () => {
await getTabsCount() await getTabsCount()

View File

@ -3,7 +3,7 @@
<Descriptions :data="detailData" :schema="allSchemas.detailSchema"> <Descriptions :data="detailData" :schema="allSchemas.detailSchema">
<!-- 展示 HTML 内容 --> <!-- 展示 HTML 内容 -->
<template #templateContent="{ row }"> <template #templateContent="{ row }">
<div v-html="row.templateContent"></div> <div v-dompurify-html="row.templateContent"></div>
</template> </template>
</Descriptions> </Descriptions>
</Dialog> </Dialog>