kouxiangtang 0.0.25 → 0.0.27-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/package.json +11 -11
  2. package/src/demo/App.vue +10 -0
  3. package/src/demo/Layout.vue +9 -0
  4. package/src/demo/assets/logo.svg +15 -0
  5. package/src/demo/config.js +25 -0
  6. package/src/demo/domains/config.js +8 -0
  7. package/src/demo/domains/platform/post/config.js +10 -0
  8. package/src/demo/domains/platform/post/fields.js +16 -0
  9. package/src/demo/domains/platform/post/rest.js +43 -0
  10. package/src/demo/domains/platform/user/config.js +11 -0
  11. package/src/demo/domains/platform/user/fields.js +59 -0
  12. package/src/demo/domains/platform/user/rest.js +52 -0
  13. package/src/demo/main.js +10 -0
  14. package/src/demo/page/platform/Home.vue +12 -0
  15. package/src/demo/page/user/Home.vue +3 -0
  16. package/src/demo/route/rest-routes.js +24 -0
  17. package/src/demo/route/routes.js +30 -0
  18. package/src/demo/side-menus.js +25 -0
  19. package/src/framework/DataDetailHeader.vue +111 -0
  20. package/src/framework/DataDetailPage.vue +153 -0
  21. package/src/framework/DataGrid.vue +237 -0
  22. package/src/framework/DataList.vue +152 -0
  23. package/src/framework/DataTable.vue +49 -0
  24. package/src/framework/DataTableHeader.vue +121 -0
  25. package/src/framework/DataTablePage.vue +221 -0
  26. package/src/framework/DomainDescription.vue +166 -0
  27. package/src/framework/DomainEdit.vue +133 -0
  28. package/src/framework/DomainNew.vue +84 -0
  29. package/src/framework/FieldsWrapper.js +91 -0
  30. package/src/framework/PageActions.vue +56 -0
  31. package/src/framework/Pagination.vue +100 -0
  32. package/src/framework/RowActionFormEditor.vue +69 -0
  33. package/src/framework/RowActions.vue +62 -0
  34. package/src/framework/TooltipText.vue +50 -0
  35. package/src/framework/component/EmptyTip.vue +24 -0
  36. package/src/framework/config-loader.js +37 -0
  37. package/src/framework/dashboard/DashboardPage.vue +7 -0
  38. package/src/framework/dashboard/DashboardRow.vue +21 -0
  39. package/src/framework/define-domain.js +5 -0
  40. package/src/framework/domain-config.js +141 -0
  41. package/src/framework/field/attachment/AttachmentUploader.vue +259 -0
  42. package/src/framework/field/attachment/FieldDisplay.vue +102 -0
  43. package/src/framework/field/attachment/FieldEditor.vue +78 -0
  44. package/src/framework/field/attachment/config.js +14 -0
  45. package/src/framework/field/base-field-config.js +142 -0
  46. package/src/framework/field/belongs-to/FieldDisplay.vue +117 -0
  47. package/src/framework/field/belongs-to/FieldEditor.vue +77 -0
  48. package/src/framework/field/belongs-to/config.js +38 -0
  49. package/src/framework/field/boolean/FieldDisplay.vue +38 -0
  50. package/src/framework/field/boolean/FieldEditor.vue +34 -0
  51. package/src/framework/field/boolean/config.js +14 -0
  52. package/src/framework/field/cascader/FieldDisplay.vue +37 -0
  53. package/src/framework/field/cascader/FieldEditor.vue +36 -0
  54. package/src/framework/field/cascader/config.js +18 -0
  55. package/src/framework/field/component/config.js +12 -0
  56. package/src/framework/field/datetime/FieldDisplay.vue +34 -0
  57. package/src/framework/field/datetime/FieldEditor.vue +37 -0
  58. package/src/framework/field/datetime/config.js +22 -0
  59. package/src/framework/field/editor/FieldDisplay.vue +25 -0
  60. package/src/framework/field/editor/FieldEditor.vue +30 -0
  61. package/src/framework/field/editor/RichTextEditor.vue +307 -0
  62. package/src/framework/field/editor/config.js +13 -0
  63. package/src/framework/field/image/FieldDisplay.vue +87 -0
  64. package/src/framework/field/image/FieldEditor.vue +31 -0
  65. package/src/framework/field/image/ImageUploader.vue +143 -0
  66. package/src/framework/field/image/config.js +13 -0
  67. package/src/framework/field/images/FieldDisplay.vue +83 -0
  68. package/src/framework/field/images/FieldEditor.vue +68 -0
  69. package/src/framework/field/images/ImagesUploader.vue +163 -0
  70. package/src/framework/field/images/config.js +15 -0
  71. package/src/framework/field/json/FieldDisplay.vue +53 -0
  72. package/src/framework/field/json/FieldEditor.vue +40 -0
  73. package/src/framework/field/json/config.js +13 -0
  74. package/src/framework/field/row-id/FieldDisplay.vue +53 -0
  75. package/src/framework/field/row-id/config.js +16 -0
  76. package/src/framework/field/select/FieldDisplay.vue +91 -0
  77. package/src/framework/field/select/FieldEditor.vue +69 -0
  78. package/src/framework/field/select/config.js +18 -0
  79. package/src/framework/field/string/FieldDisplay.vue +32 -0
  80. package/src/framework/field/string/FieldEditor.vue +37 -0
  81. package/src/framework/field/string/config.js +13 -0
  82. package/src/framework/field/switch/FieldDisplay.vue +38 -0
  83. package/src/framework/field/switch/FieldEditor.vue +34 -0
  84. package/src/framework/field/switch/config.js +14 -0
  85. package/src/framework/field/text/FieldDisplay.vue +34 -0
  86. package/src/framework/field/text/FieldEditor.vue +38 -0
  87. package/src/framework/field/text/config.js +13 -0
  88. package/src/framework/field/tree-select/FieldDisplay.vue +90 -0
  89. package/src/framework/field/tree-select/FieldEditor.vue +69 -0
  90. package/src/framework/field/tree-select/config.js +18 -0
  91. package/src/framework/filters/FiltersEditor.vue +328 -0
  92. package/src/framework/filters/FiltersRange.vue +120 -0
  93. package/src/framework/filters/FiltersSorters.vue +152 -0
  94. package/src/framework/form/FormEditor.vue +82 -0
  95. package/src/framework/form/PureForm.vue +218 -0
  96. package/src/framework/index.js +24 -0
  97. package/src/framework/layout/BaseLayout.vue +75 -0
  98. package/src/framework/layout/HeaderMenu.vue +314 -0
  99. package/src/framework/layout/SideBar.vue +49 -0
  100. package/src/framework/layout/SideBarItem.vue +75 -0
  101. package/src/framework/page/Detail.vue +59 -0
  102. package/src/framework/page/DetailTab.vue +54 -0
  103. package/src/framework/page/List.vue +27 -0
  104. package/src/framework/rest-client.js +122 -0
  105. package/src/framework/statistic/Statistic.vue +73 -0
  106. package/src/framework/statistic/StatisticCard.vue +63 -0
  107. package/src/framework/statistic/Widget.vue +33 -0
  108. package/src/framework/store/data-table-store.js +28 -0
  109. package/src/framework/store/route-history-store.js +52 -0
  110. package/src/framework/store/table-filter-store.js +81 -0
  111. package/src/framework/style/main.css +25 -0
  112. package/src/framework/utils/alert.js +13 -0
  113. package/src/framework/utils/color.js +36 -0
  114. package/src/framework/utils/http.js +82 -0
  115. package/src/framework/utils/route-query.js +16 -0
  116. package/src/framework/utils/str.js +5 -0
  117. package/dist/AttachmentUploader-uUJ4Qk11.js +0 -209
  118. package/dist/AttachmentUploader-uUJ4Qk11.js.map +0 -1
  119. package/dist/ImageUploader-C3OKOyHr.js +0 -99
  120. package/dist/ImageUploader-C3OKOyHr.js.map +0 -1
  121. package/dist/RichTextEditor-D9N7Cft4.js +0 -1777
  122. package/dist/RichTextEditor-D9N7Cft4.js.map +0 -1
  123. package/dist/index-C0Dtld0k.js +0 -33478
  124. package/dist/index-C0Dtld0k.js.map +0 -1
  125. package/dist/kouxiangtang.js +0 -24
  126. package/dist/kouxiangtang.js.map +0 -1
  127. package/dist/kouxiangtang.umd.cjs +0 -87
  128. package/dist/kouxiangtang.umd.cjs.map +0 -1
  129. package/dist/style.css +0 -6
  130. package/dist/vite.svg +0 -1
