diff --git a/packages/base/data/listTableDialog.vue b/packages/base/data/listTableDialog.vue
new file mode 100644
index 0000000..4ec89a3
--- /dev/null
+++ b/packages/base/data/listTableDialog.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+ {
+ if (val) {
+ selection.set(row[rowKey], row);
+ } else {
+ selection.delete(row[rowKey]);
+ }
+ }
+ "
+ />
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/base/index.ts b/packages/base/index.ts
index b3aee61..a9a9d75 100644
--- a/packages/base/index.ts
+++ b/packages/base/index.ts
@@ -1,16 +1,32 @@
-import NoobTag from './item/tag.vue';
-import NoobButton from './item/button.vue';
-import NoobSelect from './item/select.vue';
-import NoobInput from './item/input.vue';
-import NoobDate from './item/datetime.vue';
-import LightBox from './item/light-box.vue';
+import NoobTag from "./item/tag.vue";
+import NoobButton from "./item/button.vue";
+import NoobSelect from "./item/select.vue";
+import NoobInput from "./item/input.vue";
+import NoobDate from "./item/datetime.vue";
+import LightBox from "./item/light-box.vue";
+import ButtonWithTooltip from "./item/buttonWithTooltip.vue";
+import ConfirmCancel from "./item/confirmCancel.vue";
+import TzDatePicker from "./item/tzDatePicker.vue";
+import TzDateTime from "./item/tzDateTime.vue";
+import SearchRow from "./data/search-row.vue";
+import ListTable from "./data/list-table.vue";
+import Infomation from "./data/infomation.vue";
+import ModifyForm from "./data/modify-form.vue";
+import Descriptions from "./data/descriptions.vue";
+import TableAction from "./data/table-action.vue";
+import ListTableDialog from "./data/listTableDialog.vue";
-export { NoobTag, NoobButton, NoobSelect, NoobInput, NoobDate, LightBox };
+export {
+ NoobTag,
+ NoobButton,
+ NoobSelect,
+ NoobInput,
+ NoobDate,
+ LightBox,
+ ButtonWithTooltip,
+ TzDateTime,
+ TzDatePicker,
+ ConfirmCancel,
+};
-import SearchRow from './data/search-row.vue';
-import ListTable from './data/list-table.vue';
-import Infomation from './data/infomation.vue';
-import ModifyForm from './data/modify-form.vue';
-import Descriptions from './data/descriptions.vue';
-import TableAction from './data/table-action.vue';
-export { SearchRow, ListTable, Infomation, ModifyForm, Descriptions, TableAction };
+export { SearchRow, ListTable, ListTableDialog, Infomation, ModifyForm, Descriptions, TableAction };
diff --git a/packages/base/item/buttonWithTooltip.vue b/packages/base/item/buttonWithTooltip.vue
new file mode 100644
index 0000000..12c073f
--- /dev/null
+++ b/packages/base/item/buttonWithTooltip.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/base/item/confirmCancel.vue b/packages/base/item/confirmCancel.vue
new file mode 100644
index 0000000..b4dbd53
--- /dev/null
+++ b/packages/base/item/confirmCancel.vue
@@ -0,0 +1,22 @@
+
+
+ {{
+ t("base.confirm")
+ }}
+
+ {{
+ t("base.cancel")
+ }}
+
+
+
+
diff --git a/packages/base/item/datetime.vue b/packages/base/item/datetime.vue
index f9adc0b..f897665 100644
--- a/packages/base/item/datetime.vue
+++ b/packages/base/item/datetime.vue
@@ -1,73 +1,87 @@
-
+
\ No newline at end of file
+
diff --git a/packages/base/item/tzDatePicker.vue b/packages/base/item/tzDatePicker.vue
new file mode 100644
index 0000000..3677920
--- /dev/null
+++ b/packages/base/item/tzDatePicker.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
diff --git a/packages/base/item/tzDateTime.vue b/packages/base/item/tzDateTime.vue
new file mode 100644
index 0000000..ff9c0db
--- /dev/null
+++ b/packages/base/item/tzDateTime.vue
@@ -0,0 +1,55 @@
+
+ {{ display }}
+
+
diff --git a/packages/echarts/line/smoothed.vue b/packages/echarts/line/smoothed.vue
index 93aaf22..7271782 100644
--- a/packages/echarts/line/smoothed.vue
+++ b/packages/echarts/line/smoothed.vue
@@ -2,78 +2,84 @@
\ No newline at end of file
+
diff --git a/packages/manage/router/index.vue b/packages/manage/router/index.vue
index 73c8198..80cb250 100644
--- a/packages/manage/router/index.vue
+++ b/packages/manage/router/index.vue
@@ -1,247 +1,241 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/packages/manage/router/zhuBeiDong.vue b/packages/manage/router/zhuBeiDong.vue
index 4037626..7af4004 100644
--- a/packages/manage/router/zhuBeiDong.vue
+++ b/packages/manage/router/zhuBeiDong.vue
@@ -1,239 +1,234 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/packages/manage/views/scope.vue b/packages/manage/views/scope.vue
new file mode 100644
index 0000000..63cf316
--- /dev/null
+++ b/packages/manage/views/scope.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/plugs/composables/index.ts b/plugs/composables/index.ts
new file mode 100644
index 0000000..8e28f4b
--- /dev/null
+++ b/plugs/composables/index.ts
@@ -0,0 +1,6 @@
+export * from "./useActionPers";
+export * from "./useListTable";
+export * from "./useLoading";
+export * from "./useModifyForm";
+export * from "./useSysDict";
+export * from "./useWatchOnce";
diff --git a/plugs/composables/useActionPers.ts b/plugs/composables/useActionPers.ts
new file mode 100644
index 0000000..d880cae
--- /dev/null
+++ b/plugs/composables/useActionPers.ts
@@ -0,0 +1,32 @@
+import { onMounted, toRef } from "vue";
+import { useRoute } from "vue-router";
+import { useStore } from "vuex";
+
+export function useActionPers(parent?: string) {
+ const store = useStore();
+
+ if (!parent) {
+ const route = useRoute();
+ parent = route.path;
+ }
+
+ const update = async () => {
+ await store.dispatch("updateActionPers", parent);
+ };
+
+ const actionPers = toRef(() => {
+ // @ts-ignore
+ const pers = store.state.actionPers[parent] ?? [];
+ return Object.fromEntries(pers.map((code) => [code, true]));
+ });
+
+ onMounted(async () => {
+ await update();
+ });
+
+ return {
+ get: (per) => !!actionPers.value[per],
+ update,
+ actionPers,
+ };
+}
diff --git a/plugs/composables/useListTable.ts b/plugs/composables/useListTable.ts
new file mode 100644
index 0000000..3ba81f0
--- /dev/null
+++ b/plugs/composables/useListTable.ts
@@ -0,0 +1,86 @@
+import { toReactive } from "@vueuse/core";
+import { reactive, ref, shallowRef, watchEffect } from "vue";
+import * as Element from "../element";
+import { useI18n } from "vue3-i18n";
+import { PageResponse } from "../http";
+
+const { showMessage } = Element;
+
+export interface TableColumn {
+ code: string;
+ name?: string;
+ i18n?: string;
+ width: number;
+ dict?: string;
+ slot?: boolean;
+
+ [others: string]: any;
+}
+
+export interface TableData {
+ rows: PageResponse;
+ example: Record;
+}
+
+interface Options {
+ query: (example: any) => Promise | Record[] | undefined>;
+ initialPage?: number;
+ initialPageSize?: number;
+ initExample?: Record;
+ disableAutoQuery?: boolean;
+ deep?: boolean;
+}
+
+export function useListTable(options: Options) {
+ const { t } = useI18n();
+ const { initialPage, initialPageSize, initExample, deep } = options;
+
+ type Row = Record;
+ type Rows = Row[] | PageResponse;
+
+ const empty = () => [] as T[];
+ const rows = deep ? ref(empty()) : shallowRef(empty());
+
+ const setRows = (resp?: Rows) => {
+ if (resp == null) {
+ rows.value = empty();
+ } else {
+ rows.value = resp;
+ if (Array.isArray(resp) || resp.total == null) {
+ for (const key in pageParams) {
+ delete example[key];
+ }
+ }
+ }
+ };
+
+ const pageParams = {
+ page: initialPage || 1,
+ size: initialPageSize || 10,
+ };
+
+ const example = reactive({
+ ...pageParams,
+ ...initExample,
+ });
+
+ const query = async () => {
+ try {
+ const resp = await options.query(example);
+ setRows(resp);
+ } catch (error) {
+ showMessage("error", t("common.errors.listTableQueryError"));
+ }
+ };
+
+ if (!options.disableAutoQuery) {
+ watchEffect(query);
+ }
+
+ return {
+ rows: toReactive(rows),
+ example,
+ setRows,
+ query,
+ };
+}
diff --git a/plugs/composables/useLoading.ts b/plugs/composables/useLoading.ts
new file mode 100644
index 0000000..3121ba4
--- /dev/null
+++ b/plugs/composables/useLoading.ts
@@ -0,0 +1,18 @@
+// composables/useLoading.js
+import { ElLoading } from 'element-plus'
+
+export function useLoading() {
+ const showLoading = (options = {}) => {
+ return ElLoading.service({
+ lock: true,
+ text: 'Loading',
+ spinner: 'el-icon-loading',
+ background: 'rgba(0, 0, 0, 0.3)',
+ ...options
+ })
+ }
+
+ return {
+ showLoading
+ }
+}
diff --git a/plugs/composables/useModifyForm.ts b/plugs/composables/useModifyForm.ts
new file mode 100644
index 0000000..883dd64
--- /dev/null
+++ b/plugs/composables/useModifyForm.ts
@@ -0,0 +1,132 @@
+import { reactive, Ref } from "vue";
+import {} from "noob-mengyxu";
+import * as Element from "../element";
+import { clearAndAssign, deepCopy } from "../util/objectUtil";
+
+const { showMessage } = Element;
+
+export interface FormProp {
+ code: string;
+ name?: string;
+ i18n?: string;
+ dict?: string;
+ slot?: boolean;
+
+ [others: string]: any;
+}
+
+interface Options {
+ props?: FormProp[];
+ formRef?: Ref;
+ handleAdd?: (value) => Promise;
+ handleEdit?: (value) => Promise;
+ handleCancel?: () => Promise;
+ handleError?: (kind: "validation" | "internal", err?) => void;
+ init?: any;
+ extraProps?: string[] | "any" | ((code: string) => boolean);
+}
+
+export function useModifyForm(options: Options) {
+ const { formRef, handleAdd, handleEdit, handleCancel, init, extraProps } = options;
+ const initValue = init ?? {};
+ const props = options.props ?? [];
+ const propMap = Object.fromEntries(props.map((prop) => [prop.code, prop]));
+
+ let isValidProp: (code: string) => boolean;
+
+ if (typeof extraProps === "function") {
+ isValidProp = (code: string) => extraProps(code) as boolean;
+ } else if (Array.isArray(extraProps)) {
+ isValidProp = (code: string) => extraProps.includes(code);
+ } else {
+ isValidProp = (_code: string) => extraProps === "any";
+ }
+
+ const normalizeValue = (code: string, value: any) => {
+ const prop = propMap[code];
+ if (!prop && !isValidProp(code)) return null;
+ if (prop?.dict) {
+ const parsed = parseInt(value);
+ return isNaN(parsed) ? value : parsed;
+ }
+
+ return value;
+ };
+
+ const normalizeModel = (newModel: Record) =>
+ Object.fromEntries(
+ Object.entries(newModel).flatMap(([code, value]) => {
+ const normalized = normalizeValue(code, value);
+ return normalized != null ? [[code, normalized]] : [];
+ })
+ );
+
+ const handleError =
+ options.handleError ??
+ ((kind, err) => {
+ if (kind === "validation") {
+ showMessage("error", "Validation Error");
+ } else if (kind === "internal") {
+ const msg = `Internal Error: ${err}`;
+ showMessage("error", msg);
+ console.error(msg);
+ }
+ });
+
+ return {
+ flagOnClose: null,
+ flagOnAdd: "add",
+ flagOnEdit: "edit",
+
+ data: reactive<{ dialog: any; model: Record }>({
+ dialog: null,
+ model: { ...initValue },
+ }),
+
+ clearModel() {
+ clearAndAssign(this.data.model, initValue);
+ },
+
+ assignModel(newModel: Record) {
+ const normalized = normalizeModel(newModel);
+ this.clearModel();
+ Object.assign(this.data.model, normalized);
+ },
+
+ openAdd() {
+ this.clearModel();
+ formRef?.value?.clearValidate();
+ this.data.dialog = this.flagOnAdd;
+ },
+
+ openEdit(toEdit: Record) {
+ this.assignModel(toEdit);
+ formRef?.value?.clearValidate();
+ this.data.dialog = this.flagOnEdit;
+ },
+
+ close() {
+ this.data.dialog = this.flagOnClose;
+ },
+
+ async onConfirm() {
+ const value = deepCopy(this.data.model);
+ try {
+ if (this.data.dialog === this.flagOnAdd) {
+ await handleAdd?.(value);
+ } else if (this.data.dialog === this.flagOnEdit) {
+ await handleEdit?.(value);
+ }
+ } catch (err) {
+ handleError?.("internal", err);
+ }
+
+ this.close();
+ },
+
+ async onCancel() {
+ await handleCancel?.();
+ this.close();
+ },
+ };
+}
diff --git a/plugs/composables/useSysDict.ts b/plugs/composables/useSysDict.ts
new file mode 100644
index 0000000..7bc33b7
--- /dev/null
+++ b/plugs/composables/useSysDict.ts
@@ -0,0 +1,43 @@
+import { computed } from "vue";
+import { useStore } from "vuex";
+
+export function useSysDict() {
+ type Callback = () => Record | Promise>;
+ const store = useStore();
+ const callbacks: Record = {};
+
+ const normalize = (content: Record) => {
+ if (content == null) return {};
+ return Object.fromEntries(
+ Object.entries(content).map(([k, v]) => {
+ const parsed = parseInt(v);
+ return [k, isNaN(parsed) ? v : parsed];
+ })
+ );
+ };
+
+ const updateDict = async (dictIds: string[], registerCallbacks: Record = {}) => {
+ await store.dispatch("getDictMap", dictIds);
+ Object.assign(callbacks, registerCallbacks);
+
+ for (const id of dictIds) {
+ const cb = callbacks[id];
+ const resp = await cb?.();
+ if (resp) {
+ store.commit("updateDict", [id, normalize(resp)]);
+ }
+ }
+ };
+
+ const unregister = (ids: string[]) => {
+ for (const id of ids) {
+ delete callbacks[id];
+ }
+ };
+
+ return {
+ sysDict: computed>>(() => store.state.dict),
+ updateDict,
+ unregister,
+ };
+}
diff --git a/plugs/composables/useWatchOnce.ts b/plugs/composables/useWatchOnce.ts
new file mode 100644
index 0000000..cb05695
--- /dev/null
+++ b/plugs/composables/useWatchOnce.ts
@@ -0,0 +1,12 @@
+import { nextTick, Ref, watchEffect, WatchEffect } from "vue";
+
+export function watchEffectOnce(cond: Ref, cb: WatchEffect) {
+ const stop = watchEffect((onCleanup) => {
+ if (cond.value) {
+ nextTick(() => stop());
+ cb(onCleanup);
+ }
+ });
+
+ return stop;
+}
diff --git a/plugs/element/listTableDialog.ts b/plugs/element/listTableDialog.ts
new file mode 100644
index 0000000..14e5192
--- /dev/null
+++ b/plugs/element/listTableDialog.ts
@@ -0,0 +1,68 @@
+import { type TableColumn, ListTableDialog, handleAsync } from "noob-mengyxu";
+import { ElMessageBox } from "element-plus";
+import { h } from "vue";
+import { PageResponse } from "../http";
+type Row = Record;
+
+export interface ListTableProps {
+ props: TableColumn[];
+ rowKey: string;
+ initialPage?: number;
+ initialPageSize?: number;
+ initExample?: Record;
+ useDicts?: string[];
+}
+
+interface Options {
+ title: string;
+ props: ListTableProps;
+ query: (example: Record) => Promise>;
+ confirm: (rows: Row[]) => Promise;
+ children?: any;
+}
+
+export function showListTableDialog({ title, props, query, confirm, children }: Options) {
+ const open = async () => {
+ try {
+ await ElMessageBox({
+ title,
+ showConfirmButton: false,
+ showCancelButton: false,
+ message: h(
+ ListTableDialog,
+ {
+ ...props,
+ onQuery: handleAsync(query),
+ onConfirm: handleAsync(async (rows) => {
+ console.log("onConfirm");
+ await confirm(rows);
+ }),
+ onClose: handleAsync(async () => {
+ console.log("onClose");
+ ElMessageBox.close();
+ }),
+ },
+ children
+ ),
+ customStyle: {
+ maxWidth: "80%",
+ },
+ });
+ } catch (err) {
+ if (err === "cancel" || err === "close") {
+ return;
+ }
+
+ throw err;
+ }
+ };
+
+ const close = () => {
+ ElMessageBox.close();
+ };
+
+ return {
+ open,
+ close,
+ };
+}
diff --git a/plugs/http/axios3.ts b/plugs/http/axios3.ts
new file mode 100644
index 0000000..fbb8170
--- /dev/null
+++ b/plugs/http/axios3.ts
@@ -0,0 +1,318 @@
+/* eslint-disable */
+import axios from "axios";
+import * as Lang from "../i18n";
+import { loading, close, showMessage } from "../element";
+const { t } = Lang.i18n;
+
+let router;
+
+const config = {
+ baseURL: process.env.VUE_APP_BASE_URL ? "/api" : "",
+ timeout: 60 * 1000,
+ withCredentials: true, // Check cross-site Access-Control
+};
+
+const _axios = axios.create(config);
+_axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
+_axios.defaults.headers.put["Content-Type"] = "application/json;charset=UTF-8";
+_axios.defaults.headers.delete["Content-Type"] = "application/json;charset=UTF-8";
+
+export const registerBaseUrl = (url) => {
+ _axios.defaults.baseURL = url;
+};
+
+export const registerRouter = (routerP) => {
+ router = routerP;
+};
+
+// Add a request interceptor
+_axios.interceptors.request.use(
+ function (config) {
+ const time = new Date().getTime().toString();
+ const params = config.params;
+ if (params) {
+ delEmpty(params);
+ params.t = time;
+ }
+ const data = config.data;
+ if (data != null && typeof data === "object") {
+ delEmpty(data);
+ data.t = time;
+ }
+ return config;
+ },
+ function (error) {
+ return Promise.reject(error);
+ }
+);
+
+function delEmpty(data) {
+ if (data) {
+ for (const item in data) {
+ if (data.hasOwnProperty(item)) {
+ const val = data[item];
+ if ((val == null || val == "null" || val == "") && val !== 0 && val !== false) {
+ delete data[item];
+ }
+ }
+ }
+ }
+}
+
+// Add a response interceptor
+_axios.interceptors.response.use(
+ function (response) {
+ return response.data;
+ },
+ function (error) {
+ return Promise.reject(error);
+ }
+);
+
+type QueryParams = URLSearchParams | Record | ConstructorParameters[0];
+
+export interface AxiosOptions {
+ noMsg?: boolean | null;
+ noLoading?: boolean | null;
+ filter?: (response) => any;
+ query?: QueryParams;
+}
+
+const defaultFilterResp = (resp) => {
+ const isSuccess = resp.success || resp.status === "ok" || resp.status === "success" || resp.data != null;
+ const isError = resp.success === false || resp.status === "error";
+ if (isSuccess) {
+ return resp.data ? resp.data : resp;
+ } else if (isError) {
+ return false;
+ } else return resp;
+};
+
+export function post(url, data?, options: AxiosOptions = {}) {
+ return new Promise((resolve, reject) => {
+ if (!options.noLoading) {
+ loading();
+ }
+ _axios.post(url, data).then(
+ (response: any) => {
+ handResponse(response, resolve, options);
+ },
+ (err) => {
+ handError(err, reject, options);
+ }
+ );
+ });
+}
+
+function mergeQueryParams(...params: Array) {
+ const res: URLSearchParams = new URLSearchParams();
+ for (const param of params) {
+ if (param == null) continue;
+ let parsed;
+ if (param instanceof URLSearchParams) {
+ parsed = param;
+ } else {
+ parsed = new URLSearchParams(param);
+ }
+
+ parsed
+ .entries()
+ .filter(([k, v]) => v != null && v != "null" && v != "undefined" && v != "")
+ .forEach(([k, v]) => res?.append(k, v));
+ }
+
+ return res ?? new URLSearchParams();
+}
+
+export function get(url, data?: QueryParams, options: AxiosOptions = {}) {
+ return new Promise((resolve, reject) => {
+ if (!options.noLoading) {
+ loading();
+ }
+ _axios.get(url, { params: mergeQueryParams(data, options.query) }).then(
+ (response: any) => {
+ handResponse(response, resolve, options);
+ },
+ (err) => {
+ handError(err, reject, options);
+ }
+ );
+ });
+}
+
+export function put(url, data?, options: AxiosOptions = {}) {
+ return new Promise((resolve, reject) => {
+ if (!options.noLoading) {
+ loading();
+ }
+ _axios.put(url, data, { params: mergeQueryParams(options.query) }).then(
+ (response: any) => {
+ handResponse(response, resolve, options);
+ },
+ (err) => {
+ handError(err, reject, options);
+ }
+ );
+ });
+}
+
+export function delate(url, data?, options: AxiosOptions = {}) {
+ return new Promise((resolve, reject) => {
+ if (!options.noLoading) {
+ loading();
+ }
+ _axios({
+ method: "delete",
+ url: url,
+ data: data,
+ params: mergeQueryParams(options.query),
+ }).then(
+ (response: any) => {
+ handResponse(response, resolve, options);
+ },
+ (err) => {
+ handError(err, reject, options);
+ }
+ );
+ });
+}
+
+function handResponse(response, resolve, options: AxiosOptions = {}) {
+ if (!options.noLoading) {
+ close();
+ }
+
+ const filterResponse = options.filter ?? defaultFilterResp;
+ const result = filterResponse(response);
+
+ if (result) {
+ if (!options.noMsg && response.message) {
+ showMessage("success", response.message);
+ }
+
+ resolve(result);
+ } else {
+ if (response.message == "session timeout") {
+ router?.push("/login");
+ response.message = t("http.unLogin");
+ }
+ if (response.message == "no permission") {
+ response.message = t("http.unPermission");
+ }
+ if (response.message?.indexOf("no permission for ") == 0) {
+ response.message = t("http.noPermission");
+ }
+ if (!options.noMsg && response.message) {
+ showMessage("error", response.message);
+ }
+ response.error && console.log(response.error);
+ resolve(false);
+ }
+}
+
+function handError(err, reject, options: AxiosOptions = {}) {
+ if (!options.noLoading) {
+ close();
+ }
+ if (!options.noMsg) {
+ showMessage("error", t("http.error"));
+ }
+ reject(err);
+}
+
+export function upload(file, url, data) {
+ return new Promise((resolve, reject) => {
+ let param = new FormData(); // 创建form对象
+ param.append("file", file); // 通过append向form对象添加数据
+ if (data) {
+ for (const item in data) {
+ if (data.hasOwnProperty(item)) {
+ param.append(item, data[item]); // 添加form表单中其他数据
+ }
+ }
+ }
+ let config = {
+ headers: { "Content-Type": "multipart/form-data" },
+ };
+ _axios.post(url, param, config).then(
+ (response) => {
+ handResponse(response, resolve);
+ },
+ (err) => {
+ handError(err, reject);
+ }
+ );
+ });
+}
+
+export function getFile(url, data?, options: AxiosOptions = {}) {
+ return new Promise((resolve, reject) => {
+ if (!options.noLoading) {
+ loading();
+ }
+ _axios({
+ method: "get",
+ url: url, // 请求地址
+ params: data, // 参数
+ responseType: "blob", // 表明返回服务器返回的数据类型
+ }).then(
+ (response: any) => {
+ handResponse(response, resolve, options);
+ },
+ (err) => {
+ handError(err, reject, options);
+ }
+ );
+ });
+}
+
+export function download(fileName, url, data = {}, callBack?) {
+ loading();
+ _axios({
+ method: "get",
+ url: url, // 请求地址
+ params: data, // 参数
+ responseType: "blob", // 表明返回服务器返回的数据类型
+ }).then(
+ (response: any) => {
+ close();
+ const reader = new FileReader() as any;
+ reader.readAsText(response);
+ reader.onload = function () {
+ try {
+ const result = JSON.parse(reader.result);
+ if (typeof result === "object") {
+ showMessage("error", result.message, false);
+ if (callBack != null) {
+ callBack(false);
+ }
+ return;
+ }
+ } catch (err) {}
+ const blob = new Blob([response], {
+ type: "application/octet-stream",
+ });
+ const navigator = window.navigator as any;
+ if (navigator.msSaveOrOpenBlob) {
+ navigator.msSaveBlob(blob, fileName);
+ } else {
+ const link = document.createElement("a");
+ link.href = window.URL.createObjectURL(blob);
+ link.download = fileName;
+ link.click();
+ window.URL.revokeObjectURL(link.href);
+ }
+ if (callBack != null) {
+ callBack(true);
+ }
+ };
+ },
+ (err) => {
+ close();
+ showMessage("error", t("http.downFail"));
+ if (callBack != null) {
+ callBack(false);
+ }
+ }
+ );
+}
diff --git a/plugs/http/index.ts b/plugs/http/index.ts
index 57c1c24..74a6fc2 100644
--- a/plugs/http/index.ts
+++ b/plugs/http/index.ts
@@ -1,2 +1,4 @@
-export * as Axios from './axios';
-export * as Axios2 from './axios2';
+export * as Axios from "./axios";
+export * as Axios2 from "./axios2";
+export * as Axios3 from "./axios3";
+export * from "./misc";
diff --git a/plugs/http/misc.ts b/plugs/http/misc.ts
new file mode 100644
index 0000000..9ea829d
--- /dev/null
+++ b/plugs/http/misc.ts
@@ -0,0 +1,10 @@
+export interface PageResponse {
+ data: T[];
+ page?: number;
+ total: number;
+}
+
+export const pageEmpty = (): PageResponse => ({
+ data: [],
+ total: 0,
+});
diff --git a/plugs/index.ts b/plugs/index.ts
index c5fcb54..257495e 100644
--- a/plugs/index.ts
+++ b/plugs/index.ts
@@ -1,7 +1,10 @@
-export * from './config';
-export * as Element from './element';
-export * as Store from './store';
-export * as Http from './http';
-export * as Lang from './i18n';
-export * as Api from './api';
-export * from './constant';
+export * from "./config";
+export * as Element from "./element";
+export * as Store from "./store";
+export * as Http from "./http";
+export * as Lang from "./i18n";
+export * as Api from "./api";
+export * from "./constant";
+export * from "./util/asyncUtil";
+export * from "./util/objectUtil";
+export * from "./composables";
diff --git a/plugs/store/index.ts b/plugs/store/index.ts
index af19b85..66eaf5b 100644
--- a/plugs/store/index.ts
+++ b/plugs/store/index.ts
@@ -16,6 +16,8 @@ export class State {
style = Styles.plain;
size = Size.normal;
actions = [];
+ user = {};
+ actionPers = {};
}
export class Actions {
diff --git a/plugs/util/asyncUtil.ts b/plugs/util/asyncUtil.ts
new file mode 100644
index 0000000..5e32b7b
--- /dev/null
+++ b/plugs/util/asyncUtil.ts
@@ -0,0 +1,21 @@
+export type AsyncHandler = {
+ resolve: (t: T | PromiseLike) => void;
+ reject: (err: any) => void;
+};
+
+type ExtractResult = Args extends [AsyncHandler, ...any[]] ? T : never;
+
+type ExtractArgs = Args extends [AsyncHandler, ...infer Rest] ? Rest : never;
+
+export function useAsyncEmits>(emits: (evt: any, ...args: any[]) => void) {
+ const emitsAsync = (evt: Evt, ...args: ExtractArgs) =>
+ new Promise>((resolve, reject) => {
+ emits(evt, { resolve, reject }, ...args);
+ });
+
+ return emitsAsync;
+}
+
+export function handleAsync(handler: (...args: Args) => Promise) {
+ return ({ resolve, reject }: AsyncHandler, ...args: Args) => handler(...args).then(resolve, reject);
+}
diff --git a/plugs/util/objectUtil.ts b/plugs/util/objectUtil.ts
new file mode 100644
index 0000000..926de7a
--- /dev/null
+++ b/plugs/util/objectUtil.ts
@@ -0,0 +1,22 @@
+import { cloneDeep } from "lodash-es";
+
+export function deepCopy(obj: T): T {
+ return cloneDeep(obj);
+}
+
+export function clearObject(obj: Record) {
+ for (const key in obj) {
+ delete obj[key];
+ }
+}
+
+export function clearAndAssign(target: Record, source: Record) {
+ clearObject(target);
+ Object.assign(target, source);
+}
+
+export function unnest(obj: Record, key: string, prefix: string) {
+ const { [key]: toFlatten, ...rest } = obj;
+ const prefixed = Object.fromEntries(Object.entries(toFlatten).map(([k, v]) => [prefix.concat(k), v]));
+ return { ...rest, ...prefixed };
+}