@movk/nuxt 0.1.1 → 1.1.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.
- package/README.md +84 -9
- package/dist/module.d.mts +19 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +77 -8
- package/dist/runtime/components/AutoForm.d.vue.ts +12 -6
- package/dist/runtime/components/AutoForm.vue +4 -1
- package/dist/runtime/components/AutoForm.vue.d.ts +12 -6
- package/dist/runtime/components/ColorChooser.d.vue.ts +11 -5
- package/dist/runtime/components/ColorChooser.vue.d.ts +11 -5
- package/dist/runtime/components/DatePicker.d.vue.ts +14 -5
- package/dist/runtime/components/DatePicker.vue.d.ts +14 -5
- package/dist/runtime/components/SlideVerify.d.vue.ts +107 -0
- package/dist/runtime/components/SlideVerify.vue +147 -0
- package/dist/runtime/components/SlideVerify.vue.d.ts +107 -0
- package/dist/runtime/components/StarRating.d.vue.ts +7 -7
- package/dist/runtime/components/StarRating.vue +1 -0
- package/dist/runtime/components/StarRating.vue.d.ts +7 -7
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererArray.d.vue.ts +6 -4
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererArray.vue +1 -1
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererArray.vue.d.ts +6 -4
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererField.d.vue.ts +6 -4
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererField.vue.d.ts +6 -4
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererLayout.d.vue.ts +6 -4
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererLayout.vue.d.ts +6 -4
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererNested.d.vue.ts +6 -4
- package/dist/runtime/components/auto-form-renderer/AutoFormRendererNested.vue.d.ts +6 -4
- package/dist/runtime/components/input/WithCharacterLimit.d.vue.ts +11 -5
- package/dist/runtime/components/input/WithCharacterLimit.vue.d.ts +11 -5
- package/dist/runtime/components/input/WithClear.d.vue.ts +12 -5
- package/dist/runtime/components/input/WithClear.vue.d.ts +12 -5
- package/dist/runtime/components/input/WithCopy.d.vue.ts +12 -5
- package/dist/runtime/components/input/WithCopy.vue.d.ts +12 -5
- package/dist/runtime/components/input/WithPasswordToggle.d.vue.ts +11 -5
- package/dist/runtime/components/input/WithPasswordToggle.vue.d.ts +11 -5
- package/dist/runtime/components/theme-picker/ThemePicker.d.vue.ts +3 -0
- package/dist/runtime/components/theme-picker/ThemePicker.vue +235 -0
- package/dist/runtime/components/theme-picker/ThemePicker.vue.d.ts +3 -0
- package/dist/runtime/components/theme-picker/ThemePickerButton.d.vue.ts +18 -0
- package/dist/runtime/components/theme-picker/ThemePickerButton.vue +34 -0
- package/dist/runtime/components/theme-picker/ThemePickerButton.vue.d.ts +18 -0
- package/dist/runtime/composables/useApiAuth.d.ts +47 -0
- package/dist/runtime/composables/useApiAuth.js +66 -0
- package/dist/runtime/composables/useApiFetch.d.ts +42 -0
- package/dist/runtime/composables/useApiFetch.js +41 -0
- package/dist/runtime/composables/useAutoForm.d.ts +81 -605
- package/dist/runtime/composables/useAutoForm.js +3 -1
- package/dist/runtime/composables/useClientApiFetch.d.ts +24 -0
- package/dist/runtime/composables/useClientApiFetch.js +8 -0
- package/dist/runtime/composables/useDateFormatter.d.ts +21 -7
- package/dist/runtime/composables/useDateFormatter.js +92 -57
- package/dist/runtime/composables/useDownloadWithProgress.d.ts +48 -0
- package/dist/runtime/composables/useDownloadWithProgress.js +85 -0
- package/dist/runtime/composables/useTheme.d.ts +21 -0
- package/dist/runtime/composables/useTheme.js +143 -0
- package/dist/runtime/composables/useUploadWithProgress.d.ts +52 -0
- package/dist/runtime/composables/useUploadWithProgress.js +117 -0
- package/dist/runtime/internal/useAutoFormProvider.js +2 -2
- package/dist/runtime/plugins/api.factory.d.ts +2 -0
- package/dist/runtime/plugins/api.factory.js +186 -0
- package/dist/runtime/plugins/theme.d.ts +2 -0
- package/dist/runtime/plugins/theme.js +89 -0
- package/dist/runtime/schemas/api.d.ts +590 -0
- package/dist/runtime/schemas/api.js +228 -0
- package/dist/runtime/server/api/_movk/session.post.d.ts +10 -0
- package/dist/runtime/server/api/_movk/session.post.js +18 -0
- package/dist/runtime/style.css +1 -0
- package/dist/runtime/types/api.d.ts +218 -0
- package/dist/runtime/types/api.js +0 -0
- package/dist/runtime/types/auth.d.ts +34 -0
- package/dist/runtime/types/auto-form-renderer.d.ts +14 -22
- package/dist/runtime/types/auto-form-renderer.js +0 -0
- package/dist/runtime/types/components.d.ts +29 -41
- package/dist/runtime/types/components.js +0 -0
- package/dist/runtime/types/index.d.ts +1 -0
- package/dist/runtime/types/index.js +3 -2
- package/dist/runtime/utils/api-utils.d.ts +79 -0
- package/dist/runtime/utils/api-utils.js +127 -0
- package/package.json +38 -31
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ApiResponse, RequestToastOptions } from '../types/api.js';
|
|
2
|
+
/**
|
|
3
|
+
* 上传选项(带进度监控)
|
|
4
|
+
*/
|
|
5
|
+
export interface UploadWithProgressOptions {
|
|
6
|
+
/** 文件字段名 @defaultValue 'file' */
|
|
7
|
+
fieldName?: string;
|
|
8
|
+
/** 额外的表单字段 */
|
|
9
|
+
fields?: Record<string, string | Blob>;
|
|
10
|
+
/** 额外的请求头 */
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
/** Toast 配置 */
|
|
13
|
+
toast?: RequestToastOptions | false;
|
|
14
|
+
/** 端点名称 */
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
/** 上传成功回调 */
|
|
17
|
+
onSuccess?: (response: ApiResponse) => void;
|
|
18
|
+
/** 上传失败回调 */
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 带进度监控的文件上传 composable
|
|
23
|
+
*
|
|
24
|
+
* 基于原生 XMLHttpRequest 实现,支持实时进度和取消上传
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const { progress, uploading, upload, abort } = useUploadWithProgress()
|
|
29
|
+
*
|
|
30
|
+
* const { data, error } = await upload('/api/upload', files, {
|
|
31
|
+
* fieldName: 'files',
|
|
32
|
+
* onSuccess: (res) => console.log('上传成功', res)
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function useUploadWithProgress<T = unknown>(): {
|
|
37
|
+
/** 上传进度 (0-100) */
|
|
38
|
+
progress: any;
|
|
39
|
+
/** 是否正在上传 */
|
|
40
|
+
uploading: any;
|
|
41
|
+
/** 上传结果 */
|
|
42
|
+
data: any;
|
|
43
|
+
/** 错误信息 */
|
|
44
|
+
error: any;
|
|
45
|
+
/** 执行上传 */
|
|
46
|
+
upload: (url: string, files: File | File[], options?: UploadWithProgressOptions) => Promise<{
|
|
47
|
+
data: ApiResponse<T> | null;
|
|
48
|
+
error: Error | null;
|
|
49
|
+
}>;
|
|
50
|
+
/** 中止上传 */
|
|
51
|
+
abort: () => void;
|
|
52
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ref, useNuxtApp } from "#imports";
|
|
2
|
+
import {
|
|
3
|
+
showToast,
|
|
4
|
+
isBusinessSuccess,
|
|
5
|
+
extractMessage,
|
|
6
|
+
extractToastMessage,
|
|
7
|
+
getAuthHeaders
|
|
8
|
+
} from "../utils/api-utils.js";
|
|
9
|
+
export function useUploadWithProgress() {
|
|
10
|
+
const { $api } = useNuxtApp();
|
|
11
|
+
const progress = ref(0);
|
|
12
|
+
const uploading = ref(false);
|
|
13
|
+
const data = ref(null);
|
|
14
|
+
const error = ref(null);
|
|
15
|
+
let currentXhr = null;
|
|
16
|
+
const abort = () => {
|
|
17
|
+
currentXhr?.abort();
|
|
18
|
+
currentXhr = null;
|
|
19
|
+
uploading.value = false;
|
|
20
|
+
progress.value = 0;
|
|
21
|
+
};
|
|
22
|
+
const upload = async (url, files, options = {}) => {
|
|
23
|
+
const { fieldName = "file", fields = {}, headers = {}, toast, endpoint, onSuccess, onError } = options;
|
|
24
|
+
const apiInstance = endpoint ? $api.use(endpoint) : $api;
|
|
25
|
+
const config = apiInstance.getConfig();
|
|
26
|
+
const fullUrl = `${config.baseURL || ""}${url}`;
|
|
27
|
+
const formData = new FormData();
|
|
28
|
+
const fileArray = Array.isArray(files) ? files : [files];
|
|
29
|
+
fileArray.forEach((file) => formData.append(fieldName, file));
|
|
30
|
+
Object.entries(fields).forEach(([key, value]) => formData.append(key, value));
|
|
31
|
+
progress.value = 0;
|
|
32
|
+
uploading.value = true;
|
|
33
|
+
data.value = null;
|
|
34
|
+
error.value = null;
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const xhr = new XMLHttpRequest();
|
|
37
|
+
currentXhr = xhr;
|
|
38
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
39
|
+
if (e.lengthComputable) {
|
|
40
|
+
progress.value = Math.round(e.loaded / e.total * 100);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
xhr.addEventListener("load", () => {
|
|
44
|
+
uploading.value = false;
|
|
45
|
+
currentXhr = null;
|
|
46
|
+
try {
|
|
47
|
+
const response = JSON.parse(xhr.responseText);
|
|
48
|
+
const isSuccess = isBusinessSuccess(response, config.response);
|
|
49
|
+
const message = extractMessage(response, config.response);
|
|
50
|
+
if (isSuccess) {
|
|
51
|
+
data.value = response;
|
|
52
|
+
if (import.meta.client && toast !== false) {
|
|
53
|
+
const msg = extractToastMessage(toast, "success", message || "\u4E0A\u4F20\u6210\u529F");
|
|
54
|
+
showToast("success", msg, toast, config.toast);
|
|
55
|
+
}
|
|
56
|
+
onSuccess?.(response);
|
|
57
|
+
resolve({ data: response, error: null });
|
|
58
|
+
} else {
|
|
59
|
+
const err = new Error(message || "\u4E0A\u4F20\u5931\u8D25");
|
|
60
|
+
error.value = err;
|
|
61
|
+
if (import.meta.client && toast !== false) {
|
|
62
|
+
const msg = extractToastMessage(toast, "error", message || "\u4E0A\u4F20\u5931\u8D25");
|
|
63
|
+
showToast("error", msg, toast, config.toast);
|
|
64
|
+
}
|
|
65
|
+
onError?.(err);
|
|
66
|
+
resolve({ data: null, error: err });
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const parseError = err instanceof Error ? err : new Error("\u54CD\u5E94\u89E3\u6790\u5931\u8D25");
|
|
70
|
+
error.value = parseError;
|
|
71
|
+
if (import.meta.client && toast !== false) {
|
|
72
|
+
showToast("error", "\u4E0A\u4F20\u5931\u8D25", toast, config.toast);
|
|
73
|
+
}
|
|
74
|
+
onError?.(parseError);
|
|
75
|
+
resolve({ data: null, error: parseError });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
xhr.addEventListener("error", () => {
|
|
79
|
+
uploading.value = false;
|
|
80
|
+
currentXhr = null;
|
|
81
|
+
const err = new Error("\u7F51\u7EDC\u9519\u8BEF");
|
|
82
|
+
error.value = err;
|
|
83
|
+
if (import.meta.client && toast !== false) {
|
|
84
|
+
showToast("error", "\u4E0A\u4F20\u5931\u8D25", toast, config.toast);
|
|
85
|
+
}
|
|
86
|
+
onError?.(err);
|
|
87
|
+
resolve({ data: null, error: err });
|
|
88
|
+
});
|
|
89
|
+
xhr.addEventListener("abort", () => {
|
|
90
|
+
uploading.value = false;
|
|
91
|
+
currentXhr = null;
|
|
92
|
+
error.value = new Error("\u4E0A\u4F20\u5DF2\u53D6\u6D88");
|
|
93
|
+
resolve({ data: null, error: error.value });
|
|
94
|
+
});
|
|
95
|
+
xhr.open("POST", fullUrl);
|
|
96
|
+
const authHeaders = getAuthHeaders(config);
|
|
97
|
+
Object.entries({ ...headers, ...authHeaders }).forEach(([key, value]) => {
|
|
98
|
+
xhr.setRequestHeader(key, value);
|
|
99
|
+
});
|
|
100
|
+
xhr.send(formData);
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
/** 上传进度 (0-100) */
|
|
105
|
+
progress,
|
|
106
|
+
/** 是否正在上传 */
|
|
107
|
+
uploading,
|
|
108
|
+
/** 上传结果 */
|
|
109
|
+
data,
|
|
110
|
+
/** 错误信息 */
|
|
111
|
+
error,
|
|
112
|
+
/** 执行上传 */
|
|
113
|
+
upload,
|
|
114
|
+
/** 中止上传 */
|
|
115
|
+
abort
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -174,8 +174,8 @@ export function useAutoFormProvider(state, slots) {
|
|
|
174
174
|
return defu(field, {
|
|
175
175
|
meta: {
|
|
176
176
|
fieldSlots: {
|
|
177
|
-
hint: (
|
|
178
|
-
name: open ? "i-lucide-chevron-down" : "i-lucide-chevron-right",
|
|
177
|
+
hint: (props) => h(UIcon, {
|
|
178
|
+
name: props.open ? "i-lucide-chevron-down" : "i-lucide-chevron-right",
|
|
179
179
|
class: "shrink-0 size-5 transition-transform duration-200"
|
|
180
180
|
})
|
|
181
181
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { getPath, triggerDownload, extractFilename } from "@movk/core";
|
|
2
|
+
import {
|
|
3
|
+
showToast,
|
|
4
|
+
isBusinessSuccess,
|
|
5
|
+
extractMessage,
|
|
6
|
+
extractToastMessage
|
|
7
|
+
} from "../utils/api-utils.js";
|
|
8
|
+
import { defineNuxtPlugin, navigateTo, useNuxtApp, useUserSession, useRuntimeConfig } from "#imports";
|
|
9
|
+
import defu from "defu";
|
|
10
|
+
function getUserSession() {
|
|
11
|
+
try {
|
|
12
|
+
const nuxtApp = useNuxtApp();
|
|
13
|
+
return nuxtApp.runWithContext(() => useUserSession());
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getTokenFromSession(tokenPath) {
|
|
19
|
+
const userSession = getUserSession();
|
|
20
|
+
if (!userSession?.session?.value) return null;
|
|
21
|
+
const sessionData = userSession.session.value;
|
|
22
|
+
return getPath(sessionData, tokenPath) || null;
|
|
23
|
+
}
|
|
24
|
+
function buildAuthHeader(token, config) {
|
|
25
|
+
const tokenType = config.tokenType === "Custom" ? config.customTokenType || "" : config.tokenType || "Bearer";
|
|
26
|
+
return tokenType ? `${tokenType} ${token}` : token;
|
|
27
|
+
}
|
|
28
|
+
async function handleUnauthorized(config) {
|
|
29
|
+
const userSession = getUserSession();
|
|
30
|
+
const unauthorizedConfig = config.unauthorized;
|
|
31
|
+
if (unauthorizedConfig?.clearSession && userSession?.clear) {
|
|
32
|
+
await userSession.clear();
|
|
33
|
+
}
|
|
34
|
+
if (unauthorizedConfig?.redirect) {
|
|
35
|
+
const loginPath = unauthorizedConfig.loginPath || "/login";
|
|
36
|
+
const nuxtApp = useNuxtApp();
|
|
37
|
+
await nuxtApp.runWithContext(() => navigateTo(loginPath));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function getApiFetchContext(context) {
|
|
41
|
+
return context.options.context || {};
|
|
42
|
+
}
|
|
43
|
+
function createBuiltinHooks(resolvedConfig, publicConfig) {
|
|
44
|
+
const { auth: authConfig, toast: toastConfig, response: responseConfig } = resolvedConfig;
|
|
45
|
+
return {
|
|
46
|
+
onRequest(context) {
|
|
47
|
+
if (authConfig.enabled) {
|
|
48
|
+
const tokenPath = authConfig.sessionTokenPath || "token";
|
|
49
|
+
const token = getTokenFromSession(tokenPath);
|
|
50
|
+
if (token) {
|
|
51
|
+
const headerName = authConfig.headerName || "Authorization";
|
|
52
|
+
const headerValue = buildAuthHeader(token, authConfig);
|
|
53
|
+
context.options.headers = context.options.headers || new Headers();
|
|
54
|
+
if (context.options.headers instanceof Headers) {
|
|
55
|
+
context.options.headers.set(headerName, headerValue);
|
|
56
|
+
} else {
|
|
57
|
+
context.options.headers[headerName] = headerValue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (publicConfig.debug) {
|
|
62
|
+
console.log(`[Movk API] Request: ${context.options.method || "GET"} ${resolvedConfig.baseURL}${context.request}`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async onRequestError({ error }) {
|
|
66
|
+
if (publicConfig.debug) {
|
|
67
|
+
console.error("[Movk API] Request Error:", error);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async onResponse(context) {
|
|
71
|
+
const response = context.response;
|
|
72
|
+
const data = response._data;
|
|
73
|
+
if (publicConfig.debug) {
|
|
74
|
+
console.log("[Movk API] Response:", data);
|
|
75
|
+
}
|
|
76
|
+
if (!import.meta.client) return;
|
|
77
|
+
const { toast, skipBusinessCheck } = getApiFetchContext(context);
|
|
78
|
+
const isSuccess = skipBusinessCheck || isBusinessSuccess(data, responseConfig);
|
|
79
|
+
const message = extractMessage(data, responseConfig);
|
|
80
|
+
if (isSuccess) {
|
|
81
|
+
const successMessage = toast !== false ? toast?.successMessage || message : void 0;
|
|
82
|
+
showToast("success", successMessage, toast, toastConfig);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
async onResponseError(context) {
|
|
86
|
+
const { response } = context;
|
|
87
|
+
if (response.status === 401) {
|
|
88
|
+
await handleUnauthorized(authConfig);
|
|
89
|
+
}
|
|
90
|
+
if (publicConfig.debug) {
|
|
91
|
+
console.error("[Movk API] Error:", response.status, response._data);
|
|
92
|
+
}
|
|
93
|
+
if (!import.meta.client) return;
|
|
94
|
+
const { toast } = getApiFetchContext(context);
|
|
95
|
+
const data = response._data;
|
|
96
|
+
const message = data ? extractMessage(data, responseConfig) : void 0;
|
|
97
|
+
const errorMessage = toast !== false ? toast?.errorMessage || message || `\u8BF7\u6C42\u5931\u8D25 (${response.status})` : void 0;
|
|
98
|
+
showToast("error", errorMessage, toast, toastConfig);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function createApiClient(resolvedConfig, publicConfig, getOrCreateEndpoint) {
|
|
103
|
+
const builtinHooks = createBuiltinHooks(resolvedConfig, publicConfig);
|
|
104
|
+
resolvedConfig.builtinHooks = builtinHooks;
|
|
105
|
+
const $fetchInstance = $fetch.create({
|
|
106
|
+
baseURL: resolvedConfig.baseURL,
|
|
107
|
+
headers: resolvedConfig.headers,
|
|
108
|
+
onRequest: builtinHooks.onRequest,
|
|
109
|
+
onRequestError: builtinHooks.onRequestError,
|
|
110
|
+
onResponse: builtinHooks.onResponse,
|
|
111
|
+
onResponseError: builtinHooks.onResponseError
|
|
112
|
+
});
|
|
113
|
+
const download = async (url, filename, options = {}) => {
|
|
114
|
+
const { toast, ...fetchOptions } = options;
|
|
115
|
+
const response = await $fetchInstance.raw(url, {
|
|
116
|
+
...fetchOptions,
|
|
117
|
+
method: "GET",
|
|
118
|
+
responseType: "blob",
|
|
119
|
+
context: { toast }
|
|
120
|
+
});
|
|
121
|
+
if (!response._data) {
|
|
122
|
+
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u672A\u63A5\u6536\u5230\u6570\u636E");
|
|
123
|
+
}
|
|
124
|
+
const finalFilename = filename || extractFilename(response.headers, url.split("/").pop() || "download");
|
|
125
|
+
triggerDownload(response._data, finalFilename);
|
|
126
|
+
if (import.meta.client && toast !== false) {
|
|
127
|
+
const message = extractToastMessage(toast, "success", `\u4E0B\u8F7D\u6210\u529F: ${finalFilename}`);
|
|
128
|
+
showToast("success", message, toast, resolvedConfig.toast);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const upload = async (url, file, options = {}) => {
|
|
132
|
+
const { toast, fieldName = "file", ...fetchOptions } = options;
|
|
133
|
+
const formData = file instanceof FormData ? file : (() => {
|
|
134
|
+
const fd = new FormData();
|
|
135
|
+
const files = Array.isArray(file) ? file : [file];
|
|
136
|
+
files.forEach((f) => fd.append(fieldName, f));
|
|
137
|
+
return fd;
|
|
138
|
+
})();
|
|
139
|
+
return $fetchInstance(url, {
|
|
140
|
+
...fetchOptions,
|
|
141
|
+
method: "POST",
|
|
142
|
+
body: formData,
|
|
143
|
+
context: { toast }
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
$fetch: $fetchInstance,
|
|
148
|
+
use: (endpoint) => getOrCreateEndpoint(endpoint),
|
|
149
|
+
download,
|
|
150
|
+
upload,
|
|
151
|
+
getConfig: () => resolvedConfig
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export default defineNuxtPlugin(() => {
|
|
155
|
+
const runtimeConfig = useRuntimeConfig();
|
|
156
|
+
const publicConfig = runtimeConfig.public.movkApi;
|
|
157
|
+
const privateConfig = import.meta.server ? runtimeConfig.movkApi : void 0;
|
|
158
|
+
const endpointCache = /* @__PURE__ */ new Map();
|
|
159
|
+
const getOrCreateEndpoint = (endpointName) => {
|
|
160
|
+
if (endpointCache.has(endpointName)) {
|
|
161
|
+
return endpointCache.get(endpointName);
|
|
162
|
+
}
|
|
163
|
+
const endpoints = publicConfig.endpoints || {};
|
|
164
|
+
const endpointConfig = endpoints[endpointName];
|
|
165
|
+
if (!endpointConfig) {
|
|
166
|
+
console.warn(`[Movk API] Endpoint "${endpointName}" not found, using default`);
|
|
167
|
+
return getOrCreateEndpoint(publicConfig.defaultEndpoint || "default");
|
|
168
|
+
}
|
|
169
|
+
const privateEndpointConfig = privateConfig?.endpoints?.[endpointName];
|
|
170
|
+
const resolvedConfig = {
|
|
171
|
+
...endpointConfig,
|
|
172
|
+
headers: privateEndpointConfig?.headers,
|
|
173
|
+
auth: defu(endpointConfig.auth, publicConfig.auth),
|
|
174
|
+
toast: defu(endpointConfig.toast, publicConfig.toast),
|
|
175
|
+
response: defu(endpointConfig.response, publicConfig.response)
|
|
176
|
+
};
|
|
177
|
+
const client = createApiClient(resolvedConfig, publicConfig, getOrCreateEndpoint);
|
|
178
|
+
endpointCache.set(endpointName, client);
|
|
179
|
+
return client;
|
|
180
|
+
};
|
|
181
|
+
const defaultEndpoint = publicConfig.defaultEndpoint || "default";
|
|
182
|
+
const api = getOrCreateEndpoint(defaultEndpoint);
|
|
183
|
+
return {
|
|
184
|
+
provide: { api }
|
|
185
|
+
};
|
|
186
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useAppConfig, useHead, useSiteConfig } from "#imports";
|
|
2
|
+
export default defineNuxtPlugin({
|
|
3
|
+
enforce: "post",
|
|
4
|
+
setup() {
|
|
5
|
+
const appConfig = useAppConfig();
|
|
6
|
+
const site = useSiteConfig();
|
|
7
|
+
if (import.meta.client) {
|
|
8
|
+
let updateColor = function(type) {
|
|
9
|
+
const color = localStorage.getItem(`${site.name}-ui-${type}`);
|
|
10
|
+
if (color) {
|
|
11
|
+
appConfig.ui.colors[type] = color;
|
|
12
|
+
}
|
|
13
|
+
}, updateRadius = function() {
|
|
14
|
+
const radius = localStorage.getItem(`${site.name}-ui-radius`);
|
|
15
|
+
if (radius) {
|
|
16
|
+
appConfig.theme.radius = Number.parseFloat(radius);
|
|
17
|
+
}
|
|
18
|
+
}, updateBlackAsPrimary = function() {
|
|
19
|
+
const blackAsPrimary = localStorage.getItem(`${site.name}-ui-black-as-primary`);
|
|
20
|
+
if (blackAsPrimary) {
|
|
21
|
+
appConfig.theme.blackAsPrimary = blackAsPrimary === "true";
|
|
22
|
+
}
|
|
23
|
+
}, updateFont = function() {
|
|
24
|
+
const font = localStorage.getItem(`${site.name}-ui-font`);
|
|
25
|
+
if (font) {
|
|
26
|
+
appConfig.theme.font = font;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
updateColor("primary");
|
|
30
|
+
updateColor("neutral");
|
|
31
|
+
updateRadius();
|
|
32
|
+
updateBlackAsPrimary();
|
|
33
|
+
updateFont();
|
|
34
|
+
}
|
|
35
|
+
if (import.meta.server) {
|
|
36
|
+
useHead({
|
|
37
|
+
script: [{
|
|
38
|
+
innerHTML: `
|
|
39
|
+
let html = document.querySelector('style#nuxt-ui-colors').innerHTML;
|
|
40
|
+
|
|
41
|
+
if (localStorage.getItem('${site.name}-ui-primary')) {
|
|
42
|
+
const primaryColor = localStorage.getItem('${site.name}-ui-primary');
|
|
43
|
+
if (primaryColor !== 'black') {
|
|
44
|
+
html = html.replace(
|
|
45
|
+
/(--ui-color-primary-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.primary}(-\\d{2,3}.*?\\))/g,
|
|
46
|
+
\`$1\${primaryColor}$2\`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (localStorage.getItem('${site.name}-ui-neutral')) {
|
|
51
|
+
let neutralColor = localStorage.getItem('${site.name}-ui-neutral');
|
|
52
|
+
html = html.replace(
|
|
53
|
+
/(--ui-color-neutral-\\d{2,3}:\\s*var\\(--color-)${appConfig.ui.colors.neutral}(-\\d{2,3}.*?\\))/g,
|
|
54
|
+
\`$1\${neutralColor === 'neutral' ? 'old-neutral' : neutralColor}$2\`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
document.querySelector('style#nuxt-ui-colors').innerHTML = html;
|
|
59
|
+
`.replace(/\s+/g, " "),
|
|
60
|
+
type: "text/javascript",
|
|
61
|
+
tagPriority: -1
|
|
62
|
+
}, {
|
|
63
|
+
innerHTML: `
|
|
64
|
+
if (localStorage.getItem('${site.name}-ui-radius')) {
|
|
65
|
+
document.querySelector('style#nuxt-ui-radius').innerHTML = ':root { --ui-radius: ' + localStorage.getItem('${site.name}-ui-radius') + 'rem; }';
|
|
66
|
+
}
|
|
67
|
+
`.replace(/\s+/g, " "),
|
|
68
|
+
type: "text/javascript",
|
|
69
|
+
tagPriority: -1
|
|
70
|
+
}, {
|
|
71
|
+
innerHTML: `
|
|
72
|
+
if (localStorage.getItem('${site.name}-ui-black-as-primary') === 'true') {
|
|
73
|
+
document.querySelector('style#nuxt-ui-black-as-primary').innerHTML = ':root { --ui-primary: black; } .dark { --ui-primary: white; }';
|
|
74
|
+
} else {
|
|
75
|
+
document.querySelector('style#nuxt-ui-black-as-primary').innerHTML = '';
|
|
76
|
+
}
|
|
77
|
+
`.replace(/\s+/g, " ")
|
|
78
|
+
}, {
|
|
79
|
+
innerHTML: `
|
|
80
|
+
if (localStorage.getItem('${site.name}-ui-font')) {
|
|
81
|
+
const font = localStorage.getItem('${site.name}-ui-font');
|
|
82
|
+
document.querySelector('style#nuxt-ui-font').innerHTML = ':root { --font-sans: \\'' + font + '\\', sans-serif; }';
|
|
83
|
+
}
|
|
84
|
+
`.replace(/\s+/g, " ")
|
|
85
|
+
}]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|