From c4e56b1ea30255208a6415bf0cb4da11f70915c6 Mon Sep 17 00:00:00 2001 From: xingyu Date: Thu, 27 Apr 2023 17:49:33 +0800 Subject: [PATCH] chore: types --- .commitlintrc.js | 91 +++++++++++++++++++++ .lintstagedrc | 9 +++ .prettierrc.js | 27 +++++++ .stylelintrc.js | 78 ++++++++++++++++++ types/axios.d.ts | 56 +++++++++++++ types/config.d.ts | 182 ++++++++++++++++++++++++++++++++++++++++++ types/global.d.ts | 89 +++++++++++++++++++++ types/index.d.ts | 37 +++++++++ types/module.d.ts | 18 +++++ types/store.d.ts | 52 ++++++++++++ types/utils.d.ts | 5 ++ types/vue-router.d.ts | 51 ++++++++++++ 12 files changed, 695 insertions(+) create mode 100644 .commitlintrc.js create mode 100644 .lintstagedrc create mode 100644 .prettierrc.js create mode 100644 .stylelintrc.js create mode 100644 types/axios.d.ts create mode 100644 types/config.d.ts create mode 100644 types/global.d.ts create mode 100644 types/index.d.ts create mode 100644 types/module.d.ts create mode 100644 types/store.d.ts create mode 100644 types/utils.d.ts create mode 100644 types/vue-router.d.ts diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 000000000..c7271bac8 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,91 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const scopes = fs + .readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name.replace(/s$/, '')); + +// precomputed scope +const scopeComplete = execSync('git status --porcelain || true') + .toString() + .trim() + .split('\n') + .find((r) => ~r.indexOf('M src')) + ?.replace(/(\/)/g, '%%') + ?.match(/src%%((\w|-)*)/)?.[1] + ?.replace(/s$/, ''); + +/** @type {import('cz-git').UserConfig} */ +module.exports = { + ignores: [(commit) => commit.includes('init')], + extends: ['@commitlint/config-conventional'], + rules: { + 'body-leading-blank': [2, 'always'], + 'footer-leading-blank': [1, 'always'], + 'header-max-length': [2, 'always', 108], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + 'subject-case': [0], + 'type-enum': [ + 2, + 'always', + ['feat', 'fix', 'perf', 'style', 'docs', 'test', 'refactor', 'build', 'ci', 'chore', 'revert', 'wip', 'workflow', 'types', 'release'], + ], + }, + prompt: { + /** @use `yarn commit :f` */ + alias: { + f: 'docs: fix typos', + r: 'docs: update README', + s: 'style: update code format', + b: 'build: bump dependencies', + c: 'chore: update config', + }, + customScopesAlign: !scopeComplete ? 'top' : 'bottom', + defaultScope: scopeComplete, + scopes: [...scopes, 'mock'], + allowEmptyIssuePrefixs: false, + allowCustomIssuePrefixs: false, + + // English + typesAppend: [ + { value: 'wip', name: 'wip: work in process' }, + { value: 'workflow', name: 'workflow: workflow improvements' }, + { value: 'types', name: 'types: type definition file changes' }, + ], + + // 中英文对照版 + // messages: { + // type: '选择你要提交的类型 :', + // scope: '选择一个提交范围 (可选):', + // customScope: '请输入自定义的提交范围 :', + // subject: '填写简短精炼的变更描述 :\n', + // body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n', + // breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n', + // footerPrefixsSelect: '选择关联issue前缀 (可选):', + // customFooterPrefixs: '输入自定义issue前缀 :', + // footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', + // confirmCommit: '是否提交或修改commit ?', + // }, + // types: [ + // { value: 'feat', name: 'feat: 新增功能' }, + // { value: 'fix', name: 'fix: 修复缺陷' }, + // { value: 'docs', name: 'docs: 文档变更' }, + // { value: 'style', name: 'style: 代码格式' }, + // { value: 'refactor', name: 'refactor: 代码重构' }, + // { value: 'perf', name: 'perf: 性能优化' }, + // { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' }, + // { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' }, + // { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, + // { value: 'revert', name: 'revert: 回滚 commit' }, + // { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' }, + // { value: 'wip', name: 'wip: 正在开发中' }, + // { value: 'workflow', name: 'workflow: 工作流程改进' }, + // { value: 'types', name: 'types: 类型定义文件修改' }, + // ], + // emptyScopesAlias: 'empty: 不填写', + // customScopesAlias: 'custom: 自定义', + }, +}; diff --git a/.lintstagedrc b/.lintstagedrc new file mode 100644 index 000000000..24cb82d7c --- /dev/null +++ b/.lintstagedrc @@ -0,0 +1,9 @@ +// .lintstagedrc.js +module.exports = { + '*.{js,jsx,ts,tsx}': ['prettier --cache --ignore-unknown --write', 'eslint --cache --fix'], + '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --cache --write--parser json'], + 'package.json': ['prettier --cache --write'], + '*.vue': ['prettier --write', 'eslint --cache --fix', 'stylelint --fix'], + '*.{scss,less,styl,html}': ['prettier --cache --ignore-unknown --write', 'stylelint --fix'], + '*.md': ['prettier --cache --ignore-unknown --write'], +}; diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..1930cd3fb --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,27 @@ +module.exports = { + printWidth: 140, + // tab宽度为2空格 + tabWidth: 2, + semi: true, + vueIndentScriptAndStyle: false, + singleQuote: true, + trailingComma: 'all', + proseWrap: 'never', + htmlWhitespaceSensitivity: 'strict', + endOfLine: 'auto', + plugins: ['prettier-plugin-packagejson'], + overrides: [ + { + files: '.*rc', + options: { + parser: 'json', + }, + }, + { + files: '*.html', + options: { + parser: 'html', + }, + }, + ], +}; diff --git a/.stylelintrc.js b/.stylelintrc.js new file mode 100644 index 000000000..e457294a5 --- /dev/null +++ b/.stylelintrc.js @@ -0,0 +1,78 @@ +module.exports = { + root: true, + plugins: ['stylelint-order', 'stylelint-prettier'], + extends: ['stylelint-config-standard', 'stylelint-config-recess-order'], + overrides: [ + { + files: ['**/*.(css|html|vue)'], + customSyntax: 'postcss-html', + }, + { + files: ['*.less', '**/*.less'], + customSyntax: 'postcss-less', + extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'], + }, + ], + rules: { + 'prettier/prettier': true, + 'at-rule-no-unknown': null, + 'selector-not-notation': null, + 'import-notation': null, + 'function-no-unknown': null, + 'selector-class-pattern': null, + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global', 'deep'], + }, + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep'], + }, + ], + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen', 'function', 'if', 'each', 'include', 'mixin'], + }, + ], + 'media-feature-range-notation': null, + 'no-empty-source': null, + 'string-quotes': null, + 'import-notation': null, + 'named-grid-areas-no-invalid': null, + 'no-descending-specificity': null, + 'font-family-no-missing-generic-family-keyword': null, + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'], + }, + ], + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + type: 'at-rule', + name: 'supports', + }, + { + type: 'at-rule', + name: 'media', + }, + { + type: 'at-rule', + name: 'include', + }, + 'rules', + ], + { severity: 'error' }, + ], + }, + ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], +}; diff --git a/types/axios.d.ts b/types/axios.d.ts new file mode 100644 index 000000000..50dfde96e --- /dev/null +++ b/types/axios.d.ts @@ -0,0 +1,56 @@ +export type ErrorMessageMode = 'none' | 'modal' | 'message' | undefined; +export type SuccessMessageMode = ErrorMessageMode; + +export interface RequestOptions { + // Splicing request parameters to url + joinParamsToUrl?: boolean; + // Format request parameter time + formatDate?: boolean; + // Whether to process the request result + isTransformResponse?: boolean; + // Whether to return native response headers + // For example: use this attribute when you need to get the response headers + isReturnNativeResponse?: boolean; + // Whether to join url + joinPrefix?: boolean; + // Interface address, use the default apiUrl if you leave it blank + apiUrl?: string; + // 请求拼接路径 + urlPrefix?: string; + // Error message prompt type + errorMessageMode?: ErrorMessageMode; + // Success message prompt type + successMessageMode?: SuccessMessageMode; + // Whether to add a timestamp + joinTime?: boolean; + ignoreCancelToken?: boolean; + // Whether to send token in header + withToken?: boolean; + // 请求重试机制 + retryRequest?: RetryRequest; +} + +export interface RetryRequest { + isOpenRetry: boolean; + count: number; + waitTime: number; +} + +export interface Result { + code: number; + msg: string; + data: T; +} + +// multipart/form-data: upload file +export interface UploadFileParams { + // Other parameters + data?: Recordable; + // File parameter interface field name + name?: string; + // file name + file: File | Blob; + // file name + filename?: string; + [key: string]: any; +} diff --git a/types/config.d.ts b/types/config.d.ts new file mode 100644 index 000000000..10bff7879 --- /dev/null +++ b/types/config.d.ts @@ -0,0 +1,182 @@ +import { + ContentEnum, + PermissionModeEnum, + RouterTransitionEnum, + SessionTimeoutProcessingEnum, + SettingButtonPositionEnum, + ThemeEnum, +} from '@/enums/appEnum'; +import { CacheTypeEnum } from '@/enums/cacheEnum'; +import { MenuModeEnum, MenuTypeEnum, MixSidebarTriggerEnum, TriggerEnum } from '@/enums/menuEnum'; + +export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko'; + +export type AppSizeType = 'small' | 'middle' | 'large'; + +export interface MenuSetting { + bgColor: string; + fixed: boolean; + collapsed: boolean; + siderHidden: boolean; + canDrag: boolean; + show: boolean; + hidden: boolean; + split: boolean; + menuWidth: number; + mode: MenuModeEnum; + type: MenuTypeEnum; + theme: ThemeEnum; + topMenuAlign: 'start' | 'center' | 'end'; + trigger: TriggerEnum; + accordion: boolean; + closeMixSidebarOnChange: boolean; + collapsedShowTitle: boolean; + mixSideTrigger: MixSidebarTriggerEnum; + mixSideFixed: boolean; +} + +export interface MultiTabsSetting { + cache: boolean; + show: boolean; + showQuick: boolean; + canDrag: boolean; + showRedo: boolean; + showFold: boolean; +} + +export interface HeaderSetting { + bgColor: string; + fixed: boolean; + show: boolean; + theme: ThemeEnum; + // Turn on full screen + showFullScreen: boolean; + // Whether to show the lock screen + useLockPage: boolean; + // Show document button + showDoc: boolean; + // Show message center button + showNotice: boolean; + showSearch: boolean; +} + +export interface LocaleSetting { + showPicker: boolean; + // Current language + locale: LocaleType; + // default language + fallback: LocaleType; + // available Locales + availableLocales: LocaleType[]; +} + +export interface SizeSetting { + showPicker: boolean; + // Current size + size: AppSizeType; + // default size + fallback: AppSizeType; + // available size + availableSizes: AppSizeType[]; +} + +export interface TransitionSetting { + // Whether to open the page switching animation + enable: boolean; + // Route basic switching animation + basicTransition: RouterTransitionEnum; + // Whether to open page switching loading + openPageLoading: boolean; + // Whether to open the top progress bar + openNProgress: boolean; +} + +export interface ProjectConfig { + // Storage location of permission related information + permissionCacheType: CacheTypeEnum; + // Whether to show the configuration button + showSettingButton: boolean; + // Whether to show the theme switch button + showDarkModeToggle: boolean; + // Configure where the button is displayed + settingButtonPosition: SettingButtonPositionEnum; + // Permission mode + permissionMode: PermissionModeEnum; + // Session timeout processing + sessionTimeoutProcessing: SessionTimeoutProcessingEnum; + // Website gray mode, open for possible mourning dates + grayMode: boolean; + // Whether to turn on the color weak mode + colorWeak: boolean; + // Theme color + themeColor: string; + + // The main interface is displayed in full screen, the menu is not displayed, and the top + fullContent: boolean; + // content width + contentMode: ContentEnum; + // Whether to display the logo + showLogo: boolean; + // Whether to show the global footer + showFooter: boolean; + // menuType: MenuTypeEnum; + headerSetting: HeaderSetting; + // menuSetting + menuSetting: MenuSetting; + // Multi-tab settings + multiTabsSetting: MultiTabsSetting; + // Animation configuration + transitionSetting: TransitionSetting; + // pageLayout whether to enable keep-alive + openKeepAlive: boolean; + // Lock screen time + lockTime: number; + // Show breadcrumbs + showBreadCrumb: boolean; + // Show breadcrumb icon + showBreadCrumbIcon: boolean; + // Use error-handler-plugin + useErrorHandle: boolean; + // Whether to open back to top + useOpenBackTop: boolean; + // Is it possible to embed iframe pages + canEmbedIFramePage: boolean; + // Whether to delete unclosed messages and notify when switching the interface + closeMessageOnSwitch: boolean; + // Whether to cancel the http request that has been sent but not responded when switching the interface. + removeAllHttpPending: boolean; +} + +export interface GlobConfig { + // Site title + title: string; + // Service interface url + apiUrl: string; + // Upload url + uploadUrl?: string; + // Service interface url prefix + urlPrefix?: string; + // Project abbreviation + shortName: string; + // 租户开关 + tenantEnable: string; + // 验证码开关 + captchaEnable: string; +} +export interface GlobEnvConfig { + // Site title + VITE_GLOB_APP_TITLE: string; + VITE_GLOB_BASE_URL: string; + // Service interface url + VITE_GLOB_API_URL: string; + // Service interface url prefix + VITE_GLOB_API_URL_PREFIX?: string; + // Project abbreviation + VITE_GLOB_APP_SHORT_NAME: string; + // Upload url + VITE_GLOB_UPLOAD_URL?: string; + // 租户开关 + VITE_GLOB_APP_TENANT_ENABLE: string; + // 验证码开关 + VITE_GLOB_APP_CAPTCHA_ENABLE: string; +} diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 000000000..76d47b9b2 --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,89 @@ +import type { ComponentPublicInstance, ComponentRenderProxy, FunctionalComponent, PropType as VuePropType, VNode, VNodeChild } from 'vue'; + +declare global { + const __APP_INFO__: { + pkg: { + name: string; + version: string; + dependencies: Recordable; + devDependencies: Recordable; + }; + lastBuildTime: string; + }; + // declare interface Window { + // // Global vue app instance + // __APP__: App; + // } + + // vue + declare type PropType = VuePropType; + declare type VueNode = VNodeChild | JSX.Element; + + export type Writable = { + -readonly [P in keyof T]: T[P]; + }; + + declare type Nullable = T | null; + declare type NonNullable = T extends null | undefined ? never : T; + declare type Recordable = Record; + declare interface ReadonlyRecordable { + readonly [key: string]: T; + } + declare interface Indexable { + [key: string]: T; + } + declare type DeepPartial = { + [P in keyof T]?: DeepPartial; + }; + declare type TimeoutHandle = ReturnType; + declare type IntervalHandle = ReturnType; + + declare interface ChangeEvent extends Event { + target: HTMLInputElement; + } + + declare interface WheelEvent { + path?: EventTarget[]; + } + interface ImportMetaEnv extends ViteEnv { + __: unknown; + } + + declare interface ViteEnv { + VITE_PORT: number; + VITE_USE_PWA: boolean; + VITE_PUBLIC_PATH: string; + VITE_PROXY: [string, string][]; + VITE_GLOB_APP_TITLE: string; + VITE_GLOB_APP_SHORT_NAME: string; + VITE_USE_CDN: boolean; + VITE_DROP_CONSOLE: boolean; + VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'; + VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean; + VITE_GENERATE_UI: string; + } + + declare function parseInt(s: string | number, radix?: number): number; + + declare function parseFloat(string: string | number): number; + + namespace JSX { + // tslint:disable no-empty-interface + type Element = VNode; + // tslint:disable no-empty-interface + type ElementClass = ComponentRenderProxy; + interface ElementAttributesProperty { + $props: any; + } + interface IntrinsicElements { + [elem: string]: any; + } + interface IntrinsicAttributes { + [elem: string]: any; + } + } +} + +declare module 'vue' { + export type JSXComponent = { new (): ComponentPublicInstance } | FunctionalComponent; +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 000000000..5dfbc429b --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,37 @@ +declare interface Fn { + (...arg: T[]): R; +} + +declare interface PromiseFn { + (...arg: T[]): Promise; +} + +declare type RefType = T | null; + +declare interface PageParam { + pageSize?: number; + pageNo?: number; +} + +declare interface PageResult { + list: T[]; + total: number; +} + +declare type LabelValueOptions = { + label: string; + value: any; + [key: string]: string | number | boolean; +}[]; + +declare type EmitType = (event: string, ...args: any[]) => void; + +declare type TargetContext = '_self' | '_blank'; + +declare interface ComponentElRef { + $el: T; +} + +declare type ComponentRef = ComponentElRef | null; + +declare type ElRef = Nullable; diff --git a/types/module.d.ts b/types/module.d.ts new file mode 100644 index 000000000..61a0c34e5 --- /dev/null +++ b/types/module.d.ts @@ -0,0 +1,18 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue'; + + const Component: DefineComponent<{}, {}, any>; + export default Component; +} + +declare module 'ant-design-vue/es/locale/*' { + import { Locale } from 'ant-design-vue/types/locale-provider'; + + const locale: Locale & ReadonlyRecordable; + export default locale as Locale & ReadonlyRecordable; +} + +declare module 'virtual:*' { + const result: any; + export default result; +} diff --git a/types/store.d.ts b/types/store.d.ts new file mode 100644 index 000000000..f973304b6 --- /dev/null +++ b/types/store.d.ts @@ -0,0 +1,52 @@ +import { ErrorTypeEnum } from '@/enums/exceptionEnum'; +import { MenuModeEnum, MenuTypeEnum } from '@/enums/menuEnum'; + +// Lock screen information +export interface LockInfo { + // Password required + pwd?: string | undefined; + // Is it locked? + isLock?: boolean; +} + +// Error-log information +export interface ErrorLogInfo { + // Type of error + type: ErrorTypeEnum; + // Error file + file: string; + // Error name + name?: string; + // Error message + message: string; + // Error stack + stack?: string; + // Error detail + detail: string; + // Error url + url: string; + // Error time + time?: string; +} + +export interface UserInfo { + userId: string | number; + username: string; + realName: string; + avatar: string; + desc?: string; + homePath?: string; + roles: string[]; +} + +export interface BeforeMiniState { + menuCollapsed?: boolean; + menuSplit?: boolean; + menuMode?: MenuModeEnum; + menuType?: MenuTypeEnum; +} + +export interface DictState { + dictMap: Map; + isSetDict: boolean; +} diff --git a/types/utils.d.ts b/types/utils.d.ts new file mode 100644 index 000000000..6500d4472 --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,5 @@ +import type { ComputedRef, Ref } from 'vue'; + +export type DynamicProps = { + [P in keyof T]: Ref | T[P] | ComputedRef; +}; diff --git a/types/vue-router.d.ts b/types/vue-router.d.ts new file mode 100644 index 000000000..d06d06a3b --- /dev/null +++ b/types/vue-router.d.ts @@ -0,0 +1,51 @@ +import { RoleEnum } from '@/enums/roleEnum'; + +export {}; + +declare module 'vue-router' { + interface RouteMeta extends Record { + orderNo?: number; + // 路由title 一般必填 + title: string; + // 动态路由可打开Tab页数 + dynamicLevel?: number; + // 动态路由的实际Path, 即去除路由的动态部分; + realPath?: string; + // 是否忽略权限,只在权限模式为Role的时候有效 + ignoreAuth?: boolean; + // 可以访问的角色,只在权限模式为Role的时候有效 + roles?: RoleEnum[]; + // 是否忽略KeepAlive缓存 + ignoreKeepAlive?: boolean; + // 是否固定标签 + affix?: boolean; + // 图标,也是菜单图标 + icon?: string; + // 内嵌iframe的地址 + frameSrc?: string; + // 指定该路由切换的动画名 + transitionName?: string; + // 隐藏该路由在面包屑上面的显示 + hideBreadcrumb?: boolean; + // 如果该路由会携带参数,且需要在tab页上面显示。则需要设置为true + carryParam?: boolean; + // 隐藏所有子菜单 + hideChildrenInMenu?: boolean; + // Carrying parameters + carryParam?: boolean; + // Used internally to mark single-level menus + single?: boolean; + // 当前激活的菜单。用于配置详情页时左侧激活的菜单路径 + currentActiveMenu?: string; + // 当前路由不再标签页显示 + hideTab?: boolean; + // 当前路由不再菜单显示 + hideMenu?: boolean; + // 菜单排序,只对第一级有效 + orderNo?: number; + // 忽略路由。用于在ROUTE_MAPPING以及BACK权限模式下,生成对应的菜单而忽略路由。2.5.3以上版本有效 + ignoreRoute?: boolean; + // 是否在子级菜单的完整path中忽略本级path。2.5.3以上版本有效 + hidePathForChildren?: boolean; + } +}