overrideRules.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. <template>
  2. <div class="container">
  3. <Breadcrumb :items="['menu.overrideRules', 'menu.overrideRules.list']" />
  4. <a-card class="general-card" :title="$t('menu.overrideRules.list')">
  5. <a-row>
  6. <a-col :flex="1">
  7. <a-form :model="formModel" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
  8. label-align="left">
  9. <a-row :gutter="16">
  10. <a-col :span="8">
  11. <a-form-item field="keyword" label="搜索">
  12. <a-input v-model="formModel.keyword" placeholder="输入关键词搜索" />
  13. </a-form-item>
  14. </a-col>
  15. </a-row>
  16. </a-form>
  17. </a-col>
  18. <a-divider style="height: 38px" direction="vertical" />
  19. <a-col :flex="'86px'" style="text-align: right">
  20. <a-space direction="vertical" :size="18">
  21. <a-button type="primary" @click="search">
  22. <template #icon>
  23. <icon-search />
  24. </template>
  25. {{ $t('searchTable.form.search') }}
  26. </a-button>
  27. </a-space>
  28. </a-col>
  29. </a-row>
  30. <a-divider style="margin-top: 0" />
  31. <a-row style="margin-bottom: 16px">
  32. <a-col :span="12">
  33. <a-space>
  34. <a-button type="primary" @click="onAdd">
  35. <template #icon>
  36. <icon-plus />
  37. </template>
  38. {{ $t('searchTable.operation.create') }}
  39. </a-button>
  40. </a-space>
  41. </a-col>
  42. <a-col :span="12" style="
  43. display: flex;
  44. align-items: center;
  45. justify-content: end;
  46. ">
  47. <a-tooltip :content="$t('searchTable.actions.refresh')">
  48. <div class="action-icon" @click="search"><icon-refresh size="18" /></div>
  49. </a-tooltip>
  50. <a-dropdown @select="handleSelectDensity">
  51. <a-tooltip :content="$t('searchTable.actions.density')">
  52. <div class="action-icon"><icon-line-height size="18" /></div>
  53. </a-tooltip>
  54. <template #content>
  55. <a-doption v-for="item in densityList" :key="item.value" :value="item.value"
  56. :class="{ active: item.value === size }">
  57. <span>{{ item.name }}</span>
  58. </a-doption>
  59. </template>
  60. </a-dropdown>
  61. <a-tooltip :content="$t('searchTable.actions.columnSetting')">
  62. <a-popover trigger="click" position="bl" @popup-visible-change="popupVisibleChange">
  63. <div class="action-icon"><icon-settings size="18" /></div>
  64. <template #content>
  65. <div id="tableSetting">
  66. <div v-for="(item, index) in showColumns" :key="item.dataIndex" class="setting">
  67. <div style="
  68. margin-right: 4px;
  69. cursor: move;
  70. ">
  71. <icon-drag-arrow />
  72. </div>
  73. <div>
  74. <a-checkbox v-model="item.checked" @change="
  75. handleChange(
  76. $event,
  77. item as TableColumnData,
  78. index
  79. )
  80. ">
  81. </a-checkbox>
  82. </div>
  83. <div class="title">
  84. {{
  85. item.title === '#'
  86. ? '序列号'
  87. : item.title
  88. }}
  89. </div>
  90. </div>
  91. </div>
  92. </template>
  93. </a-popover>
  94. </a-tooltip>
  95. </a-col>
  96. </a-row>
  97. <a-table row-key="id" :loading="loading" :pagination="pagination"
  98. :columns="(cloneColumns as TableColumnData[])" :data="renderData" :bordered="{ headerCell: true }"
  99. :size="size" @page-change="onPageChange" @page-size-change="onPageSizeChange">
  100. <template #index="{ record }">
  101. {{ record.id }}
  102. </template>
  103. <template #platform="{ record }">
  104. <a-tag color="blue">{{ record.platform }}</a-tag>
  105. </template>
  106. <template #original_text="{ record }">
  107. <a-typography-paragraph :ellipsis="{ rows: 2 }">
  108. {{ record.original_text }}
  109. </a-typography-paragraph>
  110. </template>
  111. <template #rule="{ record }">
  112. <a-tag v-if="record.rule === 'keyword'" color="blue">关键词</a-tag>
  113. <a-tag v-else-if="record.rule === 'regex'" color="orange">正则</a-tag>
  114. <a-tag v-else-if="record.rule === 'text'" color="green">全文匹配</a-tag>
  115. <a-tag v-else>{{ record.rule }}</a-tag>
  116. </template>
  117. <template #output="{ record }">
  118. <div>
  119. <a-tag v-if="record.output_type === 'content'" color="arcoblue">原文处理</a-tag>
  120. <a-tag v-else-if="record.output_type === 'deeplink'" color="purple">深度链接</a-tag>
  121. <a-tag v-else-if="record.output_type === 'url'" color="cyan">URL</a-tag>
  122. <a-tag v-else>{{ record.output_type }}</a-tag>
  123. </div>
  124. <a-typography-paragraph :ellipsis="{ rows: 2 }">
  125. <a-typography-text>
  126. {{ record.output_text }}
  127. </a-typography-text>
  128. </a-typography-paragraph>
  129. <div>
  130. <a-tag :color="record.response_success ? 'green' : 'red'" size="small">
  131. success: {{ record.response_success ? '成功' : '失败' }}
  132. </a-tag>
  133. </div>
  134. <a-typography-text v-if="record.response_message || record.response_reason" type="secondary"
  135. style="font-size: 12px;">
  136. <span v-if="record.response_message">message: {{ record.response_message }}</span>
  137. <span v-if="record.response_message && record.response_reason">;</span>
  138. <span v-if="record.response_reason">reason: {{ record.response_reason }}</span>
  139. </a-typography-text>
  140. </template>
  141. <template #status="{ record }">
  142. <a-tag v-if="record.status" bordered color="green">
  143. <template #icon>
  144. <icon-check />
  145. </template>
  146. 启用
  147. </a-tag>
  148. <a-tag v-else bordered color="">
  149. <template #icon>
  150. <icon-stop />
  151. </template>
  152. 禁用
  153. </a-tag>
  154. </template>
  155. <template #limits="{ record }">
  156. <div style="font-size: 12px; color: #666;">
  157. <div>日限: {{ record.current_daily_calls || 0 }}/{{ record.daily_calls_limit }}</div>
  158. <div>时限: {{ record.current_hourly_calls || 0 }}/{{ record.hourly_calls_limit }}</div>
  159. <div v-if="record.use_special_text === 1">验证入参: special_text</div>
  160. <div v-if="record.use_risk_control === 1">启用风控</div>
  161. </div>
  162. </template>
  163. <template #description="{ record }">
  164. <a-typography-paragraph v-if="record.description" :ellipsis="{ rows: 2 }">
  165. <a-typography-text type="secondary">
  166. {{ record.description }}
  167. </a-typography-text>
  168. </a-typography-paragraph>
  169. <span v-else style="color: #ccc;">-</span>
  170. </template>
  171. <template #time="{ record }">
  172. <div style="color:gray;">
  173. <div>添加:{{ dayjs(record.create_time).format('MM-DD HH:mm') }}</div>
  174. <div>更新:{{ dayjs(record.last_time).format('MM-DD HH:mm') }}</div>
  175. </div>
  176. </template>
  177. <template #command="{ record }">
  178. <a-space>
  179. <a-button type="primary" size="small" @click="onEdit(record)">
  180. 修改
  181. </a-button>
  182. <a-button status="danger" size="small" @click="onDelete(record)">
  183. 删除
  184. </a-button>
  185. </a-space>
  186. </template>
  187. </a-table>
  188. </a-card>
  189. <a-modal v-model:visible="visible" :title="isEdit ? '修改覆盖规则' : '新建覆盖规则'" @ok="handleSubmit"
  190. @cancel="handleCancel" width="800px">
  191. <a-form :model="editForm" layout="vertical">
  192. <a-row :gutter="24">
  193. <a-col :span="12">
  194. <a-form-item label="平台" required>
  195. <a-input v-model="editForm.platform" placeholder="请输入平台标识,如:tk、jd、pdd" />
  196. </a-form-item>
  197. </a-col>
  198. <a-col :span="12">
  199. <a-form-item label="规则类型" required>
  200. <a-select v-model="editForm.rule" :options="ruleOptions" placeholder="请选择规则类型" />
  201. </a-form-item>
  202. </a-col>
  203. <a-col :span="24">
  204. <a-form-item label="原始文本/参考关键词" required>
  205. <template #extra>
  206. <div>如请求传入 query_text 参数,将替代此文本进行匹配</div>
  207. </template>
  208. <a-textarea v-model="editForm.original_text" placeholder="请输入原始文本或正则表达式"
  209. :auto-size="{ minRows: 2, maxRows: 4 }" />
  210. </a-form-item>
  211. </a-col>
  212. <a-col :span="12">
  213. <a-form-item label="输出类型" required>
  214. <a-select v-model="editForm.output_type" :options="outputTypeOptions"
  215. placeholder="请选择输出类型" />
  216. </a-form-item>
  217. </a-col>
  218. <a-col :span="12">
  219. <a-form-item label="权重(优先级)">
  220. <a-input-number v-model="editForm.sort" placeholder="数值越大越优先" :min="0"
  221. style="width: 100%;" />
  222. </a-form-item>
  223. </a-col>
  224. <a-col :span="24">
  225. <a-form-item label="输出文本" required>
  226. <template #extra>
  227. <div>支持占位符:{query_text}、{url:query_text}、{url2:query_text}</div>
  228. </template>
  229. <a-textarea v-model="editForm.output_text" placeholder="请输入替换后的文本"
  230. :auto-size="{ minRows: 2, maxRows: 4 }" />
  231. </a-form-item>
  232. </a-col>
  233. <a-col :span="24">
  234. <a-divider orientation="left" style="margin-top: 0;">响应字段覆写</a-divider>
  235. </a-col>
  236. <a-col :span="8">
  237. <a-form-item label="响应结果 success" required>
  238. <a-radio-group v-model="editForm.response_success" type="button">
  239. <a-radio :value="true">成功</a-radio>
  240. <a-radio :value="false">失败</a-radio>
  241. </a-radio-group>
  242. </a-form-item>
  243. </a-col>
  244. <a-col :span="8">
  245. <a-form-item label="message">
  246. <a-input v-model="editForm.response_message" placeholder="留空沿用默认值" allow-clear
  247. :max-length="255" />
  248. </a-form-item>
  249. </a-col>
  250. <a-col :span="8">
  251. <a-form-item label="reason / sub_message">
  252. <a-input v-model="editForm.response_reason" placeholder="留空沿用默认值" allow-clear
  253. :max-length="255" />
  254. </a-form-item>
  255. </a-col>
  256. <a-col :span="24">
  257. <div style="font-size: 12px; color: #999; margin: -12px 0 16px;">
  258. 命中本规则后,API 响应及转链日志(后续统计)中的 success/message/reason 将使用此处配置;reason 同时作为 sub_message 输出
  259. </div>
  260. </a-col>
  261. <a-col :span="24">
  262. <a-form-item label="备注">
  263. <a-textarea v-model="editForm.description" placeholder="请输入备注说明"
  264. :auto-size="{ minRows: 2, maxRows: 3 }" />
  265. </a-form-item>
  266. </a-col>
  267. <a-col :span="12">
  268. <a-form-item label="每日调用限制">
  269. <a-input-number v-model="editForm.daily_calls_limit" placeholder="0表示不限制" :min="0"
  270. style="width: 100%;" />
  271. </a-form-item>
  272. </a-col>
  273. <a-col :span="12">
  274. <a-form-item label="每小时调用限制">
  275. <a-input-number v-model="editForm.hourly_calls_limit" placeholder="0表示不限制" :min="0"
  276. style="width: 100%;" />
  277. </a-form-item>
  278. </a-col>
  279. <a-col :span="12">
  280. <a-form-item label="验证入参 special_text">
  281. <a-switch v-model="editForm.use_special_text" :checked-value="1" :unchecked-value="0"
  282. checked-text="是" unchecked-text="否" />
  283. <div style="font-size: 12px; color: #999; margin-top: 4px;">
  284. 是否仅在入参 special_text=1 时匹配
  285. </div>
  286. </a-form-item>
  287. </a-col>
  288. <a-col :span="12">
  289. <a-form-item label="启用风控">
  290. <a-switch v-model="editForm.use_risk_control" :checked-value="1" :unchecked-value="0"
  291. checked-text="是" unchecked-text="否" />
  292. <div style="font-size: 12px; color: #999; margin-top: 4px;">
  293. OAID/地区/IP控制
  294. </div>
  295. </a-form-item>
  296. </a-col>
  297. <a-col :span="12">
  298. <a-form-item label="状态">
  299. <a-switch v-model="editForm.status" :checked-value="true" :unchecked-value="false"
  300. checked-text="启用" unchecked-text="禁用" />
  301. </a-form-item>
  302. </a-col>
  303. </a-row>
  304. </a-form>
  305. </a-modal>
  306. </div>
  307. </template>
  308. <script lang="ts" setup>
  309. import { computed, ref, reactive, watch, nextTick } from 'vue';
  310. import { useI18n } from 'vue-i18n';
  311. import dayjs from 'dayjs';
  312. import useLoading from '@/hooks/loading';
  313. import { Pagination } from '@/types/global';
  314. import { Modal, Message } from '@arco-design/web-vue';
  315. import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
  316. import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
  317. import cloneDeep from 'lodash/cloneDeep';
  318. import Sortable from 'sortablejs';
  319. import {
  320. OverrideRuleParams,
  321. OverrideRuleRecord,
  322. queryOverrideRuleList,
  323. updateOverrideRule,
  324. createOverrideRule,
  325. deleteOverrideRule
  326. } from '@/api/overrideRules';
  327. type SizeProps = 'mini' | 'small' | 'medium' | 'large';
  328. type Column = TableColumnData & { checked?: true };
  329. const generateFormModel = () => {
  330. return {
  331. keyword: '',
  332. sort: 'sort',
  333. order: 'descending',
  334. };
  335. };
  336. const { loading, setLoading } = useLoading(true);
  337. const { t } = useI18n();
  338. const renderData = ref<OverrideRuleRecord[]>([]);
  339. const formModel = ref(generateFormModel());
  340. const cloneColumns = ref<Column[]>([]);
  341. const showColumns = ref<Column[]>([]);
  342. const size = ref<SizeProps>('small');
  343. const basePagination: Pagination = {
  344. current: 1,
  345. pageSize: 50,
  346. showPageSize: true,
  347. showTotal: true,
  348. pageSizeOptions: [20, 50, 100],
  349. };
  350. const pagination = reactive({
  351. ...basePagination,
  352. });
  353. const densityList = computed(() => [
  354. {
  355. name: t('searchTable.size.mini'),
  356. value: 'mini',
  357. },
  358. {
  359. name: t('searchTable.size.small'),
  360. value: 'small',
  361. },
  362. {
  363. name: t('searchTable.size.medium'),
  364. value: 'medium',
  365. },
  366. {
  367. name: t('searchTable.size.large'),
  368. value: 'large',
  369. },
  370. ]);
  371. const columns = computed<TableColumnData[]>(() => [
  372. {
  373. title: '#',
  374. dataIndex: 'index',
  375. slotName: 'index',
  376. width: 60,
  377. },
  378. {
  379. title: '平台',
  380. slotName: 'platform',
  381. width: 100,
  382. },
  383. {
  384. title: '原始文本',
  385. slotName: 'original_text',
  386. width: 180,
  387. },
  388. {
  389. title: '规则类型',
  390. slotName: 'rule',
  391. width: 120,
  392. },
  393. {
  394. title: '输出',
  395. slotName: 'output',
  396. width: 180,
  397. },
  398. {
  399. title: '限制条件',
  400. slotName: 'limits',
  401. width: 150,
  402. },
  403. {
  404. title: '备注',
  405. slotName: 'description',
  406. width: 150,
  407. },
  408. {
  409. title: '状态',
  410. slotName: 'status',
  411. width: 100,
  412. },
  413. {
  414. title: '权重',
  415. dataIndex: 'sort',
  416. width: 80,
  417. },
  418. {
  419. title: '时间',
  420. slotName: 'time',
  421. width: 170,
  422. },
  423. {
  424. title: '操作',
  425. slotName: 'command',
  426. width: 150,
  427. },
  428. ]);
  429. const ruleOptions = computed<SelectOptionData[]>(() => [
  430. { label: '关键词', value: 'keyword' },
  431. { label: '正则', value: 'regex' },
  432. { label: '全文匹配', value: 'text' },
  433. ]);
  434. const outputTypeOptions = computed<SelectOptionData[]>(() => [
  435. { label: '原文处理', value: 'content' },
  436. { label: '深度链接', value: 'deeplink' },
  437. { label: 'URL', value: 'url' },
  438. ]);
  439. const fetchData = async (
  440. params: OverrideRuleParams = {
  441. current: 1,
  442. pageSize: 50,
  443. sort: 'sort',
  444. order: 'descending',
  445. getTotal: true,
  446. }
  447. ) => {
  448. setLoading(true);
  449. try {
  450. const { data } = await queryOverrideRuleList(params);
  451. if (!data) return;
  452. renderData.value = data.list;
  453. pagination.current = data.page;
  454. pagination.total = data.count;
  455. } catch (err) {
  456. console.log(err);
  457. } finally {
  458. setLoading(false);
  459. }
  460. };
  461. const search = () => {
  462. fetchData({
  463. ...basePagination,
  464. ...formModel.value,
  465. } as unknown as OverrideRuleParams);
  466. };
  467. const onPageChange = (current: number) => {
  468. fetchData({
  469. ...basePagination,
  470. ...formModel.value,
  471. current
  472. } as unknown as OverrideRuleParams);
  473. };
  474. const onPageSizeChange = (pageSize: number) => {
  475. basePagination.pageSize = pageSize;
  476. pagination.pageSize = pageSize;
  477. fetchData({
  478. ...basePagination,
  479. ...formModel.value,
  480. current: 1,
  481. } as unknown as OverrideRuleParams);
  482. };
  483. const visible = ref(false);
  484. const isEdit = ref(false);
  485. const editForm = ref<Partial<OverrideRuleRecord>>({
  486. platform: '',
  487. original_text: '',
  488. rule: 'keyword',
  489. output_text: '',
  490. output_type: 'content',
  491. status: true,
  492. sort: 0,
  493. description: '',
  494. daily_calls_limit: 0,
  495. hourly_calls_limit: 0,
  496. use_special_text: 0,
  497. use_risk_control: 0,
  498. response_success: true,
  499. response_message: '',
  500. response_reason: '',
  501. });
  502. const onEdit = (current: OverrideRuleRecord) => {
  503. isEdit.value = true;
  504. editForm.value = {
  505. ...current,
  506. status: Boolean(current.status),
  507. response_success: Boolean(current.response_success ?? true),
  508. };
  509. visible.value = true;
  510. };
  511. const onAdd = () => {
  512. isEdit.value = false;
  513. editForm.value = {
  514. platform: '',
  515. original_text: '',
  516. rule: 'keyword',
  517. output_text: '',
  518. output_type: 'content',
  519. status: true,
  520. sort: 0,
  521. description: '',
  522. daily_calls_limit: 0,
  523. hourly_calls_limit: 0,
  524. use_special_text: 0,
  525. use_risk_control: 0,
  526. response_success: true,
  527. response_message: '',
  528. response_reason: '',
  529. };
  530. visible.value = true;
  531. };
  532. const onDelete = (current: OverrideRuleRecord) => {
  533. Modal.confirm({
  534. title: '确认删除',
  535. content: '确定要删除这条规则吗?删除后无法恢复。',
  536. onOk: async () => {
  537. try {
  538. const response = await deleteOverrideRule(current.id);
  539. if (response.data.success) {
  540. Message.success(response.data.msg || '删除成功');
  541. fetchData({
  542. ...basePagination,
  543. ...formModel.value,
  544. } as unknown as OverrideRuleParams);
  545. } else {
  546. Message.error(response.data.msg || '删除失败');
  547. }
  548. } catch (error) {
  549. Message.error('删除请求失败');
  550. }
  551. }
  552. });
  553. };
  554. fetchData();
  555. const handleSelectDensity = (
  556. val: string | number | Record<string, any> | undefined,
  557. e: Event
  558. ) => {
  559. size.value = val as SizeProps;
  560. };
  561. const handleChange = (
  562. checked: boolean | (string | boolean | number)[],
  563. column: Column,
  564. index: number
  565. ) => {
  566. if (!checked) {
  567. cloneColumns.value = showColumns.value.filter(
  568. (item) => item.dataIndex !== column.dataIndex
  569. );
  570. } else {
  571. cloneColumns.value.splice(index, 0, column);
  572. }
  573. };
  574. const exchangeArray = <T extends Array<any>>(
  575. array: T,
  576. beforeIdx: number,
  577. newIdx: number,
  578. isDeep = false
  579. ): T => {
  580. const newArray = isDeep ? cloneDeep(array) : array;
  581. if (beforeIdx > -1 && newIdx > -1) {
  582. newArray.splice(
  583. beforeIdx,
  584. 1,
  585. newArray.splice(newIdx, 1, newArray[beforeIdx]).pop()
  586. );
  587. }
  588. return newArray;
  589. };
  590. const popupVisibleChange = (val: boolean) => {
  591. if (val) {
  592. nextTick(() => {
  593. const el = document.getElementById('tableSetting') as HTMLElement;
  594. const sortable = new Sortable(el, {
  595. onEnd(e: any) {
  596. const { oldIndex, newIndex } = e;
  597. exchangeArray(cloneColumns.value, oldIndex, newIndex);
  598. exchangeArray(showColumns.value, oldIndex, newIndex);
  599. },
  600. });
  601. });
  602. }
  603. };
  604. const handleSubmit = async () => {
  605. try {
  606. let response;
  607. if (isEdit.value) {
  608. response = await updateOverrideRule(editForm.value as OverrideRuleRecord);
  609. } else {
  610. response = await createOverrideRule(editForm.value);
  611. }
  612. if (response.data.success) {
  613. Message.success(response.data.msg || '操作成功');
  614. visible.value = false;
  615. fetchData({
  616. ...basePagination,
  617. ...formModel.value,
  618. } as unknown as OverrideRuleParams);
  619. } else {
  620. Message.error(response.data.msg || '操作失败');
  621. }
  622. } catch (error) {
  623. Message.error('请求失败');
  624. }
  625. };
  626. const handleCancel = () => {
  627. visible.value = false;
  628. };
  629. watch(
  630. () => columns.value,
  631. (val) => {
  632. cloneColumns.value = cloneDeep(val);
  633. cloneColumns.value.forEach((item, index) => {
  634. item.checked = true;
  635. });
  636. showColumns.value = cloneDeep(cloneColumns.value);
  637. },
  638. { deep: true, immediate: true }
  639. );
  640. </script>
  641. <script lang="ts">
  642. export default {
  643. name: 'OverrideRules',
  644. };
  645. </script>
  646. <style scoped lang="less">
  647. .container {
  648. padding: 0 20px 20px 20px;
  649. }
  650. :deep(.arco-table-th) {
  651. &:last-child {
  652. .arco-table-th-item-title {
  653. margin-left: 16px;
  654. }
  655. }
  656. }
  657. .action-icon {
  658. margin-left: 12px;
  659. cursor: pointer;
  660. }
  661. .active {
  662. color: #0960bd;
  663. background-color: #e3f4fc;
  664. }
  665. .setting {
  666. display: flex;
  667. align-items: center;
  668. width: 200px;
  669. .title {
  670. margin-left: 12px;
  671. cursor: pointer;
  672. }
  673. }
  674. </style>