@liujitcn/kratos-admin-core 0.0.31 → 0.0.33

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 (110) hide show
  1. package/README.md +10 -4
  2. package/dist/package/declarations/src/api/base/{config.d.ts → v1/config.d.ts} +1 -1
  3. package/dist/package/declarations/src/api/base/{file.d.ts → v1/file.d.ts} +3 -1
  4. package/dist/package/declarations/src/api/base/{language.d.ts → v1/language.d.ts} +1 -1
  5. package/dist/package/declarations/src/api/base/{login.d.ts → v1/login.d.ts} +2 -2
  6. package/dist/package/declarations/src/api/base/{mfa.d.ts → v1/mfa.d.ts} +3 -3
  7. package/dist/package/declarations/src/api/base/{oauth.d.ts → v1/oauth.d.ts} +1 -1
  8. package/dist/package/declarations/src/api/system/{auth.d.ts → admin/v1/auth.d.ts} +2 -2
  9. package/dist/package/declarations/src/api/system/{base_dict.d.ts → admin/v1/base_dict.d.ts} +1 -1
  10. package/dist/package/declarations/src/api/system/{base_tenant.d.ts → admin/v1/base_tenant.d.ts} +2 -2
  11. package/dist/package/declarations/src/components/ProTable/index.vue.d.ts +114 -0
  12. package/dist/package/declarations/src/components/TreeFilter/index.vue.d.ts +84 -76
  13. package/dist/package/declarations/src/components/Upload/File.vue.d.ts +1 -1
  14. package/dist/package/declarations/src/components/Upload/Files.vue.d.ts +1 -1
  15. package/dist/package/declarations/src/components/Upload/Img.vue.d.ts +1 -1
  16. package/dist/package/declarations/src/components/Upload/Imgs.vue.d.ts +1 -1
  17. package/dist/package/declarations/src/components/WangEditor/index.vue.d.ts +2 -0
  18. package/dist/package/declarations/src/documentTitle.d.ts +2 -0
  19. package/dist/package/declarations/src/format.d.ts +1 -1
  20. package/dist/package/declarations/src/index.d.ts +2 -1
  21. package/dist/package/declarations/src/locales/generated.d.ts +68 -0
  22. package/dist/package/declarations/src/modules/index.d.ts +1 -3
  23. package/dist/package/declarations/src/request.d.ts +1 -1
  24. package/dist/package/declarations/src/routers/index.d.ts +1 -1
  25. package/dist/package/declarations/src/rpc/base/v1/login.d.ts +5 -3
  26. package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +13 -0
  27. package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +0 -101
  28. package/dist/package/declarations/src/rpc/system/common/v1/common.d.ts +26 -0
  29. package/dist/package/declarations/src/stores/interface/index.d.ts +6 -1
  30. package/dist/package/declarations/src/stores/modules/user.d.ts +2 -4
  31. package/dist/package/declarations/src/utils/passwordStrength.d.ts +1 -12
  32. package/dist/package/declarations/src/utils/request.d.ts +13 -2
  33. package/dist/package/declarations/src/utils/utils.d.ts +4 -0
  34. package/dist/package/src/api/base/{config.ts → v1/config.ts} +2 -2
  35. package/dist/package/src/api/base/{file.ts → v1/file.ts} +20 -24
  36. package/dist/package/src/api/base/{language.ts → v1/language.ts} +2 -2
  37. package/dist/package/src/api/base/{login.ts → v1/login.ts} +3 -3
  38. package/dist/package/src/api/base/{mfa.ts → v1/mfa.ts} +4 -4
  39. package/dist/package/src/api/base/{oauth.ts → v1/oauth.ts} +2 -2
  40. package/dist/package/src/api/system/{auth.ts → admin/v1/auth.ts} +3 -3
  41. package/dist/package/src/api/system/{base_dict.ts → admin/v1/base_dict.ts} +2 -2
  42. package/dist/package/src/api/system/{base_tenant.ts → admin/v1/base_tenant.ts} +3 -3
  43. package/dist/package/src/bootstrap.ts +6 -5
  44. package/dist/package/src/components/CronExpression/index.vue +4 -3
  45. package/dist/package/src/components/Dialog/ProDialog.vue +1 -0
  46. package/dist/package/src/components/ImportExcel/index.vue +4 -2
  47. package/dist/package/src/components/MarkdownPreview/index.vue +1 -1
  48. package/dist/package/src/components/PasswordStrength/index.vue +1 -1
  49. package/dist/package/src/components/ProTable/components/TableColumn.vue +14 -6
  50. package/dist/package/src/components/ProTable/index.vue +3 -3
  51. package/dist/package/src/components/RichTextPreview/index.vue +12 -1
  52. package/dist/package/src/components/SelectIcon/index.vue +3 -2
  53. package/dist/package/src/components/Upload/File.vue +2 -2
  54. package/dist/package/src/components/Upload/Files.vue +2 -2
  55. package/dist/package/src/components/Upload/Img.vue +7 -5
  56. package/dist/package/src/components/Upload/Imgs.vue +6 -5
  57. package/dist/package/src/components/WangEditor/index.vue +10 -7
  58. package/dist/package/src/documentTitle.ts +8 -0
  59. package/dist/package/src/format.ts +1 -1
  60. package/dist/package/src/index.ts +2 -1
  61. package/dist/package/src/layouts/components/Header/components/Avatar.vue +7 -6
  62. package/dist/package/src/layouts/components/Header/components/InfoDialog.vue +3 -2
  63. package/dist/package/src/layouts/components/Header/components/PasswordDialog.vue +3 -2
  64. package/dist/package/src/layouts/components/Header/components/SearchMenu.vue +16 -8
  65. package/dist/package/src/layouts/components/LockScreen.vue +175 -52
  66. package/dist/package/src/layouts/components/Tabs/index.vue +12 -3
  67. package/dist/package/src/layouts/components/ThemeDrawer/index.scss +4 -4
  68. package/dist/package/src/locales/en-US.json +15 -2
  69. package/dist/package/src/locales/ja-JP.json +15 -2
  70. package/dist/package/src/locales/zh-CN.json +15 -2
  71. package/dist/package/src/locales/zh-TW.json +15 -2
  72. package/dist/package/src/modules/index.ts +2 -4
  73. package/dist/package/src/request.ts +3 -0
  74. package/dist/package/src/routers/index.ts +15 -14
  75. package/dist/package/src/rpc/base/v1/config.ts +1 -1
  76. package/dist/package/src/rpc/base/v1/file.ts +1 -1
  77. package/dist/package/src/rpc/base/v1/language.ts +1 -1
  78. package/dist/package/src/rpc/base/v1/login.ts +5 -3
  79. package/dist/package/src/rpc/base/v1/mfa.ts +1 -1
  80. package/dist/package/src/rpc/base/v1/oauth.ts +1 -1
  81. package/dist/package/src/rpc/base/v1/oauth_client.ts +1 -1
  82. package/dist/package/src/rpc/common/v1/common.ts +1 -1
  83. package/dist/package/src/rpc/common/v1/enum.ts +1 -1
  84. package/dist/package/src/rpc/common/v1/types.ts +1 -1
  85. package/dist/package/src/rpc/google/protobuf/empty.ts +1 -1
  86. package/dist/package/src/rpc/google/protobuf/wrappers.ts +1 -1
  87. package/dist/package/src/rpc/system/admin/v1/auth.ts +14 -1
  88. package/dist/package/src/rpc/system/admin/v1/base_dict.ts +1 -113
  89. package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +1 -1
  90. package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +1 -1
  91. package/dist/package/src/rpc/system/admin/v1/common.ts +1 -1
  92. package/dist/package/src/rpc/system/common/v1/common.ts +35 -0
  93. package/dist/package/src/stores/interface/index.ts +6 -1
  94. package/dist/package/src/stores/modules/auth.ts +1 -1
  95. package/dist/package/src/stores/modules/config.ts +2 -2
  96. package/dist/package/src/stores/modules/dict.ts +1 -1
  97. package/dist/package/src/stores/modules/user.ts +21 -22
  98. package/dist/package/src/styles/element.scss +35 -4
  99. package/dist/package/src/typings/global.d.ts +6 -0
  100. package/dist/package/src/utils/passwordCrypto.ts +169 -12
  101. package/dist/package/src/utils/passwordStrength.ts +7 -24
  102. package/dist/package/src/utils/request.ts +155 -36
  103. package/dist/package/src/utils/tenant.ts +1 -1
  104. package/dist/package/src/utils/utils.ts +25 -6
  105. package/dist/package/src/views/login/components/LoginForm.vue +36 -21
  106. package/dist/package/src/views/login/index.scss +2 -2
  107. package/dist/package/types/generated/auto-imports.d.ts +2 -0
  108. package/package.json +2 -1
  109. package/types/generated/auto-imports.d.ts +2 -0
  110. package/vite.config.ts +24 -1
