📖 CRM:产品模块的 review
This commit is contained in:
commit
66c48a913c
@ -41,3 +41,8 @@ export const deleteProduct = async (id: number) => {
|
|||||||
export const exportProduct = async (params) => {
|
export const exportProduct = async (params) => {
|
||||||
return await request.download({ url: `/crm/product/export-excel`, params })
|
return await request.download({ url: `/crm/product/export-excel`, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询产品操作日志
|
||||||
|
export const getOperateLogPage = async (params: any) => {
|
||||||
|
return await request.get({ url: '/crm/product/operate-log-page', params })
|
||||||
|
}
|
||||||
|
@ -504,7 +504,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '客户详情',
|
title: '客户详情',
|
||||||
noCache: true,
|
noCache: true,
|
||||||
hidden: true
|
hidden: true,
|
||||||
|
activeMenu: '/crm/customer'
|
||||||
},
|
},
|
||||||
component: () => import('@/views/crm/customer/detail/index.vue')
|
component: () => import('@/views/crm/customer/detail/index.vue')
|
||||||
},
|
},
|
||||||
@ -514,9 +515,21 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '联系人详情',
|
title: '联系人详情',
|
||||||
noCache: true,
|
noCache: true,
|
||||||
hidden: true
|
hidden: true,
|
||||||
|
activeMenu: '/crm/contact'
|
||||||
},
|
},
|
||||||
component: () => import('@/views/crm/contact/detail/index.vue')
|
component: () => import('@/views/crm/contact/detail/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'product/detail/:id',
|
||||||
|
name: 'CrmProductDetail',
|
||||||
|
meta: {
|
||||||
|
title: '产品详情',
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
activeMenu: '/crm/product'
|
||||||
|
},
|
||||||
|
component: () => import('@/views/crm/product/detail/index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="产品详情">
|
|
||||||
<el-descriptions :column="1" border>
|
|
||||||
<el-descriptions-item label="产品名称">
|
|
||||||
{{ detailData.name }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="创建时间">
|
|
||||||
{{ formatDate(detailData.createTime) }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="状态">
|
|
||||||
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="detailData.status" />
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="产品分类">
|
|
||||||
{{ productCategoryList?.find((c) => c.id === detailData.categoryId)?.name }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="产品编码">
|
|
||||||
{{ detailData.no }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="产品描述">
|
|
||||||
{{ detailData.description }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="负责人">
|
|
||||||
{{ detailData.ownerUserId }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="单位">
|
|
||||||
<dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="detailData.unit" />
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="价格">
|
|
||||||
{{ fenToYuan(detailData.price) }}元
|
|
||||||
</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
// TODO 芋艿:统一改成,独立 tab
|
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
|
||||||
import * as ProductCategoryApi from '@/api/crm/product/productCategory'
|
|
||||||
import * as ProductApi from '@/api/crm/product'
|
|
||||||
import { formatDate } from '@/utils/formatTime'
|
|
||||||
import { fenToYuan } from '@/utils'
|
|
||||||
import { getSimpleUserList, UserVO } from '@/api/system/user'
|
|
||||||
|
|
||||||
defineOptions({ name: 'CrmProductDetail' })
|
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
|
||||||
const detailLoading = ref(false) // 表单的加载中
|
|
||||||
const detailData = ref() // 详情数据
|
|
||||||
|
|
||||||
/** 打开弹窗 */
|
|
||||||
const open = async (data: ProductApi.ProductVO) => {
|
|
||||||
dialogVisible.value = true
|
|
||||||
// 设置数据
|
|
||||||
detailLoading.value = true
|
|
||||||
try {
|
|
||||||
detailData.value = data
|
|
||||||
} finally {
|
|
||||||
detailLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
|
||||||
|
|
||||||
const productCategoryList = ref([]) // 产品分类树
|
|
||||||
const userList = ref<UserVO[]>([]) // 系统用户
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
productCategoryList.value = await ProductCategoryApi.getProductCategoryList({})
|
|
||||||
userList.value = await getSimpleUserList()
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -62,13 +62,13 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="价格" prop="price">
|
<el-form-item label="价格" prop="price">
|
||||||
<el-input
|
<el-input-number
|
||||||
type="number"
|
|
||||||
v-model="formData.price"
|
v-model="formData.price"
|
||||||
placeholder="请输入价格"
|
placeholder="请输入价格"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:step="0.1"
|
:step="0.1"
|
||||||
|
class="w-full!"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -149,7 +149,7 @@ const open = async (type: string, id?: number) => {
|
|||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
formData.value = await ProductApi.getProduct(id)
|
formData.value = await ProductApi.getProduct(id)
|
||||||
formData.value.price = fenToYuan(formData.value.price)
|
formData.value.price = Number(fenToYuan(formData.value.price))
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
|
55
src/views/crm/product/detail/ProductDetailsHeader.vue
Normal file
55
src/views/crm/product/detail/ProductDetailsHeader.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<el-col>
|
||||||
|
<el-row>
|
||||||
|
<span class="text-xl font-bold">{{ product.name }}</span>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<!-- 右上:按钮 -->
|
||||||
|
<el-button @click="openForm('update', product.id)" v-hasPermi="['crm:product:update']">
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ContentWrap class="mt-10px">
|
||||||
|
<el-descriptions :column="5" direction="vertical">
|
||||||
|
<el-descriptions-item label="产品类别">
|
||||||
|
{{ productCategoryList?.find((c) => c.id === product.categoryId)?.name }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品单位">
|
||||||
|
<dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="product.unit" />
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品价格">{{ fenToYuan(product.price) }}元</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品编码">{{ product.no }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</ContentWrap>
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<ProductForm ref="formRef" @success="emit('refresh')" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ProductForm from '@/views/crm/product/ProductForm.vue'
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { fenToYuan } from '@/utils'
|
||||||
|
import * as ProductApi from '@/api/crm/product'
|
||||||
|
import * as ProductCategoryApi from '@/api/crm/product/productCategory'
|
||||||
|
|
||||||
|
// 操作修改
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
const { product } = defineProps<{ product: ProductApi.ProductVO }>()
|
||||||
|
const emit = defineEmits(['refresh']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
const productCategoryList = ref([]) // 产品分类树
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
productCategoryList.value = await ProductCategoryApi.getProductCategoryList({})
|
||||||
|
})
|
||||||
|
</script>
|
45
src/views/crm/product/detail/ProductDetailsInfo.vue
Normal file
45
src/views/crm/product/detail/ProductDetailsInfo.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-collapse v-model="activeNames">
|
||||||
|
<el-collapse-item name="basicInfo">
|
||||||
|
<template #title>
|
||||||
|
<span class="text-base font-bold">基本信息</span>
|
||||||
|
</template>
|
||||||
|
<el-descriptions :column="4">
|
||||||
|
<el-descriptions-item label="产品名称">{{ product.name }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品编码">{{ product.no }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="价格">{{ fenToYuan(product.price) }}元</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品描述">{{ product.description }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品类型">
|
||||||
|
{{ productCategoryList?.find((c) => c.id === product.categoryId)?.name }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="是否上下架">
|
||||||
|
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="product.status"/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="单位">
|
||||||
|
<dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="product.unit"/>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {DICT_TYPE} from '@/utils/dict'
|
||||||
|
import * as ProductApi from '@/api/crm/product'
|
||||||
|
import {fenToYuan} from '@/utils'
|
||||||
|
import * as ProductCategoryApi from '@/api/crm/product/productCategory'
|
||||||
|
|
||||||
|
const {product} = defineProps<{
|
||||||
|
product: ProductApi.ProductVO
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 展示的折叠面板
|
||||||
|
const activeNames = ref(['basicInfo'])
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
const productCategoryList = ref([]) // 产品分类树
|
||||||
|
onMounted(async () => {
|
||||||
|
productCategoryList.value = await ProductCategoryApi.getProductCategoryList({})
|
||||||
|
})
|
||||||
|
</script>
|
62
src/views/crm/product/detail/index.vue
Normal file
62
src/views/crm/product/detail/index.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<ProductDetailsHeader :product="product" :loading="loading" @refresh="getProductData(id)" />
|
||||||
|
<el-col>
|
||||||
|
<el-tabs>
|
||||||
|
<el-tab-pane label="详细资料">
|
||||||
|
<ProductDetailsInfo :product="product" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="操作日志">
|
||||||
|
<OperateLogV2 :log-list="logList" />
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
import { OperateLogV2VO } from '@/api/system/operatelog'
|
||||||
|
import * as ProductApi from '@/api/crm/product'
|
||||||
|
import ProductDetailsHeader from '@/views/crm/product/detail/ProductDetailsHeader.vue'
|
||||||
|
import ProductDetailsInfo from '@/views/crm/product/detail/ProductDetailsInfo.vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CrmProductDetail' })
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const id = Number(route.params.id) // 编号
|
||||||
|
const loading = ref(true) // 加载中
|
||||||
|
const product = ref<ProductApi.ProductVO>({} as ProductApi.ProductVO) // 详情
|
||||||
|
|
||||||
|
/** 获取详情 */
|
||||||
|
const getProductData = async (id: number) => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
product.value = await ProductApi.getProduct(id)
|
||||||
|
await getOperateLog(id)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取操作日志 */
|
||||||
|
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
|
||||||
|
const getOperateLog = async (productId: number) => {
|
||||||
|
if (!productId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const data = await ProductApi.getOperateLogPage({
|
||||||
|
bizId: productId
|
||||||
|
})
|
||||||
|
logList.value = data.list
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
const { delView } = useTagsViewStore() // 视图操作
|
||||||
|
const { currentRoute } = useRouter() // 路由
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!id) {
|
||||||
|
ElMessage.warning('参数错误,产品不能为空!')
|
||||||
|
delView(unref(currentRoute))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await getProductData(id)
|
||||||
|
})
|
||||||
|
</script>
|
@ -40,7 +40,8 @@
|
|||||||
:loading="exportLoading"
|
:loading="exportLoading"
|
||||||
v-hasPermi="['crm:product:export']"
|
v-hasPermi="['crm:product:export']"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
<Icon icon="ep:download" class="mr-5px" />
|
||||||
|
导出
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -49,8 +50,14 @@
|
|||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
<el-table-column label="产品名称" align="center" prop="name" />
|
<el-table-column label="产品名称" align="center" prop="name" width="160">
|
||||||
<el-table-column label="产品类型" align="center" prop="categoryName" />
|
<template #default="scope">
|
||||||
|
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
||||||
|
{{ scope.row.name }}
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="产品类型" align="center" prop="categoryName" width="160" />
|
||||||
<el-table-column label="产品单位" align="center" prop="unit">
|
<el-table-column label="产品单位" align="center" prop="unit">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="scope.row.unit" />
|
<dict-tag :type="DICT_TYPE.PRODUCT_UNIT" :value="scope.row.unit" />
|
||||||
@ -62,14 +69,15 @@
|
|||||||
align="center"
|
align="center"
|
||||||
prop="price"
|
prop="price"
|
||||||
:formatter="fenToYuanFormat"
|
:formatter="fenToYuanFormat"
|
||||||
|
width="100"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="产品描述" align="center" prop="description" />
|
<el-table-column label="产品描述" align="center" prop="description" width="150" />
|
||||||
<el-table-column label="是否上下架" align="center" prop="status">
|
<el-table-column label="上架状态" align="center" prop="status" width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="scope.row.status" />
|
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_STATUS" :value="scope.row.status" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="负责人" align="center" prop="ownerUserName" />
|
<el-table-column label="负责人" align="center" prop="ownerUserName" width="120" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="更新时间"
|
label="更新时间"
|
||||||
align="center"
|
align="center"
|
||||||
@ -77,7 +85,7 @@
|
|||||||
:formatter="dateFormatter"
|
:formatter="dateFormatter"
|
||||||
width="180px"
|
width="180px"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="创建" align="center" prop="creatorName" />
|
<el-table-column label="创建人" align="center" prop="creatorName" width="120" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="创建时间"
|
label="创建时间"
|
||||||
align="center"
|
align="center"
|
||||||
@ -85,16 +93,8 @@
|
|||||||
:formatter="dateFormatter"
|
:formatter="dateFormatter"
|
||||||
width="180px"
|
width="180px"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="操作" align="center" width="160">
|
<el-table-column label="操作" align="center" fixed="right" width="160">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
|
||||||
v-hasPermi="['crm:product:query']"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="openDetail(scope.row)"
|
|
||||||
>
|
|
||||||
详情
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -125,8 +125,6 @@
|
|||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ProductForm ref="formRef" @success="getList" />
|
<ProductForm ref="formRef" @success="getList" />
|
||||||
<!-- 表单弹窗:详情 -->
|
|
||||||
<ProductDetail ref="detailRef" @success="getList" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -135,7 +133,6 @@ import { dateFormatter } from '@/utils/formatTime'
|
|||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import * as ProductApi from '@/api/crm/product'
|
import * as ProductApi from '@/api/crm/product'
|
||||||
import ProductForm from './ProductForm.vue'
|
import ProductForm from './ProductForm.vue'
|
||||||
import ProductDetail from './ProductDetail.vue'
|
|
||||||
import { fenToYuanFormat } from '@/utils/formatter'
|
import { fenToYuanFormat } from '@/utils/formatter'
|
||||||
|
|
||||||
defineOptions({ name: 'CrmProduct' })
|
defineOptions({ name: 'CrmProduct' })
|
||||||
@ -184,10 +181,11 @@ const formRef = ref()
|
|||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
/** 详情操作 */
|
|
||||||
const detailRef = ref()
|
/** 打开详情 */
|
||||||
const openDetail = (data: ProductApi.ProductVO) => {
|
const { currentRoute, push } = useRouter()
|
||||||
detailRef.value.open(data)
|
const openDetail = (id: number) => {
|
||||||
|
push({ name: 'CrmProductDetail', params: { id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
@ -218,8 +216,13 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 激活时 */
|
||||||
|
onActivated(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
await getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user