Преглед изворни кода

提交销售订单相关代码

Lijy пре 11 месеци
родитељ
комит
5dd1298eba

+ 13 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/api.ts

@@ -14,6 +14,13 @@ export const EXPORTTIME = `${MOD}/exportData`
 export const IMPORITEM = `${MOD}/importData`
 export const URL_BATCHDELETE = `${MOD}/batchDeleteOrder`
 export const URL_RECOVER = `${MOD}/recover`
+export const URL_DETEALORDER = `${MOD}/getDetail`
+export const URL_TRANSFER = `${MOD}/transfer`
+export const URL_FERDETAILTASK = `${MOD}/taskWithOrder`
+export const URL_PAYLIST = `${MOD}/paymentCollectionList`
+export const URL_ADDREBATE = `${MOD}/paymentCollection`
+export const URL_EDITEBATE = `${MOD}/editPayment`
+export const URL_DETELEITEMS = `${MOD}/deletePayment`
 
 export const tableColumns: TableColumn[] = [
     { prop: 'orderCode', label: '订单编号', event: 'toDetali', width: '150' },
@@ -33,4 +40,10 @@ export const tableColumns: TableColumn[] = [
     { prop: 'inchargerName', label: '负责人', width: '200' },
     { prop: 'creatorName', label: '创建人', width: '200' },
     { prop: 'createTime', label: '创建时间', width: '200' },
+]
+
+export const paymentStatus = [
+    { value: 0, label: '未回款' },
+    { value: 1, label: '已回款' },
+    { value: 2, label: '完全回款' },
 ]

+ 87 - 70
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/component/attachment.vue

@@ -4,141 +4,158 @@
             <div class="title">附件</div>
             <div>
                 <el-upload ref="uploadRef" :http-request="httpUploadFile" :limit="1" :show-file-list="false"
-                    element-loading-text="正在上传" :loading="allLoading.uploadFileLoading">
+                    element-loading-text="正在上传">
                     <template #trigger>
-                        <el-button type="primary">上传</el-button>
+                        <el-button type="primary" :loading="allLoading.uploadLoading">上传</el-button>
                     </template>
                 </el-upload>
             </div>
         </div>
         <div class="flex-1 overflow-auto pt-3">
             <el-table :data="attachmenttable" border style="width: 100%;height: 200px;">
-                <el-table-column prop="documentName" label="附件名称" width="180" />
+                <el-table-column prop="attachmentName" label="附件名称" width="180" />
                 <el-table-column prop="size" label="附件大小" width="120" />
                 <el-table-column prop="creatorName" label="上传人" width="120" />
                 <el-table-column prop="indate" label="上传时间" width="180" />
                 <el-table-column label="操作" width="180" fixed="right">
                     <template #default="scope">
                         <el-button link type="primary" size="large" @click="fileDownload(scope.row)">下载</el-button>
-                        <el-button link type="primary" size="large" @click="operation(scope.row)">重命名</el-button>
-                        <el-button link type="danger" size="large" @click="fileDetele(scope.row)">删除</el-button>
+                        <el-button link type="primary" size="large" @click="showVisible(scope.row)">重命名</el-button>
+                        <el-button link type="danger" size="large" @click="deteleFile(scope.row)">删除</el-button>
                     </template>
                 </el-table-column>
             </el-table>
         </div>
 
         <!-- 弹窗 -->
-        <el-dialog v-model="allVisible.renameDialogVisible" width="800" :show-close="false" top="10vh">
+        <el-dialog v-model="allVisible.renameVisible" width="800" :show-close="false" top="10vh">
             <template #header="{ close, titleId, titleClass }">
                 <div class="flex justify-between items-center border-b pb-3 dialog-header">
                     <h4 :id="titleId">{{ '文件重命名' }}</h4>
                     <div>
-                        <el-button type="primary" @click="saveEditClue()" :loading="allLoading.saveLoading">保存</el-button>
-                        <el-button @click="allVisible.renameDialogVisible = false">取消</el-button>
+                        <el-button type="primary" :loading="allLoading.renameLoading" :disabled="!renameVal"
+                            @click="saveEditClue()">保存</el-button>
+                        <el-button @click="allVisible.renameVisible = false">取消</el-button>
                     </div>
                 </div>
             </template>
             <div class="pt-3">
-                <el-input v-model.trim="fileFormVal.name" style="width: 100%" class="pb-3" clearable />
+                <el-input v-model.trim="renameVal" style="width: 100%" class="pb-3" clearable />
             </div>
         </el-dialog>
     </div>
 </template>
 <script lang="ts" setup>
-import { post, uploadFile } from '@/utils/request';
+import { get, post, uploadFile } from '@/utils/request';
 import { UploadRequestOptions } from 'element-plus';
-import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffect } from 'vue'
-import { URLFILEDETELE, URL_REFNAME, URL_UPLOADFILE } from '@/pages/api';
-import { downloadFile } from '@/utils/tools';
+import { ref, reactive, onMounted, onUnmounted, defineEmits, inject, watchEffect } from 'vue'
+import { confirmAction, downloadFile } from '@/utils/tools';
+import { FILEDETELE, FILERENAME, UPLOADATTACHMENT } from '@/pages/product/api';
+import { IMPORTMOD } from '../api';
 
 const globalPopup = inject<GlobalPopup>('globalPopup')
 const emits = defineEmits(['refreshData']);
-const props = defineProps<{
-    data?: any
-}>()
-
-type fileFormVal = {
-    id?: string,
-    name?: string
-}
 
 const uploadRef = ref<any>()
