feat: 优化常量助手函数

master
luoer 2023-09-05 17:46:33 +08:00
parent 56efcc0919
commit 7f88e736e2
16 changed files with 179 additions and 249 deletions

4
.env
View File

@ -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
# =====================================================================================
# 开发设置

View File

@ -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

View File

@ -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`
*/
*/

View File

@ -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 };

View File

@ -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;

42
src/constants/gender.ts Normal file
View File

@ -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 };

1
src/constants/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./gender";

View File

@ -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;
}

View File

@ -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",

View File

@ -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']

2
src/types/env.d.ts vendored
View File

@ -16,7 +16,7 @@ interface ImportMetaEnv {
/**
* API
*/
VITE_API_BASE_URL: string;
VITE_API: string;
/**
*
*/

View File

@ -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);

29
src/utils/delConfirm.ts Normal file
View File

@ -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 };

71
src/utils/extendEnum.ts Normal file
View File

@ -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;
}

2
src/utils/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from "./extendEnum";
export * from "./delConfirm";

View File

@ -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);
});
},
};