PriceSheetDetail.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <template>
  2. <div v-loading="loading">
  3. <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="formDom">
  4. <template #chart>
  5. <div style="width:100%;padding-left:25px">
  6. <div ref="chartDom" style="height:300px"></div>
  7. </div>
  8. </template>
  9. <template #buyer>
  10. <div style="width: 100%">
  11. <el-form-item label="客户信息" prop="buyCorporationId" class="wid100">
  12. <el-select v-model="formData.data.buyCorporationId" filterable remote reserve-keyword placeholder="请输入关键字" remote-show-suffix
  13. :remote-method="remoteMethod" :loading="loadingSearch" @input="remoteMethod" style="width: 100%" @change="changeCustomer">
  14. <el-option v-for="item in customerList" :key="item.value" :label="item.label" :value="item.value" />
  15. </el-select>
  16. </el-form-item>
  17. <el-row style="width: 100%">
  18. <el-col :span="12">
  19. <el-form-item label="地址" class="wid100 margin-b-0">
  20. <el-row style="padding-right:5px;width:100%">
  21. <el-col :span="6">
  22. <el-form-item label="" prop="buyCountryId" class="margin-b-0 wid100" label-width="0px">
  23. <el-select v-model="formData.data.buyCountryId" placeholder="国家" style="width:100%" filterable
  24. @change="(val) => getCityData(val, '20', true)">
  25. <el-option v-for="item in countryData" :label="item.name" :value="item.id">
  26. </el-option>
  27. </el-select>
  28. </el-form-item>
  29. </el-col>
  30. <el-col :span="6">
  31. <el-form-item label="" prop="provinceName" class="margin-b-0 wid100" label-width="0px">
  32. <selectCity placeholder="省/洲" @change="(val) => getCityData(val, '30', true)" addressId="buyProvinceId"
  33. addressName="provinceName" v-model="formData.data" :data="provinceData">
  34. </selectCity>
  35. </el-form-item>
  36. </el-col>
  37. <el-col :span="6">
  38. <el-form-item label="" prop="cityName" class="margin-b-0 wid100" label-width="0px">
  39. <selectCity placeholder="城市" addressId="buyCityId" addressName="cityName" v-model="formData.data" :data="cityData">
  40. </selectCity>
  41. </el-form-item>
  42. </el-col>
  43. <el-col :span="6">
  44. <el-form-item label="" prop="buyPostalCode" class="margin-b-0" label-width="0px">
  45. <el-input v-model="formData.data.buyPostalCode" placeholder="请输入邮编" />
  46. </el-form-item>
  47. </el-col>
  48. </el-row>
  49. </el-form-item>
  50. </el-col>
  51. <el-col :span="12" style="padding-left:5px">
  52. <el-form-item label="详细地址" prop="buyAddress" class="margin-b-0 wid100">
  53. <el-input v-model="formData.data.buyAddress" type="text" placeholder="请输入详细地址">
  54. </el-input>
  55. </el-form-item>
  56. </el-col>
  57. </el-row>
  58. </div>
  59. </template>
  60. <template #commodity>
  61. <div style="width: 100%;padding-left:25px">
  62. <el-table :data="formData.data.quotationProductList" style="width: 100%;" default-expand-all>
  63. <el-table-column type="expand" width="50" align="center">
  64. <template #default="scope">
  65. <div style="padding-left:50px">
  66. <div style="margin-bottom:10px;">
  67. <TitleInfo content='BOM单:'></TitleInfo>
  68. </div>
  69. <el-table :data="scope.row.quotationProductBomList" style="width: 100%;" border class="bom-table">
  70. <el-table-column label="图片" width="80">
  71. <template #default="{ row }">
  72. <div v-if="row.fileUrl">
  73. <img :src="row.fileUrl" class="pic" @click="onPicture(row.fileUrl)" />
  74. </div>
  75. <div v-else></div>
  76. </template>
  77. </el-table-column>
  78. <el-table-column prop="productCode" label="物料编码" width="190" />
  79. <el-table-column prop="productName" label="物料名称" min-width="200" />
  80. <el-table-column label="尺寸 (cm)" width="150">
  81. <template #default="{ row, $index }">
  82. <div style="width: 100%">
  83. {{row.productLength}} * {{row.productWidth}} * {{row.productHeight}}
  84. </div>
  85. </template>
  86. </el-table-column>
  87. <el-table-column prop="quantity" label="数量" width="110" />
  88. <!-- <template #default="{ row, $index }">
  89. <div style="width: 100%">
  90. <el-form-item :prop="'quotationProductList.' + scope.$index + '.quotationProductBomList.' + $index + '.quantity'"
  91. :rules="rules.quantity" :inline-message="true" class="margin-b-0 wid100">
  92. <el-input-number onmousewheel="return false;" v-model="row.quantity" placeholder="请输入" style="width: 100%" :precision="0"
  93. :controls="false" :min="1" :disabled="row.type==1" @change="changeQuantity()" />
  94. </el-form-item>
  95. </div>
  96. </template> -->
  97. <el-table-column prop="allQuantity" label="总量" width="80" />
  98. <el-table-column prop="price" label="单价" width="110">
  99. <template #default="{ row, $index }">
  100. <div style="width: 100%">
  101. ¥ {{row.price}}
  102. </div>
  103. </template>
  104. </el-table-column>
  105. <el-table-column prop="amount" label="小计" width="110">
  106. <template #default="{ row, $index }">
  107. <div style="width: 100%">
  108. ¥ {{row.amount}}
  109. </div>
  110. </template>
  111. </el-table-column>
  112. <el-table-column prop="remark" label="备注" width="180" />
  113. <!-- <template #default="{ row, $index }">
  114. <div style="width: 100%">
  115. <el-form-item :prop="'quotationProductList.' + scope.$index + '.quotationProductBomList.' + $index + '.remark'"
  116. :rules="rules.remark" :inline-message="true" class="margin-b-0 wid100">
  117. <el-input v-model="row.remark" placeholder="请输入" style="width: 100%" :min="0" />
  118. </el-form-item>
  119. </div>
  120. </template> -->
  121. </el-table>
  122. </div>
  123. </template>
  124. </el-table-column>
  125. <el-table-column label="图片" width="80">
  126. <template #default="{ row }">
  127. <div v-if="row.fileUrl">
  128. <img :src="row.fileUrl" class="pic" @click="onPicture(row.fileUrl)" />
  129. </div>
  130. <div v-else></div>
  131. </template>
  132. </el-table-column>
  133. <el-table-column prop="productCode" label="商品编码" width="190" />
  134. <el-table-column prop="productName" label="商品名称" min-width="200" />
  135. <el-table-column label="尺寸 (cm)" width="150">
  136. <template #default="{ row, $index }">
  137. <div style="width: 100%">
  138. {{row.productLength}} * {{row.productWidth}} * {{row.productHeight}}
  139. </div>
  140. </template>
  141. </el-table-column>
  142. <el-table-column prop="productColor" label="颜色" width="100" />
  143. <el-table-column prop="quantity" label="数量" width="110" />
  144. <!-- <template #default="{ row, $index }">
  145. <div style="width: 100%">
  146. <el-form-item :prop="'quotationProductList.' + $index + '.quantity'" :rules="rules.quantity" :inline-message="true"
  147. class="margin-b-0 wid100">
  148. <el-input-number onmousewheel="return false;" v-model="row.quantity" placeholder="请输入" style="width: 100%" :precision="0"
  149. :controls="false" :min="1" @change="changeQuantity()" />
  150. </el-form-item>
  151. </div>
  152. </template> -->
  153. <el-table-column prop="price" label="单价" width="110">
  154. <template #default="{ row, $index }">
  155. <div style="width: 100%">
  156. ¥ {{row.price}}
  157. </div>
  158. </template>
  159. </el-table-column>
  160. <el-table-column prop="amount" label="小计" width="110">
  161. <template #default="{ row, $index }">
  162. <div style="width: 100%">
  163. ¥ {{row.amount}}
  164. </div>
  165. </template>
  166. </el-table-column>
  167. </el-table>
  168. </div>
  169. </template>
  170. </byForm>
  171. </div>
  172. </template>
  173. <script setup>
  174. import byForm from "@/components/byForm/index";
  175. import selectCity from "@/components/selectCity/index.vue";
  176. import * as echarts from "echarts";
  177. const { proxy } = getCurrentInstance();
  178. const props = defineProps({
  179. rowData: Object,
  180. dataType: {
  181. type: String,
  182. default: "1",
  183. },
  184. });
  185. const typeData = ref([
  186. {
  187. label: "常规订单",
  188. value: 1,
  189. },
  190. {
  191. label: "出口退税订单",
  192. value: 2,
  193. },
  194. ]);
  195. const treeData = ref([]);
  196. const countryData = ref([]);
  197. const provinceData = ref([]);
  198. const cityData = ref([]);
  199. const formData = reactive({
  200. data: {
  201. type: 1,
  202. quotationProductList: [],
  203. },
  204. });
  205. const loading = ref(false);
  206. const formDom = ref(null);
  207. const formOption = reactive({
  208. inline: true,
  209. labelWidth: 100,
  210. itemWidth: 100,
  211. disabled: true,
  212. });
  213. let myChart = null;
  214. const chartDom = ref(null);
  215. const isShowChart = ref(false);
  216. const formConfig = computed(() => {
  217. return [
  218. // {
  219. // type: "title1",
  220. // title: "报价趋势",
  221. // isShow: isShowChart.value,
  222. // },
  223. // {
  224. // type: "slot",
  225. // slotName: "chart",
  226. // isShow: isShowChart.value,
  227. // },
  228. {
  229. type: "title1",
  230. title: "报价类型",
  231. },
  232. {
  233. type: "input",
  234. prop: "code",
  235. label: "报价单号",
  236. isShow: formData.data.code ? true : false,
  237. },
  238. {
  239. type: "select",
  240. prop: "type",
  241. label: "报价类型",
  242. data: typeData.value,
  243. itemWidth: 50,
  244. },
  245. {
  246. type: "treeSelect",
  247. prop: "companyId",
  248. label: "报价公司",
  249. data: treeData.value,
  250. propsTreeLabel: "deptName",
  251. propsTreeValue: "deptId",
  252. itemWidth: 50,
  253. fn: (val) => {
  254. companyId.value = val;
  255. },
  256. },
  257. {
  258. type: "title1",
  259. title: "贸易信息",
  260. },
  261. {
  262. type: "slot",
  263. slotName: "buyer",
  264. label: "",
  265. itemWidth: 100,
  266. },
  267. {
  268. type: "input",
  269. itemType: "text",
  270. label: "联系人",
  271. prop: "buyContactName",
  272. itemWidth: 50,
  273. },
  274. {
  275. type: "input",
  276. itemType: "text",
  277. label: "联系人电话",
  278. prop: "buyContactNumber",
  279. itemWidth: 50,
  280. },
  281. {
  282. type: "title1",
  283. title: "商品信息",
  284. },
  285. {
  286. type: "slot",
  287. slotName: "commodity",
  288. label: "",
  289. },
  290. {
  291. type: "title1",
  292. title: "报价总金额",
  293. },
  294. {
  295. type: "input",
  296. prop: "amount",
  297. label: "报价总金额",
  298. itemWidth: 25,
  299. disabled: true,
  300. },
  301. ];
  302. });
  303. const getCityData = (id, type, isChange = false) => {
  304. proxy.post("/customizeArea/list", { parentId: id }).then((res) => {
  305. if (type === "20") {
  306. provinceData.value = res;
  307. if (isChange) {
  308. formData.data.buyProvinceId = "";
  309. formData.data.provinceName = "";
  310. formData.data.buyCityId = "";
  311. formData.data.cityName = "";
  312. }
  313. } else if (type === "30") {
  314. cityData.value = res;
  315. if (isChange) {
  316. formData.data.buyCityId = "";
  317. formData.data.cityName = "";
  318. }
  319. } else {
  320. countryData.value = res;
  321. }
  322. });
  323. };
  324. getCityData("0");
  325. const getDict = () => {
  326. proxy
  327. .get("/tenantDept/list", {
  328. pageNum: 1,
  329. pageSize: 9999,
  330. keyword: "",
  331. tenantId: proxy.useUserStore().user.tenantId,
  332. type: 0,
  333. })
  334. .then((res) => {
  335. treeData.value = proxy.handleTree(res.data, "deptId");
  336. });
  337. };
  338. getDict();
  339. const getFileData = () => {
  340. let ids = [];
  341. formData.data.quotationProductList.map((x) => {
  342. // ids.push(x.productId);
  343. x.quotationProductBomList.map((y) => {
  344. ids.push(y.materialId);
  345. });
  346. });
  347. ids = Array.from(new Set(ids));
  348. proxy
  349. .post("/fileInfo/getList", {
  350. businessIdList: ids,
  351. })
  352. .then((fileObj) => {
  353. formData.data.quotationProductList.map((x) => {
  354. // x.fileList = fileObj[x.productId] || [];
  355. // if (x.fileList && x.fileList.length > 0) {
  356. // x.fileUrl = x.fileList[0].fileUrl;
  357. // }
  358. x.quotationProductBomList.map((y) => {
  359. y.fileList = fileObj[y.materialId] || [];
  360. if (y.fileList && y.fileList.length > 0) {
  361. y.fileUrl = y.fileList[0].fileUrl;
  362. }
  363. });
  364. });
  365. });
  366. };
  367. const changeQuantity = () => {
  368. let money = 0;
  369. if (
  370. formData.data.quotationProductList &&
  371. formData.data.quotationProductList.length > 0
  372. ) {
  373. // 单个产品的价格
  374. for (let i = 0; i < formData.data.quotationProductList.length; i++) {
  375. let iele = formData.data.quotationProductList[i];
  376. let productPrice = 0;
  377. for (let j = 0; j < iele.quotationProductBomList.length; j++) {
  378. const jele = iele.quotationProductBomList[j];
  379. productPrice += Number(
  380. parseFloat(Number(jele.quantity) * Number(jele.price)).toFixed(2)
  381. );
  382. }
  383. iele.price = parseFloat(productPrice).toFixed(2);
  384. }
  385. // 计算数量以及小计
  386. for (let i = 0; i < formData.data.quotationProductList.length; i++) {
  387. let iele = formData.data.quotationProductList[i];
  388. if (iele.quantity) {
  389. if (iele.price) {
  390. iele.amount = parseFloat(
  391. Number(iele.quantity) * Number(iele.price)
  392. ).toFixed(2);
  393. money += Number(iele.amount);
  394. }
  395. for (let j = 0; j < iele.quotationProductBomList.length; j++) {
  396. const jele = iele.quotationProductBomList[j];
  397. jele.allQuantity = iele.quantity * jele.quantity;
  398. if (jele.price) {
  399. jele.amount = parseFloat(
  400. Number(jele.allQuantity) * Number(jele.price)
  401. ).toFixed(2);
  402. }
  403. }
  404. }
  405. }
  406. formData.data.amount = parseFloat(money).toFixed(2);
  407. }
  408. };
  409. const chartData = ref([]);
  410. const chartOption = reactive({
  411. data: {
  412. tooltip: {
  413. trigger: "axis",
  414. },
  415. // legend: {
  416. // data: ["价格"],
  417. // },
  418. grid: {
  419. left: "3%",
  420. right: "6%",
  421. top: "10%",
  422. bottom: "3%",
  423. containLabel: true,
  424. },
  425. tooltip: {
  426. show: true,
  427. trigger: "axis",
  428. // 格式化函数
  429. // valueFormatter: (val) => {
  430. // return val + "aaa";
  431. // },
  432. formatter: (params, ticket, callback) => {
  433. return `
  434. ${params[0].seriesName}:${params[0].value}
  435. <br/>
  436. 报价单号:${chartData.value[params[0].dataIndex].code}
  437. `;
  438. },
  439. textStyle: {
  440. fontSize: 12,
  441. },
  442. },
  443. // toolbox: {
  444. // feature: {
  445. // saveAsImage: {},
  446. // },
  447. // },
  448. xAxis: {
  449. type: "category",
  450. boundaryGap: false,
  451. data: [],
  452. },
  453. yAxis: {
  454. type: "value",
  455. },
  456. series: [
  457. {
  458. name: "报价金额",
  459. type: "line",
  460. data: [],
  461. },
  462. ],
  463. },
  464. });
  465. const customerList = ref([]);
  466. const getData = (query) => {
  467. let url =
  468. props.dataType == "1" ? "/saleQuotation/detail" : "/extQuotation/detail";
  469. loading.value = true;
  470. proxy.post(url, query).then((res) => {
  471. formData.data = res;
  472. proxy
  473. .post("/customer/selPage", { keyword: formData.data.buyCorporationName })
  474. .then((res) => {
  475. customerList.value = res.rows.map((x) => ({
  476. ...x,
  477. label: x.name,
  478. value: x.id,
  479. }));
  480. });
  481. // 城市数据回显
  482. if (formData.data.buyCountryId) {
  483. getCityData(formData.data.buyCountryId, "20");
  484. }
  485. if (formData.data.buyProvinceId) {
  486. getCityData(formData.data.buyProvinceId, "30");
  487. }
  488. getFileData();
  489. let productIds = formData.data.quotationProductList.map((x) => x.productId);
  490. proxy.getFileData({
  491. businessIdList: productIds,
  492. data: formData.data.quotationProductList,
  493. att: "productId",
  494. businessType: "0",
  495. fileAtt: "productFile",
  496. filePathAtt: "fileUrl",
  497. });
  498. changeQuantity();
  499. loading.value = false;
  500. // if (res.quotationTrendList && res.quotationTrendList.length >= 2) {
  501. // isShowChart.value = true;
  502. // nextTick(() => {
  503. // myChart = echarts.init(chartDom.value);
  504. // window.addEventListener("resize", () => {
  505. // myChart.resize();
  506. // });
  507. // chartData.value = res.quotationTrendList;
  508. // chartOption.data.xAxis.data = chartData.value.map((item) => {
  509. // return item.createTime.slice(0, 10);
  510. // });
  511. // chartOption.data.series[0].data = chartData.value.map((item, index) => {
  512. // if (item.code == props.rowData.code) {
  513. // return {
  514. // value: item.amount || 0,
  515. // itemStyle: { color: "red" },
  516. // };
  517. // } else {
  518. // return item.amount || 0;
  519. // }
  520. // });
  521. // myChart.setOption(chartOption.data);
  522. // myChart.resize();
  523. // });
  524. // } else {
  525. // isShowChart.value = false;
  526. // }
  527. });
  528. };
  529. watch(
  530. () => props.rowData,
  531. (val) => {
  532. if (props.rowData.id) {
  533. getData({ id: props.rowData.id });
  534. }
  535. },
  536. {
  537. immediate: true,
  538. deep: true,
  539. }
  540. );
  541. </script>
  542. <style lang="scss" scoped>
  543. .pic {
  544. object-fit: contain;
  545. width: 50px;
  546. height: 50px;
  547. cursor: pointer;
  548. vertical-align: middle;
  549. }
  550. :deep(.bom-table .el-table__body-wrapper .el-table__body .el-table__row) {
  551. background: #f4f4f5 !important;
  552. }
  553. </style>