@@ -1,5 +1,5 @@
1
- import service from "../../utils/request";
2
- import type { DownloadFileRequest, MultiUploadFileResponse, FileInfo } from "../../rpc/base/v1/file";
1
+ import service from "../../../utils/request";
2
+ import type { DownloadFileRequest, MultiUploadFileResponse, FileInfo } from "../../../rpc/base/v1/file";
3
3
 
4
4
  const FILE_URL = "/v1/base/file";
5
5
 
@@ -35,33 +35,29 @@ export class FileServiceImpl {
35
35
  }
36
36
  });
37
37
  }
38
+
39
+ /** 读取文件内容并返回 Blob。 */
40
+ async GetFileBlob(file: string, fileName: string): Promise<Blob> {
41
+ const response = await service<DownloadFileRequest, any>({
42
+ url: `${FILE_URL}`,
43
+ method: "get",
44
+ params: {
45
+ name: fileName,
46
+ path: file
47
+ } as DownloadFileRequest,
48
+ responseType: "blob"
49
+ });
50
+ return response.data instanceof Blob ? response.data : new Blob([response.data]);
51
+ }
52
+
38
53
  /** 下载文件 */
39
- async DownloadFile(file: string, fileName: string) {
54
+ async DownloadFile(file: string, fileName: string): Promise<void> {
40
55
  try {
41
- const response = await service<DownloadFileRequest, any>({
42
- url: `${FILE_URL}`,
43
- method: "get",
44
- params: {
45
- name: fileName,
46
- path: file
47
- } as DownloadFileRequest,
48
- responseType: "blob" // 明确要求返回 Blob 类型
49
- });
50
- // 获取文件名
51
- const contentDisposition = response.headers["content-disposition"];
52
- let downloadFilename = fileName;
53
- if (contentDisposition) {
54
- const match = contentDisposition.match(/filename="?(.+?)"?$/);
55
- if (match && match[1]) {
56
- downloadFilename = match[1];
57
- }
58
- }
59
-
60
56
  // 创建下载链接
61
- const url = window.URL.createObjectURL(new Blob([response.data]));
57
+ const url = window.URL.createObjectURL(await this.GetFileBlob(file, fileName));
62
58
  const a = document.createElement("a");
63
59
  a.href = url;
64
- a.download = downloadFilename;
60
+ a.download = fileName;
65
61
  document.body.appendChild(a);
66
62
  a.click();
67
63
  document.body.removeChild(a);
@@ -1,5 +1,5 @@
1
- import service from "../../utils/request";
2
- import type { LanguageService, OptionLanguageRequest, OptionLanguageResponse } from "../../rpc/base/v1/language";
1
+ import service from "../../../utils/request";
2
+ import type { LanguageService, OptionLanguageRequest, OptionLanguageResponse } from "../../../rpc/base/v1/language";
3
3
 
4
4
  const LANGUAGE_URL = "/v1/base/language";
5
5
 
@@ -1,4 +1,4 @@
1
- import service from "../../utils/request";
1
+ import service from "../../../utils/request";
2
2
  import type {
3
3
  CaptchaRequest,
4
4
  CaptchaResponse,
@@ -12,8 +12,8 @@ import type {
12
12
  VerifyCaptchaRequest,
13
13
  VerifyCaptchaResponse,
14
14
  LoginService
15
- } from "../../rpc/base/v1/login";
16
- import type { Empty } from "../../rpc/google/protobuf/empty";
15
+ } from "../../../rpc/base/v1/login";
16
+ import type { Empty } from "../../../rpc/google/protobuf/empty";
17
17
 
18
18
  const CAPTCHA_URL = "/v1/base/captcha";
19
19
  const CAPTCHA_VERIFY_URL = "/v1/base/captcha/verify";
@@ -1,5 +1,5 @@
1
- import service from "../../utils/request";
2
- import type { LoginResponse } from "../../rpc/base/v1/login";
1
+ import service from "../../../utils/request";
2
+ import type { LoginResponse } from "../../../rpc/base/v1/login";
3
3
  import type {
4
4
  BeginMfaDisableRequest,
5
5
  BeginMfaDisableResponse,
@@ -18,8 +18,8 @@ import type {
18
18
  RecoveryCodesResponse,
19
19
  RegenerateMfaRecoveryCodesRequest,
20
20
  VerifyMfaRequest
21
- } from "../../rpc/base/v1/mfa";
22
- import type { Empty } from "../../rpc/google/protobuf/empty";
21
+ } from "../../../rpc/base/v1/mfa";
22
+ import type { Empty } from "../../../rpc/google/protobuf/empty";
23
23
 
24
24
  const MFA_URL = "/v1/base/mfa";
25
25
 
@@ -1,4 +1,4 @@
1
- import service from "../../utils/request";
1
+ import service from "../../../utils/request";
2
2
  import type {
3
3
  CreateOauthAuthorizationRequest,
4
4
  CreateOauthAuthorizationResponse,
@@ -6,7 +6,7 @@ import type {
6
6
  ExchangeOauthTicketResponse,
7
7
  ListOauthProviderRequest,
8
8
  ListOauthProviderResponse
9
- } from "../../rpc/base/v1/oauth";
9
+ } from "../../../rpc/base/v1/oauth";
10
10
 
11
11
  const OAUTH_PROVIDER_URL = "/v1/base/oauth/provider";
12
12
  const OAUTH_AUTHORIZATION_URL = "/v1/base/oauth/authorization";
@@ -1,12 +1,12 @@
1
- import service from "../../utils/request";
1
+ import service from "../../../../utils/request";
2
2
  import type {
3
3
  GetUserInfoRequest,
4
4
  ListUserButtonRequest,
5
5
  TreeRouteResponse,
6
6
  TreeUserMenuRequest,
7
7
  UserInfoForm
8
- } from "../../rpc/system/admin/v1/auth";
9
- import type { StringValues } from "../../rpc/common/v1/types";
8
+ } from "../../../../rpc/system/admin/v1/auth";
9
+ import type { StringValues } from "../../../../rpc/common/v1/types";
10
10
 
11
11
  const AUTH_URL = "/v1/admin/auth";
12
12
 
@@ -1,5 +1,5 @@
1
- import service from "../../utils/request";
2
- import type { OptionBaseDictRequest, OptionBaseDictResponse } from "../../rpc/system/admin/v1/base_dict";
1
+ import service from "../../../../utils/request";
2
+ import type { OptionBaseDictRequest, OptionBaseDictResponse } from "../../../../rpc/system/admin/v1/base_dict";
3
3
 
4
4
  const BASE_DICT_URL = "/v1/admin/base/dict";
5
5
 
@@ -1,6 +1,6 @@
1
- import service from "../../utils/request";
2
- import type { SelectOptionResponse } from "../../rpc/common/v1/common";
3
- import type { OptionBaseTenantRequest } from "../../rpc/system/admin/v1/base_tenant";
1
+ import service from "../../../../utils/request";
2
+ import type { SelectOptionResponse } from "../../../../rpc/common/v1/common";
3
+ import type { OptionBaseTenantRequest } from "../../../../rpc/system/admin/v1/base_tenant";
4
4
 
5
5
  const BASE_TENANT_URL = "/v1/admin/base/tenant";
6
6
 
@@ -28,7 +28,8 @@ import { useDictStore } from "./stores/modules/dict";
28
28
  import { useTabsStore } from "./stores/modules/tabs";
29
29
  import { useUserStore } from "./stores/modules/user";
30
30
  import { getRouteMetaTitle } from "./utils";
31
- import { defLanguageService } from "./api/base/language";
31
+ import { defLanguageService } from "./api/base/v1/language";
32
+ import { setAdminDocumentTitle } from "./documentTitle";
32
33
 
33
34
  /**
34
35
  * 管理端启动参数。
@@ -44,7 +45,7 @@ export interface AdminBootstrapOptions {
44
45
  * 创建并启动管理端应用,业务项目可通过 modules 注册自己的页面。
45
46
  */
46
47
  export async function bootstrapAdminApp(options: AdminBootstrapOptions = {}) {
47
- const modules = [kratosAdminModule, ...(options.modules ?? [])];
48
+ const modules = [kratosAdminModule, ...(options.modules ?? [])];
48
49
  registerAdminModules(modules);
49
50
  registerLocaleMessages(modules);
50
51
  initializeLocale();
@@ -79,6 +80,7 @@ export async function bootstrapAdminApp(options: AdminBootstrapOptions = {}) {
79
80
  } catch {
80
81
  // 配置接口失败时继续使用本地默认配置,避免阻断应用启动。
81
82
  }
83
+ updateDocumentTitle();
82
84
 
83
85
  app.mount(options.mount ?? "#app");
84
86
  return app;
@@ -91,7 +93,7 @@ async function refreshLocalizedRuntimeData() {
91
93
  const dictStore = useDictStore(pinia);
92
94
  const configStore = useConfigStore(pinia);
93
95
  await Promise.allSettled([configStore.loadDisplayConfig()]);
94
- if (userStore.token || userStore.refreshToken) {
96
+ if (userStore.token) {
95
97
  await Promise.allSettled([authStore.getAuthMenuList(), dictStore.updateDictionaryCache()]);
96
98
  syncLocalizedRouteState();
97
99
  }
@@ -119,6 +121,5 @@ function updateDocumentTitle() {
119
121
  const route = router.currentRoute.value;
120
122
  const titleKey = typeof route.meta.titleKey === "string" ? route.meta.titleKey : "";
121
123
  const routeTitle = titleKey ? t(titleKey) : String(route.meta.title ?? "");
122
- const appTitle = import.meta.env.VITE_GLOB_APP_TITLE;
123
- document.title = routeTitle ? `${routeTitle} - ${appTitle}` : appTitle;
124
+ setAdminDocumentTitle(routeTitle);
124
125
  }
@@ -12,7 +12,7 @@
12
12
  </template>
13
13
  </el-input>
14
14
 
15
- <el-dialog v-model="dialogVisible" :title="t('core.cron.title')" width="980px" top="2vh" destroy-on-close>
15
+ <ProDialog v-model="dialogVisible" :title="t('core.cron.title')" width="980px" top="2vh" destroy-on-close>
16
16
  <div class="cron-editor">
17
17
  <div class="cron-editor__preset">
18
18
  <div class="cron-editor__section-title">{{ t("core.cron.presets") }}</div>
@@ -107,7 +107,7 @@
107
107
  <el-button @click="dialogVisible = false">{{ t("common.action.cancel") }}</el-button>
108
108
  </div>
109
109
  </template>
110
- </el-dialog>
110
+ </ProDialog>
111
111
  </div>
112
112
  </template>
113
113
 
@@ -116,6 +116,7 @@ import { computed, defineComponent, h, reactive, ref, watch } from "vue";
116
116
  import type { PropType } from "vue";
117
117
  import { ElCheckbox, ElCheckboxGroup, ElInputNumber, ElRadio } from "element-plus";
118
118
  import { Operation } from "@element-plus/icons-vue";
119
+ import ProDialog from "../../components/Dialog/ProDialog.vue";
119
120
  import { useLocaleStore } from "../../locales";
120
121
 
121
122
  const { t } = useLocaleStore();
@@ -669,7 +670,7 @@ const CronSegmentEditor = defineComponent({
669
670
  gap: 8px;
670
671
  padding: 12px;
671
672
  background: var(--el-fill-color-light);
672
- border-radius: 8px;
673
+ border-radius: var(--admin-page-radius);
673
674
  }
674
675
  .cron-editor__preset-list {
675
676
  display: flex;
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <el-dialog
3
+ class="pro-dialog"
3
4
  :model-value="modelValue"
4
5
  :title="title"
5
6
  :width="width"
@@ -1,9 +1,10 @@
1
1
  <template>
2
- <el-dialog
2
+ <ProDialog
3
3
  v-model="dialogVisible"
4
4
  :title="t('core.upload.batch_add', { resource: parameter.title })"
5
5
  :destroy-on-close="true"
6
6
  width="580px"
7
+ :show-footer="false"
7
8
  draggable
8
9
  >
9
10
  <el-form class="drawer-multiColumn-form" label-width="180px">
@@ -44,11 +45,12 @@
44
45
  <el-switch v-model="isCover" />
45
46
  </el-form-item>
46
47
  </el-form>
47
- </el-dialog>
48
+ </ProDialog>
48
49
  </template>
49
50
 
50
51
  <script setup lang="ts" name="ImportExcel">
51
52
  import { ref } from "vue";
53
+ import ProDialog from "../../components/Dialog/ProDialog.vue";
52
54
  import { useDownload } from "../../hooks/useDownload";
53
55
  import { Download } from "@element-plus/icons-vue";
54
56
  import { ElNotification, UploadRequestOptions, UploadRawFile } from "element-plus";
@@ -241,7 +241,7 @@ function createMarkdownHeadingId({ text, index }: Parameters<MdHeadingId>[0]) {
241
241
  cursor: pointer;
242
242
  background: var(--admin-page-card-bg);
243
243
  border: 1px solid var(--admin-page-card-border-soft);
244
- border-radius: 4px;
244
+ border-radius: var(--admin-page-radius);
245
245
  box-shadow: var(--admin-page-shadow);
246
246
  }
247
247
  .core-markdown-preview :deep(.md-editor-code pre) {
@@ -53,7 +53,7 @@ const strength = computed(() => getPasswordStrength(props.password));
53
53
  padding: 14px 16px;
54
54
  background: #fafbfd;
55
55
  border: 1px solid #ebeef5;
56
- border-radius: 10px;
56
+ border-radius: var(--admin-page-radius);
57
57
  }
58
58
  .password-strength__header {
59
59
  display: flex;
@@ -9,7 +9,7 @@ import DictLabel from "../../../components/Dict/DictLabel.vue";
9
9
  import { ColumnProps, HeaderRenderScope, RenderScope, TableActionProps } from "../../../components/ProTable/interface";
10
10
  import type { TableAlign } from "../../../utils/proTable";
11
11
  import { filterEnum, formatValue, handleProp, handleRowAccordingToProp } from "../../../utils";
12
- import { formatPrice } from "../../../utils/utils";
12
+ import { formatPrice, formatSrc } from "../../../utils/utils";
13
13
  import { t } from "../../../locales";
14
14
 
15
15
  const props = defineProps<{
@@ -93,12 +93,14 @@ const getBooleanValue = (value: boolean | ((scope: RenderScope<any>) => boolean)
93
93
  */
94
94
  const renderImageCell = (item: ColumnProps, scope: RenderScope<any>) => {
95
95
  const imageProps = item.imageProps ?? {};
96
- const src =
96
+ const rawSrc =
97
97
  typeof imageProps.src === "function"
98
98
  ? imageProps.src(scope)
99
99
  : (imageProps.src ?? handleRowAccordingToProp(scope.row, item.prop!));
100
- const previewSrc = typeof imageProps.previewSrc === "function" ? imageProps.previewSrc(scope) : (imageProps.previewSrc ?? src);
101
- if (!src || src === "--") return "--";
100
+ const rawPreviewSrc = typeof imageProps.previewSrc === "function" ? imageProps.previewSrc(scope) : (imageProps.previewSrc ?? rawSrc);
101
+ if (!rawSrc || rawSrc === "--") return "--";
102
+ const src = formatSrc(String(rawSrc));
103
+ const previewSrc = formatSrc(String(rawPreviewSrc));
102
104
  const thumbWidth = typeof imageProps.width === "number" ? `${imageProps.width}px` : (imageProps.width ?? "60px");
103
105
  const thumbHeight = typeof imageProps.height === "number" ? `${imageProps.height}px` : (imageProps.height ?? "60px");
104
106
  return h(
@@ -116,7 +118,7 @@ const renderImageCell = (item: ColumnProps, scope: RenderScope<any>) => {
116
118
  style: {
117
119
  width: thumbWidth,
118
120
  height: thumbHeight,
119
- borderRadius: "4px"
121
+ borderRadius: "var(--admin-page-radius)"
120
122
  }
121
123
  },
122
124
  {
@@ -187,6 +189,12 @@ const renderActionsCell = (item: ColumnProps, scope: RenderScope<any>) => {
187
189
  });
188
190
  };
189
191
 
192
+ /** 渲染默认表头标题,避免窄列换行并保留完整标题提示。 */
193
+ const renderHeaderLabel = (item: ColumnProps) => {
194
+ if (!item.label || item.showOverflowTooltip === false) return item.label;
195
+ return h("span", { title: item.label }, item.label);
196
+ };
197
+
190
198
  /**
191
199
  * 渲染预置列类型,统一收敛图片、状态和操作按钮等通用场景。
192
200
  */
@@ -227,7 +235,7 @@ const RenderTableColumn = (item: ColumnProps) => {
227
235
  header: (scope: HeaderRenderScope<any>) => {
228
236
  if (item.headerRender) return item.headerRender(scope);
229
237
  if (item.prop && slots[`${handleProp(item.prop)}Header`]) return slots[`${handleProp(item.prop)}Header`]!(scope);
230
- return item.label;
238
+ return renderHeaderLabel(item);
231
239
  }
232
240
  }
233
241
  );
@@ -72,7 +72,7 @@
72
72
  >
73
73
  <!-- 默认插槽 -->
74
74
  <slot />
75
- <template v-for="item in tableColumns" :key="item">
75
+ <template v-for="(item, index) in tableColumns" :key="item.prop ?? item.type ?? index">
76
76
  <!-- selection || radio || index || expand || sort -->
77
77
  <el-table-column
78
78
  v-if="item.type && columnTypes.includes(item.type)"
@@ -133,7 +133,7 @@
133
133
 
134
134
  <script setup lang="ts" name="ProTable">
135
135
  import { ref, watch, provide, onMounted, unref, computed, nextTick, useAttrs, useSlots, isProxy, markRaw, toRaw } from "vue";
136
- import { ElTable } from "element-plus";
136
+ import { ElTable, type TableInstance } from "element-plus";
137
137
  import { useTable } from "../../hooks/useTable";
138
138
  import { useSelection } from "../../hooks/useSelection";
139
139
  import {
@@ -171,7 +171,7 @@ const props = withDefaults(defineProps<ProTableProps>(), {
171
171
  });
172
172
 
173
173
  // table 实例
174
- const tableRef = ref<InstanceType<typeof ElTable>>();
174
+ const tableRef = ref<TableInstance>();
175
175
  const attrs = useAttrs();
176
176
  const slots = useSlots();
177
177
 
@@ -5,6 +5,7 @@
5
5
  <script setup lang="ts">
6
6
  import { computed } from "vue";
7
7
  import DOMPurify from "dompurify";
8
+ import { formatSrc } from "../../utils/utils";
8
9
 
9
10
  defineOptions({
10
11
  name: "CoreRichTextPreview"
@@ -17,7 +18,17 @@ interface RichTextPreviewProps {
17
18
  }
18
19
 
19
20
  const props = defineProps<RichTextPreviewProps>();
20
- const sanitizedHtml = computed(() => DOMPurify.sanitize(props.modelValue, { USE_PROFILES: { html: true } }));
21
+ const sanitizedHtml = computed(() => {
22
+ const sanitized = DOMPurify.sanitize(props.modelValue, { USE_PROFILES: { html: true } });
23
+ if (typeof DOMParser === "undefined") return sanitized;
24
+
25
+ const document = new DOMParser().parseFromString(sanitized, "text/html");
26
+ document.body.querySelectorAll<HTMLElement>("img[src], video[src]").forEach(element => {
27
+ const src = element.getAttribute("src");
28
+ if (src) element.setAttribute("src", formatSrc(src));
29
+ });
30
+ return document.body.innerHTML;
31
+ });
21
32
  </script>
22
33
 
23
34
  <style scoped lang="scss">
@@ -13,7 +13,7 @@
13
13
  <el-button :icon="customIcons[iconValue]" />
14
14
  </template>
15
15
  </el-input>
16
- <el-dialog v-model="dialogVisible" :title="placeholder" top="50px" width="760px">
16
+ <ProDialog v-model="dialogVisible" :title="placeholder" top="50px" width="760px" :show-footer="false">
17
17
  <el-input v-model="inputValue" :placeholder="t('core.icon.search')" size="large" :prefix-icon="Icons.Search" />
18
18
  <el-scrollbar v-if="Object.keys(iconsList).length">
19
19
  <div class="icon-list">
@@ -24,13 +24,14 @@
24
24
  </div>
25
25
  </el-scrollbar>
26
26
  <el-empty v-else :description="t('core.icon.empty')" />
27
- </el-dialog>
27
+ </ProDialog>
28
28
  </div>
29
29
  </template>
30
30
 
31
31
  <script setup lang="ts" name="SelectIcon">
32
32
  import { ref, computed, watch } from "vue";
33
33
  import * as Icons from "@element-plus/icons-vue";
34
+ import ProDialog from "../../components/Dialog/ProDialog.vue";
34
35
  import { useLocaleStore } from "../../locales";
35
36
 
36
37
  const { t } = useLocaleStore();
@@ -38,7 +38,7 @@ import { computed, inject } from "vue";
38
38
  import { Delete, Document, Download } from "@element-plus/icons-vue";
39
39
  import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
40
40
  import type { UploadProps, UploadRequestOptions, UploadUserFile } from "element-plus";
41
- import { defFileService } from "../../api/base/file";
41
+ import { defFileService } from "../../api/base/v1/file";
42
42
  import type { FileInfo } from "../../rpc/base/v1/file";
43
43
  import { useLocaleStore } from "../../locales";
44
44
 
@@ -171,7 +171,7 @@ async function handleDownload() {
171
171
  margin-top: 12px;
172
172
  background: var(--el-fill-color-light);
173
173
  border: 1px solid var(--el-border-color-light);
174
- border-radius: 8px;
174
+ border-radius: var(--admin-page-radius);
175
175
  }
176
176
  .file-card__main {
177
177
  display: flex;
@@ -44,7 +44,7 @@ import { computed, inject, ref, watch } from "vue";
44
44
  import { Delete, Document, Download } from "@element-plus/icons-vue";
45
45
  import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
46
46
  import type { UploadProps, UploadRequestOptions, UploadUserFile } from "element-plus";
47
- import { defFileService } from "../../api/base/file";
47
+ import { defFileService } from "../../api/base/v1/file";
48
48
  import type { FileInfo } from "../../rpc/base/v1/file";
49
49
  import { useLocaleStore } from "../../locales";
50
50
 
@@ -206,7 +206,7 @@ async function handleDownload(file: UploadUserFile) {
206
206
  padding: 12px 14px;
207
207
  background: var(--el-fill-color-light);
208
208
  border: 1px solid var(--el-border-color-light);
209
- border-radius: 8px;
209
+ border-radius: var(--admin-page-radius);
210
210
  }
211
211
  .file-card__main {
212
212
  display: flex;
@@ -15,7 +15,7 @@
15
15
  :accept="fileType.join(',')"
16
16
  >
17
17
  <template v-if="imageUrl">
18
- <img :src="imageUrl" class="upload-image" />
18
+ <img :src="displayImageUrl" class="upload-image" />
19
19
  <div class="upload-handle" @click.stop>
20
20
  <div v-if="!self_disabled" class="handle-icon" @click="editImg">
21
21
  <el-icon><Edit /></el-icon>
@@ -43,14 +43,15 @@
43
43
  <div class="el-upload__tip">
44
44
  <slot name="tip"></slot>
45
45
  </div>
46
- <el-image-viewer v-if="imgViewVisible" :url-list="[imageUrl]" @close="imgViewVisible = false" />
46
+ <el-image-viewer v-if="imgViewVisible" :url-list="[displayImageUrl]" @close="imgViewVisible = false" />
47
47
  </div>
48
48
  </template>
49
49
 
50
50
  <script setup lang="ts" name="UploadImg">
51
51
  import { ref, computed, inject } from "vue";
52
52
  import { generateUUID } from "../../utils";
53
- import { defFileService } from "../../api/base/file";
53
+ import { formatSrc } from "../../utils/utils";
54
+ import { defFileService } from "../../api/base/v1/file";
54
55
  import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
55
56
  import type { UploadProps, UploadRequestOptions } from "element-plus";
56
57
  import type { FileInfo } from "../../rpc/base/v1/file";
@@ -69,7 +70,7 @@ interface UploadFileProps {
69
70
  fileType?: File.ImageMimeType[]; // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
70
71
  height?: string; // 组件高度 ==> 非必传(默认为 150px)
71
72
  width?: string; // 组件宽度 ==> 非必传(默认为 150px)
72
- borderRadius?: string; // 组件边框圆角 ==> 非必传(默认为 8px)
73
+ borderRadius?: string; // 组件边框圆角 ==> 非必传(默认为全局圆角)
73
74
  }
74
75
 
75
76
  // 接受父组件参数
@@ -82,7 +83,7 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
82
83
  fileType: () => ["image/jpeg", "image/png", "image/gif"],
83
84
  height: "150px",
84
85
  width: "150px",
85
- borderRadius: "8px"
86
+ borderRadius: "var(--admin-page-radius)"
86
87
  });
87
88
 
88
89
  // 生成组件唯一id
@@ -98,6 +99,7 @@ const formItemContext = inject(formItemContextKey, void 0);
98
99
  const self_disabled = computed(() => {
99
100
  return props.disabled || formContext?.disabled;
100
101
  });
102
+ const displayImageUrl = computed(() => formatSrc(props.imageUrl));
101
103
 
102
104
  /**
103
105
  * @description 图片上传
@@ -23,7 +23,7 @@
23
23
  </slot>
24
24
  </div>
25
25
  <template #file="{ file }">
26
- <img :src="file.url" class="upload-image" />
26
+ <img :src="formatSrc(file.url ?? '')" class="upload-image" />
27
27
  <div class="upload-handle" @click.stop>
28
28
  <div class="handle-icon" @click="handlePictureCardPreview(file)">
29
29
  <el-icon><ZoomIn /></el-icon>
@@ -46,7 +46,8 @@
46
46
  <script setup lang="ts" name="UploadImgs">
47
47
  import { ref, computed, inject, watch } from "vue";
48
48
  import { Plus } from "@element-plus/icons-vue";
49
- import { defFileService } from "../../api/base/file";
49
+ import { defFileService } from "../../api/base/v1/file";
50
+ import { formatSrc } from "../../utils/utils";
50
51
  import type { UploadProps, UploadFile, UploadUserFile, UploadRequestOptions } from "element-plus";
51
52
  import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
52
53
  import type { FileInfo } from "../../rpc/base/v1/file";
@@ -66,7 +67,7 @@ interface UploadFileProps {
66
67
  fileType?: File.ImageMimeType[]; // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
67
68
  height?: string; // 组件高度 ==> 非必传(默认为 150px)
68
69
  width?: string; // 组件宽度 ==> 非必传(默认为 150px)
69
- borderRadius?: string; // 组件边框圆角 ==> 非必传(默认为 8px)
70
+ borderRadius?: string; // 组件边框圆角 ==> 非必传(默认为全局圆角)
70
71
  }
71
72
 
72
73
  const props = withDefaults(defineProps<UploadFileProps>(), {
@@ -79,7 +80,7 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
79
80
  fileType: () => ["image/jpeg", "image/png", "image/gif"],
80
81
  height: "150px",
81
82
  width: "150px",
82
- borderRadius: "8px"
83
+ borderRadius: "var(--admin-page-radius)"
83
84
  });
84
85
 
85
86
  // 获取 el-form 组件上下文
@@ -200,7 +201,7 @@ const handleExceed = () => {
200
201
  const viewImageUrl = ref("");
201
202
  const imgViewVisible = ref(false);
202
203
  const handlePictureCardPreview: UploadProps["onPreview"] = file => {
203
- viewImageUrl.value = file.url!;
204
+ viewImageUrl.value = formatSrc(file.url ?? "");
204
205
  imgViewVisible.value = true;
205
206
  };
206
207
  </script>
@@ -17,7 +17,8 @@
17
17
  import { nextTick, computed, inject, shallowRef, onBeforeUnmount } from "vue";
18
18
  import { IToolbarConfig, IEditorConfig } from "@wangeditor/editor";
19
19
  import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
20
- import { defFileService } from "../../api/base/file";
20
+ import { defFileService } from "../../api/base/v1/file";
21
+ import { normalizeRichTextMediaPaths, normalizeStaticAssetPath } from "../../utils/utils";
21
22
  import "@wangeditor/editor/dist/css/style.css";
22
23
  import { formContextKey, formItemContextKey } from "element-plus";
23
24
  import { useLocaleStore } from "../../locales";
@@ -41,6 +42,7 @@ interface RichEditorProps {
41
42
  mode?: "default" | "simple"; // 富文本模式 ==> 非必传(默认为 default)
42
43
  hideToolBar?: boolean; // 是否隐藏工具栏 ==> 非必传(默认为false)
43
44
  disabled?: boolean; // 是否禁用编辑器 ==> 非必传(默认为false)
45
+ uploadType?: string; // 富文本上传业务类型 ==> 非必传(默认为 content)
44
46
  }
45
47
  const props = withDefaults(defineProps<RichEditorProps>(), {
46
48
  toolbarConfig: () => {
@@ -56,7 +58,8 @@ const props = withDefaults(defineProps<RichEditorProps>(), {
56
58
  height: "500px",
57
59
  mode: "default",
58
60
  hideToolBar: false,
59
- disabled: false
61
+ disabled: false,
62
+ uploadType: "content"
60
63
  });
61
64
  const editorMenuConfig = props.editorConfig.MENU_CONF ?? {};
62
65
  const resolvedEditorConfig = computed(() => ({
@@ -84,7 +87,7 @@ const emit = defineEmits<{
84
87
  }>();
85
88
  const valueHtml = computed({
86
89
  get() {
87
- return props.value;
90
+ return normalizeRichTextMediaPaths(props.value);
88
91
  },
89
92
  set(val: string) {
90
93
  // 防止富文本内容为空时,校验失败
@@ -103,8 +106,8 @@ editorMenuConfig["uploadImage"] = {
103
106
  async customUpload(file: File, insertFn: InsertFnTypeImg) {
104
107
  if (!uploadImgValidate(file)) return;
105
108
  try {
106
- const data = await defFileService.UploadFile(file, "image");
107
- insertFn(data.url);
109
+ const data = await defFileService.UploadFile(file, props.uploadType);
110
+ insertFn(normalizeStaticAssetPath(data.url));
108
111
  } catch (error) {
109
112
  console.log(error);
110
113
  }
@@ -127,8 +130,8 @@ editorMenuConfig["uploadVideo"] = {
127
130
  async customUpload(file: File, insertFn: InsertFnTypeVideo) {
128
131
  if (!uploadVideoValidate(file)) return;
129
132
  try {
130
- const data = await defFileService.UploadFile(file, "video");
131
- insertFn(data.url);
133
+ const data = await defFileService.UploadFile(file, props.uploadType);
134
+ insertFn(normalizeStaticAssetPath(data.url));
132
135
  } catch (error) {
133
136
  console.log(error);
134
137
  }
@@ -0,0 +1,8 @@
1
+ import pinia from "./stores";
2
+ import { useConfigStore } from "./stores/modules/config";
3
+
4
+ /** setAdminDocumentTitle 使用后端站点名称更新管理端浏览器标题。 */
5
+ export function setAdminDocumentTitle(pageTitle = "") {
6
+ const appTitle = useConfigStore(pinia).display.sysName || import.meta.env.VITE_GLOB_APP_TITLE;
7
+ document.title = pageTitle ? `${pageTitle} - ${appTitle}` : appTitle;
8
+ }
@@ -1 +1 @@
1
- export { formatJson, formatSrc } from "./utils/utils";
1
+ export { formatJson, formatSrc, normalizeRichTextMediaPaths, normalizeStaticAssetPath } from "./utils/utils";