index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. <template>
  2. <div class="pageIndexClass">
  3. <byTable :source="sourceList.data" :pagination="sourceList.pagination" :config="config" :loading="loading" :selectConfig="selectConfig"
  4. highlight-current-row :action-list="[
  5. ]" @get-list="getList">
  6. <template #code="{ item }">
  7. <div style="width: 100%">
  8. {{ item.code }}
  9. <!-- <span style="color: #409eff; cursor: pointer; word-break: break-all" @click="handleGenerate(item,true)">{{ item.code }}</span> -->
  10. </div>
  11. </template>
  12. <template #amount="{ item }">
  13. <div>
  14. <span style="padding-right: 4px">{{ item.currency }}</span>
  15. <span>{{ moneyFormat(item.amount, 2) }}</span>
  16. </div>
  17. </template>
  18. <template #status="{ item }">
  19. <div style="width: 100%">
  20. <span :style="{color: item.status ==88? 'red' :''}">{{dictValueLabel(item.status, statusData)}}</span>
  21. </div>
  22. </template>
  23. <template #follow="{ item }">
  24. <div style="width: 100%">
  25. <div style="width: 100%; display: flex">
  26. <template v-if="
  27. item.extQuotationFollowList &&
  28. item.extQuotationFollowList.length > 0
  29. ">
  30. <div class="tag" :style="
  31. index > 2
  32. ? 'display: none'
  33. : ''
  34. " v-for="(record, index) in item.extQuotationFollowList" :key="record.id">
  35. <el-popover placement="bottom" :width="300" trigger="hover">
  36. <template #default>
  37. <div style="width: 100%">
  38. <div style="color: #909399; margin: 8px 0">
  39. 跟进时间: {{ record.followTime }}
  40. </div>
  41. <div style="margin-top: 8px">
  42. 跟进说明: {{ record.remark }}
  43. </div>
  44. </div>
  45. </template>
  46. <template #reference>
  47. <div>
  48. <span v-if="record.followTime">{{
  49. record.followTime.substr(0, 10)
  50. }}</span>
  51. <el-icon style="margin-left: 8px; transform: translateY(2px)" @click="deleteFollow(record)">
  52. <DeleteFilled />
  53. </el-icon>
  54. </div>
  55. </template>
  56. </el-popover>
  57. </div>
  58. <div class="tag" @click="clickMore(item)" v-if="item.extQuotationFollowList.length > 3">
  59. 更多
  60. </div>
  61. </template>
  62. </div>
  63. </div>
  64. </template>
  65. <template #product="{ item }">
  66. <div style="width:100%;">
  67. <el-popover placement="bottom-start" title="" :width="300" trigger="hover">
  68. <div default>
  69. <div v-for="(product,index) in item.quotationProductList">
  70. {{index+1}}、{{product.productName}}
  71. <!-- <span v-if="index<item.quotationProductList.length-1">,</span> -->
  72. </div>
  73. </div>
  74. <template #reference>
  75. <div style="overflow:hidden;white-space:nowrap;text-overflow:ellipsis;cursor:pointer">
  76. <span v-for="(product,index) in item.quotationProductList">
  77. {{product.productName}}
  78. <span v-if="index<item.quotationProductList.length-1"> , </span>
  79. </span>
  80. </div>
  81. </template>
  82. </el-popover>
  83. </div>
  84. </template>
  85. <template #btn="{item}">
  86. <div style="width: 100%">
  87. <el-button type="primary" text v-debounce @click="handleFollow(item)">跟进</el-button>
  88. <el-button type="primary" text v-debounce @click="handleGenerate(item)">生成订单</el-button>
  89. <el-button type="danger" text v-debounce v-if="item.status !=0" @click="handleRepeal(item)">作废</el-button>
  90. </div>
  91. </template>
  92. </byTable>
  93. <el-dialog v-if="followDialog" v-model="followDialog" title="跟进" width="600" append-to-body>
  94. <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="formDom" v-loading="submitLoading">
  95. </byForm>
  96. <template #footer>
  97. <el-button @click="followDialog = false" size="default" v-debounce>关 闭</el-button>
  98. <el-button type="primary" @click="handleSubmit()" size="default" v-debounce>提 交</el-button>
  99. </template>
  100. </el-dialog>
  101. <el-dialog title="跟进记录" v-if="openRecordMore" v-model="openRecordMore" width="800" destroy-on-close>
  102. <div>
  103. <el-button style="margin:0px 0px 20px 20px" type="primary" plain v-debounce @click="handleFollow(rowData)">添加跟进记录</el-button>
  104. <el-timeline>
  105. <el-timeline-item v-for="(record, index) in recordAllData" :key="record.id" :timestamp="record.followTime" hide-timestamp>
  106. <div>
  107. 跟进时间:{{ record.followTime }}
  108. </div>
  109. <div style="word-wrap: break-word; margin: 8px 0">
  110. 跟进说明:{{ record.remark }}
  111. </div>
  112. </el-timeline-item>
  113. </el-timeline>
  114. </div>
  115. <template #footer>
  116. <el-button @click="openRecordMore = false" size="default" v-debounce>关 闭</el-button>
  117. </template>
  118. </el-dialog>
  119. </div>
  120. </template>
  121. <script setup>
  122. import byTable from "@/components/byTable/index";
  123. import moment from "moment";
  124. import byForm from "@/components/byForm/index";
  125. const { proxy } = getCurrentInstance();
  126. const accountList = ref([]);
  127. const corporationList = ref([]);
  128. const tradeMethods = ref([]);
  129. const accountCurrency = ref([]);
  130. const companyData = ref([]);
  131. const statusData = ref([
  132. {
  133. label: "草稿",
  134. value: 0,
  135. },
  136. {
  137. label: "正常",
  138. value: 30,
  139. },
  140. {
  141. label: "作废",
  142. value: 88,
  143. },
  144. ]);
  145. const quotationStatusData = ref([
  146. {
  147. label: "未报价",
  148. value: 0,
  149. },
  150. {
  151. label: "报价中",
  152. value: 1,
  153. },
  154. {
  155. label: "已报价",
  156. value: 2,
  157. },
  158. ]);
  159. const typeData = ref([
  160. {
  161. label: "内销",
  162. value: "1",
  163. },
  164. {
  165. label: "外销",
  166. value: "2",
  167. },
  168. ]);
  169. const sourceList = ref({
  170. data: [],
  171. pagination: {
  172. total: 0,
  173. pageNum: 1,
  174. pageSize: 10,
  175. keyword: "",
  176. status: "",
  177. type: "",
  178. quotationStatus: "",
  179. companyId: "",
  180. quotationTimeSta: "",
  181. quotationTimeEnd: "",
  182. beginTime: "",
  183. endTime: "",
  184. },
  185. });
  186. const loading = ref(false);
  187. const selectConfig = computed(() => {
  188. return [
  189. {
  190. label: "报价单状态",
  191. prop: "status",
  192. data: statusData.value,
  193. },
  194. {
  195. label: "报价单类型",
  196. prop: "type",
  197. data: typeData.value,
  198. },
  199. {
  200. type: "time",
  201. label: "报价时间",
  202. placeholder: "开始日期",
  203. prop: "quotationTimeSta",
  204. placeholderOne: "结束日期",
  205. propOne: "quotationTimeEnd",
  206. },
  207. ];
  208. });
  209. const config = computed(() => {
  210. return [
  211. {
  212. attrs: {
  213. label: "报价单号",
  214. slot: "code",
  215. width: 180,
  216. },
  217. },
  218. {
  219. attrs: {
  220. label: "报价单状态",
  221. slot: "status",
  222. width: 100,
  223. },
  224. },
  225. {
  226. attrs: {
  227. label: "报价单类型",
  228. prop: "type",
  229. width: 100,
  230. },
  231. render(val) {
  232. return proxy.dictValueLabel(val, typeData.value);
  233. },
  234. },
  235. {
  236. attrs: {
  237. label: "客户名称",
  238. prop: "buyCorporationName",
  239. "min-width": 150,
  240. },
  241. },
  242. {
  243. attrs: {
  244. label: "报价金额",
  245. slot: "amount",
  246. width: 120,
  247. },
  248. },
  249. {
  250. attrs: {
  251. label: "报价时间",
  252. prop: "quotationTime",
  253. width: 160,
  254. },
  255. },
  256. {
  257. attrs: {
  258. label: "报价单产品",
  259. slot: "product",
  260. "min-width": 180,
  261. },
  262. },
  263. {
  264. attrs: {
  265. label: "跟进",
  266. slot: "follow",
  267. "min-width": 360,
  268. },
  269. },
  270. {
  271. attrs: {
  272. label: "操作",
  273. width: 200,
  274. slot: "btn",
  275. align: "center",
  276. fixed: "right",
  277. },
  278. },
  279. ];
  280. });
  281. const getDict = () => {
  282. // proxy
  283. // .post("/customer/selPage", {
  284. // pageNum: 1,
  285. // pageSize: 50,
  286. // })
  287. // .then((res) => {
  288. // customerList.value = res.rows.map((x) => ({
  289. // ...x,
  290. // label: x.name,
  291. // value: x.id,
  292. // }));
  293. // });
  294. proxy
  295. .get("/tenantDept/list", {
  296. pageNum: 1,
  297. pageSize: 9999,
  298. keyword: "",
  299. tenantId: proxy.useUserStore().user.tenantId,
  300. type: 0,
  301. })
  302. .then((res) => {
  303. companyData.value = res.data.map((x) => ({
  304. ...x,
  305. label: x.deptName,
  306. value: x.deptId,
  307. }));
  308. });
  309. };
  310. const getList = async (req) => {
  311. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  312. loading.value = true;
  313. proxy.post("/extQuotation/page", sourceList.value.pagination).then((res) => {
  314. sourceList.value.data = res.rows;
  315. sourceList.value.pagination.total = res.total;
  316. setTimeout(() => {
  317. loading.value = false;
  318. }, 200);
  319. });
  320. };
  321. getDict();
  322. getList();
  323. const formDom = ref(null);
  324. const followDialog = ref(false);
  325. const submitLoading = ref(false);
  326. const formData = reactive({
  327. data: {},
  328. });
  329. const formOption = reactive({
  330. inline: true,
  331. labelWidth: 100,
  332. itemWidth: 100,
  333. disabled: false,
  334. });
  335. const formConfig = computed(() => {
  336. return [
  337. {
  338. type: "title1",
  339. title: "跟进信息",
  340. },
  341. {
  342. type: "date",
  343. itemType: "datetime",
  344. prop: "followTime",
  345. label: "跟进时间",
  346. itemWidth: 100,
  347. disabled: false,
  348. },
  349. {
  350. type: "input",
  351. itemType: "textarea",
  352. prop: "remark",
  353. label: "跟进说明",
  354. itemWidth: 100,
  355. disabled: false,
  356. },
  357. ];
  358. });
  359. const rules = ref({
  360. followTime: [
  361. { required: true, message: "请选择跟进时间", trigger: "change" },
  362. ],
  363. remark: [{ required: true, message: "请输入跟进说明", trigger: "blur" }],
  364. });
  365. const handleFollow = (row) => {
  366. formData.data = {
  367. quotationId: row.id,
  368. followTime: moment().format("yyyy-MM-DD HH:mm:ss"),
  369. remark: "",
  370. };
  371. followDialog.value = true;
  372. };
  373. const handleSubmit = (type) => {
  374. formDom.value.handleSubmit(() => {
  375. submitLoading.value = true;
  376. proxy.post("/extQuotationFollow/add", formData.data).then(
  377. (res) => {
  378. proxy.msgTip("操作成功", 1);
  379. followDialog.value = false;
  380. submitLoading.value = false;
  381. getList();
  382. },
  383. (err) => {
  384. submitLoading.value = false;
  385. }
  386. );
  387. });
  388. };
  389. const deleteFollow = (record) => {
  390. proxy
  391. .msgConfirm()
  392. .then((res) => {
  393. proxy
  394. .post("/extQuotationFollow/delete", {
  395. id: record.id,
  396. })
  397. .then((res) => {
  398. proxy.msgTip("操作成功", 1);
  399. getList();
  400. });
  401. })
  402. .catch((err) => {});
  403. };
  404. const openRecordMore = ref(false);
  405. const recordAllData = ref([]);
  406. const rowData = ref({});
  407. const clickMore = (row) => {
  408. rowData.value = row;
  409. recordAllData.value = proxy.deepClone(row.extQuotationFollowList);
  410. openRecordMore.value = true;
  411. };
  412. const handleGenerate = (row) => {
  413. proxy.$router.replace({
  414. path: "/platform_manage/process/processApproval",
  415. query: {
  416. priceSheetId: row.id,
  417. flowKey: "contract_flow",
  418. flowName: "销售合同审批流程",
  419. random: proxy.random(),
  420. },
  421. });
  422. };
  423. const handleRepeal = (row) => {
  424. proxy
  425. .msgConfirm()
  426. .then((res) => {
  427. proxy
  428. .post("/extQuotation/cancellation", {
  429. id: row.id,
  430. })
  431. .then((res) => {
  432. proxy.msgTip("操作成功", 1);
  433. getList();
  434. });
  435. })
  436. .catch((err) => {});
  437. };
  438. </script>
  439. <style lang="scss" scoped>
  440. .tag {
  441. line-height: 32px;
  442. margin-right: 8px;
  443. padding: 0 8px;
  444. background-color: #eeeeee;
  445. border-radius: 4px;
  446. cursor: pointer;
  447. }
  448. </style>