createKeyword.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <script lang="ts" setup>/**
  2. * @Name: createKeyword.vue
  3. * @Description: 谷歌关键词添加
  4. * @Author: xinyan
  5. */
  6. import { deleteKeyword, getKeyword, postCreateKeyword, UpdateKeyword } from '/@/views/googleTrends/api';
  7. import { Delete, Plus } from '@element-plus/icons-vue';
  8. import { ref } from 'vue';
  9. import { VxeGridInstance } from 'vxe-table';
  10. const emit = defineEmits([ 'updateKeyword' ]);
  11. const keywordInput = ref('');
  12. const btnLoading = ref(false);
  13. const isCancel = ref(false); // 是否取消编辑
  14. const isEditing = ref(false); // 是否正在编辑
  15. const keywordList = ref([]); // 关键词列表
  16. const xGrid = ref<VxeGridInstance>();
  17. const gridOptions = reactive({
  18. border: 'inner',
  19. keepSource: true,
  20. loading: false,
  21. height: 555,
  22. columnConfig: {
  23. resizable: true
  24. },
  25. rowConfig: {
  26. isHover: true,
  27. height: 38,
  28. },
  29. editConfig: {
  30. trigger: 'manual',
  31. showIcon: false,
  32. // autoFocus: true,
  33. autoClear: false,
  34. },
  35. pagerConfig: {
  36. enabled: true,
  37. total: 20,
  38. currentPage: 1,
  39. pageSize: 20,
  40. pageSizes: [10, 20, 30],
  41. },
  42. columns: [
  43. { field: 'keyword', title: '关键词', editRender: { name: 'input' }, slots: { edit: 'keyword_edit' } },
  44. { field: 'insert_time', title: '添加时间' },
  45. { title: '操作', width: 120, slots: { default: 'operate' }, align: 'center' },
  46. ],
  47. data: []
  48. });
  49. const gridEvents = {
  50. pageChange({ currentPage, pageSize }) {
  51. if (gridOptions.pagerConfig) {
  52. gridOptions.pagerConfig.currentPage = currentPage;
  53. gridOptions.pagerConfig.pageSize = pageSize;
  54. }
  55. getList();
  56. }
  57. };
  58. const hasActiveEditRow = (row) => {
  59. const $grid = xGrid.value;
  60. if ($grid) {
  61. return $grid.isEditByRow(row);
  62. }
  63. return false;
  64. };
  65. function editRowEvent(row) {
  66. isCancel.value = false;
  67. isEditing.value = true;
  68. const $grid = xGrid.value;
  69. if ($grid) {
  70. $grid.setEditRow(row);
  71. row.original_keyword = row.keyword;
  72. }
  73. }
  74. function handelEditClosed({ row }) {
  75. isEditing.value = false;
  76. if (isCancel.value)return;
  77. if (row.original_keyword !== row.keyword){
  78. updateKeyword(row);
  79. }
  80. }
  81. async function getList() {
  82. gridOptions.loading = true;
  83. try {
  84. const resp = await getKeyword({
  85. page: gridOptions.pagerConfig.currentPage,
  86. limit: gridOptions.pagerConfig.pageSize,
  87. });
  88. gridOptions.data = resp.data;
  89. keywordList.value = resp.data.map(item => item.keyword);
  90. emit('updateKeyword',keywordList.value);
  91. gridOptions.pagerConfig.total = resp.total;
  92. gridOptions.loading = false;
  93. } catch (error) {
  94. console.log(error);
  95. }
  96. }
  97. async function handleAdd() {
  98. const keywordsInput = keywordInput.value.trim();
  99. if (!keywordsInput) {
  100. return;
  101. }
  102. // 按行拆分关键词,并过滤掉空行
  103. const keywords = keywordsInput.split('\n').map(keyword => keyword.trim()).filter(keyword => keyword.length > 0);
  104. try {
  105. btnLoading.value = true;
  106. const resp = await postCreateKeyword({ keyword: keywords.join(',') });
  107. if (resp.code === 2000) {
  108. ElMessage.success('关键词添加成功');
  109. keywordInput.value = ''; // 清空输入框
  110. await getList();
  111. }
  112. } catch (error) {
  113. ElMessage.error('添加关键词失败,请重试!'); // 提示失败消息
  114. } finally {
  115. btnLoading.value = false;
  116. }
  117. }
  118. async function updateKeyword(row) {
  119. try {
  120. gridOptions.loading = true;
  121. const resp = await UpdateKeyword({
  122. id: row.id,
  123. new_keyword: row.keyword
  124. });
  125. if (resp.code === 2000) {
  126. await getList();
  127. ElMessage.success('关键词更新成功');
  128. }
  129. } catch (error) {
  130. ElMessage.error('更新关键词失败,请重试!'); // 提示失败消息
  131. } finally {
  132. gridOptions.loading = false;
  133. }
  134. }
  135. function cancel(row) {
  136. isCancel.value = true;
  137. const $grid = xGrid.value;
  138. if ($grid) {
  139. $grid.clearEdit().then(() => {
  140. $grid.revertData(row)
  141. })
  142. }
  143. }
  144. async function handleDelete(row) {
  145. try {
  146. gridOptions.loading = true;
  147. const resp = await deleteKeyword({ keyword: row.keyword });
  148. if (resp.code === 2000) {
  149. ElMessage.success('关键词删除成功');
  150. await getList();
  151. }
  152. } catch (error) {
  153. ElMessage.error('删除关键词失败,请重试!'); // 提示失败消息
  154. } finally {
  155. gridOptions.loading = false;
  156. }
  157. }
  158. function handleClear() {
  159. keywordInput.value = '';
  160. }
  161. const cellStyle = () => {
  162. return {
  163. fontSize: '13px',
  164. fontWeight: '500',
  165. };
  166. };
  167. const headerCellStyle = () => {
  168. return {
  169. fontSize: '13px',
  170. backgroundColor: '#f0f1f3',
  171. height: 10,
  172. };
  173. };
  174. onMounted(() => {
  175. getList();
  176. });
  177. </script>
  178. <template>
  179. <el-card class="mx-2 my-5">
  180. <div class="my-4 mx-1.5" style="font-size: 18px;font-weight: bold;color: #464646">谷歌关键词添加</div>
  181. <div class="mx-2">
  182. <el-row :gutter="20">
  183. <el-col :span="8" class="input-section">
  184. <div class="input-container">
  185. <el-input
  186. v-model="keywordInput"
  187. :rows="26"
  188. class="textarea"
  189. placeholder="添加谷歌趋势关键词,一行一个关键词......(关键词添加上限200个)"
  190. type="textarea"
  191. />
  192. <!-- 按钮组 -->
  193. <div class="button-group">
  194. <el-popconfirm
  195. :icon="InfoFilled"
  196. icon-color="#626AEF"
  197. title="确认清空关键词?"
  198. width="220"
  199. @confirm="handleClear"
  200. >
  201. <template #reference>
  202. <el-button :icon="Delete" bg size="small" text type="danger">清空</el-button>
  203. </template>
  204. </el-popconfirm>
  205. <el-button :icon="Plus" :loading="btnLoading" bg size="small" text type="primary" @click="handleAdd">添加
  206. </el-button>
  207. </div>
  208. </div>
  209. </el-col>
  210. <el-col :span="16">
  211. <el-card body-style="padding: 0" shadow="never">
  212. <vxe-grid ref="xGrid" :cell-style="cellStyle" :header-cell-style="headerCellStyle" show-overflow
  213. v-bind="gridOptions" v-on="gridEvents" @edit-closed="handelEditClosed">
  214. <template #operate="{ row }">
  215. <template v-if="hasActiveEditRow(row)">
  216. <el-button link size="small" @click="cancel(row)">取消</el-button>
  217. <el-button link size="small" type="warning" @click="updateKeyword(row)">保存</el-button>
  218. </template>
  219. <template v-else>
  220. <el-button link size="small" type="primary" @click="editRowEvent(row)">编辑</el-button>
  221. <el-popconfirm
  222. :icon="InfoFilled"
  223. icon-color="#626AEF"
  224. title="确认删除关键词?"
  225. width="220"
  226. @confirm="handleDelete(row)"
  227. >
  228. <template #reference>
  229. <el-button :disabled="isEditing" link size="small" type="danger">删除</el-button>
  230. </template>
  231. </el-popconfirm>
  232. </template>
  233. </template>
  234. <template #keyword_edit="{ row }">
  235. <el-input v-model="row.keyword"/>
  236. </template>
  237. </vxe-grid>
  238. </el-card>
  239. </el-col>
  240. </el-row>
  241. </div>
  242. </el-card>
  243. </template>
  244. <style scoped>
  245. .input-container {
  246. position: relative;
  247. }
  248. .textarea {
  249. width: 100%;
  250. padding-bottom: 40px; /* 为按钮腾出空间 */
  251. }
  252. .button-group {
  253. position: absolute;
  254. bottom: 50px;
  255. right: 10px;
  256. /* display: flex; */
  257. /* gap: 5px; */
  258. }
  259. </style>