index.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  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: 'juridicalPerson', type: 'input', placeholder: '请输入公司法人' },
  737. // { label: '公司地址', prop: 'address', 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 :disabled="isDeleteDisabled" text bg :icon="Delete" plain type="danger" @click="removeEvent">删除
  776. </el-button>
  777. <el-button v-if="!isDeleteDisabled" plain text bg 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)">{{info.label}}</el-dropdown-item>
  795. </el-dropdown-menu>
  796. </template>
  797. </el-dropdown>
  798. </el-tooltip>
  799. <el-tooltip content="下载表格" placement="top">
  800. <vxe-button circle icon="vxe-icon-download" @click="handleExport"></vxe-button>
  801. </el-tooltip>
  802. </div>
  803. </template>
  804. <template #operate="{ row }">
  805. <template v-if="hasActiveEditRow(row)">
  806. <vxe-button content="取消" type="text" @click="clearRowEvent(row)"></vxe-button>
  807. <vxe-button content="保存" status="success" type="text" @click="saveRowEvent(row)"></vxe-button>
  808. </template>
  809. <template v-else>
  810. <el-tooltip content="编辑" placement="top">
  811. <el-button icon="Edit" type="text" @click="editRowEvent(row)"></el-button>
  812. </el-tooltip>
  813. </template>
  814. </template>
  815. <template #number_edit="{ row }">
  816. <vxe-input v-model="row.platformNumber"></vxe-input>
  817. </template>
  818. <template #name_edit="{ row }">
  819. <vxe-input v-model="row.platformName"></vxe-input>
  820. </template>
  821. <template #country_edit="{ row }">
  822. <vxe-input v-model="row.country"></vxe-input>
  823. </template>
  824. <template #brand_edit="{ row }">
  825. <vxe-input v-model="row.brandName"></vxe-input>
  826. </template>
  827. <template #department_edit="{ row }">
  828. <vxe-input v-model="row.department"></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 #juridicalPerson_edit="{ row }">
  911. <vxe-input v-model="row.juridicalPerson"></vxe-input>
  912. </template>
  913. </vxe-grid>
  914. </el-card>
  915. </div>
  916. <el-dialog v-model="dialogFormVisible" :before-close="handleClose" style="border-radius: 10px;" title="新建任务"
  917. width="500">
  918. <el-form
  919. ref="taskRuleFormRef"
  920. :model="taskRuleForm"
  921. :rules="rules"
  922. :size="formSize"
  923. class="demo-taskRuleForm"
  924. label-position="top"
  925. label-width="auto"
  926. status-icon
  927. style="max-width: 600px">
  928. <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px">
  929. <el-form-item
  930. v-for="(item, index) in formItems"
  931. :key="index"
  932. :label="item.label"
  933. :prop="item.prop"
  934. >
  935. <template v-if="item.type === 'input'">
  936. <el-input v-model="taskRuleForm[item.prop]" :placeholder="item.placeholder" />
  937. </template>
  938. <template v-else-if="item.type === 'select'">
  939. <el-select
  940. v-model="taskRuleForm[item.prop]"
  941. :collapse-tags="item.collapseTags"
  942. :collapse-tags-tooltip="item.collapseTagsTooltip"
  943. :multiple="item.multiple"
  944. :placeholder="item.placeholder"
  945. >
  946. <el-option
  947. v-for="option in item.options"
  948. :key="option.value"
  949. :label="option.label"
  950. :value="option.value"
  951. ></el-option>
  952. </el-select>
  953. </template>
  954. <template v-else-if="item.type === 'autocomplete'">
  955. <el-autocomplete
  956. v-model="taskRuleForm[item.prop]"
  957. :clearable="item.clearable"
  958. :debounce="item.debounce"
  959. :fetch-suggestions="item.fetchSuggestions"
  960. :placeholder="item.placeholder"
  961. :trigger-on-focus="item.triggerOnFocus"
  962. @select="item.onSelect"
  963. >
  964. <template v-slot="{ item }">
  965. <div>{{ item }}</div>
  966. </template>
  967. </el-autocomplete>
  968. </template>
  969. </el-form-item>
  970. </div>
  971. </el-form>
  972. <template #footer>
  973. <div class="dialog-footer">
  974. <el-button @click="dialogFormVisible = false ;resetForm(taskRuleFormRef)">取消</el-button>
  975. <el-button type="primary" @click="submitForm(taskRuleFormRef)">确认</el-button>
  976. </div>
  977. </template>
  978. </el-dialog>
  979. <el-dialog v-model="userDialogFormVisible" :before-close="updateUserCancel" align-center
  980. style="border-radius: 10px;" title="修改填写人" width="500">
  981. <div class="mb-3">
  982. <el-radio-group v-model="updateSelect">
  983. <el-radio :label="1">添加</el-radio>
  984. <el-radio :label="2">删除</el-radio>
  985. </el-radio-group>
  986. </div>
  987. <el-form-item align-center label="录入人员:" prop="operation" width="500">
  988. <el-select v-model="taskRuleForm.operation" collapse-tags collapse-tags-tooltip multiple filterable clearable
  989. placeholder="请选择录入人员">
  990. <el-option v-for="item in operationList" :key="item.value" :label="item.label"
  991. :value="item.value"></el-option>
  992. </el-select>
  993. </el-form-item>
  994. <template #footer>
  995. <div class="dialog-footer">
  996. <el-button @click="updateUserCancel">取消</el-button>
  997. <el-button type="primary" @click="updateUser"> 确认</el-button>
  998. </div>
  999. </template>
  1000. </el-dialog>
  1001. <el-dialog v-model="noticeDialogFormVisible" align-center
  1002. style="border-radius: 10px;" title="指定店铺发送通知" width="500">
  1003. <el-form-item align-center label="消息内容:" width="500">
  1004. <el-input v-model="noticeMessage" type="textarea"></el-input>
  1005. </el-form-item>
  1006. <template #footer>
  1007. <div class="dialog-footer">
  1008. <el-button @click="noticePersonCancel">取消</el-button>
  1009. <el-button type="primary" @click="noticePerson"> 确认</el-button>
  1010. </div>
  1011. </template>
  1012. </el-dialog>
  1013. </template>
  1014. <style scoped>
  1015. .custom-card-style {
  1016. z-index: 999;
  1017. position: sticky;
  1018. /* margin-top: 15px; */
  1019. margin-bottom: 5px;
  1020. }
  1021. .el-card {
  1022. border: none;
  1023. box-shadow: none;
  1024. }
  1025. :deep(.vxe-table--header .vxe-header--row th .vxe-cell,
  1026. .vxe-table--body .vxe-body--row td .vxe-cell) {
  1027. padding-left: 0 !important;
  1028. padding-right: 0 !important;
  1029. }
  1030. </style>