index.vue 35 KB

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