diff --git a/.env b/.env index 57f4a0a..1ead4d4 100644 --- a/.env +++ b/.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 # ===================================================================================== # 开发设置 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0d46d2c..fdaddc5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 diff --git a/scripts/openapi/index.ts b/scripts/openapi/index.ts index 299678b..f30ee6f 100644 --- a/scripts/openapi/index.ts +++ b/scripts/openapi/index.ts @@ -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` 关键字 - */ \ No newline at end of file + */ diff --git a/src/api/instance/api.ts b/src/api/instance/api.ts index 8c155c7..336ce40 100644 --- a/src/api/instance/api.ts +++ b/src/api/instance/api.ts @@ -21,8 +21,8 @@ class Service extends Api { * 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 }; diff --git a/src/components/table/use-table.tsx b/src/components/table/use-table.tsx index 3035722..6f855bb 100644 --- a/src/components/table/use-table.tsx +++ b/src/components/table/use-table.tsx @@ -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; diff --git a/src/constants/gender.ts b/src/constants/gender.ts new file mode 100644 index 0000000..920e8a9 --- /dev/null +++ b/src/constants/gender.ts @@ -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 }; diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..b2facdc --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1 @@ +export * from "./gender"; diff --git a/src/pages/_login/index.vue b/src/pages/_login/index.vue index 5be44d6..90f7886 100644 --- a/src/pages/_login/index.vue +++ b/src/pages/_login/index.vue @@ -39,9 +39,9 @@
记住我 - 忘记密码? + 忘记密码?
- + 立即登录

暂不支持其他方式登录

@@ -85,7 +85,7 @@ const formRules: Record = { ], }; -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; } diff --git a/src/pages/system/user/index.vue b/src/pages/system/user/index.vue index 6d2d1b8..fbc7bb2 100644 --- a/src/pages/system/user/index.vue +++ b/src/pages/system/user/index.vue @@ -87,14 +87,6 @@ const table = useTable({ }, create: { title: "新建用户", - trigger: () => ( - - ), 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", diff --git a/src/types/auto-component.d.ts b/src/types/auto-component.d.ts index 19dfc9f..1570305 100644 --- a/src/types/auto-component.d.ts +++ b/src/types/auto-component.d.ts @@ -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'] diff --git a/src/types/env.d.ts b/src/types/env.d.ts index ce450dc..c7b8b84 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -16,7 +16,7 @@ interface ImportMetaEnv { /** * API 地址 */ - VITE_API_BASE_URL: string; + VITE_API: string; /** * 开发服务器主机 */ diff --git a/src/utils/defineConstants.ts b/src/utils/defineConstants.ts deleted file mode 100644 index 0ff44fa..0000000 --- a/src/utils/defineConstants.ts +++ /dev/null @@ -1,112 +0,0 @@ - -type MergeIntersection = A extends infer T ? { [Key in keyof T]: T[Key] } : never; - -interface Item { - label: string; - value: any; - enumKey: string; - [key: string]: any; -} - -type ConstantType = MergeIntersection< - { - /** - * 枚举值 - */ - [K in T[number]as K["enumKey"]]: K["value"]; - } & { - /** - * 格式化 - * @param value value值 - * @param key 指定返回的属性,默认为label - */ - format(value: K, key?: keyof T[number]): any; - /** - * 所有值组成的数组 - */ - values: any[]; - /** - * 返回数组,由指定属性的值组成 - */ - prop(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(items: T): ConstantType { - 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); diff --git a/src/utils/delConfirm.ts b/src/utils/delConfirm.ts new file mode 100644 index 0000000..df962a6 --- /dev/null +++ b/src/utils/delConfirm.ts @@ -0,0 +1,29 @@ +import { Modal, ModalConfig } from "@arco-design/web-vue"; +import { merge } from "lodash-es"; + +type DelOptions = string | Partial>; + +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((onOk: () => void, onCancel) => { + Modal.open({ ...merged, onOk, onCancel }); + }); +}; + +export { delConfirm }; diff --git a/src/utils/extendEnum.ts b/src/utils/extendEnum.ts new file mode 100644 index 0000000..304d0d3 --- /dev/null +++ b/src/utils/extendEnum.ts @@ -0,0 +1,71 @@ +type MergeIntersection = A extends infer T ? { [Key in keyof T]: T[Key] } : never; + +interface Item { + label: string; + value: any; + [key: string]: any; +} + +interface ProtoInterface { + /** + * 格式化 + * @param value 值 + * @param key 属性名,默认为 `label` + */ + fmt(value: K, key?: keyof T[number]): any; + + /** + * 返回数组,由指定属性的值组成 + * @description 默认返回 value 属性值组成的数组 + */ + val(key: K): T[number][K][]; + + /** + * 原始项数组 + */ + raw(): T; + + /** + * 字典项映射 + */ + map(): { + [k in T[number] as k["value"]]: k; + }; +} + +class Proto implements ProtoInterface { + _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 = MergeIntersection>; + +/** + * 扩展枚举对象,添加额外的方法 + * @param target 枚举对象 + * @param items 对象数组 + * @returns + */ +export function extendEnum(target: T, items: I): EnumType { + Object.assign(target, { _raw: items }); + Object.setPrototypeOf(target, Proto.prototype); + return target as any; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..f481c71 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./extendEnum"; +export * from "./delConfirm"; diff --git a/src/utils/modal.ts b/src/utils/modal.ts deleted file mode 100644 index 9c9713d..0000000 --- a/src/utils/modal.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Modal, ModalConfig } from "@arco-design/web-vue"; -import { merge } from "lodash-es"; - -export const modal = { - delConfirm(config: Partial> = {}) { - return new Promise((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); - }); - }, -};