AppConfigManage.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <template>
  2. <div class="app-config-manage">
  3. <el-dialog
  4. class="page-dialog config-manage-dialog"
  5. :visible.sync="modalIsShow"
  6. :close-on-click-modal="false"
  7. :close-on-press-escape="false"
  8. append-to-body
  9. fullscreen
  10. @opened="visibleChange"
  11. >
  12. <div class="part-box part-box-filter" slot="title">
  13. <el-form
  14. v-if="modalIsShow"
  15. ref="FilterForm"
  16. label-position="left"
  17. label-width="80px"
  18. :model="filter"
  19. :rules="rules"
  20. inline
  21. >
  22. <el-form-item>
  23. <h2 class="dialog-title">应用配置管理-{{ app.name }}</h2>
  24. </el-form-item>
  25. <el-form-item prop="versionId" label="版本">
  26. <version-select
  27. ref="VersionSelect"
  28. v-model="filter.versionId"
  29. :app-id="filter.appId"
  30. :clearable="false"
  31. manual-fetch
  32. select-default
  33. style="width: 140px"
  34. ></version-select>
  35. </el-form-item>
  36. <el-form-item prop="moduleId" label="模块">
  37. <module-select
  38. ref="ModuleSelect"
  39. v-model="filter.moduleId"
  40. :app-id="filter.appId"
  41. :clearable="false"
  42. manual-fetch
  43. select-default
  44. ></module-select>
  45. </el-form-item>
  46. <el-form-item label-width="0px">
  47. <el-button type="primary" icon="ios-search" @click="search"
  48. >查询</el-button
  49. >
  50. <el-button
  51. v-if="checkPrivilege('PROPERTY_BASELINE_EDIT', app.id)"
  52. type="success"
  53. icon="md-add"
  54. @click="toUpdate"
  55. >配置基线修改</el-button
  56. >
  57. </el-form-item>
  58. </el-form>
  59. </div>
  60. <div class="config-menu box-justify">
  61. <div class="tab-btns tab-nowrap">
  62. <el-button
  63. v-for="env in envList"
  64. :key="env.id"
  65. size="mini"
  66. :type="filter.envId == env.id ? 'primary' : 'default'"
  67. @click="selectEnv(env)"
  68. >{{ env.name }}
  69. </el-button>
  70. </div>
  71. <div class="menu-set" v-if="!IS_BASELINE">
  72. <el-checkbox v-model="hideReadonly">隐藏只读配置</el-checkbox>
  73. </div>
  74. </div>
  75. <div class="config-body">
  76. <div class="part-box part-box-pad config-guide">
  77. <ul>
  78. <li
  79. class="box-justify"
  80. v-for="group in groupConfigList"
  81. :key="group.name"
  82. @click="scrollTo(group)"
  83. >
  84. <p>{{ group.name | groupNameFilter }}</p>
  85. <span v-if="group.children.length" class="group-count">{{
  86. group.children.length
  87. }}</span>
  88. </li>
  89. </ul>
  90. </div>
  91. <div ref="ConfigTableBody" class="part-box part-box-pad config-content">
  92. <div
  93. v-for="group in groupConfigList"
  94. :key="group.name"
  95. :class="[
  96. 'config-content-part',
  97. {
  98. 'config-content-none': !group.children.length,
  99. 'config-content-hideread': hideReadonly && !IS_BASELINE,
  100. },
  101. ]"
  102. :id="group.name"
  103. >
  104. <div class="config-content-title">
  105. <h3>
  106. {{ group.name | groupNameFilter }}
  107. </h3>
  108. <el-button
  109. v-if="checkAddPrivilege(group)"
  110. class="btn-success"
  111. type="text"
  112. @click="toAddGroupConfigItem(group)"
  113. >新增</el-button
  114. >
  115. </div>
  116. <div class="config-content-list">
  117. <div
  118. v-for="item in group.children"
  119. :key="item.key"
  120. :class="[
  121. 'config-content-item',
  122. `is-${item.mode.toLowerCase()}`,
  123. {
  124. 'is-danger':
  125. item.mode === 'OVERRIDE' &&
  126. isEmpty(item.value) &&
  127. !IS_BASELINE,
  128. },
  129. ]"
  130. >
  131. <div v-if="item.comment" class="config-content-comment">
  132. {{ item.comment }}
  133. </div>
  134. <div class="config-content-cont">
  135. <span class="cont-mode">[{{ modeMap[item.mode] }}]</span>
  136. <span>{{ item.key }}</span>
  137. <span>=</span>
  138. <span>{{ item.value }}</span>
  139. <el-button
  140. v-if="checkEditPrivilege(item)"
  141. class="btn-primary cont-edit"
  142. type="text"
  143. @click="toEdit(item)"
  144. >编辑</el-button
  145. >
  146. <el-button
  147. v-if="
  148. checkEditPrivilege(item) &&
  149. !item.isFromBaseline &&
  150. group.name !== 'custom'
  151. "
  152. class="btn-danger cont-edit"
  153. type="text"
  154. @click="toDelete(item)"
  155. >删除</el-button
  156. >
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. </div>
  162. </div>
  163. </el-dialog>
  164. <!-- UpdateAppBaseline -->
  165. <update-app-baseline
  166. v-if="checkPrivilege('PROPERTY_BASELINE_EDIT', app.id)"
  167. ref="UpdateAppBaseline"
  168. :data="filter"
  169. @modified="baselineModified"
  170. ></update-app-baseline>
  171. <!-- ModifyAppBaselineItem -->
  172. <modify-app-baseline-item
  173. ref="ModifyAppBaselineItem"
  174. :instance="curBaselineItem"
  175. :modes="configModes"
  176. @modified="baselineItemModified"
  177. ></modify-app-baseline-item>
  178. <!-- ModifyAppConfigItem -->
  179. <modify-app-config-item
  180. ref="ModifyAppConfigItem"
  181. :instance="curConfigItem"
  182. :group="curGroupForAdd"
  183. @modified="resetSearch"
  184. ></modify-app-config-item>
  185. </div>
  186. </template>
  187. <script>
  188. import {
  189. appConfigBaseline,
  190. appConfigModes,
  191. appConfigGroups,
  192. appEnvList,
  193. appConfigList,
  194. appConfigItemDelete,
  195. } from "../api";
  196. import UpdateAppBaseline from "./UpdateAppBaseline.vue";
  197. import ModifyAppBaselineItem from "./ModifyAppBaselineItem.vue";
  198. import ModifyAppConfigItem from "./ModifyAppConfigItem.vue";
  199. import { deepCopy } from "@/plugins/utils";
  200. export default {
  201. name: "app-config-manage",
  202. components: { UpdateAppBaseline, ModifyAppBaselineItem, ModifyAppConfigItem },
  203. props: {
  204. app: {
  205. type: Object,
  206. default() {
  207. return {};
  208. },
  209. },
  210. },
  211. filters: {
  212. groupNameFilter(val) {
  213. return val === "custom" ? "应用配置自定义" : val;
  214. },
  215. },
  216. data() {
  217. return {
  218. modalIsShow: false,
  219. filter: {
  220. appId: "",
  221. moduleId: null,
  222. versionId: null,
  223. envId: null,
  224. },
  225. searchFilter: {},
  226. rules: {
  227. moduleId: [
  228. {
  229. required: true,
  230. type: "number",
  231. message: "请选择模块",
  232. triggr: "change",
  233. },
  234. ],
  235. versionId: [
  236. {
  237. required: true,
  238. type: "number",
  239. message: "请选择版本",
  240. triggr: "change",
  241. },
  242. ],
  243. },
  244. configList: [],
  245. curConfigItem: {},
  246. baselineCaches: {},
  247. baselineList: [],
  248. curBaselineItem: {},
  249. configModes: [],
  250. modeMap: {},
  251. envList: [],
  252. curEnv: {},
  253. configGroups: [],
  254. curGroupForAdd: null,
  255. groupConfigList: [],
  256. hideReadonly: true,
  257. user: {},
  258. };
  259. },
  260. computed: {
  261. IS_BASELINE() {
  262. return this.filter.envId === null;
  263. },
  264. },
  265. created() {
  266. this.user = this.$ls.get("user", {});
  267. },
  268. methods: {
  269. isEmpty(val) {
  270. return val === null || val === "" || val === undefined;
  271. },
  272. async visibleChange() {
  273. await this.getConfigModes();
  274. await this.appConfigGroups();
  275. if (!this.filter.appId !== this.app.id) this.resetData();
  276. this.filter.appId = this.app.id;
  277. this.$nextTick(async () => {
  278. await this.$refs.VersionSelect.search();
  279. await this.$refs.ModuleSelect.search();
  280. await this.getEnvList();
  281. this.selectEnv(this.envList[0]);
  282. });
  283. },
  284. resetData() {
  285. this.filter = {
  286. appId: "",
  287. moduleId: null,
  288. versionId: null,
  289. envId: null,
  290. };
  291. this.searchFilter = {};
  292. this.configList = [];
  293. this.curConfigItem = {};
  294. this.baselineCaches = {};
  295. this.baselineList = [];
  296. this.curBaselineItem = {};
  297. this.envList = [];
  298. this.groupConfigList = [];
  299. },
  300. checkAddPrivilege(group) {
  301. const privilege = this.checkPrivilege("PROPERTY_EDIT", this.filter.envId);
  302. return group.name !== "custom" && !this.IS_BASELINE && privilege;
  303. },
  304. checkEditPrivilege(item) {
  305. const privilege = this.checkPrivilege("PROPERTY_EDIT", this.filter.envId);
  306. return (
  307. (item.mode !== "READONLY" && privilege) ||
  308. (this.checkPrivilege("PROPERTY_BASELINE_EDIT", this.app.id) &&
  309. this.IS_BASELINE)
  310. );
  311. },
  312. cancel() {
  313. this.modalIsShow = false;
  314. },
  315. open() {
  316. this.modalIsShow = true;
  317. },
  318. async getEnvList() {
  319. const res = await appEnvList({
  320. appId: this.app.id,
  321. });
  322. this.envList = res || [];
  323. this.envList.unshift({ id: null, name: "基线" });
  324. },
  325. async getConfigModes() {
  326. if (Object.keys(this.modeMap).length) return;
  327. const data = await appConfigModes();
  328. this.configModes = data;
  329. let modeMap = {};
  330. data.forEach(({ code, name }) => {
  331. modeMap[code] = name;
  332. });
  333. this.modeMap = modeMap;
  334. },
  335. async appConfigGroups() {
  336. if (this.configGroups.length) return;
  337. const data = await appConfigGroups();
  338. this.configGroups = data || [];
  339. },
  340. selectEnv(env) {
  341. this.curEnv = env;
  342. this.filter.envId = env.id;
  343. this.search();
  344. },
  345. async search() {
  346. const valid = await this.$refs.FilterForm.validate().catch(() => {});
  347. if (!valid) return;
  348. this.searchFilter = { ...this.filter };
  349. await this.resetSearch();
  350. this.$refs.ConfigTableBody.scrollTop = 0;
  351. },
  352. async resetSearch() {
  353. if (this.searchFilter.envId) {
  354. await this.getConfigList();
  355. this.buildGroupConfigList(this.configList);
  356. } else {
  357. await this.getBaselineList();
  358. this.buildGroupConfigList(this.baselineList);
  359. }
  360. },
  361. getCacheBaselineList() {
  362. const k = [
  363. this.searchFilter.appId,
  364. this.searchFilter.moduleId,
  365. this.searchFilter.versionId,
  366. ].join("_");
  367. return this.baselineCaches[k] || [];
  368. },
  369. setCacheBaselineList(baseline) {
  370. const k = [
  371. this.searchFilter.appId,
  372. this.searchFilter.moduleId,
  373. this.searchFilter.versionId,
  374. ].join("_");
  375. this.baselineCaches[k] = baseline;
  376. },
  377. async getBaselineList() {
  378. const cacheBaseline = this.getCacheBaselineList();
  379. if (cacheBaseline.length) {
  380. this.baselineList = cacheBaseline;
  381. return;
  382. }
  383. let data = await appConfigBaseline(this.searchFilter);
  384. data.forEach((item) => {
  385. item.isFromBaseline = true;
  386. });
  387. this.setCacheBaselineList(data);
  388. this.baselineList = data;
  389. },
  390. async getConfigList() {
  391. await this.getBaselineList();
  392. const data = await appConfigList(this.searchFilter);
  393. let configList = this.baselineList.map((item) => {
  394. let nitem = { ...item, isFromBaseline: true };
  395. if (item.mode === "OVERRIDE") nitem.value = null;
  396. nitem.refVal = item.value;
  397. return nitem;
  398. });
  399. // 只有配置项位于group分组内,不是应用自定义,且基线不包含此配置项时才能删除
  400. data.forEach((item) => {
  401. let bIndex = configList.findIndex((citem) => citem.key === item.key);
  402. if (bIndex !== -1) {
  403. configList[bIndex].value = item.value;
  404. } else {
  405. configList.push({ ...item, isFromBaseline: false });
  406. }
  407. });
  408. this.configList = configList;
  409. },
  410. buildGroupConfigList(dataList) {
  411. let groupConfigList = deepCopy(this.configGroups);
  412. groupConfigList.forEach((item) => {
  413. item.children = [];
  414. });
  415. const defaultAvailable = {
  416. type: "string",
  417. };
  418. dataList.forEach((configItem) => {
  419. const info = this.getConfigItemGroupPosInfo(configItem.key);
  420. if (info) {
  421. let group = groupConfigList.find(
  422. (gitem) => gitem.name === info.group.name
  423. );
  424. group.children.push({
  425. ...configItem,
  426. available: info.available || { ...defaultAvailable },
  427. });
  428. return;
  429. }
  430. let group = groupConfigList.find((gitem) => gitem.name === "custom");
  431. if (!group) {
  432. group = {
  433. name: "custom",
  434. prefix: "",
  435. children: [],
  436. };
  437. groupConfigList.unshift(group);
  438. }
  439. group.children.push({
  440. ...configItem,
  441. available: { ...defaultAvailable },
  442. });
  443. });
  444. // groupConfigList.sort((a, b) => b.children.length - a.children.length);
  445. this.groupConfigList = groupConfigList;
  446. },
  447. getConfigItemGroupPosInfo(configItemKey) {
  448. const curGroup = this.configGroups.find((group) =>
  449. configItemKey.startsWith(group.prefix + ".")
  450. );
  451. if (!curGroup) return;
  452. let groupData = {
  453. name: curGroup.name,
  454. prefix: curGroup.prefix,
  455. };
  456. for (let i = 0; i < curGroup.available.length; i++) {
  457. const avai = { ...curGroup.available[i] };
  458. if (avai.key.includes(".*.")) {
  459. const regCont = avai.key.split(".").join("\\.").replace("*", ".*");
  460. const reg = new RegExp(`^${regCont}$`);
  461. if (reg.test(configItemKey)) {
  462. return {
  463. group: groupData,
  464. available: avai,
  465. };
  466. }
  467. continue;
  468. }
  469. if (avai.key === configItemKey) {
  470. return {
  471. group: groupData,
  472. available: avai,
  473. };
  474. }
  475. }
  476. return {
  477. group: groupData,
  478. available: null,
  479. };
  480. },
  481. async toUpdate() {
  482. const valid = await this.$refs.FilterForm.validate().catch(() => {});
  483. if (!valid) return;
  484. this.$refs.UpdateAppBaseline.open();
  485. },
  486. toAddGroupConfigItem(group) {
  487. const ginfo = this.configGroups.find((item) => item.name === group.name);
  488. if (!ginfo) {
  489. this.$message.error("当前分组不存在!");
  490. return;
  491. }
  492. const unvalidKeys = this.configList.map((item) => item.key);
  493. this.curGroupForAdd = { ...ginfo, unvalidKeys };
  494. const groupUnvalidKeys = group.children.map((item) => item.key);
  495. this.curGroupForAdd.available = this.curGroupForAdd.available.filter(
  496. (item) => !groupUnvalidKeys.includes(item.key)
  497. );
  498. if (!this.curGroupForAdd.available.length && ginfo.available.length) {
  499. this.$message.error("当前分组不可添加配置项!");
  500. return;
  501. }
  502. this.curConfigItem = { ...this.filter, available: { type: "string" } };
  503. this.$refs.ModifyAppConfigItem.open();
  504. },
  505. toEdit(row) {
  506. if (this.filter.envId) {
  507. this.toEditConfigItem(row);
  508. } else {
  509. this.toEditBaseline(row);
  510. }
  511. },
  512. toEditBaseline(row) {
  513. this.curBaselineItem = { ...row, ...this.filter };
  514. this.$refs.ModifyAppBaselineItem.open();
  515. },
  516. toEditConfigItem(row) {
  517. this.curConfigItem = { ...row, ...this.filter };
  518. this.$refs.ModifyAppConfigItem.open();
  519. },
  520. baselineItemModified() {
  521. this.setCacheBaselineList([]);
  522. this.resetSearch();
  523. },
  524. baselineModified(data) {
  525. this.setCacheBaselineList(data);
  526. this.resetSearch();
  527. },
  528. async toDelete(item) {
  529. const confirm = await this.$confirm(
  530. `确定要删除配置项【${item.key}】吗?`,
  531. "提示",
  532. {
  533. type: "warning",
  534. }
  535. ).catch(() => {});
  536. if (confirm !== "confirm") return;
  537. const res = await appConfigItemDelete({
  538. ...this.filter,
  539. key: item.key,
  540. }).catch(() => {});
  541. if (!res) return;
  542. this.$message.success("删除成功!");
  543. this.resetSearch();
  544. },
  545. scrollTo(group) {
  546. this.$refs.ConfigTableBody.scrollTop =
  547. document.getElementById(group.name).offsetTop - 20;
  548. },
  549. },
  550. };
  551. </script>