This commit is contained in:
qsh
2024-06-05 17:08:27 +08:00
parent a557255b4a
commit 94943df4f9
30 changed files with 1069 additions and 846 deletions

View File

@@ -57,5 +57,5 @@ export const getBrandParam = (params: PageParam) => {
// 获得商品品牌精简信息列表 // 获得商品品牌精简信息列表
export const getSimpleBrandList = () => { export const getSimpleBrandList = () => {
return request.get({ url: '/product/brand/list-all-simple' }) return request.get({ url: '/admin-api/crm/erp-product-brand/simple-list' })
} }

View File

@@ -56,5 +56,10 @@ export const getCategory = (id: number) => {
// 获得商品分类列表 // 获得商品分类列表
export const getCategoryList = (params: any) => { export const getCategoryList = (params: any) => {
return request.get({ url: '/admin-api/crm/erp-product-category/page', params }) return request.get({ url: '/admin-api/crm/erp-product-category/list', params })
}
// 获得商品分类列表
export const getCategorySimpleList = (params: any) => {
return request.get({ url: '/admin-api/crm/erp-product-category/simple-list', params })
} }

View File

@@ -23,3 +23,7 @@ export const updateProduct = async (params) => {
export const deleteProduct = async (id) => { export const deleteProduct = async (id) => {
return await request.delete({ url: '/admin-api/crm/erp-product/delete?id=' + id }) return await request.delete({ url: '/admin-api/crm/erp-product/delete?id=' + id })
} }
export const getSimpleProductList = async () => {
return await request.get({ url: '/admin-api/crm/erp-product/simple-list' })
}

View File

@@ -42,7 +42,7 @@ export interface PropertyValueDetailVO {
// 创建属性项 // 创建属性项
export const createProperty = (data: PropertyVO) => { export const createProperty = (data: PropertyVO) => {
return request.post({ url: '/product/property/create', data }) return request.post({ url: '/admin-api/crm/erp-product-property/create', data })
} }
// 更新属性项 // 更新属性项
@@ -89,7 +89,7 @@ export const getPropertyValue = (id: number): Promise<PropertyValueVO> => {
// 创建属性值 // 创建属性值
export const createPropertyValue = (data: PropertyValueVO) => { export const createPropertyValue = (data: PropertyValueVO) => {
return request.post({ url: '/product/property/value/create', data }) return request.post({ url: '/admin-api/crm/erp-product-property-value/create', data })
} }
// 更新属性值 // 更新属性值

View File

@@ -1,92 +0,0 @@
import request from '@/config/axios'
export interface Property {
propertyId?: number // 属性编号
propertyName?: string // 属性名称
valueId?: number // 属性值编号
valueName?: string // 属性值名称
}
// TODO puhui999是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
export interface SkuType {
id?: number // 商品 SKU 编号
spuId?: number // SPU 编号
properties?: Property[] // 属性数组
price?: number // 商品价格
marketPrice?: number // 市场价
costPrice?: number // 成本价
barCode?: string // 商品条码
picUrl?: string // 图片地址
stock?: number // 库存
weight?: number // 商品重量单位kg 千克
volume?: number // 商品体积单位m^3 平米
subCommissionFirstPrice?: number // 一级分销的佣金
subCommissionSecondPrice?: number // 二级分销的佣金
salesCount?: number // 商品销量
}
// TODO puhui999是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
export interface SpuType {
id?: number
name?: string // 商品名称
categoryId?: number | null // 商品分类
keyword?: string // 关键字
unit?: number | null // 单位
picUrl?: string // 商品封面图
sliderPicUrls?: string[] // 商品轮播图
introduction?: string // 商品简介
deliveryTemplateId?: number | null // 运费模版
brandId?: number | null // 商品品牌编号
specType?: boolean // 商品规格
subCommissionType?: boolean // 分销类型
skus: SkuType[] // sku数组
description?: string // 商品详情
sort?: string // 商品排序
giveIntegral?: number // 赠送积分
virtualSalesCount?: number // 虚拟销量
recommendHot?: boolean // 是否热卖
recommendBenefit?: boolean // 是否优惠
recommendBest?: boolean // 是否精品
recommendNew?: boolean // 是否新品
recommendGood?: boolean // 是否优品
}
// 获得 Spu 列表
export const getSpuPage = (params: PageParam) => {
return request.get({ url: '/product/spu/page', params })
}
// 获得 Spu 列表 tabsCount
export const getTabsCount = () => {
return request.get({ url: '/product/spu/get-count' })
}
// 创建商品 Spu
export const createSpu = (data: SpuType) => {
return request.post({ url: '/product/spu/create', data })
}
// 更新商品 Spu
export const updateSpu = (data: SpuType) => {
return request.put({ url: '/product/spu/update', data })
}
// 更新商品 Spu status
export const updateStatus = (data: { id: number; status: number }) => {
return request.put({ url: '/product/spu/update-status', data })
}
// 获得商品 Spu
export const getSpu = (id: number) => {
return request.get({ url: `/product/spu/get-detail?id=${id}` })
}
// 删除商品 Spu
export const deleteSpu = (id: number) => {
return request.delete({ url: `/product/spu/delete?id=${id}` })
}
// 导出商品 Spu Excel
export const exportSpu = async (params) => {
return await request.download({ url: '/product/spu/export', params })
}

View File

@@ -0,0 +1,25 @@
import request from '@/config/axios'
// 查询列表
export const getPurchasePage = async (params) => {
return await request.get({ url: '/admin-api/crm/erp-purchase/page', params })
}
// 新增
export const createPurchase = async (data) => {
return await request.post({ url: '/admin-api/crm/erp-purchase/create', data: data })
}
// 修改
export const updatePurchase = async (params) => {
return await request.put({ url: '/admin-api/crm/erp-purchase/update', data: params })
}
// 删除
export const deletePurchase = async (id) => {
return await request.delete({ url: '/admin-api/crm/erp-purchase/delete?id=' + id })
}
// 审核
export const auditPurchase = async (params) => {
return await request.get({ url: '/admin-api/crm/erp-purchase/audit', params })
}

View File

@@ -15,7 +15,7 @@ export type DictDataVO = {
// 查询字典数据(精简)列表 // 查询字典数据(精简)列表
export const listSimpleDictData = () => { export const listSimpleDictData = () => {
return request.get({ url: '/admin-api/system/dict-data/simple-list' }) return request.get({ url: '/admin-api/crm/dict-data/simple-list' })
} }
// 查询字典数据列表 // 查询字典数据列表

View File

@@ -0,0 +1,26 @@
import request from '@/config/axios'
// 查询知识库列表
export const getLibraryPage = (params) => {
return request.get({ url: '/admin-api/crm/knowledge-lib/page', params })
}
// 查询知识库详情
export const getLibrary = (id) => {
return request.get({ url: '/admin-api/crm/knowledge-lib/get?id=' + id })
}
// 新增知识库
export const createLibrary = (data) => {
return request.post({ url: '/admin-api/crm/knowledge-lib/create', data })
}
// 修改知识库
export const updateLibrary = (data) => {
return request.put({ url: '/admin-api/crm/knowledge-lib/update', data })
}
// 删除知识库
export const deleteLibrary = (id) => {
return request.delete({ url: '/admin-api/crm/knowledge-lib/delete?id=' + id })
}

View File

@@ -0,0 +1,26 @@
import request from '@/config/axios'
// 查询资源列表
export const getResourcePage = (params) => {
return request.get({ url: '/admin-api/crm/knowledge-lib-info/page', params })
}
// 查询资源详情
export const getResource = (id) => {
return request.get({ url: '/admin-api/crm/knowledge-lib-info/get?id=' + id })
}
// 新增资源
export const createResource = (data) => {
return request.post({ url: '/admin-api/crm/knowledge-lib-info/create', data })
}
// 修改资源
export const updateResource = (data) => {
return request.put({ url: '/admin-api/crm/knowledge-lib-info/update', data })
}
// 删除资源
export const deleteResource = (id) => {
return request.delete({ url: '/admin-api/crm/knowledge-lib-info/delete?id=' + id })
}

View File

@@ -39,7 +39,7 @@
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
v-model:limit="pageSize" v-model:limit="pageSize"
v-model:page="currentPage" v-model:page="pageNo"
:total="tableObject.total" :total="tableObject.total"
@pagination="getList" @pagination="getList"
/> />
@@ -61,7 +61,7 @@ const emit = defineEmits(['update:tableObject', 'getList', 'getCheckedColumns'])
const route = useRoute() const route = useRoute()
const { id: userId } = useUserStore().user //取用户ID const { id: userId } = useUserStore().user //取用户ID
const currentPage = ref(props.tableObject?.currentPage || 1) const pageNo = ref(props.tableObject?.pageNo || 1)
const pageSize = ref(props.tableObject?.pageSize || 20) const pageSize = ref(props.tableObject?.pageSize || 20)
@@ -74,7 +74,7 @@ const checkedColumns = ref([])
// 调用获取数据的接口,分页时需要使用 // 调用获取数据的接口,分页时需要使用
function getList({ page, limit }) { function getList({ page, limit }) {
emit('update:tableObject', { ...props.tableObject, currentPage: page, pageSize: limit }) emit('update:tableObject', { ...props.tableObject, pageNo: page, pageSize: limit })
nextTick(() => { nextTick(() => {
emit('getList') emit('getList')
}) })

View File

@@ -34,20 +34,14 @@
</div> </div>
</template> </template>
<script setup lang="ts" name="UploadFile"> <script setup lang="ts" name="UploadFile">
import { PropType } from 'vue'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId, getAppId } from '@/utils/auth'
import type { UploadInstance, UploadUserFile, UploadProps, UploadRawFile } from 'element-plus' import type { UploadInstance, UploadUserFile, UploadProps, UploadRawFile } from 'element-plus'
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
type: Array as PropType<UploadUserFile[]>,
required: true
},
title: propTypes.string.def('文件上传'), title: propTypes.string.def('文件上传'),
updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL), updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg'] fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
@@ -66,8 +60,10 @@ const fileList = ref<UploadUserFile[]>(props.modelValue)
const uploadNumber = ref<number>(0) const uploadNumber = ref<number>(0)
const uploadHeaders = ref({ const uploadHeaders = ref({
Authorization: 'Bearer ' + getAccessToken(), Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId() 'tenant-id': getTenantId(),
'instance-id': getAppId()
}) })
// 文件上传之前判断 // 文件上传之前判断
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => { const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) { if (fileList.value.length >= props.limit) {

View File

@@ -55,7 +55,7 @@ import type { UploadProps } from 'element-plus'
import { generateUUID } from '@/utils' import { generateUUID } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId, getAppId } from '@/utils/auth'
type FileTypes = type FileTypes =
| 'image/apng' | 'image/apng'
@@ -96,7 +96,8 @@ const deleteImg = () => {
const uploadHeaders = ref({ const uploadHeaders = ref({
Authorization: 'Bearer ' + getAccessToken(), Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId() 'tenant-id': getTenantId(),
'instance-id': getAppId()
}) })
const editImg = () => { const editImg = () => {

View File

@@ -18,7 +18,7 @@ export function formatDate(date: Date, format?: string): string {
} }
// 日期存在,则进行格式化 // 日期存在,则进行格式化
if (format === undefined) { if (format === undefined) {
format = 'YYYY-MM-DD HH:mm:ss' format = 'YYYY-MM-DD'
} }
return dayjs(date).format(format) return dayjs(date).format(format)
} }

View File

@@ -1,11 +1,11 @@
<template> <template>
<Dialog :title="title" v-model="dialogVisible" width="800px"> <Dialog :title="title" v-model="dialogVisible" width="800px">
<el-form :model="form" ref="addForm" :rules="rules" label-width="100px"> <el-form v-loading="formLoading" :model="form" ref="formRef" :rules="rules" label-width="100px">
<el-form-item label="知识库名称" prop="name"> <el-form-item label="知识库名称" prop="libName">
<el-input v-model="form.name" placeholder="请输入" /> <el-input v-model="form.libName" placeholder="请输入" />
</el-form-item> </el-form-item>
<el-form-item label="资源类型" prop="type"> <el-form-item label="资源类型" prop="libType">
<el-radio-group v-model="form.type"> <el-radio-group v-model="form.libType">
<el-radio :label="1"> 文件 </el-radio> <el-radio :label="1"> 文件 </el-radio>
<el-radio :label="2"> 纯图片 </el-radio> <el-radio :label="2"> 纯图片 </el-radio>
<el-radio :label="3"> 自主编辑 </el-radio> <el-radio :label="3"> 自主编辑 </el-radio>
@@ -18,49 +18,71 @@
<template #footer> <template #footer>
<span> <span>
<el-button @click="dialogVisible = false"> </el-button> <el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button> <el-button type="primary" :disabled="formLoading" @click="handleSave"> </el-button>
</span> </span>
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import * as LibraryApi from '@/api/system/library'
const emit = defineEmits(['success'])
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const form = ref() const form = ref()
const rules = ref({ const rules = ref({
name: { required: true, message: '名称不可为空', trigger: 'blur' } libName: { required: true, message: '名称不可为空', trigger: 'blur' }
}) })
const title = ref('') const title = ref('')
const addForm = ref() const formRef = ref()
const formLoading = ref(false)
const formType = ref('create')
const emit = defineEmits(['update']) const open = (type, val) => {
const open = (val) => {
dialogVisible.value = true dialogVisible.value = true
formType.value = type
if (val) { if (val) {
title.value = '修改知识库' title.value = '修改知识库'
form.value = { ...val } form.value = { ...val }
} else { } else {
title.value = '新增知识库' title.value = '新增知识库'
form.value = { form.value = {
name: '', libName: '',
type: 1, libType: 1,
remark: undefined remark: undefined,
isDefault: false
} }
} }
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
function handleSave() { async function handleSave() {
addForm.value.validate((valid) => { // 校验表单
if (valid) { if (!formRef.value) return
emit('update', form.value) const valid = await formRef.value.validate()
dialogVisible.value = false if (!valid) return
// 提交请求
formLoading.value = true
try {
if (formType.value === 'create') {
await LibraryApi.createLibrary(form.value)
message.success(t('common.createSuccess'))
} else {
await LibraryApi.updateLibrary(form.value)
message.success(t('common.updateSuccess'))
} }
}) dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
} }
</script> </script>

View File

@@ -1,6 +1,6 @@
<template> <template>
<Dialog :title="title" v-model="show" width="800px"> <Dialog :title="title" v-model="show" width="800px">
<el-form :model="form" ref="resourceForm" :rules="rules" label-width="60px"> <el-form v-loading="formLoading" :model="form" ref="formRef" :rules="rules" label-width="60px">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="标题" prop="title"> <el-form-item label="标题" prop="title">
@@ -8,16 +8,19 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="标签" prop="tipList"> <el-form-item label="标签" prop="tags">
<el-select <el-select
v-model="form.tipList" v-model="form.tags"
multiple multiple
filterable filterable
collapse-tags
collapse-tags-tooltip
allow-create allow-create
default-first-option default-first-option
:reserve-keyword="false" :reserve-keyword="false"
placeholder="请选择标签或输入" placeholder="请选择标签或输入"
clearable clearable
@change="tipChange"
style="width: 100%" style="width: 100%"
> >
<el-option <el-option
@@ -33,15 +36,10 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24" :offset="0"> <el-col :span="24" :offset="0">
<el-form-item label="内容" prop="content"> <el-form-item label="内容" prop="content">
<UploadFile <UploadFile v-if="libType == 1" v-model="form.files" :limit="1" :isShowTip="false" />
v-if="form.type == 1" <UploadImg
v-model="form.sliderPicUrls" v-else-if="libType == 2"
:isShowTip="false" v-model:modelValue="form.files"
:fileType="[]"
/>
<UploadImgs
v-else-if="form.type == 2"
v-model:modelValue="form.sliderPicUrls"
width="100px" width="100px"
height="100px" height="100px"
/> />
@@ -60,41 +58,107 @@
<template #footer> <template #footer>
<span> <span>
<el-button @click="show = false"> </el-button> <el-button @click="show = false"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button> <el-button type="primary" :disabled="formLoading" @click="handleSave"> </el-button>
</span> </span>
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup name="DialogResource">
import * as ResourceApi from '@/api/system/library/resource'
import { getDictOptions } from '@/utils/dict'
import * as dictApi from '@/api/system/dict/dict.data'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const show = ref(false) const show = ref(false)
const title = ref('') const title = ref('')
const formType = ref('create')
const libType = ref(1)
const form = ref({}) const form = ref({})
const rules = ref({}) const formLoading = ref(false)
const rules = ref({
title: { required: true, message: '标题不可为空', trigger: 'blur' }
})
const tipOptions = ref([{ label: '绿色', value: '绿色' }]) const tipOptions = ref([])
function open(type, val) { function getOptions() {
tipOptions.value = getDictOptions('knowledge_tags')
}
async function open(type, info, id) {
show.value = true show.value = true
if (val) { title.value = type == 'update' ? '修改资源' : '新增资源'
title.value = '修改资源' formType.value = type
form.value = { ...val, type } libType.value = info.libType
} else { resetForm(info.libId)
title.value = '新增资源' getOptions()
form.value = { if (id) {
type, formLoading.value = true
title: '', try {
tipList: [], form.value = await ResourceApi.getResource(id)
sliderPicUrls: [], } finally {
content: '', formLoading.value = false
remark: null
} }
} }
} }
function resetForm(libId) {
form.value = {
libId,
title: '',
tags: [],
files: '',
content: '',
remark: null,
isDefault: false
}
}
async function tipChange(val) {
const valStr = val.at(-1)
if (!tipOptions.value.some((it) => it.value == valStr)) {
await dictApi.createDictData({
label: valStr,
value: valStr,
sort: 1,
status: 0,
dictType: 'knowledge_tags'
})
tipOptions.value.push({ label: valStr, value: valStr })
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
function handleSave() { const formRef = ref()
console.log('保存成功')
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
async function handleSave() {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
if (formType.value === 'create') {
await ResourceApi.createResource(form.value)
message.success(t('common.createSuccess'))
} else {
await ResourceApi.updateResource(form.value)
message.success(t('common.updateSuccess'))
}
show.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
} }
</script> </script>

View File

@@ -1,7 +1,6 @@
<template> <template>
<teleport v-if="show" to="#app"> <teleport v-if="show" to="#app">
<div class="container"> <div class="container">
<!-- <Icon class="ep:circle-close close-icon" /> -->
<el-icon class="close-icon" @click="show = false"><CircleClose /></el-icon> <el-icon class="close-icon" @click="show = false"><CircleClose /></el-icon>
<el-drawer <el-drawer
v-model="showDrawer" v-model="showDrawer"
@@ -16,22 +15,30 @@
<template #header> <template #header>
<span style="color: #fff">资源详情</span> <span style="color: #fff">资源详情</span>
</template> </template>
<el-form style="flex: 1" :model="info" label-width="80px" label-position="left"> <div>
<el-form-item label="标题:"> <div class="flex mb-18px">
{{ info.title }} <div class="w-80px text-light-50">标题</div>
</el-form-item> <div class="flex-1 ml-10px text-light-50">{{ info.title }}</div>
<el-form-item label="文件名称:"> </div>
{{ info.fileName }} <div class="flex mb-18px">
</el-form-item> <div class="w-80px text-light-50">文件名称</div>
<el-form-item label="标签:"> <div class="flex-1 ml-10px text-light-50 w-80px" style="word-wrap: break-word">{{
<el-tag class="mr-5px mb-5px" v-for="(item, index) in info.tipList" :key="index">{{ info.files
item }}</div>
}}</el-tag> </div>
</el-form-item> <div class="flex mb-18px">
<el-form-item label="备注:"> <div class="w-80px text-light-50">标签</div>
{{ info.remark }} <div class="flex-1 ml-10px w-80px">
</el-form-item> <el-tag class="mr-5px" v-for="(item, index) in info.tags" :key="index">
</el-form> {{ getDictLabel('knowledge_tags', item) }}
</el-tag>
</div>
</div>
<div class="flex mb-18px">
<div class="w-80px text-light-50">备注</div>
<div class="flex-1 ml-10px text-light-50">{{ info.remark }}</div>
</div>
</div>
<template #footer> <template #footer>
<div class="flex justify-between"> <div class="flex justify-between">
<el-button plain :disabled="imgIndex <= 0" @click="imgIndex--">上一张</el-button> <el-button plain :disabled="imgIndex <= 0" @click="imgIndex--">上一张</el-button>
@@ -42,13 +49,14 @@
</template> </template>
</el-drawer> </el-drawer>
<img :src="info.fileUrl" :alt="info.fileName" srcset="" class="width-fit img" /> <img :src="info.files" :alt="info.files" srcset="" class="width-fit img" />
<div class="img-idx">{{ imgIndex + 1 }} / {{ imgList.length }}</div> <div class="img-idx">{{ imgIndex + 1 }} / {{ imgList.length }}</div>
</div> </div>
</teleport> </teleport>
</template> </template>
<script setup> <script setup>
import { getDictLabel } from '@/utils/dict'
import { ElIcon } from 'element-plus' import { ElIcon } from 'element-plus'
import { CircleClose } from '@element-plus/icons-vue' import { CircleClose } from '@element-plus/icons-vue'

View File

@@ -3,7 +3,9 @@
<el-card shadow="always" :body-style="{ padding: '10px' }"> <el-card shadow="always" :body-style="{ padding: '10px' }">
<div class="flex justify-between items-center" style="width: 400px"> <div class="flex justify-between items-center" style="width: 400px">
<div class="text-16px font-bold">知识库名称</div> <div class="text-16px font-bold">知识库名称</div>
<el-button type="primary" style="padding: 0px" text @click="handleAdd">新增</el-button> <el-button type="primary" style="padding: 0px" text @click="openForm('create', null)"
>新增</el-button
>
</div> </div>
<div class="border-top-1px mt-10px pt-10px"> <div class="border-top-1px mt-10px pt-10px">
<div <div
@@ -11,24 +13,27 @@
v-for="(item, index) in libraryList" v-for="(item, index) in libraryList"
:key="index" :key="index"
:class="{ actived: libraryIndex == index }" :class="{ actived: libraryIndex == index }"
@click="libraryIndex = index" @click="handleClickLib(index)"
> >
<div class="flex-1 text-14px">{{ item.name }}</div> <div class="flex-1 text-14px">{{ item.libName }}</div>
<div class="ml-10px"> <div class="ml-10px">
<el-button type="primary" style="padding: 0px" text @click="handleUpdate(item)" <el-button type="primary" style="padding: 0px" text @click="openForm('update', item)">
>修改</el-button 修改
> </el-button>
<el-button <el-button
type="primary" type="primary"
class="ml-10px" class="ml-10px"
style="padding: 0px" style="padding: 0px"
text text
@click="handleRemove(index)" @click="handleRemove(item.libId)"
>删除</el-button
> >
删除
</el-button>
</div> </div>
</div> </div>
<Pagination <Pagination
small
layout="total, prev, pager, next, jumper"
v-model:limit="pageSize" v-model:limit="pageSize"
v-model:page="currentPage" v-model:page="currentPage"
:total="total" :total="total"
@@ -36,44 +41,55 @@
/> />
</div> </div>
</el-card> </el-card>
<el-card class="ml-20px" style="flex: 1" shadow="always" :body-style="{ padding: '10px' }"> <el-card
v-if="libraryList.length"
class="ml-20px"
style="flex: 1"
shadow="always"
:body-style="{ padding: '10px' }"
>
<div class="flex justify-between items-center border-bottom-1px pb-10px mb-20px"> <div class="flex justify-between items-center border-bottom-1px pb-10px mb-20px">
<div>{{ libraryList[libraryIndex].name }}资源详情</div> <div>{{ libraryList[libraryIndex].libName }}资源详情</div>
<el-button type="primary" @click="handleAddResource">新增资源</el-button> <el-button type="primary" @click="openResource('create', null)">新增资源</el-button>
</div> </div>
<div v-if="libraryList[libraryIndex].type == 1"> <div v-if="libraryList[libraryIndex].libType == 1">
<el-table :data="tableList" border> <el-table :data="tableList" border v-loading="loading">
<el-table-column type="index" width="50" /> <el-table-column type="index" width="50" />
<el-table-column prop="title" label="标题" width="200" /> <el-table-column prop="title" label="标题" width="200" />
<el-table-column label="标签" width="200px"> <el-table-column label="标签" width="200px">
<template #default="{ row }"> <template #default="{ row }">
<el-tag v-for="(item, index) in row.tipList" :key="index" class="mr-5px">{{ <el-tag v-for="(item, index) in row.tags" :key="index" class="mr-5px">
item {{ getDictLabel('knowledge_tags', item) }}
}}</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="fileName" label="附件"> <el-table-column prop="files" label="附件">
<template #default="{ row }"> <template #default="{ row }">
<el-link type="primary" underline :href="row.fileUrl" target="_blank">{{ <el-link type="primary" underline :href="row.fileUrl" target="_blank">
row.fileName {{ row.files }}
}}</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="120px"> <el-table-column label="操作" width="120px">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<el-button type="primary" style="padding: 0" text @click="updateResource(row)" <el-button
>修改</el-button type="primary"
> style="padding: 0"
<el-button type="danger" style="padding: 0" text @click="removeResource($index)" text
>删除</el-button @click="openResource('update', row.id)"
> >
修改
</el-button>
<el-button type="danger" style="padding: 0" text @click="removeResource($index)">
删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
<div v-else-if="libraryList[libraryIndex].type == 2" class="flex"> <div v-else-if="libraryList[libraryIndex].libType == 2" class="flex">
<div v-for="(item, index) in tableList" :key="index" class="mr-10px"> <div v-for="(item, index) in tableList" :key="index" class="mr-10px">
<el-image :src="item.fileUrl" @click="imagePreview(index)" class="w-150px h-150px" /> <el-image :src="item.files" @click="imagePreview(index)" class="w-150px h-150px" />
<div class="mt-5px text-center">{{ item.title }}</div> <div class="mt-5px text-center">{{ item.title }}</div>
</div> </div>
</div> </div>
@@ -83,19 +99,24 @@
<el-table-column prop="title" label="标题" /> <el-table-column prop="title" label="标题" />
<el-table-column label="标签"> <el-table-column label="标签">
<template #default="{ row }"> <template #default="{ row }">
<el-tag v-for="(item, index) in row.tipList" :key="index" class="mr-5px">{{ <el-tag v-for="(item, index) in row.tags" :key="index" class="mr-5px">
item {{ getDictLabel('knowledge_tags', item) }}
}}</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="120px"> <el-table-column label="操作" width="120px">
<template #default="{ row, $index }"> <template #default="{ row, $index }">
<el-button type="primary" style="padding: 0" text @click="updateResource(row)" <el-button
>修改</el-button type="primary"
> style="padding: 0"
<el-button type="danger" style="padding: 0" text @click="removeResource($index)" text
>删除</el-button @click="openResource('update', row.infoId)"
> >
修改
</el-button>
<el-button type="danger" style="padding: 0" text @click="removeResource($index)">
删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -108,27 +129,31 @@
/> />
</el-card> </el-card>
</div> </div>
<DialogLibrary ref="library" @update="afterSaveLibrary" /> <DialogLibrary ref="library" @success="getList" />
<DialogResource ref="resourceDialog" /> <DialogResource ref="resourceDialog" @success="getResourceList" />
<ImagePreview ref="imgPreview" /> <ImagePreview ref="imgPreview" />
</template> </template>
<script setup> <script setup name="Library">
import * as LibraryApi from '@/api/system/library/index'
import * as ResourceApi from '@/api/system/library/resource'
import DialogLibrary from './Comp/DialogLibrary.vue' import DialogLibrary from './Comp/DialogLibrary.vue'
import DialogResource from './Comp/DialogResource.vue' import DialogResource from './Comp/DialogResource.vue'
import ImagePreview from './Comp/ImagePreview.vue' import ImagePreview from './Comp/ImagePreview.vue'
import { getDictLabel } from '@/utils/dict'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const libraryIndex = ref(0) const libraryIndex = ref(0)
const libraryList = ref([ const libraryList = ref([])
{ name: '成交案例', id: 1, type: 2 },
{ name: '公司资质材料', id: 2, type: 1 },
{ name: '会议纪要', id: 3, type: 3 }
])
const pageSize = ref(20) const pageSize = ref(20)
const currentPage = ref(1) const currentPage = ref(1)
const total = ref(0) const total = ref(0)
const loading = ref(false)
const library = ref() const library = ref()
const resourceDialog = ref() const resourceDialog = ref()
const imgPreview = ref() const imgPreview = ref()
@@ -137,61 +162,70 @@ const resourcePageSize = ref(20)
const resourcePageNum = ref(1) const resourcePageNum = ref(1)
const resourceTotal = ref(0) const resourceTotal = ref(0)
const tableList = ref([ const tableList = ref([])
{
fileUrl:
'https://img0.baidu.com/it/u=1033018635,7901815&fm=253&fmt=auto&app=138&f=JPEG?w=750&h=500',
fileName: '测试图片1'
},
{
tipList: ['优质材料', '无污染'],
fileUrl:
'https://img0.baidu.com/it/u=1610680713,975251961&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750',
fileName: '测试图片2'
}
])
function handleAdd() { function openForm(type, item) {
// 新增知识库 library.value.open(type, item)
library.value.open(null)
} }
function handleUpdate(item) { async function handleRemove(id) {
library.value.open(item) try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await LibraryApi.deleteLibrary(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
} }
function handleRemove(index) { async function getList() {
libraryList.value.splice(index, 1) const data = await LibraryApi.getLibraryPage({
pageNo: currentPage.value,
pageSize: pageSize.value
})
libraryList.value = data.list
total.value = data.total
handleClickLib(0)
} }
function afterSaveLibrary(val) { function handleClickLib(index) {
libraryList.value.push(val) libraryIndex.value = index
getResourceList()
} }
function getList() { function openResource(type, id) {
libraryList.value = [] resourceDialog.value.open(type, libraryList.value[libraryIndex.value], id)
}
function handleAddResource() {
resourceDialog.value.open(libraryList.value[libraryIndex.value].type, null)
}
function updateResource(row) {
resourceDialog.value.open(libraryList.value[libraryIndex.value].type, row)
} }
function removeResource(index) { function removeResource(index) {
tableList.value.splice(index, 1) tableList.value.splice(index, 1)
} }
function getResourceList() { async function getResourceList() {
tableList.value = [] loading.value = true
try {
const data = await ResourceApi.getResourcePage({
libId: libraryList.value[libraryIndex.value].libId,
pageNo: resourcePageNum.value,
pageSize: resourcePageSize.value
})
tableList.value = data.list
resourceTotal.value = data.total
} finally {
loading.value = false
}
} }
/** 商品图预览 */ /** 商品图预览 */
function imagePreview(index) { function imagePreview(index) {
imgPreview.value.open(index, tableList.value) imgPreview.value.open(index, tableList.value)
} }
onMounted(() => {
getList()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -32,8 +32,10 @@
/> />
<el-table-column label="操作"> <el-table-column label="操作">
<template #default="scope"> <template #default="scope">
<el-button type="primary" text @click="openForm('update', scope.row.id)">修改</el-button> <el-button type="primary" text @click="openForm('update', scope.row.brandId)"
<el-button type="danger" text @click="handleDelete(scope.row.id)">删除</el-button> >修改</el-button
>
<el-button type="danger" text @click="handleDelete(scope.row.brandId)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -54,6 +56,9 @@ import { dateFormatter } from '@/utils/formatTime'
import * as ProductBrandApi from '@/api/mall/product/brand' import * as ProductBrandApi from '@/api/mall/product/brand'
import DialogBrand from './DialogBrand.vue' import DialogBrand from './DialogBrand.vue'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const searchForm = ref({ const searchForm = ref({
name: undefined, name: undefined,
pageNo: 1, pageNo: 1,
@@ -102,8 +107,14 @@ async function handleDelete(id) {
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
// 刷新列表 // 刷新列表
await getList() await getList()
} catch {} } catch (err) {
console.log(err)
}
} }
onMounted(() => {
handleQuery()
})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -16,10 +16,10 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table v-loading="loading" :data="list" row-key="id" :tree-props="{ children: 'children' }"> <el-table v-loading="loading" :data="list" row-key="id" :tree-props="{ children: 'children' }">
<el-table-column prop="categoryName" label="分类名称" /> <el-table-column prop="name" label="分类名称" />
<el-table-column prop="orderNum" label="排序" width="100px" /> <el-table-column prop="sort" label="排序" width="100px" />
<el-table-column prop="remark" label="备注" /> <el-table-column prop="remark" label="备注" />
<el-table-column label="状态" align="center" min-width="150" prop="status"> <el-table-column label="状态" min-width="150" prop="status">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template> </template>
@@ -33,16 +33,16 @@
<el-table-column label="操作"> <el-table-column label="操作">
<template #default="scope"> <template #default="scope">
<el-button type="primary" text @click="openForm('update', scope.row)">修改</el-button> <el-button type="primary" text @click="openForm('update', scope.row)">修改</el-button>
<el-button type="primary" text @click="openForm('createChildren', scope.row)" <el-button type="primary" text @click="openForm('createChildren', scope.row)">
>新增</el-button 新增
> </el-button>
<el-button type="danger" text @click="handleDelete(scope.row.id)">删除</el-button> <el-button type="danger" text @click="handleDelete(scope.row.id)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<Pagination <Pagination
v-model:limit="searchForm.pageSize" v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum" v-model:page="searchForm.pageNo"
:total="total" :total="total"
@pagination="handleQuery" @pagination="handleQuery"
/> />

View File

@@ -7,7 +7,7 @@
:rules="formRules" :rules="formRules"
label-width="80px" label-width="80px"
> >
<el-form-item v-if="formData.level > 1" label="上级分类"> <el-form-item v-if="formData.parentCategory" label="上级分类">
<el-input v-model="formData.parentCategory" disabled /> <el-input v-model="formData.parentCategory" disabled />
</el-form-item> </el-form-item>
<el-form-item label="分类名称" prop="name"> <el-form-item label="分类名称" prop="name">
@@ -71,12 +71,14 @@ const open = async (type, info) => {
if (type == 'update') { if (type == 'update') {
formData.value = await ProductCategoryApi.getCategory(info.id) formData.value = await ProductCategoryApi.getCategory(info.id)
} else { } else {
formData.value.level = info.level + 1
formData.value.parentCategory = info.name formData.value.parentCategory = info.name
formData.value.parentId = info.id
} }
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} else {
formData.value.parentId = 0
} }
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
@@ -91,12 +93,12 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
if (formType.value === 'create') { if (formType.value === 'update') {
await ProductCategoryApi.updateCategory(formData.value)
message.success(t('common.updateSuccess'))
} else {
await ProductCategoryApi.createCategory(formData.value) await ProductCategoryApi.createCategory(formData.value)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
} else {
await ProductCategoryApi.updateCategory(data)
message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false dialogVisible.value = false
// 发送操作成功的事件 // 发送操作成功的事件

View File

@@ -7,11 +7,11 @@
:rules="formRules" :rules="formRules"
label-width="80px" label-width="80px"
> >
<el-form-item label="名称" prop="name"> <el-form-item label="名称" prop="label">
<el-input v-model="formData.name" placeholder="请输入供应商名称" /> <el-input v-model="formData.label" placeholder="请输入供应商名称" />
</el-form-item> </el-form-item>
<el-form-item label="排序" prop="orderNum"> <el-form-item label="排序" prop="sort">
<el-input v-model="formData.orderNum" placeholder="请输入排序" type="number" :min="0" /> <el-input v-model="formData.sort" placeholder="请输入排序" type="number" :min="0" />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input <el-input
@@ -29,6 +29,7 @@
</Dialog> </Dialog>
</template> </template>
<script name="DialogSupplier" setup> <script name="DialogSupplier" setup>
import * as dictApi from '@/api/system/dict/dict.data'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
@@ -37,27 +38,26 @@ const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用 const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改 const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({ const formData = ref({
name: '', label: '',
orderNum: 1, sort: 1,
remark: '' remark: ''
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] label: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type, info) => { const open = async (type, id) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = type == 'update' ? '修改供应商' : '新增供应商' dialogTitle.value = type == 'update' ? '修改供应商' : '新增供应商'
formType.value = type formType.value = type
resetForm() resetForm()
// 修改时,设置数据 // 修改时,设置数据
if (info.id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = { ...info } formData.value = await dictApi.getDictData(id)
// formData.value = await UserApi.getUser(id)
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@@ -75,12 +75,14 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
// const data = formData.value as unknown as UserApi.UserVO if (!formData.value.value) {
formData.value.value = formData.value.label
}
if (formType.value === 'create') { if (formType.value === 'create') {
// await UserApi.createUser(data) await dictApi.createDictData(formData.value)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
} else { } else {
// await UserApi.updateUser(data) await dictApi.updateDictData(formData.value)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false dialogVisible.value = false
@@ -94,8 +96,10 @@ const submitForm = async () => {
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
name: '', label: '',
orderNum: 1, sort: 1,
status: 0,
dictType: 'erp_supplier',
remark: '' remark: ''
} }
formRef.value?.resetFields() formRef.value?.resetFields()

View File

@@ -16,23 +16,27 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table v-loading="loading" :data="tableList"> <el-table v-loading="loading" :data="tableList">
<el-table-column prop="name" label="供应商名称" /> <el-table-column prop="label" label="供应商名称" />
<el-table-column prop="orderNum" label="排序" width="100px" /> <el-table-column prop="sort" label="排序" width="100px" />
<el-table-column prop="remark" label="备注" /> <el-table-column prop="remark" label="备注" />
<el-table-column label="创建时间" prop="createTime" width="180px" /> <el-table-column
<el-table-column label="创建人" prop="createUser" width="150px" /> label="创建时间"
prop="createTime"
width="180px"
:formatter="dateFormatter"
/>
<el-table-column label="操作"> <el-table-column label="操作">
<template #default="scope"> <template #default="scope">
<el-button type="primary" text @click="openForm('update', scope.row)">修改</el-button> <el-button type="primary" text @click="openForm('update', scope.row.id)">修改</el-button>
<el-button type="danger" text @click="handleDelete(scope.row)">删除</el-button> <el-button type="danger" text @click="handleDelete(scope.row.id)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<Pagination <Pagination
v-model:limit="searchForm.pageSize" v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum" v-model:page="searchForm.pageNo"
:total="total" :total="total"
@pagination="handleQuery" @pagination="getList"
/> />
<DialogSupplier ref="brandDialog" @success="handleQuery" /> <DialogSupplier ref="brandDialog" @success="handleQuery" />
@@ -40,11 +44,18 @@
</template> </template>
<script setup name="SupplierSet"> <script setup name="SupplierSet">
import { dateFormatter } from '@/utils/formatTime'
import DialogSupplier from './DialogSupplier.vue' import DialogSupplier from './DialogSupplier.vue'
import * as dictApi from '@/api/system/dict/dict.data'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const searchForm = ref({ const searchForm = ref({
pageNum: 1, label: '',
pageSize: 20 pageSize: 20,
pageNo: 1,
dictType: 'erp_supplier'
}) })
const total = ref(0) const total = ref(0)
@@ -53,43 +64,49 @@ const tableList = ref([])
const loading = ref(false) const loading = ref(false)
function handleQuery() { function handleQuery() {
searchForm.value.pageNum = 1 searchForm.value.pageNo = 1
getList() getList()
} }
function resetQuery() { function resetQuery() {
searchForm.value = { searchForm.value = {
name: '', label: '',
pageSize: 20, pageSize: 20,
pageNum: 1 pageNo: 1,
dictType: 'erp_supplier'
} }
getList() getList()
} }
function getList() { async function getList() {
tableList.value = [ loading.value = true
{ try {
id: 1, const data = await dictApi.getDictDataPage(searchForm.value)
name: '测试' tableList.value = data.list
} total.value = data.total
] } finally {
} loading.value = false
}
function openForm(type, info) { }
brandDialog.value.open(type, info)
} function openForm(type, id) {
brandDialog.value.open(type, id)
async function handleDelete(row) { }
async function handleDelete(id) {
try { try {
console.log(row)
// 删除的二次确认 // 删除的二次确认
await message.delConfirm() await message.delConfirm()
// 发起删除 // 发起删除
// await UserApi.deleteUser(row.id) await dictApi.deleteDictData(id)
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
// 刷新列表 // 刷新列表
await getList() await getList()
} catch {} } catch {}
} }
onMounted(() => {
handleQuery()
})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -6,6 +6,7 @@
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="80px" label-width="80px"
@submit.prevent
> >
<el-form-item label="属性名称" prop="name"> <el-form-item label="属性名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" /> <el-input v-model="formData.name" placeholder="请输入名称" />
@@ -33,7 +34,7 @@ const formRules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const attributeList = ref([]) // 商品属性列表 const attributeList = ref<any>([]) // 商品属性列表
const props = defineProps({ const props = defineProps({
propertyList: { propertyList: {
type: Array, type: Array,
@@ -69,17 +70,13 @@ const submitForm = async () => {
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as PropertyApi.PropertyVO const data = formData.value as PropertyApi.PropertyVO
// 检查属性是否已存在,如果有则返回属性和其下属性值 const propertyId = await PropertyApi.createProperty(data)
const res = await PropertyApi.getPropertyListAndValue({ name: data.name }) // 添加到属性列表
if (res.length === 0) { attributeList.value.push({
const propertyId = await PropertyApi.createProperty(data) id: propertyId + '',
attributeList.value.push({ id: propertyId, ...formData.value, values: [] }) ...formData.value,
} else { values: []
if (res[0].values === null) { })
res[0].values = []
}
attributeList.value.push(res[0]) // 因为只用一个
}
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
dialogVisible.value = false dialogVisible.value = false
} finally { } finally {
@@ -90,8 +87,7 @@ const submitForm = async () => {
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
name: '', name: ''
remark: ''
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }

View File

@@ -1,317 +0,0 @@
<template>
<el-table
:data="isBatch ? skuList : formData.skus"
border
class="tabNumWidth"
max-height="500"
size="small"
>
<el-table-column align="center" fixed="left" label="图片" min-width="100">
<template #default="{ row }">
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
</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="120"
>
<template #default="{ row }">
<!-- TODO puhui999展示成蓝色有点区分度哈 -->
{{ row.properties[index]?.valueName }}
</template>
</el-table-column>
</template>
<el-table-column align="center" label="商品条码" min-width="168">
<template #default="{ row }">
<el-input v-model="row.barCode" class="w-100%" />
</template>
</el-table-column>
<el-table-column align="center" label="销售价(元)" min-width="168">
<template #default="{ row }">
<el-input-number v-model="row.price" :min="0" :precision="2" :step="0.1" class="w-100%" />
</template>
</el-table-column>
<el-table-column align="center" label="市场价(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.marketPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template>
</el-table-column>
<el-table-column align="center" label="成本价(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.costPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template>
</el-table-column>
<el-table-column align="center" label="库存" min-width="168">
<template #default="{ row }">
<el-input-number v-model="row.stock" :min="0" class="w-100%" />
</template>
</el-table-column>
<el-table-column align="center" label="重量(kg)" min-width="168">
<template #default="{ row }">
<el-input-number v-model="row.weight" :min="0" :precision="2" :step="0.1" class="w-100%" />
</template>
</el-table-column>
<el-table-column align="center" label="体积(m^3)" min-width="168">
<template #default="{ row }">
<el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
</template>
</el-table-column>
<template v-if="formData.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.subCommissionFirstPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template>
</el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="168">
<template #default="{ row }">
<el-input-number
v-model="row.subCommissionSecondPrice"
:min="0"
:precision="2"
:step="0.1"
class="w-100%"
/>
</template>
</el-table-column>
</template>
<el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
<template #default="{ row }">
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
批量添加
</el-button>
<el-button v-else link size="small" type="primary" @click="deleteSku(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script lang="ts" name="SkuList" setup>
import { PropType } from 'vue'
import { copyValueToTarget } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { UploadImg } from '@/components/UploadFile'
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
const props = defineProps({
propFormData: {
type: Object as PropType<SpuType>,
default: () => {}
},
propertyList: {
type: Array,
default: () => []
},
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
})
const formData = ref<SpuType>() // 表单数据
const skuList = ref<SkuType[]>([
{
price: 0, // 商品价格
marketPrice: 0, // 市场价
costPrice: 0, // 成本价
barCode: '', // 商品条码
picUrl: '', // 图片地址
stock: 0, // 库存
weight: 0, // 商品重量
volume: 0, // 商品体积
subCommissionFirstPrice: 0, // 一级分销的佣金
subCommissionSecondPrice: 0 // 二级分销的佣金
}
]) // 批量添加时的临时数据
// TODO @puhui999保存时每个商品规格的表单要校验下。例如说销售金额最低是 0.01 这种。
/** 批量添加 */
const batchAdd = () => {
formData.value.skus.forEach((item) => {
copyValueToTarget(item, skuList.value[0])
})
}
/** 删除 sku */
const deleteSku = (row) => {
const index = formData.value.skus.findIndex(
// 直接把列表转成字符串比较
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
formData.value.skus.splice(index, 1)
}
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
/**
* 将传进来的值赋值给 skuList
*/
watch(
() => props.propFormData,
(data) => {
if (!data) return
formData.value = data
},
{
deep: true,
immediate: true
}
)
/** 生成表数据 */
const generateTableData = (propertyList: any[]) => {
// 构建数据结构
const propertyValues = propertyList.map((item) =>
item.values.map((v) => ({
propertyId: item.id,
propertyName: item.name,
valueId: v.id,
valueName: v.name
}))
)
// TODO @puhui是不是 buildSkuList这样容易理解一点哈。item 改成 sku
const buildList = build(propertyValues)
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
if (!validateData(propertyList)) {
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
formData.value!.skus = []
}
for (const item of buildList) {
const row = {
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
subCommissionFirstPrice: 0,
subCommissionSecondPrice: 0
}
// 如果存在属性相同的 sku 则不做处理
const index = formData.value!.skus.findIndex(
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
if (index !== -1) {
continue
}
formData.value.skus.push(row)
}
}
/**
* 生成 skus 前置校验
*/
const validateData = (propertyList: any[]) => {
const skuPropertyIds = []
formData.value.skus.forEach((sku) =>
sku.properties
?.map((property) => property.propertyId)
.forEach((propertyId) => {
if (skuPropertyIds.indexOf(propertyId) === -1) {
skuPropertyIds.push(propertyId)
}
})
)
const propertyIds = propertyList.map((item) => item.id)
return skuPropertyIds.length === propertyIds.length
}
/** 构建所有排列组合 */
const build = (propertyValuesList: Property[][]) => {
if (propertyValuesList.length === 0) {
return []
} else if (propertyValuesList.length === 1) {
return propertyValuesList[0]
} else {
const result: Property[][] = []
const rest = build(propertyValuesList.slice(1))
for (let i = 0; i < propertyValuesList[0].length; i++) {
for (let j = 0; j < rest.length; j++) {
// 第一次不是数组结构,后面的都是数组结构
if (Array.isArray(rest[j])) {
result.push([propertyValuesList[0][i], ...rest[j]])
} else {
result.push([propertyValuesList[0][i], rest[j]])
}
}
}
return result
}
}
/** 监听属性列表,生成相关参数和表头 */
watch(
() => props.propertyList,
(propertyList) => {
// 如果不是多规格则结束
if (!formData.value.specType) {
return
}
// 如果当前组件作为批量添加数据使用,则重置表数据
if (props.isBatch) {
skuList.value = [
{
price: 0,
marketPrice: 0,
costPrice: 0,
barCode: '',
picUrl: '',
stock: 0,
weight: 0,
volume: 0,
subCommissionFirstPrice: 0,
subCommissionSecondPrice: 0
}
]
}
// 判断代理对象是否为空
if (JSON.stringify(propertyList) === '[]') {
return
}
// 重置表头
tableHeaders.value = []
// 生成表头
propertyList.forEach((item, index) => {
// name加属性项index区分属性值
tableHeaders.value.push({ prop: `name${index}`, label: item.name })
})
// 如果回显的 sku 属性和添加的属性一致则不处理
if (validateData(propertyList)) {
return
}
// 添加新属性没有属性值也不做处理
if (propertyList.some((item) => item.values.length === 0)) {
return
}
// 生成 table 数据,即 sku 列表
generateTableData(propertyList)
},
{
deep: true,
immediate: true
}
)
// 暴露出生成 sku 方法,给添加属性成功时调用
defineExpose({ generateTableData })
</script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<el-tabs v-model="tabName" type="border-card" tab-position="top"> <el-tabs v-model="tabName" type="border-card" tab-position="top" v-loading="formLoading">
<el-tab-pane label="商品信息" name="basic"> <el-tab-pane label="商品信息" name="basic">
<el-form :model="form" ref="spuForm" :rules="rules" label-width="90px"> <el-form :model="form" ref="spuForm" :rules="rules" label-width="90px">
<el-row :gutter="20"> <el-row :gutter="20">
@@ -11,9 +11,10 @@
<el-col :span="8" :offset="0"> <el-col :span="8" :offset="0">
<el-form-item label="分类" prop="productCategory"> <el-form-item label="分类" prop="productCategory">
<el-cascader <el-cascader
:options="opts.productCategory" :options="opts.category"
v-model="form.productCategory" v-model="form.productCategory"
placeholder="请选择分类" placeholder="请选择分类"
:props="{ label: 'name', value: 'id' }"
filterable filterable
show-all-levels show-all-levels
style="width: 100%" style="width: 100%"
@@ -25,9 +26,9 @@
<el-select v-model="form.productBrand" placeholder="请选择品牌" filterable> <el-select v-model="form.productBrand" placeholder="请选择品牌" filterable>
<el-option <el-option
v-for="item in opts.brand" v-for="item in opts.brand"
:key="item.value" :key="item.brandId"
:label="item.label" :label="item.name"
:value="item.value" :value="item.brandId"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -110,11 +111,16 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
v-for="col in form.productSpecList" v-for="(col, index) in form.productSpecList"
:prop="col.id"
:key="col.id" :key="col.id"
:label="col.name" :label="col.name"
/> >
<template #default="{ row }">
<span style="font-weight: bold; color: #40aaff">
{{ row.properties[index]?.valueName }}
</span>
</template>
</el-table-column>
<el-table-column prop="price" label="销售价"> <el-table-column prop="price" label="销售价">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number v-model="row.price" :min="0.01" :step="1" /> <el-input-number v-model="row.price" :min="0.01" :step="1" />
@@ -130,22 +136,30 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<div class="mt-20px flex justify-center">
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button plain>重置</el-button>
</div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="详细信息" name="detail"> <el-tab-pane label="详细信息" name="detail">
<Editor v-model:modelValue="form.detailInfo" /> <Editor v-model:modelValue="form.detailInfo" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<div class="mt-20px flex justify-center">
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button plain>重置</el-button>
</div>
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="form.productSpecList" /> <ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="form.productSpecList" />
</template> </template>
<script setup> <script setup>
import { cloneDeep } from 'lodash-es'
import * as PropertyApi from '@/api/mall/product/property'
import * as ProductApi from '@/api/mall/product/index'
import ProductAttributesAddForm from './Comp/ProductAttributesAddForm.vue' import ProductAttributesAddForm from './Comp/ProductAttributesAddForm.vue'
import * as BrandApi from '@/api/mall/product/brand'
import * as CategoryApi from '@/api/mall/product/category'
import { handleTree } from '@/utils/tree'
const route = useRoute() const route = useRoute()
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const tabName = ref('basic') const tabName = ref('basic')
const form = ref({ const form = ref({
@@ -157,25 +171,32 @@ const form = ref({
carouselImages: [], carouselImages: [],
productSpecList: [], productSpecList: [],
skuList: [], skuList: [],
detailInfo: null detailInfo: null,
status: 0
}) })
const rules = ref({}) const rules = {
productName: { required: true, message: '产品名称不可为空', trigger: 'blur' }
}
const attributesAddFormRef = ref() // 添加商品属性表单 const attributesAddFormRef = ref() // 添加商品属性表单
const opts = { const opts = ref({
brand: [{ value: 1, label: '品牌1' }], brand: [],
productCategory: [ category: []
{ })
id: 1,
label: '分类1', async function getOptions() {
children: [{ id: 2, label: '子分类1' }] BrandApi.getSimpleBrandList().then((data) => {
} opts.value.brand = data || []
] })
CategoryApi.getCategorySimpleList().then((data) => {
opts.value.category = handleTree(data || [])
})
} }
/** 删除属性*/ /** 删除属性*/
function handleCloseProperty(index) { function handleCloseProperty(index) {
form.value.productSpecList?.splice(index, 1) form.value.productSpecList?.splice(index, 1)
getTableList()
} }
const attributeIndex = ref(null) const attributeIndex = ref(null)
@@ -191,8 +212,7 @@ async function handleInputConfirm(index, propertyId) {
if (inputValue.value) { if (inputValue.value) {
// 保存属性值 // 保存属性值
try { try {
// const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value }) const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value })
const id = propertyId || parseInt(Math.random() * 1000000)
form.value.productSpecList[index].values.push({ id, name: inputValue.value }) form.value.productSpecList[index].values.push({ id, name: inputValue.value })
message.success('添加成功') message.success('添加成功')
} catch { } catch {
@@ -201,31 +221,88 @@ async function handleInputConfirm(index, propertyId) {
} }
attributeIndex.value = null attributeIndex.value = null
inputValue.value = '' inputValue.value = ''
form.value.skuList = getTableList() getTableList()
}
/** 删除属性值*/
function handleCloseValue(index, valueIndex) {
form.value.productSpecList[index].values?.splice(valueIndex, 1)
getTableList()
} }
function getTableList() { function getTableList() {
let list = [] const propertyList = [...form.value.productSpecList]
form.value.productSpecList.map((item) => { // 构建数据结构
if (!list.length) { const propertyValues = propertyList.map((item) =>
item.values.map((it) => { item.values.map((v) => ({
const obj = {} propertyId: item.id,
obj[it.id] = it.name propertyName: item.name,
list.push(obj) valueId: v.id,
}) valueName: v.name
} else { }))
item.values.map((it, index) => { )
if (index < list.length) { const buildSkuList = build(propertyValues)
list[index][it.id] = it.name // 如果回显的 sku 属性和添加的属性不一致则重置 skuList 列表
} else { if (!validateData(propertyList)) {
const obj = {} // 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
obj[it.id] = it.name // form.value.skuList = []
list.push(obj) }
} form.value.skuList = []
}) for (const item of buildSkuList) {
const row = {
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
price: 0.01,
intro: '',
specsName: ''
} }
}) // 如果存在属性相同的 sku 则不做处理
return list const index = form.value.skuList.findIndex(
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
)
if (index !== -1) {
continue
}
form.value.skuList.push(row)
}
}
/**
* 生成 skuList 前置校验
*/
const validateData = (propertyList) => {
const skuPropertyIds = []
form.value.skuList.forEach((sku) =>
sku.properties.map((property) => {
if (skuPropertyIds.indexOf(property.propertyId) === -1) {
skuPropertyIds.push(property.propertyId)
}
})
)
const propertyIds = propertyList.map((item) => item.id)
return skuPropertyIds.length === propertyIds.length
}
/** 构建所有排列组合 */
const build = (propertyValuesList) => {
if (propertyValuesList.length === 0) {
return []
} else if (propertyValuesList.length === 1) {
return propertyValuesList[0]
} else {
const result = []
const rest = build(propertyValuesList.slice(1))
for (let i = 0; i < propertyValuesList[0].length; i++) {
for (let j = 0; j < rest.length; j++) {
// 第一次不是数组结构,后面的都是数组结构
if (Array.isArray(rest[j])) {
result.push([propertyValuesList[0][i], ...rest[j]])
} else {
result.push([propertyValuesList[0][i], rest[j]])
}
}
}
return result
}
} }
const inputRef = ref([]) //标签输入框Ref const inputRef = ref([]) //标签输入框Ref
@@ -248,14 +325,103 @@ function handleAddSpec() {
attributesAddFormRef.value.open() attributesAddFormRef.value.open()
} }
const spuForm = ref()
function onSubmit() { function onSubmit() {
message.success('保存成功!') spuForm.value.validate(async (valid) => {
if (valid && validateSku()) {
// 深拷贝一份, 这样最终 server 端不满足,不需要影响原始数据
const deepCopyFormData = cloneDeep(unref(form.value))
deepCopyFormData.productSpecList = deepCopyFormData.skuList
if (deepCopyFormData.productCategory && deepCopyFormData.productCategory.length) {
deepCopyFormData.productCategory = deepCopyFormData.productCategory.at(-1)
}
delete deepCopyFormData.skuList
// 校验都通过后提交表单
const data = deepCopyFormData
const id = route.query.id
if (!id) {
await ProductApi.createProduct(data)
message.success(t('common.createSuccess'))
} else {
await ProductApi.updateProduct(data)
message.success(t('common.updateSuccess'))
}
}
})
} }
onMounted(() => { // 作为活动组件的校验
const ruleConfig = [
{
name: 'specsName',
rule: (arg) => arg.length,
message: '规格名称不可为空 '
},
{
name: 'price',
rule: (arg) => arg >= 0.01,
message: '商品销售价格必须大于等于 0.01 元!!!'
}
]
/**
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
*/
const validateSku = () => {
let warningInfo = '请检查商品各行相关属性配置,'
let validate = form.value.skuList.length > 0 // 默认通过
if (!form.value.skuList.length) {
message.info('请添加商品规格!!!')
}
for (const sku of form.value.skuList) {
for (const rule of ruleConfig) {
const arg = getValue(sku, rule.name)
if (!rule.rule(arg)) {
validate = false // 只要有一个不通过则直接不通过
warningInfo += rule.message
break
}
}
// 只要有一个不通过则结束后续的校验
if (!validate) {
message.warning(warningInfo)
throw new Error(warningInfo)
}
}
return validate
}
const getValue = (obj, arg) => {
const keys = arg.split('.')
let value = obj
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key]
} else {
value = undefined
break
}
}
return value
}
const formLoading = ref(false)
/** 获得详情 */
const getDetail = async () => {
if (route.query?.id) { if (route.query?.id) {
form.value = { formLoading.value = true
productName: '商品名称哦~' try {
const res = await ProductApi.getProduct(route.query.id)
const propList = getPropertyList(res.productSpecList)
form.value = {
...res,
skuList: res.productSpecList,
productSpecList: propList
}
} finally {
formLoading.value = false
} }
} else { } else {
form.value = { form.value = {
@@ -267,9 +433,41 @@ onMounted(() => {
carouselImages: ['https://ss-cloud.ahduima.com/1001/1796426117251600384.png'], carouselImages: ['https://ss-cloud.ahduima.com/1001/1796426117251600384.png'],
productSpecList: [], productSpecList: [],
skuList: [], skuList: [],
detailInfo: null detailInfo: null,
status: 0
} }
} }
}
/**
* 获得商品的规格列表 - 商品相关的公共函数
*
* @param spu
* @return PropertyAndValues 规格列表
*/
const getPropertyList = (list) => {
// 直接拿返回的 skus 属性逆向生成出 propertyList
const properties = []
// 只有是多规格才处理
list.forEach((sku) => {
sku.properties?.forEach(({ propertyId, propertyName, valueId, valueName }) => {
// 添加属性
if (!properties?.some((item) => item.id === propertyId)) {
properties.push({ id: propertyId, name: propertyName, values: [] })
}
// 添加属性值
const index = properties?.findIndex((item) => item.id === propertyId)
if (!properties[index].values?.some((value) => value.id === valueId)) {
properties[index].values?.push({ id: valueId, name: valueName })
}
})
})
return properties
}
onMounted(async () => {
getOptions()
await getDetail()
}) })
</script> </script>

View File

@@ -20,9 +20,9 @@
> >
<el-option <el-option
v-for="item in opts.brand" v-for="item in opts.brand"
:key="item.value" :key="item.brandId"
:label="item.label" :label="item.name"
:value="item.value" :value="item.brandId"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -33,6 +33,7 @@
placeholder="请选择分类" placeholder="请选择分类"
clearable clearable
filterable filterable
:props="{ label: 'name', value: 'id', checkStrictly: true }"
show-all-levels show-all-levels
@change="handleQuery" @change="handleQuery"
/> />
@@ -40,7 +41,7 @@
<el-form-item> <el-form-item>
<el-button @click="handleQuery" v-hasPermi="['mall:prod:search']"> 搜索 </el-button> <el-button @click="handleQuery" v-hasPermi="['mall:prod:search']"> 搜索 </el-button>
<el-button @click="resetQuery" v-hasPermi="['mall:prod:reset']"> 重置 </el-button> <el-button @click="resetQuery" v-hasPermi="['mall:prod:reset']"> 重置 </el-button>
<el-button plain type="primary" @click="openForm" v-hasPermi="['mall:prod:add']"> <el-button plain type="primary" @click="openForm(null)" v-hasPermi="['mall:prod:add']">
新增 新增
</el-button> </el-button>
</el-form-item> </el-form-item>
@@ -53,6 +54,16 @@
<div class="pl-100px pr-100px"> <div class="pl-100px pr-100px">
<el-table :data="row.productSpecList"> <el-table :data="row.productSpecList">
<el-table-column label="规格名称" prop="specsName" /> <el-table-column label="规格名称" prop="specsName" />
<el-table-column />
<el-table-column
v-for="(item, index) in row.properties"
:key="index"
:label="item.label"
>
<template #default="scope">
{{ scope.row.properties[index].valueName }}
</template>
</el-table-column>
<el-table-column label="售价" prop="price" /> <el-table-column label="售价" prop="price" />
<el-table-column label="简介" prop="intro" /> <el-table-column label="简介" prop="intro" />
</el-table> </el-table>
@@ -105,6 +116,9 @@
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { createImageViewer } from '@/components/ImageViewer' import { createImageViewer } from '@/components/ImageViewer'
import * as ProductApi from '@/api/mall/product' import * as ProductApi from '@/api/mall/product'
import * as BrandApi from '@/api/mall/product/brand'
import * as CategoryApi from '@/api/mall/product/category'
import { handleTree } from '@/utils/tree'
const { currentRoute, push } = useRouter() const { currentRoute, push } = useRouter()
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
@@ -143,8 +157,18 @@ async function handleDelete(id) {
async function getList() { async function getList() {
loading.value = true loading.value = true
try { try {
const data = await ProductApi.getProductPage(queryParams.value) const params = { ...queryParams.value }
tableList.value = data.list if (params.productCategory && params.productCategory.length) {
params.productCategory = params.productCategory.at(-1)
}
const data = await ProductApi.getProductPage(params)
tableList.value = data.list.map((prod) => ({
...prod,
properties: prod.productSpecList[0].properties.map((item) => ({
propertyId: item.propertyId,
label: item.propertyName
}))
}))
total.value = data.total total.value = data.total
} finally { } finally {
loading.value = false loading.value = false
@@ -163,6 +187,15 @@ function handleQuery() {
getList() getList()
} }
async function getOptions() {
BrandApi.getSimpleBrandList().then((data) => {
opts.value.brand = data || []
})
CategoryApi.getCategorySimpleList().then((data) => {
opts.value.productCategory = handleTree(data || [])
})
}
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
queryParams.value = { queryParams.value = {
@@ -182,7 +215,7 @@ function resetQuery() {
*/ */
function openForm(id) { function openForm(id) {
// 修改 // 修改
if (typeof id == 'number') { if (id) {
push({ push({
path: '/miniMall/productEdit', path: '/miniMall/productEdit',
query: { id } query: { id }
@@ -201,6 +234,10 @@ watch(
) )
handleQuery() handleQuery()
onMounted(() => {
getOptions()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,27 +1,27 @@
<template> <template>
<Dialog title="发起采购" v-model="dialogVisible" width="800px"> <Dialog title="发起采购" v-model="dialogVisible" width="800px">
<el-form :model="form" ref="addForm" :rules="rules" label-width="100px"> <el-form :model="form" ref="formRef" :rules="rules" label-width="100px">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="产品" prop="product"> <el-form-item label="产品" prop="productId">
<el-select v-model="form.product" placeholder="请选择" filterable style="width: 100%"> <el-select v-model="form.productId" placeholder="请选择" filterable style="width: 100%">
<el-option <el-option
v-for="item in productOptions" v-for="item in props.opts.product"
:key="item.value" :key="item.productId"
:label="item.label" :label="item.productName"
:value="item.value" :value="item.productId"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="规格" prop="specs"> <el-form-item label="规格" prop="specsId">
<el-select v-model="form.specs" placeholder="请选择" filterable style="width: 100%"> <el-select v-model="form.specsId" placeholder="请选择" filterable style="width: 100%">
<el-option <el-option
v-for="item in specsOptions" v-for="item in specOptions"
:key="item.value" :key="item.specsId"
:label="item.label" :label="item.specsName"
:value="item.value" :value="item.specsId"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -30,34 +30,41 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="供应商" prop="supplier"> <el-form-item label="供应商" prop="supplier">
<el-input v-model="form.supplier" placeholder="请输入" /> <el-select v-model="form.supplier" placeholder="选择供应商" filterable>
<el-option
v-for="item in opts.supplier"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="采购数量" prop="purchaseCount"> <el-form-item label="采购数量" prop="num">
<el-input-number <el-input-number :min="1" v-model="form.num" placeholder="请输入" style="width: 100%" />
:min="1"
v-model="form.purchaseCount"
placeholder="请输入"
style="width: 100%"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="采购单价" prop="price"> <el-form-item label="采购单价" prop="unitPrice">
<el-input-number <el-input-number
:min="0" :min="0"
v-model="form.price" v-model="form.unitPrice"
placeholder="请输入" placeholder="请输入"
style="width: 100%" style="width: 100%"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" :offset="0"> <el-col :span="12" :offset="0">
<el-form-item label="存放仓库" prop="warehouse"> <el-form-item label="存放仓库" prop="warehouseId">
<el-select v-model="form.warehouse" placeholder="请选择" filterable style="width: 100%"> <el-select
v-model="form.warehouseId"
placeholder="请选择"
filterable
style="width: 100%"
>
<el-option <el-option
v-for="item in warehouseOptions" v-for="item in warehouseOptions"
:key="item.value" :key="item.value"
@@ -86,13 +93,42 @@
</template> </template>
<script setup> <script setup>
import * as PurchaseApi from '@/api/mall/purchase'
const props = defineProps({
opts: {
type: Object,
default: () => {
return {
product: [],
spec: [],
supplier: []
}
}
}
})
const specOptions = computed(() => {
if (form.value.productId) {
return (
props.opts.product.find((it) => it.productId == form.value.productId)?.productSpecList || []
)
} else {
return []
}
})
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const form = ref() const form = ref({})
const rules = ref({}) const rules = {
productId: { required: true, message: '产品不可为空', trigger: 'change' },
specsId: { required: true, message: '规格不可为空', trigger: 'change' },
supplier: { required: true, message: '供应商不可为空', trigger: 'change' },
warehouseId: { required: true, message: '仓库不可为空', trigger: 'change' },
num: { required: true, message: '采购数量不可为空', trigger: 'change,blur' },
unitPrice: { required: true, message: '采购单价不可为空', trigger: 'change,blur' }
}
const productOptions = ref([])
const specsOptions = ref([])
const warehouseOptions = ref([ const warehouseOptions = ref([
{ label: '自营仓', value: 1 }, { label: '自营仓', value: 1 },
{ label: '供应商仓', value: 2 } { label: '供应商仓', value: 2 }
@@ -100,19 +136,37 @@ const warehouseOptions = ref([
const open = (val) => { const open = (val) => {
dialogVisible.value = true dialogVisible.value = true
form.value = val || { form.value = { ...val } || {
product: '', productId: undefined,
specs: '', specsId: undefined,
supplier: '', supplier: undefined,
purchaseCount: 1, num: undefined,
warehouse: 1, unitPrice: undefined,
remark: '' warehouseId: undefined,
remark: undefined
} }
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
function handleSave() { const formRef = ref()
console.log('保存') const formLoading = ref(false)
async function handleSave() {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
await PurchaseApi.createPurchase(form.value)
message.success(t('common.createSuccess'))
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
} }
</script> </script>

View File

@@ -1,11 +1,11 @@
<template> <template>
<Dialog title="采购审核" v-model="dialogVisible" width="800px"> <Dialog title="采购审核" v-model="dialogVisible" width="800px">
<Descriptions :data="form" :schema="schema" :columns="3" /> <Descriptions :data="form" :schema="schema" :columns="3" />
<el-form v-if="canEdit" :model="form" ref="auditForm" :rules="rules" label-width="80px"> <el-form v-if="canEdit" :model="form" ref="auditForm" label-width="80px">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24" :offset="0"> <el-col :span="24" :offset="0">
<el-form-item label="审核" prop="status"> <el-form-item label="审核" prop="isPass">
<el-radio-group v-model="form.status"> <el-radio-group v-model="form.isPass">
<el-radio :label="2">通过</el-radio> <el-radio :label="2">通过</el-radio>
<el-radio :label="3">驳回</el-radio> <el-radio :label="3">驳回</el-radio>
</el-radio-group> </el-radio-group>
@@ -29,74 +29,31 @@
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup name="DialogAuditPurchase">
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const form = ref({}) const form = ref({})
const rules = ref({})
const canEdit = ref(true) const canEdit = ref(true)
const schema = ref([ const schema = ref([])
{
field: 'name',
label: '产品名称'
},
{
field: 'specsName',
label: '规格名称'
},
{
field: 'supplier',
label: '供应商'
},
{
field: 'purchaseCount',
label: '采购数量'
},
{
field: 'unitPrice',
label: '采购单价'
},
{
field: 'totalPrice',
label: '总金额'
},
{
field: 'warehouse',
label: '仓库'
},
{
field: 'warehouse',
label: '申请人'
},
{
field: '',
label: '申请时间'
},
{
field: 'remark',
label: '备注',
isEditor: true,
span: 3
}
])
const open = (val, flag) => { const open = (val, flag) => {
dialogVisible.value = true dialogVisible.value = true
form.value = { ...val, status: 2, remark: '<p style="color: red;">哈哈哈</p>' } form.value = { ...val }
canEdit.value = flag canEdit.value = flag
resetSchema()
if (!canEdit.value) { if (!canEdit.value) {
const arr = [ const arr = [
{ {
field: 'status', field: 'auditStatusName',
label: '采购状态' label: '采购状态'
}, },
{ {
field: 'auditUser', field: 'checkUserName',
label: '审核人' label: '审核人'
}, },
{ {
field: 'auditTime', field: 'checkTime',
label: '审核时间' label: '审核时间'
}, },
{ {
@@ -107,10 +64,61 @@ const open = (val, flag) => {
} }
] ]
schema.value = [...schema.value, ...arr] schema.value = [...schema.value, ...arr]
} else {
form.value.isPass = 2
form.value.auditRemark = ''
} }
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
const resetSchema = () => {
schema.value = [
{
field: 'productName',
label: '产品名称'
},
{
field: 'specsName',
label: '规格名称'
},
{
field: 'supplier',
label: '供应商'
},
{
field: 'num',
label: '采购数量'
},
{
field: 'unitPrice',
label: '采购单价'
},
{
field: 'totalPrice',
label: '总金额'
},
{
field: 'warehouseName',
label: '仓库'
},
{
field: 'applyUserName',
label: '申请人'
},
{
field: 'applyTime',
label: '申请时间',
dateFormat: 'YYYY-MM-DD'
},
{
field: 'remark',
label: '备注',
isEditor: true,
span: 3
}
]
}
function handleSave() { function handleSave() {
console.log('保存') console.log('保存')
} }

View File

@@ -1,4 +1,3 @@
// import { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
const statusOptions = [ const statusOptions = [
@@ -7,11 +6,10 @@ const statusOptions = [
{ label: '已驳回', value: 3 } { label: '已驳回', value: 3 }
] ]
// CrudSchemahttps://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive([ const crudSchemas = reactive([
{ {
label: '产品名称', label: '产品名称',
field: 'name', field: 'productName',
isSearch: true, isSearch: true,
isTable: true isTable: true
}, },
@@ -29,7 +27,7 @@ const crudSchemas = reactive([
}, },
{ {
label: '采购数量', label: '采购数量',
field: 'purchaseCount', field: 'num',
isSearch: false, isSearch: false,
isTable: true isTable: true
}, },
@@ -47,7 +45,7 @@ const crudSchemas = reactive([
}, },
{ {
label: '仓库', label: '仓库',
field: 'warehouse', field: 'warehouseName',
isSearch: false, isSearch: false,
isTable: true isTable: true
}, },
@@ -79,13 +77,13 @@ const crudSchemas = reactive([
}, },
{ {
label: '审核人', label: '审核人',
field: 'auditUserName', field: 'checkUserName',
isSearch: true, isSearch: true,
isTable: true isTable: true
}, },
{ {
label: '审核时间', label: '审核时间',
field: 'auditTime', field: 'checkTime',
isSearch: true, isSearch: true,
isTable: true, isTable: true,
formatter: dateFormatter, formatter: dateFormatter,
@@ -105,7 +103,7 @@ const crudSchemas = reactive([
}, },
{ {
label: '采购状态', label: '采购状态',
field: 'status', field: 'auditStatus',
isSearch: true, isSearch: true,
isTable: true, isTable: true,
search: { search: {
@@ -119,7 +117,7 @@ const crudSchemas = reactive([
} }
}, },
table: { table: {
field: 'statusName', field: 'auditStatusName',
fixed: 'right' fixed: 'right'
} }
}, },

View File

@@ -1,22 +1,76 @@
<template> <template>
<div> <div>
<!-- 搜索工作栏 --> <el-form inline :model="queryParams" class="-mb-15px" label-width="0">
<Search :schema="allSchemas.searchSchema" labelWidth="0"> <el-form-item>
<template #actionMore> <el-select
v-model="queryParams.productId"
placeholder="选择产品"
clearable
filterable
@change="changeProd"
>
<el-option
v-for="item in opts.product"
:key="item.productId"
:label="item.productName"
:value="item.productId"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select
v-model="queryParams.specsId"
placeholder="选择规格"
clearable
filterable
:disabled="!queryParams.productId"
>
<el-option
v-for="item in opts.spec"
:key="item.specsId"
:label="item.specsName"
:value="item.specsId"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="queryParams.supplier" placeholder="供应商" clearable filterable>
<el-option
v-for="item in opts.supplier"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-date-picker
v-model="queryParams.applyTime"
type="daterange"
range-separator="-"
start-placeholder="申请时间"
end-placeholder="申请时间"
/>
</el-form-item>
<el-form-item>
<el-date-picker
v-model="queryParams.checkTime"
type="daterange"
range-separator="-"
start-placeholder="审核时间"
end-placeholder="审核时间"
/>
</el-form-item>
<el-form-item>
<el-button @click="getList" v-hasPermi="['mall:purchase:search']"> 搜索 </el-button> <el-button @click="getList" v-hasPermi="['mall:purchase:search']"> 搜索 </el-button>
<el-button @click="resetQuery" v-hasPermi="['mall:purchase:reset']"> 重置 </el-button> <el-button @click="resetQuery" v-hasPermi="['mall:purchase:reset']"> 重置 </el-button>
<el-button type="primary" @click="handleAdd" v-hasPermi="['mall:purchase:add']"> <el-button type="primary" @click="handleAdd" v-hasPermi="['mall:purchase:add']">
发起采购 发起采购
</el-button> </el-button>
</template> </el-form-item>
</Search> </el-form>
<!-- 列表 --> <!-- 列表 -->
<SSTable <el-table v-loading="loading" class="mt-20px" :data="tableList" border>
class="mt-20px"
v-model:tableObject="tableObject"
:tableColumns="allSchemas.tableColumns"
@get-list="getTableList"
>
<el-table-column <el-table-column
v-for="item in allSchemas.tableColumns" v-for="item in allSchemas.tableColumns"
:key="item.table?.field || item.field" :key="item.table?.field || item.field"
@@ -24,6 +78,7 @@
:label="item.label" :label="item.label"
:fixed="item.fixed" :fixed="item.fixed"
min-width="150px" min-width="150px"
:formatter="item.formatter"
showOverflowTooltip showOverflowTooltip
/> />
<el-table-column label="操作" width="150px" fixed="right"> <el-table-column label="操作" width="150px" fixed="right">
@@ -56,53 +111,89 @@
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</SSTable> </el-table>
<DialogAdd ref="creatPurchase" /> <!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
<DialogAdd :opts="opts" ref="creatPurchase" />
<DialogAudit ref="auditPurchase" /> <DialogAudit ref="auditPurchase" />
</div> </div>
</template> </template>
<script setup> <script setup name="Purchase">
import * as ProductApi from '@/api/mall/product'
import { allSchemas } from './index.data.js' import { allSchemas } from './index.data.js'
import DialogAdd from './Comp/DialogAdd.vue' import DialogAdd from './Comp/DialogAdd.vue'
import DialogAudit from './Comp/DialogAudit.vue' import DialogAudit from './Comp/DialogAudit.vue'
import * as PurchaseApi from '@/api/mall/purchase'
import { getDictOptions } from '@/utils/dict'
const creatPurchase = ref() const creatPurchase = ref()
const auditPurchase = ref() const auditPurchase = ref()
const tableObject = ref({ const opts = ref({
tableList: [], product: [],
loading: false, spec: [],
total: 1, supplier: []
pageSize: 20,
currentPage: 1
}) })
const tableList = ref([])
const loading = ref(false)
const total = ref(0)
const queryParams = ref({
productId: undefined,
specsId: undefined,
supplier: undefined,
applyTime: [],
checkTime: [],
pageNo: 1,
pageSize: 20
})
function getOptions() {
ProductApi.getSimpleProductList().then((data) => {
opts.value.product = data
})
opts.value.supplier = getDictOptions('erp_supplier')
}
function changeProd(val) {
if (val) {
opts.value.spec = opts.value.product.find((it) => it.productId == val).productSpecList
} else {
opts.value.spec = []
}
queryParams.value.specsId = undefined
}
function resetQuery() { function resetQuery() {
queryParams.value = { queryParams.value = {
productName: undefined, productId: undefined,
productBrand: undefined, specsId: undefined,
productCategory: undefined, supplier: undefined,
applyTime: [],
checkTime: [],
pageNo: 1, pageNo: 1,
pageSize: 10 pageSize: 20
} }
getList() getList()
} }
const getList = function () { const getList = async function () {
tableObject.value.tableList = [ loading.value = true
{ name: '测试', status: 1, statusName: '审核中', supplier: '林氏木业', purchaseCount: 10 }, try {
{ name: '测试2', status: 2, statusName: '已通过', supplier: '张氏木业', purchaseCount: 1 }, const data = await PurchaseApi.getPurchasePage(queryParams.value)
{ name: '测试3', status: 3, statusName: '已驳回', supplier: '周氏木业', purchaseCount: 5 } tableList.value = data.list
] total.value = data.total
} } finally {
loading.value = false
function getTableList() { }
tableObject.value.tableList = [
{ name: '测试', status: 1, statusName: '审核中', supplier: '林氏木业', purchaseCount: 10 },
{ name: '测试2', status: 2, statusName: '已通过', supplier: '张氏木业', purchaseCount: 1 },
{ name: '测试3', status: 3, statusName: '已驳回', supplier: '周氏木业', purchaseCount: 5 }
]
} }
function purchaseAgain(row) { function purchaseAgain(row) {
@@ -119,6 +210,11 @@ function handleAdd() {
function handleDetail(row) { function handleDetail(row) {
auditPurchase.value.open({ ...row }, false) auditPurchase.value.open({ ...row }, false)
} }
onMounted(() => {
getOptions()
getList()
})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>