index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996
  1. <script setup lang="tsx">
  2. import { ref } from 'vue';
  3. import { NButton, NSpin, NTag, useMessage } from 'naive-ui';
  4. import type { TreeOption } from 'naive-ui';
  5. import {
  6. fetchAreaDetail,
  7. fetchAreaRule,
  8. fetchCheckAreaRule,
  9. fetchCheckMemberRule,
  10. fetchCheckUserRule,
  11. fetchConfig,
  12. fetchDelAreaRule,
  13. fetchDelMemberRule,
  14. fetchDelUserRule,
  15. fetchMemberDetail,
  16. fetchMemberList,
  17. fetchMemberRule,
  18. fetchProvince,
  19. fetchSaveAreaRule,
  20. fetchSaveConfig,
  21. fetchSaveMemberRule,
  22. fetchSaveTypeRule,
  23. fetchSaveUserRule,
  24. fetchTypeList,
  25. fetchTypeRule,
  26. fetchUpdateAreaRule,
  27. fetchUpdateAreaStatus,
  28. fetchUpdateMemberRule,
  29. fetchUpdateMemberStatus,
  30. fetchUpdateUserRule,
  31. fetchUpdateUserStatus,
  32. fetchUserDetail,
  33. fetchUserList,
  34. fetchUserRule
  35. } from '@/service/api/xsb-manage/free-ship';
  36. import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
  37. import { useTable } from '@/components/zt/Table/hooks/useTable';
  38. const message = useMessage();
  39. const config = ref({
  40. isEnabled: 0, // 总开关: 0-关闭, 1-开启
  41. calcBasis: 1, // 1-实付金额, 2-商品价格
  42. refundPolicy: 0 // : 0-关闭(不支持部分退款免邮), 1-开启
  43. });
  44. const userOptions = ref<any[]>([]);
  45. const memberOptions = ref<any[]>([]);
  46. const defaultCheckedKeys = ref<any[]>([]);
  47. const treeData = ref<TreeOption[]>([]);
  48. const treeLoading = ref(true);
  49. const typeRule = ref<any[]>([]);
  50. const [
  51. registerModalForm,
  52. {
  53. openModal: openModalForm,
  54. setFieldsValue: setModalFormValue,
  55. getFieldsValue: getModalFormValue,
  56. closeModal: closeModalForm,
  57. setSubLoading
  58. }
  59. ] = useModalFrom({
  60. modalConfig: {
  61. title: '区域规则',
  62. isShowHeaderText: false
  63. },
  64. formConfig: {
  65. schemas: [
  66. {
  67. label: '',
  68. field: 'id',
  69. component: 'NInput',
  70. show: false
  71. },
  72. {
  73. label: '',
  74. field: 'ruleName',
  75. component: 'NInput',
  76. show: false
  77. },
  78. {
  79. label: '',
  80. field: 'updateTime',
  81. component: 'NInput',
  82. show: false
  83. },
  84. {
  85. label: '',
  86. field: 'createTime',
  87. component: 'NInput',
  88. show: false
  89. },
  90. {
  91. label: '',
  92. field: 'tenantId',
  93. component: 'NInput',
  94. show: false
  95. },
  96. {
  97. label: '',
  98. field: 'ruleName',
  99. component: 'NInput',
  100. show: false
  101. },
  102. {
  103. field: 'regionType',
  104. label: '区域类型',
  105. component: 'NSelect',
  106. required: true,
  107. componentProps: {
  108. options: [
  109. {
  110. label: '省内',
  111. value: 1
  112. },
  113. {
  114. label: '省外',
  115. value: 2
  116. },
  117. {
  118. label: '指定省份',
  119. value: 3
  120. }
  121. ],
  122. placeholder: '请选择'
  123. }
  124. },
  125. {
  126. field: 'ruleName',
  127. label: '省份类型',
  128. component: 'ApiSelect',
  129. required: true,
  130. componentProps: {
  131. api: fetchProvince,
  132. labelField: 'provinceName',
  133. valueField: 'provinceName',
  134. placeholder: '请选择'
  135. },
  136. ifShow({ model }) {
  137. return model.regionType == 3;
  138. }
  139. },
  140. {
  141. field: 'thresholdAmount',
  142. label: '门槛金额',
  143. component: 'NInputNumber',
  144. componentProps: {
  145. placeholder: '请输入',
  146. precision: 2
  147. },
  148. suffix: 'desc-suffix',
  149. required: true
  150. },
  151. {
  152. field: 'regionStatus',
  153. label: '状态',
  154. component: 'NSwitch',
  155. defaultValue: 1,
  156. required: true,
  157. componentProps: {
  158. checkedValue: 1,
  159. uncheckedValue: 0
  160. }
  161. }
  162. ],
  163. labelWidth: 120,
  164. layout: 'horizontal',
  165. gridProps: {
  166. cols: '1',
  167. itemResponsive: true
  168. }
  169. }
  170. });
  171. const [
  172. registerUserModalForm,
  173. {
  174. openModal: openUserModalForm,
  175. setFieldsValue: setUserModalFormValue,
  176. getFieldsValue: getUserModalFormValue,
  177. closeModal: closeUserModal,
  178. setSubLoading: setUserSubLoading
  179. }
  180. ] = useModalFrom({
  181. modalConfig: {
  182. title: '身份规则',
  183. isShowHeaderText: false
  184. },
  185. formConfig: {
  186. schemas: [
  187. {
  188. label: '',
  189. field: 'id',
  190. component: 'NInput',
  191. show: false
  192. },
  193. {
  194. label: '',
  195. field: 'ruleName',
  196. component: 'NInput',
  197. show: false
  198. },
  199. {
  200. label: '',
  201. field: 'updateTime',
  202. component: 'NInput',
  203. show: false
  204. },
  205. {
  206. label: '',
  207. field: 'createTime',
  208. component: 'NInput',
  209. show: false
  210. },
  211. {
  212. label: '',
  213. field: 'tenantId',
  214. component: 'NInput',
  215. show: false
  216. },
  217. {
  218. label: '',
  219. field: 'identityName',
  220. component: 'NInput',
  221. show: false
  222. },
  223. {
  224. field: 'identityId',
  225. label: '身份类型',
  226. component: 'ApiSelect',
  227. required: true,
  228. componentProps: {
  229. api: fetchUserList,
  230. labelField: 'channelName',
  231. valueField: 'id',
  232. placeholder: '请选择',
  233. getOptions: async (options: any) => {
  234. userOptions.value = options;
  235. }
  236. }
  237. },
  238. {
  239. field: 'thresholdAmount',
  240. label: '门槛金额',
  241. component: 'NInputNumber',
  242. componentProps: {
  243. placeholder: '请输入',
  244. precision: 2
  245. },
  246. suffix: 'desc-suffix',
  247. required: true
  248. },
  249. {
  250. field: 'identityStatus',
  251. label: '状态',
  252. component: 'NSwitch',
  253. defaultValue: 1,
  254. required: true,
  255. componentProps: {
  256. checkedValue: 1,
  257. uncheckedValue: 0
  258. }
  259. }
  260. ],
  261. labelWidth: 120,
  262. layout: 'horizontal',
  263. gridProps: {
  264. cols: '1',
  265. itemResponsive: true
  266. }
  267. }
  268. });
  269. const [
  270. registerMemberModalForm,
  271. {
  272. openModal: openMemberModalForm,
  273. setFieldsValue: setMemberModalFormValue,
  274. getFieldsValue: getMemberModalFormValue,
  275. closeModal: closeMemberModal,
  276. setSubLoading: setMemberSubLoading
  277. }
  278. ] = useModalFrom({
  279. modalConfig: {
  280. title: '会员身份规则',
  281. isShowHeaderText: false
  282. },
  283. formConfig: {
  284. schemas: [
  285. {
  286. label: '',
  287. field: 'id',
  288. component: 'NInput',
  289. show: false
  290. },
  291. {
  292. label: '',
  293. field: 'ruleName',
  294. component: 'NInput',
  295. show: false
  296. },
  297. {
  298. label: '',
  299. field: 'updateTime',
  300. component: 'NInput',
  301. show: false
  302. },
  303. {
  304. label: '',
  305. field: 'createTime',
  306. component: 'NInput',
  307. show: false
  308. },
  309. {
  310. label: '',
  311. field: 'tenantId',
  312. component: 'NInput',
  313. show: false
  314. },
  315. {
  316. label: '',
  317. field: 'identityName',
  318. component: 'NInput',
  319. show: false
  320. },
  321. {
  322. field: 'identityId',
  323. label: '身份类型',
  324. component: 'ApiSelect',
  325. required: true,
  326. componentProps: {
  327. api: fetchMemberList,
  328. labelField: 'memberName',
  329. valueField: 'id',
  330. placeholder: '请选择',
  331. getOptions: async (options: any) => {
  332. memberOptions.value = options;
  333. }
  334. }
  335. },
  336. {
  337. field: 'thresholdAmount',
  338. label: '门槛金额',
  339. component: 'NInputNumber',
  340. componentProps: {
  341. placeholder: '请输入',
  342. precision: 2
  343. },
  344. suffix: 'desc-suffix',
  345. required: true
  346. },
  347. {
  348. field: 'memberStatus',
  349. label: '状态',
  350. component: 'NSwitch',
  351. defaultValue: 1,
  352. required: true,
  353. componentProps: {
  354. checkedValue: 1,
  355. uncheckedValue: 0
  356. }
  357. }
  358. ],
  359. labelWidth: 120,
  360. layout: 'horizontal',
  361. gridProps: {
  362. cols: '1',
  363. itemResponsive: true
  364. }
  365. }
  366. });
  367. const [registerTable, { refresh }] = useTable({
  368. tableConfig: {
  369. showTableHeaderAction: false,
  370. showSearch: false,
  371. showAddButton: false,
  372. keyField: 'id',
  373. title: '',
  374. showDeleteButton: false,
  375. showPagination: false,
  376. opWdith: 240
  377. }
  378. });
  379. const [registerUserTable, { refresh: refreshUser }] = useTable({
  380. tableConfig: {
  381. showTableHeaderAction: false,
  382. showSearch: false,
  383. showAddButton: false,
  384. keyField: 'id',
  385. title: '',
  386. showDeleteButton: false,
  387. showPagination: false,
  388. opWdith: 240
  389. }
  390. });
  391. const [registerMemberTable, { refresh: refreshMember }] = useTable({
  392. tableConfig: {
  393. showTableHeaderAction: false,
  394. showSearch: false,
  395. showAddButton: false,
  396. keyField: 'id',
  397. title: '',
  398. showDeleteButton: false,
  399. showPagination: false,
  400. opWdith: 240
  401. }
  402. });
  403. const areaColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
  404. {
  405. title: '规则类型',
  406. key: 'ruleName',
  407. align: 'center'
  408. },
  409. {
  410. title: '省份',
  411. key: 'provinceName',
  412. align: 'center',
  413. render: row => {
  414. return row.provinceName || '--';
  415. }
  416. },
  417. {
  418. title: '门槛金额',
  419. align: 'center',
  420. key: 'thresholdAmount',
  421. render: row => {
  422. return `¥${row.thresholdAmount}`;
  423. }
  424. },
  425. {
  426. title: '状态',
  427. align: 'center',
  428. key: 'regionStatus',
  429. render: row => {
  430. return row.regionStatus === 1 ? '启用' : '停用';
  431. }
  432. }
  433. ];
  434. const userColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
  435. {
  436. title: '用户身份',
  437. key: 'identityName',
  438. align: 'center'
  439. },
  440. {
  441. title: '门槛金额',
  442. align: 'center',
  443. key: 'thresholdAmount',
  444. render: row => {
  445. return `¥${row.thresholdAmount}`;
  446. }
  447. },
  448. {
  449. title: '状态',
  450. align: 'center',
  451. key: 'identityStatus',
  452. render: row => {
  453. return row.identityStatus === 1 ? '启用' : '停用';
  454. }
  455. }
  456. ];
  457. const memberColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
  458. {
  459. title: '用户身份',
  460. key: 'identityName',
  461. align: 'center'
  462. },
  463. {
  464. title: '门槛金额',
  465. align: 'center',
  466. key: 'thresholdAmount',
  467. render: row => {
  468. return `¥${row.thresholdAmount}`;
  469. }
  470. },
  471. {
  472. title: '状态',
  473. align: 'center',
  474. key: 'memberStatus',
  475. render: row => {
  476. return row.memberStatus === 1 ? '启用' : '停用';
  477. }
  478. }
  479. ];
  480. async function handleEdit(row: any) {
  481. // 编辑逻辑待实现
  482. openModalForm();
  483. const { data } = await fetchAreaDetail({ id: row.id });
  484. setModalFormValue(data);
  485. }
  486. function handleDelete(row: any) {
  487. // 删除逻辑待实现
  488. window.$dialog?.info({
  489. title: '提示',
  490. content: '你确定要删除吗?',
  491. positiveText: '确定',
  492. negativeText: '取消',
  493. onPositiveClick: async () => {
  494. const { error } = await fetchDelAreaRule({ id: row.id });
  495. if (!error) {
  496. refresh();
  497. }
  498. }
  499. });
  500. }
  501. function handleDisable(row: any) {
  502. // 停用逻辑待实现
  503. window.$dialog?.info({
  504. title: '提示',
  505. content: `你确定要${row.regionStatus === 1 ? '停用' : '启用'}吗?`,
  506. positiveText: '确定',
  507. negativeText: '取消',
  508. onPositiveClick: async () => {
  509. const { error } = await fetchUpdateAreaStatus({ id: row.id });
  510. if (!error) {
  511. refresh();
  512. }
  513. }
  514. });
  515. }
  516. async function handleValidateClick(e: MouseEvent) {
  517. e.preventDefault();
  518. const res = await fetchSaveConfig(config.value);
  519. if (!res.error) {
  520. message.success('保存成功');
  521. }
  522. }
  523. async function getConfig() {
  524. const res = await fetchConfig();
  525. console.log(res, 'res');
  526. if (!res.error && res.data) {
  527. config.value = res.data;
  528. }
  529. }
  530. getConfig();
  531. async function handleSubmit() {
  532. const form = await getModalFormValue();
  533. console.log('submit', form);
  534. setSubLoading(true);
  535. if (form.id) {
  536. // 编辑
  537. fetchUpdateAreaRule(form).then(res => {
  538. if (!res.error) {
  539. message.success('编辑成功');
  540. closeModalForm();
  541. refresh();
  542. }
  543. });
  544. } else {
  545. const { error } = await fetchCheckAreaRule(form);
  546. if (error) {
  547. // 确认逻辑待实现
  548. window.$dialog?.info({
  549. title: '提示',
  550. content: '已存在该省份数据,是否覆盖?',
  551. positiveText: '确定',
  552. negativeText: '取消',
  553. onPositiveClick: async () => {
  554. // 新增
  555. fetchSaveAreaRule(form).then(res => {
  556. if (!res.error) {
  557. message.success('新增成功');
  558. closeModalForm();
  559. refresh();
  560. }
  561. });
  562. },
  563. onNegativeClick: () => {
  564. setSubLoading(false);
  565. }
  566. });
  567. return;
  568. }
  569. // 新增
  570. fetchSaveAreaRule(form).then(res => {
  571. if (!res.error) {
  572. message.success('新增成功');
  573. closeModalForm();
  574. refresh();
  575. }
  576. });
  577. }
  578. }
  579. // -------------身份----------------
  580. // 身份
  581. async function handleUserSubmit() {
  582. const form = await getUserModalFormValue();
  583. console.log('submit', form);
  584. form.identityName = userOptions.value.find((item: any) => String(item.id) === String(form.identityId))?.channelName;
  585. setUserSubLoading(true);
  586. if (form.id) {
  587. // 编辑
  588. fetchUpdateUserRule(form).then(res => {
  589. if (!res.error) {
  590. message.success('编辑成功');
  591. closeUserModal();
  592. refreshUser();
  593. }
  594. });
  595. } else {
  596. const { error } = await fetchCheckUserRule(form);
  597. if (error) {
  598. // 确认逻辑待实现
  599. window.$dialog?.info({
  600. title: '提示',
  601. content: '已存在该身份数据,是否覆盖?',
  602. positiveText: '确定',
  603. negativeText: '取消',
  604. onPositiveClick: async () => {
  605. // 新增
  606. fetchSaveUserRule(form).then(res => {
  607. if (!res.error) {
  608. message.success('新增成功');
  609. closeUserModal();
  610. refreshUser();
  611. }
  612. });
  613. },
  614. onNegativeClick: () => {
  615. setUserSubLoading(false);
  616. }
  617. });
  618. return;
  619. }
  620. // 新增
  621. fetchSaveUserRule(form).then(res => {
  622. if (!res.error) {
  623. message.success('新增成功');
  624. closeUserModal();
  625. refreshUser();
  626. }
  627. });
  628. }
  629. }
  630. async function handleEditUser(row: any) {
  631. // 编辑逻辑待实现
  632. openUserModalForm();
  633. const { data } = await fetchUserDetail({ id: row.id });
  634. setUserModalFormValue(data);
  635. }
  636. function handleDeleteUser(row: any) {
  637. // 删除逻辑待实现
  638. window.$dialog?.info({
  639. title: '提示',
  640. content: '你确定要删除吗?',
  641. positiveText: '确定',
  642. negativeText: '取消',
  643. onPositiveClick: async () => {
  644. const { error } = await fetchDelUserRule({ id: row.id });
  645. if (!error) {
  646. refreshUser();
  647. }
  648. }
  649. });
  650. }
  651. function handleDisableUser(row: any) {
  652. // 停用逻辑待实现
  653. window.$dialog?.info({
  654. title: '提示',
  655. content: `你确定要${row.identityStatus === 1 ? '停用' : '启用'}吗?`,
  656. positiveText: '确定',
  657. negativeText: '取消',
  658. onPositiveClick: async () => {
  659. const { error } = await fetchUpdateUserStatus({ id: row.id });
  660. if (!error) {
  661. refreshUser();
  662. }
  663. }
  664. });
  665. }
  666. // -------------会员----------------
  667. // 身份
  668. async function handleMemberSubmit() {
  669. const form = await getMemberModalFormValue();
  670. console.log('submit', form);
  671. form.identityName = memberOptions.value.find((item: any) => String(item.id) === String(form.identityId))?.memberName;
  672. setMemberSubLoading(true);
  673. if (form.id) {
  674. // 编辑
  675. fetchUpdateMemberRule(form).then(res => {
  676. if (!res.error) {
  677. message.success('编辑成功');
  678. closeMemberModal();
  679. refreshMember();
  680. }
  681. });
  682. } else {
  683. const { error } = await fetchCheckMemberRule(form);
  684. if (error) {
  685. // 确认逻辑待实现
  686. window.$dialog?.info({
  687. title: '提示',
  688. content: '已存在该身份数据,是否覆盖?',
  689. positiveText: '确定',
  690. negativeText: '取消',
  691. onPositiveClick: async () => {
  692. // 新增
  693. fetchSaveMemberRule(form).then(res => {
  694. if (!res.error) {
  695. message.success('新增成功');
  696. closeMemberModal();
  697. refreshMember();
  698. }
  699. });
  700. },
  701. onNegativeClick: () => {
  702. setMemberSubLoading(false);
  703. }
  704. });
  705. return;
  706. }
  707. // 新增
  708. fetchSaveMemberRule(form).then(res => {
  709. if (!res.error) {
  710. message.success('新增成功');
  711. closeMemberModal();
  712. refreshMember();
  713. }
  714. });
  715. }
  716. }
  717. async function handleEditMember(row: any) {
  718. // 编辑逻辑待实现
  719. openMemberModalForm();
  720. const { data } = await fetchMemberDetail({ id: row.id });
  721. setMemberModalFormValue(data);
  722. }
  723. function handleDeleteMember(row: any) {
  724. // 删除逻辑待实现
  725. window.$dialog?.info({
  726. title: '提示',
  727. content: '你确定要删除吗?',
  728. positiveText: '确定',
  729. negativeText: '取消',
  730. onPositiveClick: async () => {
  731. const { error } = await fetchDelMemberRule({ id: row.id });
  732. if (!error) {
  733. refreshMember();
  734. }
  735. }
  736. });
  737. }
  738. function handleDisableMember(row: any) {
  739. // 停用逻辑待实现
  740. window.$dialog?.info({
  741. title: '提示',
  742. content: `你确定要${row.memberStatus === 1 ? '停用' : '启用'}吗?`,
  743. positiveText: '确定',
  744. negativeText: '取消',
  745. onPositiveClick: async () => {
  746. const { error } = await fetchUpdateMemberStatus({ id: row.id });
  747. if (!error) {
  748. refreshMember();
  749. }
  750. }
  751. });
  752. }
  753. // --------------------分类-----------------------
  754. function updateCheckedKeys(_keys: Array<string | number>, options: Array<TreeOption | null>) {
  755. typeRule.value = options.map(item => {
  756. return {
  757. categoryId: item?.id,
  758. categoryName: item?.name,
  759. parentCategoryId: item?.pid,
  760. shopId: item?.shopId
  761. };
  762. });
  763. }
  764. async function getTreeData() {
  765. const { data } = await fetchTypeList();
  766. treeData.value = data;
  767. }
  768. async function getTypeRule() {
  769. const { data } = await fetchTypeRule();
  770. typeRule.value = data;
  771. data.forEach((item: any) => {
  772. defaultCheckedKeys.value.push(item.categoryId);
  773. });
  774. }
  775. async function initTree() {
  776. treeLoading.value = true;
  777. await Promise.all([getTreeData(), getTypeRule()]);
  778. treeLoading.value = false;
  779. }
  780. initTree();
  781. async function submitType() {
  782. treeLoading.value = true;
  783. const { error } = await fetchSaveTypeRule(typeRule.value);
  784. if (!error) {
  785. message.success('保存成功');
  786. await getTypeRule();
  787. treeLoading.value = false;
  788. }
  789. }
  790. function removeCategory(id: string | number) {
  791. defaultCheckedKeys.value = defaultCheckedKeys.value.filter(key => key !== id);
  792. typeRule.value = typeRule.value.filter(item => item.categoryId !== id);
  793. }
  794. </script>
  795. <template>
  796. <div class="flex-col-stretch">
  797. <NCard :bordered="false" class="mb-20px flex-shrink-0">
  798. <div class="mb-20px text-16px font-bold">基础设置</div>
  799. <NForm
  800. :model="config"
  801. label-placement="left"
  802. label-width="auto"
  803. require-mark-placement="right-hanging"
  804. size="medium"
  805. class="max-w-640px"
  806. >
  807. <NFormItem label="启用免邮功能">
  808. <NSwitch v-model:value="config.isEnabled" :checked-value="1" :unchecked-value="0" />
  809. <div class="text-12px text-#ccc">(关闭后所有免邮规则不生效,订单运费恢复正常计费)</div>
  810. </NFormItem>
  811. <NFormItem label="计算基准">
  812. <NRadioGroup v-model:value="config.calcBasis">
  813. <NRadio :value="1">实付金额</NRadio>
  814. <NRadio :value="2">商品价格</NRadio>
  815. </NRadioGroup>
  816. </NFormItem>
  817. <NFormItem label="免邮退款限制">
  818. <NSwitch v-model:value="config.refundPolicy" :checked-value="1" :unchecked-value="0" />
  819. <div class="text-12px text-#ccc">(开启后免邮订单仅支持整单退款,不允许部分退款)</div>
  820. </NFormItem>
  821. <NFormItem>
  822. <NButton attr-type="button" type="primary" size="small" @click="handleValidateClick">保存设置</NButton>
  823. </NFormItem>
  824. </NForm>
  825. </NCard>
  826. <div class="rounded-8px bg-#fff p-10px">
  827. <div class="flex items-center">
  828. <div class="text-16px font-bold">区域门槛规则</div>
  829. <div class="text-12px text-#ccc">
  830. (根据收货地址区域设置不同的免邮门槛,若 规则命中"指定省份"规则后不再向上匹配)
  831. </div>
  832. </div>
  833. <div>
  834. <NButton type="primary" size="small" @click="openModalForm">+添加区域</NButton>
  835. </div>
  836. </div>
  837. <LayoutTable class="mh-200 mb-20px">
  838. <ZTable :columns="areaColumns" :api="fetchAreaRule" @register="registerTable">
  839. <template #op="{ row }">
  840. <NSpace>
  841. <NButton size="small" type="primary" @click="handleEdit(row)">编辑</NButton>
  842. <NButton size="small" @click="handleDisable(row)">{{ row.regionStatus == 1 ? '停用' : '启用' }}</NButton>
  843. <NButton size="small" type="error" @click="handleDelete(row)">删除</NButton>
  844. </NSpace>
  845. </template>
  846. </ZTable>
  847. </LayoutTable>
  848. <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmit">
  849. <template #desc-suffix="{}">
  850. <span>设为0元表示包邮</span>
  851. </template>
  852. </BasicModelForm>
  853. <div class="rounded-8px bg-#fff p-10px">
  854. <div class="flex items-center">
  855. <div class="text-16px font-bold">用户身份规则</div>
  856. <div class="text-12px text-#ccc">
  857. (根据不同身份等级的用户配置专属免邮策略,若同一用户命中多条身份规则时,取最低门槛金额)
  858. </div>
  859. </div>
  860. <div>
  861. <NButton type="primary" size="small" @click="openUserModalForm">+添加身份</NButton>
  862. </div>
  863. </div>
  864. <LayoutTable class="mh-200 mb-20px">
  865. <ZTable :columns="userColumns" :api="fetchUserRule" @register="registerUserTable">
  866. <template #op="{ row }">
  867. <NSpace>
  868. <NButton size="small" type="primary" @click="handleEditUser(row)">编辑</NButton>
  869. <NButton size="small" @click="handleDisableUser(row)">
  870. {{ row.identityStatus == 1 ? '停用' : '启用' }}
  871. </NButton>
  872. <NButton size="small" type="error" @click="handleDeleteUser(row)">删除</NButton>
  873. </NSpace>
  874. </template>
  875. </ZTable>
  876. </LayoutTable>
  877. <BasicModelForm @register-modal-form="registerUserModalForm" @submit-form="handleUserSubmit">
  878. <template #desc-suffix="{}">
  879. <span>设为0元表示包邮</span>
  880. </template>
  881. </BasicModelForm>
  882. <!------------ 会员------------ -->
  883. <div class="rounded-8px bg-#fff p-10px">
  884. <div class="flex items-center">
  885. <div class="text-16px font-bold">会员身份规则</div>
  886. <div class="text-12px text-#ccc">
  887. (根据不同身份等级的用户配置专属免邮策略,若同一用户命中多条身份规则时,取最低门槛金额)
  888. </div>
  889. </div>
  890. <div>
  891. <NButton type="primary" size="small" @click="openMemberModalForm">+添加身份</NButton>
  892. </div>
  893. </div>
  894. <LayoutTable class="mh-200 mb-20px">
  895. <ZTable :columns="memberColumns" :api="fetchMemberRule" @register="registerMemberTable">
  896. <template #op="{ row }">
  897. <NSpace>
  898. <NButton size="small" type="primary" @click="handleEditMember(row)">编辑</NButton>
  899. <NButton size="small" @click="handleDisableMember(row)">
  900. {{ row.memberStatus == 1 ? '停用' : '启用' }}
  901. </NButton>
  902. <NButton size="small" type="error" @click="handleDeleteMember(row)">删除</NButton>
  903. </NSpace>
  904. </template>
  905. </ZTable>
  906. </LayoutTable>
  907. <BasicModelForm @register-modal-form="registerMemberModalForm" @submit-form="handleMemberSubmit">
  908. <template #desc-suffix="{}">
  909. <span>设为0元表示包邮</span>
  910. </template>
  911. </BasicModelForm>
  912. <!--------------- 分类 ---------------------->
  913. <NCard :bordered="false" class="mb-20px flex-shrink-0">
  914. <div class="flex items-center rounded-8px bg-#fff">
  915. <div class="text-16px font-bold">商品类目规则</div>
  916. <div class="text-12px text-#ccc">(选择参与免邮的商品类目)</div>
  917. </div>
  918. <div class="flex">
  919. <div class="w-400px">
  920. <NSpin :show="treeLoading">
  921. <NTree
  922. v-model:checked-keys="defaultCheckedKeys"
  923. block-line
  924. cascade
  925. checkable
  926. :selectable="false"
  927. check-strategy="child"
  928. :data="treeData"
  929. key-field="id"
  930. label-field="name"
  931. default-expand-all
  932. @update:checked-keys="updateCheckedKeys"
  933. />
  934. </NSpin>
  935. </div>
  936. <div class="ml-12px min-h-400px flex-1 border-1px border-#e8e8e8 rounded-8px bg-#fafafa p-12px">
  937. <div class="mb-8px text-14px text-#333 font-bold">已选包邮类目</div>
  938. <div class="flex flex-wrap content-start items-start gap-6px">
  939. <NTag v-for="item in typeRule" :key="item.categoryId" closable @close="removeCategory(item.categoryId)">
  940. {{ item.categoryName }}
  941. </NTag>
  942. <span v-if="!typeRule.length" class="text-12px text-#999">暂未选择</span>
  943. </div>
  944. </div>
  945. </div>
  946. <NButton :loading="treeLoading" type="primary" size="small" @click="submitType">保存设置</NButton>
  947. </NCard>
  948. </div>
  949. </template>
  950. <style scoped>
  951. .mh-200 {
  952. min-height: 200px !important;
  953. }
  954. </style>