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.

557 lines
20 KiB

4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
4 months ago
4 months ago
3 months ago
3 months ago
4 months ago
3 months ago
3 months ago
4 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
4 months ago
4 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
4 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
  1. <template>
  2. <el-card class="card1" style="margin-bottom: 1vh;">
  3. <div class="condition">
  4. <div class="condition-item">
  5. <el-text size="large" class="label-text">{{ t('common.activityName') }}</el-text>
  6. <el-input v-model="searchForm.activityName" class="input-width" :placeholder="t('common.activityNamePlaceholder')" clearable />
  7. </div>
  8. <div class="condition-item">
  9. <el-text size="large" class="label-text">{{ 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')" class="input-width" 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-item">
  18. <el-text size="large" class="label-text">{{ 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" class="input-width" clearable />
  21. </div>
  22. <div class="condition-item">
  23. <el-text size="large" class="label-text">{{ 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" class="input-width" clearable />
  26. </div>
  27. <div class="condition-buttons">
  28. <el-button type="primary" @click="getActivity">{{ t('common.search') }}</el-button>
  29. <el-button type="success" @click="reset">{{ t('common.reset') }}</el-button>
  30. </div>
  31. </div>
  32. </el-card>
  33. <el-card class="card2">
  34. <div class="add-item">
  35. <el-button type="success" @click="showAdd = true">{{ t('common.addActivity') }}</el-button>
  36. </div>
  37. <div>
  38. <el-table :data="tableData" style="width: 82vw;height:70vh;" :row-style="{ height: '50px' }">
  39. <el-table-column type="index" :label="t('common_list.id')" width="100px" fixed="left">
  40. <template #default="scope">
  41. <span>{{ scope.$index + 1 + (pagination.pageNum - 1) * pagination.pageSize }}</span>
  42. </template>
  43. </el-table-column>
  44. <el-table-column prop="activityName" :label="t('common_list.activity')" width="150px" show-overflow-tooltip />
  45. <el-table-column prop="businessBelong" :label="t('common_list.businessBelong')" width="150px" />
  46. <el-table-column prop="areaName" :label="t('common_list.market')" width="150px" />
  47. <el-table-column prop="startTime" :label="t('common_list.startTime')" width="200px">
  48. <template #default="scope">
  49. {{ moment(scope.row.startTime).format('YYYY-MM-DD HH:mm:ss') }}
  50. </template>
  51. </el-table-column>
  52. <el-table-column prop="endTime" :label="t('common_list.endTime')" width="200px">
  53. <template #default="scope">
  54. {{
  55. moment(scope.row.endTime).format('YYYY-MM-DD HH:mm:ss')
  56. }}
  57. </template>
  58. </el-table-column>
  59. <el-table-column prop="status" :label="t('common_list.status')" width="150px">
  60. <template #default="scope">
  61. {{ getActivityStatusText(scope.row.status) }}
  62. </template>
  63. </el-table-column>
  64. <el-table-column prop="creatorName" :label="t('common_list.creator')" width="150px" />
  65. <el-table-column prop="operation" :label="t('common_list.operation')" width="220px">
  66. <template #default="scope">
  67. <el-button type="primary" text @click="editOpen(scope.row)">{{ t('common.edit') }}</el-button>
  68. <!-- <el-button type="danger" text @click="openDel(scope.row)">删除</el-button> -->
  69. </template>
  70. </el-table-column>
  71. </el-table>
  72. </div>
  73. <div style="margin-top: 20px;display: flex;">
  74. <el-pagination background v-model:current-page="pagination.pageNum" v-model:page-size="pagination.pageSize"
  75. layout="total, sizes, prev, pager, next, jumper" :total="pagination.total" style="margin-top: 1vh;"
  76. @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  77. </div>
  78. </el-card>
  79. <el-dialog v-model="showAdd" width="20vw" draggable align-center style="background-color: rgb(243,250,254);">
  80. <div class="add-item">
  81. <el-text size="large">{{ t('common_add.activity') }}</el-text>
  82. <el-input v-model="addForm.activityName" style="width: 12vw" :placeholder="t('common_add.activityPlaceholder')" maxlength="200" clearable />
  83. </div>
  84. <div class="add-item">
  85. <el-text size="large">{{ t('common_add.businessBelong') }}</el-text>
  86. <el-radio-group v-model="addForm.businessBelong" style="width: 12vw">
  87. <el-radio size="large" value="客户归属地">{{ t('common.customerBelong') }}</el-radio>
  88. <el-radio size="large" value="活动归属地">{{ t('common.activityBelong') }}</el-radio>
  89. </el-radio-group>
  90. </div>
  91. <div class="add-item" v-show="addForm.businessBelong === '活动归属地'">
  92. <el-text size="large">{{ t('common_add.market') }}</el-text>
  93. <el-cascader v-model="addForm.area" :options="marketOptions" :placeholder="t('common_add.marketPlaceholder')" clearable
  94. style="width: 12vw" />
  95. </div>
  96. <div class="add-item">
  97. <el-text size="large">{{ t('common_add.startTime') }}</el-text>
  98. <el-date-picker v-model="addForm.startTime" type="datetime" :placeholder="t('common_add.startTime')"
  99. :default-time="defaultStartTime" style="width: 12vw" />
  100. </div>
  101. <div class="add-item">
  102. <el-text size="large">{{ t('common_add.endTime') }}</el-text>
  103. <el-date-picker v-model="addForm.endTime" type="datetime" :placeholder="t('common_add.endTime')"
  104. :default-time="defaultEndTime" style="width: 12vw" />
  105. </div>
  106. <div style="display: flex; justify-content: center; margin-top: 5vh;">
  107. <el-button type="primary" @click="hideAdd">{{ t('common.cancel') }}</el-button>
  108. <el-button type="primary" @click="throttleGetActivity">{{ t('common.confirm') }}</el-button>
  109. </div>
  110. </el-dialog>
  111. <el-dialog v-model="showEdit" width="20vw" draggable align-center style="background-color: rgb(243,250,254);">
  112. <div class="edit-item">
  113. <el-text size="large">{{ t('common_add.activity') }}</el-text>
  114. <el-input v-model="editForm.activityName" style="width: 12vw" :placeholder="t('common_add.activityPlaceholder')" maxlength="200" clearable />
  115. </div>
  116. <div class="edit-item">
  117. <el-text size="large">{{ t('common_add.businessBelong') }}</el-text>
  118. <el-radio-group v-model="editForm.businessBelong" style="width: 12vw">
  119. <el-radio size="large" value="客户归属地">{{ t('common.customerBelong') }}</el-radio>
  120. <el-radio size="large" value="活动归属地">{{ t('common.activityBelong') }}</el-radio>
  121. </el-radio-group>
  122. </div>
  123. <div class="edit-item" v-show="editForm.businessBelong === '活动归属地'">
  124. <el-text size="large">{{ t('common_add.market') }}</el-text>
  125. <el-cascader v-model="editForm.area" :options="marketOptions" :placeholder="t('common_add.marketPlaceholder')" clearable
  126. style="width: 12vw" />
  127. </div>
  128. <div class="edit-item">
  129. <el-text size="large">{{ t('common_add.startTime') }}</el-text>
  130. <el-date-picker v-model="editForm.startTime" type="datetime" :placeholder="t('common_add.startTime')"
  131. :default-time="defaultStartTime" style="width: 12vw" />
  132. </div>
  133. <div class="edit-item">
  134. <el-text size="large">{{ t('common_add.endTime') }}</el-text>
  135. <el-date-picker v-model="editForm.endTime" type="datetime" :placeholder="t('common_add.endTime')"
  136. :default-time="defaultEndTime" style="width: 12vw" />
  137. </div>
  138. <div style="display: flex; justify-content: center; margin-top: 5vh;">
  139. <el-button type="primary" @click="hideEdit">{{ t('common.cancel') }}</el-button>
  140. <el-button type="primary" @click="handleEdit">{{ t('common.confirm') }}</el-button>
  141. </div>
  142. </el-dialog>
  143. <ConfirmDialog v-model="showDel" :message="t('common.deleteActivityRecord')" @confirm="handleDel()" @cancel="hideDel" @close="hideDel" />
  144. </template>
  145. <script setup>
  146. import { ElMessage, ElPagination } from 'element-plus';
  147. import { onMounted, ref } from 'vue'
  148. import API from "@/util/http.js"
  149. import moment from 'moment'
  150. import { useAdminStore } from "@/store/index.js"
  151. import { storeToRefs } from "pinia"
  152. import { permissionMapping, hasMenuPermission } from "@/utils/menuTreePermission.js"
  153. const adminStore = useAdminStore();
  154. const { adminData, menuTree } = storeToRefs(adminStore)
  155. import ConfirmDialog from '@/components/dialogs/ConfirmDialog.vue'
  156. import _ from 'lodash'
  157. // 国际化
  158. import { useI18n } from 'vue-i18n';
  159. const {t} = useI18n();
  160. const getActivityStatusText = (status) => {
  161. if (status === '0') return t('common_list.activityStatus.notStarted')
  162. if (status === '1') return t('common_list.activityStatus.inProgress')
  163. if (status === '2') return t('common_list.activityStatus.ended')
  164. return status
  165. }
  166. const activityNameReg = /^[\u4e00-\u9fa5a-zA-Z0-9,。!?、;:“”()‘’《》【】{}——~,.!?:;'()\[\]_&+=\/-]+$/;
  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. const value = name.trim()
  437. // 非空校验
  438. if (!value) {
  439. ElMessage.error('活动名称不能为空');
  440. return false;
  441. }
  442. // 长度校验(限制100字符)
  443. if (value.length > 100) {
  444. ElMessage.error('活动名称长度不能超过100字符');
  445. return false;
  446. }
  447. // 字符格式校验
  448. if (!activityNameReg.test(value)) {
  449. ElMessage.error('活动名称仅支持汉字、英文字母、数字及常见标点,中文字符,。!?、;:“ ” ‘ ’ ()《》【】——~,英文字符, . ! ? : ; " ( ) [ ] - _ & + =/')
  450. return false;
  451. }
  452. return true;
  453. };
  454. const handleSizeChange = function (val) {
  455. pagination.pageSize = val
  456. getActivity()
  457. }
  458. const handleCurrentChange = function (val) {
  459. pagination.pageNum = val
  460. getActivity()
  461. }
  462. onMounted(() => {
  463. getActivity()
  464. getmarkets()
  465. console.log('看看adminData', adminData.value)
  466. })
  467. </script>
  468. <style scoped lang="scss">
  469. // 搜索的卡片样式
  470. .card1 {
  471. background: #F3FAFE;
  472. }
  473. // 表单的卡片样式
  474. .card2 {
  475. background: #E7F4FD;
  476. }
  477. // 表头背景等
  478. :deep(.el-table__header-wrapper),
  479. :deep(.el-table__body-wrapper),
  480. :deep(.el-table__cell),
  481. /* 表格 */
  482. :deep(.el-table__body td) {
  483. background-color: #F3FAFE !important;
  484. }
  485. /* 表头 */
  486. :deep(.el-table__header th) {
  487. background-color: #F3FAFE !important;
  488. }
  489. /* 鼠标悬停 */
  490. :deep(.el-table__row:hover > .el-table__cell) {
  491. background-color: #E5EBFE !important;
  492. }
  493. .condition {
  494. display: flex;
  495. align-items: center;
  496. flex-wrap: wrap;
  497. gap: 10px;
  498. }
  499. .condition-item {
  500. display: flex;
  501. align-items: center;
  502. margin-right: 10px;
  503. }
  504. .label-text {
  505. white-space: nowrap;
  506. }
  507. .input-width {
  508. width: 160px;
  509. }
  510. .condition-buttons {
  511. display: flex;
  512. align-items: center;
  513. gap: 10px;
  514. margin-left: 10px;
  515. }
  516. .add-item {
  517. display: flex;
  518. align-items: center;
  519. margin-bottom: 1vh;
  520. /* 去掉固定宽度以适应英文文本 */
  521. }
  522. .edit-item {
  523. display: flex;
  524. align-items: center;
  525. margin-bottom: 1vh;
  526. /* 去掉固定宽度以适应英文文本 */
  527. }
  528. </style>