From c9ed3d1958a2489460592b3b17e386d9d515d7ea Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 12 六月 2026 18:07:01 +0800
Subject: [PATCH] 君歌 1.工序修改 2.销售报价重构
---
src/views/productionManagement/productionProcess/index.vue | 147 +++-
src/api/salesManagement/salesQuotation.js | 37 +
src/views/salesManagement/salesQuotation/ImportQuotationDetail.vue | 105 +++
src/views/salesManagement/salesQuotation/index.vue | 1039 +++++++++++++++++++++++------------
src/api/salesManagement/salesQuotationRecord.js | 11
src/views/productionManagement/productionProcess/Edit.vue | 194 ++++--
src/views/productionManagement/productionProcess/New.vue | 189 ++++--
7 files changed, 1,168 insertions(+), 554 deletions(-)
diff --git a/src/api/salesManagement/salesQuotation.js b/src/api/salesManagement/salesQuotation.js
index 4329dd9..f7ee3a2 100644
--- a/src/api/salesManagement/salesQuotation.js
+++ b/src/api/salesManagement/salesQuotation.js
@@ -110,3 +110,40 @@
responseType: "blob",
});
}
+
+// 涓嬭浇鎶ヤ环瀵煎叆妯℃澘
+export function downloadQuotationTemplate() {
+ return request({
+ url: "/sales/quotation/downloadTemplate",
+ method: "get",
+ responseType: "blob",
+ });
+}
+
+// 瀵煎叆鎶ヤ环鍗�
+export function importQuotation(data) {
+ return request({
+ url: "/sales/quotation/import",
+ method: "post",
+ data: data,
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+}
+
+// 鏌ヨ瀵煎叆璁板綍鍒楄〃
+export function getImportLogList(query) {
+ return request({
+ url: "/sales/quotation/importLog/list",
+ method: "get",
+ params: query,
+ });
+}
+
+// 鏌ヨ闄嶄环鍘嗗彶璁板綍
+export function getPriceHistoryList(query) {
+ return request({
+ url: "/sales/quotation/priceHistory/list",
+ method: "get",
+ params: query,
+ });
+}
diff --git a/src/api/salesManagement/salesQuotationRecord.js b/src/api/salesManagement/salesQuotationRecord.js
new file mode 100644
index 0000000..4276312
--- /dev/null
+++ b/src/api/salesManagement/salesQuotationRecord.js
@@ -0,0 +1,11 @@
+// 閿�鍞姤浠烽〉闈㈡帴鍙�
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ鎶ヤ环鍗曞垪琛�
+export function getQuotationRecordList(query) {
+ return request({
+ url: "/sales/quotationRecord/listPage",
+ method: "get",
+ params: query,
+ });
+}
\ No newline at end of file
diff --git a/src/views/productionManagement/productionProcess/Edit.vue b/src/views/productionManagement/productionProcess/Edit.vue
index 28077b6..e1d91d8 100644
--- a/src/views/productionManagement/productionProcess/Edit.vue
+++ b/src/views/productionManagement/productionProcess/Edit.vue
@@ -1,74 +1,95 @@
<template>
- <div>
- <el-dialog
- v-model="isShow"
- title="缂栬緫宸ュ簭"
- width="400"
- @close="closeModal"
- >
- <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
- <el-form-item
- label="宸ュ簭鍚嶇О锛�"
- prop="name"
- :rules="[
- {
- required: true,
- message: '璇疯緭鍏ュ伐搴忓悕绉�',
- },
+ <FormDialog
+ v-model="isShow"
+ title="缂栬緫宸ュ簭"
+ width="800px"
+ @confirm="handleSubmit"
+ @cancel="closeModal"
+ >
+ <el-form label-width="180px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="閮ㄤ欢锛�"
+ prop="name"
+ :rules="[
{
- max: 100,
- message: '鏈�澶�100涓瓧绗�',
- }
- ]">
- <el-input v-model="formState.name" />
- </el-form-item>
- <el-form-item label="宸ュ簭缂栧彿" prop="no">
- <el-input v-model="formState.no" />
- </el-form-item>
- <el-form-item
- label="宸ュ簭绫诲瀷"
- prop="type"
- :rules="[
- {
- required: true,
- message: '璇烽�夋嫨宸ュ簭绫诲瀷',
- }
- ]"
- >
- <el-select v-model="formState.type" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷">
- <el-option label="璁℃椂" :value="0" />
- <el-option label="璁′欢" :value="1" />
- </el-select>
- </el-form-item>
- <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
- <el-input v-model="formState.salaryQuota" type="number" :step="0.001" />
- </el-form-item>
- <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
- <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
- </el-form-item>
- <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
- <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
- </el-form-item>
- <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
- <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
- </el-form-item>
- <el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="formState.remark" type="textarea" />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">纭</el-button>
- <el-button @click="closeModal">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
+ required: true,
+ message: '璇疯緭鍏ラ儴浠�',
+ },
+ {
+ max: 100,
+ message: '鏈�澶�100涓瓧绗�',
+ }
+ ]">
+ <el-input v-model="formState.name" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿" prop="no">
+ <el-input v-model="formState.no" />
+ </el-form-item>
+ <el-form-item
+ label="宸ュ簭绫诲瀷"
+ prop="processType"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨宸ュ簭绫诲瀷',
+ }
+ ]"
+ >
+ <el-select v-model="formState.processType" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷" style="width: 100%">
+ <el-option v-for="item in processTypeOptions"
+ :key="item"
+ :label="item"
+ :value="item" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁″垝宸ユ椂(灏忔椂)" prop="salaryQuota">
+ <el-input v-model="formState.salaryQuota" type="number" :step="0.5" />
+ </el-form-item>
+ <el-form-item label="璁″垝浜哄憳" prop="planPerson">
+ <el-select v-model="formState.planPerson"
+ placeholder="璇烽�夋嫨璁″垝浜哄憳"
+ clearable
+ filterable
+ style="width: 100%">
+ <el-option v-for="item in employeeOptions"
+ :key="item.id"
+ :label="item.staffName"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁″垝鎵ц浜哄憳" prop="executor">
+ <el-select v-model="formState.executor"
+ placeholder="璇烽�夋嫨璁″垝鎵ц浜哄憳"
+ clearable
+ filterable
+ style="width: 100%">
+ <el-option v-for="item in employeeOptions"
+ :key="item.id"
+ :label="item.staffName"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
+ <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
+ <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
+ <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
</template>
<script setup>
-import { ref, computed, getCurrentInstance, watch } from "vue";
-import {update} from "@/api/productionManagement/productionProcess.js";
+import { ref, computed, getCurrentInstance, watch, onMounted } from "vue";
+import { update } from "@/api/productionManagement/productionProcess.js";
+import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
const props = defineProps({
visible: {
@@ -84,14 +105,26 @@
const emit = defineEmits(['update:visible', 'completed']);
-// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const processTypeOptions = [
+ "鏈哄姞宸�",
+ "鍒澘鍐疯姱鍒朵綔",
+ "绠¤矾缁勫",
+ "缃愪綋杩炴帴鍙婅皟璇�",
+ "娴嬭瘯鎵撳帇",
+ "鍏朵粬",
+];
+
+const employeeOptions = ref([]);
+
const formState = ref({
id: props.record.id,
name: props.record.name,
- type: props.record.type,
no: props.record.no,
+ processType: props.record.processType || '',
remark: props.record.remark,
salaryQuota: props.record.salaryQuota,
+ planPerson: props.record.planPerson || null,
+ executor: props.record.executor || null,
isQuality: props.record.isQuality,
inbound: props.record.inbound,
reportWork: props.record.reportWork,
@@ -106,16 +139,17 @@
},
});
-// 鐩戝惉 record 鍙樺寲锛屾洿鏂拌〃鍗曟暟鎹�
watch(() => props.record, (newRecord) => {
if (newRecord && isShow.value) {
formState.value = {
id: newRecord.id,
name: newRecord.name || '',
no: newRecord.no || '',
- type: newRecord.type,
+ processType: newRecord.processType || '',
remark: newRecord.remark || '',
salaryQuota: newRecord.salaryQuota || '',
+ planPerson: newRecord.planPerson || null,
+ executor: newRecord.executor || null,
isQuality: props.record.isQuality,
inbound: newRecord.inbound,
reportWork: newRecord.reportWork,
@@ -123,16 +157,17 @@
}
}, { immediate: true, deep: true });
-// 鐩戝惉寮圭獥鎵撳紑锛岄噸鏂板垵濮嬪寲琛ㄥ崟鏁版嵁
watch(() => props.visible, (visible) => {
if (visible && props.record) {
formState.value = {
id: props.record.id,
name: props.record.name || '',
no: props.record.no || '',
- type: props.record.type,
+ processType: props.record.processType || '',
remark: props.record.remark || '',
salaryQuota: props.record.salaryQuota || '',
+ planPerson: props.record.planPerson || null,
+ executor: props.record.executor || null,
isQuality: props.record.isQuality,
inbound: props.record.inbound,
reportWork: props.record.reportWork,
@@ -150,9 +185,7 @@
proxy.$refs["formRef"].validate(valid => {
if (valid) {
update(formState.value).then(res => {
- // 鍏抽棴妯℃�佹
isShow.value = false;
- // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
emit('completed');
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
})
@@ -160,6 +193,19 @@
})
};
+const loadEmployees = async () => {
+ try {
+ const res = await staffOnJobListPage({ current: -1, size: -1, staffState: 1 });
+ employeeOptions.value = res.data?.records || [];
+ } catch (error) {
+ console.error("鍔犺浇鍛樺伐鍒楄〃澶辫触", error);
+ }
+};
+
+onMounted(() => {
+ loadEmployees();
+});
+
defineExpose({
closeModal,
handleSubmit,
diff --git a/src/views/productionManagement/productionProcess/New.vue b/src/views/productionManagement/productionProcess/New.vue
index 0b3fd47..f532f09 100644
--- a/src/views/productionManagement/productionProcess/New.vue
+++ b/src/views/productionManagement/productionProcess/New.vue
@@ -1,76 +1,95 @@
<template>
- <div>
- <el-dialog
- v-model="isShow"
- title="鏂板宸ュ簭"
- width="400"
- @close="closeModal"
- >
- <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
- <el-form-item
- label="宸ュ簭鍚嶇О锛�"
- prop="name"
- :rules="[
- {
- required: true,
- message: '璇疯緭鍏ュ伐搴忓悕绉�',
- },
+ <FormDialog
+ v-model="isShow"
+ title="鏂板宸ュ簭"
+ width="1000px"
+ @confirm="handleSubmit"
+ @cancel="closeModal"
+ >
+ <el-form label-width="180px" :model="formState" label-position="top" ref="formRef">
+ <el-form-item
+ label="閮ㄤ欢锛�"
+ prop="name"
+ :rules="[
{
- max: 100,
- message: '鏈�澶�100涓瓧绗�',
- }
- ]">
- <el-input v-model="formState.name" />
- </el-form-item>
- <el-form-item label="宸ュ簭缂栧彿" prop="no">
- <el-input v-model="formState.no" />
- </el-form-item>
- <el-form-item
- label="宸ュ簭绫诲瀷"
- prop="type"
- :rules="[
- {
- required: true,
- message: '璇烽�夋嫨宸ュ簭绫诲瀷',
- }
- ]"
- >
- <el-select v-model="formState.type" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷">
- <el-option label="璁℃椂" :value="0" />
- <el-option label="璁′欢" :value="1" />
- </el-select>
- </el-form-item>
- <el-form-item label="宸ヨ祫瀹氶" prop="salaryQuota">
- <el-input v-model="formState.salaryQuota" type="number" :step="0.001">
- <template #append>鍏�</template>
- </el-input>
- </el-form-item>
- <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
- <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
- </el-form-item>
- <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
- <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
- </el-form-item>
- <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
- <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
- </el-form-item>
- <el-form-item label="澶囨敞" prop="remark">
- <el-input v-model="formState.remark" type="textarea" />
- </el-form-item>
- </el-form>
- <template #footer>
- <div class="dialog-footer">
- <el-button type="primary" @click="handleSubmit">纭</el-button>
- <el-button @click="closeModal">鍙栨秷</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
+ required: true,
+ message: '璇疯緭鍏ラ儴浠�',
+ },
+ {
+ max: 100,
+ message: '鏈�澶�100涓瓧绗�',
+ }
+ ]">
+ <el-input v-model="formState.name" />
+ </el-form-item>
+ <el-form-item label="宸ュ簭缂栧彿" prop="no">
+ <el-input v-model="formState.no" />
+ </el-form-item>
+ <el-form-item
+ label="宸ュ簭绫诲瀷"
+ prop="processType"
+ :rules="[
+ {
+ required: true,
+ message: '璇烽�夋嫨宸ュ簭绫诲瀷',
+ }
+ ]"
+ >
+ <el-select v-model="formState.processType" placeholder="璇烽�夋嫨宸ュ簭绫诲瀷" style="width: 100%">
+ <el-option v-for="item in processTypeOptions"
+ :key="item"
+ :label="item"
+ :value="item" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁″垝宸ユ椂(灏忔椂)" prop="salaryQuota">
+ <el-input v-model="formState.salaryQuota" type="number" :step="0.5" />
+ </el-form-item>
+ <el-form-item label="璁″垝浜哄憳" prop="planPerson">
+ <el-select v-model="formState.planPerson"
+ placeholder="璇烽�夋嫨璁″垝浜哄憳"
+ clearable
+ filterable
+ style="width: 100%">
+ <el-option v-for="item in employeeOptions"
+ :key="item.id"
+ :label="item.staffName"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁″垝鎵ц浜哄憳" prop="executor">
+ <el-select v-model="formState.executor"
+ placeholder="璇烽�夋嫨璁″垝鎵ц浜哄憳"
+ clearable
+ filterable
+ style="width: 100%">
+ <el-option v-for="item in employeeOptions"
+ :key="item.id"
+ :label="item.staffName"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏄惁璐ㄦ" prop="isQuality">
+ <el-switch v-model="formState.isQuality" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="鏄惁鍏ュ簱" prop="inbound">
+ <el-switch v-model="formState.inbound" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="鏄惁鎶ュ伐" prop="reportWork">
+ <el-switch v-model="formState.reportWork" :active-value="true" inactive-value="false"/>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="formState.remark" type="textarea" />
+ </el-form-item>
+ </el-form>
+ </FormDialog>
</template>
<script setup>
-import { ref, computed, getCurrentInstance } from "vue";
-import {add} from "@/api/productionManagement/productionProcess.js";
+import { ref, computed, getCurrentInstance, onMounted } from "vue";
+import { add } from "@/api/productionManagement/productionProcess.js";
+import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
const props = defineProps({
visible: {
@@ -81,12 +100,25 @@
const emit = defineEmits(['update:visible', 'completed']);
-// 鍝嶅簲寮忔暟鎹紙鏇夸唬閫夐」寮忕殑 data锛�
+const processTypeOptions = [
+ "鏈哄姞宸�",
+ "鍒澘鍐疯姱鍒朵綔",
+ "绠¤矾缁勫",
+ "缃愪綋杩炴帴鍙婅皟璇�",
+ "娴嬭瘯鎵撳帇",
+ "鍏朵粬",
+];
+
+const employeeOptions = ref([]);
+
const formState = ref({
name: '',
- type: undefined,
+ no: '',
+ processType: '',
remark: '',
- salaryQuota: '',
+ salaryQuota: '',
+ planPerson: null,
+ executor: null,
isQuality: false,
inbound: false,
reportWork: false,
@@ -111,9 +143,7 @@
proxy.$refs["formRef"].validate(valid => {
if (valid) {
add(formState.value).then(res => {
- // 鍏抽棴妯℃�佹
isShow.value = false;
- // 鍛婄煡鐖剁粍浠跺凡瀹屾垚
emit('completed');
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
})
@@ -121,6 +151,19 @@
})
};
+const loadEmployees = async () => {
+ try {
+ const res = await staffOnJobListPage({ current: -1, size: -1, staffState: 1 });
+ employeeOptions.value = res.data?.records || [];
+ } catch (error) {
+ console.error("鍔犺浇鍛樺伐鍒楄〃澶辫触", error);
+ }
+};
+
+onMounted(() => {
+ loadEmployees();
+});
+
defineExpose({
closeModal,
handleSubmit,
diff --git a/src/views/productionManagement/productionProcess/index.vue b/src/views/productionManagement/productionProcess/index.vue
index ee49657..56f4381 100644
--- a/src/views/productionManagement/productionProcess/index.vue
+++ b/src/views/productionManagement/productionProcess/index.vue
@@ -57,14 +57,8 @@
:type="process.isProduction ? 'warning' : 'info'">
{{ process.isProduction ? '鐢熶骇' : '涓嶇敓浜�' }}
</el-tag>
- <el-tag v-if="process.type !== null && process.type !== undefined"
- size="small"
- :type="process.type == 1 ? 'primary' : 'success'"
- style="margin-left: 8px">
- {{ process.type == 0 ? '璁℃椂' : '璁′欢' }}
- </el-tag>
</div>
- <span class="param-count">宸ヨ祫瀹氶: 楼{{ process.salaryQuota || 0 }}</span>
+ <span class="param-count">璁″垝宸ユ椂: {{ process.salaryQuota || 0 }}灏忔椂</span>
</div>
</div>
</div>
@@ -101,28 +95,68 @@
</div>
</div>
<!-- 宸ュ簭鏂板/缂栬緫瀵硅瘽妗� -->
- <el-dialog v-model="processDialogVisible"
- :title="isProcessEdit ? '缂栬緫宸ュ簭' : '鏂板宸ュ簭'"
- width="500px">
+ <FormDialog v-model="processDialogVisible"
+ :title="isProcessEdit ? '缂栬緫閮ㄤ欢' : '鏂板閮ㄤ欢'"
+ width="600"
+ @confirm="handleProcessSubmit"
+ @cancel="processDialogVisible = false">
<el-form :model="processForm"
:rules="processRules"
ref="processFormRef"
- label-width="100px">
- <el-form-item label="宸ュ簭缂栫爜"
- prop="no">
- <el-input v-model="processForm.no"
- placeholder="璇疯緭鍏ュ伐搴忕紪鐮�" />
- </el-form-item>
- <el-form-item label="宸ュ簭鍚嶇О"
+ label-width="120px">
+ <el-form-item label="閮ㄤ欢鍚嶇О"
prop="name">
<el-input v-model="processForm.name"
- placeholder="璇疯緭鍏ュ伐搴忓悕绉�" />
+ placeholder="璇疯緭鍏ラ儴浠跺悕绉�" />
</el-form-item>
- <el-form-item label="宸ヨ祫瀹氶"
+ <el-form-item label="閮ㄤ欢缂栧彿"
+ prop="no">
+ <el-input v-model="processForm.no"
+ placeholder="璇疯緭鍏ラ儴浠剁紪鍙�" />
+ </el-form-item>
+ <el-form-item label="閮ㄤ欢绫诲瀷"
+ prop="processType">
+ <el-select v-model="processForm.processType"
+ placeholder="璇烽�夋嫨閮ㄤ欢绫诲瀷"
+ style="width: 100%">
+ <el-option v-for="item in processTypeOptions"
+ :key="item"
+ :label="item"
+ :value="item" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁″垝宸ユ椂(灏忔椂)"
prop="salaryQuota">
<el-input v-model="processForm.salaryQuota"
type="number"
- :step="0.001" />
+ :step="0.5"
+ placeholder="璇疯緭鍏ヨ鍒掑伐鏃�" />
+ </el-form-item>
+ <el-form-item label="璁″垝浜哄憳"
+ prop="planPerson">
+ <el-select v-model="processForm.planPerson"
+ placeholder="璇烽�夋嫨璁″垝浜哄憳"
+ clearable
+ filterable
+ style="width: 100%">
+ <el-option v-for="item in employeeOptions"
+ :key="item.id"
+ :label="item.staffName"
+ :value="item.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁″垝鎵ц浜哄憳"
+ prop="executor">
+ <el-select v-model="processForm.executor"
+ placeholder="璇烽�夋嫨璁″垝鎵ц浜哄憳"
+ clearable
+ filterable
+ style="width: 100%">
+ <el-option v-for="item in employeeOptions"
+ :key="item.id"
+ :label="item.staffName"
+ :value="item.id" />
+ </el-select>
</el-form-item>
<el-form-item label="鏄惁璐ㄦ"
prop="isQuality">
@@ -131,13 +165,6 @@
<el-form-item label="鏄惁鐢熶骇"
prop="isProduction">
<el-switch v-model="processForm.isProduction" />
- </el-form-item>
- <el-form-item label="璁¤垂绫诲瀷"
- prop="type">
- <el-radio-group v-model="processForm.type">
- <el-radio :label="0">璁℃椂</el-radio>
- <el-radio :label="1">璁′欢</el-radio>
- </el-radio-group>
</el-form-item>
<el-form-item label="鍏宠仈璁惧"
prop="deviceLedgerId">
@@ -160,18 +187,11 @@
placeholder="璇疯緭鍏ュ伐搴忔弿杩�" />
</el-form-item>
</el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button type="primary"
- @click="handleProcessSubmit">纭畾</el-button>
- <el-button @click="processDialogVisible = false">鍙栨秷</el-button>
- </span>
- </template>
- </el-dialog>
+ </FormDialog>
<!-- 閫夋嫨鍙傛暟瀵硅瘽妗� -->
- <el-dialog v-model="paramDialogVisible"
- title="閫夋嫨鍙傛暟"
- width="1000px">
+ <FormDialog v-model="paramDialogVisible"
+ title="閫夋嫨鍙傛暟"
+ width="1000px">
<div class="param-select-container">
<!-- 宸︿晶鍙傛暟鍒楄〃 -->
<div class="param-list-area">
@@ -259,7 +279,7 @@
<el-button @click="paramDialogVisible = false">鍙栨秷</el-button>
</span>
</template>
- </el-dialog>
+ </FormDialog>
<!-- 缂栬緫鍙傛暟瀵硅瘽妗� -->
<el-dialog v-model="editParamDialogVisible"
title="缂栬緫鍙傛暟"
@@ -308,6 +328,18 @@
} from "@/api/productionManagement/productionProcess.js";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import { getBaseParamList } from "@/api/basicData/parameterMaintenance.js";
+ import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
+ import FormDialog from "@/components/Dialog/FormDialog.vue";
+
+ // 閮ㄤ欢绫诲瀷涓嬫媺閫夐」锛堝啓姝伙級
+ const processTypeOptions = [
+ "鏈哄姞宸�",
+ "鍒澘鍐疯姱鍒朵綔",
+ "绠¤矾缁勫",
+ "缃愪綋杩炴帴鍙婅皟璇�",
+ "娴嬭瘯鎵撳帇",
+ "鍏朵粬",
+ ];
// 宸ュ簭鍒楄〃鏁版嵁
const processValueList = ref([]);
@@ -333,6 +365,9 @@
// 鏁版嵁瀛楀吀
const dictTypes = ref([]);
+ // 鍛樺伐鍒楄〃锛堣鍒掍汉鍛樸�佽鍒掓墽琛屼汉鍛樹笅鎷夌敤锛�
+ const employeeOptions = ref([]);
+
// 宸ュ簭瀵硅瘽妗�
const processDialogVisible = ref(false);
const isProcessEdit = ref(false);
@@ -346,19 +381,19 @@
isProduction: false,
remark: "",
deviceLedgerId: null,
- type: 0,
+ processType: "",
+ planPerson: null,
+ executor: null,
});
const processRules = {
- no: [{ required: true, message: "璇疯緭鍏ュ伐搴忕紪鐮�", trigger: "blur" }],
- name: [{ required: true, message: "璇疯緭鍏ュ伐搴忓悕绉�", trigger: "blur" }],
+ no: [{ required: true, message: "璇疯緭鍏ラ儴浠剁紪鐮�", trigger: "blur" }],
+ name: [{ required: true, message: "璇疯緭鍏ラ儴浠跺悕绉�", trigger: "blur" }],
salaryQuota: [
{
required: false,
- message: "璇疯緭鍏ュ伐璧勫畾棰�",
- trigger: "blur",
validator: (rule, value, callback) => {
- if (isNaN(value) || value < 0) {
- callback(new Error("宸ヨ祫瀹氶蹇呴』鏄潪璐熸暟瀛�"));
+ if (value !== null && value !== undefined && value !== "" && (isNaN(value) || Number(value) < 0)) {
+ callback(new Error("璁″垝宸ユ椂蹇呴』鏄潪璐熸暟瀛�"));
} else {
callback();
}
@@ -368,7 +403,7 @@
deviceLedgerId: [
{ required: false, message: "璇烽�夋嫨璁惧", trigger: "change" },
],
- type: [{ required: false, message: "璇烽�夋嫨璁¤垂绫诲瀷", trigger: "change" }],
+ processType: [{ required: true, message: "璇烽�夋嫨閮ㄤ欢绫诲瀷", trigger: "change" }],
};
// 鍙傛暟瀵硅瘽妗�
@@ -557,7 +592,9 @@
processForm.isProduction = false;
processForm.remark = "";
processForm.deviceLedgerId = null;
- processForm.type = 0;
+ processForm.processType = "";
+ processForm.planPerson = null;
+ processForm.executor = null;
processDialogVisible.value = true;
};
@@ -574,7 +611,9 @@
const deviceId = Number(process.deviceLedgerId);
const hasDevice = deviceOptions.value.some(item => item.id === deviceId);
processForm.deviceLedgerId = deviceId && hasDevice ? deviceId : null;
- processForm.type = process.type;
+ processForm.processType = process.processType || "";
+ processForm.planPerson = process.planPerson || null;
+ processForm.executor = process.executor || null;
processDialogVisible.value = true;
};
@@ -791,10 +830,20 @@
});
};
+ const loadEmployees = async () => {
+ try {
+ const res = await staffOnJobListPage({ current: -1, size: -1, staffState: 1 });
+ employeeOptions.value = res.data?.records || [];
+ } catch (error) {
+ console.error("鍔犺浇鍛樺伐鍒楄〃澶辫触", error);
+ }
+ };
+
onMounted(() => {
loadDeviceName();
getProcessList();
getDictTypes();
+ loadEmployees();
});
</script>
diff --git a/src/views/salesManagement/salesQuotation/ImportQuotationDetail.vue b/src/views/salesManagement/salesQuotation/ImportQuotationDetail.vue
new file mode 100644
index 0000000..a9f31de
--- /dev/null
+++ b/src/views/salesManagement/salesQuotation/ImportQuotationDetail.vue
@@ -0,0 +1,105 @@
+<script setup>
+import {ref, computed, onMounted} from 'vue'
+import {getQuotationRecordList} from "@/api/salesManagement/salesQuotationRecord.js";
+
+const props = defineProps({
+ showModal: {
+ type: Boolean,
+ required: true
+ },
+ quotationId: {
+ type: Number,
+ required: true
+ }
+})
+
+const emit = defineEmits(['update:showModal'])
+
+const dialogVisible = computed({
+ get: () => props.showModal,
+ set: (val) => emit('update:showModal', val)
+})
+
+const leftTableData = ref([])
+const rightTableData = ref([])
+const selectedLeftRow = ref(null)
+
+const handleRowClick = (row) => {
+ selectedLeftRow.value = row
+ rightTableData.value = row.products || []
+}
+
+const tableRowClassName = ({row}) => {
+ return selectedLeftRow.value === row ? 'selected-row' : ''
+}
+
+const fetchData = () => {
+ getQuotationRecordList({quotationRecordId: props.quotationId}).then(res => {
+ const data = res.data.records || []
+ data.forEach(item => {
+ leftTableData.value.push(JSON.parse(item?.info || '{}'))
+ })
+ })
+}
+
+const leftColumns = [
+ {prop: 'customer', label: '瀹㈡埛鍚嶇О', minWidth: '120'},
+ {prop: 'salesperson', label: '涓氬姟鍛�', minWidth: '120'},
+ {prop: 'quotationDate', label: '鎶ヤ环鏃ユ湡', minWidth: '120'},
+ {prop: 'validDate', label: '鏈夋晥鏈熻嚦', minWidth: '120'},
+ {prop: 'paymentMethod', label: '鏀粯鏂瑰紡', minWidth: '120'},
+ {prop: 'remark', label: '澶囨敞', minWidth: '120'}
+]
+
+const rightColumns = [
+ {prop: 'product', label: '浜у搧鍚嶇О', minWidth: '120'},
+ {prop: 'specification', label: '鍨嬪彿', minWidth: '120'},
+ {prop: 'unit', label: '鍗曚綅', minWidth: '100'},
+ {prop: 'unitPrice', label: '鍗曚环', minWidth: '100'}
+]
+
+onMounted(() => {
+ fetchData()
+})
+
+</script>
+
+<template>
+ <el-dialog v-model="dialogVisible" title="璇︽儏" width="1000px" :close-on-click-modal="false">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <div class="table-title">鎶ヤ环鍗曡褰�</div>
+ <el-table :data="leftTableData" border height="400" stripe highlight-current-row
+ @row-click="handleRowClick" :row-class-name="tableRowClassName">
+ <el-table-column v-for="col in leftColumns" :key="col.prop" :prop="col.prop" :label="col.label"
+ :min-width="col.minWidth" show-overflow-tooltip/>
+ </el-table>
+ </el-col>
+ <el-col :span="12">
+ <div class="table-title">鎶ヤ环鍗曚骇鍝�</div>
+ <el-table :data="rightTableData" border height="400" stripe>
+ <el-table-column v-for="col in rightColumns" :key="col.prop" :prop="col.prop" :label="col.label"
+ :min-width="col.minWidth" show-overflow-tooltip/>
+ </el-table>
+ </el-col>
+ </el-row>
+ <template #footer>
+ <div class="dialog-footer">
+ <el-button @click="dialogVisible = false">鍏抽棴</el-button>
+ </div>
+ </template>
+ </el-dialog>
+</template>
+
+<style scoped>
+.table-title {
+ font-size: 15px;
+ font-weight: bold;
+ margin-bottom: 10px;
+ color: #303133;
+}
+
+:deep(.selected-row) {
+ background-color: #ecf5ff !important;
+}
+</style>
diff --git a/src/views/salesManagement/salesQuotation/index.vue b/src/views/salesManagement/salesQuotation/index.vue
index fce764f..5e010cd 100644
--- a/src/views/salesManagement/salesQuotation/index.vue
+++ b/src/views/salesManagement/salesQuotation/index.vue
@@ -5,57 +5,64 @@
<el-row :gutter="20" class="search-row">
<el-col :span="8">
<el-input
- v-model="searchForm.quotationNo"
- placeholder="璇疯緭鍏ユ姤浠峰崟鍙�"
- clearable
- @keyup.enter="handleSearch"
+ v-model="searchForm.quotationNo"
+ placeholder="璇疯緭鍏ユ姤浠峰崟鍙�"
+ clearable
+ @keyup.enter="handleSearch"
>
<template #prefix>
- <el-icon><Search /></el-icon>
+ <el-icon>
+ <Search/>
+ </el-icon>
</template>
</el-input>
</el-col>
<el-col :span="8">
- <el-select v-model="searchForm.customerId" placeholder="璇烽�夋嫨瀹㈡埛" clearable>
- <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id">
- {{
- item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
- }}
- </el-option>
+ <el-select v-model="searchForm.customer" placeholder="璇烽�夋嫨瀹㈡埛" clearable>
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName"
+ :value="item.customerName">
+ {{
+ item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
+ }}
+ </el-option>
</el-select>
</el-col>
-<!-- <el-col :span="6">-->
-<!-- <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鎶ヤ环鐘舵��" clearable>-->
-<!-- <el-option label="鑽夌" value="鑽夌"></el-option>-->
-<!-- <el-option label="宸插彂閫�" value="宸插彂閫�"></el-option>-->
-<!-- <el-option label="瀹㈡埛纭" value="瀹㈡埛纭"></el-option>-->
-<!-- <el-option label="宸茶繃鏈�" value="宸茶繃鏈�"></el-option>-->
-<!-- </el-select>-->
-<!-- </el-col>-->
+ <!-- <el-col :span="6">-->
+ <!-- <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨鎶ヤ环鐘舵��" clearable>-->
+ <!-- <el-option label="鑽夌" value="鑽夌"></el-option>-->
+ <!-- <el-option label="宸插彂閫�" value="宸插彂閫�"></el-option>-->
+ <!-- <el-option label="瀹㈡埛纭" value="瀹㈡埛纭"></el-option>-->
+ <!-- <el-option label="宸茶繃鏈�" value="宸茶繃鏈�"></el-option>-->
+ <!-- </el-select>-->
+ <!-- </el-col>-->
<el-col :span="8">
<el-button type="primary" @click="handleSearch">鎼滅储</el-button>
<el-button @click="resetSearch">閲嶇疆</el-button>
- <el-button style="float: right;" type="primary" @click="handleAdd">
- 鏂板鎶ヤ环
- </el-button>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20" style="margin-bottom: 20px;">
+ <el-col :span="24">
+ <el-button type="primary" @click="handleAdd">鏂板鎶ヤ环</el-button>
+ <el-button type="primary" @click="handleImport">瀵煎叆閿�鍞姤浠�</el-button>
+ <el-button type="success" @click="handleShowImportLog">瀵煎叆璁板綍</el-button>
</el-col>
</el-row>
<!-- 鎶ヤ环鍒楄〃 -->
<el-table
- :data="filteredList"
- style="width: 100%"
- v-loading="loading"
- border
- stripe
- height="calc(100vh - 22em)"
+ :data="filteredList"
+ style="width: 100%"
+ v-loading="loading"
+ border
+ stripe
+ height="calc(100vh - 22em)"
>
- <el-table-column align="center" label="搴忓彿" type="index" width="60" />
- <el-table-column prop="quotationNo" label="鎶ヤ环鍗曞彿" />
- <el-table-column prop="customer" label="瀹㈡埛鍚嶇О" />
- <el-table-column prop="salesperson" label="涓氬姟鍛�" width="100" />
- <el-table-column prop="quotationDate" label="鎶ヤ环鏃ユ湡" width="120" />
- <el-table-column prop="validDate" label="鏈夋晥鏈熻嚦" width="120" />
+ <el-table-column align="center" label="搴忓彿" type="index" width="60"/>
+ <el-table-column prop="quotationNo" label="鎶ヤ环鍗曞彿"/>
+ <el-table-column prop="customer" label="瀹㈡埛鍚嶇О"/>
+ <el-table-column prop="salesperson" label="涓氬姟鍛�" width="100"/>
+ <el-table-column prop="quotationDate" label="鎶ヤ环鏃ユ湡" width="120"/>
+ <el-table-column prop="validDate" label="鏈夋晥鏈熻嚦" width="120"/>
<el-table-column prop="status" label="瀹℃壒鐘舵��" width="120" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)" disable-transitions>
@@ -68,191 +75,277 @@
楼{{ scope.row.totalAmount.toFixed(2) }}
</template>
</el-table-column>
- <el-table-column label="鎿嶄綔" width="200" fixed="right" align="center">
+ <el-table-column label="鎿嶄綔" width="300" fixed="right" align="center">
<template #default="scope">
- <el-button link type="primary" @click="handleEdit(scope.row)" :disabled="!['寰呭鎵�','鎷掔粷'].includes(scope.row.status)">缂栬緫</el-button>
+ <el-button link type="primary" @click="handleEdit(scope.row)"
+ >缂栬緫
+ </el-button>
<el-button link type="primary" @click="handleView(scope.row)" style="color: #67C23A">鏌ョ湅</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+ <el-button link type="info" @click="handleShowPriceHistory(scope.row)">闄嶄环鍘嗗彶</el-button>
</template>
</el-table-column>
</el-table>
<!-- 鍒嗛〉 -->
<pagination
- :total="pagination.total"
- layout="total, sizes, prev, pager, next, jumper"
- :page="pagination.currentPage"
- :limit="pagination.pageSize"
- @pagination="handleCurrentChange"
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.currentPage"
+ :limit="pagination.pageSize"
+ @pagination="handleCurrentChange"
/>
</el-card>
<!-- 鏂板/缂栬緫瀵硅瘽妗� -->
- <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false" @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
+ <FormDialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false"
+ @close="dialogVisible = false" @confirm="handleSubmit" @cancel="dialogVisible = false">
<div class="quotation-form-container">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="quotation-form">
- <!-- 鍩烘湰淇℃伅 -->
- <el-card class="form-card" shadow="hover">
- <template #header>
- <div class="card-header-wrapper">
- <el-icon class="card-icon"><Document /></el-icon>
- <span class="card-title">鍩烘湰淇℃伅</span>
- </div>
- </template>
- <div class="form-content">
- <el-row :gutter="24">
- <el-col :span="12">
- <el-form-item label="瀹㈡埛鍚嶇О" prop="customerId">
- <el-select v-model="form.customerId" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%" clearable filterable>
- <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"></el-option>
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="涓氬姟鍛�" prop="salesperson">
- <el-select v-model="form.salesperson" placeholder="璇烽�夋嫨涓氬姟鍛�" style="width: 100%" clearable filterable>
- <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
- :value="item.nickName" />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="24">
- <el-col :span="12">
- <el-form-item label="鎶ヤ环鏃ユ湡" prop="quotationDate">
- <el-date-picker
- v-model="form.quotationDate"
- type="date"
- placeholder="閫夋嫨鎶ヤ环鏃ユ湡"
- style="width: 100%"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- clearable
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="鏈夋晥鏈熻嚦" prop="validDate">
- <el-date-picker
- v-model="form.validDate"
- type="date"
- placeholder="閫夋嫨鏈夋晥鏈�"
- style="width: 100%"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- clearable
- />
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="24">
- <el-col :span="12">
- <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
- <el-input v-model="form.paymentMethod" placeholder="璇疯緭鍏ヤ粯娆炬柟寮�" clearable />
- </el-form-item>
- </el-col>
- </el-row>
- </div>
- </el-card>
-
- <!-- 浜у搧淇℃伅 -->
- <el-card class="form-card" shadow="hover">
- <template #header>
- <div class="card-header-wrapper">
- <el-icon class="card-icon"><Box /></el-icon>
- <span class="card-title">浜у搧淇℃伅</span>
- <el-button type="primary" size="small" @click="addProduct" class="header-btn">
- <el-icon><Plus /></el-icon>
- 娣诲姞浜у搧
- </el-button>
- </div>
- </template>
- <div class="form-content">
- <el-table :data="form.products" border style="width: 100%" class="product-table" v-if="form.products.length > 0">
- <el-table-column prop="product" label="浜у搧鍚嶇О" width="200">
- <template #default="scope">
- <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item">
- <el-tree-select
- v-model="scope.row.productId"
- placeholder="璇烽�夋嫨"
- clearable
- check-strictly
- @change="getModels($event, scope.row)"
- :data="productOptions"
- :render-after-expand="false"
- style="width: 100%"
- />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" width="200">
- <template #default="scope">
- <el-form-item :prop="`products.${scope.$index}.productModelId`" class="product-table-form-item">
- <el-select
- v-model="scope.row.productModelId"
- placeholder="璇烽�夋嫨"
- clearable
- @change="getProductModel($event, scope.row)"
- style="width: 100%"
- >
- <el-option
- v-for="item in scope.row.modelOptions || []"
- :key="item.id"
- :label="item.model"
- :value="item.id"
+ <!-- 鍩烘湰淇℃伅 -->
+ <el-card class="form-card" shadow="hover">
+ <template #header>
+ <div class="card-header-wrapper">
+ <el-icon class="card-icon">
+ <Document/>
+ </el-icon>
+ <span class="card-title">鍩烘湰淇℃伅</span>
+ </div>
+ </template>
+ <div class="form-content">
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customer">
+ <el-select v-model="form.customer" placeholder="璇烽�夋嫨瀹㈡埛" style="width: 100%"
+ @change="handleCustomerChange" clearable>
+ <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName"
+ :value="item.customerName">
+ {{
+ item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
+ }}
+ </el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓氬姟鍛�" prop="salesperson">
+ <el-select v-model="form.salesperson" placeholder="璇烽�夋嫨涓氬姟鍛�" style="width: 100%" clearable>
+ <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
+ :value="item.nickName"/>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="鎶ヤ环鏃ユ湡" prop="quotationDate">
+ <el-date-picker
+ v-model="form.quotationDate"
+ type="date"
+ placeholder="閫夋嫨鎶ヤ环鏃ユ湡"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ clearable
/>
- </el-select>
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column prop="unit" label="鍗曚綅">
- <template #default="scope">
- <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item">
- <el-input v-model="scope.row.unit" placeholder="鍗曚綅" clearable/>
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column prop="unitPrice" label="鍗曚环">
- <template #default="scope">
- <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item">
- <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔" width="80" align="center">
- <template #default="scope">
- <el-button link type="danger" @click="removeProduct(scope.$index)">鍒犻櫎</el-button>
- </template>
- </el-table-column>
- </el-table>
- <el-empty v-else description="鏆傛棤浜у搧锛岃鐐瑰嚮娣诲姞浜у搧" :image-size="80" />
- </div>
- </el-card>
-
- <!-- 澶囨敞淇℃伅 -->
- <el-card class="form-card" shadow="hover">
- <template #header>
- <div class="card-header-wrapper">
- <el-icon class="card-icon"><EditPen /></el-icon>
- <span class="card-title">澶囨敞淇℃伅</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈夋晥鏈熻嚦" prop="validDate">
+ <el-date-picker
+ v-model="form.validDate"
+ type="date"
+ placeholder="閫夋嫨鏈夋晥鏈�"
+ style="width: 100%"
+ format="YYYY-MM-DD"
+ value-format="YYYY-MM-DD"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="浠樻鏂瑰紡" prop="paymentMethod">
+ <el-input v-model="form.paymentMethod" placeholder="璇疯緭鍏ヤ粯娆炬柟寮�" clearable/>
+ </el-form-item>
+ </el-col>
+ </el-row>
</div>
- </template>
- <div class="form-content">
- <el-form-item label="澶囨敞" prop="remark">
- <el-input
- type="textarea"
- v-model="form.remark"
- placeholder="璇疯緭鍏ュ娉ㄤ俊鎭紙閫夊~锛�"
- :rows="4"
- maxlength="500"
- show-word-limit
- ></el-input>
- </el-form-item>
- </div>
- </el-card>
- </el-form>
+ </el-card>
+
+ <!-- 浜у搧淇℃伅 -->
+ <el-card class="form-card" shadow="hover">
+ <template #header>
+ <div class="card-header-wrapper">
+ <el-icon class="card-icon">
+ <Box/>
+ </el-icon>
+ <span class="card-title">浜у搧淇℃伅</span>
+ <el-button type="primary" size="small" @click="addProduct" class="header-btn">
+ <el-icon>
+ <Plus/>
+ </el-icon>
+ 娣诲姞浜у搧
+ </el-button>
+ </div>
+ </template>
+ <div class="form-content">
+ <el-table :data="form.products" border style="width: 100%" class="product-table"
+ v-if="form.products.length > 0">
+ <el-table-column prop="product" label="浜у搧鍚嶇О" width="200">
+ <template #default="scope">
+ <el-form-item :prop="`products.${scope.$index}.productId`" class="product-table-form-item">
+ <el-tree-select
+ v-model="scope.row.productId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ check-strictly
+ @change="getModels($event, scope.row)"
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" width="200">
+ <template #default="scope">
+ <el-form-item :prop="`products.${scope.$index}.specificationId`" class="product-table-form-item">
+ <el-select
+ v-model="scope.row.specificationId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="getProductModel($event, scope.row)"
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in scope.row.modelOptions || []"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unit" label="鍗曚綅">
+ <template #default="scope">
+ <el-form-item :prop="`products.${scope.$index}.unit`" class="product-table-form-item">
+ <el-input v-model="scope.row.unit" placeholder="鍗曚綅" clearable/>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column prop="unitPrice" label="鍗曚环">
+ <template #default="scope">
+ <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item">
+ <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%"/>
+ </el-form-item>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" align="center">
+ <template #default="scope">
+ <el-button link type="danger" @click="removeProduct(scope.$index)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-empty v-else description="鏆傛棤浜у搧锛岃鐐瑰嚮娣诲姞浜у搧" :image-size="80"/>
+ </div>
+ </el-card>
+
+ <!-- 澶囨敞淇℃伅 -->
+ <el-card class="form-card" shadow="hover">
+ <template #header>
+ <div class="card-header-wrapper">
+ <el-icon class="card-icon">
+ <EditPen/>
+ </el-icon>
+ <span class="card-title">澶囨敞淇℃伅</span>
+ </div>
+ </template>
+ <div class="form-content">
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input
+ type="textarea"
+ v-model="form.remark"
+ placeholder="璇疯緭鍏ュ娉ㄤ俊鎭紙閫夊~锛�"
+ :rows="4"
+ maxlength="500"
+ show-word-limit
+ ></el-input>
+ </el-form-item>
+ </div>
+ </el-card>
+ </el-form>
</div>
</FormDialog>
+
+ <ImportDialog ref="importDialogRef"
+ v-model="importDialogVisible"
+ title="瀵煎叆鎶ヤ环鍗�"
+ :action="importAction"
+ :headers="importHeaders"
+ :auto-upload="false"
+ :on-success="handleImportSuccess"
+ :on-error="handleImportError"
+ @confirm="handleImportConfirm"
+ @download-template="handleDownloadTemplate"
+ @close="handleImportClose" />
+
+ <!-- 瀵煎叆璁板綍瀵硅瘽妗� -->
+ <el-dialog v-model="importLogDialogVisible" title="瀵煎叆璁板綍" width="900px">
+ <el-table :data="importLogList" border stripe v-loading="importLogLoading" height="400">
+ <el-table-column align="center" label="搴忓彿" type="index" width="60"/>
+ <el-table-column prop="batchNo" label="鎵规鍙�" min-width="180"/>
+ <el-table-column prop="fileName" label="鏂囦欢鍚�" min-width="160"/>
+ <el-table-column prop="totalCount" label="鎬绘暟" width="80" align="center"/>
+ <el-table-column prop="successCount" label="鎴愬姛" width="80" align="center"/>
+ <el-table-column prop="failCount" label="澶辫触" width="80" align="center"/>
+ <el-table-column prop="status" label="鐘舵��" width="100" align="center">
+ <template #default="{ row }">
+ <el-tag :type="row.status === 'completed' ? 'success' : 'danger'" disable-transitions>
+ {{ row.status === 'completed' ? '瀹屾垚' : row.status }}
+ </el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="createUserName" label="鎿嶄綔浜�" width="100"/>
+ <el-table-column prop="createTime" label="瀵煎叆鏃堕棿" width="160"/>
+ </el-table>
+ <pagination
+ v-if="importLogTotal > 0"
+ :total="importLogTotal"
+ layout="total, prev, pager, next"
+ :page="importLogPage.current"
+ :limit="importLogPage.size"
+ @pagination="handleImportLogPageChange"
+ />
+ </el-dialog>
+
+ <!-- 闄嶄环鍘嗗彶瀵硅瘽妗� -->
+ <el-dialog v-model="priceHistoryDialogVisible" title="闄嶄环鍘嗗彶" width="900px">
+ <el-table :data="priceHistoryList" border stripe v-loading="priceHistoryLoading" height="400">
+ <el-table-column align="center" label="搴忓彿" type="index" width="60"/>
+ <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="140"/>
+ <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" min-width="120"/>
+ <el-table-column prop="oldPrice" label="鍘熶环" width="100" align="center">
+ <template #default="{ row }">楼{{ row.oldPrice?.toFixed(2) }}</template>
+ </el-table-column>
+ <el-table-column prop="newPrice" label="鏂颁环" width="100" align="center">
+ <template #default="{ row }">楼{{ row.newPrice?.toFixed(2) }}</template>
+ </el-table-column>
+ <el-table-column prop="priceChange" label="鍙樺姩" width="100" align="center">
+ <template #default="{ row }">
+ <span :style="{ color: row.priceChange < 0 ? '#67C23A' : '#F56C6C' }">
+ {{ row.priceChange?.toFixed(2) }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="changeReason" label="鍘熷洜" width="100"/>
+ <el-table-column prop="importBatch" label="瀵煎叆鎵规" min-width="180"/>
+ <el-table-column prop="importTime" label="瀵煎叆鏃堕棿" width="160"/>
+ <el-table-column prop="createUserName" label="鎿嶄綔浜�" width="100"/>
+ </el-table>
+ </el-dialog>
<!-- 鏌ョ湅璇︽儏瀵硅瘽妗� -->
<el-dialog v-model="viewDialogVisible" title="鎶ヤ环璇︽儏" width="800px">
@@ -263,20 +356,22 @@
<el-descriptions-item label="鎶ヤ环鏃ユ湡">{{ currentQuotation.quotationDate }}</el-descriptions-item>
<el-descriptions-item label="鏈夋晥鏈熻嚦">{{ currentQuotation.validDate }}</el-descriptions-item>
<el-descriptions-item label="浠樻鏂瑰紡">{{ currentQuotation.paymentMethod }}</el-descriptions-item>
-<!-- <el-descriptions-item label="鎶ヤ环鐘舵��">-->
-<!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>-->
-<!-- </el-descriptions-item>-->
+ <!-- <el-descriptions-item label="鎶ヤ环鐘舵��">-->
+ <!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>-->
+ <!-- </el-descriptions-item>-->
<el-descriptions-item label="鎶ヤ环鎬婚" :span="2">
- <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">楼{{ currentQuotation.totalAmount?.toFixed(2) }}</span>
+ <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">楼{{
+ currentQuotation.totalAmount?.toFixed(2)
+ }}</span>
</el-descriptions-item>
</el-descriptions>
<div style="margin: 20px 0;">
<h4>浜у搧鏄庣粏</h4>
<el-table :data="currentQuotation.products" border style="width: 100%">
- <el-table-column prop="product" label="浜у搧鍚嶇О" />
- <el-table-column prop="specification" label="瑙勬牸鍨嬪彿" />
- <el-table-column prop="unit" label="鍗曚綅" />
+ <el-table-column prop="product" label="浜у搧鍚嶇О"/>
+ <el-table-column prop="specification" label="瑙勬牸鍨嬪彿"/>
+ <el-table-column prop="unit" label="鍗曚綅"/>
<el-table-column prop="unitPrice" label="鍗曚环">
<template #default="scope">
楼{{ scope.row.unitPrice.toFixed(2) }}
@@ -294,28 +389,48 @@
</template>
<script setup>
-import { ref, reactive, computed, onMounted, markRaw, shallowRef } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { Search, Document, Box, EditPen, Plus } from '@element-plus/icons-vue'
+import {ref, reactive, computed, onMounted, nextTick, getCurrentInstance} from 'vue'
+import {ElMessage, ElMessageBox} from 'element-plus'
+import {
+ Search,
+ Document,
+ UserFilled,
+ Box,
+ EditPen,
+ Plus,
+ ArrowRight,
+ Delete,
+} from '@element-plus/icons-vue'
import Pagination from '@/components/PIMTable/Pagination.vue'
import FormDialog from '@/components/Dialog/FormDialog.vue'
-import {getQuotationList,addQuotation,updateQuotation,deleteQuotation} from '@/api/salesManagement/salesQuotation.js'
+import ImportDialog from '@/components/Dialog/ImportDialog.vue'
+import {
+ getQuotationList,
+ addQuotation,
+ updateQuotation,
+ deleteQuotation,
+ downloadQuotationTemplate,
+ getImportLogList,
+ getPriceHistoryList
+} from '@/api/salesManagement/salesQuotation.js'
+import {userListNoPage} from "@/api/system/user.js";
+import {customerList} from "@/api/salesManagement/salesLedger.js";
import {modelList, productTreeList} from "@/api/basicData/product.js";
-import {listCustomer} from "@/api/basicData/customer.js";
-import { userListNoPage } from "@/api/system/user.js";
+import {getToken} from "@/utils/auth";
+
+const {proxy} = getCurrentInstance();
// 鍝嶅簲寮忔暟鎹�
const loading = ref(false)
const searchForm = reactive({
quotationNo: '',
- customerId: '',
+ customer: '',
status: ''
})
const quotationList = ref([])
-const userList = ref([])
const productOptions = ref([]);
-const modelOptions = ref([]);
+const modelOptions = ref([]);
const pagination = reactive({
total: 3,
currentPage: 1,
@@ -323,11 +438,27 @@
})
const dialogVisible = ref(false)
+const importDialogVisible = ref(false)
+const importLogDialogVisible = ref(false)
+const priceHistoryDialogVisible = ref(false)
const viewDialogVisible = ref(false)
-const dialogTitle = ref('鏂板鎶ヤ环')
+const importDialogRef = ref(null)
+const importAction = import.meta.env.VITE_APP_BASE_API + "/sales/quotation/import"
+const importHeaders = ref({
+ Authorization: `Bearer ${getToken()}`,
+})
+const importLogList = ref([])
+const importLogLoading = ref(false)
+const importLogTotal = ref(0)
+const importLogPage = reactive({ current: 1, size: 10 })
+const priceHistoryList = ref([])
+const priceHistoryLoading = ref(false)
+const currentQuotationForLog = ref(null)
+const currentQuotationForPriceHistory = ref(null)
+
+const dialogTitle= ref('鏂板鎶ヤ环')
const form = reactive({
quotationNo: '',
- customerId: undefined,
customer: '',
salesperson: '',
quotationDate: '',
@@ -345,35 +476,126 @@
})
const baseRules = {
- customer: [{ required: true, message: '璇烽�夋嫨瀹㈡埛', trigger: 'change' }],
- salesperson: [{ required: true, message: '璇烽�夋嫨涓氬姟鍛�', trigger: 'change' }],
- quotationDate: [{ required: true, message: '璇烽�夋嫨鎶ヤ环鏃ユ湡', trigger: 'change' }],
- validDate: [{ required: true, message: '璇烽�夋嫨鏈夋晥鏈�', trigger: 'change' }],
- paymentMethod: [{ required: true, message: '璇疯緭鍏ヤ粯娆炬柟寮�', trigger: 'blur' }]
+ customer: [{required: true, message: '璇烽�夋嫨瀹㈡埛', trigger: 'change'}],
+ salesperson: [{required: true, message: '璇烽�夋嫨涓氬姟鍛�', trigger: 'change'}],
+ quotationDate: [{required: true, message: '璇烽�夋嫨鎶ヤ环鏃ユ湡', trigger: 'change'}],
+ validDate: [{required: true, message: '璇烽�夋嫨鏈夋晥鏈�', trigger: 'change'}],
+ paymentMethod: [{required: true, message: '璇疯緭鍏ヤ粯娆炬柟寮�', trigger: 'blur'}]
}
const productRowRules = {
- productId: [{ required: true, message: '璇烽�夋嫨浜у搧鍚嶇О', trigger: 'change' }],
- productModelId: [{ required: true, message: '璇烽�夋嫨瑙勬牸鍨嬪彿', trigger: 'change' }],
- unit: [{ required: true, message: '璇峰~鍐欏崟浣�', trigger: 'blur' }],
- unitPrice: [{ required: true, message: '璇峰~鍐欏崟浠�', trigger: 'change' }]
+ productId: [{required: true, message: '璇烽�夋嫨浜у搧鍚嶇О', trigger: 'change'}],
+ specificationId: [{required: true, message: '璇烽�夋嫨瑙勬牸鍨嬪彿', trigger: 'change'}],
+ unit: [{required: true, message: '璇峰~鍐欏崟浣�', trigger: 'blur'}],
+ unitPrice: [{required: true, message: '璇峰~鍐欏崟浠�', trigger: 'change'}]
}
const rules = computed(() => {
- const r = { ...baseRules }
+ const r = {...baseRules}
;(form.products || []).forEach((_, i) => {
r[`products.${i}.productId`] = productRowRules.productId
- r[`products.${i}.productModelId`] = productRowRules.productModelId
+ r[`products.${i}.specificationId`] = productRowRules.specificationId
r[`products.${i}.unit`] = productRowRules.unit
r[`products.${i}.unitPrice`] = productRowRules.unitPrice
})
return r
})
+const userList = ref([]);
const customerOption = ref([]);
const isEdit = ref(false)
const editId = ref(null)
const currentQuotation = ref({})
const formRef = ref()
+
+// 瀵煎叆鎴愬姛
+const handleImportSuccess = (response) => {
+ if (response.code === 200) {
+ ElMessage.success("瀵煎叆鎴愬姛")
+ importDialogVisible.value = false
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles()
+ }
+ handleSearch()
+ } else {
+ ElMessage.error(response.msg || "瀵煎叆澶辫触")
+ }
+}
+
+// 瀵煎叆澶辫触
+const handleImportError = () => {
+ ElMessage.error("瀵煎叆澶辫触锛岃妫�鏌ユ枃浠舵牸寮忔槸鍚︽纭�")
+}
+
+// 纭瀵煎叆
+const handleImportConfirm = () => {
+ if (importDialogRef.value) {
+ importDialogRef.value.submit()
+ }
+}
+
+// 涓嬭浇妯℃澘
+const handleDownloadTemplate = () => {
+ downloadQuotationTemplate().then(blob => {
+ const url = window.URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = '鎶ヤ环鍗曞鍏ユā鏉�.xlsx'
+ a.click()
+ window.URL.revokeObjectURL(url)
+ })
+}
+
+// 鍏抽棴瀵煎叆寮圭獥
+const handleImportClose = () => {
+ importDialogVisible.value = false
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles()
+ }
+}
+
+// 瀵煎叆璁板綍
+const handleShowImportLog = () => {
+ importLogPage.current = 1
+ importLogDialogVisible.value = true
+ fetchImportLogList()
+}
+
+const fetchImportLogList = () => {
+ importLogLoading.value = true
+ getImportLogList({ pageNum: importLogPage.current, pageSize: importLogPage.size }).then(res => {
+ if (res.code === 200) {
+ importLogList.value = res.data.records || []
+ importLogTotal.value = res.data.total || 0
+ }
+ }).finally(() => {
+ importLogLoading.value = false
+ })
+}
+
+const handleImportLogPageChange = (val) => {
+ importLogPage.current = val.page
+ importLogPage.size = val.limit
+ fetchImportLogList()
+}
+
+// 闄嶄环鍘嗗彶
+const handleShowPriceHistory = (row) => {
+ currentQuotationForPriceHistory.value = row
+ priceHistoryDialogVisible.value = true
+ priceHistoryList.value = []
+ fetchPriceHistoryList(row)
+}
+
+const fetchPriceHistoryList = (row) => {
+ priceHistoryLoading.value = true
+ getPriceHistoryList({ quotationProductId: row.id }).then(res => {
+ priceHistoryList.value = res.data
+ }).finally(() => {
+ priceHistoryLoading.value = false
+ })
+}
+
+const handlePriceHistoryPageChange = () => {}
// 璁$畻灞炴��
const filteredList = computed(() => {
@@ -401,113 +623,133 @@
handleSearch()
}
+// 瀵煎叆鏂囦欢
+const handleImport = () => {
+ importDialogVisible.value = true
+ nextTick(() => {
+ if (importDialogRef.value) {
+ importDialogRef.value.clearFiles()
+ }
+ })
+}
+
const handleAdd = async () => {
dialogTitle.value = '鏂板鎶ヤ环'
isEdit.value = false
resetForm()
dialogVisible.value = true
+ let userLists = await userListNoPage();
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ userList.value = (userLists.data || []).map(item => ({
+ userId: item.userId,
+ nickName: item.nickName || '',
+ userName: item.userName || ''
+ }));
getProductOptions();
- fetchCustomerOptions()
-}
-
-const fetchCustomerOptions = () => {
- if (customerOption.value.length > 0) return
- listCustomer({current: -1,size:-1, type: 0}).then((res) => {
- customerOption.value = res.data.records;
+ customerList().then((res) => {
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
+ id: item.id,
+ customerName: item.customerName || '',
+ taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
+ }))
});
}
const getProductOptions = () => {
- // 杩斿洖 Promise锛屼究浜庣紪杈戞椂 await 纭繚鑳藉弽鏄�
- return productTreeList().then((res) => {
- productOptions.value = convertIdToValue(res);
- return productOptions.value
- });
+ // 杩斿洖 Promise锛屼究浜庣紪杈戞椂 await 纭繚鑳藉弽鏄�
+ return productTreeList().then((res) => {
+ productOptions.value = convertIdToValue(res);
+ return productOptions.value
+ });
};
-function convertIdToValue(data) {
- return data.map((item) => {
- const { id, children, ...rest } = item;
- const newItem = {
- ...rest,
- value: id, // 灏� id 鏀逛负 value
- };
- if (children && children.length > 0) {
- newItem.children = convertIdToValue(children);
- }
- return newItem;
- });
+function convertIdToValue(data) {
+ return data.map((item) => {
+ const {id, children, ...rest} = item;
+ const newItem = {
+ ...rest,
+ value: id, // 灏� id 鏀逛负 value
+ };
+ if (children && children.length > 0) {
+ newItem.children = convertIdToValue(children);
+ }
+
+ return newItem;
+ });
}
+
// 鏍规嵁鍚嶇О鍙嶆煡鑺傜偣 id锛屼究浜庝粎瀛樺悕绉版椂鐨勫弽鏄�
function findNodeIdByLabel(nodes, label) {
- if (!label) return null;
- for (let i = 0; i < nodes.length; i++) {
- const node = nodes[i];
- if (node.label === label) return node.value;
- if (node.children && node.children.length > 0) {
- const found = findNodeIdByLabel(node.children, label);
- if (found !== null && found !== undefined) return found;
- }
- }
- return null;
+ if (!label) return null;
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i];
+ if (node.label === label) return node.value;
+ if (node.children && node.children.length > 0) {
+ const found = findNodeIdByLabel(node.children, label);
+ if (found !== null && found !== undefined) return found;
+ }
+ }
+ return null;
}
+
const getModels = (value, row) => {
- if (!row) return;
- // 濡傛灉娓呯┖閫夋嫨锛屽垯娓呯┖鐩稿叧瀛楁
- if (!value) {
- row.productId = '';
- row.product = '';
- row.modelOptions = [];
- row.productModelId = '';
- row.specification = '';
- row.unit = '';
- return;
- }
- // 鏇存柊 productId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
- row.productId = value;
- // 鎵惧埌瀵瑰簲鐨� label 骞惰祴鍊肩粰 row.product
- const label = findNodeById(productOptions.value, value);
- if (label) {
- row.product = label;
- }
- // 鑾峰彇瑙勬牸鍨嬪彿鍒楄〃锛岃缃埌褰撳墠琛岀殑 modelOptions
- modelList({ id: value }).then((res) => {
- row.modelOptions = res || [];
- });
+ if (!row) return;
+ // 濡傛灉娓呯┖閫夋嫨锛屽垯娓呯┖鐩稿叧瀛楁
+ if (!value) {
+ row.productId = '';
+ row.product = '';
+ row.modelOptions = [];
+ row.specificationId = '';
+ row.specification = '';
+ row.unit = '';
+ return;
+ }
+ // 鏇存柊 productId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
+ row.productId = value;
+ // 鎵惧埌瀵瑰簲鐨� label 骞惰祴鍊肩粰 row.product
+ const label = findNodeById(productOptions.value, value);
+ if (label) {
+ row.product = label;
+ }
+ // 鑾峰彇瑙勬牸鍨嬪彿鍒楄〃锛岃缃埌褰撳墠琛岀殑 modelOptions
+ modelList({id: value}).then((res) => {
+ row.modelOptions = res || [];
+ });
};
const getProductModel = (value, row) => {
- if (!row) return;
- // 濡傛灉娓呯┖閫夋嫨锛屽垯娓呯┖鐩稿叧瀛楁
- if (!value) {
- row.productModelId = '';
- row.specification = '';
- row.unit = '';
- return;
- }
- // 鏇存柊 productModelId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
- row.productModelId = value;
- const modelOptions = row.modelOptions || [];
- const index = modelOptions.findIndex((item) => item.id === value);
- if (index !== -1) {
- row.specification = modelOptions[index].model;
- row.unit = modelOptions[index].unit;
- } else {
- row.specification = '';
- row.unit = '';
- }
+ if (!row) return;
+ // 濡傛灉娓呯┖閫夋嫨锛屽垯娓呯┖鐩稿叧瀛楁
+ if (!value) {
+ row.specificationId = '';
+ row.specification = '';
+ row.unit = '';
+ return;
+ }
+ // 鏇存柊 specificationId锛坴-model 宸茬粡鑷姩鏇存柊锛岃繖閲岀‘淇濅竴鑷存�э級
+ row.specificationId = value;
+ const modelOptions = row.modelOptions || [];
+ const index = modelOptions.findIndex((item) => item.id === value);
+ if (index !== -1) {
+ row.specification = modelOptions[index].model;
+ row.unit = modelOptions[index].unit;
+ } else {
+ row.specification = '';
+ row.unit = '';
+ }
};
const findNodeById = (nodes, productId) => {
- for (let i = 0; i < nodes.length; i++) {
- if (nodes[i].value === productId) {
- return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥� label
- }
- if (nodes[i].children && nodes[i].children.length > 0) {
- const foundLabel = findNodeById(nodes[i].children, productId);
- if (foundLabel) {
- return foundLabel; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖 label
- }
- }
- }
- return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].value === productId) {
+ return nodes[i].label; // 鎵惧埌鑺傜偣锛岃繑鍥� label
+ }
+ if (nodes[i].children && nodes[i].children.length > 0) {
+ const foundLabel = findNodeById(nodes[i].children, productId);
+ if (foundLabel) {
+ return foundLabel; // 鍦ㄥ瓙鑺傜偣涓壘鍒帮紝杩斿洖 label
+ }
+ }
+ }
+ return null; // 娌℃湁鎵惧埌鑺傜偣锛岃繑鍥瀗ull
};
const handleView = (row) => {
// 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
@@ -523,7 +765,7 @@
products: row.products ? row.products.map(product => ({
productId: product.productId || '',
product: product.product || product.productName || '',
- productModelId: product.productModelId || '',
+ specificationId: product.specificationId || '',
specification: product.specification || '',
quantity: product.quantity || 0,
unit: product.unit || '',
@@ -542,12 +784,10 @@
form.id = row.id || form.id || null
// 鍏堝姞杞戒骇鍝佹爲鏁版嵁锛屽惁鍒� el-tree-select 鏃犳硶鍙嶆樉浜у搧鍚嶇О
await getProductOptions()
- await fetchCustomerOptions()
// 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
form.quotationNo = row.quotationNo || ''
form.customer = row.customer || ''
- form.customerId = row.customerId || undefined
form.salesperson = row.salesperson || ''
form.quotationDate = row.quotationDate || ''
form.validDate = row.validDate || ''
@@ -558,23 +798,23 @@
const productName = product.product || product.productName || ''
// 浼樺厛鐢� productId锛涘鏋滃彧鏈夊悕绉帮紝灏濊瘯鍙嶆煡 id 浠ヤ究鏍戦�夋嫨鍣ㄥ弽鏄�
const resolvedProductId = product.productId
- ? Number(product.productId)
- : findNodeIdByLabel(productOptions.value, productName) || ''
+ ? Number(product.productId)
+ : findNodeIdByLabel(productOptions.value, productName) || ''
// 濡傛灉鏈変骇鍝両D锛屽姞杞藉搴旂殑瑙勬牸鍨嬪彿鍒楄〃
let modelOptions = [];
- let resolvedProductModelId = product.productModelId || '';
+ let resolvedSpecificationId = product.specificationId || '';
if (resolvedProductId) {
try {
- const res = await modelList({ id: resolvedProductId });
+ const res = await modelList({id: resolvedProductId});
modelOptions = res || [];
- // 濡傛灉杩斿洖鐨勬暟鎹病鏈� productModelId锛屼絾鏈� specification 鍚嶇О锛屾牴鎹悕绉版煡鎵� ID
- if (!resolvedProductModelId && product.specification) {
+ // 濡傛灉杩斿洖鐨勬暟鎹病鏈� specificationId锛屼絾鏈� specification 鍚嶇О锛屾牴鎹悕绉版煡鎵� ID
+ if (!resolvedSpecificationId && product.specification) {
const foundModel = modelOptions.find(item => item.model === product.specification);
if (foundModel) {
- resolvedProductModelId = foundModel.id;
+ resolvedSpecificationId = foundModel.id;
}
}
} catch (error) {
@@ -585,7 +825,7 @@
return {
productId: resolvedProductId,
product: productName,
- productModelId: resolvedProductModelId,
+ specificationId: resolvedSpecificationId,
specification: product.specification || '',
quantity: product.quantity || 0,
unit: product.unit || '',
@@ -601,6 +841,14 @@
form.discountAmount = row.discountAmount || 0
form.totalAmount = row.totalAmount || 0
+ // 鍔犺浇鐢ㄦ埛鍒楄〃
+ let userLists = await userListNoPage();
+ userList.value = (userLists.data || []).map(item => ({
+ userId: item.userId,
+ nickName: item.nickName || '',
+ userName: item.userName || ''
+ }));
+
dialogVisible.value = true
}
@@ -613,9 +861,9 @@
}).then(() => {
const index = quotationList.value.findIndex(item => item.id === row.id)
if (index > -1) {
- deleteQuotation(row.id).then(res=>{
+ deleteQuotation(row.id).then(res => {
// console.log(res)
- if(res.code===200){
+ if (res.code === 200) {
ElMessage.success('鍒犻櫎鎴愬姛')
handleSearch()
}
@@ -649,7 +897,8 @@
productId: '',
product: '',
productName: '',
- productModelId: '',
+ specificationId: '',
+ specification: '',
quantity: 1,
unit: '',
unitPrice: 0,
@@ -678,6 +927,10 @@
form.totalAmount = form.subtotal + form.freight + form.otherFee - form.discountAmount
}
+const handleCustomerChange = () => {
+ // 鍙互鏍规嵁瀹㈡埛淇℃伅鑷姩濉厖涓�浜涢粯璁ゅ��
+}
+
const handleSubmit = () => {
formRef.value.validate((valid) => {
if (valid) {
@@ -692,14 +945,13 @@
return sum + price
}, 0)
- form.customer = customerOption.value.find(item => item.id === form.customerId)?.customerName || ''
if (isEdit.value) {
// 缂栬緫
const index = quotationList.value.findIndex(item => item.id === editId.value)
if (index > -1) {
- updateQuotation(form).then(res=>{
+ updateQuotation(form).then(res => {
// console.log(res)
- if(res.code===200){
+ if (res.code === 200) {
ElMessage.success('缂栬緫鎴愬姛')
dialogVisible.value = false
handleSearch()
@@ -708,8 +960,8 @@
}
} else {
// 鏂板
- addQuotation(form).then(res=>{
- if(res.code===200){
+ addQuotation(form).then(res => {
+ if (res.code === 200) {
ElMessage.success('鏂板鎴愬姛')
dialogVisible.value = false
handleSearch()
@@ -721,40 +973,41 @@
})
}
+const downloadImportTemplate = () => {
+ proxy.download("/sales/quotation/downloadTemplate", {}, "鎶ヤ环鍗曞鍏ユā鏉�.xlsx");
+}
+
const handleCurrentChange = (val) => {
pagination.currentPage = val.page
pagination.pageSize = val.limit
// 鍒嗛〉鍙樺寲鏃堕噸鏂版煡璇㈠垪琛�
handleSearch()
}
-const handleSearch = ()=>{
+const handleSearch = () => {
const params = {
// 鍚庣鍒嗛〉鍙傛暟锛歝urrent / size
current: pagination.currentPage,
size: pagination.pageSize,
...searchForm
}
- getQuotationList(params).then(res=>{
+ getQuotationList(params).then(res => {
// console.log(res)
- if(res.code===200){
+ if (res.code === 200) {
// 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鎴栧叾浠栧璞℃斁鍏ュ搷搴斿紡瀵硅薄
quotationList.value = (res.data.records || []).map(item => ({
id: item.id,
quotationNo: item.quotationNo || '',
customer: item.customer || '',
- customerId: item.customerId || undefined,
salesperson: item.salesperson || '',
quotationDate: item.quotationDate || '',
validDate: item.validDate || '',
paymentMethod: item.paymentMethod || '',
status: item.status || '鑽夌',
- // 瀹℃壒浜猴紙鐢ㄤ簬缂栬緫鏃跺弽鏄撅級
- approveUserIds: item.approveUserIds || '',
remark: item.remark || '',
products: item.products ? item.products.map(product => ({
productId: product.productId || '',
product: product.product || product.productName || '',
- productModelId: product.productModelId || '',
+ specificationId: product.specificationId || '',
specification: product.specification || '',
quantity: product.quantity || 0,
unit: product.unit || '',
@@ -771,25 +1024,18 @@
pagination.total = res.data.total
}
})
- // customerList().then((res) => {
- // customerOption.value = res;
- // });
+ customerList().then((res) => {
+ // 鍙鍒堕渶瑕佺殑瀛楁锛岄伩鍏嶅皢缁勪欢寮曠敤鏀惧叆鍝嶅簲寮忓璞�
+ customerOption.value = (Array.isArray(res) ? res : []).map(item => ({
+ id: item.id,
+ customerName: item.customerName || '',
+ taxpayerIdentificationNumber: item.taxpayerIdentificationNumber || ''
+ }))
+ });
}
-const getUserList = async () => {
- try {
- const res = await userListNoPage()
- userList.value = Array.isArray(res?.data) ? res.data : []
- } catch (error) {
- userList.value = []
- ElMessage.error('鍔犺浇涓氬姟鍛樺垪琛ㄥけ璐�')
- }
-}
-
-onMounted(()=>{
- getUserList()
+onMounted(() => {
handleSearch()
- fetchCustomerOptions()
})
</script>
@@ -872,13 +1118,80 @@
.product-table-form-item {
margin-bottom: 0;
+
:deep(.el-form-item__content) {
margin-left: 0 !important;
}
+
:deep(.el-form-item__label) {
width: auto;
min-width: auto;
}
+}
+
+.approver-nodes-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 24px;
+ padding: 12px 0;
+}
+
+.approver-node-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ padding: 16px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e4e7ed;
+ transition: all 0.3s ease;
+ min-width: 180px;
+
+ &:hover {
+ border-color: #409eff;
+ background: #f0f7ff;
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+ }
+}
+
+.approver-node-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: #606266;
+
+ .node-step {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ background: #409eff;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 12px;
+ font-weight: 600;
+ }
+
+ .node-text {
+ font-weight: 500;
+ }
+
+ .arrow-icon {
+ color: #909399;
+ font-size: 14px;
+ }
+}
+
+.approver-select {
+ width: 100%;
+ min-width: 150px;
+}
+
+.remove-btn {
+ margin-top: 4px;
}
.product-table {
@@ -907,4 +1220,14 @@
text-align: right;
}
+// 鍝嶅簲寮忎紭鍖�
+@media (max-width: 1200px) {
+ .approver-nodes-container {
+ gap: 16px;
+ }
+
+ .approver-node-item {
+ min-width: 160px;
+ }
+}
</style>
--
Gitblit v1.9.3