package/package.json CHANGED
@@ -1,22 +1,23 @@
1
1
  {
2
2
  "name": "kouxiangtang",
3
3
  "private": false,
4
- "version": "0.0.25",
4
+ "version": "0.0.27-beta.0",
5
5
  "type": "module",
6
- "main": "./dist/kouxiangtang.umd.cjs",
7
- "module": "./dist/kouxiangtang.js",
6
+ "main": "./src/framework/index.js",
7
+ "module": "./src/framework/index.js",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/kouxiangtang.js",
11
- "require": "./dist/kouxiangtang.umd.cjs"
10
+ "import": "./src/framework/index.js"
12
11
  },
13
- "./dist/*.css": {
14
- "import": "./dist/*.css",
15
- "require": "./dist/*.css"
12
+ "./*": {
13
+ "import": "./src/framework/*"
14
+ },
15
+ "./*.css": {
16
+ "import": "./src/framework/style/*.css"
16
17
  }
17
18
  },
18
19
  "files": [
19
- "dist"
20
+ "src"
20
21
  ],
21
22
  "scripts": {
22
23
  "dev": "vite",
@@ -47,8 +48,7 @@
47
48
  "eslint-plugin-vue": "^9.19.2",
48
49
  "husky": "^9.0.11",
49
50
  "prettier": "3.1.1",
50
- "unplugin-auto-import": "^0.17.5",
51
- "unplugin-vue-components": "^0.26.0",
51
+ "unplugin-element-plus": "^0.8.0",
52
52
  "vite": "^5.2.10"
53
53
  }
