index.vue 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118
  1. <script lang="ts" setup>
  2. import Selector from '/src/views/reportManage/dataCenter/normalDisplay/components/Selector/index.vue';
  3. import { onMounted, reactive, ref } from 'vue';
  4. import { VxeGridInstance, VxeGridListeners, VxeGridProps, VXETable } from 'vxe-table';
  5. import {
  6. exportTaskData,
  7. getCurrencyCodeSelect,
  8. getOperationSelect,
  9. getTasks,
  10. postCreateTask,
  11. postDeleteTask,
  12. postNoticePerson,
  13. postSendMessage,
  14. postUpdateManyTask,
  15. postUpdateTask,
  16. postUpdateTaskStatus
  17. } from '/src/views/reportManage/TaskManage/api.ts';
  18. import { ComponentSize, ElMessage, FormInstance, FormRules } from 'element-plus';
  19. import { Delete, Message, Plus, TopRight } from '@element-plus/icons-vue';
  20. import { dateType, requiredFields } from '/@/views/reportManage/TaskManage/utils/enum';
  21. import { taskColumns } from '/@/views/reportManage/TaskManage/utils/columns';
  22. import { useRouter } from 'vue-router';
  23. const router = useRouter();
  24. const selectorRef = ref(null);
  25. const message = ref('');
  26. const currencyList = ref([]);// 货币列表
  27. //表单
  28. interface taskRuleForm {
  29. number: string;
  30. name: string;
  31. country: string;
  32. brand: string;
  33. operation: string[];
  34. operater: [];
  35. currency: string;
  36. currencyCodePlatform: string;
  37. line: string;
  38. ipaddress: string;
  39. company: string;
  40. platform: string;
  41. companyEnglishName: string;
  42. address: string;
  43. juridicalPerson: string;
  44. juridicalPersonCreditCard: string;
  45. juridicalPersonCreditCardAddress: string;
  46. receivablesAccount: string;
  47. receivablesAccountCompany: string;
  48. vatNumber: string;
  49. vatCompany: string;
  50. }
  51. const formSize = ref<ComponentSize>('default');
  52. const dialogFormVisible = ref(false);
  53. const taskRuleFormRef = ref<FormInstance>();
  54. const taskRuleForm = reactive({
  55. number: '',
  56. name: '',
  57. country: '',
  58. brand: '',
  59. operation: [],
  60. operater: [],
  61. currency: '',
  62. currencyCodePlatform: '',
  63. });
  64. const resetForm = (formEl: FormInstance | undefined) => {
  65. if (!formEl) return;
  66. formEl.resetFields();
  67. };
  68. const rules = reactive<FormRules>({
  69. number: [{ required: true, message: '请输入平台编号', trigger: 'blur' },],
  70. name: [{ required: true, message: '请输入平台名称', trigger: 'blur' }],
  71. country: [{ required: true, message: '请输入国家', trigger: 'blur' }],
  72. brand: [{ required: true, message: '请输入品牌', trigger: 'blur' }],
  73. operation: [{ required: true, message: '请选择填写人', trigger: 'change' }],
  74. operater: [{ required: true, message: '请输入运营', trigger: 'change' }],
  75. currency: [{ required: true, message: '请输入回款/余额币种', trigger: 'blur' }],
  76. currencyCodePlatform: [{ required: true, message: '请输入平台货币', trigger: 'blur' }],
  77. line: [{ required: true, message: '请输入线路', trigger: 'blur' }],
  78. ipaddress: [{ required: true, message: '请输入IP地址', trigger: 'blur' }],
  79. company: [{ required: true, message: '请输入注册公司', trigger: 'blur' }],
  80. platform: [{ required: true, message: '请输入平台', trigger: 'blur' }],
  81. });
  82. // 修改填写人弹窗
  83. const userDialogFormVisible = ref(false);
  84. const updateSelect = ref(1);
  85. //发送通知弹窗
  86. const noticeDialogFormVisible = ref(false);
  87. const noticeMessage = ref(null);
  88. //表格
  89. interface RowVO {
  90. platformNumber: string;
  91. platformName: string;
  92. country: string;
  93. brandName: string;
  94. user_name: string;
  95. operater: [];
  96. currencyCode: string;
  97. currencyCodePlatform: string;
  98. child_user_number: number;
  99. user: [];
  100. line: string;
  101. ipaddress: string;
  102. company: string;
  103. platform: string;
  104. }
  105. const xGrid = ref<VxeGridInstance<RowVO>>();
  106. const originalData = ref([]);
  107. let allTasks = []; // 用于存储所有任务数据
  108. const operationList = ref([]);// 填写人列表
  109. const filter = ref({}); // 筛选条件
  110. const gridOptions = reactive<VxeGridProps<RowVO>>({
  111. border: 'inner',
  112. keepSource: true,
  113. //showOverflow: true,
  114. height: 780,
  115. loading: false,
  116. round: true,
  117. toolbarConfig: {
  118. zoom: {
  119. iconIn: 'vxe-icon-fullscreen',
  120. iconOut: 'vxe-icon-minimize'
  121. },
  122. slots: {
  123. buttons: 'toolbar_buttons',
  124. tools: 'toolbar_tools'
  125. }
  126. },
  127. rowConfig: {
  128. isHover: true,
  129. },
  130. columnConfig: {
  131. resizable: true,
  132. },
  133. pagerConfig: {
  134. enabled: true,
  135. total: 20,
  136. currentPage: 1,
  137. pageSize: 20,
  138. pageSizes: [10, 20, 30],
  139. },
  140. editConfig: {
  141. trigger: 'click',
  142. mode: 'row',
  143. showStatus: true,
  144. //autoClear: false,
  145. },
  146. checkboxConfig: {
  147. reserve: true,
  148. highlight: true,
  149. range: true,
  150. },
  151. columns: taskColumns,
  152. data: [],
  153. });
  154. const gridEvents: VxeGridListeners<RowVO> = {
  155. pageChange({ currentPage, pageSize }) {
  156. if (gridOptions.pagerConfig) {
  157. gridOptions.pagerConfig.currentPage = currentPage;
  158. gridOptions.pagerConfig.pageSize = pageSize;
  159. getTaskList();
  160. }
  161. },
  162. };
  163. const hasActiveEditRow = (row: RowVO) => {
  164. const $grid = xGrid.value;
  165. if ($grid) {
  166. return $grid.isEditByRow(row);
  167. }
  168. return false;
  169. };
  170. const editRowEvent = (row: RowVO) => {
  171. const $grid = xGrid.value;
  172. if ($grid) {
  173. // 在进入编辑状态前保存原始数据
  174. //originalDataMap.set(row.id, { ...row });
  175. //// 初始化 row.user 确保其与 row.user_name 同步
  176. if (!row.user || row.user.length === 0) {
  177. row.user = userNameToUser(row.user_name); // 转换 user_name 到 user
  178. }
  179. $grid.setEditRow(row);
  180. }
  181. };
  182. const handleEditActived = ({ row, column }) => {
  183. if (!row.user || row.user.length === 0) {
  184. row.user = userNameToUser(row.user_name); // 转换 user_name 到 user
  185. }
  186. };
  187. const handleEditClosed = ({ row, column }) => {
  188. // const $grid = xGrid.value;
  189. if (column.property === 'user_name') {
  190. // 将 user 转换为 user_name 并更新 row
  191. row.user_name = userToUserName(row.user);
  192. // 强制刷新视图
  193. // $grid.refreshRow(row);
  194. }
  195. };
  196. // 禁用自动退出编辑模式
  197. const disableAutoExitEdit = () => {
  198. const $grid = xGrid.value;
  199. if ($grid) {
  200. gridOptions.editConfig.autoClear = false; // 手动模式,禁止点击其他地方退出编辑模式
  201. }
  202. };
  203. // 启用自动退出编辑模式
  204. const enableAutoExitEdit = () => {
  205. const $grid = xGrid.value;
  206. if ($grid) {
  207. gridOptions.editConfig.autoClear = true; // 点击触发退出编辑模式
  208. }
  209. };
  210. // 将 user 数组转换为 user_name 字符串
  211. function userToUserName(user: string[]): string {
  212. return operationList.value
  213. .filter(op => user.includes(op.value))
  214. .map(op => op.label)
  215. .join(', ');
  216. }
  217. // 将 user_name 字符串转换为 user 数组
  218. function userNameToUser(user_name: string): string[] {
  219. return operationList.value
  220. .filter(op => user_name.includes(op.label))
  221. .map(op => op.value);
  222. }
  223. // 清除编辑状态并保存已编辑的数据
  224. const clearRowEvent = (row) => {
  225. const $grid = xGrid.value;
  226. if ($grid) {
  227. // 手动保存当前行的数据
  228. const editRecord = $grid.getEditRecord();
  229. if (editRecord) {
  230. const { row: editedRow } = editRecord;
  231. // 更新原始 row 数据,确保编辑的值被保存
  232. row.user = editedRow.user;
  233. row.user_name = userToUserName(editedRow.user);
  234. }
  235. // 清除编辑状态
  236. $grid.clearEdit();
  237. }
  238. };
  239. // 获取任务列表
  240. async function getTaskList() {
  241. try {
  242. gridOptions.loading = true;
  243. const response = await getTasks({
  244. page: gridOptions.pagerConfig.currentPage,
  245. limit: gridOptions.pagerConfig.pageSize,
  246. ...filter.value,
  247. });
  248. gridOptions.data = response.data;
  249. originalData.value = JSON.parse(JSON.stringify(response.data));
  250. gridOptions.pagerConfig.total = response.total;
  251. } catch (error) {
  252. console.error('Error fetching task data:', error);
  253. } finally {
  254. gridOptions.loading = false;
  255. }
  256. }
  257. // 筛选器变化
  258. function filteredDataChange(newList) {
  259. filter.value = newList.value;
  260. if (selectorRef.value) {
  261. if (gridOptions.pagerConfig) {
  262. gridOptions.pagerConfig.currentPage = 1;
  263. }
  264. getTaskList(newList.value);
  265. }
  266. }
  267. // 全部保存按钮
  268. async function saveEvent(row) {
  269. clearRowEvent(row);
  270. const $grid = xGrid.value;
  271. if ($grid) {
  272. try {
  273. const { updateRecords } = $grid.getRecordset();
  274. const originalDataMap = new Map(originalData.value.map(item => [item.id, item]));
  275. const updatedRecords = updateRecords.map(record => {
  276. const originalRecord = originalDataMap.get(record.id);
  277. if (!originalRecord) return null;
  278. const updatedFields = {};
  279. // 比较字段并记录变化
  280. for (const key in record) {
  281. // 针对 operater 字段的特殊处理
  282. if (key === 'operater') {
  283. // 如果 operater 是字符串,将其转换为数组
  284. let recordOperater = Array.isArray(record.operater)
  285. ? record.operater
  286. : record.operater.split(',').map(item => item.trim()); // 中文逗号分割并去除空格
  287. // 如果 originalRecord.operater 是字符串,也转换为数组
  288. let originalOperater = Array.isArray(originalRecord.operater)
  289. ? originalRecord.operater
  290. : originalRecord.operater.split(',').map(item => item.trim());
  291. // 比较两个数组,确保它们不相等时才记录变化
  292. if (JSON.stringify(recordOperater) !== JSON.stringify(originalOperater)) {
  293. updatedFields[key] = recordOperater; // 存储更新后的 operater 数组
  294. }
  295. } else {
  296. // 对于非 operater 字段,进行普通的比较
  297. if (record[key] !== originalRecord[key]) {
  298. updatedFields[key] = record[key];
  299. }
  300. }
  301. }
  302. // 如果有变化字段,返回该记录,否则跳过
  303. if (Object.keys(updatedFields).length > 0) {
  304. return {
  305. id: record.id,
  306. ...updatedFields
  307. };
  308. }
  309. return null;
  310. }).filter(record => record); // 过滤掉值为 null 的记录
  311. if (updatedRecords.length > 0) {
  312. // 调用接口提交更新
  313. await postUpdateManyTask(updatedRecords);
  314. await getTaskList();
  315. await VXETable.modal.message({
  316. content: `更新 ${ updatedRecords.length } 条`,
  317. status: 'success',
  318. });
  319. } else {
  320. await VXETable.modal.message({
  321. content: '没有检测到变化',
  322. status: 'info',
  323. });
  324. }
  325. } catch (e) {
  326. console.log('error', e);
  327. }
  328. }
  329. }
  330. // 批量修改填写人
  331. async function updateUser() {
  332. const $grid = xGrid.value;
  333. if ($grid) {
  334. const selectRecords = $grid.getCheckboxRecords(); // 获取勾选的表格行
  335. const selectedUsers = taskRuleForm.operation; // 获取选择的填写人
  336. const operationType = updateSelect.value; // 获取选择的操作类型
  337. const updateData = selectRecords.map(record => {
  338. const existingUsers = userNameToUser(record.user_name) || [];
  339. let updatedUsers;
  340. if (operationType === 1) { // 添加操作
  341. updatedUsers = Array.from(new Set([...existingUsers, ...selectedUsers]));
  342. } else if (operationType === 2) { // 删除操作
  343. updatedUsers = existingUsers.filter(user => !selectedUsers.includes(user));
  344. }
  345. return {
  346. id: record.id,
  347. user: updatedUsers, // 更新后的 user 数组
  348. };
  349. });
  350. try {
  351. // 调用接口批量保存修改的数据
  352. await postUpdateManyTask(updateData);
  353. if (operationType === 1) { // 添加操作
  354. ElMessage.success('添加成功');
  355. } else if (operationType === 2) { // 删除操作
  356. ElMessage.success('删除成功');
  357. }
  358. userDialogFormVisible.value = false; // 关闭弹窗
  359. await getTaskList(); // 重新加载表格数据
  360. } catch (error) {
  361. console.error('修改失败', error);
  362. ElMessage.error('修改失败');
  363. } finally {
  364. // 清除表单数据
  365. taskRuleForm.operation = []; // 清除选择的填写人
  366. updateSelect.value = 1; // 清除操作类型
  367. }
  368. }
  369. }
  370. function updateUserCancel() {
  371. taskRuleForm.operation = [];
  372. updateSelect.value = 1;
  373. userDialogFormVisible.value = false;
  374. }
  375. // 指定人通知
  376. async function noticePerson() {
  377. const $grid = xGrid.value;
  378. if ($grid) {
  379. const selectRecords = $grid.getCheckboxRecords();
  380. const selectedIds = selectRecords.map(record => record.id);
  381. const obj = {
  382. task_list: selectedIds,
  383. message: noticeMessage.value,
  384. };
  385. try {
  386. const response = await postNoticePerson(obj);
  387. if (response.code === 2000) {
  388. ElMessage.success('通知成功');
  389. noticeDialogFormVisible.value = false; // 关闭弹窗
  390. } else {
  391. ElMessage.error('通知失败');
  392. }
  393. } catch (error) {
  394. ElMessage.error('通知失败');
  395. }
  396. }
  397. }
  398. function noticePersonCancel() {
  399. noticeMessage.value = null;
  400. noticeDialogFormVisible.value = false; // 关闭弹窗
  401. }
  402. // 删除任务
  403. async function deleteTask() {
  404. const $grid = xGrid.value;
  405. if ($grid) {
  406. const selectRecords = $grid.getCheckboxRecords();
  407. const selectedIds = selectRecords.map(record => record.id);
  408. const obj = { keys: selectedIds };
  409. try {
  410. const resp = await postDeleteTask(obj);
  411. if (resp.code === 2000) {
  412. ElMessage({
  413. message: '删除成功',
  414. type: 'success',
  415. });
  416. await getTaskList();
  417. }
  418. } catch (e) {
  419. ElMessage({
  420. message: '删除失败',
  421. type: 'error',
  422. });
  423. }
  424. }
  425. }
  426. const isDeleteDisabled = computed(() => {
  427. const $grid = xGrid.value;
  428. return !$grid || $grid.getCheckboxRecords().length === 0;
  429. });
  430. const removeEvent = async () => {
  431. const $grid = xGrid.value;
  432. if ($grid) {
  433. const selectRecords = $grid.getCheckboxRecords();
  434. if (selectRecords.length > 0) {
  435. const type = await VXETable.modal.confirm('您确定要删除选中的数据?');
  436. if (type === 'confirm') {
  437. await deleteTask(selectRecords);
  438. await $grid.removeCheckboxRow();
  439. }
  440. } else {
  441. await VXETable.modal.message({ content: '请选择要删除的数据', status: 'error' });
  442. }
  443. }
  444. };
  445. // 数据校验
  446. const validateRow = (row) => {
  447. for (const { field, title } of requiredFields) {
  448. if (!row[field] || (Array.isArray(row[field]) && row[field].length === 0)) {
  449. ElMessage.error(`${ title }不能为空`);
  450. return;
  451. }
  452. }
  453. if (!currencyList.value.includes(row.currencyCode)) {
  454. ElMessage.error('回款币种格式不正确,请重新选择');
  455. return;
  456. }
  457. if (!currencyList.value.includes(row.currencyCodePlatform)) {
  458. ElMessage.error('回款/余额币种格式不正确,请重新选择');
  459. return;
  460. }
  461. return true;
  462. };
  463. async function updateRow(row) {
  464. const $grid = xGrid.value;
  465. if ($grid) {
  466. const updatedRowData = {
  467. id: row.id,
  468. platformNumber: row.platformNumber,
  469. platformName: row.platformName,
  470. country: row.country,
  471. brandName: row.brandName,
  472. user: row.user,
  473. operater: Array.isArray(row.operater) ? row.operater : row.operater.split(',').map(item => item.trim()),
  474. currencyCode: row.currencyCode,
  475. currencyCodePlatform: row.currencyCodePlatform,
  476. line: row.line,
  477. ipaddress: row.ipaddress,
  478. company: row.company,
  479. platform: row.platform,
  480. companyEnglishName: row.companyEnglishName,
  481. address: row.address,
  482. juridicalPerson: row.juridicalPerson,
  483. juridicalPersonCreditCard: row.juridicalPersonCreditCard,
  484. juridicalPersonCreditCardAddress: row.juridicalPersonCreditCardAddress,
  485. receivablesAccount: row.receivablesAccount,
  486. receivablesAccountCompany: row.receivablesAccountCompany,
  487. vatNumber: row.vatNumber,
  488. vatCompany: row.vatCompany,
  489. };
  490. try {
  491. const response = await postUpdateTask(updatedRowData);
  492. if (response.code === 2000) {
  493. ElMessage.success('更新成功');
  494. } else if (response.code == 400) {
  495. ElMessage.warning(`${ response.data.description }`);
  496. } else {
  497. ElMessage.error('更新失败');
  498. }
  499. } catch (error) {
  500. console.log('error:', error);
  501. }
  502. }
  503. }
  504. // 更新行任务保存按钮
  505. const saveRowEvent = async (row: RowVO) => {
  506. //clearRowEvent(row)
  507. const $grid = xGrid.value;
  508. if ($grid) {
  509. if (!validateRow(row)) {
  510. return;
  511. }
  512. await $grid.clearEdit();
  513. await updateRow(row);
  514. await getTaskList();
  515. //gridOptions.loading = true;
  516. //setTimeout(() => {
  517. // gridOptions.loading = false;
  518. //}, 300);
  519. }
  520. };
  521. // 更新状态
  522. async function handleStatusChange(row) {
  523. const $grid = xGrid.value;
  524. if ($grid) {
  525. const updatedData = {
  526. id: row.id,
  527. status: row.status,
  528. };
  529. // 传partial=1,可只修改状态
  530. const query = { partial: 1, };
  531. try {
  532. const response = await postUpdateTaskStatus(query, updatedData);
  533. if (response.code === 2000) {
  534. ElMessage.success('状态更新成功');
  535. } else if (response.code == 400) {
  536. ElMessage.warning(`${ response.data.description }`);
  537. } else {
  538. ElMessage.error('状态更新失败');
  539. }
  540. } catch (error) {
  541. console.log('error:', error);
  542. }
  543. }
  544. }
  545. // 创建任务
  546. async function createTask() {
  547. const body = {
  548. country: taskRuleForm.country,
  549. platformNumber: taskRuleForm.number,
  550. platformName: taskRuleForm.name,
  551. brandName: taskRuleForm.brand,
  552. currencyCode: taskRuleForm.currency,
  553. currencyCodePlatform: taskRuleForm.currencyCodePlatform,
  554. line: taskRuleForm.line,
  555. ipaddress: taskRuleForm.ipaddress,
  556. company: taskRuleForm.company,
  557. platform: taskRuleForm.platform,
  558. companyEnglishName: taskRuleForm.companyEnglishName,
  559. address: taskRuleForm.address,
  560. juridicalPerson: taskRuleForm.juridicalPerson,
  561. juridicalPersonCreditCard: taskRuleForm.juridicalPersonCreditCard,
  562. juridicalPersonCreditCardAddress: taskRuleForm.juridicalPersonCreditCardAddress,
  563. receivablesAccount: taskRuleForm.receivablesAccount,
  564. receivablesAccountCompany: taskRuleForm.receivablesAccountCompany,
  565. vatNumber: taskRuleForm.vatNumber,
  566. vatCompany: taskRuleForm.vatCompany,
  567. user: taskRuleForm.operation,
  568. operater: [taskRuleForm.operater],
  569. };
  570. try {
  571. const resp = await postCreateTask(body);
  572. if (resp.code === 2000) {
  573. dialogFormVisible.value = false;
  574. await getTaskList(); // 重新获取任务列表
  575. ElMessage({ message: '创建成功', type: 'success', });
  576. }
  577. } catch (error) {
  578. console.log('error:', error);
  579. ElMessage({ message: '创建失败', type: 'error', });
  580. }
  581. }
  582. // 提交任务确认按钮
  583. const submitForm = async (formEl) => {
  584. if (!formEl) return;
  585. await formEl.validate(async (valid, fields) => {
  586. if (valid) {
  587. const isDuplicate = allTasks.some(task => String(task.platformNumber) === String(taskRuleForm.number));
  588. if (isDuplicate) {
  589. await ElMessage({ message: '平台编号已存在,请重新输入', type: 'warning' });
  590. return;
  591. }
  592. if (!currencyList.value.includes(taskRuleForm.currency)) {
  593. await ElMessage({ message: '回款币种无效,请重新选择', type: 'warning' });
  594. return;
  595. }
  596. if (!currencyList.value.includes(taskRuleForm.currencyCodePlatform)) {
  597. await ElMessage({ message: '回款/余额币种无效,请重新选择', type: 'warning' });
  598. return;
  599. }
  600. await createTask();
  601. taskRuleFormRef.value.resetFields();
  602. }
  603. });
  604. };
  605. function handleClose(done: Function) {
  606. if (taskRuleFormRef.value) taskRuleFormRef.value.resetFields();
  607. done();
  608. }
  609. //发送通知
  610. async function sendMessage(selectedValue: string) {
  611. const body = {
  612. date_type: selectedValue,
  613. };
  614. try {
  615. const response = await postSendMessage(body);
  616. if (response.code === 2000) {
  617. ElMessage.success('发送成功');
  618. } else if (response.code == 400) {
  619. ElMessage.warning(`${ response.data.description }`);
  620. } else {
  621. ElMessage.error('发送失败');
  622. }
  623. } catch (error) {
  624. }
  625. }
  626. // 导出接口
  627. async function handleExport() {
  628. try {
  629. gridOptions.loading = true;
  630. const response = await exportTaskData(filter.value,);
  631. const url = window.URL.createObjectURL(new Blob([response.data]));
  632. const link = document.createElement('a');
  633. link.href = url;
  634. link.setAttribute('download', '店铺数据.xlsx');
  635. document.body.appendChild(link);
  636. link.click();
  637. gridOptions.loading = false;
  638. ElMessage.success('导出数据成功');
  639. } catch (error) {
  640. console.error('导出数据失败:', error);
  641. }
  642. }
  643. // 获取填写人下拉框
  644. async function fetchOperationSelect() {
  645. try {
  646. const resp = await getOperationSelect();
  647. operationList.value = resp.data.map((item: any) => {
  648. return { value: item.id, label: item.name };
  649. });
  650. } catch (e) {
  651. console.error('Failed to fetch operation select:', e);
  652. }
  653. }
  654. // 获取币种下拉框
  655. async function fetchCurrencyList() {
  656. try {
  657. const response = await getCurrencyCodeSelect(); // 替换为你的后端接口
  658. currencyList.value = response.data;
  659. } catch (error) {
  660. ElMessage.error('请求失败');
  661. }
  662. }
  663. const querySearch = (queryString, cb) => {
  664. const results = queryString
  665. ? currencyList.value.filter(currency => currency.toLowerCase().includes(queryString.toLowerCase()))
  666. : currencyList.value;
  667. cb(results);
  668. };
  669. const handleSelect = item => {
  670. taskRuleForm.currency = item;
  671. };
  672. const handleCurrencyCodePlatformSelect = item => {
  673. taskRuleForm.currencyCodePlatform = item;
  674. };
  675. function handleRowSelect(item, row) {
  676. row.currencyCode = item;
  677. }
  678. function handelRowCurrencyCodePlatformSelect(item, row) {
  679. row.currencyCodePlatform = item;
  680. }
  681. const formItems = ref([
  682. { label: '平台编号', prop: 'number', type: 'input', placeholder: '请输入平台编号' },
  683. { label: '平台名称', prop: 'name', type: 'input', placeholder: '请输入平台名称' },
  684. { label: '国家', prop: 'country', type: 'input', placeholder: '请输入国家' },
  685. { label: '品牌', prop: 'brand', type: 'input', placeholder: '请输入品牌' },
  686. {
  687. label: '录入人员',
  688. prop: 'operation',
  689. type: 'select',
  690. placeholder: '请选择录入人员',
  691. multiple: true,
  692. collapseTags: true,
  693. collapseTagsTooltip: true,
  694. options: operationList
  695. },
  696. { label: '运营', prop: 'operater', type: 'input', placeholder: '请输入运营' },
  697. {
  698. label: '平台币种',
  699. prop: 'currency',
  700. type: 'autocomplete',
  701. placeholder: '请输入回款币种',
  702. debounce: 100,
  703. fetchSuggestions: querySearch,
  704. triggerOnFocus: false,
  705. clearable: true,
  706. onSelect: handleSelect
  707. },
  708. {
  709. label: '回款/余额币种',
  710. prop: 'currencyCodePlatform',
  711. type: 'autocomplete',
  712. placeholder: '请输入回款/余额币种',
  713. debounce: 100,
  714. fetchSuggestions: querySearch,
  715. triggerOnFocus: false,
  716. clearable: true,
  717. onSelect: handleCurrencyCodePlatformSelect
  718. },
  719. { label: '平台', prop: 'platform', type: 'input', placeholder: '请输入平台' },
  720. { label: '线路', prop: 'line', type: 'input', placeholder: '请输入线路' },
  721. { label: 'IP地址', prop: 'ipaddress', type: 'input', placeholder: '请输入IP地址' },
  722. { label: '注册公司', prop: 'company', type: 'input', placeholder: '请输入注册公司' },
  723. { label: '公司英文名称', prop: 'companyEnglishName', type: 'input', placeholder: '请输入公司英文名称' },
  724. { label: '公司地址', prop: 'address', type: 'input', placeholder: '请输入公司地址' },
  725. { label: '公司法人', prop: 'juridicalPerson', type: 'input', placeholder: '请输入公司法人' },
  726. { label: '法人信用卡', prop: 'juridicalPersonCreditCard', type: 'input', placeholder: '请输入法人信用卡' },
  727. { label: '信用卡地址', prop: 'juridicalPersonCreditCardAddress', type: 'input', placeholder: '请输入信用卡地址' },
  728. { label: '收款账号', prop: 'receivablesAccount', type: 'input', placeholder: '请输入收款账号' },
  729. { label: '收款账号公司', prop: 'receivablesAccountCompany', type: 'input', placeholder: '请输入收款账号公司' },
  730. { label: 'VAT税号', prop: 'vatNumber', type: 'input', placeholder: '请输入VAT税号' },
  731. { label: 'VAT税号公司名称', prop: 'vatCompany', type: 'input', placeholder: '请输入VAT税号公司名称' },
  732. ]);
  733. const handelPlanSales = () => {
  734. const url = router.resolve({
  735. name: 'planInfo',
  736. // query: {
  737. // dateType: dateType.value,
  738. // }
  739. }).href;
  740. window.open(url, '_blank');
  741. };
  742. // 表格样式
  743. const cellStyle = () => {
  744. return {
  745. fontSize: '12px',
  746. fontWeight: '600',
  747. padding: 0,
  748. };
  749. };
  750. const headerCellStyle = () => {
  751. return {
  752. fontSize: '13px',
  753. };
  754. };
  755. onMounted(() => {
  756. //getTaskList();
  757. fetchOperationSelect();
  758. fetchCurrencyList();
  759. });
  760. </script>
  761. <template>
  762. <div class="p-2.5">
  763. <el-card shadow="hover">
  764. <div class="custom-card-style flex gap-1.5 justify-between" >
  765. <Selector ref="selectorRef" @update:filteredData="filteredDataChange" />
  766. <el-button :icon="TopRight" text bg @click="handelPlanSales">计划销售额</el-button>
  767. </div>
  768. </el-card>
  769. <el-card class="my-3" shadow="hover">
  770. <vxe-grid ref="xGrid" :cell-style="cellStyle" :header-cell-style="headerCellStyle" stripe v-bind="gridOptions"
  771. v-on="gridEvents" @edit-actived="handleEditActived" @edit-closed="handleEditClosed">
  772. <template #toolbar_buttons>
  773. <el-button :icon="Plus" type="primary" @click="dialogFormVisible = true"> 添加任务</el-button>
  774. <!--<el-button plain type="success" @click="saveEvent">保存</el-button>-->
  775. <el-button :disabled="isDeleteDisabled" :icon="Delete" plain type="danger" @click="removeEvent">删除
  776. </el-button>
  777. <el-button v-if="!isDeleteDisabled" plain round type="success" @click="userDialogFormVisible =true">
  778. 修改填写人
  779. </el-button>
  780. </template>
  781. <template #toolbar_tools>
  782. <div class="pr-2.5">
  783. <el-tooltip content="指定店铺发送通知" placement="top">
  784. <vxe-button v-if="!isDeleteDisabled" plain circle icon="vxe-icon-envelope" @click="noticeDialogFormVisible =true"></vxe-button>
  785. </el-tooltip>
  786. <el-tooltip content="保存" placement="top">
  787. <vxe-button circle icon="vxe-icon-save" @click="saveEvent"></vxe-button>
  788. </el-tooltip>
  789. <el-tooltip content="发送通知" placement="top">
  790. <el-dropdown style="padding: 0 10px;" trigger="click">
  791. <vxe-button circle icon="vxe-icon-bell"></vxe-button>
  792. <template #dropdown>
  793. <el-dropdown-menu>
  794. <el-dropdown-item v-for="info of dateType" @click="sendMessage(info.value)">{{
  795. info.label
  796. }}
  797. </el-dropdown-item>
  798. </el-dropdown-menu>
  799. </template>
  800. </el-dropdown>
  801. </el-tooltip>
  802. <el-tooltip content="下载表格" placement="top">
  803. <vxe-button circle icon="vxe-icon-download" @click="handleExport"></vxe-button>
  804. </el-tooltip>
  805. </div>
  806. </template>
  807. <template #operate="{ row }">
  808. <template v-if="hasActiveEditRow(row)">
  809. <vxe-button content="取消" type="text" @click="clearRowEvent(row)"></vxe-button>
  810. <vxe-button content="保存" status="success" type="text" @click="saveRowEvent(row)"></vxe-button>
  811. </template>
  812. <template v-else>
  813. <el-tooltip content="编辑" placement="top">
  814. <el-button icon="Edit" type="text" @click="editRowEvent(row)"></el-button>
  815. </el-tooltip>
  816. </template>
  817. </template>
  818. <template #number_edit="{ row }">
  819. <vxe-input v-model="row.platformNumber"></vxe-input>
  820. </template>
  821. <template #name_edit="{ row }">
  822. <vxe-input v-model="row.platformName"></vxe-input>
  823. </template>
  824. <template #country_edit="{ row }">
  825. <vxe-input v-model="row.country"></vxe-input>
  826. </template>
  827. <template #brand_edit="{ row }">
  828. <vxe-input v-model="row.brandName"></vxe-input>
  829. </template>
  830. <template #line_edit="{ row }">
  831. <vxe-input v-model="row.line"></vxe-input>
  832. </template>
  833. <template #ipaddress_edit="{ row }">
  834. <vxe-input v-model="row.ipaddress"></vxe-input>
  835. </template>
  836. <template #company_edit="{ row }">
  837. <vxe-input v-model="row.company"></vxe-input>
  838. </template>
  839. <template #platform_edit="{ row }">
  840. <vxe-input v-model="row.platform"></vxe-input>
  841. </template>
  842. <template #status_default="{ row }">
  843. <el-switch
  844. v-model="row.status"
  845. :active-value="1"
  846. :inactive-value="0"
  847. inline-prompt
  848. size="small"
  849. @change="handleStatusChange(row)"
  850. />
  851. </template>
  852. <template #operation_edit="{ row }">
  853. <!--<vxe-select v-model="row.user" multiple>-->
  854. <!-- <vxe-option v-for="item in operationList" :key="item.value" :label="item.label"-->
  855. <!-- :value="item.value"></vxe-option>-->
  856. <!--</vxe-select>-->
  857. <vxe-select
  858. v-model="row.user"
  859. multiple
  860. filterable
  861. clearable
  862. >
  863. <vxe-option
  864. v-for="item in operationList"
  865. :key="item.value"
  866. :label="item.label"
  867. :value="item.value"
  868. ></vxe-option>
  869. </vxe-select>
  870. </template>
  871. <template #operater_name_edit="{ row }">
  872. <vxe-input v-model="row.operater"></vxe-input>
  873. </template>
  874. <template #currency_edit="{ row }">
  875. <!--<vxe-input v-model="row.currencyCode"></vxe-input>-->
  876. <el-autocomplete
  877. v-model="row.currencyCode"
  878. :debounce="100"
  879. :fetch-suggestions="querySearch"
  880. :trigger-on-focus="false"
  881. clearable
  882. @blur="enableAutoExitEdit"
  883. @focus="disableAutoExitEdit"
  884. @select="item => handleRowSelect(item, row)"
  885. >
  886. <template v-slot="{ item }">
  887. <div>{{ item }}</div>
  888. </template>
  889. </el-autocomplete>
  890. </template>
  891. <template #currencyCodePlatform_edit="{ row }">
  892. <el-autocomplete
  893. v-model="row.currencyCodePlatform"
  894. :debounce="100"
  895. :fetch-suggestions="querySearch"
  896. :trigger-on-focus="false"
  897. clearable
  898. @blur="enableAutoExitEdit"
  899. @focus="disableAutoExitEdit"
  900. @select="item => handelRowCurrencyCodePlatformSelect(item,row)"
  901. >
  902. <template v-slot="{ item }">
  903. <div>{{ item }}</div>
  904. </template>
  905. </el-autocomplete>
  906. </template>
  907. <template #companyEnglishName_edit="{ row }">
  908. <vxe-input v-model="row.companyEnglishName"></vxe-input>
  909. </template>
  910. <template #address_edit="{ row }">
  911. <vxe-input v-model="row.address"></vxe-input>
  912. </template>
  913. <template #juridicalPerson_edit="{ row }">
  914. <vxe-input v-model="row.juridicalPerson"></vxe-input>
  915. </template>
  916. <template #juridicalPersonCreditCard_edit="{ row }">
  917. <vxe-input v-model="row.juridicalPersonCreditCard"></vxe-input>
  918. </template>
  919. <template #juridicalPersonCreditCardAddress_edit="{ row }">
  920. <vxe-input v-model="row.juridicalPersonCreditCardAddress"></vxe-input>
  921. </template>
  922. <template #receivablesAccount_edit="{ row }">
  923. <vxe-input v-model="row.receivablesAccount"></vxe-input>
  924. </template>
  925. <template #receivablesAccountCompany_edit="{ row }">
  926. <vxe-input v-model="row.receivablesAccountCompany"></vxe-input>
  927. </template>
  928. <template #vatNumber_edit="{ row }">
  929. <vxe-input v-model="row.vatNumber"></vxe-input>
  930. </template>
  931. <template #vatCompany_edit="{ row }">
  932. <vxe-input v-model="row.vatCompany"></vxe-input>
  933. </template>
  934. </vxe-grid>
  935. </el-card>
  936. </div>
  937. <el-dialog v-model="dialogFormVisible" :before-close="handleClose" style="border-radius: 10px;" title="新建任务"
  938. width="500">
  939. <el-form
  940. ref="taskRuleFormRef"
  941. :model="taskRuleForm"
  942. :rules="rules"
  943. :size="formSize"
  944. class="demo-taskRuleForm"
  945. label-width="auto"
  946. status-icon
  947. style="max-width: 600px">
  948. <el-form-item
  949. v-for="(item, index) in formItems"
  950. :key="index"
  951. :label="item.label"
  952. :prop="item.prop"
  953. >
  954. <template v-if="item.type === 'input'">
  955. <el-input v-model="taskRuleForm[item.prop]" :placeholder="item.placeholder" />
  956. </template>
  957. <template v-else-if="item.type === 'select'">
  958. <el-select
  959. v-model="taskRuleForm[item.prop]"
  960. :collapse-tags="item.collapseTags"
  961. :collapse-tags-tooltip="item.collapseTagsTooltip"
  962. :multiple="item.multiple"
  963. :placeholder="item.placeholder"
  964. >
  965. <el-option
  966. v-for="option in item.options"
  967. :key="option.value"
  968. :label="option.label"
  969. :value="option.value"
  970. ></el-option>
  971. </el-select>
  972. </template>
  973. <template v-else-if="item.type === 'autocomplete'">
  974. <el-autocomplete
  975. v-model="taskRuleForm[item.prop]"
  976. :clearable="item.clearable"
  977. :debounce="item.debounce"
  978. :fetch-suggestions="item.fetchSuggestions"
  979. :placeholder="item.placeholder"
  980. :trigger-on-focus="item.triggerOnFocus"
  981. @select="item.onSelect"
  982. >
  983. <template v-slot="{ item }">
  984. <div>{{ item }}</div>
  985. </template>
  986. </el-autocomplete>
  987. </template>
  988. </el-form-item>
  989. </el-form>
  990. <template #footer>
  991. <div class="dialog-footer">
  992. <el-button @click="dialogFormVisible = false ;resetForm(taskRuleFormRef)">取消</el-button>
  993. <el-button type="primary" @click="submitForm(taskRuleFormRef)"> 确认</el-button>
  994. </div>
  995. </template>
  996. </el-dialog>
  997. <el-dialog v-model="userDialogFormVisible" :before-close="updateUserCancel" align-center
  998. style="border-radius: 10px;" title="修改填写人" width="500">
  999. <div class="mb-3">
  1000. <el-radio-group v-model="updateSelect">
  1001. <el-radio :label="1">添加</el-radio>
  1002. <el-radio :label="2">删除</el-radio>
  1003. </el-radio-group>
  1004. </div>
  1005. <el-form-item align-center label="录入人员:" prop="operation" width="500">
  1006. <el-select v-model="taskRuleForm.operation" collapse-tags collapse-tags-tooltip multiple filterable clearable
  1007. placeholder="请选择录入人员">
  1008. <el-option v-for="item in operationList" :key="item.value" :label="item.label"
  1009. :value="item.value"></el-option>
  1010. </el-select>
  1011. </el-form-item>
  1012. <template #footer>
  1013. <div class="dialog-footer">
  1014. <el-button @click="updateUserCancel">取消</el-button>
  1015. <el-button type="primary" @click="updateUser"> 确认</el-button>
  1016. </div>
  1017. </template>
  1018. </el-dialog>
  1019. <el-dialog v-model="noticeDialogFormVisible" align-center
  1020. style="border-radius: 10px;" title="指定店铺发送通知" width="500">
  1021. <el-form-item align-center label="消息内容:" width="500">
  1022. <el-input v-model="noticeMessage" type="textarea"></el-input>
  1023. </el-form-item>
  1024. <template #footer>
  1025. <div class="dialog-footer">
  1026. <el-button @click="noticePersonCancel">取消</el-button>
  1027. <el-button type="primary" @click="noticePerson"> 确认</el-button>
  1028. </div>
  1029. </template>
  1030. </el-dialog>
  1031. </template>
  1032. <style scoped>
  1033. .custom-card-style {
  1034. z-index: 999;
  1035. position: sticky;
  1036. /* margin-top: 15px; */
  1037. margin-bottom: 5px;
  1038. }
  1039. .el-card {
  1040. border: none;
  1041. box-shadow: none;
  1042. }
  1043. :deep(.vxe-table--header .vxe-header--row th .vxe-cell,
  1044. .vxe-table--body .vxe-body--row td .vxe-cell) {
  1045. padding-left: 0 !important;
  1046. padding-right: 0 !important;
  1047. }
  1048. </style>