deepchart后台管理系统
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.

693 lines
17 KiB

  1. <template>
  2. <div class="page-container">
  3. <!-- 搜索区域 -->
  4. <div class="search-container">
  5. <div class="search-form">
  6. <div class="search-item">
  7. <span class="form-label">账号</span>
  8. <el-input
  9. v-model="searchForm.dccode"
  10. placeholder="请输入账号"
  11. clearable
  12. style="height: 36px; width: 140px;"
  13. />
  14. </div>
  15. <div class="search-item">
  16. <span class="form-label">姓名</span>
  17. <el-input
  18. v-model="searchForm.dcname"
  19. placeholder="请输入姓名"
  20. clearable
  21. style="height: 36px; width: 180px;"
  22. />
  23. </div>
  24. <div class="search-item">
  25. <span class="form-label">地区</span>
  26. <el-select
  27. v-model="searchForm.market"
  28. placeholder="请选择地区"
  29. clearable
  30. style="height: 36px; width: 160px;"
  31. :loading="isRegionLoading"
  32. >
  33. <el-option
  34. v-for="region in regionList"
  35. :key="region.id"
  36. :label="region.text_cn"
  37. :value="region.market"
  38. />
  39. </el-select>
  40. </div>
  41. <div class="search-item">
  42. <span class="form-label">注册方式</span>
  43. <el-input
  44. v-model="searchForm.register_type"
  45. placeholder="请输入手机号/邮箱"
  46. clearable
  47. style="height: 36px; width: 220px;"
  48. />
  49. </div>
  50. <div class="button-group">
  51. <el-button type="primary" @click="search">搜索</el-button>
  52. <el-button type="danger" @click="enableAccess">开通权限</el-button>
  53. <el-button type="success" @click="exportExcel">导出Excel列表</el-button>
  54. <el-button color="#626aef" @click="exportList">查看导出列表</el-button>
  55. <el-button type="primary" @click="resetBn">重置</el-button>
  56. </div>
  57. </div>
  58. </div>
  59. <!-- 数据 -->
  60. <el-table
  61. :data="tableData"
  62. style="width: 100%; margin-top: 20px;"
  63. header-cell-class-name="table-header"
  64. @sort-change="handleSortChange"
  65. :default-sort="{ prop: null, order: null }"
  66. class="table-rounded"
  67. :loading="tableLoading"
  68. >
  69. <el-table-column prop="id" label="序号" align="center" header-align="center" type="index" width="60"/>
  70. <el-table-column prop="dccode" label="账号" align="center" header-align="center"/>
  71. <el-table-column prop="dcname" label="姓名" align="center" header-align="center" width="150"/>
  72. <el-table-column prop="market" label="地区" align="center" header-align="center"/>
  73. <el-table-column prop="phone" label="电话" align="center" header-align="center"/>
  74. <el-table-column prop="email" label="邮箱" align="center" header-align="center" width="200"/>
  75. <el-table-column prop="created_at" label="注册时间" align="center" header-align="center" sortable="custom" width="200"/>
  76. <el-table-column prop="expire_time" label="到期时间" align="center" header-align="center" sortable="custom" width="200"/>
  77. <el-table-column label="操作" width="180" align="center" header-align="center">
  78. <template #default="scope">
  79. <el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
  80. <el-button type="text" @click="handleLog(scope.row.dccode)">操作日志</el-button>
  81. </template>
  82. </el-table-column>
  83. </el-table>
  84. <!-- 分页组件 -->
  85. <div class="demo-pagination-block">
  86. <el-pagination
  87. @size-change="handleSizeChange"
  88. @current-change="handleCurrentChange"
  89. :current-page="currentPage"
  90. :page-sizes="[15, 20, 50, 100]"
  91. :page-size="pageSize"
  92. layout="total, sizes, prev, pager, next, jumper"
  93. :total= "datatotal"
  94. />
  95. </div>
  96. <el-dialog v-model="dialogVisible" :title="addOrUpdata === 1 ? '添加权限' : '设置'" width="500px" :before-close="cancel">
  97. <!-- 设置用户 -->
  98. <div class="form-item" v-if="addOrUpdata === 1">
  99. <label class="form-label">设置用户</label>
  100. <el-input type="textarea" v-model="hlidsInput" rows=10
  101. placeholder="请输入HLid...
  102. 示例
  103. HL30454647
  104. HL30454648
  105. HL30454649"
  106. />
  107. <div class="tip">支持批量输入每次最多1000个手动/Excel粘贴均可</div>
  108. </div>
  109. <!-- 编辑回显 -->
  110. <div class="info-container" v-if="addOrUpdata === 0">
  111. <span class="info-item">HLid{{ hlidsInput }}</span>
  112. <span class="info-item">当前到期时间{{ deadline.split(' ')[0] }}</span>
  113. </div>
  114. <!-- 设置权限时间 -->
  115. <div class="form-item">
  116. <label class="form-label">设置权限时间</label>
  117. <el-radio-group v-model="timeType" class="radio-group">
  118. <el-radio label="expire" class="radio-item">
  119. <span>到期时间</span>
  120. <el-date-picker
  121. v-model="expireTime"
  122. type="date"
  123. placeholder="请选择到期日期"
  124. :disabled-date="disabledDate"
  125. style="width: 220px; margin-left: 8px;"
  126. />
  127. </el-radio>
  128. <el-radio label="delay" class="radio-item">
  129. <span>延期时间</span>
  130. <el-input
  131. v-model.number="delayValue"
  132. type="number"
  133. style="width: 60px; margin: 0 8px;"
  134. placeholder="1"
  135. @input="handleDelayInput"
  136. />
  137. <el-select v-model="delayUnit" placeholder="请选择" style="width: 150px;">
  138. <el-option label="年" value="year"></el-option>
  139. <el-option label="月" value="month"></el-option>
  140. <el-option label="周" value="week"></el-option>
  141. <el-option label="日" value="day"></el-option>
  142. </el-select>
  143. </el-radio>
  144. </el-radio-group>
  145. </div>
  146. <!-- 备注-->
  147. <div class="form-item inline-form-item">
  148. <label class="form-label">备注</label>
  149. <el-input
  150. type="textarea"
  151. v-model="remark"
  152. rows=3
  153. placeholder="请输入备注..."
  154. maxlength="150"
  155. show-word-limit
  156. />
  157. </div>
  158. <!-- 操作人 -->
  159. <div class="form-item inline-form-item">
  160. <label class="form-label">操作人</label>
  161. <el-input v-model="operator" placeholder="请填写操作人"/>
  162. </div>
  163. <!-- 按钮区域 -->
  164. <div class="dialog-footer">
  165. <el-button type="default" plain @click="cancel">取消</el-button>
  166. <el-button type="primary" @click="submitForm" style="background-color: #ff0000; border-color: #ff0000;">提交</el-button>
  167. </div>
  168. </el-dialog>
  169. </div>
  170. </template>
  171. <script setup>
  172. import { ref, reactive, onMounted } from 'vue';
  173. import { ElMessage } from 'element-plus';
  174. import { marketListApi, userMListApi, exportCreateApi, exitMApi } from '../../api/userPermissions'
  175. import router from '../../router';
  176. // token
  177. const token = localStorage.getItem('token')
  178. // 搜索表单
  179. const searchForm = reactive({
  180. dccode: '',
  181. dcname: '',
  182. market: '',
  183. register_type: ''
  184. });
  185. // 排序参数
  186. const sortProp = ref(null);
  187. const sortOrder = ref(null);
  188. // 表格数据
  189. const tableData = ref([]);
  190. const tableLoading = ref(false);
  191. const datatotal = ref(0)
  192. // 分页参数
  193. const currentPage = ref(1);
  194. const pageSize = ref(15);
  195. // 地区下拉框
  196. const regionList = ref([]);
  197. const isRegionLoading = ref(false);
  198. // 弹框显隐控制
  199. const dialogVisible = ref(false);
  200. // 开通权限表单
  201. const hlidsInput = ref('');
  202. const timeType = ref('');
  203. const expireTime = ref('');
  204. const delayValue = ref('');
  205. const delayUnit = ref('');
  206. const remark = ref('');
  207. const operator = ref('');
  208. // 判断开通还是编辑
  209. const addOrUpdata = ref(0);
  210. // 编辑回显内容
  211. const deadline = ref('');
  212. // 禁用当前日期之前的日期(当天0点之前的时间)
  213. const disabledDate = (time) => {
  214. return time.getTime() < new Date().getTime() - 8.64e7;
  215. };
  216. // 格式化日期
  217. const formatDate = (date) => {
  218. if (!date) return '';
  219. const year = date.getFullYear();
  220. const month = String(date.getMonth() + 1).padStart(2, '0');
  221. const day = String(date.getDate()).padStart(2, '0');
  222. return `${year}/${month}/${day}`;
  223. };
  224. // 校验HLid
  225. const checkHlids = () => {
  226. // 非空
  227. if (!hlidsInput.value.trim()) {
  228. ElMessage.error('请输入HLid');
  229. return false;
  230. }
  231. // 处理输入:去空、去重,转数组
  232. const hlidList = hlidsInput.value.split('\n')
  233. .map(item => item.trim())
  234. .filter(item => item)
  235. .filter((item, index, self) => self.indexOf(item) === index); // 去重
  236. // 数量校验(最多1000个)
  237. if (hlidList.length > 1000) {
  238. ElMessage.error('HLid数量不能超过1000个');
  239. return false;
  240. }
  241. // 格式校验(HL开头+8位数字)
  242. const hlidReg = /^DC\d{8}$/;
  243. for (const hlid of hlidList) {
  244. if (!hlidReg.test(hlid)) {
  245. ElMessage.error(`HLid格式错误:${hlid},请重新输入`);
  246. return false;
  247. }
  248. }
  249. return true;
  250. };
  251. // 校验时间
  252. const checkTime = () => {
  253. if (timeType.value === 'expire') {
  254. // 到期时间
  255. // 校验不为空
  256. if (!expireTime.value) {
  257. ElMessage.error('请选择到期时间');
  258. return false;
  259. }
  260. } else if (timeType.value === 'delay') {
  261. // 延期时间
  262. // 校验不为空
  263. if (!delayValue.value || !delayUnit.value) {
  264. ElMessage.error('延期时间必须填写完整(数值+单位)');
  265. return false;
  266. }
  267. } else {
  268. ElMessage.error('请设置权限时间');
  269. return false;
  270. }
  271. return true;
  272. };
  273. // 禁止为小数
  274. const handleDelayInput = () => {
  275. if (delayValue.value !== null && delayValue.value !== undefined) {
  276. delayValue.value = Math.floor(delayValue.value);
  277. }
  278. };
  279. // 校验备注
  280. const checkRemark = () => {
  281. if (!remark.value.trim()) {
  282. ElMessage.error('请输入备注');
  283. return false;
  284. }
  285. return true;
  286. };
  287. // 提交表单
  288. const submitForm = async () => {
  289. // 表单校验
  290. if (!checkHlids() || !checkTime() || !checkRemark()) {
  291. return;
  292. }
  293. try {
  294. // 组装后端要求的参数格式
  295. const requestParams = {
  296. token: token,
  297. // HLid
  298. hlids: hlidsInput.value.split('\n')
  299. .map(item => item.trim())
  300. .filter(item => item)
  301. .join('\n'),
  302. // 备注
  303. remark: remark.value.trim(),
  304. // 操作人
  305. ...(operator.value.trim() && { operator: operator.value.trim() }),
  306. // 时间参数
  307. ...(timeType.value === 'expire'
  308. ? { expire_time: formatDate(expireTime.value) }
  309. : {
  310. [delayUnit.value]: Number(delayValue.value)
  311. })
  312. };
  313. console.log('传给后端的参数:', requestParams);
  314. // 调用后端接口
  315. const res = await exitMApi(requestParams);
  316. ElMessage.success('成功添加用户权限');
  317. // 重置表单并关闭弹框
  318. resetForm();
  319. dialogVisible.value = false;
  320. addOrUpdata.value = 0;
  321. // 重新获取表格
  322. fetchTableData();
  323. } catch (error) {
  324. ElMessage.error('添加权限失败,请重试');
  325. }
  326. };
  327. // 取消表单
  328. const cancel = () => {
  329. resetForm();
  330. dialogVisible.value = false;
  331. addOrUpdata.value = 0;
  332. };
  333. // 重置表单数据
  334. const resetForm = () => {
  335. hlidsInput.value = '';
  336. timeType.value = '';
  337. expireTime.value = '';
  338. delayValue.value = '';
  339. delayUnit.value = '';
  340. remark.value = '';
  341. operator.value = '';
  342. };
  343. // 获取地区列表
  344. const fetchRegionList = async () => {
  345. try {
  346. isRegionLoading.value = true;
  347. const data = await marketListApi({token: token});
  348. regionList.value = data.list;
  349. } catch (error) {
  350. console.error('获取地区列表失败:', error);
  351. regionList.value = [];
  352. } finally {
  353. isRegionLoading.value = false;
  354. }
  355. };
  356. // 获取表格数据
  357. const fetchTableData = async () => {
  358. try {
  359. tableLoading.value = true;
  360. const requestParams = {
  361. token: token,
  362. dccode: searchForm.dccode,
  363. dcname: searchForm.dcname,
  364. market: searchForm.market,
  365. register_type: searchForm.register_type,
  366. sort_field: sortProp.value,
  367. sort_order: sortOrder.value,
  368. page: currentPage.value,
  369. page_size: pageSize.value
  370. };
  371. const data = await userMListApi(requestParams);
  372. tableData.value = data.list
  373. datatotal.value = data.total
  374. } catch (error) {
  375. console.error('获取表格数据失败:', error);
  376. tableData.value = [];
  377. datatotal.value = 0
  378. } finally {
  379. tableLoading.value = false;
  380. }
  381. };
  382. // 组件挂载时:获取地区列表 + 初始化表格数据
  383. onMounted(() => {
  384. fetchRegionList();
  385. fetchTableData();
  386. });
  387. // 搜索按钮
  388. const search = () => {
  389. currentPage.value = 1;
  390. fetchTableData();
  391. };
  392. // 开通权限按钮
  393. const enableAccess = () => {
  394. dialogVisible.value = true;
  395. addOrUpdata.value = 1;
  396. };
  397. // 导出Excel列表按钮
  398. const exportExcel = async () => {
  399. const requestParams = {
  400. token: token,
  401. dccode: searchForm.dccode,
  402. dcname: searchForm.dcname,
  403. market: searchForm.market,
  404. register_type: searchForm.register_type,
  405. sort_field: sortProp.value,
  406. sort_order: sortOrder.value
  407. };
  408. const data = await exportCreateApi(requestParams);
  409. if (data != '') {
  410. ElMessage.success('已导出');
  411. }
  412. };
  413. // 查看导出列表按钮
  414. const exportList = () => {
  415. router.push({
  416. path: "/userPermissions/export"
  417. });
  418. };
  419. // 重置按钮
  420. const resetBn = () => {
  421. searchForm.dccode = '';
  422. searchForm.dcname = '';
  423. searchForm.market = '';
  424. searchForm.register_type = '';
  425. sortProp.value = null;
  426. sortOrder.value = null;
  427. currentPage.value = 1;
  428. pageSize.value = 10;
  429. fetchTableData();
  430. };
  431. // 编辑
  432. const handleEdit = (row) => {
  433. dialogVisible.value = true;
  434. hlidsInput.value = row.dccode;
  435. deadline.value = row.expire_time;
  436. };
  437. // 操作日志
  438. const handleLog = (dccode) => {
  439. router.push({
  440. path: "/userPermissions/logMarket",
  441. query: { dccode: dccode }
  442. });
  443. };
  444. // 分页方法
  445. const handleSizeChange = (val) => {
  446. pageSize.value = val;
  447. fetchTableData();
  448. console.log(`每页 ${val}`);
  449. };
  450. const handleCurrentChange = (val) => {
  451. currentPage.value = val;
  452. fetchTableData();
  453. console.log(`当前页: ${val}`);
  454. };
  455. // 排序事件
  456. const handleSortChange = (sort) => {
  457. const { prop, order } = sort;
  458. // 仅允许注册时间和到期时间排序
  459. if (!['created_at', 'expire_time'].includes(prop)) return;
  460. // 覆盖排序状态(实现二选一)
  461. sortProp.value = prop; // 保存当前排序字段
  462. sortOrder.value = order; // 保存当前排序方式
  463. fetchTableData();
  464. // console.log(`排序字段:${sortProp.value},排序方式:${sortOrder.value}`);
  465. };
  466. </script>
  467. <style scoped>
  468. /* 父容器 */
  469. .page-container {
  470. position: relative;
  471. min-height: 600px;
  472. }
  473. /* 搜索区域 */
  474. .search-container {
  475. display: flex;
  476. height: 67px;
  477. flex-direction: column;
  478. justify-content: center;
  479. align-items: flex-start;
  480. gap: 10px;
  481. align-self: stretch;
  482. border-radius: 8px;
  483. background: #FEFAF9;
  484. box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
  485. padding: 0 15px;
  486. margin-bottom: 20px;
  487. }
  488. /* 搜索表单 */
  489. .search-form {
  490. display: flex;
  491. align-items: center;
  492. width: 100%;
  493. gap: 15px;
  494. flex-wrap: nowrap;
  495. }
  496. /* 单个搜索项 */
  497. .search-item {
  498. display: flex;
  499. align-items: center;
  500. gap: 6px;
  501. }
  502. /* 搜索标签文字 */
  503. .form-label {
  504. font-weight: 800 !important;
  505. font-size: 15px;
  506. text-align: left;
  507. color: #333;
  508. margin-top: 13px;
  509. }
  510. /* 按钮组 */
  511. .button-group {
  512. display: flex;
  513. align-items: center;
  514. gap: 0px !important;
  515. margin-left: auto;
  516. }
  517. /* 按钮样式 */
  518. .button-group .el-button {
  519. padding: 6px 10px !important;
  520. font-size: 14px !important;
  521. height: 36px !important;
  522. }
  523. /* 表格样式 */
  524. .table-rounded {
  525. border-radius: 12px !important;
  526. overflow: hidden !important;
  527. border: 1px solid #e4e7ed !important;
  528. }
  529. .table-header {
  530. text-align: center !important;
  531. font-weight: 800 !important;
  532. font-size: 15px !important;
  533. color: #333 !important;
  534. background-color: #f8f9fa !important;
  535. }
  536. .el-table__cell {
  537. border-right: none !important;
  538. border-bottom: 1px solid #e4e7ed !important;
  539. }
  540. .el-table__header th.el-table__cell {
  541. border-right: none !important;
  542. border-bottom: 1px solid #e4e7ed !important;
  543. }
  544. .el-table__row:hover .el-table__cell {
  545. background-color: #fafafa !important;
  546. }
  547. /* 分页组件样式 */
  548. .demo-pagination-block {
  549. display: flex;
  550. width: 100%;
  551. height: 44px;
  552. padding: 0 16px;
  553. align-items: center;
  554. gap: 16px;
  555. position: absolute;
  556. margin-top: 10px;
  557. border-radius: 0 0 3px 3px;
  558. border-top: 1px solid #EAEAEA;
  559. background: #FEFBFB;
  560. box-sizing: border-box;
  561. }
  562. /* 添加/修改样式 */
  563. .form-item {
  564. margin-bottom: 24px;
  565. padding-left: 20px;
  566. padding-right: 20px;
  567. text-align: left;
  568. }
  569. .form-label {
  570. display: block;
  571. margin-bottom: 8px;
  572. font-weight: 500;
  573. }
  574. .radio-group {
  575. display: flex;
  576. flex-direction: column;
  577. gap: 12px;
  578. align-items: flex-start;
  579. }
  580. .radio-item {
  581. display: flex;
  582. align-items: center;
  583. }
  584. .radio-item span {
  585. margin-right: 8px;
  586. }
  587. .tip {
  588. font-size: 12px;
  589. color: #909399;
  590. margin-top: 4px;
  591. }
  592. .dialog-footer {
  593. display: flex;
  594. justify-content: flex-end;
  595. gap: 16px;
  596. margin-top: 20px;
  597. }
  598. .inline-form-item {
  599. display: flex;
  600. align-items: flex-start;
  601. gap: 12px;
  602. }
  603. .inline-form-item .form-label {
  604. display: inline-block;
  605. margin-bottom: 0;
  606. width: 60px;
  607. text-align: left;
  608. padding-right: 12px;
  609. }
  610. .info-container {
  611. display: flex;
  612. align-items: center;
  613. gap: 40px;
  614. padding: 0 20px 18px;
  615. color: #333;
  616. font-size: 16px;
  617. }
  618. .info-item {
  619. white-space: nowrap;
  620. }
  621. </style>