54
54
  }
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <el-config-provider :locale="zhCN">
3
+ <router-view />
4
+ </el-config-provider>
5
+ </template>
6
+
7
+ <script setup>
8
+ import { ElConfigProvider } from "element-plus";
9
+ import zhCN from "element-plus/dist/locale/zh-cn.mjs";
10
+ </script>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <BaseLayout />
3
+ </template>
4
+
5
+ <script setup>
6
+ import { BaseLayout } from "kouxiangtang";
7
+ </script>
8
+
9
+ <style scoped lang="scss"></style>
@@ -0,0 +1,15 @@
1
+ <svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
3
+ <path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
4
+ <defs>
5
+ <linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
6
+ <stop stop-color="#41D1FF"/>
7
+ <stop offset="1" stop-color="#BD34FE"/>
8
+ </linearGradient>
9
+ <linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
10
+ <stop stop-color="#FFEA83"/>
11
+ <stop offset="0.0833333" stop-color="#FFDD35"/>
12
+ <stop offset="1" stop-color="#FFA800"/>
13
+ </linearGradient>
14
+ </defs>
15
+ </svg>
@@ -0,0 +1,25 @@
1
+ import { setupAppConfig, setupDomainConfigs } from "kouxiangtang";
2
+ import domainConfigs from "./domains/config";
3
+ import sideMenus from "./side-menus";
4
+
5
+ const baseUrl = import.meta.env.VITE_BASE_URL;
6
+ const imageUploadUrl = `${baseUrl}dict/upload/image`;
7
+ const fileUploadUrl = `${baseUrl}dict/upload/file`;
8
+
9
+ const config = {
10
+ name: "kouxiangtang",
11
+ logo: new URL("./assets/logo.svg", import.meta.url).href,
12
+ sideMenus,
13
+ baseUrl,
14
+ imageUploadUrl,
15
+ fileUploadUrl,
16
+ };
17
+
18
+ setupAppConfig(config);
19
+ setupDomainConfigs(domainConfigs, (side, domainCode) => {
20
+ return () => {
21
+ return import(`@/demo/domains/${side}/${domainCode}/config.js`);
22
+ };
23
+ });
24
+
25
+ export default config;
@@ -0,0 +1,8 @@
1
+ const domainConfigs = {
2
+ platform: {
3
+ user: { name: "用户" },
4
+ post: { name: "文章" },
5
+ },
6
+ };
7
+
8
+ export default domainConfigs;
@@ -0,0 +1,10 @@
1
+ import { defineDomain } from "kouxiangtang";
2
+ import fields from "./fields.js";
3
+ import rest from "./rest.js";
4
+
5
+ export default defineDomain("platform", {
6
+ name: "文章",
7
+ code: "post",
8
+ fields,
9
+ rest,
10
+ });
@@ -0,0 +1,16 @@
1
+ const fields = [
2
+ { code: "id", label: "ID", width: 100 },
3
+ {
4
+ code: "title",
5
+ label: "标题",
6
+ editable: true,
7
+ },
8
+ {
9
+ code: "images",
10
+ label: "图片",
11
+ type: "images",
12
+ editable: true,
13
+ },
14
+ ];
15
+
16
+ export default fields;
@@ -0,0 +1,43 @@
1
+ function mockPost(id) {
2
+ return {
3
+ id,
4
+ title: `title-${id}`,
5
+ images: [],
6
+ };
7
+ }
8
+
9
+ const posts = [mockPost(1), mockPost(2)];
10
+
11
+ function getList(data) {
12
+ return Promise.resolve({
13
+ data: {
14
+ count: posts.length,
15
+ rows: posts,
16
+ },
17
+ });
18
+ }
19
+
20
+ function getOne(data) {
21
+ const post = posts.find((post) => post.id === parseInt(data.id));
22
+ return Promise.resolve({ data: post });
23
+ }
24
+
25
+ function update(data) {
26
+ const post = posts.find((post) => post.id === parseInt(data.id));
27
+ Object.keys(post).forEach((key) => {
28
+ post[key] = data[key];
29
+ });
30
+ return Promise.resolve();
31
+ }
32
+
33
+ function create(data) {
34
+ posts.push(data);
35
+ return Promise.resolve();
36
+ }
37
+
38
+ export default {
39
+ getList,
40
+ getOne,
41
+ update,
42
+ create,
43
+ };
@@ -0,0 +1,11 @@
1
+ import { defineDomain } from "kouxiangtang";
2
+ import fields from "./fields.js";
3
+ import rest from "./rest.js";
4
+
5
+ export default defineDomain("platform", {
6
+ name: "用户",
7
+ code: "user",
8
+ hasMany: ["post"],
9
+ fields,
10
+ rest,
11
+ });
@@ -0,0 +1,59 @@
1
+ const fields = [
2
+ { code: "id", label: "ID", width: 100 },
3
+ {
4
+ code: "username",
5
+ label: "用户名",
6
+ editable: true,
7
+ type: "string",
8
+ },
9
+ {
10
+ code: "avatar",
11
+ label: "头像",
12
+ editable: true,
13
+ type: "image",
14
+ },
15
+ {
16
+ code: "enabled",
17
+ label: "启用",
18
+ editable: true,
19
+ type: "boolean",
20
+ },
21
+ {
22
+ code: "datetime",
23
+ label: "时间",
24
+ editable: true,
25
+ type: "datetime",
26
+ },
27
+ {
28
+ code: "description",
29
+ label: "说明",
30
+ editable: true,
31
+ type: "text",
32
+ },
33
+ {
34
+ code: "rules",
35
+ label: "规则",
36
+ editable: true,
37
+ type: "json",
38
+ },
39
+ {
40
+ code: "introduction",
41
+ label: "介绍",
42
+ editable: true,
43
+ type: "rich-text",
44
+ },
45
+ {
46
+ code: "type",
47
+ label: "类型",
48
+ editable: true,
49
+ type: "select",
50
+ options: () => {
51
+ return [
52
+ { code: "ok", name: "OK" },
53
+ { code: "no", name: "NO" },
54
+ ];
55
+ },
56
+ },
57
+ ];
58
+
59
+ export default fields;
@@ -0,0 +1,52 @@
1
+ function mockUser(id) {
2
+ return {
3
+ id,
4
+ username: `username-${id}`,
5
+ };
6
+ }
7
+
8
+ const users = [mockUser(1), mockUser(2)];
9
+
10
+ function getList(data) {
11
+ return Promise.resolve({
12
+ data: {
13
+ count: users.length,
14
+ rows: users,
15
+ },
16
+ });
17
+ }
18
+
19
+ function getOne(data) {
20
+ const user = users.find((user) => user.id === parseInt(data.id));
21
+ return Promise.resolve({ data: user });
22
+ }
23
+
24
+ function update(data) {
25
+ const user = users.find((user) => user.id === parseInt(data.id));
26
+ Object.keys(user).forEach((key) => {
27
+ user[key] = data[key];
28
+ });
29
+ return Promise.resolve();
30
+ }
31
+
32
+ function create(data) {
33
+ users.push(data);
34
+ return Promise.resolve();
35
+ }
36
+
37
+ function remove(data) {
38
+ const foundIndex = users.findIndex((item) => item.id === data.id);
39
+ if (foundIndex > -1) {
40
+ users.splice(foundIndex, 1);
41
+ }
42
+
43
+ return Promise.resolve();
44
+ }
45
+
46
+ export default {
47
+ getList,
48
+ getOne,
49
+ update,
50
+ create,
51
+ remove,
52
+ };
@@ -0,0 +1,10 @@
1
+ import { createApp } from "vue";
2
+ import { createPinia } from "pinia";
3
+ import App from "./App.vue";
4
+ import router from "./route/routes";
5
+ import "./config";
6
+
7
+ const app = createApp(App);
8
+ app.use(createPinia());
9
+ app.use(router);
10
+ app.mount("#app");
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <DashboardPage>
3
+ <DashboardRow>
4
+ <el-col :span="24"> platform home </el-col>
5
+ </DashboardRow>
6
+ </DashboardPage>
7
+ </template>
8
+
9
+ <script setup>
10
+ import { ElCol } from "element-plus";
11
+ import { DashboardPage, DashboardRow } from "kouxiangtang";
12
+ </script>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <div>user home</div>
3
+ </template>
@@ -0,0 +1,24 @@
1
+ import { List, Detail, DetailTab } from "kouxiangtang";
2
+
3
+ export default function create(side) {
4
+ return [
5
+ {
6
+ path: ":domain/list",
7
+ component: List,
8
+ meta: { side },
9
+ },
10
+ {
11
+ path: ":domain/:id",
12
+ component: Detail,
13
+ meta: { side },
14
+ children: [
15
+ {
16
+ name: `${side}-association`,
17
+ path: ":association",
18
+ component: DetailTab,
19
+ meta: { side },
20
+ },
21
+ ],
22
+ },
23
+ ];
24
+ }
@@ -0,0 +1,30 @@
1
+ import { createRouter, createWebHashHistory } from "vue-router";
2
+ import createRestRoutes from "./rest-routes";
3
+ import Layout from "../Layout.vue";
4
+
5
+ const routes = [
6
+ {
7
+ path: "/",
8
+ redirect: "/platform",
9
+ },
10
+ {
11
+ path: "/platform",
12
+ component: Layout,
13
+ redirect: "/platform/home",
14
+ children: [
15
+ {
16
+ path: "home",
17
+ component: () => import("@/demo/page/platform/Home.vue"),
18
+ },
19
+ ...createRestRoutes("platform"),
20
+ ],
21
+ },
22
+ ];
23
+
24
+ const router = createRouter({
25
+ history: createWebHashHistory(),
26
+ routes,
27
+ scrollBehavior: () => ({ left: 0, top: 0 }),
28
+ });
29
+
30
+ export default router;
@@ -0,0 +1,25 @@
1
+ const sideMenus = {
2
+ platform: [
3
+ {
4
+ title: "首页",
5
+ route: "/platform/home",
6
+ },
7
+ {
8
+ title: "用户",
9
+ children: [
10
+ { title: "用户", route: "/platform/user/list" },
11
+ { title: "用户1", route: "/platform/user/1" },
12
+ ],
13
+ },
14
+ {
15
+ title: "组织",
16
+ route: "/platform/orgs",
17
+ },
18
+ {
19
+ title: "文章",
20
+ children: [{ title: "文章", route: "/platform/post/list" }],
21
+ },
22
+ ],
23
+ };
24
+
25
+ export default sideMenus;
@@ -0,0 +1,111 @@
1
+ <template>
2
+ <div class="data-page-title">
3
+ <div class="title">
4
+ {{ pageTitle }}
5
+ </div>
6
+
7
+ <div>
8
+ <el-button-group v-if="props.data">
9
+ <RowActions
10
+ :row="props.data"
11
+ :settings="props.settings"
12
+ :query="routeQuery"
13
+ />
14
+ <DomainEdit
15
+ :row="props.data"
16
+ :settings="props.settings"
17
+ :operations="operations"
18
+ :is-detail="true"
19
+ />
20
+ </el-button-group>
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup>
26
+ import { ElButtonGroup } from "element-plus";
27
+ import { ref, watch } from "vue";
28
+ import { useRoute } from "vue-router";
29
+ import { useRouteHistoryStore } from "./store/route-history-store";
30
+ import DomainEdit from "./DomainEdit.vue";
31
+ import RowActions from "./RowActions.vue";
32
+ import { setupRouteQuery } from "./utils/route-query";
33
+ import { snakeToCamel } from "./utils/str.js";
34
+
35
+ const emit = defineEmits(["get-item"]);
36
+ const routeHistoryStore = useRouteHistoryStore();
37
+ const route = useRoute();
38
+ const routeQuery = setupRouteQuery(route);
39
+ const props = defineProps({
40
+ data: {
41
+ type: Object,
42
+ },
43
+ settings: {
44
+ type: Object,
45
+ },
46
+ query: {
47
+ type: Object,
48
+ },
49
+ });
50
+ const pageTitle = ref("加载中...");
51
+ const operations = ref({});
52
+
53
+ function updateHeaderTab(domain, id) {
54
+ const code = `${domain}-${id}`;
55
+ routeHistoryStore.updateTitleByCode(code, pageTitle.value);
56
+ document.title = pageTitle.value;
57
+ }
58
+
59
+ function setupTitle(newData) {
60
+ const config = props.settings;
61
+ const prefix = config.name;
62
+ const domainName =
63
+ newData[snakeToCamel(`${config.code}-name`)] ||
64
+ newData[config.code + "name"];
65
+ if (domainName) {
66
+ pageTitle.value = `${prefix}: ${domainName}`;
67
+ } else if (newData.name) {
68
+ pageTitle.value = `${prefix}: ${newData.name}(#${newData.id})`;
69
+ } else {
70
+ pageTitle.value = `${prefix}: ${newData.id}`;
71
+ }
72
+ updateHeaderTab(config.code, newData.id);
73
+ }
74
+
75
+ watch(
76
+ () => props.data,
77
+ (newData) => {
78
+ if (!newData) {
79
+ return;
80
+ }
81
+
82
+ operations.value = props.settings.operations(routeQuery);
83
+ setupTitle(newData);
84
+ },
85
+ { immediate: true },
86
+ );
87
+ </script>
88
+
89
+ <style scoped lang="scss">
90
+ .data-page-title {
91
+ display: flex;
92
+ justify-content: space-between;
93
+ padding: 20px 0 10px;
94
+ border-bottom: 2px solid #ddd;
95
+ margin-bottom: 10px;
96
+
97
+ .title {
98
+ line-height: 32px;
99
+ font-size: 18px;
100
+ color: #000;
101
+ }
102
+ }
103
+
104
+ .button-groups {
105
+ margin-right: 10px;
106
+
107
+ &:last-child {
108
+ margin-right: 0;
109
+ }
110
+ }
111
+ </style>
@@ -0,0 +1,153 @@
1
+ <template>
2
+ <div :key="`${route.params.domain}-${route.params.id}`">
3
+ <DataDetailHeader
4
+ :data="domainData"
5
+ :settings="props.settings"
6
+ :query="{ ...route.params, ...routeQuery }"
7
+ />
8
+
9
+ <DomainDescription
10
+ :query="{ ...route.params, ...routeQuery }"
11
+ :settings="props.settings"
12
+ @done="domainDataLoaded"
13
+ />
14
+
15
+ <el-tabs
16
+ v-model="activeTab"
17
+ class="association-tabs"
18
+ @tab-click="associationSelect"
19
+ >
20
+ <el-tab-pane
21
+ v-for="association in props.associations"
22
+ :label="association.name"
23
+ :name="association.domain"
24
+ />
25
+ </el-tabs>
26
+
27
+ <router-view
28
+ v-if="activeAssociation"
29
+ :key="activeAssociation"
30
+ :association="activeAssociation"
31
+ />
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { ElTabs, ElTabPane } from "element-plus";
37
+ import DomainDescription from "./DomainDescription.vue";
38
+ import DataDetailHeader from "./DataDetailHeader.vue";
39
+ import { useRoute, useRouter } from "vue-router";
40
+ import { setupRouteQuery } from "./utils/route-query";
41
+ import { useDataTableStore } from "./store/data-table-store";
42
+ import { storeToRefs } from "pinia";
43
+ import { watch, shallowRef, ref } from "vue";
44
+
45
+ const router = useRouter();
46
+ const route = useRoute();
47
+ const routeQuery = setupRouteQuery(route);
48
+ const props = defineProps({
49
+ settings: {
50
+ type: Object,
51
+ },
52
+ associations: {
53
+ type: Array,
54
+ },
55
+ });
56
+ const fields = shallowRef([]);
57
+ const activeTab = ref("");
58
+ const activeAssociation = shallowRef(null);
59
+ const domainData = ref(null);
60
+ const dataTableStore = useDataTableStore();
61
+ const { removed } = storeToRefs(dataTableStore);
62
+
63
+ fields.value = props.settings.fields.filter((field) => {
64
+ return field.showInDetail !== false;
65
+ });
66
+
67
+ function domainDataLoaded(data) {
68
+ domainData.value = data;
69
+ }
70
+
71
+ function associationSelect(tab) {
72
+ activeTab.value = tab.props.name;
73
+
74
+ router.push({
75
+ name: `${route.meta.side}-association`,
76
+ params: { association: tab.props.name },
77
+ query: routeQuery,
78
+ });
79
+ }
80
+
81
+ function setupActiveTab(callback) {
82
+ if (props.associations.length > 0) {
83
+ if (route.params.association) {
84
+ activeTab.value = route.params.association;
85
+ const association = props.associations.find((item) => {
86
+ return item.domain === route.params.association;
87
+ });
88
+ activeAssociation.value = {
89
+ ...association,
90
+ domainData: domainData.value,
91
+ };
92
+ } else {
93
+ activeTab.value = props.associations[0].domain;
94
+ activeAssociation.value = {
95
+ ...props.associations[0],
96
+ domainData: domainData.value,
97
+ };
98
+
99
+ if (callback) {
100
+ callback();
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ watch(
107
+ domainData,
108
+ () => {
109
+ setupActiveTab(() => {
110
+ router.replace({
111
+ name: `${route.meta.side}-association`,
112
+ params: { association: activeTab.value },
113
+ query: routeQuery,
114
+ });
115
+ });
116
+ },
117
+ { immediate: true },
118
+ );
119
+
120
+ watch(
121
+ () => route.params,
122
+ () => {
123
+ if (!domainData.value) {
124
+ return;
125
+ }
126
+
127
+ setupActiveTab();
128
+ },
129
+ { immediate: true },
130
+ );
131
+ </script>
132
+
133
+ <style scoped lang="scss">
134
+ .descriptions {
135
+ margin-bottom: 15px;
136
+ }
137
+
138
+ .association-tabs {
139
+ :deep(.el-tabs__header) {
140
+ margin: 0;
141
+ }
142
+ }
143
+
144
+ .detail-image {
145
+ width: 100px;
146
+ }
147
+
148
+ .image-slot {
149
+ width: 100%;
150
+ height: 100%;
151
+ background: var(--el-fill-color-light);
152
+ }
153
+ </style>