PriceSheetDetail.vue 17 KB

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