commit
541b23c8c1
4
.env.dev
4
.env.dev
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 })
|
||||||
|
@ -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"
|
||||||
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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'
|
||||||
|
@ -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>
|
||||||
|
@ -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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
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()
|
||||||
|
@ -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 @puhui999:if 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>
|
||||||
|
@ -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: '' // 商品详情
|
||||||
})
|
})
|
||||||
// 表单规则
|
// 表单规则
|
||||||
|
@ -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, // 虚拟销量
|
||||||
|
@ -90,8 +90,7 @@ const submitForm = async () => {
|
|||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
name: '',
|
name: ''
|
||||||
remark: ''
|
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
@ -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:是不是 buildSkuList,这样容易理解一点哈。item 改成 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>
|
||||||
|
105
src/views/mall/product/spu/components/spu.data.ts
Normal file
105
src/views/mall/product/spu/components/spu.data.ts
Normal 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'
|
||||||
|
}
|
||||||
|
])
|
@ -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 @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
|
// 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。
|
||||||
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()
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user