Merge remote-tracking branch 'origin/master'

pull/781/head
苑坤 2025-04-28 17:18:50 +08:00
commit 4aa2b31abb
17 changed files with 156 additions and 96 deletions

View File

@ -4,7 +4,7 @@ NODE_ENV=development
VITE_DEV=true VITE_DEV=true
# 请求路径 # 请求路径
VITE_BASE_URL='http://localhost:48080' VITE_BASE_URL='http://172.22.3.6:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务 # 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=server VITE_UPLOAD_TYPE=server

View File

@ -534,6 +534,28 @@ const remainingRouter: AppRouteRecordRaw[] = [
}, },
component: () => import('@/views/crm/business/detail/index.vue') component: () => import('@/views/crm/business/detail/index.vue')
}, },
{
path: 'business/bill-templete-add',
name: 'BillTempleteAdd',
meta: {
title: '票据模版新增',
noCache: true,
hidden: true,
activeMenu: '/crm/business'
},
component: () => import('@/views/crm/billtemplate/BillTemplateForm.vue')
},
{
path: 'business/bill-templete-edit',
name: 'BillTempleteEdit',
meta: {
title: '票据模版编辑',
noCache: true,
hidden: true,
activeMenu: '/crm/business'
},
component: () => import('@/views/crm/billtemplate/BillTemplateForm.vue')
},
{ {
path: 'business/add', path: 'business/add',
name: 'CrmBusinessAdd', name: 'CrmBusinessAdd',

View File

@ -226,6 +226,9 @@ export enum DICT_TYPE {
CRM_COMPLAINT_METHOD = "crm_complaint_method", // 投诉方式 CRM_COMPLAINT_METHOD = "crm_complaint_method", // 投诉方式
CRM_COMPLAINT_LEVEL = "crm_complaint_level", // 投诉级别 CRM_COMPLAINT_LEVEL = "crm_complaint_level", // 投诉级别
// 商机账期
PAYMENT_TERM = "payment_term", // 投诉级别
// ========== ERP - 企业资源计划模块 ========== // ========== ERP - 企业资源计划模块 ==========
ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态 ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态

View File

@ -1,5 +1,5 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1280"> <ContentWrap class="mt-10px">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
@ -34,20 +34,21 @@
ref="productFormRef" ref="productFormRef"
:products="formData.products" :products="formData.products"
:disabled="disabled" :disabled="disabled"
@success="setList"
/> />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</ContentWrap> </ContentWrap>
</el-form> </el-form>
<template #footer> <div style="text-align: right">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button> <el-button @click="goBack"> </el-button>
</template> </div>
</Dialog> </ContentWrap>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, getStrDictOptions, DICT_TYPE, getBoolDictOptions } from '@/utils/dict' import { getIntDictOptions, getStrDictOptions, DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
import * as BusinessApi from '@/api/crm/business' import { BillTemplateApi, BillTemplateVO } from '@/api/crm/billtemplate'
import * as BusinessStatusApi from '@/api/crm/business/status' import * as BusinessStatusApi from '@/api/crm/business/status'
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
@ -56,6 +57,7 @@ import { useUserStore } from '@/store/modules/user'
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
import BusinessProductForm from './components/index.vue' import BusinessProductForm from './components/index.vue'
import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils' import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils'
const { proxy }: any = getCurrentInstance();
const deptTree = ref() // const deptTree = ref() //
const { t } = useI18n() // const { t } = useI18n() //
@ -88,7 +90,9 @@ const formData = ref({
creditMethod: undefined, creditMethod: undefined,
creditCalcCycle: undefined, creditCalcCycle: undefined,
creditLimit: undefined, creditLimit: undefined,
techSupport: undefined techSupport: undefined,
products: [],
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '票据模版不能为空', trigger: 'blur' }], name: [{ required: true, message: '票据模版不能为空', trigger: 'blur' }],
@ -105,34 +109,18 @@ const customerList = ref([]) // 客户列表的数据
const subTabsName = ref('product') const subTabsName = ref('product')
const productFormRef = ref() const productFormRef = ref()
/** 计算 discountPrice、totalPrice 价格 */ const setList = (newProducts) => {
watch( formData.value.productItems = newProducts;
() => formData.value, };
(val) => {
if (!val) {
return
}
const totalOnlinePrice = val.products.reduce((prev, curr) => prev + curr.onlinePrice, 0)
const totalOfflinePrice = val.products.reduce((prev, curr) => prev + curr.offlinePrice, 0)
//
formData.value.onlinePrice = totalOnlinePrice
formData.value.offlinePrice = totalOfflinePrice
formData.value.totalPrice = totalOnlinePrice + totalOfflinePrice
},
{ deep: true }
)
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number, customerId?: number, contactId?: number) => { const open = async (id?: number, customerId?: number) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// //
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await BusinessApi.getBusiness(id) formData.value = await BillTemplateApi.getBillTemplate(id)
formData.value.products = formData.value.productItems
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
@ -141,22 +129,7 @@ const open = async (type: string, id?: number, customerId?: number, contactId?:
formData.value.customerId = customerId formData.value.customerId = customerId
formData.value.customerDefault = true // formData.value.customerDefault = true //
} }
// contactId
if (contactId) {
formData.value.contactId = contactId
}
}
//
customerList.value = await CustomerApi.getCustomerSimpleList()
//
statusTypeList.value = await BusinessStatusApi.getBusinessStatusTypeSimpleList()
//
userOptions.value = await UserApi.getSimpleUserList()
//
deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
//
if (formType.value === 'create') {
formData.value.ownerUserId = useUserStore().getUser.id
} }
} }
defineExpose({ open }) // open defineExpose({ open }) // open
@ -178,22 +151,29 @@ const submitForm = async () => {
// //
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as BusinessApi.BusinessVO const data = formData.value as unknown as BillTemplateApi.BillTemplateVO
if (formType.value === 'create') { if (!formType.value) {
await BusinessApi.createBusiness(data) await BillTemplateApi.createBillTemplate(data)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
} else { } else {
await BusinessApi.updateBusiness(data) await BillTemplateApi.updateBillTemplate(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false dialogVisible.value = false
// //
emit('success') emit('success')
goBack()
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} }
const { push } = useRouter()
const goBack = ()=> {
push({ name: 'BillTemplate' })
}
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
@ -210,4 +190,12 @@ const resetForm = () => {
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
const route = useRoute();
onMounted(async () => {
const customerId = route.query.customerId;
formType.value = route.query.id;
if (formType.value) open(formType.value, customerId)
});
</script> </script>

View File

@ -10,7 +10,7 @@
> >
<el-table :data="formData" class="-mt-10px"> <el-table :data="formData" class="-mt-10px">
<el-table-column label="序号" type="index" align="center" width="60" /> <el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column label="产品名称" align="center" prop="name" /> <el-table-column label="产品名称" align="center" prop="productName" />
<el-table-column label="产品类型" align="center" prop="category" width="160"> <el-table-column label="产品类型" align="center" prop="category" width="160">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_CATEGORY" :value="scope.row.category" /> <dict-tag :type="DICT_TYPE.CRM_PRODUCT_CATEGORY" :value="scope.row.category" />
@ -23,9 +23,9 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="单位" align="center" prop="unit"> <el-table-column label="单位" align="center" prop="productUnit">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" /> <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.productUnit" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="产品票据" fixed="right" min-width="140"> <el-table-column label="产品票据" fixed="right" min-width="140">
@ -128,28 +128,29 @@ watch(
{ immediate: true } { immediate: true }
) )
/** 监听合同产品变化,计算合同产品总价 */ const emit = defineEmits(['success']) // success
watch(
() => formData.value,
(val) => {
if (!val || val.length === 0) {
return
}
//
val.forEach((item) => {
if (item.offlinePrice != null && item.onlinePrice != null) {
item.totalPrice = item.offlinePrice + item.onlinePrice
} else {
item.totalPrice = 0
}
})
},
{ deep: true }
)
const getList = (val: []) => { const getList = (val: []) => {
formData.value = val console.log('%csrc/views/crm/billtemplate/components/index.vue:133 object', 'color: #007acc;', formData.value);
for(let i = formData.value.length - 1; i >= 0; i--) {
let obj = formData.value[i]
if(!val.some(v => v.id === obj.productId)) formData.value.splice(i, 1)
}
val.forEach(item => {
if(!formData.value.some(v => v.productId === item.id)) {
formData.value.push({
"productId": item.id,
"category": item.category,
"productName": item.name,
"detailType": item.detailType,
"productUnit": item.unit,
"onlinePrice": '',
"offlinePrice": '',
"count": ''
})
}
})
emit('success', formData.value)
} }
// /** */ // /** */

View File

@ -49,7 +49,7 @@
<el-button <el-button
type="primary" type="primary"
plain plain
@click="openForm('create')" @click="openFormAdd"
v-hasPermi="['crm:bill-template:create']" v-hasPermi="['crm:bill-template:create']"
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
@ -90,7 +90,7 @@
<el-button <el-button
link link
type="primary" type="primary"
@click="openForm('update', scope.row.id)" @click="openFormEdit(scope.row)"
v-hasPermi="['crm:bill-template:update']" v-hasPermi="['crm:bill-template:update']"
> >
编辑 编辑
@ -115,8 +115,6 @@
/> />
</ContentWrap> </ContentWrap>
<!-- 表单弹窗添加/修改 -->
<BillTemplateForm ref="formRef" @success="getList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -124,7 +122,6 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { BillTemplateApi, BillTemplateVO } from '@/api/crm/billtemplate' import { BillTemplateApi, BillTemplateVO } from '@/api/crm/billtemplate'
import BillTemplateForm from './BillTemplateForm.vue'
/** 票据模版 列表 */ /** 票据模版 列表 */
defineOptions({ name: 'BillTemplate' }) defineOptions({ name: 'BillTemplate' })
@ -170,9 +167,14 @@ const resetQuery = () => {
} }
/** 添加/修改操作 */ /** 添加/修改操作 */
const { push } = useRouter()
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openFormEdit = (row: Object) => {
formRef.value.open(type, id)
push({ name: 'BillTempleteEdit', query: { id: row.id, customerId: row.customerId } })
}
const openFormAdd = () => {
push({ name: 'BillTempleteAdd' })
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
@ -207,4 +209,7 @@ const handleExport = async () => {
onMounted(() => { onMounted(() => {
getList() getList()
}) })
onActivated(()=>{
getList()
})
</script> </script>

View File

@ -121,9 +121,9 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="账期" prop="paymentTerm"> <el-form-item label="账期" prop="paymentTerm">
<el-select v-model="formData.paymentTerm" placeholder="请选择账期"> <el-select v-model="formData.paymentTerm" @change="changePayment" placeholder="请选择账期">
<el-option <el-option
v-for="dict in getStrDictOptions('payment_term')" v-for="dict in getStrDictOptions(DICT_TYPE.PAYMENT_TERM)"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
@ -145,28 +145,29 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="授信计算周期" prop="creditCalcCycle"> <el-form-item label="授信计算周期" prop="creditCalcCycle">
<el-select v-model="formData.creditCalcCycle" placeholder="请选择授信计算周期"> <!-- <el-select v-model="formData.creditCalcCycle" placeholder="请选择授信计算周期">
<el-option <el-option
v-for="dict in getIntDictOptions('credit_calc_cycle')" v-for="dict in getIntDictOptions('credit_calc_cycle')"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
/> />
</el-select> </el-select> -->
<el-input v-model="formData.creditCalcCycle" disabled placeholder="授信计算周期" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="授信额度" prop="creditLimit"> <el-form-item label="授信额度" prop="creditLimit">
<el-input v-model="formData.creditLimit" placeholder="请输入授信额度" /> <el-input v-model="formData.creditLimit" disabled placeholder="授信额度" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="是否技术需求支持" prop="techSupport"> <el-form-item label="技术需求支持" prop="techSupport">
<el-radio-group v-model="formData.techSupport"> <el-radio-group v-model="formData.techSupport">
<el-radio <el-radio
v-for="dict in getBoolDictOptions('tech_support')" v-for="dict in getBoolDictOptions('tech_support')"
:key="dict.value" :key="dict.value"
:label="dict.value" :value="dict.value"
> >
{{ dict.label }} {{ dict.label }}
</el-radio> </el-radio>
@ -222,8 +223,10 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button> <div style="text-align: right">
<el-button @click="goBack"> </el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="goBack"> </el-button>
</div>
</ContentWrap> </ContentWrap>
</template> </template>
@ -239,6 +242,7 @@ import { useUserStore } from '@/store/modules/user';
import { defaultProps, handleTree } from '@/utils/tree'; import { defaultProps, handleTree } from '@/utils/tree';
import BusinessProductForm from './components/BusinessProductForm.vue'; import BusinessProductForm from './components/BusinessProductForm.vue';
import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils'; import { erpPriceMultiply, erpPriceInputFormatter } from '@/utils';
import { number } from 'vue-types';
const { proxy }: any = getCurrentInstance(); const { proxy }: any = getCurrentInstance();
const { t } = useI18n(); const { t } = useI18n();
@ -316,12 +320,21 @@ watch(
{ deep: true } { deep: true }
); );
watch(
() => formData.value.totalPrice,
(newProducts) => {
console.log('%csrc/views/crm/business/BusinessForm.vue:326 111', 'color: #007acc;', 111);
formData.value.creditLimit = parseInt(newProducts / 365 * formData.value.creditCalcCycle)
},
{ deep: true }
);
const open = async (id?: number, customerId?: number) => { const open = async (id?: number, customerId?: number) => {
if (id) { if (id) {
formLoading.value = true; formLoading.value = true;
try { try {
const data = await BusinessApi.getBusiness(id); const data = await BusinessApi.getBusiness(id);
console.log('%csrc/views/crm/business/BusinessForm.vue:325 data', 'color: #007acc;', data);
formData.value = { formData.value = {
...data, ...data,
products: data.products || [] products: data.products || []
@ -345,6 +358,15 @@ const setList = (newProducts) => {
formData.value.products = newProducts; formData.value.products = newProducts;
}; };
//
const changePayment = (val) => {
const currentDate = new Date(); //
const currentMonth = currentDate.getMonth() + 1; // 01
const daysInMonth = new Date(currentDate.getFullYear(), currentMonth, 0).getDate(); //
formData.value.creditCalcCycle = (val > 2 ? parseInt(daysInMonth + Number(val)) : 0)
formData.value.creditLimit = parseInt(formData.value.totalPrice / 365 * formData.value.creditCalcCycle)
}
const { push } = useRouter() const { push } = useRouter()
const submitForm = async () => { const submitForm = async () => {
@ -392,6 +414,7 @@ const resetForm = () => {
const route = useRoute(); const route = useRoute();
onMounted(async () => { onMounted(async () => {
console.log('%csrc/views/crm/business/BusinessForm.vue:406 getStrDictOptions(DICT_TYPE.PAYMENT_TERM)', 'color: #007acc;', getStrDictOptions(DICT_TYPE.PAYMENT_TERM));
const customerId = route.query.customerId; const customerId = route.query.customerId;
formType.value = route.query.id; formType.value = route.query.id;
if (formType.value) open(formType.value, customerId) if (formType.value) open(formType.value, customerId)

View File

@ -57,8 +57,6 @@
/> />
</ContentWrap> </ContentWrap>
<!-- 表单弹窗添加 -->
<BusinessForm ref="formRef" @success="getList" />
<!-- 关联商机选择弹框 --> <!-- 关联商机选择弹框 -->
<BusinessListModal <BusinessListModal
ref="businessModalRef" ref="businessModalRef"
@ -129,13 +127,13 @@ const handleQuery = () => {
} }
/** 添加操作 */ /** 添加操作 */
const { push } = useRouter()
const formRef = ref() const formRef = ref()
const openForm = () => { const openForm = () => {
formRef.value.open('create', null, props.customerId, props.contactId) push({ name: 'CrmBusinessAdd' })
} }
/** 打开联系人详情 */ /** 打开联系人详情 */
const { push } = useRouter()
const openDetail = (id: number) => { const openDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } }) push({ name: 'CrmBusinessDetail', params: { id } })
} }

View File

@ -272,4 +272,7 @@ const handleExport = async () => {
onMounted(() => { onMounted(() => {
getList() getList()
}) })
onActivated(()=>{
getList()
})
</script> </script>

View File

@ -45,7 +45,9 @@ const getList = async () => {
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>

View File

@ -47,7 +47,9 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>

View File

@ -42,7 +42,9 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>

View File

@ -45,7 +45,9 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>

View File

@ -45,7 +45,9 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>

View File

@ -46,7 +46,9 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>

View File

@ -44,7 +44,9 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>

View File

@ -28,6 +28,7 @@ const total = ref(0) // 列表的总页数
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 999, pageSize: 999,
customerInfoId: ''
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
@ -45,7 +46,9 @@ const getList = async () => {
} }
/** 初始化 **/ /** 初始化 **/
const { params } = useRoute()
onMounted(() => { onMounted(() => {
queryParams.customerInfoId = params.id
getList() getList()
}) })
</script> </script>