You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
20 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <el-card class="card1" style="margin-bottom: 1vh;">
  3. <div class="condition">
  4. <div class="condition-item1">
  5. <el-text size="large">{{ t('common.activityName') }}</el-text>
  6. <el-input v-model="searchForm.activityName" style="width: 10vw" :placeholder="t('common.activityNamePlaceholder')" clearable />
  7. </div>
  8. <div class="condition-item1">
  9. <el-text size="large">{{ t('common.type') }}</el-text>
  10. <!-- <el-cascader v-model="searchForm.businessBelong" :options="marketOptions" placeholder="请选择所属地区" clearable
  11. style="width: 10vw" /> -->
  12. <el-select v-model="searchForm.businessBelong" :placeholder="t('common.typePlaceholder')" style="width: 10vw" clearable>
  13. <el-option :label="t('common.customerBelong')" value="客户归属地" />
  14. <el-option :label="t('common.activityBelong')" value="活动归属地" />
  15. </el-select>
  16. </div>
  17. <div class="condition-item2">
  18. <el-text size="large">{{ t('common.startTime') }}</el-text>
  19. <el-date-picker v-model="searchForm.startTime" type="datetime" :placeholder="t('common.startTime')"
  20. format="YYYY-MM-DD HH:mm:ss" :default-time="defaultStartTime" clearable />
  21. </div>
  22. <div class="condition-item2">
  23. <el-text size="large">{{ t('common.endTime') }}</el-text>
  24. <el-date-picker v-model="searchForm.endTime" type="datetime" :placeholder="t('common.endTime')"
  25. format="YYYY-MM-DD HH:mm:ss" :default-time="defaultEndTime" clearable />
  26. </div>
  27. <el-button type="primary" @click="getActivity">{{ t('common.search') }}</el-button>
  28. <el-button type="success" @click="reset">{{ t('common.reset') }}</el-button>
  29. </div>
  30. </el-card>
  31. <el-card class="card2">
  32. <div class="add-item">
  33. <el-button type="success" @click="showAdd = true">{{ t('common.addActivity') }}</el-button>
  34. </div>
  35. <div>
  36. <el-table :data="tableData" style="width: 82vw;height:70vh;" :row-style="{ height: '50px' }">
  37. <el-table-column type="index" :label="t('common_list.id')" width="100px" fixed="left">
  38. <template #default="scope">
  39. <span>{{ scope.$index + 1 + (pagination.pageNum - 1) * pagination.pageSize }}</span>
  40. </template>
  41. </el-table-column>
  42. <el-table-column prop="activityName" :label="t('common_list.activity')" width="150px" show-overflow-tooltip />
  43. <el-table-column prop="businessBelong" :label="t('common_list.businessBelong')" width="150px" />
  44. <el-table-column prop="areaName" :label="t('common_list.market')" width="150px" />
  45. <el-table-column prop="startTime" :label="t('common_list.startTime')" width="200px">
  46. <template #default="scope">
  47. {{ moment(scope.row.startTime).format('YYYY-MM-DD HH:mm:ss') }}
  48. </template>
  49. </el-table-column>
  50. <el-table-column prop="endTime" :label="t('common_list.endTime')" width="200px">
  51. <template #default="scope">
  52. {{
  53. moment(scope.row.endTime).format('YYYY-MM-DD HH:mm:ss')
  54. }}
  55. </template>
  56. </el-table-column>
  57. <el-table-column prop="status" :label="t('common_list.status')" width="150px">
  58. <template #default="scope">
  59. {{ getActivityStatusText(scope.row.status) }}
  60. </template>
  61. </el-table-column>
  62. <el-table-column prop="creatorName" :label="t('common_list.creator')" width="150px" />
  63. <el-table-column prop="operation" :label="t('common_list.operation')" width="220px">
  64. <template #default="scope">
  65. <el-button type="primary" text @click="editOpen(scope.row)">{{ t('common.edit') }}</el-button>
  66. <!-- <el-button type="danger" text @click="openDel(scope.row)">删除</el-button> -->
  67. </template>
  68. </el-table-column>
  69. </el-table>
  70. </div>
  71. <div style="margin-top: 20px;display: flex;">
  72. <el-pagination background v-model:current-page="pagination.pageNum" v-model:page-size="pagination.pageSize"
  73. layout="total, sizes, prev, pager, next, jumper" :total="pagination.total" style="margin-top: 1vh;"
  74. @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  75. </div>
  76. </el-card>
  77. <el-dialog v-model="showAdd" width="20vw" draggable align-center style="background-color: rgb(243,250,254);">
  78. <div class="add-item">
  79. <el-text size="large">{{ t('common_add.activity') }}</el-text>
  80. <el-input v-model="addForm.activityName" style="width: 12vw" :placeholder="t('common_add.activityPlaceholder')" maxlength="200" clearable />
  81. </div>
  82. <div class="add-item">
  83. <el-text size="large">{{ t('common_add.businessBelong') }}</el-text>
  84. <el-radio-group v-model="addForm.businessBelong" style="width: 12vw">
  85. <el-radio size="large" value="客户归属地">{{ t('common.customerBelong') }}</el-radio>
  86. <el-radio size="large" value="活动归属地">{{ t('common.activityBelong') }}</el-radio>
  87. </el-radio-group>
  88. </div>
  89. <div class="add-item" v-show="addForm.businessBelong === '活动归属地'">
  90. <el-text size="large">{{ t('common_add.market') }}</el-text>
  91. <el-cascader v-model="addForm.area" :options="marketOptions" :placeholder="t('common_add.marketPlaceholder')" clearable
  92. style="width: 12vw" />
  93. </div>
  94. <div class="add-item">
  95. <el-text size="large">{{ t('common_add.startTime') }}</el-text>
  96. <el-date-picker v-model="addForm.startTime" type="datetime" :placeholder="t('common_add.startTime')"
  97. :default-time="defaultStartTime" style="width: 12vw" />
  98. </div>
  99. <div class="add-item">
  100. <el-text size="large">{{ t('common_add.endTime') }}</el-text>
  101. <el-date-picker v-model="addForm.endTime" type="datetime" :placeholder="t('common_add.endTime')"
  102. :default-time="defaultEndTime" style="width: 12vw" />
  103. </div>
  104. <div style="display: flex; justify-content: center; margin-top: 5vh;">
  105. <el-button type="primary" @click="hideAdd">{{ t('common.cancel') }}</el-button>
  106. <el-button type="primary" @click="throttleGetActivity">{{ t('common.confirm') }}</el-button>
  107. </div>
  108. </el-dialog>
  109. <el-dialog v-model="showEdit" width="20vw" draggable align-center style="background-color: rgb(243,250,254);">
  110. <div class="edit-item">
  111. <el-text size="large">{{ t('common_add.activity') }}</el-text>
  112. <el-input v-model="editForm.activityName" style="width: 12vw" :placeholder="t('common_add.activityPlaceholder')" maxlength="200" clearable />
  113. </div>
  114. <div class="edit-item">
  115. <el-text size="large">{{ t('common_add.businessBelong') }}</el-text>
  116. <el-radio-group v-model="editForm.businessBelong" style="width: 12vw">
  117. <el-radio size="large" value="客户归属地">{{ t('common.customerBelong') }}</el-radio>
  118. <el-radio size="large" value="活动归属地">{{ t('common.activityBelong') }}</el-radio>
  119. </el-radio-group>
  120. </div>
  121. <div class="edit-item" v-show="editForm.businessBelong === '活动归属地'">
  122. <el-text size="large">{{ t('common_add.market') }}</el-text>
  123. <el-cascader v-model="editForm.area" :options="marketOptions" :placeholder="t('common_add.marketPlaceholder')" clearable
  124. style="width: 12vw" />
  125. </div>
  126. <div class="edit-item">
  127. <el-text size="large">{{ t('common_add.startTime') }}</el-text>
  128. <el-date-picker v-model="editForm.startTime" type="datetime" :placeholder="t('common_add.startTime')"
  129. :default-time="defaultStartTime" style="width: 12vw" />
  130. </div>
  131. <div class="edit-item">
  132. <el-text size="large">{{ t('common_add.endTime') }}</el-text>
  133. <el-date-picker v-model="editForm.endTime" type="datetime" :placeholder="t('common_add.endTime')"
  134. :default-time="defaultEndTime" style="width: 12vw" />
  135. </div>
  136. <div style="display: flex; justify-content: center; margin-top: 5vh;">
  137. <el-button type="primary" @click="hideEdit">{{ t('common.cancel') }}</el-button>
  138. <el-button type="primary" @click="handleEdit">{{ t('common.confirm') }}</el-button>
  139. </div>
  140. </el-dialog>
  141. <ConfirmDialog v-model="showDel" :message="t('common.deleteActivityRecord')" @confirm="handleDel()" @cancel="hideDel" @close="hideDel" />
  142. </template>
  143. <script setup>
  144. import { ElMessage, ElPagination } from 'element-plus';
  145. import { onMounted, ref } from 'vue'
  146. import API from "@/util/http.js"
  147. import moment from 'moment'
  148. import { useAdminStore } from "@/store/index.js"
  149. import { storeToRefs } from "pinia"
  150. import { permissionMapping, hasMenuPermission } from "@/utils/menuTreePermission.js"
  151. const adminStore = useAdminStore();
  152. const { adminData, menuTree } = storeToRefs(adminStore)
  153. import ConfirmDialog from '@/components/dialogs/ConfirmDialog.vue'
  154. import _ from 'lodash'
  155. // 国际化
  156. import { useI18n } from 'vue-i18n';
  157. const {t} = useI18n();
  158. const getActivityStatusText = (status) => {
  159. if (status === '0') return t('common_list.activityStatus.notStarted')
  160. if (status === '1') return t('common_list.activityStatus.inProgress')
  161. if (status === '2') return t('common_list.activityStatus.ended')
  162. return status
  163. }
  164. // 活动名称正则表达式
  165. const activityNameReg = /^[\\u4e00-\\u9fa5a-zA-Z0-9,。!?、;:“”()‘’《》【】{}——~,.!?:;'--()\"\"\\[\\]_&+=]+$/;
  166. // 为什么一定要两个--才能成功?????????
  167. const tableData = ref([])
  168. const pagination = ref({
  169. pageNum: 1,
  170. pageSize: 10,
  171. total: 0
  172. })
  173. const searchForm = ref({
  174. activityName: ''
  175. })
  176. const showAdd = ref(false)
  177. const showEdit = ref(false)
  178. const showDel = ref(false)
  179. const currentDelRow = ref(null)
  180. const addForm = ref({
  181. activityName: '',
  182. area: []
  183. })
  184. const editForm = ref({
  185. activityName: '',
  186. businessBelong: '',
  187. area: [],
  188. startTime: null,
  189. endTime: null,
  190. id: ''
  191. })
  192. const marketOptions = ref([])
  193. const getActivity = async function () {
  194. if (!hasMenuPermission(menuTree.value,permissionMapping.view_activity)) {
  195. ElMessage.error(t('elmessage.noPermission'))
  196. return
  197. }
  198. const rechargeActivity = {
  199. activityName: searchForm.value.activityName,
  200. businessBelong: searchForm.value.businessBelong,
  201. }
  202. if (searchForm.value.startTime && moment(searchForm.value.startTime).isValid()) {
  203. rechargeActivity.startTime = moment(searchForm.value.startTime).format('YYYY-MM-DD HH:mm:ss');
  204. }
  205. if (searchForm.value.endTime && moment(searchForm.value.endTime).isValid()) {
  206. rechargeActivity.endTime = moment(searchForm.value.endTime).format('YYYY-MM-DD HH:mm:ss');
  207. }
  208. const params = {
  209. pageNum: pagination.value.pageNum,
  210. pageSize: pagination.value.pageSize,
  211. rechargeActivity
  212. }
  213. const res = await API({
  214. url: '/admin/coin/rechargeActivityCenter/queryActivity',
  215. data: params
  216. })
  217. if (res.code === 200) {
  218. tableData.value = res.data.list
  219. pagination.value.total = res.data.total
  220. }
  221. }
  222. const handleAdd = async function () {
  223. if (!hasMenuPermission(menuTree.value,permissionMapping.add_activity)) {
  224. ElMessage.error(t('elmessage.noPermission'))
  225. return
  226. }
  227. const activityName = addForm.value.activityName
  228. if (!validateActivityName(activityName)) return
  229. if (!addForm.value.businessBelong) {
  230. ElMessage.error(t('elmessage.selectBusinessBelong'))
  231. return
  232. }
  233. if (addForm.value.businessBelong === '活动归属地' && addForm.value.area.length === 0) {
  234. ElMessage.error(t('elmessage.selectMarket'))
  235. return
  236. }
  237. if (!addForm.value.startTime) {
  238. ElMessage.error(t('elmessage.selectStartTime'))
  239. return
  240. }
  241. if (!addForm.value.endTime) {
  242. ElMessage.error(t('elmessage.selectEndTime'))
  243. return
  244. }
  245. if (addForm.value.businessBelong === '客户归属地') {
  246. addForm.value.area = []
  247. }
  248. const params = {
  249. activityName: addForm.value.activityName,
  250. businessBelong: addForm.value.businessBelong,
  251. area: addForm.value.area.length > 0 ? addForm.value.area.slice(-1)[0] : null,
  252. startTime: moment(addForm.value.startTime).format('YYYY-MM-DD HH:mm:ss'),
  253. endTime: moment(addForm.value.endTime).format('YYYY-MM-DD HH:mm:ss'),
  254. creator: adminData.value.id
  255. }
  256. const res = await API({
  257. url: '/admin/coin/rechargeActivityCenter/addActivity',
  258. data: params
  259. })
  260. if (res.code === 200) {
  261. ElMessage.success(t('elmessage.addSuccess'))
  262. getActivity()
  263. hideAdd()
  264. addForm.value = {
  265. activityName: '',
  266. businessBelong: '',
  267. area: [],
  268. startTime: null,
  269. endTime: null,
  270. }
  271. } else {
  272. ElMessage.error(res.msg || t('elmessage.addFailed'))
  273. return
  274. }
  275. }
  276. // 新增节流
  277. const throttleGetActivity = _.throttle(handleAdd, 5000, { trailing: false });
  278. const handleEdit = async function () {
  279. if (!hasMenuPermission(menuTree.value,permissionMapping.edit_activity)) {
  280. ElMessage.error(t('elmessage.noPermission'))
  281. return
  282. }
  283. const activityName = editForm.value.activityName
  284. if (!validateActivityName(activityName)) return
  285. if (!editForm.value.activityName) {
  286. ElMessage.error(t('elmessage.checkActivity'))
  287. return
  288. }
  289. if (!editForm.value.businessBelong) {
  290. ElMessage.error(t('elmessage.selectBusinessBelong'))
  291. return
  292. }
  293. if (editForm.value.businessBelong === '活动归属地' && editForm.value.area.length === 0) {
  294. ElMessage.error(t('elmessage.selectMarket'))
  295. return
  296. }
  297. if (!editForm.value.startTime) {
  298. ElMessage.error(t('elmessage.selectStartTime'))
  299. return
  300. }
  301. if (!editForm.value.endTime) {
  302. ElMessage.error(t('elmessage.selectEndTime'))
  303. return
  304. }
  305. if(editForm.value.businessBelong === '客户归属地'){
  306. editForm.value.area = []
  307. }
  308. const params = {
  309. id: editForm.value.id,
  310. activityName: editForm.value.activityName,
  311. businessBelong: editForm.value.businessBelong,
  312. area: editForm.value.area.length > 0 ? editForm.value.area.slice(-1)[0] : null,
  313. startTime: moment(editForm.value.startTime).format('YYYY-MM-DD HH:mm:ss'),
  314. endTime: moment(editForm.value.endTime).format('YYYY-MM-DD HH:mm:ss'),
  315. creator: adminData.value.id
  316. }
  317. console.log('看看修改params', params)
  318. const res = await API({
  319. url: '/admin/coin/rechargeActivityCenter/updateActivity',
  320. data: params
  321. })
  322. if (res.code === 200) {
  323. ElMessage.success(t('elmessage.editSuccess'))
  324. getActivity()
  325. hideEdit()
  326. }
  327. }
  328. const handleDel = async function (row) {
  329. if (!hasMenuPermission(menuTree.value,permissionMapping.delete_activity)) {
  330. ElMessage.error(t('elmessage.noPermission'))
  331. return
  332. }
  333. if (!currentDelRow.value) {
  334. ElMessage.error(t('elmessage.currentSelectionEmpty'))
  335. return
  336. }
  337. const res = await API({
  338. url: '/admin/coin/rechargeActivityCenter/deleteActivity',
  339. data: {
  340. id: currentDelRow.value.id,
  341. }
  342. })
  343. if (res.code === 200) {
  344. ElMessage.success(t('elmessage.deleteSuccess'))
  345. getActivity()
  346. showDel.value = false
  347. }
  348. }
  349. const getmarkets = async function () {
  350. try {
  351. const result = await API({
  352. url: '/market/selectMarket',
  353. });
  354. console.log('请求成功', result)
  355. // 递归转换树形结构为级联选择器需要的格式(跳过第一级节点)
  356. const transformTree = (nodes) => {
  357. // 直接处理第一级节点的子节点
  358. const allChildren = nodes.flatMap(node => node.children || []);
  359. return allChildren.map(child => {
  360. const grandchildren = child.children && child.children.length
  361. ? transformTree([child]) // 递归处理子节点
  362. : null;
  363. return {
  364. value: child.id,
  365. label: child.name,
  366. children: grandchildren
  367. }
  368. })
  369. }
  370. marketOptions.value = transformTree(result.data)
  371. console.log('转换后的地区树', marketOptions.value)
  372. } catch (error) {
  373. console.log('请求失败', error)
  374. }
  375. }
  376. const defaultStartTime = [
  377. new Date(2000, 1, 1, 0, 0, 0)
  378. ]
  379. const defaultEndTime = [
  380. new Date(2000, 2, 1, 23, 59, 59)
  381. ]
  382. const hideEdit = () => {
  383. showEdit.value = false
  384. editForm.value = {
  385. activityName: '',
  386. businessBelong: '',
  387. area: [],
  388. startTime: null,
  389. endTime: null,
  390. id: ''
  391. }
  392. }
  393. const editOpen = (row) => {
  394. editForm.value = {
  395. id: row.id,
  396. activityName: row.activityName,
  397. businessBelong: row.businessBelong,
  398. area: row.area ? [...row.area] : []
  399. }
  400. if (row.startTime) {
  401. editForm.value.startTime = moment(row.startTime).toDate()
  402. }
  403. if (row.endTime) {
  404. editForm.value.endTime = moment(row.endTime).toDate()
  405. }
  406. console.log('看看editForm', editForm.value)
  407. showEdit.value = true
  408. }
  409. const openDel = (row) => {
  410. currentDelRow.value = row
  411. showDel.value = true
  412. }
  413. const hideDel = () => {
  414. showDel.value = false
  415. currentDelRow.value = null
  416. }
  417. const reset = () => {
  418. searchForm.value = {
  419. activityName: '',
  420. businessBelong: '',
  421. startTime: null,
  422. endTime: null
  423. }
  424. getActivity()
  425. }
  426. const hideAdd = () => {
  427. showAdd.value = false
  428. addForm.value = {
  429. activityName: '',
  430. area: [],
  431. startTime: null,
  432. endTime: null
  433. }
  434. }
  435. const validateActivityName = (name) => {
  436. // 非空校验
  437. if (!name.trim()) {
  438. ElMessage.error('活动名称不能为空');
  439. return false;
  440. }
  441. // 长度校验(限制100字符)
  442. if (name.length > 100) {
  443. ElMessage.error('活动名称长度不能超过100字符');
  444. return false;
  445. }
  446. // 字符格式校验
  447. if (!activityNameReg.test(name)) {
  448. ElMessage.error('活动名称仅支持汉字、英文字母、数字及常见标点,中文字符,。!?、;:“ ” ‘ ’ ()《》【】——~,英文字符, . ! ? : ; " ( ) [ ] - _ & + =/')
  449. return false;
  450. }
  451. return true;
  452. };
  453. const handleSizeChange = function (val) {
  454. pagination.pageSize = val
  455. getActivity()
  456. }
  457. const handleCurrentChange = function (val) {
  458. pagination.pageNum = val
  459. getActivity()
  460. }
  461. onMounted(() => {
  462. getActivity()
  463. getmarkets()
  464. console.log('看看adminData', adminData.value)
  465. })
  466. </script>
  467. <style scoped lang="scss">
  468. // 搜索的卡片样式
  469. .card1 {
  470. background: #F3FAFE;
  471. }
  472. // 表单的卡片样式
  473. .card2 {
  474. background: #E7F4FD;
  475. }
  476. // 表头背景等
  477. :deep(.el-table__header-wrapper),
  478. :deep(.el-table__body-wrapper),
  479. :deep(.el-table__cell),
  480. /* 表格 */
  481. :deep(.el-table__body td) {
  482. background-color: #F3FAFE !important;
  483. }
  484. /* 表头 */
  485. :deep(.el-table__header th) {
  486. background-color: #F3FAFE !important;
  487. }
  488. /* 鼠标悬停 */
  489. :deep(.el-table__row:hover > .el-table__cell) {
  490. background-color: #E5EBFE !important;
  491. }
  492. .condition {
  493. display: flex;
  494. align-items: center;
  495. }
  496. .condition-item1 {
  497. display: flex;
  498. align-items: center;
  499. width: 15vw;
  500. }
  501. .condition-item2 {
  502. display: flex;
  503. align-items: center;
  504. width: 17vw;
  505. }
  506. .add-item {
  507. display: flex;
  508. align-items: center;
  509. width: 17vw;
  510. margin-bottom: 1vh;
  511. }
  512. .edit-item {
  513. display: flex;
  514. align-items: center;
  515. width: 17vw;
  516. margin-bottom: 1vh;
  517. }
  518. </style>