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.

1541 lines
40 KiB

3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
  1. <template>
  2. <div>
  3. <div class="page-container" v-show="taber == 'DeepMate'">
  4. <!-- 搜索区域 -->
  5. <div class="search-container">
  6. <div class="search-form">
  7. <div class="search-item">
  8. <span class="form-label">账号</span>
  9. <el-input
  10. v-model="searchFormDM.dccode"
  11. placeholder="请输入账号"
  12. clearable
  13. style="height: 36px; width: 140px;"
  14. />
  15. </div>
  16. <div class="search-item">
  17. <span class="form-label">姓名</span>
  18. <el-input
  19. v-model="searchFormDM.dcname"
  20. placeholder="请输入姓名"
  21. clearable
  22. style="height: 36px; width: 180px;"
  23. />
  24. </div>
  25. <div class="search-item">
  26. <span class="form-label">客户类型</span>
  27. <el-select
  28. v-model="searchFormDM.user_role"
  29. placeholder="请选择客户类型"
  30. clearable
  31. style="height: 36px; width: 160px;"
  32. >
  33. <el-option label="非网" value="2" />
  34. <el-option label="会员" value="1" />
  35. </el-select>
  36. </div>
  37. <div class="search-item">
  38. <span class="form-label">地区</span>
  39. <el-select
  40. v-model="searchFormDM.market"
  41. placeholder="请选择地区"
  42. clearable
  43. filterable
  44. style="height: 36px; width: 160px;"
  45. :loading="isRegionLoading"
  46. >
  47. <el-option
  48. v-for="region in regionList"
  49. :key="region.ID"
  50. :label="region.Name"
  51. :value="region.ID"
  52. />
  53. </el-select>
  54. </div>
  55. <div class="button-group">
  56. <el-button type="primary" @click="searchDM">搜索</el-button>
  57. <el-button type="danger" @click="enableAccessDM">开通权限</el-button>
  58. <el-button type="success" @click="exportExcelDM">导出Excel列表</el-button>
  59. <el-button color="#626aef" @click="exportListDM">查看导出列表</el-button>
  60. <el-button type="primary" @click="resetBnDM">重置</el-button>
  61. </div>
  62. </div>
  63. </div>
  64. <!-- tab切换 -->
  65. <div class="tab-group">
  66. <button
  67. class="tab-btn"
  68. :class="{ active: taber === 'DeepMate' }"
  69. @click="taber = 'DeepMate'"
  70. >
  71. DeepMate
  72. </button>
  73. <button
  74. class="tab-btn"
  75. :class="{ active: taber === 'DeepExplore' }"
  76. @click="taber = 'DeepExplore'"
  77. >
  78. 深度探索
  79. </button>
  80. </div>
  81. <!-- 数据 -->
  82. <el-table
  83. :data="tableDataDM"
  84. style="width: 100%; margin-top: 20px;"
  85. header-cell-class-name="table-header"
  86. @sort-change="handleSortChangeDM"
  87. :default-sort="{ order: null }"
  88. class="table-rounded"
  89. :loading="tableLoadingDM"
  90. >
  91. <el-table-column prop="id" label="序号" align="center" header-align="center" width="80">
  92. <template #default="scope">
  93. {{ (currentPageDM - 1) * pageSizeDM + scope.$index + 1 }}
  94. </template>
  95. </el-table-column>
  96. <el-table-column prop="dccode" label="账号" align="center" header-align="center"/>
  97. <el-table-column prop="dcname" label="姓名" align="center" header-align="center"/>
  98. <el-table-column prop="module_name" label="模块名称" align="center" header-align="center"/>
  99. <el-table-column prop="token_num" label="token数量" align="center" header-align="center"/>
  100. <el-table-column prop="updated_at" label="操作时间" align="center" header-align="center" sortable="custom"/>
  101. <el-table-column label="操作" align="center" header-align="center">
  102. <template #default="scope">
  103. <el-button type="text" @click="handleEditDM(scope.row)">编辑</el-button>
  104. <el-button type="text" @click="handleLogDM(scope.row.dccode)">操作日志</el-button>
  105. </template>
  106. </el-table-column>
  107. </el-table>
  108. <!-- 分页组件 -->
  109. <div class="demo-pagination-block">
  110. <el-pagination
  111. @size-change="handleSizeChangeDM"
  112. @current-change="handleCurrentChangeDM"
  113. :current-page="currentPageDM"
  114. :page-sizes="[10, 20, 50, 100]"
  115. :page-size="pageSizeDM"
  116. layout="total, sizes, prev, pager, next, jumper"
  117. :total= "datatotalDM"
  118. />
  119. </div>
  120. <!-- 开通/编辑弹窗 -->
  121. <el-dialog v-model="dialogVisibleDM" :title="addOrUpdataDM === 1 ? '添加权限' : '修改'" width="500px" :before-close="cancelDM">
  122. <!-- 设置用户 -->
  123. <div class="form-item" v-if="addOrUpdataDM === 1">
  124. <label class="form-label">设置用户</label>
  125. <el-input type="textarea" v-model="hlidsInput" rows=10
  126. placeholder="请输入HLid...
  127. 示例
  128. 90048004
  129. 90048005
  130. 90048006"
  131. />
  132. <div class="tip">支持批量输入每次最多1000个手动/Excel粘贴均可</div>
  133. </div>
  134. <!-- 编辑回显 -->
  135. <div class="info-container" v-if="addOrUpdataDM === 0">
  136. <span class="info-item">HLid: {{ hlidsInput }}</span>
  137. <span class="info-item">剩余次数{{ deadtoken }}</span>
  138. </div>
  139. <!-- 设置数量 -->
  140. <div class="form-item" v-if="addOrUpdataDM === 1">
  141. <label class="form-label">设置用户次数</label>
  142. <div class="count-group">
  143. <!-- Deep Mate不可编辑文本框 -->
  144. <el-input v-model="dmText" disabled style="width: 160px; margin-right: 16px;"/>
  145. <!-- token数量只能正整数 -->
  146. <el-input v-model.number="token_num" type="number" style="width: 210px;" placeholder="请输入次数(只能为正)"/>
  147. </div>
  148. </div>
  149. <div class="form-item" v-if="addOrUpdataDM === 0">
  150. <label class="form-label">修改用户次数</label>
  151. <div class="count-group">
  152. <!-- Deep Mate不可编辑文本框 -->
  153. <el-input v-model="dmText" disabled style="width: 160px; margin-right: 16px;"/>
  154. <!-- token数量支持正负整数 -->
  155. <el-input v-model.number="token_num" type="number" style="width: 210px;" placeholder="请输入次数(可正负)"/>
  156. </div>
  157. </div>
  158. <!-- 按钮区域 -->
  159. <div class="dialog-footer">
  160. <el-button type="default" plain @click="cancelDM">取消</el-button>
  161. <el-button type="primary" @click="submitFormDM" style="background-color: #ff0000; border-color: #ff0000;">提交</el-button>
  162. </div>
  163. </el-dialog>
  164. </div>
  165. <div class="page-container" v-show="taber == 'DeepExplore'">
  166. <!-- 搜索区域 -->
  167. <div class="search-containerDE">
  168. <!-- 输入项区域 -->
  169. <div class="search-formDE">
  170. <div class="search-itemDE">
  171. <span class="form-labelDE">账号</span>
  172. <el-input
  173. v-model="searchFormDE.dccode"
  174. placeholder="请输入账号"
  175. clearable
  176. style="height: 36px; width: 140px;"
  177. />
  178. </div>
  179. <div class="search-itemDE">
  180. <span class="form-labelDE">姓名</span>
  181. <el-input
  182. v-model="searchFormDE.dcname"
  183. placeholder="请输入姓名"
  184. clearable
  185. style="height: 36px; width: 180px;"
  186. />
  187. </div>
  188. <div class="search-itemDE">
  189. <span class="form-labelDE">客户类型</span>
  190. <el-select
  191. v-model="searchFormDE.user_role"
  192. placeholder="请选择客户类型"
  193. clearable
  194. style="height: 36px; width: 160px;"
  195. >
  196. <el-option label="非网" value="2" />
  197. <el-option label="会员" value="1" />
  198. </el-select>
  199. </div>
  200. <div class="search-itemDE">
  201. <span class="form-labelDE">地区</span>
  202. <el-select
  203. v-model="searchFormDE.market"
  204. placeholder="请选择地区"
  205. clearable
  206. filterable
  207. style="height: 36px; width: 160px;"
  208. :loading="isRegionLoading"
  209. >
  210. <el-option
  211. v-for="region in regionList"
  212. :key="region.ID"
  213. :label="region.Name"
  214. :value="region.ID"
  215. />
  216. </el-select>
  217. </div>
  218. <div class="search-itemDE">
  219. <span class="form-labelDE">指标名称</span>
  220. <el-select
  221. v-model="searchFormDE.indicator_id"
  222. placeholder="请选择指标名称"
  223. clearable
  224. style="height: 36px; width: 160px;"
  225. >
  226. <el-option
  227. v-for="item in indicatorOptions"
  228. :key="item.id"
  229. :label="item.name"
  230. :value="item.id"
  231. />
  232. </el-select>
  233. </div>
  234. </div>
  235. <!-- 按钮组 -->
  236. <div class="button-groupDE">
  237. <el-button type="primary" @click="searchDE">搜索</el-button>
  238. <el-button type="danger" @click="enableAccessDE">开通权限</el-button>
  239. <el-button type="success" @click="exportExcelDE">导出Excel列表</el-button>
  240. <el-button color="#626aef" @click="exportListDE">查看导出列表</el-button>
  241. <el-button type="primary" @click="resetBnDE">重置</el-button>
  242. </div>
  243. </div>
  244. <!-- tab切换 -->
  245. <div class="tab-group">
  246. <button
  247. class="tab-btn"
  248. :class="{ active: taber === 'DeepMate' }"
  249. @click="taber = 'DeepMate'"
  250. >
  251. DeepMate
  252. </button>
  253. <button
  254. class="tab-btn"
  255. :class="{ active: taber === 'DeepExplore' }"
  256. @click="taber = 'DeepExplore'"
  257. >
  258. 深度探索
  259. </button>
  260. </div>
  261. <!-- 数据 -->
  262. <el-table
  263. :data="tableDataDE"
  264. style="width: 100%; margin-top: 20px;"
  265. header-cell-class-name="table-header"
  266. @sort-change="handleSortChangeDE"
  267. :default-sort="{ prop: null, order: null }"
  268. class="table-roundedDE"
  269. :loading="tableLoadingDE"
  270. >
  271. <el-table-column prop="id" label="序号" align="center" header-align="center" width="80">
  272. <template #default="scope">
  273. {{ (currentPageDE - 1) * pageSizeDE + scope.$index + 1 }}
  274. </template>
  275. </el-table-column>
  276. <el-table-column prop="dccode" label="账号" align="center" header-align="center"/>
  277. <el-table-column prop="dcname" label="姓名" align="center" header-align="center"/>
  278. <el-table-column prop="name" label="指标名称" align="center" header-align="center" width="200">
  279. <template #default="scope">
  280. <el-tooltip
  281. effect="dark"
  282. :content="scope.row.name"
  283. placement="top"
  284. >
  285. <span class="ellipsis-text">{{ scope.row.name }}</span>
  286. </el-tooltip>
  287. </template>
  288. </el-table-column>
  289. <el-table-column prop="created_at" label="开通时间" align="center" header-align="center" sortable="custom" width="200"/>
  290. <el-table-column prop="expire_time" label="到期时间" align="center" header-align="center" sortable="custom" width="200"/>
  291. <el-table-column label="操作" align="center" header-align="center">
  292. <template #default="scope">
  293. <el-button type="text" @click="handleEditDE(scope.row)">编辑</el-button>
  294. <el-button type="text" @click="handleDetailsDE(scope.row.dccode)">权限详情</el-button>
  295. <el-button type="text" @click="handleLogDE(scope.row.dccode)">操作日志</el-button>
  296. </template>
  297. </el-table-column>
  298. </el-table>
  299. <!-- 分页组件 -->
  300. <div class="demo-pagination-block">
  301. <el-pagination
  302. @size-change="handleSizeChangeDE"
  303. @current-change="handleCurrentChangeDE"
  304. :current-page="currentPageDE"
  305. :page-sizes="[10, 20, 50, 100]"
  306. :page-size="pageSizeDE"
  307. layout="total, sizes, prev, pager, next, jumper"
  308. :total= "datatotalDE"
  309. />
  310. </div>
  311. <!-- 开通/编辑 -->
  312. <el-dialog v-model="dialogVisibleDE" :title="addOrUpdataDE === 1 ? '添加权限' : '设置'" width="550px" :before-close="cancelDE">
  313. <!-- 设置用户 -->
  314. <div class="form-item" v-if="addOrUpdataDE === 1">
  315. <label class="form-label">设置用户</label>
  316. <el-input type="textarea" v-model="hlidsInputDE" rows=10
  317. placeholder="请输入HLid...
  318. 示例
  319. 90048004
  320. 90048005
  321. 90048006"
  322. />
  323. <div class="tip">支持批量输入每次最多1000个手动/Excel粘贴均可</div>
  324. </div>
  325. <!-- 编辑回显 -->
  326. <div class="info-container" v-if="addOrUpdataDE === 0">
  327. <span class="info-item">HLid{{ hlidsInputDE }}</span>
  328. <span class="info-item">当前到期时间{{ deadline.split(' ')[0] }}</span>
  329. </div>
  330. <!-- 设置指标 -->
  331. <div class="form-item">
  332. <label class="form-label">选择模板</label>
  333. <el-checkbox-group v-model="indicator_id" class="indicator-checkbox-group">
  334. <el-checkbox
  335. v-for="item in indicatorOptions"
  336. :key="item.id"
  337. :label="item.id"
  338. class="indicator-checkbox-item"
  339. >
  340. {{ item.name }}
  341. </el-checkbox>
  342. </el-checkbox-group>
  343. </div>
  344. <!-- 设置权限时间 -->
  345. <div class="form-item">
  346. <label class="form-label">设置权限时间</label>
  347. <el-radio-group v-model="timeType" class="radio-group">
  348. <el-radio label="expire" class="radio-item">
  349. <span>到期时间</span>
  350. <el-date-picker
  351. v-model="expireTime"
  352. type="date"
  353. placeholder="请选择到期日期"
  354. :disabled-date="disabledDate"
  355. style="width: 220px; margin-left: 8px;"
  356. />
  357. </el-radio>
  358. <el-radio label="delay" class="radio-item">
  359. <span>延期时间</span>
  360. <el-input
  361. v-model.number="delayValue"
  362. type="number"
  363. style="width: 60px; margin: 0 8px;"
  364. placeholder="1"
  365. @input="handleDelayInput"
  366. />
  367. <el-select v-model="delayUnit" placeholder="请选择" style="width: 150px;">
  368. <el-option label="年" value="year"></el-option>
  369. <el-option label="月" value="month"></el-option>
  370. <el-option label="周" value="week"></el-option>
  371. <el-option label="日" value="day"></el-option>
  372. </el-select>
  373. </el-radio>
  374. </el-radio-group>
  375. </div>
  376. <!-- 备注-->
  377. <div class="form-item inline-form-item">
  378. <label class="form-label">备注</label>
  379. <el-input
  380. type="textarea"
  381. v-model="remark"
  382. rows=3
  383. placeholder="请输入备注..."
  384. maxlength="150"
  385. show-word-limit
  386. />
  387. </div>
  388. <!-- 操作人 -->
  389. <div class="form-item inline-form-item">
  390. <label class="form-label">操作人</label>
  391. <el-input v-model="operator" placeholder="请填写操作人"/>
  392. </div>
  393. <!-- 按钮区域 -->
  394. <div class="dialog-footer">
  395. <el-button type="default" plain @click="cancelDE">取消</el-button>
  396. <el-button type="primary" @click="submitFormDE" style="background-color: #ff0000; border-color: #ff0000;">提交</el-button>
  397. </div>
  398. </el-dialog>
  399. <!-- 权限详情 -->
  400. <el-dialog
  401. v-model="showPermissionDetail"
  402. title="权限详情"
  403. width="400px"
  404. :close-on-click-modal="false"
  405. :show-close="true"
  406. class="permission-detail-dialog"
  407. >
  408. <div class="detail-container">
  409. <!-- HLid 信息 -->
  410. <div class="hlid-item">
  411. <span class="label">HLid</span>
  412. <span class="value">{{ HLid }}</span>
  413. </div>
  414. <!-- 到期时间区域 -->
  415. <div class="expire-section">
  416. <h3 class="section-title">到期时间</h3>
  417. <div class="indicator-list">
  418. <div
  419. v-for="(item, index) in permissionData"
  420. :key="index"
  421. class="indicator-item"
  422. >
  423. <span class="indicator-name">{{ item.name }}</span>
  424. <template v-if="item.is_expired === 1">
  425. <span class="expired-tag">已到期</span>
  426. </template>
  427. <template v-else>
  428. <span class="expire-time">{{ item.expire_time.split(' ')[0] }}</span>
  429. </template>
  430. </div>
  431. </div>
  432. </div>
  433. </div>
  434. </el-dialog>
  435. </div>
  436. </div>
  437. </template>
  438. <script setup>
  439. import { ref, reactive, onMounted, computed } from 'vue';
  440. import { ElMessage } from 'element-plus';
  441. import { marketListApi, indicatorListApi, userDMListApi, exportDeepMateApi, exitDMApi, userDEListApi, exportDeepExploreApi, exitDEApi, detailDEApi } from '../../api/userPermissions'
  442. import router from '../../router';
  443. import { useRoute } from 'vue-router';
  444. // token
  445. const token = localStorage.getItem('token')
  446. // tab切换
  447. const taber = ref('DeepMate')
  448. // 获取路由实例
  449. const route = useRoute();
  450. // 组件挂载时:获取地区列表 + 初始化表格数据
  451. onMounted(() => {
  452. const taberQuery = route.query.taber;
  453. const TaberValues = ["DeepMate", "DeepExplore"];
  454. taber.value = TaberValues.includes(String(taberQuery)) ? String(taberQuery) : "DeepMate";
  455. fetchRegionList();
  456. indicatorList();
  457. DMTableData();
  458. DETableData();
  459. });
  460. // 地区下拉框
  461. const regionList = ref([]);
  462. const isRegionLoading = ref(false);
  463. // 获取地区列表
  464. const fetchRegionList = async () => {
  465. try {
  466. isRegionLoading.value = true;
  467. const data = await marketListApi({
  468. token: token,
  469. app_form: "en"
  470. });
  471. regionList.value = data.list;
  472. } catch (error) {
  473. console.error('获取地区列表失败:', error);
  474. regionList.value = [];
  475. } finally {
  476. isRegionLoading.value = false;
  477. }
  478. };
  479. // DeepMate搜索表单
  480. const searchFormDM = reactive({
  481. dccode: '',
  482. dcname: '',
  483. market: '',
  484. user_role: ''
  485. });
  486. // DeepMate排序参数
  487. const sortOrderDM = ref(null);
  488. // DeepMate表格数据
  489. const tableDataDM = ref([]);
  490. const tableLoadingDM = ref(false);
  491. const datatotalDM = ref(0)
  492. // DeepMate分页参数
  493. const currentPageDM = ref(1);
  494. const pageSizeDM = ref(10);
  495. // DeepMate获取表格数据
  496. const DMTableData = async () => {
  497. try {
  498. tableLoadingDM.value = true;
  499. const requestParams = {
  500. token: token,
  501. dccode: searchFormDM.dccode,
  502. dcname: searchFormDM.dcname,
  503. market: searchFormDM.market,
  504. user_role: searchFormDM.user_role,
  505. sort_order: sortOrderDM.value,
  506. page: currentPageDM.value,
  507. page_size: pageSizeDM.value
  508. };
  509. const data = await userDMListApi(requestParams);
  510. tableDataDM.value = data.list
  511. datatotalDM.value = data.total
  512. } catch (error) {
  513. console.error('获取表格数据失败:', error);
  514. tableDataDM.value = [];
  515. datatotalDM.value = 0
  516. } finally {
  517. tableLoadingDM.value = false;
  518. }
  519. };
  520. // DeepMate分页方法
  521. const handleSizeChangeDM = (val) => {
  522. pageSizeDM.value = val;
  523. DMTableData();
  524. console.log(`每页 ${val}`);
  525. };
  526. const handleCurrentChangeDM = (val) => {
  527. currentPageDM.value = val;
  528. DMTableData();
  529. console.log(`当前页: ${val}`);
  530. };
  531. // DeepMate排序事件
  532. const handleSortChangeDM = (sort) => {
  533. const { order } = sort;
  534. sortOrderDM.value = order; // 保存当前排序方式
  535. DMTableData();
  536. };
  537. // DeepMate搜索按钮
  538. const searchDM = () => {
  539. currentPageDM.value = 1;
  540. DMTableData();
  541. };
  542. // DeepMate重置按钮
  543. const resetBnDM = () => {
  544. searchFormDM.dccode = '';
  545. searchFormDM.dcname = '';
  546. searchFormDM.market = '';
  547. searchFormDM.user_role = '';
  548. sortOrderDM.value = null;
  549. currentPageDM.value = 1;
  550. pageSizeDM.value = 10;
  551. DMTableData();
  552. };
  553. // DeepMate开通权限按钮
  554. const enableAccessDM = () => {
  555. dialogVisibleDM.value = true;
  556. addOrUpdataDM.value = 1;
  557. };
  558. // DeepMate导出Excel列表按钮
  559. const exportExcelDM = async () => {
  560. const requestParams = {
  561. token: token,
  562. dccode: searchFormDM.dccode,
  563. dcname: searchFormDM.dcname,
  564. market: searchFormDM.market,
  565. user_role: searchFormDM.user_role,
  566. sort_order: sortOrderDM.value
  567. };
  568. const data = await exportDeepMateApi(requestParams);
  569. if (data != '') {
  570. ElMessage.success('已导出');
  571. }
  572. };
  573. // DeepMate查看导出列表按钮
  574. const exportListDM = () => {
  575. router.push({
  576. path: "/userPermissions/export"
  577. });
  578. };
  579. // DeepMate编辑按钮
  580. const handleEditDM = (row) => {
  581. dialogVisibleDM.value = true;
  582. hlidsInput.value = row.dccode;
  583. deadtoken.value = row.token_num;
  584. };
  585. // DeepMate操作日志按钮
  586. const handleLogDM = (dccode) => {
  587. router.push({
  588. path: "/userPermissions/logDeepMate",
  589. query: { dccode: dccode }
  590. });
  591. };
  592. // DeepMate弹框显隐控制
  593. const dialogVisibleDM = ref(false);
  594. // DeepMate开通权限表单
  595. const hlidsInput = ref('');
  596. const dmText = ref('Deep Mate');
  597. const token_num = ref('');
  598. // DeepMate编辑回显内容
  599. const deadtoken = ref('');
  600. // DeepMate判断开通还是编辑
  601. const addOrUpdataDM = ref(0);
  602. // 校验HLid
  603. const checkHlids = () => {
  604. // 非空
  605. if (!hlidsInput.value.trim()) {
  606. ElMessage.error('请输入HLid');
  607. return false;
  608. }
  609. // 处理输入:去空、去重,转数组
  610. const hlidList = hlidsInput.value.split('\n')
  611. .map(item => item.trim())
  612. .filter(item => item)
  613. .filter((item, index, self) => self.indexOf(item) === index); // 去重
  614. // 数量校验(最多1000个)
  615. if (hlidList.length > 1000) {
  616. ElMessage.error('HLid数量不能超过1000个');
  617. return false;
  618. }
  619. // 格式校验(8位数字)
  620. const hlidReg = /^\d{8}$/;
  621. for (const hlid of hlidList) {
  622. if (!hlidReg.test(hlid)) {
  623. ElMessage.error(`有HLid格式错误:${hlid},请重新输入`);
  624. return false;
  625. }
  626. }
  627. return true;
  628. };
  629. // 校验token数量
  630. const checkTokenNum = () => {
  631. // 添加权限场景
  632. if (addOrUpdataDM.value === 1) {
  633. if (token_num.value === null || token_num.value === '') {
  634. ElMessage.error('请输入token数量');
  635. return false;
  636. }
  637. if (!Number.isInteger(token_num.value) || token_num.value <= 0) {
  638. ElMessage.error('token数量必须为正整数');
  639. return false;
  640. }
  641. }
  642. // 修改场景
  643. else if (addOrUpdataDM.value === 0) {
  644. if (token_num.value === null || token_num.value === '') {
  645. ElMessage.error('请输入token数量');
  646. return false;
  647. }
  648. if (!Number.isInteger(token_num.value)) {
  649. ElMessage.error('token数量必须为整数');
  650. return false;
  651. }
  652. if (token_num.value < 0 && Math.abs(token_num.value) > deadtoken.value) {
  653. ElMessage.error('扣除次数大于当前总次数,请重新输入次数');
  654. return false;
  655. }
  656. }
  657. return true;
  658. };
  659. // 防抖
  660. const NoshakeDM = ref(false)
  661. // DeepMate提交表单
  662. const submitFormDM = async () => {
  663. // 防抖
  664. if (NoshakeDM.value) return;
  665. NoshakeDM.value = true;
  666. // 表单校验
  667. if (!checkHlids() || !checkTokenNum()) {
  668. NoshakeDM.value = false;
  669. return;
  670. }
  671. try {
  672. const requestParams = {
  673. token: token,
  674. // HLid
  675. hlids: hlidsInput.value.split('\n')
  676. .map(item => item.trim())
  677. .filter(item => item)
  678. .join('\n'),
  679. // token数量
  680. token_num: token_num.value
  681. };
  682. console.log('传给后端的参数:', requestParams);
  683. // 调用后端接口
  684. await exitDMApi(requestParams);
  685. ElMessage.success(addOrUpdataDM.value === 1 ? '用户次数设置成功' : '用户次数修改成功');
  686. // 重置表单并关闭弹框
  687. resetFormDM();
  688. dialogVisibleDM.value = false;
  689. addOrUpdataDM.value = 0;
  690. // 重新获取表格
  691. DMTableData();
  692. } catch (error) {
  693. ElMessage.error('添加权限失败,请重试');
  694. } finally {
  695. NoshakeDM.value = false;
  696. }
  697. };
  698. // DeepMate取消表单
  699. const cancelDM = () => {
  700. resetFormDM();
  701. dialogVisibleDM.value = false;
  702. addOrUpdataDM.value = 0;
  703. };
  704. // DeepMate重置表单数据
  705. const resetFormDM = () => {
  706. hlidsInput.value = '';
  707. token_num.value = '';
  708. };
  709. // 深度探索指标选项
  710. const indicatorOptions = ref([]);
  711. const indicatorList = async () => {
  712. try {
  713. const data = await indicatorListApi({token: token});
  714. indicatorOptions.value = data.list;
  715. } catch (error) {
  716. console.error('获取指标列表失败:', error);
  717. indicatorOptions.value = [];
  718. }
  719. }
  720. // 深度探索搜索表单
  721. const searchFormDE = reactive({
  722. dccode: '',
  723. dcname: '',
  724. market: '',
  725. user_role: '',
  726. indicator_id: ''
  727. });
  728. // 深度探索排序参数
  729. const sortPropDE = ref(null);
  730. const sortOrderDE = ref(null);
  731. // 深度探索分页参数
  732. const currentPageDE = ref(1);
  733. const pageSizeDE = ref(10);
  734. // 深度探索表格数据
  735. const tableDataDE = ref([]);
  736. const tableLoadingDE = ref(false);
  737. const datatotalDE = ref(0)
  738. // 深度探索分页方法
  739. const handleSizeChangeDE = (val) => {
  740. pageSizeDE.value = val;
  741. DETableData();
  742. console.log(`每页 ${val}`);
  743. };
  744. const handleCurrentChangeDE = (val) => {
  745. currentPageDE.value = val;
  746. DETableData();
  747. console.log(`当前页: ${val}`);
  748. };
  749. // 深度探索排序事件
  750. const handleSortChangeDE = (sort) => {
  751. const { prop, order } = sort;
  752. if (!['created_at', 'expire_time'].includes(prop)) return;
  753. sortPropDE.value = prop; // 保存当前排序字段
  754. sortOrderDE.value = order; // 保存当前排序方式
  755. DETableData();
  756. };
  757. // 深度探索获取表格数据
  758. const DETableData = async () => {
  759. try {
  760. tableLoadingDE.value = true;
  761. const requestParams = {
  762. token: token,
  763. dccode: searchFormDE.dccode,
  764. dcname: searchFormDE.dcname,
  765. market: searchFormDE.market,
  766. user_role: searchFormDE.user_role,
  767. indicator_id: searchFormDE.indicator_id,
  768. sort_field: sortPropDE.value,
  769. sort_order: sortOrderDE.value,
  770. page: currentPageDE.value,
  771. page_size: pageSizeDE.value
  772. };
  773. const data = await userDEListApi(requestParams);
  774. tableDataDE.value = data.list
  775. datatotalDE.value = data.total
  776. } catch (error) {
  777. console.error('获取表格数据失败:', error);
  778. tableDataDE.value = [];
  779. datatotalDE.value = 0
  780. } finally {
  781. tableLoadingDE.value = false;
  782. }
  783. };
  784. // 深度探索搜索按钮
  785. const searchDE = () => {
  786. currentPageDE.value = 1;
  787. DETableData();
  788. };
  789. // 深度探索重置按钮
  790. const resetBnDE = () => {
  791. searchFormDE.dccode = '';
  792. searchFormDE.dcname = '';
  793. searchFormDE.market = '';
  794. searchFormDE.user_role = '';
  795. searchFormDE.indicator_id = '';
  796. sortPropDE.value = null;
  797. sortOrderDE.value = null;
  798. currentPageDE.value = 1;
  799. pageSizeDE.value = 10;
  800. DETableData();
  801. };
  802. // 深度探索导出Excel列表按钮
  803. const exportExcelDE = async () => {
  804. const requestParams = {
  805. token: token,
  806. dccode: searchFormDE.dccode,
  807. dcname: searchFormDE.dcname,
  808. market: searchFormDE.market,
  809. user_role: searchFormDE.user_role,
  810. indicator_id: searchFormDE.indicator_id,
  811. sort_field: sortPropDE.value,
  812. sort_order: sortOrderDE.value
  813. };
  814. const data = await exportDeepExploreApi(requestParams);
  815. if (data != '') {
  816. ElMessage.success('已导出');
  817. }
  818. };
  819. // 深度探索查看导出列表按钮
  820. const exportListDE = () => {
  821. router.push({
  822. path: "/userPermissions/export"
  823. });
  824. };
  825. // 深度探索开通权限按钮
  826. const enableAccessDE = () => {
  827. dialogVisibleDE.value = true;
  828. addOrUpdataDE.value = 1;
  829. };
  830. // 深度探索编辑按钮
  831. const handleEditDE = (row) => {
  832. dialogVisibleDE.value = true;
  833. hlidsInputDE.value = row.dccode;
  834. deadline.value = row.expire_time;
  835. indicator_id.value = row.indicator_id.split("、").filter(Boolean).map(Number);
  836. };
  837. // 深度探索权限详情按钮
  838. const handleDetailsDE = (dccode) =>{
  839. openDetail(dccode)
  840. }
  841. // 深度探索操作日志按钮
  842. const handleLogDE = (dccode) => {
  843. router.push({
  844. path: "/userPermissions/logDeepExplore",
  845. query: { dccode: dccode }
  846. });
  847. };
  848. // 弹框显隐控制
  849. const dialogVisibleDE = ref(false);
  850. // 开通权限表单
  851. const hlidsInputDE = ref('');
  852. const indicator_id = ref([]);
  853. const timeType = ref('');
  854. const expireTime = ref('');
  855. const delayValue = ref('');
  856. const delayUnit = ref('');
  857. const remark = ref('');
  858. const operator = ref('');
  859. // 判断开通还是编辑
  860. const addOrUpdataDE = ref(0);
  861. // 编辑回显内容
  862. const deadline = ref('');
  863. // 将数组转为顿号拼接的字符串
  864. const indicatorStr = computed(() => {
  865. return indicator_id.value?.join('、') || '';
  866. });
  867. // 禁用当前日期之前的日期(当天0点之前的时间)
  868. const disabledDate = (time) => {
  869. return time.getTime() < new Date().getTime() - 8.64e7;
  870. };
  871. // 格式化日期
  872. const formatDate = (date) => {
  873. if (!date) return '';
  874. const year = date.getFullYear();
  875. const month = String(date.getMonth() + 1).padStart(2, '0');
  876. const day = String(date.getDate()).padStart(2, '0');
  877. return `${year}/${month}/${day}`;
  878. };
  879. // 禁止为小数
  880. const handleDelayInput = () => {
  881. if (delayValue.value !== null && delayValue.value !== undefined) {
  882. delayValue.value = Math.floor(delayValue.value);
  883. }
  884. };
  885. // 校验HLid
  886. const checkHlidsDE = () => {
  887. // 非空
  888. if (!hlidsInputDE.value.trim()) {
  889. ElMessage.error('请输入HLid');
  890. return false;
  891. }
  892. // 处理输入:去空、去重,转数组
  893. const hlidList = hlidsInputDE.value.split('\n')
  894. .map(item => item.trim())
  895. .filter(item => item)
  896. .filter((item, index, self) => self.indexOf(item) === index); // 去重
  897. // 数量校验(最多1000个)
  898. if (hlidList.length > 1000) {
  899. ElMessage.error('HLid数量不能超过1000个');
  900. return false;
  901. }
  902. // 格式校验(8位数字)
  903. const hlidReg = /^\d{8}$/;
  904. for (const hlid of hlidList) {
  905. if (!hlidReg.test(hlid)) {
  906. ElMessage.error(`有HLid格式错误:${hlid},请重新输入`);
  907. return false;
  908. }
  909. }
  910. return true;
  911. };
  912. // 校验指标
  913. const checkmodel = () => {
  914. if (indicator_id.value.length === 0) {
  915. ElMessage.error('请至少选择一个指标');
  916. return false;
  917. }
  918. return true;
  919. };
  920. // 校验时间
  921. const checkTime = () => {
  922. if (timeType.value === 'expire') {
  923. // 到期时间
  924. if (!expireTime.value) {
  925. ElMessage.error('请选择到期时间');
  926. return false;
  927. }
  928. } else if (timeType.value === 'delay') {
  929. // 延期时间
  930. if (!delayValue.value || !delayUnit.value) {
  931. ElMessage.error('延期时间必须填写完整(数值+单位)');
  932. return false;
  933. }
  934. const delayNum = Number(delayValue.value);
  935. if (isNaN(delayNum) || delayNum < 0) {
  936. ElMessage.error('延期时间不能为负数,请输入有效正数');
  937. return false;
  938. }
  939. if (delayNum === 0) {
  940. ElMessage.error('延期时间不能为0,请输入有效正数');
  941. return false;
  942. }
  943. } else {
  944. ElMessage.error('请设置权限时间');
  945. return false;
  946. }
  947. return true;
  948. };
  949. // 校验备注
  950. const checkRemark = () => {
  951. if (!remark.value.trim()) {
  952. ElMessage.error('请输入备注');
  953. return false;
  954. }
  955. return true;
  956. };
  957. // 防抖
  958. const NoshakeDE = ref(false)
  959. // 提交表单
  960. const submitFormDE = async () => {
  961. // 防抖
  962. if (NoshakeDE.value) return;
  963. NoshakeDE.value = true;
  964. // 表单校验
  965. if (!checkHlidsDE() || !checkmodel() || !checkTime() || !checkRemark()) {
  966. NoshakeDE.value = false;
  967. return;
  968. }
  969. try {
  970. // 组装后端要求的参数格式
  971. const requestParams = {
  972. token: token,
  973. // HLid
  974. hlids: hlidsInputDE.value.split('\n')
  975. .map(item => item.trim())
  976. .filter(item => item)
  977. .join('\n'),
  978. // 指标
  979. indicator_id: indicatorStr.value,
  980. // 备注
  981. remark: remark.value.trim(),
  982. // 操作人
  983. ...(operator.value.trim() && { operator: operator.value.trim() }),
  984. // 时间参数
  985. ...(timeType.value === 'expire'
  986. ? { expire_time: formatDate(expireTime.value) }
  987. : {
  988. [delayUnit.value]: Number(delayValue.value)
  989. })
  990. };
  991. console.log('传给后端的参数:', requestParams);
  992. // 调用后端接口
  993. await exitDEApi(requestParams);
  994. ElMessage.success(addOrUpdataDM.value === 1 ? '成功添加用户权限' : '成功修改用户权限');
  995. // 重置表单并关闭弹框
  996. resetForm();
  997. dialogVisibleDE.value = false;
  998. addOrUpdataDE.value = 0;
  999. // 重新获取表格
  1000. DETableData();
  1001. } catch (error) {
  1002. ElMessage.error('添加权限失败,请重试');
  1003. } finally {
  1004. NoshakeDE.value = false;
  1005. }
  1006. };
  1007. // 深度探索重置表单数据
  1008. const resetForm = () => {
  1009. hlidsInputDE.value = '';
  1010. indicator_id.value = [];
  1011. timeType.value = '';
  1012. expireTime.value = '';
  1013. delayValue.value = '';
  1014. delayUnit.value = '';
  1015. remark.value = '';
  1016. operator.value = '';
  1017. };
  1018. // 深度探索取消表单
  1019. const cancelDE = () => {
  1020. resetForm();
  1021. dialogVisibleDE.value = false;
  1022. addOrUpdataDE.value = 0;
  1023. };
  1024. // 深度探索权限详情弹窗开关
  1025. const showPermissionDetail = ref(false);
  1026. // 深度探索权限详情弹窗数据
  1027. const HLid = ref('');
  1028. const permissionData = ref([]);
  1029. // 深度探索权限详情获取数据并开启弹窗
  1030. const openDetail = async(dccode) => {
  1031. HLid.value = dccode
  1032. // 后端调用
  1033. const res = await detailDEApi({
  1034. token: token,
  1035. dccode: dccode
  1036. })
  1037. permissionData.value = res
  1038. // 显示弹窗
  1039. showPermissionDetail.value = true;
  1040. };
  1041. </script>
  1042. <style scoped>
  1043. /* 父容器 */
  1044. .page-container {
  1045. position: relative;
  1046. min-height: 600px;
  1047. }
  1048. /* 搜索区域 */
  1049. .search-container {
  1050. display: flex;
  1051. height: 67px;
  1052. flex-direction: column;
  1053. justify-content: center;
  1054. align-items: flex-start;
  1055. gap: 10px;
  1056. align-self: stretch;
  1057. border-radius: 8px;
  1058. background: #FEFAF9;
  1059. box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
  1060. padding: 0 15px;
  1061. margin-bottom: 20px;
  1062. }
  1063. /* 搜索表单 */
  1064. .search-form {
  1065. display: flex;
  1066. align-items: center;
  1067. width: 100%;
  1068. gap: 15px;
  1069. flex-wrap: nowrap;
  1070. }
  1071. /* 单个搜索项 */
  1072. .search-item {
  1073. display: flex;
  1074. align-items: center;
  1075. gap: 6px;
  1076. }
  1077. /* 搜索标签文字 */
  1078. .form-label {
  1079. font-weight: 800 !important;
  1080. font-size: 15px;
  1081. text-align: left;
  1082. color: #333;
  1083. margin-top: 13px;
  1084. font-family: "SimHei", "Heiti SC", "Microsoft YaHei", sans-serif !important;
  1085. }
  1086. /* 按钮组 */
  1087. .button-group {
  1088. display: flex;
  1089. align-items: center;
  1090. gap: 0px !important;
  1091. margin-left: auto;
  1092. }
  1093. /* 按钮样式 */
  1094. .button-group .el-button {
  1095. padding: 6px 10px !important;
  1096. font-size: 14px !important;
  1097. height: 36px !important;
  1098. }
  1099. /* 表格样式 */
  1100. .table-rounded {
  1101. border-radius: 12px !important;
  1102. overflow: hidden !important;
  1103. border: 1px solid #e4e7ed !important;
  1104. min-height: 700px;
  1105. }
  1106. .table-roundedDE {
  1107. border-radius: 12px !important;
  1108. overflow: hidden !important;
  1109. border: 1px solid #e4e7ed !important;
  1110. min-height: 650px;
  1111. }
  1112. .table-header {
  1113. text-align: center !important;
  1114. font-weight: 800 !important;
  1115. font-size: 15px !important;
  1116. color: #333 !important;
  1117. background-color: #f8f9fa !important;
  1118. }
  1119. .el-table__cell {
  1120. border-right: none !important;
  1121. border-bottom: 1px solid #e4e7ed !important;
  1122. }
  1123. .el-table__header th.el-table__cell {
  1124. border-right: none !important;
  1125. border-bottom: 1px solid #e4e7ed !important;
  1126. }
  1127. .el-table__row:hover .el-table__cell {
  1128. background-color: #fafafa !important;
  1129. }
  1130. /* 分页组件样式 */
  1131. .demo-pagination-block {
  1132. display: flex;
  1133. width: 100%;
  1134. height: 44px;
  1135. padding: 0 16px;
  1136. align-items: center;
  1137. gap: 16px;
  1138. position: absolute;
  1139. margin-top: 10px;
  1140. border-radius: 0 0 3px 3px;
  1141. border-top: 1px solid #EAEAEA;
  1142. background: #FEFBFB;
  1143. box-sizing: border-box;
  1144. }
  1145. /* 添加/修改样式 */
  1146. .form-item {
  1147. margin-bottom: 24px;
  1148. padding-left: 20px;
  1149. padding-right: 20px;
  1150. text-align: left;
  1151. }
  1152. .form-label {
  1153. display: block;
  1154. margin-bottom: 8px;
  1155. font-weight: 500;
  1156. }
  1157. .radio-group {
  1158. display: flex;
  1159. flex-direction: column;
  1160. gap: 12px;
  1161. align-items: flex-start;
  1162. }
  1163. .radio-item {
  1164. display: flex;
  1165. align-items: center;
  1166. }
  1167. .radio-item span {
  1168. margin-right: 8px;
  1169. }
  1170. .tip {
  1171. font-size: 12px;
  1172. color: #909399;
  1173. margin-top: 4px;
  1174. }
  1175. .dialog-footer {
  1176. display: flex;
  1177. justify-content: flex-end;
  1178. gap: 16px;
  1179. margin-top: 20px;
  1180. }
  1181. .inline-form-item {
  1182. display: flex;
  1183. align-items: flex-start;
  1184. gap: 12px;
  1185. }
  1186. .inline-form-item .form-label {
  1187. display: inline-block;
  1188. margin-bottom: 0;
  1189. width: 60px;
  1190. text-align: left;
  1191. padding-right: 12px;
  1192. }
  1193. .info-container {
  1194. display: flex;
  1195. align-items: center;
  1196. gap: 40px;
  1197. padding: 0 20px 18px;
  1198. color: #333;
  1199. font-size: 16px;
  1200. }
  1201. .info-item {
  1202. white-space: nowrap;
  1203. }
  1204. /* Tab容器 */
  1205. .tab-group {
  1206. display: flex;
  1207. gap: 12px;
  1208. justify-content: flex-start;
  1209. margin: 4px 0;
  1210. padding-left: 2px;
  1211. }
  1212. /* Tab按钮 */
  1213. .tab-btn {
  1214. width: 120px;
  1215. padding: 6px 0;
  1216. text-align: center;
  1217. border: 1px solid #ff0000;
  1218. border-radius: 4px;
  1219. background-color: #ffffff;
  1220. color: #ff0000;
  1221. font-size: 14px;
  1222. cursor: pointer;
  1223. transition: all 0.2s;
  1224. outline: none;
  1225. }
  1226. /* Tab选中样式 */
  1227. .tab-btn.active {
  1228. background-color: #ff0000;
  1229. color: #ffffff;
  1230. }
  1231. /* hover效果优化 */
  1232. .tab-btn:not(.active):hover {
  1233. background-color: #fff5f5;
  1234. }
  1235. /* 搜索区域(深度探索) */
  1236. .search-containerDE {
  1237. display: flex;
  1238. height: auto;
  1239. flex-direction: column;
  1240. justify-content: center;
  1241. align-items: flex-start;
  1242. gap: 12px;
  1243. align-self: stretch;
  1244. border-radius: 8px;
  1245. background: #FEFAF9;
  1246. box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
  1247. padding: 15px;
  1248. margin-bottom: 20px;
  1249. }
  1250. /* 搜索表单(深度探索) */
  1251. .search-formDE {
  1252. display: flex;
  1253. align-items: center;
  1254. width: 100%;
  1255. gap: 15px;
  1256. flex-wrap: wrap;
  1257. row-gap: 8px;
  1258. }
  1259. /* 单个搜索项(深度探索) */
  1260. .search-itemDE {
  1261. display: flex;
  1262. align-items: center;
  1263. gap: 6px;
  1264. }
  1265. /* 搜索标签文字(深度探索) */
  1266. .form-labelDE {
  1267. font-weight: 800 !important;
  1268. font-size: 15px;
  1269. text-align: left;
  1270. color: #333;
  1271. margin-top: 0;
  1272. font-family: "SimHei", "Heiti SC", "Microsoft YaHei", sans-serif !important;
  1273. }
  1274. /* 按钮组(深度探索) */
  1275. .button-groupDE {
  1276. display: flex;
  1277. align-items: center;
  1278. gap: 10px !important;
  1279. }
  1280. /* 按钮样式(深度探索) */
  1281. .button-groupDE .el-button {
  1282. padding: 6px 10px !important;
  1283. font-size: 14px !important;
  1284. height: 36px !important;
  1285. }
  1286. /* 文本溢出省略样式(深度探索) */
  1287. .ellipsis-text {
  1288. display: inline-block;
  1289. width: 100%;
  1290. white-space: nowrap;
  1291. overflow: hidden;
  1292. text-overflow: ellipsis;
  1293. vertical-align: middle;
  1294. }
  1295. /* 开通/编辑指标复选(深度探索) */
  1296. .indicator-checkbox-group {
  1297. display: flex;
  1298. flex-wrap: wrap;
  1299. gap: 8px;
  1300. margin-top: 8px;
  1301. width: 100%
  1302. }
  1303. .indicator-checkbox-item {
  1304. flex: 0 0 calc(20% - 5px);
  1305. box-sizing: border-box;
  1306. padding: 4px 0;
  1307. }
  1308. :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
  1309. background-color: #ff0000 !important;
  1310. border-color: #ff0000 !important;
  1311. }
  1312. :deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
  1313. border-color: #fff !important;
  1314. }
  1315. :deep(.el-checkbox__input:hover .el-checkbox__inner) {
  1316. border-color: #ff0000 !important;
  1317. }
  1318. :deep(.el-checkbox__input:focus .el-checkbox__inner) {
  1319. box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.2) !important;
  1320. }
  1321. :deep(.el-checkbox__label) {
  1322. color: #333 !important;
  1323. font-size: 14px !important;
  1324. }
  1325. /* 权限详情(深度探索) */
  1326. .permission-detail-dialog {
  1327. --el-dialog-padding-primary: 15px;
  1328. }
  1329. .detail-container {
  1330. background-color: #fff1f0;
  1331. border-radius: 6px;
  1332. padding: 15px;
  1333. }
  1334. .hlid-item {
  1335. margin-bottom: 25px;
  1336. }
  1337. .label {
  1338. color: #666;
  1339. font-weight: 500;
  1340. }
  1341. .value {
  1342. color: #333;
  1343. }
  1344. .expire-section {
  1345. margin-top: 10px;
  1346. }
  1347. .section-title {
  1348. font-size: 16px;
  1349. color: #333;
  1350. margin-bottom: 10px;
  1351. font-weight: bold;
  1352. }
  1353. .indicator-list {
  1354. display: flex;
  1355. flex-direction: column;
  1356. gap: 8px;
  1357. }
  1358. .indicator-item {
  1359. padding: 6px 0;
  1360. display: flex;
  1361. align-items: center;
  1362. }
  1363. .indicator-name {
  1364. color: #666;
  1365. font-size: 14px;
  1366. }
  1367. .expire-time {
  1368. color: #333;
  1369. font-size: 14px;
  1370. margin-left: 8px;
  1371. }
  1372. .expired-tag {
  1373. background-color: #ffccd5;
  1374. color: #f56c6c;
  1375. font-size: 12px;
  1376. padding: 2px 8px;
  1377. border-radius: 4px;
  1378. margin-left: 8px;
  1379. }
  1380. </style>