-const information = ref<any>({})
 const attachmenttable = ref([])
-const fileTypeStr = ref('') // 文件重命名的类型
-const fileFormVal = ref<fileFormVal>({})
+const information: any = ref({})
+const renameItem: any = ref({})
+const renameVal = ref('')
 const allLoading = reactive({
-    uploadFileLoading: false,
-    saveLoading: false
+    uploadLoading: false,
+    renameLoading: false,
 })
 const allVisible = reactive({
-    renameDialogVisible: false
+    renameVisible: false
 })
 
+const props = defineProps<{
+    data: any,
+    information: any
+}>()
+
+// 下载文件
+function fileDownload(item: any) {
+    downloadFile(item.url, `${item.attachmentName}${item.attachmentSuffix}`)
+}
+
+// 保存重命名
 function saveEditClue() {
-    if(!fileFormVal.value.name) {
-        globalPopup?.showWarning('请输入文件名称')
-        return
-    }
-    allLoading.saveLoading = true
-    post(URL_REFNAME, {
-        fileId: fileFormVal.value.id,
-        newName: fileFormVal.value.name + '.' + fileTypeStr.value
-    }).then(() => {
-        allVisible.renameDialogVisible = false
-        globalPopup?.showSuccess('重命名成功')
-        emits('refreshData');
+    const id = renameItem.value.id
+    allLoading.renameLoading = true
+    post(FILERENAME, { name: renameVal.value, id }).then((res) => {
+        if (res.code == 'ok') {
+            allVisible.renameVisible = false
+            globalPopup?.showSuccess(res.msg || '')
+            emits('refreshData', 'getFileList')
+        }
+    }).catch((err) => {
+        globalPopup?.showError(err.msg || '')
     }).finally(() => {
-        allLoading.saveLoading = false
+        allLoading.renameLoading = false
     })
 }
 
-function operation(item: any) {
-    fileTypeStr.value = item.documentName.split('.').pop()
-    fileFormVal.value = {
-        id: item.id,
-        name: item.documentName.replace(/\.[^/.]+$/, '')
-    }
-    allVisible.renameDialogVisible = true
-}
-
-function fileDownload(item: any) {
-    downloadFile(`${item.url}`, item.documentName)
+// 删除文件
+function deteleFile(item: any) {
+    const id = item.id
+    confirmAction(`确定删除【${item.attachmentName}】文件吗?`).then(() => {
+        post(FILEDETELE, { id }).then((_res) => {
+            globalPopup?.showSuccess('删除成功')
+            emits('refreshData', 'getFileList')
+        }).catch((err) => {
+            globalPopup?.showError(err.msg || '')
+        })
+    })
 }
 
-function fileDetele(item: any) {
-    post(URLFILEDETELE, { fileIds: item.id }).then(() => {
-        globalPopup?.showSuccess('删除成功')
-        emits('refreshData');
-    })
+// 显示弹窗
+function showVisible(item: any) {
+    renameItem.value = JSON.parse(JSON.stringify(item))
+    renameVal.value = renameItem.value.attachmentName
+    allVisible.renameVisible = true
 }
 
 // 上传附件
 async function httpUploadFile(param: UploadRequestOptions) {
+    allLoading.uploadLoading = true
     const id = information.value.id
     const formData = new FormData();
     formData.append('file', param.file)
-    formData.append('contactsId', id)
-    allLoading.uploadFileLoading = true
-    const res = await uploadFile(URL_UPLOADFILE, formData).finally(() => {
-        allLoading.uploadFileLoading = false
-        uploadRef.value.clearFiles()
-    })
+    formData.append('moduleId', id)
+    formData.append('moduleCode', IMPORTMOD)
+    const res = await uploadFile(UPLOADATTACHMENT, formData)
+    allLoading.uploadLoading = false
+    uploadRef.value.clearFiles()
     if (res.code == 'ok') {
-        globalPopup?.showSuccess(res.msg || '')
-        emits('refreshData');
+        globalPopup?.showSuccess('上传成功')
+        emits('refreshData', 'getFileList')
         return
     }
     globalPopup?.showError(res.msg || '')
     return res
 }
 
+// 接收参数赋值
+function receiveAssignment(item: any) {
+    attachmenttable.value = item.data
+    information.value = item.information
+}
+
 watchEffect(() => {
-    const { data } = props
-    // information.value = data
-    // attachmenttable.value = data.contactsDocumentList || []
-    information.value = {}
-    attachmenttable.value = []
+    receiveAssignment(props)
+});
+
+// 生命周期钩子
+onMounted(() => {
+    receiveAssignment(props)
 });
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.attachment {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 148 - 40
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/component/information.vue

@@ -3,9 +3,8 @@
         <div class="flex justify-between">
             <div class="title">基本信息</div>
             <div>
-                <el-button type="primary">认领</el-button>
-                <el-button type="primary">转移</el-button>
-                <el-button type="primary">编辑</el-button>
+                <el-button type="primary" @click="transferCli()">转移</el-button>
+                <el-button type="primary" @click="editInfo(info)">编辑</el-button>
             </div>
         </div>
         <div class="form flex flex-wrap justify-between">
@@ -15,30 +14,13 @@
             </div>
         </div>
 
-        <el-dialog v-model="allVisible.editContactsVisible" width="1000" :show-close="false" top="10vh">
-            <template #header="{ close, titleId, titleClass }">
-                <div class="flex justify-between items-center border-b pb-3 dialog-header">
-                    <h4 :id="titleId">编辑联系人</h4>
-                    <div>
-                        <el-button type="primary" :loading="allLoading.editContactsSaveLoading">保存</el-button>
-                        <el-button @click="closeVisible('editContactsVisible')">取消</el-button>
-                    </div>
-                </div>
-            </template>
-            <div class="h-[60vh] overflow-y-auto scroll-bar pt-3">
-                <div class="ml-4 mr-4">
-                    <GenerateForm ref="contactsTemplateRef" :data="contactsTemplate" :value="contactsTemplateValue"
-                        :key="contactsTemplateRefKey" v-loading="allLoading.contactsTemplateRefLoading" />
-                </div>
-            </div>
-        </el-dialog>
-
         <el-dialog v-model="allVisible.transferVisible" width="600" :show-close="false" top="10vh">
             <template #header="{ close, titleId, titleClass }">
                 <div class="flex justify-between items-center border-b pb-3 dialog-header">
                     <h4 :id="titleId">{{ allText.operationText }}</h4>
                     <div>
-                        <el-button type="primary" :loading="allLoading.transferLoading">转移</el-button>
+                        <el-button type="primary" :loading="allLoading.transferLoading"
+                            @click="transferChange()">转移</el-button>
                         <el-button @click="allVisible.transferVisible = false">取消</el-button>
                     </div>
                 </div>
@@ -46,7 +28,7 @@
             <div class="scroll-bar m-6">
                 <div class="flex mb-4">
                     <div class="w-20 flex items-center justify-end pr-4">转移至:</div>
-                    <el-select v-model="transferValue" placeholder="请选择" class="flex1">
+                    <el-select v-model.trim="transferValue" placeholder="请选择" class="flex1">
                         <el-option v-for="item in transferOptions" :key="item.value" :label="item.label"
                             :value="item.value" />
                     </el-select>
@@ -54,6 +36,25 @@
                 <div class="pl-3 text-[#e94a4a]">转移后,将看不到此联系人</div>
             </div>
         </el-dialog>
+
+        <el-dialog v-model="allVisible.editOrderVisible" width="1000" :show-close="false" top="10vh">
+            <template #header="{ close, titleId, titleClass }">
+                <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                    <h4 :id="titleId">编辑销售订单</h4>
+                    <div>
+                        <el-button type="primary" @click="editOrderSave()"
+                            :loading="allLoading.editOrderSaveLoading">保存</el-button>
+                        <el-button @click="closeVisible('editOrderVisible')">取消</el-button>
+                    </div>
+                </div>
+            </template>
+            <div class="h-[60vh] overflow-y-auto scroll-bar pt-3">
+                <div class="ml-4 mr-4" v-loading="allLoading.orderTemplateLoading">
+                    <GenerateForm ref="orderTemplateRef" :data="orderTemplate" :value="orderTemplateValue"
+                        :key="orderTemplateKey" />
+                </div>
+            </div>
+        </el-dialog>
     </div>
 </template>
 <script lang="ts" setup>
@@ -61,34 +62,112 @@ import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffec
 import { GenerateForm } from '@zmjs/form-design';
 import { getFromValue, getTemplateKey } from '@/utils/tools';
 import { get, post } from '@/utils/request';
+import { GETGENERATEFOEM, GETPERSONNEL, URL_OEDERUPDATE, URL_PRODUTWITHORDER, URL_TRANSFER } from '../api';
+import { formatDate } from '@/utils/times';
 
 const globalPopup = inject<GlobalPopup>('globalPopup')
 const emits = defineEmits(['refreshData']);
 const props = defineProps<{
-    data?: any
+    data: any
 }>()
 const allLoading = reactive({
-    contactsTemplateRefLoading: false,
-    editContactsSaveLoading: false,
+    orderTemplateLoading: false,
+    editOrderSaveLoading: false,
     transferLoading: false
 })
 const allVisible = reactive({
-    editContactsVisible: false,
+    editOrderVisible: false,
     transferVisible: false
 })
 const allText = reactive({
     operationText: '认领联系人'
 })
-const contactsTemplate = ref({
+const orderTemplate = ref({
     list: [],
     config: {}
 })
-const contactsTemplateValue = ref({})
-const contactsTemplateRef = ref<typeof GenerateForm>()
-const contactsTemplateRefKey = ref(1)
+const orderTemplateValue = ref({})
+const orderTemplateKey = ref(1)
+const orderTemplateRef = ref<typeof GenerateForm>()
 const info: any = ref({})
 const transferValue = ref('')
 const transferOptions = ref<optionType[]>([])
+const productTableListValue = ref([])
+
+function editOrderSave() {
+    orderTemplateRef.value?.getData().then((res: any) => {
+        allLoading.editOrderSaveLoading = true
+        post(URL_OEDERUPDATE, {
+            ...orderTemplateValue.value,
+            ...res,
+            orderEndDate: res.orderEndDate ? formatDate(res.orderEndDate) : '',
+            orderStartDate: res.orderStartDate ? formatDate(res.orderStartDate) : '',
+            orderProductDetailString: JSON.stringify(productTableListValue.value || [])
+        }).then((_res) => {
+            closeVisible('editOrderVisible')
+            globalPopup?.showSuccess('操作成功')
+            emits('refreshData', 'getDetail')
+        }).finally(() => {
+            allLoading.editOrderSaveLoading = false
+        })
+    }).catch((_err: any) => {
+        console.log(_err)
+        globalPopup?.showError('请填写完整')
+    })
+}
+
+function editInfo(item: any) {
+    showVisible('editOrderVisible')
+    allLoading.orderTemplateLoading = true
+    if (item) {
+        const templateKey = getTemplateKey(orderTemplate.value.list)
+        let formVal: templateKey = { id: item.id }
+        for (let i = 0; i < templateKey.length; i++) {
+            const key = templateKey[i]
+            formVal[key] = item[key]
+        }
+        console.log(formVal, templateKey, item)
+        orderTemplateValue.value = formVal
+        editProduct(item)
+    }
+
+    setTimeout(() => {
+        orderTemplateKey.value++
+        allLoading.orderTemplateLoading = false
+    }, 500)
+}
+
+function editProduct(row: any) {
+    post(URL_PRODUTWITHORDER, { id: row.id }).then(({ data }) => {
+        const list = data.map((item: any) => {
+            const { id, productName, productCode, unit, unitName, typeName, type, price, inventory, orderProductDetail } = item
+            return {
+                id, productId: id, productName, productCode, unit, unitName, typeName, type, price, inventory,
+                num: +orderProductDetail?.num,
+                discount: +orderProductDetail?.discount,
+                sealPrice: +orderProductDetail?.sealPrice,
+                totalPrice: +orderProductDetail?.totalPrice
+            }
+        })
+        productTableListValue.value = list
+    })
+}
+
+function transferCli() {
+    transferValue.value = ''
+    showVisible('transferVisible')
+}
+
+function transferChange() {
+    allLoading.transferLoading = true
+    post(URL_TRANSFER, { id: info.value.id, userId: transferValue.value }).then(() => {
+        globalPopup?.showSuccess('操作成功')
+        closeVisible('transferVisible')
+        emits('refreshData', 'getDetail')
+    }).finally(() => {
+        allLoading.transferLoading = false
+    })
+}
 
 function showVisible(type: keyof typeof allVisible) {
     allVisible[type] = true
@@ -99,22 +178,51 @@ function closeVisible(type: keyof typeof allVisible) {
 }
 
 const formItems = reactive([
-    { label: '联系人', key: 'name', value: '', labelClass: 'w-20 text-right text-gray-500', width: '48%' },
-    { label: '客户', key: 'productName', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
-    { label: '电话', key: 'phone', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
-    { label: '邮箱', key: 'email', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
-    { label: '职务', key: 'position', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
-    { label: '性别', key: 'sexValue', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
-    { label: '地址', key: 'address', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '订单编号', key: 'orderCode', value: '', labelClass: 'w-20 text-right text-gray-500', width: '48%' },
+    { label: '订单名称', key: 'orderName', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '客户名称', key: 'customName', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '商机名称', key: 'businessOpportunityName', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '订单金额', key: 'price', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '回款状态', key: 'receivedStatus', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '已回款金额', key: 'receivedPayment', value: '', labelClass: 'w-24 text-right text-gray-500', width: '48%' },
+    { label: '未回款', key: 'unReceivedPayment', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '订单类型', key: 'type', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '下单时间', key: 'placeTime', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '订单开始时间', key: 'orderStartDate', value: '', labelClass: 'w-30 text-right text-gray-500', width: '48%' },
+    { label: '订单结束时间', key: 'orderEndDate', value: '', labelClass: 'w-30 text-right text-gray-500', width: '48%' },
     { label: '负责人', key: 'inchargerName', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
-    { label: '创建人', key: 'creatorName', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '创建人', key: 'createName', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
     { label: '创建时间', key: 'createTime', value: '', labelClass: 'w-22 text-right text-gray-500', width: '48%' },
+    { label: '客户签约人', key: 'customSigner', value: '', labelClass: 'w-24 text-right text-gray-500', width: '48%' },
+    { label: '公司签约人', key: 'companySigner', value: '', labelClass: 'w-24 text-right text-gray-500', width: '48%' },
     { label: '备注', key: 'remark', value: '', labelClass: 'w-22 text-right text-gray-500', width: '100%' },
 ])
 
+watchEffect(() => {
+    const { data } = props
+    info.value = data
+    formItems.forEach(item => {
+        item.value = info.value[item.key] || '';
+    });
+})
+
+async function getSystemField() {
+    const { data } = await post(GETPERSONNEL, {})
+    transferOptions.value = data.map((item: any) => {
+        const { id, name, phone, jobNumber } = item
+        return {
+            value: id,
+            label: name
+        }
+    })
+
+    const datas = await get(GETGENERATEFOEM)
+    orderTemplate.value = JSON.parse(datas.data[0].config)
+}
+
 // 生命周期钩子
 onMounted(async () => {
-
+    getSystemField()
 });
 </script>
 <style scoped lang="scss">

+ 38 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/component/operationRecord.vue

@@ -0,0 +1,38 @@
+<template>
+    <div class="operationRecord pl-4 pr-4 pt-3 pb-3 h-full flex flex-col">
+        <div class="flex justify-between">
+            <div class="title">操作记录</div>
+        </div>
+        <div class="flex-1 overflow-auto pt-5">
+            <el-table :data="operationRecordtable" border style="width: 100%;height: 278px;">
+                <el-table-column prop="operateDate" label="操作时间" width="150" />
+                <el-table-column prop="operateName" label="操作人" width="120" />
+                <el-table-column prop="msg" label="操作内容" />
+            </el-table>
+        </div>
+    </div>
+</template>
+<script lang="ts" setup>
+import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffect } from 'vue'
+const props = defineProps<{
+    data: any
+}>()
+
+const operationRecordtable = ref([])
+
+watchEffect(() => {
+    const { data } = props
+    operationRecordtable.value = data.contactsLogList || []
+});
+// 生命周期钩子
+onMounted(() => {
+});
+</script>
+<style scoped lang="scss">
+.operationRecord {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 185 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/component/products.vue

@@ -0,0 +1,185 @@
+<template>
+    <div class="relatedTasks pl-4 pr-4 pt-3 pb-3 h-full flex flex-col">
+        <div class="flex justify-between">
+            <div class="title">相关产品</div>
+            <div class="flex">
+                <el-button type="primary" @click="productClick()">编辑产品</el-button>
+            </div>
+        </div>
+        <div class="flex-1 overflow-auto pt-3">
+            <el-table :data="relatedTaskstable" border style="width: 100%;height: 300px;">
+                <el-table-column label="序号" width="80">
+                    <template #default="scope">
+                        {{ scope.$index + 1 }}
+                    </template>
+                </el-table-column>
+                <el-table-column prop="productName" label="产品名称" width="200">
+                    <template #default="scope">
+                        <el-button link type="primary" size="large">{{
+                            scope.row.productName
+                        }}</el-button>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="productName" label="产品类别" width="130" />
+                <el-table-column prop="typeName" label="产品类型" width="130" />
+                <el-table-column prop="unitName" label="单位" width="130" />
+                <el-table-column prop="price" label="标准价格" width="130" />
+                <el-table-column prop="inventory" label="库存" width="130" />
+                <el-table-column prop="sellingPrice" label="售价" width="130" />
+                <el-table-column prop="quantity" label="数量" width="130" />
+                <el-table-column prop="discount" label="折扣(%)" width="130" />
+                <el-table-column prop="totalPrice" label="合计" width="130" />
+            </el-table>
+        </div>
+
+        <!-- 弹窗 -->
+        <el-dialog v-model="allVisible.editOrderVisible" width="1000" :show-close="false" top="10vh">
+            <template #header="{ close, titleId, titleClass }">
+                <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                    <h4 :id="titleId">{{ '编辑产品' }}</h4>
+                    <div>
+                        <el-button type="primary" :loading="allLoading.editSaveLading" @click="saveOrder()">保存</el-button>
+                        <el-button @click="closeVisible('editOrderVisible')">取消</el-button>
+                    </div>
+                </div>
+            </template>
+            <div class="h-[60vh] overflow-y-auto scroll-bar pt-3" v-loading="allLoading.orderTemplateLoadinng">
+                <RelatedProducts ref="relatedProductsRef" :productTableList="productTableList"
+                    :productTableListValue="productTableListValue" :height="'55vh'" />
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script lang="ts" setup>
+import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffect } from 'vue'
+import { GETGENERATEFOEM, GETTABLELISTPRODUCT, URL_OEDERUPDATE } from '../api';
+import { get, post } from '@/utils/request';
+import RelatedProducts from '@/components/relatedProducts/relatedProducts.vue'
+import { all } from 'axios';
+import { formatDate } from '@/utils/times';
+import { getTemplateKey } from '@/utils/tools';
+
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const emits = defineEmits(['refreshData']);
+const props = defineProps<{
+    data: any,
+    information: any
+}>()
+const allVisible = reactive({
+    editOrderVisible: false
+})
+const allLoading = reactive({
+    editSaveLading: false,
+    orderTemplateLoadinng: false
+})
+const info = ref({})
+const infoValue = ref({})
+const relatedProductsRef = ref<typeof RelatedProducts>()
+const relatedTaskstable = ref([])
+const productTableListValue = ref([])
+const productTableList = ref([])
+const orderTemplate = ref({
+    list: [],
+    config: {}
+})
+
+function saveOrder() {
+    let productTableListData = relatedProductsRef?.value?.returnData()
+    const items: any = infoValue.value
+    for (var i in productTableListData) {
+        productTableListData[i].sealPrice = productTableListData[i].sellingPrice,
+            productTableListData[i].discount = productTableListData[i].discount,
+            productTableListData[i].num = productTableListData[i].quantity
+    }
+    const produt = productTableListData ? JSON.stringify(productTableListData) : []
+    allLoading.editSaveLading = true
+    post(URL_OEDERUPDATE, {
+        ...items,
+        orderEndDate: items.orderEndDate ? items.orderEndDate : '',
+        orderStartDate: items.orderStartDate ? items.orderStartDate : '',
+        orderProductDetailString: produt
+    }).then(() => {
+        globalPopup?.showSuccess('操作成功')
+        allLoading.editSaveLading = false
+        closeVisible('editOrderVisible')
+        emits('refreshData', 'getRelatedProducts')
+    }).finally(() => {
+        allLoading.editSaveLading = false
+    })
+}
+
+function productClick() {
+    productTableListValue.value = JSON.parse(JSON.stringify(relatedTaskstable.value))
+    showVisible('editOrderVisible')
+}
+
+function showVisible(type: keyof typeof allVisible) { // 显示弹窗
+    allVisible[type] = true
+}
+
+function closeVisible(type: keyof typeof allVisible) {
+    allVisible[type] = false
+}
+
+function getProductTableList() {
+    post(GETTABLELISTPRODUCT, { pageIndex: -1, pageSize: -1 }).then((res) => {
+        if (res.code == 'ok') {
+            const { record, total } = res.data
+            productTableList.value = record.map((item: any) => {
+                const { id, productName, productCode, unit, unitName, typeName, type, price, inventory } = item
+                return {
+                    id,
+                    productId: id,
+                    productName,
+                    productCode,
+                    unit,
+                    unitName,
+                    price,
+                    type,
+                    typeName,
+                    inventory,
+                    quantity: '',
+                    discount: '',
+                    totalPrice: ''
+                }
+            })
+        }
+    })
+}
+
+async function getSystemField() {
+    const datas = await get(GETGENERATEFOEM)
+    orderTemplate.value = JSON.parse(datas.data[0].config)
+    setInfoValue(info.value)
+}
+
+function setInfoValue(item: any) {
+    const templateKey = getTemplateKey(orderTemplate.value.list)
+    let formVal: templateKey = { id: item.id }
+    for (let i = 0; i < templateKey.length; i++) {
+        const key = templateKey[i]
+        formVal[key] = item[key]
+    }
+    infoValue.value = formVal
+}
+
+watchEffect(() => {
+    const { data, information } = props
+    relatedTaskstable.value = data || []
+    info.value = information || {}
+});
+
+// 生命周期钩子
+onMounted(() => {
+    getProductTableList()
+    getSystemField()
+});
+</script>
+<style scoped lang="scss">
+.relatedTasks {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 134 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/component/rebate.vue

@@ -0,0 +1,134 @@
+<template>
+    <div class="operationRecord pl-4 pr-4 pt-3 pb-3 h-full flex flex-col">
+        <div class="flex justify-between">
+            <div class="title">回款</div>
+            <div class="flex">
+                <el-button type="primary" @click="editRebate(false)">新增回款</el-button>
+            </div>
+        </div>
+        <div class="flex-1 overflow-auto pt-5">
+            <el-table :data="operationRecordtable" border style="width: 100%;height: 278px;">
+                <el-table-column prop="createTime" label="回款时间" width="170" />
+                <el-table-column prop="creatorName" label="操作人" width="120" />
+                <el-table-column prop="money" label="回款金额" width="120" />
+                <el-table-column prop="unReceivedPayment" label="未回款金额" width="120" />
+                <el-table-column :label="'操作'" :width="'120px'" fixed="right">
+                    <template #default="scope">
+                        <el-button link type="primary" size="large" @click="editRebate(scope.row)">编辑</el-button>
+                        <el-button link type="danger" size="large" @click="deteItem(scope.row)">删除</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+
+        <!-- 弹窗 -->
+        <el-dialog v-model="allVisible.rebateVisible" width="680" :show-close="false" top="10vh">
+            <template #header="{ close, titleId, titleClass }">
+                <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                    <h4 :id="titleId">{{ allText.rebateText }}</h4>
+                    <div class="flex">
+                        <el-button @click="allVisible.rebateVisible = false">取消</el-button>
+                        <el-button type="primary" @click="saveRebate" :loading="allLoading.rebateLoading">保存</el-button>
+                    </div>
+                </div>
+            </template>
+            <div class="p-8">
+                <div class="flex flex-row items-center">
+                    <div>回款金额:</div>
+                    <div class="flex-1">
+                        <el-input v-model.trim="mony" v-enterNumber placeholder="请输入" clearable class="w-full"></el-input>
+                    </div>
+                    <div class="ml-4">元</div>
+                </div>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script lang="ts" setup>
+import { post } from '@/utils/request';
+import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffect } from 'vue'
+import { URL_ADDREBATE, URL_DETELEITEMS, URL_EDITEBATE } from '../api';
+import { confirmAction } from '@/utils/tools';
+import { ITEM_RENDER_EVT } from 'element-plus/es/components/virtual-list/src/defaults';
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const emits = defineEmits(['refreshData']);
+const props = defineProps<{
+    data: any,
+    information: any
+}>()
+
+const allVisible = reactive({
+    rebateVisible: false,
+})
+const allLoading = reactive({
+    rebateLoading: false
+})
+const allText = reactive({
+    rebateText: '新增回款'
+})
+
+const operationRecordtable = ref([])
+const mony = ref('')
+const monyItem = ref<any>({})
+const info = ref<any>({})
+
+function deteItem(item: any) {
+    confirmAction(`确定删除该回款记录吗?`).then(() => {
+        post(URL_DETELEITEMS, { paymentId: item.id }).then((_res) => {
+            globalPopup?.showSuccess('删除成功')
+            emits('refreshData', 'getPaymentCollectionList')
+        }).catch((err) => {
+            globalPopup?.showError(err.msg || '')
+        })
+    })
+}
+
+function editRebate(item: any) {
+    if (!item) {
+        allText.rebateText = '新增回款'
+        mony.value = ''
+        monyItem.value = {}
+    } else {
+        monyItem.value = item
+        mony.value = item.money
+        allText.rebateText = '编辑回款'
+    }
+
+    allVisible.rebateVisible = true
+}
+
+function saveRebate() {
+    if (!mony.value || mony.value == '.') {
+        globalPopup?.showWarning('请输入金额')
+        return
+    }
+    allLoading.rebateLoading = true
+    const { url, formVal } = Object.keys(monyItem.value).length === 0
+        ? { url: URL_ADDREBATE, formVal: { orderId: info.value.id, money: mony.value } }
+        : { url: URL_EDITEBATE, formVal: { paymentId: monyItem.value.id, money: mony.value } };
+    post(url, { ...formVal }).then(() => {
+        globalPopup?.showSuccess('操作成功')
+        allVisible.rebateVisible = false
+        emits('refreshData', 'getPaymentCollectionList')
+    }).finally(() => {
+        allLoading.rebateLoading = false
+    })
+}
+
+watchEffect(() => {
+    const { data, information } = props
+    operationRecordtable.value = data || []
+    info.value = information
+});
+// 生命周期钩子
+onMounted(() => {
+});
+</script>
+<style scoped lang="scss">
+.operationRecord {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 126 - 4
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/detail/index.vue

@@ -7,7 +7,7 @@
                 </el-link>
             </div>
             <div class="mr-8">
-                <el-select v-model="values" placeholder="请选择" style="width: 300px">
+                <el-select v-model="values" placeholder="请选择" style="width: 300px" @change="getAll('')">
                     <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
             </div>
@@ -16,10 +16,29 @@
         <div class="flex-1 flex flex-col overflow-y-auto overflow-x-hidden scroll-bar" v-loading="pageLoading">
             <div class="w-full h-auto flex justify-between">
                 <div class="bg-white shadow-md rounded-md" style="width: 46%;">
-                    <Information />
+                    <Information :data="information" @refreshData="getAll" />
                 </div>
                 <div class="bg-white ml-2 shadow-md rounded-md flex-1">
-                    <Attachment />
+                    <Attachment :data="attachment" :information="information" @refreshData="getAll" />
+                </div>
+            </div>
+
+            <div class="w-full h-auto flex justify-between mt-2">
+                <div class="bg-white shadow-md rounded-md" style="width: 65%;">
+                    <Detailcompinents :data="relatedTasks" :information="information" :formTaskType="2" :filed="'orderId'"
+                        :disabled-list="['taskType', 'orderId']" @refreshData="refreshData" />
+                </div>
+                <div class="bg-white ml-2 shadow-md rounded-md flex-1">
+                    <OperationRecord :data="operationRecord" />
+                </div>
+            </div>
+
+            <div class="w-full h-auto flex justify-between mt-2">
+                <div class="bg-white shadow-md rounded-md" style="width: 65%;">
+                    <Products :data="relatedProducts" :information="information" @refreshData="getAll" />
+                </div>
+                <div class="bg-white ml-2 shadow-md rounded-md flex-1" style="width: 33%;">
+                    <Rebate :data="paymentCollectionList" :information="information" @refreshData="getAll" />
                 </div>
             </div>
         </div>
@@ -32,10 +51,15 @@ import { Edit, ArrowLeft as IconView } from '@element-plus/icons-vue'
 import { backPath } from '../../../utils/tools'
 import { useRoute } from "vue-router";
 import { post } from "@/utils/request";
-import { GETTABLELIST } from "../api";
+import { GETTABLELIST, IMPORTMOD, URL_DETEALORDER, URL_FERDETAILTASK, URL_PAYLIST, URL_PRODUTWITHORDER } from "../api";
 
 import Information from '../component/information.vue'
 import Attachment from '../component/attachment.vue'
+import OperationRecord from '../component/operationRecord.vue'
+import Products from '../component/products.vue'
+import Rebate from '../component/rebate.vue'
+import Detailcompinents from '@/components/detailcompinents/relatedTasks.vue'
+import { GETATTACHMENT, GETCENTERLIST } from "@/pages/product/api";
 
 const route = useRoute()
 const globalPopup = inject<GlobalPopup>('globalPopup')
@@ -43,17 +67,115 @@ const pageLoading = ref(false)
 const rowId = ref(+(route.query.id || ''))
 const values = ref<number | string>('')
 const options = ref<optionType[]>([])
+const information = ref({}) // 基本信息
+const attachment = ref<any[]>([]) // 附件信息
+const relatedTasks = ref<any[]>([]) // 相关任务
+const operationRecord = ref<any[]>([]) // 操作记录
+const relatedProducts = ref<any[]>([]) // 相关产品
+const paymentCollectionList = ref<any[]>([]) // 回款
+
+// 获取基本信息
+function getDetail() {
+    post(URL_DETEALORDER, { id: values.value }).then(({ data }) => {
+        information.value = data
+    })
+}
 
+// 获取附件
+function getFileList() {
+    post(GETATTACHMENT, { moduleId: values.value, moduleCode: IMPORTMOD }).then((res) => {
+        attachment.value = res.data
+    })
+}
+
+// 获取所有销售订单
 function getAllContacts() {
     post(GETTABLELIST, { pageIndex: -1, pageSize: -1 }).then(({ data }) => {
         options.value = (data.record || []).map((item: any) => ({ value: item.id, label: item.orderName }))
+        values.value = rowId.value
     }).catch((err) => {
         globalPopup?.showError(err.message)
     })
 }
 
+// 获取相关任务
+function getRelatedTasks() {
+    post(URL_FERDETAILTASK, { id: values.value }).then(({ data }) => {
+        relatedTasks.value = data
+    })
+}
+function refreshData() {
+    getAll('getRelatedTasks')
+}
+
+// 获取操作记录
+function getOperationRecord() {
+    post(GETCENTERLIST, { id: values.value, moduleCode: 'SalesOrder' }).then(({ data }) => {
+        operationRecord.value = data
+    })
+}
+
+function getPaymentCollectionList() {
+    post(URL_PAYLIST, { orderId: values.value }).then(({ data }) => {
+        paymentCollectionList.value = data || []
+    })
+}
+
+// 获取相关产品
+function getRelatedProducts() {
+    post(URL_PRODUTWITHORDER, { id: values.value }).then(({ data }) => {
+        const list = (data || []).map((item: any) => {
+            const { id, productName, productCode, unit, unitName, typeName, type, price, inventory, orderProductDetail } = item
+            return {
+                id, productId: id, productName, productCode, unit, unitName, typeName, type, price, inventory,
+                quantity: +orderProductDetail?.num,
+                discount: +orderProductDetail?.discount,
+                sellingPrice: +orderProductDetail?.sealPrice,
+                totalPrice: +orderProductDetail?.totalPrice
+            }
+        })
+        relatedProducts.value = list
+    })
+}
+
+type allTypeStr = 'getDetail' | 'getFileList' | 'getRelatedTasks' | 'getOperationRecord' | 'getRelatedProducts' | 'getPaymentCollectionList' | ''
+
+async function getAll(event: allTypeStr) {
+    try {
+        pageLoading.value = true
+        if (!event) {
+            await Promise.all([
+                getDetail(),
+                getFileList(),
+                getRelatedTasks(),
+                getOperationRecord(),
+                getRelatedProducts(),
+                getPaymentCollectionList()
+            ])
+        } else if (event == 'getDetail') {
+            await getDetail()
+        } else if (event == 'getFileList') {
+            await getFileList()
+        } else if (event == 'getRelatedTasks') {
+            await getFileList()
+        } else if (event == 'getOperationRecord') {
+            await getOperationRecord()
+        } else if (event == 'getRelatedProducts') {
+            await getRelatedProducts()
+        } else if (event == 'getPaymentCollectionList') {
+            await getPaymentCollectionList()
+        }
+
+        pageLoading.value = false
+    } catch {
+        pageLoading.value = false
+    }
+}
+
 onMounted(() => {
+    values.value = rowId.value
     getAllContacts()
+    getAll('')
 })
 
 </script>

+ 6 - 7
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/order/index.vue

@@ -88,10 +88,10 @@
         </div>
       </template>
       <div class="h-[60vh] overflow-y-auto scroll-bar pt-3" v-loading="allLoading.orderTemplateLoadinng">
-        <GenerateForm ref="orderTemplateRef" :data="orderTemplate" :value="orderTemplateValue" />
+        <GenerateForm ref="orderTemplateRef" :data="orderTemplate" :key="orderTemplateKey" :value="orderTemplateValue" />
         <div>相关产品</div>
         <RelatedProducts ref="relatedProductsRef" :productTableList="productTableList"
-          :productTableListValue="productTableListValue" />
+          :productTableListValue="productTableListValue"  />
       </div>
     </el-dialog>
 
@@ -277,7 +277,6 @@ function submitForm(submitData: any, isClose: boolean) {
 function newTask(item: any) {
   const { id } = item
   taskModalForm.value = { ...createTaskFromType(2), orderId: id, }
-  console.log(taskModalForm.value)
   allVisible.taskModalVisible = true
 }
 
@@ -308,7 +307,6 @@ function saveOrder(flag: boolean) {
       allLoading.editSaveLading = false
     })
   }).catch((_err: any) => {
-    console.log(_err)
     globalPopup?.showError('请填写完整')
   })
 }
@@ -320,11 +318,12 @@ function editOrder(item: any) {
     editProduct(item)
     const templateKey = getTemplateKey(orderTemplate.value.list)
     let formVal: templateKey = { id: item.id }
-    for (let i = 0; i < orderTemplate.value.list.length; i++) {
+    for (let i = 0; i < templateKey.length; i++) {
       const key = templateKey[i]
       formVal[key] = item[key]
     }
     orderTemplateValue.value = formVal
+    console.log(formVal, '<============ 数据')
     allText.orderEditText = '编辑订单'
   }
   if (!item) {
@@ -333,9 +332,10 @@ function editOrder(item: any) {
     allText.orderEditText = '新增订单'
   }
   setTimeout(() => {
+    orderTemplateRef.value && orderTemplateRef.value.reset()
     orderTemplateKey.value++
     allLoading.orderTemplateLoadinng = false
-  }, 500)
+  }, 1000)
 }
 
 function toDetali(row: any) {
@@ -441,7 +441,6 @@ function editProduct(row: any) {
         totalPrice: +orderProductDetail?.totalPrice
       }
     })
-    console.log('开始执行', list)
     productTableListValue.value = list
   })
 }

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/detail/index.vue

@@ -93,7 +93,7 @@ async function getDetail(flag: boolean) {
       getInformationData(id),
       getFileList(id),
       post(GETCENTERLIST, { id, moduleCode: MODUCODE }).then((res) => {
-        operationRecord.value = res.datag
+        operationRecord.value = res.data
       }),
       post(GETORDER, { id }).then((res) => {
         products.value = res.data

+ 20 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/customInstructions.ts

@@ -1,4 +1,4 @@
-import { Directive } from 'vue';
+import { Directive, ObjectDirective } from 'vue';
 
 // 权限控制
 const PermissionDirective: Directive = {
@@ -19,6 +19,23 @@ const PermissionDirective: Directive = {
     }
 };
 
+const PositiveIntegerDirective: ObjectDirective = {
+    mounted(el: HTMLElement) {
+        el.addEventListener('input', handleInput);
+    },
+    beforeUnmount(el: HTMLElement) {
+        el.removeEventListener('input', handleInput);
+    },
+};
+
+function handleInput(event: Event) {
+    const input = event.target as HTMLInputElement;
+    const regex = /^\d*\.?\d*$/;
+    if (!regex.test(input.value)) {
+        input.value = input.value.replace(/[^\d\.]/g, '');
+    }
+}
+
 function extractPath(str: any) {
     const startIndex = str.indexOf('/');
     const endIndex = str.indexOf('/', startIndex + 1);
@@ -27,7 +44,8 @@ function extractPath(str: any) {
 
 // 导出的自定义指令
 const customize = [
-    { key: 'permission', directive: PermissionDirective, name: '角色权限' }
+    { key: 'permission', directive: PermissionDirective, name: '角色权限' },
+    { key: 'enterNumber', directive: PositiveIntegerDirective, name: 'input正整数' }
 ]
 
 export default customize;