@liujitcn/kratos-admin-core 0.0.29 → 0.0.32
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.
- package/README.md +15 -8
- package/build/auto-imports.json +73 -0
- package/build/plugins.ts +2 -58
- package/dist/package/declarations/src/api/base/{config.d.ts → v1/config.d.ts} +1 -1
- package/dist/package/declarations/src/api/base/{file.d.ts → v1/file.d.ts} +3 -1
- package/dist/package/declarations/src/api/base/{language.d.ts → v1/language.d.ts} +1 -1
- package/dist/package/declarations/src/api/base/{login.d.ts → v1/login.d.ts} +2 -2
- package/dist/package/declarations/src/api/base/v1/mfa.d.ts +26 -0
- package/dist/package/declarations/src/api/base/{oauth.d.ts → v1/oauth.d.ts} +1 -1
- package/dist/package/declarations/src/api/system/{auth.d.ts → admin/v1/auth.d.ts} +2 -2
- package/dist/package/declarations/src/api/system/{base_dict.d.ts → admin/v1/base_dict.d.ts} +1 -1
- package/dist/package/declarations/src/api/system/{base_tenant.d.ts → admin/v1/base_tenant.d.ts} +2 -2
- package/dist/package/declarations/src/components/Dialog/FormDialog.vue.d.ts +3 -3
- package/dist/package/declarations/src/components/Dialog/PasswordVerifyDialog.vue.d.ts +65 -0
- package/dist/package/declarations/src/components/Dialog/ProDialog.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Grid/components/GridItem.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Mfa/MfaRecoveryCodesDialog.vue.d.ts +19 -0
- package/dist/package/declarations/src/components/Mfa/MfaSetupPanel.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/ProForm/components/ProFormItem.vue.d.ts +4 -4
- package/dist/package/declarations/src/components/ProForm/index.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/ProForm/interface/index.d.ts +4 -1
- package/dist/package/declarations/src/components/ProTable/index.vue.d.ts +114 -0
- package/dist/package/declarations/src/components/RichTextPreview/index.vue.d.ts +8 -0
- package/dist/package/declarations/src/components/TreeFilter/index.vue.d.ts +84 -76
- package/dist/package/declarations/src/components/Upload/File.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Files.vue.d.ts +1 -1
- package/dist/package/declarations/src/components/Upload/Img.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/Upload/Imgs.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/WangEditor/index.vue.d.ts +2 -0
- package/dist/package/declarations/src/documentTitle.d.ts +2 -0
- package/dist/package/declarations/src/format.d.ts +1 -1
- package/dist/package/declarations/src/index.d.ts +2 -1
- package/dist/package/declarations/src/layouts/components/LockScreen.vue.d.ts +3 -0
- package/dist/package/declarations/src/locales/generated.d.ts +208 -0
- package/dist/package/declarations/src/locales/index.d.ts +2 -0
- package/dist/package/declarations/src/modules/index.d.ts +1 -3
- package/dist/package/declarations/src/request.d.ts +1 -1
- package/dist/package/declarations/src/routers/index.d.ts +1 -1
- package/dist/package/declarations/src/rpc/base/v1/login.d.ts +37 -5
- package/dist/package/declarations/src/rpc/base/v1/mfa.d.ts +152 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +25 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth_client.d.ts +23 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +13 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +0 -101
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +10 -6
- package/dist/package/declarations/src/rpc/system/common/v1/common.d.ts +26 -0
- package/dist/package/declarations/src/security.d.ts +2 -0
- package/dist/package/declarations/src/stores/interface/index.d.ts +19 -1
- package/dist/package/declarations/src/stores/modules/lockScreen.d.ts +20 -0
- package/dist/package/declarations/src/stores/modules/user.d.ts +7 -6
- package/dist/package/declarations/src/stores/runtime.d.ts +1 -0
- package/dist/package/declarations/src/utils/clipboard.d.ts +2 -0
- package/dist/package/declarations/src/utils/passwordStrength.d.ts +1 -12
- package/dist/package/declarations/src/utils/request.d.ts +13 -2
- package/dist/package/declarations/src/utils/utils.d.ts +4 -0
- package/dist/package/declarations/src/utils/webauthn.d.ts +4 -0
- package/dist/package/declarations/vite.config.d.ts +2 -0
- package/dist/package/src/api/base/{config.ts → v1/config.ts} +2 -2
- package/dist/package/src/api/base/{file.ts → v1/file.ts} +20 -24
- package/dist/package/src/api/base/{language.ts → v1/language.ts} +2 -2
- package/dist/package/src/api/base/{login.ts → v1/login.ts} +3 -3
- package/dist/package/src/api/base/v1/mfa.ts +114 -0
- package/dist/package/src/api/base/{oauth.ts → v1/oauth.ts} +2 -2
- package/dist/package/src/api/system/{auth.ts → admin/v1/auth.ts} +3 -3
- package/dist/package/src/api/system/{base_dict.ts → admin/v1/base_dict.ts} +2 -2
- package/dist/package/src/api/system/{base_tenant.ts → admin/v1/base_tenant.ts} +3 -3
- package/dist/package/src/bootstrap.ts +6 -5
- package/dist/package/src/components/CronExpression/index.vue +4 -3
- package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +176 -0
- package/dist/package/src/components/Dialog/ProDialog.vue +1 -0
- package/dist/package/src/components/ImportExcel/index.vue +4 -2
- package/dist/package/src/components/Loading/fullScreen.ts +2 -1
- package/dist/package/src/components/MarkdownPreview/index.vue +22 -2
- package/dist/package/src/components/Mfa/MfaRecoveryCodesDialog.vue +91 -0
- package/dist/package/src/components/Mfa/MfaSetupPanel.vue +81 -0
- package/dist/package/src/components/PasswordStrength/index.vue +1 -1
- package/dist/package/src/components/ProForm/components/ProFormItem.vue +16 -1
- package/dist/package/src/components/ProForm/interface/index.ts +4 -0
- package/dist/package/src/components/ProTable/components/ColSetting.vue +1 -1
- package/dist/package/src/components/ProTable/components/TableColumn.vue +14 -6
- package/dist/package/src/components/ProTable/index.vue +9 -5
- package/dist/package/src/components/RichTextPreview/index.vue +56 -0
- package/dist/package/src/components/SelectIcon/index.vue +3 -2
- package/dist/package/src/components/Upload/File.vue +2 -2
- package/dist/package/src/components/Upload/Files.vue +2 -2
- package/dist/package/src/components/Upload/Img.vue +7 -5
- package/dist/package/src/components/Upload/Imgs.vue +6 -5
- package/dist/package/src/components/WangEditor/index.vue +10 -7
- package/dist/package/src/config/index.ts +1 -1
- package/dist/package/src/documentTitle.ts +8 -0
- package/dist/package/src/format.ts +1 -1
- package/dist/package/src/index.ts +2 -1
- package/dist/package/src/layouts/LayoutClassic/index.vue +3 -1
- package/dist/package/src/layouts/LayoutColumns/index.vue +3 -1
- package/dist/package/src/layouts/LayoutTransverse/index.vue +3 -1
- package/dist/package/src/layouts/LayoutVertical/index.vue +9 -1
- package/dist/package/src/layouts/components/Header/components/Avatar.vue +26 -6
- package/dist/package/src/layouts/components/Header/components/InfoDialog.vue +4 -3
- package/dist/package/src/layouts/components/Header/components/PasswordDialog.vue +4 -3
- package/dist/package/src/layouts/components/Header/components/SearchMenu.vue +16 -8
- package/dist/package/src/layouts/components/LockScreen.vue +573 -0
- package/dist/package/src/layouts/components/Tabs/index.vue +12 -3
- package/dist/package/src/layouts/components/ThemeDrawer/index.scss +4 -4
- package/dist/package/src/layouts/index.vue +2 -0
- package/dist/package/src/layouts/indexAsync.vue +2 -0
- package/dist/package/src/locales/README.md +2 -1
- package/dist/package/src/locales/en-US.json +51 -3
- package/dist/package/src/locales/index.ts +16 -12
- package/dist/package/src/locales/ja-JP.json +51 -3
- package/dist/package/src/locales/zh-CN.json +51 -3
- package/dist/package/src/locales/zh-TW.json +51 -3
- package/dist/package/src/modules/index.ts +2 -4
- package/dist/package/src/request.ts +3 -0
- package/dist/package/src/routers/index.ts +15 -14
- package/dist/package/src/rpc/base/v1/login.ts +37 -4
- package/dist/package/src/rpc/base/v1/mfa.ts +180 -0
- package/dist/package/src/rpc/base/v1/oauth.ts +25 -0
- package/dist/package/src/rpc/base/v1/oauth_client.ts +33 -0
- package/dist/package/src/rpc/system/admin/v1/auth.ts +13 -0
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +0 -112
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +10 -6
- package/dist/package/src/rpc/system/common/v1/common.ts +35 -0
- package/dist/package/src/security.ts +2 -0
- package/dist/package/src/stores/interface/index.ts +20 -1
- package/dist/package/src/stores/modules/auth.ts +1 -1
- package/dist/package/src/stores/modules/config.ts +2 -2
- package/dist/package/src/stores/modules/dict.ts +1 -1
- package/dist/package/src/stores/modules/lockScreen.ts +78 -0
- package/dist/package/src/stores/modules/user.ts +34 -22
- package/dist/package/src/stores/runtime.ts +1 -0
- package/dist/package/src/styles/element.scss +35 -4
- package/dist/package/src/utils/clipboard.ts +22 -0
- package/dist/package/src/utils/passwordCrypto.ts +1 -1
- package/dist/package/src/utils/passwordStrength.ts +7 -24
- package/dist/package/src/utils/request.ts +175 -40
- package/dist/package/src/utils/tenant.ts +1 -1
- package/dist/package/src/utils/utils.ts +25 -6
- package/dist/package/src/utils/webauthn.ts +90 -0
- package/dist/package/src/views/login/components/LoginForm.vue +257 -24
- package/dist/package/src/views/login/index.scss +2 -2
- package/dist/package/src/views/login/index.vue +3 -1
- package/dist/package/types/generated/auto-imports.d.ts +132 -129
- package/dist/package/types/generated/components.d.ts +4 -72
- package/package.json +22 -1
- package/types/generated/auto-imports.d.ts +132 -129
- package/types/generated/components.d.ts +4 -72
- package/vite.config.ts +4 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ProDialog
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
:title="t('core.login.mfa_recovery_codes_title')"
|
|
5
|
+
width="420px"
|
|
6
|
+
:close-on-click-modal="false"
|
|
7
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
8
|
+
>
|
|
9
|
+
<div class="mfa-recovery-dialog__codes">
|
|
10
|
+
<span>{{ codesText }}</span>
|
|
11
|
+
<el-tooltip :content="t('core.login.mfa_copy_recovery_codes')" placement="top">
|
|
12
|
+
<el-button text circle :aria-label="t('core.login.mfa_copy_recovery_codes')" @click="copyRecoveryCodes">
|
|
13
|
+
<el-icon class="mfa-recovery-dialog__copy"><CopyDocument /></el-icon>
|
|
14
|
+
</el-button>
|
|
15
|
+
</el-tooltip>
|
|
16
|
+
</div>
|
|
17
|
+
<p class="mfa-recovery-dialog__warning">{{ t("core.login.mfa_recovery_codes_warning") }}</p>
|
|
18
|
+
<template #footer>
|
|
19
|
+
<el-button type="primary" @click="handleConfirm">{{ t("core.login.mfa_recovery_codes_confirm") }}</el-button>
|
|
20
|
+
</template>
|
|
21
|
+
</ProDialog>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script setup lang="ts" name="MfaRecoveryCodesDialog">
|
|
25
|
+
import { computed } from "vue";
|
|
26
|
+
import ProDialog from "../../components/Dialog/ProDialog.vue";
|
|
27
|
+
import { useLocaleStore } from "../../locales";
|
|
28
|
+
import { copyText } from "../../utils/clipboard";
|
|
29
|
+
|
|
30
|
+
/** MFA 恢复码弹窗属性。 */
|
|
31
|
+
interface MfaRecoveryCodesDialogProps {
|
|
32
|
+
/** 弹窗显示状态。 */
|
|
33
|
+
modelValue: boolean;
|
|
34
|
+
/** 本次生成的一次性恢复码。 */
|
|
35
|
+
codes: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const props = withDefaults(defineProps<MfaRecoveryCodesDialogProps>(), {
|
|
39
|
+
modelValue: false,
|
|
40
|
+
codes: () => []
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const emit = defineEmits<{
|
|
44
|
+
"update:modelValue": [value: boolean];
|
|
45
|
+
confirm: [];
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
const { t } = useLocaleStore();
|
|
49
|
+
const codesText = computed(() => props.codes.join("\n"));
|
|
50
|
+
|
|
51
|
+
/** 复制当前显示的恢复码。 */
|
|
52
|
+
async function copyRecoveryCodes() {
|
|
53
|
+
await copyText(codesText.value);
|
|
54
|
+
ElMessage.success(t("core.login.mfa_recovery_codes_copied"));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 关闭弹窗并通知调用方完成绑定流程。 */
|
|
58
|
+
function handleConfirm() {
|
|
59
|
+
emit("update:modelValue", false);
|
|
60
|
+
emit("confirm");
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<style scoped lang="scss">
|
|
65
|
+
.mfa-recovery-dialog__codes {
|
|
66
|
+
display: flex;
|
|
67
|
+
gap: 8px;
|
|
68
|
+
align-items: flex-start;
|
|
69
|
+
color: var(--el-text-color-regular);
|
|
70
|
+
line-height: 1.6;
|
|
71
|
+
overflow-wrap: anywhere;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.mfa-recovery-dialog__codes span {
|
|
75
|
+
flex: 1;
|
|
76
|
+
min-width: 0;
|
|
77
|
+
white-space: pre-wrap;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.mfa-recovery-dialog__copy {
|
|
81
|
+
color: var(--el-color-primary);
|
|
82
|
+
font-size: 18px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.mfa-recovery-dialog__warning {
|
|
86
|
+
margin: 8px 0 0;
|
|
87
|
+
color: var(--el-color-warning);
|
|
88
|
+
font-size: 13px;
|
|
89
|
+
line-height: 1.6;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="uri" class="mfa-setup-panel">
|
|
3
|
+
<img v-if="qrDataUrl" class="mfa-setup-panel__qr" :src="qrDataUrl" :alt="t('core.login.mfa_setup_qr_alt')" />
|
|
4
|
+
<el-collapse class="mfa-setup-panel__uri">
|
|
5
|
+
<el-collapse-item :title="t('core.login.mfa_show_uri')" name="uri">
|
|
6
|
+
<div class="mfa-setup-panel__value">
|
|
7
|
+
<span class="mfa-setup-panel__text">{{ uri }}</span>
|
|
8
|
+
<el-tooltip :content="t('core.login.mfa_copy_uri')" placement="top">
|
|
9
|
+
<el-button text circle :aria-label="t('core.login.mfa_copy_uri')" @click.stop="copySetupUri">
|
|
10
|
+
<el-icon class="mfa-setup-panel__copy"><CopyDocument /></el-icon>
|
|
11
|
+
</el-button>
|
|
12
|
+
</el-tooltip>
|
|
13
|
+
</div>
|
|
14
|
+
</el-collapse-item>
|
|
15
|
+
</el-collapse>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts" name="MfaSetupPanel">
|
|
20
|
+
import { computed } from "vue";
|
|
21
|
+
import qrcode from "qrcode-generator";
|
|
22
|
+
import { useLocaleStore } from "../../locales";
|
|
23
|
+
import { copyText } from "../../utils/clipboard";
|
|
24
|
+
|
|
25
|
+
/** MFA TOTP 绑定面板属性。 */
|
|
26
|
+
interface MfaSetupPanelProps {
|
|
27
|
+
/** TOTP 绑定地址。 */
|
|
28
|
+
uri: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const props = defineProps<MfaSetupPanelProps>();
|
|
32
|
+
const { t } = useLocaleStore();
|
|
33
|
+
|
|
34
|
+
const qrDataUrl = computed(() => {
|
|
35
|
+
if (!props.uri) return "";
|
|
36
|
+
const code = qrcode(0, "M");
|
|
37
|
+
code.addData(props.uri);
|
|
38
|
+
code.make();
|
|
39
|
+
return code.createDataURL(4, 8);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** 复制 TOTP 绑定地址。 */
|
|
43
|
+
async function copySetupUri() {
|
|
44
|
+
await copyText(props.uri);
|
|
45
|
+
ElMessage.success(t("core.login.mfa_uri_copied"));
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<style scoped lang="scss">
|
|
50
|
+
.mfa-setup-panel__qr {
|
|
51
|
+
display: block;
|
|
52
|
+
width: 220px;
|
|
53
|
+
height: 220px;
|
|
54
|
+
margin: 12px auto;
|
|
55
|
+
image-rendering: pixelated;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.mfa-setup-panel__uri {
|
|
59
|
+
margin-top: 12px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.mfa-setup-panel__value {
|
|
63
|
+
display: flex;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
align-items: flex-start;
|
|
66
|
+
color: var(--el-text-color-regular);
|
|
67
|
+
line-height: 1.6;
|
|
68
|
+
overflow-wrap: anywhere;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.mfa-setup-panel__text {
|
|
72
|
+
flex: 1;
|
|
73
|
+
min-width: 0;
|
|
74
|
+
white-space: pre-wrap;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.mfa-setup-panel__copy {
|
|
78
|
+
color: var(--el-color-primary);
|
|
79
|
+
font-size: 18px;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -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:
|
|
56
|
+
border-radius: var(--admin-page-radius);
|
|
57
57
|
}
|
|
58
58
|
.password-strength__header {
|
|
59
59
|
display: flex;
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
<el-input-number v-else-if="field.component === 'input-number'" v-model="fieldValue" v-bind="fieldProps" />
|
|
15
15
|
|
|
16
|
+
<el-color-picker v-else-if="field.component === 'color-picker'" v-model="fieldValue" v-bind="fieldProps" />
|
|
17
|
+
|
|
16
18
|
<el-segmented v-else-if="field.component === 'segmented'" v-model="fieldValue" :options="fieldOptions" v-bind="fieldProps" />
|
|
17
19
|
|
|
18
20
|
<el-checkbox v-else-if="field.component === 'checkbox'" v-model="fieldValue" v-bind="fieldProps">
|
|
@@ -28,7 +30,12 @@
|
|
|
28
30
|
:label="option.label"
|
|
29
31
|
:value="option.value"
|
|
30
32
|
:disabled="option.disabled"
|
|
31
|
-
|
|
33
|
+
>
|
|
34
|
+
<span class="pro-form-option">
|
|
35
|
+
<component :is="option.icon" v-if="option.icon" />
|
|
36
|
+
<span>{{ option.label }}</span>
|
|
37
|
+
</span>
|
|
38
|
+
</el-option>
|
|
32
39
|
</el-select>
|
|
33
40
|
|
|
34
41
|
<el-radio-group v-else-if="field.component === 'radio-group'" v-model="fieldValue" v-bind="fieldProps">
|
|
@@ -189,3 +196,11 @@ const multipleKvValue = computed({
|
|
|
189
196
|
set: value => setFieldValue(value)
|
|
190
197
|
});
|
|
191
198
|
</script>
|
|
199
|
+
|
|
200
|
+
<style scoped lang="scss">
|
|
201
|
+
.pro-form-option {
|
|
202
|
+
display: inline-flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 6px;
|
|
205
|
+
}
|
|
206
|
+
</style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FormItemRule } from "element-plus";
|
|
2
|
+
import type { Component } from "vue";
|
|
2
3
|
|
|
3
4
|
/** ProForm 支持的字段组件类型。 */
|
|
4
5
|
export type ProFormComponentType =
|
|
@@ -6,6 +7,7 @@ export type ProFormComponentType =
|
|
|
6
7
|
| "password"
|
|
7
8
|
| "textarea"
|
|
8
9
|
| "input-number"
|
|
10
|
+
| "color-picker"
|
|
9
11
|
| "segmented"
|
|
10
12
|
| "switch"
|
|
11
13
|
| "checkbox"
|
|
@@ -31,6 +33,8 @@ export interface ProFormOption {
|
|
|
31
33
|
label: string;
|
|
32
34
|
value: string | number | boolean;
|
|
33
35
|
disabled?: boolean;
|
|
36
|
+
/** 下拉项前置图标。 */
|
|
37
|
+
icon?: Component;
|
|
34
38
|
children?: ProFormOption[];
|
|
35
39
|
/** Element Plus 懒加载树节点是否为叶子节点。 */
|
|
36
40
|
isLeaf?: boolean;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</el-table-column>
|
|
13
13
|
<template #empty>
|
|
14
14
|
<div class="table-empty">
|
|
15
|
-
<img src="../../../assets/images/notData.png" alt="
|
|
15
|
+
<img src="../../../assets/images/notData.png" :alt="t('core.table.no_configurable_columns')" />
|
|
16
16
|
<div>{{ t("core.table.no_configurable_columns") }}</div>
|
|
17
17
|
</div>
|
|
18
18
|
</template>
|
|
@@ -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
|
|
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
|
|
101
|
-
if (!
|
|
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: "
|
|
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
|
|
238
|
+
return renderHeaderLabel(item);
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
241
|
);
|
|
@@ -41,7 +41,11 @@
|
|
|
41
41
|
<el-tooltip v-if="showToolButton('refresh')" :content="t('core.table.refresh')" placement="top">
|
|
42
42
|
<el-button class="tool-button" :icon="Refresh" circle @click="handleToolRefresh" />
|
|
43
43
|
</el-tooltip>
|
|
44
|
-
<el-tooltip
|
|
44
|
+
<el-tooltip
|
|
45
|
+
v-if="showToolButton('setting') && columns.length"
|
|
46
|
+
:content="t('core.table.column_setting')"
|
|
47
|
+
placement="top"
|
|
48
|
+
>
|
|
45
49
|
<el-button class="tool-button" :icon="Operation" circle @click="openColSetting" />
|
|
46
50
|
</el-tooltip>
|
|
47
51
|
<el-tooltip
|
|
@@ -68,7 +72,7 @@
|
|
|
68
72
|
>
|
|
69
73
|
<!-- 默认插槽 -->
|
|
70
74
|
<slot />
|
|
71
|
-
<template v-for="item in tableColumns" :key="item">
|
|
75
|
+
<template v-for="(item, index) in tableColumns" :key="item.prop ?? item.type ?? index">
|
|
72
76
|
<!-- selection || radio || index || expand || sort -->
|
|
73
77
|
<el-table-column
|
|
74
78
|
v-if="item.type && columnTypes.includes(item.type)"
|
|
@@ -107,7 +111,7 @@
|
|
|
107
111
|
<template #empty>
|
|
108
112
|
<div class="table-empty">
|
|
109
113
|
<slot name="empty">
|
|
110
|
-
<img src="../../assets/images/notData.png" alt="
|
|
114
|
+
<img src="../../assets/images/notData.png" :alt="t('common.message.no_data')" />
|
|
111
115
|
<div>{{ t("common.message.no_data") }}</div>
|
|
112
116
|
</slot>
|
|
113
117
|
</div>
|
|
@@ -129,7 +133,7 @@
|
|
|
129
133
|
|
|
130
134
|
<script setup lang="ts" name="ProTable">
|
|
131
135
|
import { ref, watch, provide, onMounted, unref, computed, nextTick, useAttrs, useSlots, isProxy, markRaw, toRaw } from "vue";
|
|
132
|
-
import { ElTable } from "element-plus";
|
|
136
|
+
import { ElTable, type TableInstance } from "element-plus";
|
|
133
137
|
import { useTable } from "../../hooks/useTable";
|
|
134
138
|
import { useSelection } from "../../hooks/useSelection";
|
|
135
139
|
import {
|
|
@@ -167,7 +171,7 @@ const props = withDefaults(defineProps<ProTableProps>(), {
|
|
|
167
171
|
});
|
|
168
172
|
|
|
169
173
|
// table 实例
|
|
170
|
-
const tableRef = ref<
|
|
174
|
+
const tableRef = ref<TableInstance>();
|
|
171
175
|
const attrs = useAttrs();
|
|
172
176
|
const slots = useSlots();
|
|
173
177
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="core-rich-text-preview" v-html="sanitizedHtml" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { computed } from "vue";
|
|
7
|
+
import DOMPurify from "dompurify";
|
|
8
|
+
import { formatSrc } from "../../utils/utils";
|
|
9
|
+
|
|
10
|
+
defineOptions({
|
|
11
|
+
name: "CoreRichTextPreview"
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/** 富文本预览组件属性。 */
|
|
15
|
+
interface RichTextPreviewProps {
|
|
16
|
+
/** 待展示的 HTML 内容。 */
|
|
17
|
+
modelValue: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = defineProps<RichTextPreviewProps>();
|
|
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
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<style scoped lang="scss">
|
|
35
|
+
.core-rich-text-preview {
|
|
36
|
+
line-height: 1.7;
|
|
37
|
+
overflow-wrap: anywhere;
|
|
38
|
+
word-break: break-word;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.core-rich-text-preview :deep(p) {
|
|
42
|
+
margin: 0 0 8px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.core-rich-text-preview :deep(ul),
|
|
46
|
+
.core-rich-text-preview :deep(ol) {
|
|
47
|
+
margin: 0 0 8px;
|
|
48
|
+
padding-left: 24px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.core-rich-text-preview :deep(img),
|
|
52
|
+
.core-rich-text-preview :deep(video) {
|
|
53
|
+
max-width: 100%;
|
|
54
|
+
height: auto;
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<el-button :icon="customIcons[iconValue]" />
|
|
14
14
|
</template>
|
|
15
15
|
</el-input>
|
|
16
|
-
<
|
|
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
|
-
</
|
|
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:
|
|
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:
|
|
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="
|
|
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="[
|
|
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 {
|
|
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; // 组件边框圆角 ==>
|
|
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: "
|
|
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; // 组件边框圆角 ==>
|
|
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: "
|
|
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,
|
|
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,
|
|
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";
|