From d85ebeb7f0817082b1a17b98f996b0d1170363de Mon Sep 17 00:00:00 2001 From: XiuHe <1109326957@qq.com> Date: Thu, 5 Mar 2026 14:53:39 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=96=B0=E7=94=9F=E6=8A=A5?= =?UTF-8?q?=E5=90=8D=E9=A1=B5=E9=9D=A2stu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stu/.gitignore | 24 +++ stu/.vscode/extensions.json | 3 + stu/README.md | 5 + stu/global.d.ts | 5 + stu/index.html | 13 ++ stu/package.json | 34 +++ stu/postcss.config.js | 6 + stu/public/vite.svg | 1 + stu/src/App.vue | 204 ++++++++++++++++++ stu/src/api/enrollment.ts | 10 + stu/src/api/request.ts | 145 +++++++++++++ stu/src/assets/vue.svg | 1 + stu/src/components/apply.vue | 223 ++++++++++++++++++++ stu/src/components/query.vue | 383 ++++++++++++++++++++++++++++++++++ stu/src/enums/appEnums.ts | 33 +++ stu/src/enums/cacheEnums.ts | 8 + stu/src/enums/pageEnum.ts | 7 + stu/src/enums/requestEnums.ts | 19 ++ stu/src/main.ts | 11 + stu/src/router/index.ts | 47 +++++ stu/src/style.css | 83 ++++++++ stu/src/utils/env.ts | 29 +++ stu/src/utils/feedback.ts | 95 +++++++++ stu/src/utils/http/index.ts | 93 +++++++++ stu/src/utils/http/request.ts | 126 +++++++++++ stu/src/utils/util.ts | 63 ++++++ stu/src/utils/validate.ts | 50 +++++ stu/tailwind.config.js | 71 +++++++ stu/tsconfig.app.json | 20 ++ stu/tsconfig.json | 14 ++ stu/tsconfig.node.json | 26 +++ stu/vite.config.ts | 17 ++ 32 files changed, 1869 insertions(+) create mode 100644 stu/.gitignore create mode 100644 stu/.vscode/extensions.json create mode 100644 stu/README.md create mode 100644 stu/global.d.ts create mode 100644 stu/index.html create mode 100644 stu/package.json create mode 100644 stu/postcss.config.js create mode 100644 stu/public/vite.svg create mode 100644 stu/src/App.vue create mode 100644 stu/src/api/enrollment.ts create mode 100644 stu/src/api/request.ts create mode 100644 stu/src/assets/vue.svg create mode 100644 stu/src/components/apply.vue create mode 100644 stu/src/components/query.vue create mode 100644 stu/src/enums/appEnums.ts create mode 100644 stu/src/enums/cacheEnums.ts create mode 100644 stu/src/enums/pageEnum.ts create mode 100644 stu/src/enums/requestEnums.ts create mode 100644 stu/src/main.ts create mode 100644 stu/src/router/index.ts create mode 100644 stu/src/style.css create mode 100644 stu/src/utils/env.ts create mode 100644 stu/src/utils/feedback.ts create mode 100644 stu/src/utils/http/index.ts create mode 100644 stu/src/utils/http/request.ts create mode 100644 stu/src/utils/util.ts create mode 100644 stu/src/utils/validate.ts create mode 100644 stu/tailwind.config.js create mode 100644 stu/tsconfig.app.json create mode 100644 stu/tsconfig.json create mode 100644 stu/tsconfig.node.json create mode 100644 stu/vite.config.ts diff --git a/stu/.gitignore b/stu/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/stu/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/stu/.vscode/extensions.json b/stu/.vscode/extensions.json new file mode 100644 index 00000000..a7cea0b0 --- /dev/null +++ b/stu/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/stu/README.md b/stu/README.md new file mode 100644 index 00000000..33895ab2 --- /dev/null +++ b/stu/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/stu/package.json b/stu/package.json new file mode 100644 index 00000000..47eae1f1 --- /dev/null +++ b/stu/package.json @@ -0,0 +1,34 @@ +{ + "name": "stu", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.13.2", + "element-plus": "^2.11.9", + "ofetch": "^1.5.1", + "vue": "^3.5.24", + "vue-router": "^4.6.3" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.8.1", + "autoprefixer": "^8.0.0", + "postcss": "^8.5.6", + "postcss-loader": "^8.2.0", + "sass-embedded": "^1.93.3", + "tailwindcss": "^3.4.17", + "typescript": "~5.9.3", + "vite": "npm:rolldown-vite@7.2.5", + "vue-tsc": "^3.1.4" + }, + "overrides": { + "vite": "npm:rolldown-vite@7.2.5" + } +} diff --git a/stu/postcss.config.js b/stu/postcss.config.js new file mode 100644 index 00000000..2e7af2b7 --- /dev/null +++ b/stu/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/stu/public/vite.svg b/stu/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/stu/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/stu/src/App.vue b/stu/src/App.vue new file mode 100644 index 00000000..4831f55f --- /dev/null +++ b/stu/src/App.vue @@ -0,0 +1,204 @@ + + + + + + \ No newline at end of file diff --git a/stu/src/api/enrollment.ts b/stu/src/api/enrollment.ts new file mode 100644 index 00000000..92dcd644 --- /dev/null +++ b/stu/src/api/enrollment.ts @@ -0,0 +1,10 @@ +// api/enrollment.ts +import request from './request' + +export function submitEnrollmentInfo(params: any) { + return request.post('enrollment/submit', params) +} + +export function getEnrollmentProcessStatus(params: any) { + return request.get('enrollment/processStatus', params) +} \ No newline at end of file diff --git a/stu/src/api/request.ts b/stu/src/api/request.ts new file mode 100644 index 00000000..03b0fb6c --- /dev/null +++ b/stu/src/api/request.ts @@ -0,0 +1,145 @@ +// api/request.ts +export class Request { + private baseURL: string + private interceptors: { + request: Array<(config: any) => any> + response: Array<(response: any) => any> + } + + constructor(baseURL: string = '') { + this.baseURL = baseURL + this.interceptors = { + request: [], + response: [] + } + } + + // 添加请求拦截器 + useRequestInterceptor(interceptor: (config: any) => any) { + this.interceptors.request.push(interceptor) + } + + // 添加响应拦截器 + useResponseInterceptor(interceptor: (response: any) => any) { + this.interceptors.response.push(interceptor) + } + + async request(method: string, url: string, data?: any) { + let config = { + method, + url: this.baseURL + url, + data, + headers: {}, + // 添加params字段用于区分GET参数 + params: method === 'GET' || method === 'HEAD' ? data : undefined + } + + // 执行请求拦截器 + for (const interceptor of this.interceptors.request) { + config = await interceptor(config) + } + + // 构建完整的URL(处理GET参数) + let fullUrl = config.url + if (config.params && Object.keys(config.params).length > 0) { + const queryString = new URLSearchParams(config.params).toString() + fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString + } + + const fetchConfig: RequestInit = { + method: config.method, + headers: { + 'Content-Type': 'application/json', + ...config.headers + } + } + + // 只有非GET/HEAD请求才设置body + if (config.method !== 'GET' && config.method !== 'HEAD' && config.data) { + fetchConfig.body = JSON.stringify(config.data) + } + + try { + const response = await fetch(fullUrl, fetchConfig) + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status}`) + } + + const result = await response.json() + + let finalResult = { ...result, status: response.status } + for (const interceptor of this.interceptors.response) { + finalResult = await interceptor(finalResult) + } + + return finalResult + } catch (error) { + // 错误处理 + console.error('请求失败:', error) + throw error + } + } + + get(url: string, params?: any) { + return this.request('GET', url, params) + } + + post(url: string, data?: any) { + return this.request('POST', url, data) + } + + put(url: string, data?: any) { + return this.request('PUT', url, data) + } + + delete(url: string, data?: any) { + return this.request('DELETE', url, data) + } +} + +// 创建实例 +const request = new Request('http://192.168.123.111:8083/api/') + +// 添加全局请求拦截器 +request.useRequestInterceptor(async (config) => { + // 添加认证token + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + + if (config.url.includes('/enrollment/') && config.params?.idCard) { + const idCardRegex = /(^\d{18}$)|(^\d{17}(\d|X|x)$)/ + if (!idCardRegex.test(config.params.idCard)) { + throw new Error('身份证号格式不正确') + } + } + + return config +}) + +// 添加全局响应拦截器 +request.useResponseInterceptor(async (response) => { + // 根据你的后端返回结构调整 + if (response.code && response.code !== 200 && response.code !== 0 && response.code !== 1) { + // 处理业务错误 + switch (response.code) { + case 401: + window.location.href = '/login' + break + case 403: + throw new Error('权限不足,无法访问') + default: + throw new Error(response.message || '请求失败') + } + } + return response +}) + +// 添加错误处理拦截器 +request.useResponseInterceptor(async (response) => { + return response +}) + +export default request \ No newline at end of file diff --git a/stu/src/assets/vue.svg b/stu/src/assets/vue.svg new file mode 100644 index 00000000..770e9d33 --- /dev/null +++ b/stu/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/stu/src/components/apply.vue b/stu/src/components/apply.vue new file mode 100644 index 00000000..a042bb83 --- /dev/null +++ b/stu/src/components/apply.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/stu/src/components/query.vue b/stu/src/components/query.vue new file mode 100644 index 00000000..351df1aa --- /dev/null +++ b/stu/src/components/query.vue @@ -0,0 +1,383 @@ + + + + diff --git a/stu/src/enums/appEnums.ts b/stu/src/enums/appEnums.ts new file mode 100644 index 00000000..fb94401a --- /dev/null +++ b/stu/src/enums/appEnums.ts @@ -0,0 +1,33 @@ +//菜单主题类型 +export enum ThemeEnum { + LIGHT = 'light', + DARK = 'dark' +} + +// 菜单类型 +export enum MenuEnum { + CATALOGUE = 'M', + MENU = 'C', + BUTTON = 'A' +} + +// 屏幕 +export enum ScreenEnum { + SM = 640, + MD = 768, + LG = 1024, + XL = 1280, + '2XL' = 1536 +} + +export enum SMSEnum { + LOGIN = 'YZMDL', + BIND_MOBILE = 'BDSJHM', + CHANGE_MOBILE = 'BGSJHM', + FIND_PASSWORD = 'ZHDLMM' +} + +export enum PolicyAgreementEnum { + SERVICE = 'service', + PRIVACY = 'privacy' +} diff --git a/stu/src/enums/cacheEnums.ts b/stu/src/enums/cacheEnums.ts new file mode 100644 index 00000000..45624e4d --- /dev/null +++ b/stu/src/enums/cacheEnums.ts @@ -0,0 +1,8 @@ +// 本地缓冲key + +//token +export const TOKEN_KEY = 'token' +//账号 +export const ACCOUNT_KEY = 'account' +//设置 +export const SETTING_KEY = 'setting' diff --git a/stu/src/enums/pageEnum.ts b/stu/src/enums/pageEnum.ts new file mode 100644 index 00000000..1509ff58 --- /dev/null +++ b/stu/src/enums/pageEnum.ts @@ -0,0 +1,7 @@ +export enum PageEnum { + //登录页面 + LOGIN = '/login', + //无权限页面 + ERROR_403 = '/403', + INDEX = '/' +} diff --git a/stu/src/enums/requestEnums.ts b/stu/src/enums/requestEnums.ts new file mode 100644 index 00000000..f792502d --- /dev/null +++ b/stu/src/enums/requestEnums.ts @@ -0,0 +1,19 @@ +export enum ContentTypeEnum { + // json + JSON = 'application/json;charset=UTF-8', + // form-data 上传资源(图片,视频) + FORM_DATA = 'multipart/form-data' +} + +export enum RequestMethodsEnum { + GET = 'GET', + POST = 'POST' +} + +export enum RequestCodeEnum { + NOT_INSTALL = -2, + LOGIN_FAILURE = -1, + FAIL = 0, + SUCCESS = 1, + OPEN_NEW_PAGE = 2 +} diff --git a/stu/src/main.ts b/stu/src/main.ts new file mode 100644 index 00000000..1fcc0b5f --- /dev/null +++ b/stu/src/main.ts @@ -0,0 +1,11 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import router from './router' + +createApp(App) + .use(ElementPlus) + .use(router) + .mount('#app') diff --git a/stu/src/router/index.ts b/stu/src/router/index.ts new file mode 100644 index 00000000..0a30624b --- /dev/null +++ b/stu/src/router/index.ts @@ -0,0 +1,47 @@ +// src/router/index.ts +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' +// 导入组件(支持懒加载) +import apply from '@/components/apply.vue' +import query from '@/components/query.vue' + +// 1. 定义路由数组:用 RouteRecordRaw 约束类型 +const routes: RouteRecordRaw[] = [ + { + path: '/', + redirect: '/apply' + }, + { + path: '/apply', + name: 'apply', // 路由名称(可选,用于编程式导航) + component: apply, + meta: { + title: '申请', // 自定义元信息(可扩展类型) + requiresAuth: false // 是否需要登录 + } + }, + { + path: '/query', + name: 'query', + component: query, + meta: { + title: '查询', + requiresAuth: false + } + } +] + +// 2. 创建路由实例 +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), // HTML5 模式(无 #) + routes // 传入路由数组 +}) + +// 3. 路由守卫(可选,示例:修改页面标题) +router.beforeEach((to, from, next) => { + if (to.meta.title) { + document.title = to.meta.title as string // TS 类型断言(因 meta 已扩展,可优化) + } + next() +}) + +export default router \ No newline at end of file diff --git a/stu/src/style.css b/stu/src/style.css new file mode 100644 index 00000000..91c2a9b7 --- /dev/null +++ b/stu/src/style.css @@ -0,0 +1,83 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/stu/src/utils/env.ts b/stu/src/utils/env.ts new file mode 100644 index 00000000..0ce6fbb2 --- /dev/null +++ b/stu/src/utils/env.ts @@ -0,0 +1,29 @@ +/** + * @description 获取客户端类型 + */ +export function getClient() { + return useRuntimeConfig().public.client +} + +/** + * @description 获取版本号 + */ +export function getVersion() { + return useRuntimeConfig().public.version +} + +/** + * @description 获取请求域名 + */ +export function getApiUrl() { + return ( + useRuntimeConfig().public.apiUrl || 'http://192.168.123.111:8084/api/' + ) +} + +/** + * @description 获取请求前缀 + */ +export function getApiPrefix() { + return useRuntimeConfig().public.apiPrefix +} diff --git a/stu/src/utils/feedback.ts b/stu/src/utils/feedback.ts new file mode 100644 index 00000000..91672abe --- /dev/null +++ b/stu/src/utils/feedback.ts @@ -0,0 +1,95 @@ +import { + ElMessage, + ElMessageBox, + ElNotification, + ElLoading, + type ElMessageBoxOptions +} from 'element-plus' +import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading' + +export class Feedback { + private loadingInstance: LoadingInstance | null = null + static instance: Feedback | null = null + static getInstance() { + return this.instance ?? (this.instance = new Feedback()) + } + // 消息提示 + msg(msg: string) { + ElMessage.info(msg) + } + // 错误消息 + msgError(msg: string) { + ElMessage.error(msg) + } + // 成功消息 + msgSuccess(msg: string) { + ElMessage.success(msg) + } + // 警告消息 + msgWarning(msg: string) { + ElMessage.warning(msg) + } + // 弹出提示 + alert(msg: string) { + ElMessageBox.alert(msg, '系统提示') + } + // 错误提示 + alertError(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'error' }) + } + // 成功提示 + alertSuccess(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'success' }) + } + // 警告提示 + alertWarning(msg: string) { + ElMessageBox.alert(msg, '系统提示', { type: 'warning' }) + } + // 通知提示 + notify(msg: string) { + ElNotification.info(msg) + } + // 错误通知 + notifyError(msg: string) { + ElNotification.error(msg) + } + // 成功通知 + notifySuccess(msg: string) { + ElNotification.success(msg) + } + // 警告通知 + notifyWarning(msg: string) { + ElNotification.warning(msg) + } + // 确认窗体 + confirm(msg: string) { + return ElMessageBox.confirm(msg, '温馨提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }) + } + // 提交内容 + prompt(content: string, title: string, options?: ElMessageBoxOptions) { + return ElMessageBox.prompt(content, title, { + confirmButtonText: '确定', + cancelButtonText: '取消', + ...options + }) + } + // 打开全局loading + loading(msg: string) { + this.loadingInstance = ElLoading.service({ + lock: true, + text: msg + }) + } + // 关闭全局loading + closeLoading() { + this.loadingInstance?.close() + } +} + +const feedback = Feedback.getInstance() + +export default feedback diff --git a/stu/src/utils/http/index.ts b/stu/src/utils/http/index.ts new file mode 100644 index 00000000..c7a71c51 --- /dev/null +++ b/stu/src/utils/http/index.ts @@ -0,0 +1,93 @@ +import { FetchOptions } from 'ofetch' +import { RequestCodeEnum, RequestMethodsEnum } from '@/enums/requestEnums' +import feedback from '@/utils/feedback' +import { merge } from 'lodash-es' +import { Request } from './request' +import { getApiPrefix, getApiUrl, getVersion } from '../env' +import { useUserStore } from '@/stores/user' + +export function createRequest(opt?: Partial) { + const userStore = useUserStore() + // const { setPopupType, toggleShowPopup } = useAccount() + const defaultOptions: FetchOptions = { + // 基础接口地址 + baseURL: getApiUrl(), + //请求头 + headers: { + version: getVersion() + }, + retry: 2, + requestOptions: { + apiPrefix: getApiPrefix(), + isTransformResponse: true, + isReturnDefaultResponse: false, + withToken: true, + isParamsToData: true, + requestInterceptorsHook(options) { + const { apiPrefix, isParamsToData, withToken } = + options.requestOptions + // 拼接请求前缀 + if (apiPrefix) { + options.url = `${apiPrefix}${options.url}` + } + const params = options.params || {} + // POST请求下如果无data,则将params视为data + if ( + isParamsToData && + !Reflect.has(options, 'body') && + options.method?.toUpperCase() === RequestMethodsEnum.POST + ) { + options.body = params + options.params = {} + } + const headers = options.headers || {} + if (withToken) { + const token = userStore.token + headers['token'] = token + } + options.headers = headers + return options + }, + async responseInterceptorsHook(response, options) { + const { isTransformResponse, isReturnDefaultResponse } = + options.requestOptions + //返回默认响应,当需要获取响应头及其他数据时可使用 + if (isReturnDefaultResponse) { + return response + } + // 是否需要对数据进行处理 + if (!isTransformResponse) { + return response._data + } + const { code, data, show, msg } = response._data + switch (code) { + case RequestCodeEnum.SUCCESS: + if (show) { + msg && feedback.msgSuccess(msg) + } + return data + case RequestCodeEnum.FAIL: + if (show) { + msg && feedback.msgError(msg) + } + return Promise.reject(msg) + case RequestCodeEnum.LOGIN_FAILURE: + userStore.logout() + return Promise.reject(data) + case RequestCodeEnum.NOT_INSTALL: + window.location.replace('/install/install.php') + break + default: + return data + } + }, + responseInterceptorsCatchHook(err) { + return err + } + } + } + return new Request( + // 深度合并 + merge(defaultOptions, opt || {}) + ) +} diff --git a/stu/src/utils/http/request.ts b/stu/src/utils/http/request.ts new file mode 100644 index 00000000..7e34865f --- /dev/null +++ b/stu/src/utils/http/request.ts @@ -0,0 +1,126 @@ +import { + FetchOptions, + $fetch, + $Fetch, + FetchResponse, + RequestOptions, + FileParams, + RequestEventStreamOptions +} from 'ofetch' +import { merge } from 'lodash-es' +import { isFunction } from '../validate' +import { RequestMethodsEnum } from '@/enums/requestEnums' +import { objectToQuery } from '../util' + +export class Request { + private requestOptions: RequestOptions + private fetchInstance: $Fetch + constructor(private fetchOptions: FetchOptions) { + this.fetchInstance = $fetch.create(fetchOptions) + this.requestOptions = fetchOptions.requestOptions + } + + getInstance() { + return this.fetchInstance + } + /** + * @description get请求 + */ + get(fetchOptions: FetchOptions, requestOptions?: Partial) { + return this.request( + { ...fetchOptions, method: RequestMethodsEnum.GET }, + requestOptions + ) + } + + /** + * @description post请求 + */ + post(fetchOptions: FetchOptions, requestOptions?: Partial) { + return this.request( + { ...fetchOptions, method: RequestMethodsEnum.POST }, + requestOptions + ) + } + /** + * @description: 文件上传 + */ + uploadFile(options: FetchOptions, params: FileParams) { + const formData = new FormData() + const customFilename = params.name || 'file' + formData.append(customFilename, params.file) + if (params.data) { + Object.keys(params.data).forEach((key) => { + const value = params.data![key] + if (Array.isArray(value)) { + value.forEach((item) => { + formData.append(`${key}[]`, item) + }) + return + } + + formData.append(key, params.data![key]) + }) + } + return this.request({ + ...options, + method: RequestMethodsEnum.POST, + body: formData + }) + } + /** + * @description 请求函数 + */ + request( + fetchOptions: FetchOptions, + requestOptions?: Partial + ): Promise { + let mergeOptions = merge({}, this.fetchOptions, fetchOptions) + mergeOptions.requestOptions = merge( + {}, + this.requestOptions, + requestOptions + ) + const { + requestInterceptorsHook, + responseInterceptorsHook, + responseInterceptorsCatchHook + } = this.requestOptions + if (requestInterceptorsHook && isFunction(requestInterceptorsHook)) { + mergeOptions = requestInterceptorsHook(mergeOptions) + } + return new Promise((resolve, reject) => { + return this.fetchInstance + .raw(mergeOptions.url, mergeOptions) + .then(async (response: FetchResponse) => { + if ( + responseInterceptorsHook && + isFunction(responseInterceptorsHook) + ) { + try { + response = await responseInterceptorsHook( + response, + mergeOptions + ) + + resolve(response) + } catch (error) { + reject(error) + } + return + } + resolve(response) + }) + .catch((err) => { + if ( + responseInterceptorsCatchHook && + isFunction(responseInterceptorsCatchHook) + ) { + reject(responseInterceptorsCatchHook(err)) + return + } + reject(err) + }) + }) + } +} diff --git a/stu/src/utils/util.ts b/stu/src/utils/util.ts new file mode 100644 index 00000000..aaac9db4 --- /dev/null +++ b/stu/src/utils/util.ts @@ -0,0 +1,63 @@ +/** + * @description 添加单位 + * @param {String | Number} value 值 100 + * @param {String} unit 单位 px em rem + */ +export const addUnit = (value: string | number, unit = 'px') => { + return !Object.is(Number(value), NaN) ? `${value}${unit}` : value +} + +/** + * @description 树转数组,队列实现广度优先遍历 + * @param {Array} data 数据 + * @param {Object} props `{ children: 'children' }` + */ + +export const treeToArray = (data: any[], props = { children: 'children' }) => { + data = JSON.parse(JSON.stringify(data)) + const { children } = props + const newData = [] + const queue: any[] = [] + data.forEach((child: any) => queue.push(child)) + while (queue.length) { + const item: any = queue.shift() + if (item[children]) { + item[children].forEach((child: any) => queue.push(child)) + delete item[children] + } + newData.push(item) + } + return newData +} + +/** + * @description 获取正确的路经 + * @param {String} path 数据 + */ +export function getNormalPath(path: string) { + if (path.length === 0 || !path || path == 'undefined') { + return path + } + const newPath = path.replace('//', '/') + const length = newPath.length + if (newPath[length - 1] === '/') { + return newPath.slice(0, length - 1) + } + return newPath +} + +/** + * @description对象格式化为Query语法 + * @param { Object } params + * @return {string} Query语法 + */ +export function objectToQuery(params: Record): string { + let query = '' + for (const props of Object.keys(params)) { + const value = params[props] + if (!isEmpty(value)) { + query += props + '=' + value + '&' + } + } + return query.slice(0, -1) +} diff --git a/stu/src/utils/validate.ts b/stu/src/utils/validate.ts new file mode 100644 index 00000000..f96239dc --- /dev/null +++ b/stu/src/utils/validate.ts @@ -0,0 +1,50 @@ +export { + isArray, + isBoolean, + isDate, + isObject, + isFunction, + isString, + isNumber, + isNull +} from 'lodash-es' +import { isObject } from 'lodash-es' +/** + * @description 是否是http,邮件,电话号码 + */ +export function isExternal(path: string) { + return /^(https?:|mailto:|tel:)/.test(path) +} + +/** + * @description 是否是http + */ +export const isLinkHttp = (link: string): boolean => + /^(https?:)?\/\//.test(link) + +/** + * @description 是否是电话号码 + */ +export const isLinkTel = (link: string): boolean => /^tel:/.test(link) + +/** + * @description 是否是邮件 + */ +export const isLinkMailto = (link: string): boolean => /^mailto:/.test(link) + +/** + * @description 是否为空 + * @param {unknown} value + * @return {Boolean} + */ +export const isEmpty = (value: unknown) => { + return value !== null && value !== '' && typeof value !== 'undefined' +} +/** + * @description 是否为空对象 + * @param {Object} value + * @return {Boolean} + */ +export const isEmptyObject = (target: object) => { + return isObject(target) && !Object.keys(target).length +} diff --git a/stu/tailwind.config.js b/stu/tailwind.config.js new file mode 100644 index 00000000..c47b9a61 --- /dev/null +++ b/stu/tailwind.config.js @@ -0,0 +1,71 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './index.html', + './src/**/*.{vue,js}' + ], + theme: { + colors: { + white: 'var(--color-white)', + black: 'var(--el-color-black)', + primary: { + DEFAULT: 'var(--el-color-primary)', + 'light-3': 'var(--el-color-primary-light-3)', + 'light-5': 'var(--el-color-primary-light-5)', + 'light-7': 'var(--el-color-primary-light-7)', + 'light-8': 'var(--el-color-primary-light-8)', + 'light-9': 'var(--el-color-primary-light-9)', + 'dark-2': 'var(--el-color-primary-dark-2)' + }, + success: 'var(--el-color-success)', + warning: 'var(--el-color-warning)', + danger: 'var(--el-color-danger)', + error: 'var(--el-color-error)', + info: 'var(--el-color-info)', + body: 'var(--el-bg-color)', + page: 'var(--el-bg-color-page)', + 'tx-primary': 'var(--el-text-color-primary)', + 'tx-regular': 'var(--el-text-color-regular)', + 'tx-secondary': 'var(--el-text-color-secondary)', + 'tx-placeholder': 'var(--el-text-color-placeholder)', + 'tx-disabled': 'var(--el-text-color-disabled)', + br: 'var(--el-border-color)', + 'br-light': 'var(--el-border-color-light)', + 'br-extra-light': 'var(--el-border-color-extra-light)', + 'br-dark': 'var( --el-border-color-dark)', + fill: 'var(--el-fill-color)', + mask: 'var(--el-mask-color)', + overlay: 'var(--el-overlay-color-light)' + }, + fontFamily: { + sans: [ + 'PingFang SC', + 'Arial', + 'Hiragino Sans GB', + 'Microsoft YaHei', + 'sans-serif' + ] + }, + boxShadow: { + DEFAULT: 'var(--el-box-shadow)', + light: 'var(--el-box-shadow-light)', + lighter: 'var(--el-box-shadow-lighter)', + dark: 'var(--el-box-shadow-dark)' + }, + fontSize: { + xs: 'var(--el-font-size-extra-small)', + sm: 'var( --el-font-size-small)', + base: 'var( --el-font-size-base)', + lg: 'var( --el-font-size-medium)', + xl: 'var( --el-font-size-large)', + '2xl': 'var( --el-font-size-extra-large)', + '3xl': '20px', + '4xl': '24px', + '5xl': '28px', + '6xl': '30px', + '7xl': '36px', + '8xl': '48px', + '9xl': '60px' + } + } +} diff --git a/stu/tsconfig.app.json b/stu/tsconfig.app.json new file mode 100644 index 00000000..a66ca042 --- /dev/null +++ b/stu/tsconfig.app.json @@ -0,0 +1,20 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "strict": true, + "composite":true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": false, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "verbatimModuleSyntax": false, + "baseUrl": ".", // 基础路径为项目根目录 + "paths": { "@/*": ["src/*"] } // 与 Vite 别名同步 + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/stu/tsconfig.json b/stu/tsconfig.json new file mode 100644 index 00000000..c840ea5d --- /dev/null +++ b/stu/tsconfig.json @@ -0,0 +1,14 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/stu/tsconfig.node.json b/stu/tsconfig.node.json new file mode 100644 index 00000000..8a67f62f --- /dev/null +++ b/stu/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/stu/vite.config.ts b/stu/vite.config.ts new file mode 100644 index 00000000..006b3b7b --- /dev/null +++ b/stu/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], + server: { + port: 5274, + host: '0.0.0.0', + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +})