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.

704 lines
18 KiB

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