feat: 优化常量助手函数
parent
56efcc0919
commit
7f88e736e2
4
.env
4
.env
|
|
@ -6,9 +6,9 @@ VITE_TITLE = 绝弹管理后台
|
|||
# 网站副标题
|
||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||
# API接口前缀:参见 axios 的 baseURL
|
||||
VITE_API_PREFIX = http://127.0.0.1:3030/
|
||||
VITE_API = http://127.0.0.1:3030/
|
||||
# API文档地址:需返回符合 OPENAPI 规范的json内容
|
||||
VITE_API_SWAGGER = http://127.0.0.1:3030/openapi.json
|
||||
VITE_OPENAPI = http://127.0.0.1:3030/openapi.json
|
||||
|
||||
# =====================================================================================
|
||||
# 开发设置
|
||||
|
|
|
|||
|
|
@ -1,31 +1,19 @@
|
|||
# 工作流名称,可自定义
|
||||
name: 自动部署
|
||||
# 事件监听,决定什么时候触发该工作流内的任务
|
||||
on:
|
||||
# 在master分支推动到github时触发
|
||||
push:
|
||||
branches: [ master ]
|
||||
# 任务集合,可包含多个任务
|
||||
branches: [master]
|
||||
jobs:
|
||||
# 任务名称
|
||||
build:
|
||||
# 运行的操作系统
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
# 步骤集合,可包含多个步骤
|
||||
steps:
|
||||
# 单个步骤,没有名称,直接使用一个action
|
||||
- uses: actions/checkout@v2
|
||||
# 单个步骤,带有名称,带有参数
|
||||
- name: build and deploy
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: build
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
cd dist
|
||||
git config --global user.name "juetan"
|
||||
git config --global user.email "810335188@qq.com"
|
||||
git init
|
||||
git add -A
|
||||
git commit -m "Build through github action"
|
||||
git push -f "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" master:gh-pages
|
||||
corepack enable
|
||||
pnpm install
|
||||
pnpm build
|
||||
- name: deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./dist
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const env = loadEnv("development", process.cwd());
|
|||
|
||||
const run = async () => {
|
||||
const output = await generateApi({
|
||||
url: env.VITE_API_SWAGGER,
|
||||
url: env.VITE_API_OPENAPI,
|
||||
templates: path.resolve(__dirname, "./template"),
|
||||
output: path.resolve(process.cwd(), "src/api/service"),
|
||||
name: "Api.ts",
|
||||
|
|
@ -28,44 +28,14 @@ const run = async () => {
|
|||
parser: "typescript",
|
||||
},
|
||||
});
|
||||
// const { configuration, getTemplate, renderTemplate, createFile } = output
|
||||
// const { config } = configuration
|
||||
// const { templateInfos } = config
|
||||
// const templateMap = templateInfos.reduce((acc, { fileName, name }) => ({
|
||||
// ...acc,
|
||||
// [name]: getTemplate({ fileName, name }),
|
||||
// }),
|
||||
// {});
|
||||
// const files = [
|
||||
// {
|
||||
// path: config.output,
|
||||
// fileName: 'dataContracts.ts',
|
||||
// content: renderTemplate(templateMap.dataContracts, configuration),
|
||||
// },
|
||||
// {
|
||||
// path: config.output,
|
||||
// fileName: 'httpClient.ts',
|
||||
// content: renderTemplate(templateMap.httpClient, configuration),
|
||||
// },
|
||||
// {
|
||||
// path: config.output,
|
||||
// fileName: 'apiClient.ts',
|
||||
// content: renderTemplate(templateMap.api, configuration),
|
||||
// }
|
||||
// ]
|
||||
// for (const file of files) {
|
||||
// createFile(file)
|
||||
// } pathParams, queryParams, bodyParams, headerParams, formDataParams, responses, method, url
|
||||
debugger
|
||||
return output;
|
||||
};
|
||||
|
||||
run();
|
||||
|
||||
|
||||
/**
|
||||
* 模板修改备注:
|
||||
*
|
||||
* route-docs.ejs
|
||||
* - 移除 `@description` 关键字
|
||||
*/
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ class Service extends Api<unknown> {
|
|||
* API 接口实例
|
||||
* @see src/api/instance/instance.ts
|
||||
*/
|
||||
export const api = new Service({
|
||||
baseURL: import.meta.env.VITE_API_PREFIX,
|
||||
const api = new Service({
|
||||
baseURL: import.meta.env.VITE_API,
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -64,10 +64,9 @@ api.instance.interceptors.response.use(
|
|||
return res;
|
||||
},
|
||||
(error) => {
|
||||
const userStore = useUserStore(store);
|
||||
error.config.closeToast?.();
|
||||
const userStore = useUserStore(store);
|
||||
if (error.response) {
|
||||
console.log("response error", error.response);
|
||||
const code = error.response.data?.code;
|
||||
if (code === 4050 || code === 4051) {
|
||||
userStore.clearUser();
|
||||
|
|
@ -75,8 +74,10 @@ api.instance.interceptors.response.use(
|
|||
}
|
||||
} else if (error.request) {
|
||||
console.log("request error", error.request);
|
||||
Message.error(`提示:请求失败,检查网络状态或稍后再试!`);
|
||||
Message.error(`提示:网络异常,请检查网络是否正常或稍后重试!`);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export { api };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { modal } from "@/utils/modal";
|
||||
import { delConfirm } from "@/utils";
|
||||
import { Doption, Dropdown, Link, Message, TableColumnData } from "@arco-design/web-vue";
|
||||
import { isArray, merge } from "lodash-es";
|
||||
import { reactive } from "vue";
|
||||
|
|
@ -14,7 +14,7 @@ const onClick = async (item: any, columnData: any, getTable: any) => {
|
|||
return;
|
||||
}
|
||||
if (item.type === "delete") {
|
||||
await modal.delConfirm();
|
||||
await delConfirm();
|
||||
try {
|
||||
const resData: any = await item?.onClick?.(columnData);
|
||||
const message = resData?.data?.message;
|
||||
|
|
@ -191,7 +191,7 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
|||
}
|
||||
}
|
||||
}
|
||||
const merged = merge({ modalProps: { titleAlign: 'start', closable: false } }, options.create, options.modify);
|
||||
const merged = merge({ modalProps: { titleAlign: "start", closable: false } }, options.create, options.modify);
|
||||
options.modify = useFormModal(merged as any) as any;
|
||||
} else {
|
||||
options.modify = useFormModal(options.modify as any) as any;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import { extendEnum } from "@/utils";
|
||||
|
||||
/**
|
||||
* 性别枚举
|
||||
*/
|
||||
enum GenderEnum {
|
||||
/**
|
||||
* 男
|
||||
*/
|
||||
Man = 1,
|
||||
/**
|
||||
* 女
|
||||
*/
|
||||
Famale = 2,
|
||||
/**
|
||||
* 保密
|
||||
*/
|
||||
Secret = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
const GenderDict = extendEnum(GenderEnum, [
|
||||
{
|
||||
value: GenderEnum.Man,
|
||||
label: "男",
|
||||
color: "blue",
|
||||
},
|
||||
{
|
||||
value: GenderEnum.Famale,
|
||||
label: "女",
|
||||
color: "pink",
|
||||
},
|
||||
{
|
||||
value: GenderEnum.Secret,
|
||||
label: "保密",
|
||||
color: "gray",
|
||||
},
|
||||
]);
|
||||
|
||||
export { GenderEnum, GenderDict };
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "./gender";
|
||||
|
|
@ -39,9 +39,9 @@
|
|||
<a-space :size="16" direction="vertical">
|
||||
<div class="flex items-center justify-between">
|
||||
<a-checkbox checked="rememberPassword">记住我</a-checkbox>
|
||||
<a-link @click="onForgetPasswordClick">忘记密码?</a-link>
|
||||
<a-link @click="onForgetPassword">忘记密码?</a-link>
|
||||
</div>
|
||||
<a-button type="primary" html-type="submit" long class="mt-2" :loading="loading" @click="onSubmitClick">
|
||||
<a-button type="primary" html-type="submit" long class="mt-2" :loading="loading" @click="onSubmitForm">
|
||||
立即登录
|
||||
</a-button>
|
||||
<p type="text" long class="text-gray-400 text-center m-0">暂不支持其他方式登录</p>
|
||||
|
|
@ -85,7 +85,7 @@ const formRules: Record<string, FieldRule[]> = {
|
|||
],
|
||||
};
|
||||
|
||||
const onForgetPasswordClick = () => {
|
||||
const onForgetPassword = () => {
|
||||
Modal.info({
|
||||
title: "忘记密码?",
|
||||
content: "如已忘记密码,请联系管理员进行密码重置!",
|
||||
|
|
@ -94,9 +94,8 @@ const onForgetPasswordClick = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const onSubmitClick = async () => {
|
||||
const errors = await formRef.value?.validate();
|
||||
if (errors) {
|
||||
const onSubmitForm = async () => {
|
||||
if (await formRef.value?.validate()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
@ -107,9 +106,7 @@ const onSubmitClick = async () => {
|
|||
router.push({ path: (route.query.redirect as string) || "/" });
|
||||
} catch (error: any) {
|
||||
const message = error?.response?.data?.message;
|
||||
if (message) {
|
||||
Message.warning(`提示:${message}`);
|
||||
}
|
||||
message && Message.warning(`提示:${message}`);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,14 +87,6 @@ const table = useTable({
|
|||
},
|
||||
create: {
|
||||
title: "新建用户",
|
||||
trigger: () => (
|
||||
<Button type="primary">
|
||||
{{
|
||||
icon: () => <i class="icon-park-outline-people-plus-one" />,
|
||||
default: () => "添加",
|
||||
}}
|
||||
</Button>
|
||||
),
|
||||
modalProps: {
|
||||
width: 772,
|
||||
maskClosable: false,
|
||||
|
|
@ -120,8 +112,8 @@ const table = useTable({
|
|||
label: "个人描述",
|
||||
type: "textarea",
|
||||
itemProps: {
|
||||
class: 'col-span-2'
|
||||
}
|
||||
class: "col-span-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "password",
|
||||
|
|
|
|||
|
|
@ -7,22 +7,15 @@ export {}
|
|||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AAlert: typeof import('@arco-design/web-vue')['Alert']
|
||||
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
|
||||
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
|
||||
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
|
||||
AButton: typeof import('@arco-design/web-vue')['Button']
|
||||
ACard: typeof import('@arco-design/web-vue')['Card']
|
||||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
||||
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
|
||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
|
||||
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
||||
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
||||
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
||||
AForm: typeof import('@arco-design/web-vue')['Form']
|
||||
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
||||
AImage: typeof import('@arco-design/web-vue')['Image']
|
||||
AInput: typeof import('@arco-design/web-vue')['Input']
|
||||
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
|
||||
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
||||
|
|
@ -30,25 +23,12 @@ declare module '@vue/runtime-core' {
|
|||
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||
AList: typeof import('@arco-design/web-vue')['List']
|
||||
AListItem: typeof import('@arco-design/web-vue')['ListItem']
|
||||
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
|
||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||
AMenuItemGroup: typeof import('@arco-design/web-vue')['MenuItemGroup']
|
||||
AModal: typeof import('@arco-design/web-vue')['Modal']
|
||||
APagination: typeof import('@arco-design/web-vue')['Pagination']
|
||||
ARadio: typeof import('@arco-design/web-vue')['Radio']
|
||||
ARadioGroup: typeof import('@arco-design/web-vue')['RadioGroup']
|
||||
ASpace: typeof import('@arco-design/web-vue')['Space']
|
||||
ASwitch: typeof import('@arco-design/web-vue')['Switch']
|
||||
ATabPane: typeof import('@arco-design/web-vue')['TabPane']
|
||||
ATabs: typeof import('@arco-design/web-vue')['Tabs']
|
||||
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
|
||||
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
|
||||
ATree: typeof import('@arco-design/web-vue')['Tree']
|
||||
AUpload: typeof import('@arco-design/web-vue')['Upload']
|
||||
BreadCrumb: typeof import('./../components/breadcrumb/bread-crumb.vue')['default']
|
||||
BreadPage: typeof import('./../components/breadcrumb/bread-page.vue')['default']
|
||||
Page403: typeof import('./../components/error/page-403.vue')['default']
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ interface ImportMetaEnv {
|
|||
/**
|
||||
* API 地址
|
||||
*/
|
||||
VITE_API_BASE_URL: string;
|
||||
VITE_API: string;
|
||||
/**
|
||||
* 开发服务器主机
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
|
||||
type MergeIntersection<A> = A extends infer T ? { [Key in keyof T]: T[Key] } : never;
|
||||
|
||||
interface Item {
|
||||
label: string;
|
||||
value: any;
|
||||
enumKey: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type ConstantType<T extends readonly Item[]> = MergeIntersection<
|
||||
{
|
||||
/**
|
||||
* 枚举值
|
||||
*/
|
||||
[K in T[number]as K["enumKey"]]: K["value"];
|
||||
} & {
|
||||
/**
|
||||
* 格式化
|
||||
* @param value value值
|
||||
* @param key 指定返回的属性,默认为label
|
||||
*/
|
||||
format<K extends T[number]["value"]>(value: K, key?: keyof T[number]): any;
|
||||
/**
|
||||
* 所有值组成的数组
|
||||
*/
|
||||
values: any[];
|
||||
/**
|
||||
* 返回数组,由指定属性的值组成
|
||||
*/
|
||||
prop<K extends keyof T[number]>(key: K): T[number][K][];
|
||||
/**
|
||||
* 获取指定值的项
|
||||
*/
|
||||
pick(...values: T[number]["value"][]): Item[];
|
||||
/**
|
||||
* 排除指定值的项
|
||||
*/
|
||||
omit(...values: T[number]["value"][]): Item[];
|
||||
/**
|
||||
* 原始项数组
|
||||
*/
|
||||
raw: T;
|
||||
/**
|
||||
* 字典项映射
|
||||
*/
|
||||
map: {
|
||||
[k in T[number]as k["value"]]: k;
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* 提供公共方法
|
||||
*/
|
||||
class Constanter {
|
||||
raw: Item[] = [];
|
||||
pick(...values: any[]) {
|
||||
return this.raw.filter((item) => values.includes(item.value));
|
||||
}
|
||||
omit(...values: any[]) {
|
||||
return this.raw.filter((item) => !values.includes(item.value));
|
||||
}
|
||||
each(key: string) {
|
||||
return this.raw.map((item) => item[key]);
|
||||
}
|
||||
format(value: any, key: string = "label") {
|
||||
return this.raw.find((item) => item.value === value)?.[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义字典常量
|
||||
*/
|
||||
export function defineConstants<T extends readonly Item[]>(items: T): ConstantType<T> {
|
||||
const constants: any = {
|
||||
items,
|
||||
map: {},
|
||||
values: [],
|
||||
};
|
||||
for (const item of items) {
|
||||
constants[item.enumKey] = item.value;
|
||||
constants.map[item.value] = item;
|
||||
constants.values.push(item.value);
|
||||
}
|
||||
return Object.setPrototypeOf(constants, Constanter.prototype);
|
||||
}
|
||||
|
||||
// const media = defineConstants([
|
||||
// {
|
||||
// label: "视频",
|
||||
// value: 1,
|
||||
// enumKey: "VIDEO",
|
||||
// },
|
||||
// {
|
||||
// label: "图片",
|
||||
// value: 2,
|
||||
// enumKey: "IMAGE",
|
||||
// },
|
||||
// {
|
||||
// label: "文本",
|
||||
// value: 3,
|
||||
// enumKey: "TEXT",
|
||||
// },
|
||||
// ] as const);
|
||||
|
||||
// console.log("media", media, media.VIDEO, media.IMAGE, media.TEXT);
|
||||
// console.log("media pick", media.pick(media.VIDEO));
|
||||
// console.log("media omit", media.omit(media.TEXT));
|
||||
// console.log("media each", media.prop("label"));
|
||||
// console.log("media format", media.format(2));
|
||||
// console.log("media maps", media.map);
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { Modal, ModalConfig } from "@arco-design/web-vue";
|
||||
import { merge } from "lodash-es";
|
||||
|
||||
type DelOptions = string | Partial<Omit<ModalConfig, "onOk" | "onCancel">>;
|
||||
|
||||
const delOptions = {
|
||||
title: "提示",
|
||||
titleAlign: "start",
|
||||
width: 432,
|
||||
content: "确定删除该数据吗?注意:该操作不可恢复!",
|
||||
maskClosable: false,
|
||||
closable: false,
|
||||
okText: "确定",
|
||||
okButtonProps: {
|
||||
status: "danger",
|
||||
},
|
||||
};
|
||||
|
||||
const delConfirm = (config: DelOptions = {}) => {
|
||||
if (typeof config === "string") {
|
||||
config = { content: config };
|
||||
}
|
||||
const merged = merge(delOptions, config);
|
||||
return new Promise<void>((onOk: () => void, onCancel) => {
|
||||
Modal.open({ ...merged, onOk, onCancel });
|
||||
});
|
||||
};
|
||||
|
||||
export { delConfirm };
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
type MergeIntersection<A> = A extends infer T ? { [Key in keyof T]: T[Key] } : never;
|
||||
|
||||
interface Item {
|
||||
label: string;
|
||||
value: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface ProtoInterface<T extends readonly Item[]> {
|
||||
/**
|
||||
* 格式化
|
||||
* @param value 值
|
||||
* @param key 属性名,默认为 `label`
|
||||
*/
|
||||
fmt<K extends T[number]["value"]>(value: K, key?: keyof T[number]): any;
|
||||
|
||||
/**
|
||||
* 返回数组,由指定属性的值组成
|
||||
* @description 默认返回 value 属性值组成的数组
|
||||
*/
|
||||
val<K extends keyof T[number]>(key: K): T[number][K][];
|
||||
|
||||
/**
|
||||
* 原始项数组
|
||||
*/
|
||||
raw(): T;
|
||||
|
||||
/**
|
||||
* 字典项映射
|
||||
*/
|
||||
map(): {
|
||||
[k in T[number] as k["value"]]: k;
|
||||
};
|
||||
}
|
||||
|
||||
class Proto implements ProtoInterface<Item[]> {
|
||||
_raw: Item[] = [];
|
||||
|
||||
fmt(value: any, key = "label") {
|
||||
return this._raw.find((item) => item.value === value)?.[key];
|
||||
}
|
||||
|
||||
val(key: any = "value") {
|
||||
return this._raw.map((item) => item[key]);
|
||||
}
|
||||
|
||||
raw() {
|
||||
return this._raw;
|
||||
}
|
||||
|
||||
map() {
|
||||
return this._raw.reduce((acc, cur) => {
|
||||
acc[cur.value] = cur;
|
||||
return acc;
|
||||
}, {} as any);
|
||||
}
|
||||
}
|
||||
|
||||
type EnumType<T extends Object, R extends readonly Item[]> = MergeIntersection<T & ProtoInterface<R>>;
|
||||
|
||||
/**
|
||||
* 扩展枚举对象,添加额外的方法
|
||||
* @param target 枚举对象
|
||||
* @param items 对象数组
|
||||
* @returns
|
||||
*/
|
||||
export function extendEnum<T extends Object, I extends readonly Item[]>(target: T, items: I): EnumType<T, I> {
|
||||
Object.assign(target, { _raw: items });
|
||||
Object.setPrototypeOf(target, Proto.prototype);
|
||||
return target as any;
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./extendEnum";
|
||||
export * from "./delConfirm";
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { Modal, ModalConfig } from "@arco-design/web-vue";
|
||||
import { merge } from "lodash-es";
|
||||
|
||||
export const modal = {
|
||||
delConfirm(config: Partial<Omit<ModalConfig, "onOk" | "onCancel">> = {}) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const mergedConfig = merge(
|
||||
{
|
||||
title: "提示",
|
||||
titleAlign: "start",
|
||||
width: 432,
|
||||
content: "确定删除该数据吗?注意:该操作不可恢复!",
|
||||
maskClosable: false,
|
||||
closable: false,
|
||||
okText: "确定删除",
|
||||
okButtonProps: {
|
||||
status: "danger",
|
||||
},
|
||||
onOk: () => {
|
||||
resolve();
|
||||
},
|
||||
onCancel: () => {
|
||||
reject();
|
||||
},
|
||||
},
|
||||
config
|
||||
);
|
||||
Modal.open(mergedConfig);
|
||||
});
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue