Commit e033793a by dhb52

Merge remote-tracking branch 'yudao/dev' into crm-msg

parents 9d9cdf42 ce9317e3
...@@ -32,9 +32,7 @@ export const getModelPage = async (params) => { ...@@ -32,9 +32,7 @@ export const getModelPage = async (params) => {
export const getModel = async (id: number) => { export const getModel = async (id: number) => {
return await request.get({ url: '/bpm/model/get?id=' + id }) return await request.get({ url: '/bpm/model/get?id=' + id })
} }
export const getModelByKey = async (key: string) => {
return await request.get({ url: '/bpm/model/get-by-key?key=' + key })
}
export const updateModel = async (data: ModelVO) => { export const updateModel = async (data: ModelVO) => {
return await request.put({ url: '/bpm/model/update', data: data }) return await request.put({ url: '/bpm/model/update', data: data })
} }
......
import request from '@/config/axios' import request from '@/config/axios'
export interface BiContractRanKingRespVO { export interface BiRankRespVO {
price: number count: number
nickname: string nickname: string
deptName: string deptName: string
} }
export interface BiReceivablesRanKingRespVO {
price: number
nickname: string
deptName: string
}
export interface BiRankReqVO {
deptId: number
type: string
}
// 排行 API // 排行 API
export const RankingStatisticsApi = { export const RankApi = {
// 获得合同排行榜 // 获得合同排行榜
contractAmountRanking: (params: any) => { getContractPriceRank: (params: any) => {
return request.get({ return request.get({
url: '/bi/ranking/contract-ranking', url: '/crm/bi-rank/get-contract-price-rank',
params params
}) })
}, },
// 获得回款排行榜 // 获得回款排行榜
receivablesAmountRanking: (params: any) => { getReceivablePriceRank: (params: any) => {
return request.get({ return request.get({
url: '/bi/ranking/receivables-ranking', url: '/crm/bi-rank/get-receivable-price-rank',
params params
}) })
} }
......
...@@ -73,6 +73,6 @@ export const getBusinessListByIds = async (val: number[]) => { ...@@ -73,6 +73,6 @@ export const getBusinessListByIds = async (val: number[]) => {
} }
// 商机转移 // 商机转移
export const transfer = async (data: TransferReqVO) => { export const transferBusiness = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/business/transfer', data }) return await request.put({ url: '/crm/business/transfer', data })
} }
import request from '@/config/axios' import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/customer'
export interface ClueVO { export interface ClueVO {
id: number id: number
...@@ -44,3 +45,8 @@ export const deleteClue = async (id: number) => { ...@@ -44,3 +45,8 @@ export const deleteClue = async (id: number) => {
export const exportClue = async (params) => { export const exportClue = async (params) => {
return await request.download({ url: `/crm/clue/export-excel`, params }) return await request.download({ url: `/crm/clue/export-excel`, params })
} }
// 线索转移
export const transferClue = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/clue/transfer', data })
}
...@@ -88,6 +88,6 @@ export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => { ...@@ -88,6 +88,6 @@ export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => {
} }
// 联系人转移 // 联系人转移
export const transfer = async (data: TransferReqVO) => { export const transferContact = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/contact/transfer', data }) return await request.put({ url: '/crm/contact/transfer', data })
} }
...@@ -7,6 +7,7 @@ export interface ContractVO { ...@@ -7,6 +7,7 @@ export interface ContractVO {
name: string name: string
customerId: number customerId: number
businessId: number businessId: number
businessName: string
processInstanceId: number processInstanceId: number
orderDate: Date orderDate: Date
ownerUserId: number ownerUserId: number
...@@ -18,8 +19,9 @@ export interface ContractVO { ...@@ -18,8 +19,9 @@ export interface ContractVO {
productPrice: number productPrice: number
contactId: number contactId: number
signUserId: number signUserId: number
signUserName: string
contactLastTime: Date contactLastTime: Date
status: number auditStatus: number
remark: string remark: string
productItems: ProductExpandVO[] productItems: ProductExpandVO[]
creatorName: string creatorName: string
...@@ -66,11 +68,11 @@ export const exportContract = async (params) => { ...@@ -66,11 +68,11 @@ export const exportContract = async (params) => {
} }
// 提交审核 // 提交审核
export const handleApprove = async (id: number) => { export const submitContract = async (id: number) => {
return await request.put({ url: `/crm/contract/approve?id=${id}` }) return await request.put({ url: `/crm/contract/submit?id=${id}` })
} }
// 合同转移 // 合同转移
export const transfer = async (data: TransferReqVO) => { export const transferContract = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/contract/transfer', data }) return await request.put({ url: '/crm/contract/transfer', data })
} }
...@@ -82,7 +82,7 @@ export interface TransferReqVO { ...@@ -82,7 +82,7 @@ export interface TransferReqVO {
} }
// 客户转移 // 客户转移
export const transfer = async (data: TransferReqVO) => { export const transferCustomer = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/customer/transfer', data }) return await request.put({ url: '/crm/customer/transfer', data })
} }
......
import request from '@/config/axios'
// ERP 商品分类 VO
export interface ProductCategoryVO {
// 分类编号
id: number
// 父分类编号
parentId: number
// 分类名称
name: string
// 分类编码
code: string
// 分类排序
sort: number
// 开启状态
status: number
}
// ERP 商品分类 API
export const ProductCategoryApi = {
// 查询ERP 商品分类列表
getProductCategoryList: async (params) => {
return await request.get({ url: `/erp/product-category/list`, params })
},
// 查询ERP 商品分类详情
getProductCategory: async (id: number) => {
return await request.get({ url: `/erp/product-category/get?id=` + id })
},
// 新增ERP 商品分类
createProductCategory: async (data: ProductCategoryVO) => {
return await request.post({ url: `/erp/product-category/create`, data })
},
// 修改ERP 商品分类
updateProductCategory: async (data: ProductCategoryVO) => {
return await request.put({ url: `/erp/product-category/update`, data })
},
// 删除ERP 商品分类
deleteProductCategory: async (id: number) => {
return await request.delete({ url: `/erp/product-category/delete?id=` + id })
},
// 导出ERP 商品分类 Excel
exportProductCategory: async (params) => {
return await request.download({ url: `/erp/product-category/export-excel`, params })
}
}
\ No newline at end of file
import request from '@/config/axios'
// ERP 产品 VO
export interface ProductVO {
// 产品编号
id: number
// 产品名称
name: string
// 产品条码
barCode: string
// 产品类型编号
categoryId: number
// 单位编号
unitId: number
// 产品状态
status: number
// 产品规格
standard: string
// 产品备注
remark: string
// 保质期天数
expiryDay: number
// 基础重量(kg)
weight: number
// 采购价格,单位:元
purchasePrice: number
// 销售价格,单位:元
salePrice: number
// 最低价格,单位:元
minPrice: number
}
// ERP 产品 API
export const ProductApi = {
// 查询ERP 产品分页
getProductPage: async (params: any) => {
return await request.get({ url: `/erp/product/page`, params })
},
// 查询ERP 产品详情
getProduct: async (id: number) => {
return await request.get({ url: `/erp/product/get?id=` + id })
},
// 新增ERP 产品
createProduct: async (data: ProductVO) => {
return await request.post({ url: `/erp/product/create`, data })
},
// 修改ERP 产品
updateProduct: async (data: ProductVO) => {
return await request.put({ url: `/erp/product/update`, data })
},
// 删除ERP 产品
deleteProduct: async (id: number) => {
return await request.delete({ url: `/erp/product/delete?id=` + id })
},
// 导出ERP 产品 Excel
exportProduct: async (params) => {
return await request.download({ url: `/erp/product/export-excel`, params })
}
}
\ No newline at end of file
import request from '@/config/axios'
// ERP 产品单位 VO
export interface ProductUnitVO {
// 单位编号
id: number
// 单位名字
name: string
// 单位状态
status: number
}
// ERP 产品单位 API
export const ProductUnitApi = {
// 查询ERP 产品单位分页
getProductUnitPage: async (params: any) => {
return await request.get({ url: `/erp/product-unit/page`, params })
},
// 查询ERP 产品单位详情
getProductUnit: async (id: number) => {
return await request.get({ url: `/erp/product-unit/get?id=` + id })
},
// 新增ERP 产品单位
createProductUnit: async (data: ProductUnitVO) => {
return await request.post({ url: `/erp/product-unit/create`, data })
},
// 修改ERP 产品单位
updateProductUnit: async (data: ProductUnitVO) => {
return await request.put({ url: `/erp/product-unit/update`, data })
},
// 删除ERP 产品单位
deleteProductUnit: async (id: number) => {
return await request.delete({ url: `/erp/product-unit/delete?id=` + id })
},
// 导出ERP 产品单位 Excel
exportProductUnit: async (params) => {
return await request.download({ url: `/erp/product-unit/export-excel`, params })
}
}
\ No newline at end of file
<!-- 列表选择通用组件,参考 ProductList 组件使用 -->
<template> <template>
<Dialog v-model="dialogVisible" :appendToBody="true" :scroll="true" :title="title" width="60%"> <Dialog v-model="dialogVisible" :appendToBody="true" :scroll="true" :title="title" width="60%">
<el-table <el-table
......
...@@ -10,7 +10,9 @@ interface UserVO { ...@@ -10,7 +10,9 @@ interface UserVO {
id: number id: number
avatar: string avatar: string
nickname: string nickname: string
deptId: number
} }
interface UserInfoVO { interface UserInfoVO {
permissions: string[] permissions: string[]
roles: string[] roles: string[]
...@@ -26,7 +28,8 @@ export const useUserStore = defineStore('admin-user', { ...@@ -26,7 +28,8 @@ export const useUserStore = defineStore('admin-user', {
user: { user: {
id: 0, id: 0,
avatar: '', avatar: '',
nickname: '' nickname: '',
deptId: 0
} }
}), }),
getters: { getters: {
...@@ -73,7 +76,8 @@ export const useUserStore = defineStore('admin-user', { ...@@ -73,7 +76,8 @@ export const useUserStore = defineStore('admin-user', {
this.user = { this.user = {
id: 0, id: 0,
avatar: '', avatar: '',
nickname: '' nickname: '',
deptId: 0
} }
} }
} }
......
...@@ -96,8 +96,8 @@ const leftSides = ref([ ...@@ -96,8 +96,8 @@ const leftSides = ref([
const sideClick = (item) => { const sideClick = (item) => {
leftType.value = item.infoType leftType.value = item.infoType
} }
// TODO @dhb52: 侧边栏样式,在黑暗模式下,颜色会不对。是不是可以读取主题色哈;
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.side-item-list { .side-item-list {
top: 0; top: 0;
......
<!-- 合同金额排行 -->
<template> <template>
<el-card shadow="never">
<!-- 柱状图 --> <!-- 柱状图 -->
<el-skeleton :loading="trendLoading" animated> <el-card shadow="never">
<Echart :height="500" :options="barChartOptions" /> <el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption" />
</el-skeleton> </el-skeleton>
</el-card> </el-card>
<el-card shadow="never" class="mt-16px">
<!-- 排行列表 --> <!-- 排行列表 -->
<el-card shadow="never" class="mt-16px">
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="公司排名" align="center" type="index" width="80" /> <el-table-column label="公司排名" align="center" type="index" width="80" />
<el-table-column label="签订人" align="center" prop="nickname" min-width="200" /> <el-table-column label="签订人" align="center" prop="nickname" min-width="200" />
<el-table-column label="部门" align="center" prop="deptName" min-width="200" /> <el-table-column label="部门" align="center" prop="deptName" min-width="200" />
<el-table-column label="合同金额(元)" align="center" prop="price" min-width="200" /> <el-table-column
label="合同金额(元)"
align="center"
prop="count"
min-width="200"
:formatter="fenToYuanFormat"
/>
</el-table> </el-table>
</el-card> </el-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { RankingStatisticsApi, BiContractRanKingRespVO, BiRankReqVO } from '@/api/crm/bi/ranking' import { RankApi, BiRankRespVO } from '@/api/crm/bi/rank'
import { EChartsOption } from 'echarts' import { EChartsOption } from 'echarts'
import { fenToYuanFormat } from '@/utils/formatter'
import { fenToYuan } from '@/utils'
import { clone } from 'unocss'
/** 合同金额排行 */ defineOptions({ name: 'ContractPriceRank' })
defineOptions({ name: 'RankingContractStatistics' }) const props = defineProps<{ queryParams: any }>() // 搜索参数
const trendLoading = ref(true) // 状态加载中 const loading = ref(false) // 加载中
const loading = ref(false) // 列表的加载中 const list = ref<BiRankRespVO[]>([]) // 列表的数据
const list = ref<BiContractRanKingRespVO[]>([]) // 列表的数据
const params = defineProps<{ queryParams: BiRankReqVO }>() // 搜索参数
/** 柱状图配置 横向 */ /** 柱状图配置横向 */
const barChartOptions = reactive<EChartsOption>({ const echartsOption = reactive<EChartsOption>({
dataset: { dataset: {
dimensions: ['name', 'value'], dimensions: ['nickname', 'count'],
source: [] source: []
}, },
grid: { grid: {
left: 20, left: 20,
right: 20, right: 20,
bottom: 20, bottom: 20,
top: 80,
containLabel: true containLabel: true
}, },
legend: { legend: {
...@@ -46,16 +54,13 @@ const barChartOptions = reactive<EChartsOption>({ ...@@ -46,16 +54,13 @@ const barChartOptions = reactive<EChartsOption>({
series: [ series: [
{ {
name: '合同金额排行', name: '合同金额排行',
type: 'bar', type: 'bar'
smooth: true,
itemStyle: { color: '#B37FEB' }
} }
], ],
toolbox: { toolbox: {
feature: { feature: {
// 数据区域缩放
dataZoom: { dataZoom: {
yAxisIndex: false // Y轴不缩放 yAxisIndex: false // 数据区域缩放:Y 轴不缩放
}, },
brush: { brush: {
type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
...@@ -67,67 +72,36 @@ const barChartOptions = reactive<EChartsOption>({ ...@@ -67,67 +72,36 @@ const barChartOptions = reactive<EChartsOption>({
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'shadow' type: 'shadow'
} },
valueFormatter: fenToYuan
}, },
xAxis: { xAxis: {
type: 'value', type: 'value',
name: '合同金额(元)', name: '合同金额(元)'
nameGap: 30,
nameTextStyle: {
color: '#666',
fontSize: 14
}
}, },
yAxis: { yAxis: {
type: 'category', type: 'category',
name: '签订人', name: '签订人'
nameGap: 30,
nameTextStyle: {
color: '#666',
fontSize: 14
},
axisLabel: {
formatter: (value: string) => {
return value
}
}
} }
}) as EChartsOption }) as EChartsOption
/** 获取合同金额排行 */ /** 获取合同金额排行 */
const getRankingContractStatistics = async () => { const loadData = async () => {
trendLoading.value = true // 1. 加载排行数据
loading.value = true loading.value = true
const rankingList = await RankingStatisticsApi.contractAmountRanking(params.queryParams) const rankingList = await RankApi.getContractPriceRank(props.queryParams)
let source = rankingList.map((item: BiContractRanKingRespVO) => { // 2.1 更新 Echarts 数据
return { if (echartsOption.dataset && echartsOption.dataset['source']) {
name: item.nickname, echartsOption.dataset['source'] = clone(rankingList).reverse()
value: item.price
}
})
// 反转数据源
source = source.reverse()
// 更新 Echarts 数据
if (barChartOptions.dataset && barChartOptions.dataset['source']) {
barChartOptions.dataset['source'] = source
} }
// 更新列表数据 // 2.2 更新列表数据
list.value = rankingList list.value = rankingList
trendLoading.value = false
loading.value = false loading.value = false
} }
defineExpose({ loadData })
/** 重新加载数据 */ /** 初始化 */
const reloadData = async () => {
await getRankingContractStatistics()
}
// 暴露 reloadData 函数
defineExpose({
reloadData
})
onMounted(() => { onMounted(() => {
getRankingContractStatistics() loadData()
}) })
</script> </script>
<style scoped lang="scss"></style>
<!-- 回款金额排行 -->
<template> <template>
<el-card shadow="never">
<!-- 柱状图 --> <!-- 柱状图 -->
<el-skeleton :loading="trendLoading" animated> <el-card shadow="never">
<Echart :height="500" :options="barChartOptions" /> <el-skeleton :loading="loading" animated>
<Echart :height="500" :options="echartsOption" />
</el-skeleton> </el-skeleton>
</el-card> </el-card>
<el-card shadow="never" class="mt-16px">
<!-- 排行列表 --> <!-- 排行列表 -->
<el-card shadow="never" class="mt-16px">
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="公司排名" align="center" type="index" width="80" /> <el-table-column label="公司排名" align="center" type="index" width="80" />
<el-table-column label="签订人" align="center" prop="nickname" min-width="200" /> <el-table-column label="签订人" align="center" prop="nickname" min-width="200" />
<el-table-column label="部门" align="center" prop="deptName" min-width="200" /> <el-table-column label="部门" align="center" prop="deptName" min-width="200" />
<el-table-column label="合同金额(元)" align="center" prop="price" min-width="200" /> <el-table-column
label="回款金额(元)"
align="center"
prop="count"
min-width="200"
:formatter="fenToYuanFormat"
/>
</el-table> </el-table>
</el-card> </el-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { RankingStatisticsApi, BiReceivablesRanKingRespVO } from '@/api/crm/bi/ranking' import { RankApi, BiRankRespVO } from '@/api/crm/bi/rank'
import { EChartsOption } from 'echarts' import { EChartsOption } from 'echarts'
import { fenToYuanFormat } from '@/utils/formatter'
import { fenToYuan } from '@/utils'
import { clone } from 'unocss'
/** 回款金额排行 */ defineOptions({ name: 'ReceivablePriceRank' })
defineOptions({ name: 'RankingReceivablesStatistics' }) const props = defineProps<{ queryParams: any }>() // 搜索参数
const trendLoading = ref(true) // 状态加载中 const loading = ref(false) // 加载中
const loading = ref(false) // 列表的加载中 const list = ref<BiRankRespVO[]>([]) // 列表的数据
const list = ref<BiReceivablesRanKingRespVO[]>([]) // 列表的数据
const params = defineProps<{ queryParams: any }>() // 搜索参数
/** 柱状图配置 横向 */ /** 柱状图配置横向 */
const barChartOptions = reactive<EChartsOption>({ const echartsOption = reactive<EChartsOption>({
dataset: { dataset: {
dimensions: ['name', 'value'], dimensions: ['nickname', 'count'],
source: [] source: []
}, },
grid: { grid: {
left: 20, left: 20,
right: 20, right: 20,
bottom: 20, bottom: 20,
top: 80,
containLabel: true containLabel: true
}, },
legend: { legend: {
...@@ -46,16 +54,13 @@ const barChartOptions = reactive<EChartsOption>({ ...@@ -46,16 +54,13 @@ const barChartOptions = reactive<EChartsOption>({
series: [ series: [
{ {
name: '回款金额排行', name: '回款金额排行',
type: 'bar', type: 'bar'
smooth: true,
itemStyle: { color: '#B37FEB' }
} }
], ],
toolbox: { toolbox: {
feature: { feature: {
// 数据区域缩放
dataZoom: { dataZoom: {
yAxisIndex: false // Y轴不缩放 yAxisIndex: false // 数据区域缩放:Y 轴不缩放
}, },
brush: { brush: {
type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
...@@ -67,67 +72,37 @@ const barChartOptions = reactive<EChartsOption>({ ...@@ -67,67 +72,37 @@ const barChartOptions = reactive<EChartsOption>({
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'shadow' type: 'shadow'
} },
valueFormatter: fenToYuan
}, },
xAxis: { xAxis: {
type: 'value', type: 'value',
name: '回款金额(元)', name: '回款金额(元)'
nameGap: 30,
nameTextStyle: {
color: '#666',
fontSize: 14
}
}, },
yAxis: { yAxis: {
type: 'category', type: 'category',
name: '签订人', name: '签订人',
nameGap: 30, nameGap: 30
nameTextStyle: {
color: '#666',
fontSize: 14
},
axisLabel: {
formatter: (value: string) => {
return value
}
}
} }
}) as EChartsOption }) as EChartsOption
/** 获取回款金额排行 */ /** 获取回款金额排行 */
const getRankingReceivablesStatistics = async () => { const loadData = async () => {
trendLoading.value = true // 1. 加载排行数据
loading.value = true loading.value = true
const rankingList = await RankingStatisticsApi.receivablesAmountRanking(params.queryParams) const rankingList = await RankApi.getReceivablePriceRank(props.queryParams)
let source = rankingList.map((item: BiReceivablesRanKingRespVO) => { // 2.1 更新 Echarts 数据
return { if (echartsOption.dataset && echartsOption.dataset['source']) {
name: item.nickname, echartsOption.dataset['source'] = clone(rankingList).reverse()
value: item.price
}
})
// 反转数据源
source = source.reverse()
// 更新 Echarts 数据
if (barChartOptions.dataset && barChartOptions.dataset['source']) {
barChartOptions.dataset['source'] = source
} }
// 更新列表数据 // 2.2 更新列表数据
list.value = rankingList list.value = rankingList
trendLoading.value = false
loading.value = false loading.value = false
} }
defineExpose({ loadData })
/** 重新加载数据 */ /** 初始化 */
const reloadData = async () => {
await getRankingReceivablesStatistics()
}
// 暴露 reloadData 函数
defineExpose({
reloadData
})
onMounted(() => { onMounted(() => {
getRankingReceivablesStatistics() loadData()
}) })
</script> </script>
<style scoped lang="scss"></style>
<!-- BI 排行版 -->
<template> <template>
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
...@@ -8,15 +9,17 @@ ...@@ -8,15 +9,17 @@
:inline="true" :inline="true"
label-width="68px" label-width="68px"
> >
<el-form-item label="类型" prop="type"> <el-form-item label="时间范围" prop="orderDate">
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable class="!w-240px"> <el-date-picker
<el-option v-model="queryParams.times"
v-for="dict in getIntDictOptions(DICT_TYPE.BI_ANALYZE_TYPE)" :shortcuts="defaultShortcuts"
:key="dict.value" class="!w-240px"
:label="dict.label" end-placeholder="结束日期"
:value="dict.value" start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/> />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="归属部门" prop="deptId"> <el-form-item label="归属部门" prop="deptId">
<el-tree-select <el-tree-select
...@@ -34,47 +37,52 @@ ...@@ -34,47 +37,52 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
<!-- 排行数据 -->
<el-col> <el-col>
<el-tabs v-model="activeTab"> <el-tabs v-model="activeTab">
<el-tab-pane label="合同金额排行" name="contractAmountRanking">
<!-- 合同金额排行 --> <!-- 合同金额排行 -->
<RankingContractStatistics :queryParams="queryParams" ref="rankingContractStatisticsRef" /> <el-tab-pane label="合同金额排行" name="contractPriceRank" lazy>
<ContractPriceRank :query-params="queryParams" ref="contractPriceRankRef" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="回款金额排行" name="receivablesRanKing" lazy>
<!-- 回款金额排行 --> <!-- 回款金额排行 -->
<RankingReceivablesStatistics <el-tab-pane label="回款金额排行" name="receivablePriceRank" lazy>
:queryParams="queryParams" <ReceivablePriceRank :query-params="queryParams" ref="receivablePriceRankRef" />
ref="rankingReceivablesStatisticsRef"
/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-col> </el-col>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import RankingContractStatistics from './components/RankingContractStatistics.vue' import ContractPriceRank from './ContractPriceRank.vue'
import ReceivablePriceRank from './ReceivablePriceRank.vue'
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { beginOfDay, defaultShortcuts, endOfDay, formatDate } from '@/utils/formatTime'
import { useUserStore } from '@/store/modules/user'
/** 排行榜 */ defineOptions({ name: 'CrmBiRank' })
defineOptions({ name: 'RankingStatistics' })
const queryParams = reactive({ const queryParams = reactive({
type: 9, // 将 type 的初始值设置为 9 本年 deptId: useUserStore().getUser.deptId,
deptId: null times: [
// 默认显示最近一周的数据
formatDate(beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7))),
formatDate(endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)))
]
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const deptList = ref<Tree[]>([]) // 树形结构 const deptList = ref<Tree[]>([]) // 树形结构
const activeTab = ref('contractAmountRanking') const activeTab = ref('contractPriceRank')
const rankingContractStatisticsRef = ref() // RankingContractStatistics组件的引用 const contractPriceRankRef = ref() // ContractPriceRank 组件的引用
const rankingReceivablesStatisticsRef = ref() // RankingReceivablesStatistics组件的引用 const receivablePriceRankRef = ref() // ReceivablePriceRank 组件的引用
/** 搜索按钮操作 */ /** 搜索按钮操作 */
const handleQuery = () => { const handleQuery = () => {
if (activeTab.value === 'contractAmountRanking') { if (activeTab.value === 'contractPriceRank') {
rankingContractStatisticsRef.value.reloadData() contractPriceRankRef.value.loadData()
} else if (activeTab.value === 'receivablesRanKing') { } else if (activeTab.value === 'receivablePriceRank') {
rankingReceivablesStatisticsRef.value.reloadData() receivablePriceRankRef.value.loadData()
} }
} }
...@@ -83,6 +91,7 @@ const resetQuery = () => { ...@@ -83,6 +91,7 @@ const resetQuery = () => {
queryFormRef.value.resetFields() queryFormRef.value.resetFields()
handleQuery() handleQuery()
} }
// 加载部门树 // 加载部门树
onMounted(async () => { onMounted(async () => {
deptList.value = handleTree(await DeptApi.getSimpleDeptList()) deptList.value = handleTree(await DeptApi.getSimpleDeptList())
......
...@@ -57,6 +57,7 @@ const message = useMessage() ...@@ -57,6 +57,7 @@ const message = useMessage()
const id = Number(route.params.id) // 联系人编号 const id = Number(route.params.id) // 联系人编号
const loading = ref(true) // 加载中 const loading = ref(true) // 加载中
const contact = ref<ContactApi.ContactVO>({} as ContactApi.ContactVO) // 联系人详情 const contact = ref<ContactApi.ContactVO>({} as ContactApi.ContactVO) // 联系人详情
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
/** 获取详情 */ /** 获取详情 */
const getContactData = async (id: number) => { const getContactData = async (id: number) => {
...@@ -68,22 +69,20 @@ const getContactData = async (id: number) => { ...@@ -68,22 +69,20 @@ const getContactData = async (id: number) => {
loading.value = false loading.value = false
} }
} }
/** 编辑 */ /** 编辑 */
const formRef = ref() 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 crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 联系人转移表单 ref const crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 联系人转移表单 ref
const transfer = () => { const transfer = () => {
crmTransferFormRef.value?.open('联系人转移', contact.value.id, ContactApi.transfer) crmTransferFormRef.value?.open('联系人转移', contact.value.id, ContactApi.transferContact)
} }
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref /** 获取操作日志 */
/**
* 获取操作日志
*/
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表 const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
const getOperateLog = async (contactId: number) => { const getOperateLog = async (contactId: number) => {
if (!contactId) { if (!contactId) {
...@@ -95,9 +94,12 @@ const getOperateLog = async (contactId: number) => { ...@@ -95,9 +94,12 @@ const getOperateLog = async (contactId: number) => {
}) })
logList.value = data.list logList.value = data.list
} }
/** 关闭窗口 */
const close = () => { const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
} }
/** 初始化 */ /** 初始化 */
const { delView } = useTagsViewStore() // 视图操作 const { delView } = useTagsViewStore() // 视图操作
const { currentRoute } = useRouter() // 路由 const { currentRoute } = useRouter() // 路由
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
v-for="item in customerList" v-for="item in customerList"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id!"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
......
<template>
<!-- 弹窗:流程模型图的预览 -->
<Dialog v-model="bpmnDetailVisible" :append-to-body="true" title="流程图" width="800">
<MyProcessViewer
key="designer"
v-model="bpmnXML"
:prefix="bpmnControlForm.prefix"
:value="bpmnXML as any"
v-bind="bpmnControlForm"
/>
</Dialog>
</template>
<script lang="ts" setup>
import * as ModelApi from '@/api/bpm/model'
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
defineOptions({ name: 'BPMLModel' })
/** 流程图的详情按钮操作 */
const bpmnDetailVisible = ref(false)
const bpmnXML = ref(null)
const bpmnControlForm = ref({
prefix: 'flowable'
})
const handleBpmnDetail = async (key: string) => {
const data = await ModelApi.getModelByKey(key)
bpmnXML.value = data.bpmnXml || ''
bpmnDetailVisible.value = true
}
defineExpose({ handleBpmnDetail })
</script>
<!-- 合同 Form 表单下的 Product 列表 -->
<template> <template>
<el-row justify="end"> <el-row justify="end">
<el-button plain type="primary" @click="openForm">添加产品</el-button> <el-button plain type="primary" @click="openForm">添加产品</el-button>
</el-row> </el-row>
<el-table :data="list" :show-overflow-tooltip="true" :stripe="true"> <el-table :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="产品名称" prop="name" width="160" /> <el-table-column align="center" label="产品名称" prop="name" width="120" />
<el-table-column align="center" label="产品类型" prop="categoryName" width="160" />
<el-table-column align="center" label="产品单位" prop="unit">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" />
</template>
</el-table-column>
<el-table-column align="center" label="产品编码" prop="no" />
<el-table-column <el-table-column
:formatter="fenToYuanFormat" :formatter="fenToYuanFormat"
align="center" align="center"
label="价格(元)" label="价格"
prop="price" prop="price"
width="100" width="100"
/> />
<el-table-column align="center" label="数量" prop="count" width="200"> <el-table-column align="center" label="产品类型" prop="categoryName" width="100" />
<el-table-column align="center" label="产品单位" prop="unit">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" />
</template>
</el-table-column>
<el-table-column align="center" label="产品编码" prop="no" />
<el-table-column align="center" fixed="right" label="数量" prop="count" width="100">
<template #default="{ row }: { row: ProductApi.ProductExpandVO }"> <template #default="{ row }: { row: ProductApi.ProductExpandVO }">
<el-input-number v-model="row.count" class="!w-100%" /> <el-input-number
v-model="row.count"
controls-position="right"
:min="0"
:precision="0"
class="!w-100%"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="折扣(%)" prop="discountPercent" width="200"> <el-table-column
align="center"
fixed="right"
label="折扣(%)"
prop="discountPercent"
width="120"
>
<template #default="{ row }: { row: ProductApi.ProductExpandVO }"> <template #default="{ row }: { row: ProductApi.ProductExpandVO }">
<el-input-number v-model="row.discountPercent" class="!w-100%" /> <el-input-number
v-model="row.discountPercent"
controls-position="right"
:min="0"
:max="100"
:precision="0"
class="!w-100%"
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="合计" prop="totalPrice" width="100"> <el-table-column align="center" fixed="right" label="合计" prop="totalPrice" width="100">
<template #default="{ row }: { row: ProductApi.ProductExpandVO }"> <template #default="{ row }: { row: ProductApi.ProductExpandVO }">
{{ getTotalPrice(row) }} {{ fenToYuan(getTotalPrice(row)) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="130"> <el-table-column align="center" fixed="right" label="操作" width="60">
<template #default="scope"> <template #default="scope">
<el-button link type="danger" @click="handleDelete(scope.row.id)"> 移除</el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)"> 移除</el-button>
</template> </template>
...@@ -41,7 +61,7 @@ ...@@ -41,7 +61,7 @@
</el-table> </el-table>
<!-- table 选择表单 --> <!-- table 选择表单 -->
<TableSelectForm ref="tableSelectFormRef" v-model="multipleSelection" title="选择品"> <TableSelectForm ref="tableSelectFormRef" v-model="multipleSelection" title="选择品">
<el-table-column align="center" label="产品名称" prop="name" width="160" /> <el-table-column align="center" label="产品名称" prop="name" width="160" />
<el-table-column align="center" label="产品类型" prop="categoryName" width="160" /> <el-table-column align="center" label="产品类型" prop="categoryName" width="160" />
<el-table-column align="center" label="产品单位" prop="unit"> <el-table-column align="center" label="产品单位" prop="unit">
...@@ -65,29 +85,61 @@ import * as ProductApi from '@/api/crm/product' ...@@ -65,29 +85,61 @@ import * as ProductApi from '@/api/crm/product'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { fenToYuanFormat } from '@/utils/formatter' import { fenToYuanFormat } from '@/utils/formatter'
import { TableSelectForm } from '@/components/Table/index' import { TableSelectForm } from '@/components/Table/index'
import { fenToYuan, floatToFixed2, yuanToFen } from '@/utils'
defineOptions({ name: 'ProductList' }) defineOptions({ name: 'ProductList' })
withDefaults(defineProps<{ modelValue: any[] }>(), { modelValue: () => [] }) const props = withDefaults(defineProps<{ modelValue: ProductApi.ProductExpandVO[] }>(), {
modelValue: () => []
})
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'update:modelValue', v: any[]): void (e: 'update:modelValue', v: any[]): void
}>() }>()
const list = ref<ProductApi.ProductExpandVO[]>([])
const list = ref<ProductApi.ProductExpandVO[]>([]) // 已添加的产品列表
const multipleSelection = ref<ProductApi.ProductExpandVO[]>([]) // 多选
/** 处理删除 */
const handleDelete = (id: number) => { const handleDelete = (id: number) => {
const index = list.value.findIndex((item) => item.id === id) const index = list.value.findIndex((item) => item.id === id)
if (index !== -1) { if (index !== -1) {
list.value.splice(index, 1) list.value.splice(index, 1)
} }
} }
/** 打开 Product 弹窗 */
const tableSelectFormRef = ref<InstanceType<typeof TableSelectForm>>() const tableSelectFormRef = ref<InstanceType<typeof TableSelectForm>>()
const multipleSelection = ref<ProductApi.ProductExpandVO[]>([])
const openForm = () => { const openForm = () => {
tableSelectFormRef.value?.open(ProductApi.getProductPage) tableSelectFormRef.value?.open(ProductApi.getProductPage)
} }
/** 计算 totalPrice */
const getTotalPrice = computed(() => (row: ProductApi.ProductExpandVO) => { const getTotalPrice = computed(() => (row: ProductApi.ProductExpandVO) => {
const totalPrice = (row.price * row.count * row.discountPercent) / 100 const totalPrice =
row.totalPrice = isNaN(totalPrice) ? 0 : totalPrice (Number(row.price) / 100) * Number(row.count) * (1 - Number(row.discountPercent) / 100)
return isNaN(totalPrice) ? 0 : totalPrice row.totalPrice = isNaN(totalPrice) ? 0 : yuanToFen(totalPrice)
return isNaN(totalPrice) ? 0 : totalPrice.toFixed(2)
}) })
/** 编辑时合同产品回显 */
const isSetListValue = ref(false) // 判断是否已经给 list 赋值过,用于编辑表单产品回显
watch(
() => props.modelValue,
(val) => {
if (!val || val.length === 0 || isSetListValue.value) {
return
}
list.value = [
...props.modelValue.map((item) => {
item.totalPrice = floatToFixed2(item.totalPrice) as unknown as number
return item
})
]
isSetListValue.value = true
},
{ immediate: true, deep: true }
)
/** 监听列表变化,动态更新合同产品列表 */
watch( watch(
list, list,
(val) => { (val) => {
...@@ -98,14 +150,21 @@ watch( ...@@ -98,14 +150,21 @@ watch(
}, },
{ deep: true } { deep: true }
) )
// 监听产品选择结果动态添加产品到列表中,如果产品存在则不放入列表中
watch( watch(
multipleSelection, multipleSelection,
(val) => { (val) => {
if (!val || val.length === 0) { if (!val || val.length === 0) {
return return
} }
// 过滤出不在列表中的产品
const ids = list.value.map((item) => item.id) const ids = list.value.map((item) => item.id)
list.value.push(...multipleSelection.value.filter((item) => ids.indexOf(item.id) === -1)) const productList = multipleSelection.value.filter((item) => ids.indexOf(item.id) === -1)
if (!productList || productList.length === 0) {
return
}
list.value.push(...productList)
}, },
{ deep: true } { deep: true }
) )
......
<!-- 合同详情头部组件-->
<template> <template>
<div> <div>
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
...@@ -16,17 +17,20 @@ ...@@ -16,17 +17,20 @@
</div> </div>
<ContentWrap class="mt-10px"> <ContentWrap class="mt-10px">
<el-descriptions :column="5" direction="vertical"> <el-descriptions :column="5" direction="vertical">
<el-descriptions-item label="客户"> <el-descriptions-item label="客户名称">
{{ contract.customerName }} {{ contract.customerName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="客户签约人"> <el-descriptions-item label="合同金额(元)">
{{ contract.contactName }} {{ floatToFixed2(contract.price) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="合同金额"> <el-descriptions-item label="下单时间">
{{ contract.productPrice }} {{ contract.orderDate ? formatDate(contract.orderDate) : '空' }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="创建时间"> <el-descriptions-item label="回款金额(元)">
{{ contract.createTime ? formatDate(contract.createTime) : '空' }} {{ floatToFixed2(contract.price) }}
</el-descriptions-item>
<el-descriptions-item label="负责人">
{{ contract.ownerUserName }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</ContentWrap> </ContentWrap>
...@@ -34,6 +38,7 @@ ...@@ -34,6 +38,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import { floatToFixed2 } from '@/utils'
defineOptions({ name: 'ContractDetailsHeader' }) defineOptions({ name: 'ContractDetailsHeader' })
defineProps<{ contract: ContractApi.ContractVO }>() defineProps<{ contract: ContractApi.ContractVO }>()
......
<!-- 合同详情组件 -->
<template> <template>
<ContentWrap> <ContentWrap>
<el-collapse v-model="activeNames"> <el-collapse v-model="activeNames">
...@@ -5,14 +6,43 @@ ...@@ -5,14 +6,43 @@
<template #title> <template #title>
<span class="text-base font-bold">基本信息</span> <span class="text-base font-bold">基本信息</span>
</template> </template>
<!-- TODO puhui999: 先出详情样式后补全 --> <el-descriptions :column="3">
<el-descriptions :column="4"> <el-descriptions-item label="合同编号">
{{ contract.no }}
</el-descriptions-item>
<el-descriptions-item label="合同名称"> <el-descriptions-item label="合同名称">
{{ contract.name }} {{ contract.name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="客户名称">
{{ contract.customerName }}
</el-descriptions-item>
<el-descriptions-item label="商机名称">
{{ contract.businessName }}
</el-descriptions-item>
<el-descriptions-item label="合同金额(元)">
{{ contract.price }}
</el-descriptions-item>
<el-descriptions-item label="下单时间">
{{ formatDate(contract.orderDate) }}
</el-descriptions-item>
<el-descriptions-item label="开始时间">
{{ formatDate(contract.startTime) }}
</el-descriptions-item>
<el-descriptions-item label="结束时间">
{{ formatDate(contract.endTime) }}
</el-descriptions-item>
<el-descriptions-item label="客户签约人">
{{ contract.contactName }}
</el-descriptions-item>
<el-descriptions-item label="公司签约人">
{{ contract.signUserName }}
</el-descriptions-item>
<el-descriptions-item label="备注"> <el-descriptions-item label="备注">
{{ contract.remark }} {{ contract.remark }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="合同状态">
{{ contract.auditStatus }}
</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item> </el-collapse-item>
<el-collapse-item name="systemInfo"> <el-collapse-item name="systemInfo">
......
<!-- 合同详情:产品列表 -->
<template>
<el-table :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="产品名称" prop="name" width="160" />
<el-table-column align="center" label="产品类型" prop="categoryName" width="160" />
<el-table-column align="center" label="产品单位" prop="unit">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" />
</template>
</el-table-column>
<el-table-column align="center" label="产品编码" prop="no" />
<el-table-column
:formatter="fenToYuanFormat"
align="center"
label="价格(元)"
prop="price"
width="100"
/>
<el-table-column align="center" label="数量" prop="count" width="200" />
<el-table-column align="center" label="折扣(%)" prop="discountPercent" width="200" />
<el-table-column align="center" label="合计" prop="totalPrice" width="100">
<template #default="{ row }: { row: ProductApi.ProductExpandVO }">
{{ getTotalPrice(row) }}
</template>
</el-table-column>
</el-table>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { fenToYuanFormat } from '@/utils/formatter'
import * as ProductApi from '@/api/crm/product'
import { floatToFixed2, yuanToFen } from '@/utils'
defineOptions({ name: 'ContractProductList' })
const props = withDefaults(defineProps<{ modelValue: ProductApi.ProductExpandVO[] }>(), {
modelValue: () => []
})
const list = ref<ProductApi.ProductExpandVO[]>([]) // 产品列表
/** 计算 totalPrice */
const getTotalPrice = computed(() => (row: ProductApi.ProductExpandVO) => {
const totalPrice =
(Number(row.price) / 100) * Number(row.count) * (1 - Number(row.discountPercent) / 100)
row.totalPrice = isNaN(totalPrice) ? 0 : yuanToFen(totalPrice)
return isNaN(totalPrice) ? 0 : totalPrice.toFixed(2)
})
/** 编辑时合同产品回显 */
const isSetListValue = ref(false) // 判断是否已经给 list 赋值过,用于编辑表单产品回显
watch(
() => props.modelValue,
(val) => {
if (!val || val.length === 0 || isSetListValue.value) {
return
}
list.value = [
...props.modelValue.map((item) => {
item.totalPrice = floatToFixed2(item.totalPrice) as unknown as number
return item
})
]
isSetListValue.value = true
},
{ immediate: true, deep: true }
)
</script>
<!-- 合同详情页面组件-->
<template> <template>
<ContractDetailsHeader v-loading="loading" :contract="contract"> <ContractDetailsHeader v-loading="loading" :contract="contract">
<el-button v-if="permissionListRef?.validateWrite" @click="openForm('update', contract.id)"> <el-button v-if="permissionListRef?.validateWrite" @click="openForm('update', contract.id)">
编辑 编辑
</el-button> </el-button>
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer"> <el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transferContract">
转移 转移
</el-button> </el-button>
</ContractDetailsHeader> </ContractDetailsHeader>
<el-col> <el-col>
<el-tabs> <el-tabs>
<el-tab-pane label="详细资料"> <!-- TODO @puhui999:跟进记录 -->
<el-tab-pane label="基本信息">
<ContractDetailsInfo :contract="contract" /> <ContractDetailsInfo :contract="contract" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="操作日志"> <!-- TODO @puhui999:products 更合适哈 -->
<OperateLogV2 :log-list="logList" /> <el-tab-pane label="产品">
<ContractProductList v-model="contract.productItems" />
</el-tab-pane> </el-tab-pane>
<!-- TODO @puhui999:回款信息 -->
<!-- TODO @puhui999:这里是不是不用 isPool 哈 -->
<el-tab-pane label="团队成员"> <el-tab-pane label="团队成员">
<PermissionList <PermissionList
ref="permissionListRef" ref="permissionListRef"
...@@ -24,18 +29,15 @@ ...@@ -24,18 +29,15 @@
@quit-team="close" @quit-team="close"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="商机" lazy> <el-tab-pane label="操作日志">
<BusinessList <OperateLogV2 :log-list="logList" />
:biz-id="contract.id!"
:biz-type="BizTypeEnum.CRM_CONTRACT"
:customer-id="contract.customerId"
/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-col> </el-col>
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<ContractForm ref="formRef" @success="getContractData" /> <ContractForm ref="formRef" @success="getContractData" />
<CrmTransferForm ref="crmTransferFormRef" @success="close" /> <CrmTransferForm ref="transferFormRef" @success="close" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
...@@ -43,12 +45,12 @@ import { OperateLogV2VO } from '@/api/system/operatelog' ...@@ -43,12 +45,12 @@ import { OperateLogV2VO } from '@/api/system/operatelog'
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import ContractDetailsHeader from './ContractDetailsHeader.vue' import ContractDetailsHeader from './ContractDetailsHeader.vue'
import ContractDetailsInfo from './ContractDetailsInfo.vue' import ContractDetailsInfo from './ContractDetailsInfo.vue'
import ContractProductList from './ContractProductList.vue'
import { BizTypeEnum } from '@/api/crm/permission' import { BizTypeEnum } from '@/api/crm/permission'
import { getOperateLogPage } from '@/api/crm/operateLog' import { getOperateLogPage } from '@/api/crm/operateLog'
import ContractForm from '@/views/crm/contract/ContractForm.vue' import ContractForm from '@/views/crm/contract/ContractForm.vue'
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue' import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' import PermissionList from '@/views/crm/permission/components/PermissionList.vue'
import BusinessList from '@/views/crm/business/components/BusinessList.vue'
defineOptions({ name: 'CrmContractDetail' }) defineOptions({ name: 'CrmContractDetail' })
...@@ -57,11 +59,14 @@ const message = useMessage() ...@@ -57,11 +59,14 @@ const message = useMessage()
const contractId = ref(0) // 编号 const contractId = ref(0) // 编号
const loading = ref(true) // 加载中 const loading = ref(true) // 加载中
const contract = ref<ContractApi.ContractVO>({} as ContractApi.ContractVO) // 详情 const contract = ref<ContractApi.ContractVO>({} as ContractApi.ContractVO) // 详情
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
/** 编辑 */ /** 编辑 */
const formRef = ref() 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 getContractData = async () => { const getContractData = async () => {
loading.value = true loading.value = true
...@@ -86,18 +91,21 @@ const getOperateLog = async (contractId: number) => { ...@@ -86,18 +91,21 @@ const getOperateLog = async (contractId: number) => {
logList.value = data.list logList.value = data.list
} }
const crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 合同转移表单 ref /** 转移 */
const transfer = () => { // TODO @puhui999:这个组件,要不传递业务类型,然后组件里判断 title 和 api 能调用哪个;整体治理掉;
crmTransferFormRef.value?.open('合同转移', contract.value.id, ContractApi.transfer) const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 合同转移表单 ref
const transferContract = () => {
transferFormRef.value?.open('合同转移', contract.value.id, ContractApi.transferContract)
} }
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref /** 关闭 */
/** 初始化 */
const { delView } = useTagsViewStore() // 视图操作 const { delView } = useTagsViewStore() // 视图操作
const { currentRoute } = useRouter() // 路由 const { currentRoute } = useRouter() // 路由
const close = () => { const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
} }
/** 初始化 */
onMounted(async () => { onMounted(async () => {
const id = route.params.id const id = route.params.id
if (!id) { if (!id) {
......
...@@ -54,47 +54,72 @@ ...@@ -54,47 +54,72 @@
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<!-- TODO 芋艿:各种字段要调整 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="合同编号" prop="id" /> <el-table-column align="center" fixed="left" label="合同编号" prop="no" width="130" />
<el-table-column align="center" label="合同名称" prop="name" /> <el-table-column align="center" label="合同名称" prop="name" width="130" />
<el-table-column align="center" label="客户名称" prop="customerId" /> <el-table-column align="center" label="客户名称" prop="customerName" width="120">
<el-table-column align="center" label="商机名称" prop="businessId" /> <template #default="scope">
<el-table-column align="center" label="工作流名称" prop="processInstanceId" /> <el-link
:underline="false"
type="primary"
@click="openCustomerDetail(scope.row.customerId)"
>
{{ scope.row.customerName }}
</el-link>
</template>
</el-table-column>
<!-- TODO @puhui999:做了商机详情后,可以把这个超链接加上 -->
<el-table-column align="center" label="商机名称" prop="businessName" width="130" />
<el-table-column <el-table-column
:formatter="dateFormatter"
align="center" align="center"
label="下单时间" label="下单时间"
prop="orderDate" prop="orderDate"
width="180px" width="120"
:formatter="dateFormatter2"
/>
<el-table-column
align="center"
label="合同金额"
prop="price"
width="130"
:formatter="fenToYuanFormat"
/> />
<el-table-column align="center" label="负责人" prop="ownerUserId" />
<el-table-column align="center" label="合同编号" prop="no" />
<el-table-column <el-table-column
:formatter="dateFormatter"
align="center" align="center"
label="开始时间" label="合同开始时间"
prop="startTime" prop="startTime"
width="180px" width="120"
:formatter="dateFormatter2"
/> />
<el-table-column <el-table-column
:formatter="dateFormatter"
align="center" align="center"
label="结束时间" label="合同结束时间"
prop="endTime" prop="endTime"
width="180px" width="120"
:formatter="dateFormatter2"
/> />
<el-table-column align="center" label="合同金额" prop="price" /> <el-table-column align="center" label="客户签约人" prop="contactName" width="130">
<el-table-column align="center" label="整单折扣" prop="discountPercent" /> <template #default="scope">
<el-table-column align="center" label="产品总金额" prop="productPrice" /> <el-link
<el-table-column align="center" label="联系人" prop="contactId" /> :underline="false"
<el-table-column align="center" label="公司签约人" prop="signUserId" /> type="primary"
@click="openContactDetail(scope.row.contactId)"
>
{{ scope.row.contactName }}
</el-link>
</template>
</el-table-column>
<el-table-column align="center" label="公司签约人" prop="signUserName" width="130" />
<el-table-column align="center" label="备注" prop="remark" width="130" />
<!-- TODO @puhui999:后续可加 【已收款金额】、【未收款金额】 -->
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" />
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
label="最后跟进时间" label="更新时间"
prop="contactLastTime" prop="updateTime"
width="180px" width="180px"
/> />
<el-table-column <el-table-column
...@@ -104,7 +129,11 @@ ...@@ -104,7 +129,11 @@
prop="createTime" prop="createTime"
width="180px" width="180px"
/> />
<el-table-column align="center" label="备注" prop="remark" /> <el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="250"> <el-table-column fixed="right" label="操作" width="250">
<template #default="scope"> <template #default="scope">
<el-button <el-button
...@@ -115,11 +144,12 @@ ...@@ -115,11 +144,12 @@
> >
编辑 编辑
</el-button> </el-button>
<!-- TODO @puhui999:可以加下判断,什么情况下,可以审批;然后加个【查看审批】按钮 -->
<el-button <el-button
v-hasPermi="['crm:contract:update']" v-hasPermi="['crm:contract:update']"
link link
type="primary" type="primary"
@click="handleApprove(scope.row)" @click="handleSubmit(scope.row)"
> >
提交审核 提交审核
</el-button> </el-button>
...@@ -155,10 +185,12 @@ ...@@ -155,10 +185,12 @@
<ContractForm ref="formRef" @success="getList" /> <ContractForm ref="formRef" @success="getList" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import ContractForm from './ContractForm.vue' import ContractForm from './ContractForm.vue'
import { fenToYuanFormat } from '@/utils/formatter'
import { DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'CrmContract' }) defineOptions({ name: 'CrmContract' })
...@@ -241,16 +273,29 @@ const handleExport = async () => { ...@@ -241,16 +273,29 @@ const handleExport = async () => {
} }
/** 提交审核 **/ /** 提交审核 **/
const handleApprove = async (row: ContractApi.ContractVO) => { const handleSubmit = async (row: ContractApi.ContractVO) => {
await message.confirm(`您确定提交【${row.name}】审核吗?`) await message.confirm(`您确定提交【${row.name}】审核吗?`)
await ContractApi.handleApprove(row.id) await ContractApi.submitContract(row.id)
message.success('提交审核成功!') message.success('提交审核成功!')
await getList() await getList()
} }
/** 打开合同详情 */
const { push } = useRouter() const { push } = useRouter()
const openDetail = (id: number) => { const openDetail = (id: number) => {
push({ name: 'CrmContractDetail', params: { id } }) push({ name: 'CrmContractDetail', params: { id } })
} }
/** 打开客户详情 */
const openCustomerDetail = (id: number) => {
push({ name: 'CrmCustomerDetail', params: { id } })
}
/** 打开联系人详情 */
const openContactDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } })
}
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()
......
<!-- TODO @puhui999:这个好像和 detail 重复了???能不能复用 detail 哈? -->
<template> <template>
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="110px"> <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="110px">
<el-row> <el-row>
......
<!-- 客户导入窗口 -->
<template> <template>
<Dialog v-model="dialogVisible" title="客户导入" width="400"> <Dialog v-model="dialogVisible" title="客户导入" width="400">
<el-upload <el-upload
...@@ -20,7 +21,7 @@ ...@@ -20,7 +21,7 @@
<div class="el-upload__tip text-center"> <div class="el-upload__tip text-center">
<div class="el-upload__tip"> <div class="el-upload__tip">
<el-checkbox v-model="updateSupport" /> <el-checkbox v-model="updateSupport" />
是否更新已经存在的客户数据 是否更新已经存在的客户数据(“客户名称”重复)
</div> </div>
<span>仅允许导入 xls、xlsx 格式文件。</span> <span>仅允许导入 xls、xlsx 格式文件。</span>
<el-link <el-link
......
...@@ -124,7 +124,7 @@ const openForm = () => { ...@@ -124,7 +124,7 @@ const openForm = () => {
/** 客户转移 */ /** 客户转移 */
const crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 客户转移表单 ref const crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 客户转移表单 ref
const transfer = () => { const transfer = () => {
crmTransferFormRef.value?.open('客户转移', customerId.value, CustomerApi.transfer) crmTransferFormRef.value?.open('客户转移', customerId.value, CustomerApi.transferCustomer)
} }
/** 锁定客户 */ /** 锁定客户 */
......
...@@ -208,7 +208,7 @@ ...@@ -208,7 +208,7 @@
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<CustomerForm ref="formRef" @success="getList" /> <CustomerForm ref="formRef" @success="getList" />
<CustomerImportForm ref="customerImportFormRef" @success="getList" /> <CustomerImportForm ref="importFormRef" @success="getList" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
...@@ -339,10 +339,13 @@ const handleDelete = async (id: number) => { ...@@ -339,10 +339,13 @@ const handleDelete = async (id: number) => {
await getList() await getList()
} catch {} } catch {}
} }
const customerImportFormRef = ref<InstanceType<typeof CustomerImportForm>>()
/** 导入按钮操作 */
const importFormRef = ref<InstanceType<typeof CustomerImportForm>>()
const handleImport = () => { const handleImport = () => {
customerImportFormRef.value?.open() importFormRef.value?.open()
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
const handleExport = async () => { const handleExport = async () => {
try { try {
......
<!-- ERP 产品的新增/修改 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="条码" prop="barCode">
<el-input v-model="formData.barCode" placeholder="请输入条码" />
</el-form-item>
<el-form-item label="分类编号" prop="categoryId">
<el-input v-model="formData.categoryId" placeholder="请输入分类编号" />
</el-form-item>
<el-form-item label="单位编号" prop="unitId">
<el-input v-model="formData.unitId" placeholder="请输入单位编号" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="规格" prop="standard">
<el-input v-model="formData.standard" placeholder="请输入规格" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="保质期天数" prop="expiryDay">
<el-input v-model="formData.expiryDay" placeholder="请输入保质期天数" />
</el-form-item>
<el-form-item label="基础重量(kg)" prop="weight">
<el-input v-model="formData.weight" placeholder="请输入基础重量(kg)" />
</el-form-item>
<el-form-item label="采购价格,单位:元" prop="purchasePrice">
<el-input v-model="formData.purchasePrice" placeholder="请输入采购价格,单位:元" />
</el-form-item>
<el-form-item label="销售价格,单位:元" prop="salePrice">
<el-input v-model="formData.salePrice" placeholder="请输入销售价格,单位:元" />
</el-form-item>
<el-form-item label="最低价格,单位:元" prop="minPrice">
<el-input v-model="formData.minPrice" placeholder="请输入最低价格,单位:元" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ProductApi } from '@/api/erp/product'
/** ERP 产品 表单 */
defineOptions({ name: 'ProductForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: undefined,
barCode: undefined,
categoryId: undefined,
unitId: undefined,
status: undefined,
standard: undefined,
remark: undefined,
expiryDay: undefined,
weight: undefined,
purchasePrice: undefined,
salePrice: undefined,
minPrice: undefined
})
const formRules = reactive({
name: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
barCode: [{ required: true, message: '产品条码不能为空', trigger: 'blur' }],
categoryId: [{ required: true, message: '产品分类编号不能为空', trigger: 'blur' }],
unitId: [{ required: true, message: '单位编号不能为空', trigger: 'blur' }],
status: [{ required: true, message: '产品状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ProductApi.getProduct(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as ProductApi.ProductVO
if (formType.value === 'create') {
await ProductApi.createProduct(data)
message.success(t('common.createSuccess'))
} else {
await ProductApi.updateProduct(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
barCode: undefined,
categoryId: undefined,
unitId: undefined,
status: undefined,
standard: undefined,
remark: undefined,
expiryDay: undefined,
weight: undefined,
purchasePrice: undefined,
salePrice: undefined,
minPrice: undefined
}
formRef.value?.resetFields()
}
</script>
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="上级编号" prop="parentId">
<el-tree-select
v-model="formData.parentId"
:data="productCategoryTree"
:props="defaultProps"
check-strictly
default-expand-all
placeholder="请选择上级编号"
/>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入编码" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" placeholder="请输入排序" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { ProductCategoryApi } from '@/api/erp/product/category'
import { defaultProps, handleTree } from '@/utils/tree'
import { CommonStatusEnum } from '@/utils/constants'
/** ERP 产品分类 表单 */
defineOptions({ name: 'ProductCategoryForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
parentId: undefined,
name: undefined,
code: undefined,
sort: undefined,
status: undefined
})
const formRules = reactive({
parentId: [{ required: true, message: '上级编号不能为空', trigger: 'blur' }],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
code: [{ required: true, message: '编码不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const productCategoryTree = ref() // 树形结构
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ProductCategoryApi.getProductCategory(id)
} finally {
formLoading.value = false
}
}
await getProductCategoryTree()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as ProductCategoryApi.ProductCategoryVO
if (formType.value === 'create') {
await ProductCategoryApi.createProductCategory(data)
message.success(t('common.createSuccess'))
} else {
await ProductCategoryApi.updateProductCategory(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
parentId: undefined,
name: undefined,
code: undefined,
sort: undefined,
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
/** 获得产品分类树 */
const getProductCategoryTree = async () => {
productCategoryTree.value = []
const data = await ProductCategoryApi.getProductCategoryList()
const root: Tree = { id: 0, name: '顶级产品分类', children: [] }
root.children = handleTree(data, 'id', 'parentId')
productCategoryTree.value.push(root)
}
</script>
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="分类名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入分类名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择开启状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:product-category:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:product-category:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
<el-button type="danger" plain @click="toggleExpandAll">
<Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
:default-expand-all="isExpandAll"
v-if="refreshTable"
>
<el-table-column label="编码" align="center" prop="code" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="排序" align="center" prop="sort" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:product-category:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:product-category:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ProductCategoryForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { handleTree } from '@/utils/tree'
import download from '@/utils/download'
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
import ProductCategoryForm from './ProductCategoryForm.vue'
/** ERP 产品分类 列表 */
defineOptions({ name: 'ErpProductCategory' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<ProductCategoryVO[]>([]) // 列表的数据
const queryParams = reactive({
parentId: undefined,
name: undefined,
code: undefined,
sort: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProductCategoryApi.getProductCategoryList(queryParams)
list.value = handleTree(data, 'id', 'parentId')
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProductCategoryApi.deleteProductCategory(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ProductCategoryApi.exportProductCategory(queryParams)
download.excel(data, '产品分类.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 展开/折叠操作 */
const isExpandAll = ref(true) // 是否展开,默认全部展开
const refreshTable = ref(true) // 重新渲染表格状态
const toggleExpandAll = async () => {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
await nextTick()
refreshTable.value = true
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<!-- ERP 产品列表 -->
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="产品名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入产品名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="产品分类" prop="categoryId">
<el-input
v-model="queryParams.categoryId"
placeholder="请输入产品分类"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:product:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:product:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="条码" align="center" prop="barCode" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="规格" align="center" prop="standard" />
<!-- TODO 芋艿:待实现 -->
<el-table-column label="分类" align="center" prop="categoryId" />
<!-- TODO 芋艿:待实现 -->
<el-table-column label="单位" align="center" prop="unitId" />
<el-table-column label="采购价格" align="center" prop="purchasePrice" />
<el-table-column label="销售价格" align="center" prop="salePrice" />
<el-table-column label="最低价格" align="center" prop="minPrice" />
<!-- TODO 芋艿:待实现 -->
<el-table-column label="产品状态" align="center" prop="status" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" width="110">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:product:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:product:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ProductForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ProductApi, ProductVO } from '@/api/erp/product'
import ProductForm from './ProductForm.vue'
/** ERP 产品列表 */
defineOptions({ name: 'ErpProduct' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<ProductVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
categoryId: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProductApi.getProductPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProductApi.deleteProduct(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ProductApi.exportProduct(queryParams)
download.excel(data, '产品.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="单位名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入单位名字" />
</el-form-item>
<el-form-item label="单位状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ProductUnitApi } from '@/api/erp/product/unit'
/** ERP 产品单位表单 */
defineOptions({ name: 'ProductUnitForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: undefined,
status: undefined
})
const formRules = reactive({
name: [{ required: true, message: '单位名字不能为空', trigger: 'blur' }],
status: [{ required: true, message: '单位状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ProductUnitApi.getProductUnit(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as ProductUnitApi.ProductUnitVO
if (formType.value === 'create') {
await ProductUnitApi.createProductUnit(data)
message.success(t('common.createSuccess'))
} else {
await ProductUnitApi.updateProductUnit(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
status: undefined
}
formRef.value?.resetFields()
}
</script>
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="单位名字" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入单位名字"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="单位状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择单位状态"
clearable
class="!w-240px"
>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:product-unit:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:product-unit:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="单位编号" align="center" prop="id" />
<el-table-column label="单位名字" align="center" prop="name" />
<el-table-column label="单位状态" align="center" prop="status" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:product-unit:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:product-unit:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ProductUnitForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import ProductUnitForm from './ProductUnitForm.vue'
/** ERP 产品单位列表 */
defineOptions({ name: 'ErpProductUnit' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<ProductUnitVO[]>([]) // 列表的数据
// 列表的总页数
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProductUnitApi.getProductUnitPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProductUnitApi.deleteProductUnit(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ProductUnitApi.exportProductUnit(queryParams)
download.excel(data, 'ERP 产品单位.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment