补充新生报名页面stu
This commit is contained in:
parent
2fe6a590e8
commit
d85ebeb7f0
|
|
@ -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?
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
|
|
@ -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 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
import { Request } from './src/utils/http/request'
|
||||||
|
declare global {
|
||||||
|
const $request: Request
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>stu</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,204 @@
|
||||||
|
<!-- App.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const isMenuOpen = ref(false); // 用于移动端菜单控制
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 导航栏固定在页面顶端 -->
|
||||||
|
<header class="navbar">
|
||||||
|
<div class="navbar-container">
|
||||||
|
<!-- 品牌标识 -->
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<router-link to="/">新生报名系统</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 桌面端导航 -->
|
||||||
|
<nav class="navbar-nav">
|
||||||
|
<router-link
|
||||||
|
to="/apply"
|
||||||
|
:class="{ active: route.path === '/apply' }"
|
||||||
|
>
|
||||||
|
报名注册
|
||||||
|
</router-link>
|
||||||
|
<router-link
|
||||||
|
to="/query"
|
||||||
|
:class="{ active: route.path === '/query' }"
|
||||||
|
>
|
||||||
|
状态查询
|
||||||
|
</router-link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- 移动端菜单按钮 -->
|
||||||
|
<button class="menu-toggle" @click="isMenuOpen = !isMenuOpen">
|
||||||
|
<span v-if="!isMenuOpen">☰</span>
|
||||||
|
<span v-else>✕</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端导航菜单 -->
|
||||||
|
<div class="mobile-menu" v-if="isMenuOpen">
|
||||||
|
<router-link
|
||||||
|
to="/apply"
|
||||||
|
:class="{ active: route.path === '/apply' }"
|
||||||
|
@click="isMenuOpen = false"
|
||||||
|
>
|
||||||
|
报名注册
|
||||||
|
</router-link>
|
||||||
|
<router-link
|
||||||
|
to="/query"
|
||||||
|
:class="{ active: route.path === '/query' }"
|
||||||
|
@click="isMenuOpen = false"
|
||||||
|
>
|
||||||
|
状态查询
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main><router-view /></main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 重置页面默认边距,确保导航栏紧贴顶端 */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: hidden; /* 防止横向滚动 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导航栏固定在顶端,z-index设为最高确保不被遮挡 */
|
||||||
|
.navbar {
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
position: fixed; /* 改为fixed确保始终固定在顶端 */
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10; /* 提高层级,避免被其他元素覆盖 */
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand a {
|
||||||
|
color: #165DFF;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a {
|
||||||
|
color: #333333;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 8px 0;
|
||||||
|
position: relative;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a:hover {
|
||||||
|
color: #165DFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a.active {
|
||||||
|
color: #165DFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: #165DFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #333333;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu {
|
||||||
|
display: none;
|
||||||
|
padding: 15px 20px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 0;
|
||||||
|
color: #333333;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu a:hover {
|
||||||
|
color: #165DFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu a.active {
|
||||||
|
color: #165DFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主内容区添加顶部内边距,避免被固定导航栏遮挡 */
|
||||||
|
main {
|
||||||
|
max-width: 1200px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 80px 20px 20px; /* 顶部padding=导航栏高度(60px)+20px间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.navbar-nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端主内容区顶部padding调整(考虑展开的移动端菜单) */
|
||||||
|
main {
|
||||||
|
padding-top: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
|
|
@ -0,0 +1,223 @@
|
||||||
|
<template>
|
||||||
|
<!-- 报名表单区域 -->
|
||||||
|
<div class="border-b border-br pb-5">
|
||||||
|
<span class="text-2xl font-medium">新生报名</span>
|
||||||
|
</div>
|
||||||
|
<el-card class="!border-none mb-4" shadow="never">
|
||||||
|
<div class="lg:flex gap-6">
|
||||||
|
<!-- 基本信息表单 -->
|
||||||
|
<el-card class="!border-none flex-1" shadow="never">
|
||||||
|
<el-form
|
||||||
|
layout="vertical"
|
||||||
|
:model="studentData.baseInfo"
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<el-form-item label="学生姓名">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.name"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="性别">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="studentData.baseInfo.gender"
|
||||||
|
>
|
||||||
|
<el-radio :label="0">男</el-radio>
|
||||||
|
<el-radio :label="1">女</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="身份证号">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.idCard"
|
||||||
|
placeholder="请输入18位身份证号"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="出生日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="studentData.baseInfo.birthday"
|
||||||
|
type="datetime"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="请输入出生日期"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系方式">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.phone"
|
||||||
|
placeholder="请输入手机号码"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="电子邮箱">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.email"
|
||||||
|
placeholder="请输入电子邮箱"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="籍贯">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.nativePlace"
|
||||||
|
placeholder="请输入籍贯"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="民族">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.nationality"
|
||||||
|
placeholder="请输入民族"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="政治面貌">
|
||||||
|
<el-select
|
||||||
|
v-model="studentData.baseInfo.politicalStatus"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="群众" :value="0" />
|
||||||
|
<el-option label="团员" :value="1" />
|
||||||
|
<el-option label="党员" :value="2" />
|
||||||
|
<el-option label="其他" :value="3" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="毕业年份">
|
||||||
|
<el-input-number
|
||||||
|
v-model="studentData.baseInfo.graduationYear"
|
||||||
|
placeholder="请输入毕业年份"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="毕业院校">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.previousSchool"
|
||||||
|
placeholder="请输入毕业院校"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="学校类型">
|
||||||
|
<el-select
|
||||||
|
v-model="studentData.baseInfo.schoolType"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<el-option label="普通高中" :value="1" />
|
||||||
|
<el-option label="职业高中" :value="2" />
|
||||||
|
<el-option label="中专" :value="3" />
|
||||||
|
<el-option label="大专" :value="4" />
|
||||||
|
<el-option label="本科" :value="5" />
|
||||||
|
<el-option label="其他" :value="6" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
label="家庭住址"
|
||||||
|
class="md:col-span-2"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.homeAddress"
|
||||||
|
placeholder="请输入详细家庭住址"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="紧急联系人">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.emergencyContact"
|
||||||
|
placeholder="请输入紧急联系人姓名"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="紧急联系电话">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.emergencyPhone"
|
||||||
|
placeholder="请输入紧急联系人电话"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="与紧急联系人关系">
|
||||||
|
<el-input
|
||||||
|
v-model="studentData.baseInfo.relationship"
|
||||||
|
placeholder="请输入与紧急联系人的关系"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<!-- 提交按钮 -->
|
||||||
|
<el-card class="!border-none mb-4 flex-1" shadow="never">
|
||||||
|
<div class="mt-6">
|
||||||
|
<el-button
|
||||||
|
class="ml-4"
|
||||||
|
type="primary"
|
||||||
|
block
|
||||||
|
@click="handleConfirmSubmit"
|
||||||
|
>
|
||||||
|
提交报名
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { ElButton, ElMessage, ElForm, ElFormItem, ElInput } from 'element-plus'
|
||||||
|
import {
|
||||||
|
submitEnrollmentInfo
|
||||||
|
} from '@/api/enrollment'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 学生信息数据
|
||||||
|
const studentData = reactive({
|
||||||
|
baseInfo: {
|
||||||
|
name: '',
|
||||||
|
gender: 0,
|
||||||
|
idCard: '',
|
||||||
|
birthday: '',
|
||||||
|
nationality: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
homeAddress: '',
|
||||||
|
nativePlace: '',
|
||||||
|
invitationCode: '',
|
||||||
|
politicalStatus: 0,
|
||||||
|
previousSchool: '',
|
||||||
|
schoolType: '',
|
||||||
|
graduationYear: 2025,
|
||||||
|
emergencyContact: '',
|
||||||
|
emergencyPhone: '',
|
||||||
|
relationship: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 确认提交
|
||||||
|
const handleConfirmSubmit = async () => {
|
||||||
|
try {
|
||||||
|
// 提交前校验身份证号
|
||||||
|
if (!/(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(studentData.baseInfo.idCard)) {
|
||||||
|
ElMessage.warning('请输入有效的18位身份证号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const data = await submitEnrollmentInfo(studentData.baseInfo)
|
||||||
|
console.log(data)
|
||||||
|
if(data.code === 1){
|
||||||
|
ElMessage.success('报名提交成功')
|
||||||
|
router.push({
|
||||||
|
path: '/query',
|
||||||
|
query: { idCard: studentData.baseInfo.idCard }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else ElMessage.error(data.msg)
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('报名提交失败,请稍后重试')
|
||||||
|
console.error('提交报名信息失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.el-tabs {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
<template>
|
||||||
|
<div class="px-[30px] py-5 user-info min-h-full flex flex-col">
|
||||||
|
<!-- 标签页切换 -->
|
||||||
|
<el-form>
|
||||||
|
<!-- 查询进度区域 -->
|
||||||
|
<div class="border-b border-br pb-5">
|
||||||
|
<span class="text-2xl font-medium">查询报名进度</span>
|
||||||
|
</div>
|
||||||
|
<!-- 查询表单 -->
|
||||||
|
<el-card class="!border-none mb-6" shadow="never">
|
||||||
|
<el-form
|
||||||
|
:model="queryForm"
|
||||||
|
:rules="queryRules"
|
||||||
|
ref="queryFormRef"
|
||||||
|
label-width="100px"
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<el-form-item label="身份证号" prop="idCard">
|
||||||
|
<el-input
|
||||||
|
v-model="queryForm.idCard"
|
||||||
|
placeholder="请输入18位身份证号"
|
||||||
|
maxlength="18"
|
||||||
|
@input="handleIdCardInput"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuerySubmit" :loading="isQueryLoading">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
<!-- 进度展示区域 -->
|
||||||
|
<div v-if="submission" class="border-b border-br pb-5">
|
||||||
|
<span class="text-2xl font-medium">注册状态</span>
|
||||||
|
</div>
|
||||||
|
<el-card
|
||||||
|
v-if="submission"
|
||||||
|
class="!border-none mb-4 flex-1"
|
||||||
|
shadow="never"
|
||||||
|
>
|
||||||
|
<div class="status-info mt-4">
|
||||||
|
<!-- 步骤条展示 -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="text-tx-secondary text-sm mb-4">状态进度</div>
|
||||||
|
<el-steps
|
||||||
|
:active="activeStep"
|
||||||
|
finish-status="success"
|
||||||
|
class="w-full"
|
||||||
|
style="text-align: left"
|
||||||
|
>
|
||||||
|
<el-step
|
||||||
|
v-for="(step, index) in statusConfig.steps"
|
||||||
|
:key="index"
|
||||||
|
:title="stepTitles[index]"
|
||||||
|
:description="getStepDescription(index)"
|
||||||
|
:status="getStepStatus(index)"
|
||||||
|
/>
|
||||||
|
</el-steps>
|
||||||
|
</div>
|
||||||
|
<!-- 显示拒绝原因 -->
|
||||||
|
<div v-if="rejectionReason" class="text-danger mt-4">
|
||||||
|
<i class="el-icon-error mr-2"></i>
|
||||||
|
拒绝原因:{{ rejectionReason }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 查询进度弹窗(保持全局可访问) -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="isQueryDialogOpen"
|
||||||
|
title="查询报名进度"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
:model="queryForm"
|
||||||
|
:rules="queryRules"
|
||||||
|
ref="queryFormRef"
|
||||||
|
label-width="100px"
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<el-form-item label="身份证号" prop="idCard">
|
||||||
|
<el-input
|
||||||
|
v-model="queryForm.idCard"
|
||||||
|
placeholder="请输入18位身份证号"
|
||||||
|
maxlength="18"
|
||||||
|
@input="handleIdCardInput"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="isQueryDialogOpen = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleQuerySubmit" :loading="isQueryLoading">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, computed, onMounted, ref, unref } from 'vue'
|
||||||
|
import { ElButton, ElMessage, ElForm, ElFormItem, ElInput, ElDialog, ElTabs, ElTabPane } from 'element-plus'
|
||||||
|
import {
|
||||||
|
getEnrollmentProcessStatus
|
||||||
|
} from '@/api/enrollment'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
const activeTab = ref('enroll') // 默认显示报名页面
|
||||||
|
const route = useRoute()
|
||||||
|
// 步骤标题与后端状态对应
|
||||||
|
const stepTitles = ['报名', '缴费', '等待录取']
|
||||||
|
const submission = ref(false) // 提交状态
|
||||||
|
const rejectionReason = ref('') // 拒绝原因
|
||||||
|
// 查询相关状态
|
||||||
|
const isQueryDialogOpen = ref(false) // 查询弹窗显示状态
|
||||||
|
const isQueryLoading = ref(false) // 查询加载状态
|
||||||
|
const queryFormRef = ref<InstanceType<typeof ElForm> | null>(null) // 查询表单引用
|
||||||
|
// 查询表单数据
|
||||||
|
const queryForm = reactive({
|
||||||
|
idCard: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询表单校验规则
|
||||||
|
const queryRules = reactive({
|
||||||
|
idCard: [
|
||||||
|
{ required: true, message: '请输入身份证号', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
pattern: /(^\d{18}$)|(^\d{17}(\d|X|x)$)/,
|
||||||
|
message: '请输入有效的18位身份证号',
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 学生信息数据
|
||||||
|
const studentData = reactive({
|
||||||
|
baseInfo: {
|
||||||
|
name: '',
|
||||||
|
gender: 0,
|
||||||
|
idCard: '',
|
||||||
|
birthday: '',
|
||||||
|
nationality: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
homeAddress: '',
|
||||||
|
nativePlace: '',
|
||||||
|
invitationCode: '',
|
||||||
|
politicalStatus: 0,
|
||||||
|
previousSchool: '',
|
||||||
|
schoolType: '',
|
||||||
|
graduationYear: 2025,
|
||||||
|
emergencyContact: '',
|
||||||
|
emergencyPhone: '',
|
||||||
|
relationship: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 状态配置
|
||||||
|
const statusConfig = reactive({
|
||||||
|
steps: [
|
||||||
|
{ completed: false, time: 0, status: 0 }, // 报名
|
||||||
|
{ completed: false, time: 0, status: 0 }, // 缴费
|
||||||
|
{ completed: false, time: 0, status: 0 }, // 等待录取通知
|
||||||
|
],
|
||||||
|
applicationNumber: '' // 报名编号
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化时间戳为日期
|
||||||
|
const formatTime = (timestamp: number) => {
|
||||||
|
if (!timestamp) return ''
|
||||||
|
const date = new Date(timestamp)
|
||||||
|
return date.toLocaleDateString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取步骤描述
|
||||||
|
const getStepDescription = (index: number) => {
|
||||||
|
const step = statusConfig.steps[index]
|
||||||
|
const timeStr = formatTime(step.time)
|
||||||
|
if (index === 0) {
|
||||||
|
return step.completed ? `已完成${timeStr}` : '状态异常'
|
||||||
|
} else if (index === 1) {
|
||||||
|
if (step.status === 0) return '未缴费'
|
||||||
|
if (step.status === 1) return `已缴费 ${timeStr}`
|
||||||
|
} else if (index === 2) {
|
||||||
|
if (step.status === 0) return '等待录取通知'
|
||||||
|
if (step.status === 1) return `录取通知已发送 ${timeStr}`
|
||||||
|
if (step.status === 2) return `未录取 ${timeStr}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理身份证输入
|
||||||
|
const handleIdCardInput = () => {
|
||||||
|
let idCardStr = String(queryForm.idCard || '')
|
||||||
|
idCardStr = idCardStr.replace(/[^0-9Xx]/g, '')
|
||||||
|
idCardStr = idCardStr.toUpperCase()
|
||||||
|
if (idCardStr.length > 18) {
|
||||||
|
idCardStr = idCardStr.slice(0, 18)
|
||||||
|
}
|
||||||
|
queryForm.idCard = idCardStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交查询
|
||||||
|
const handleQuerySubmit = async () => {
|
||||||
|
const formRef = unref(queryFormRef)
|
||||||
|
if (!formRef) return
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
await formRef.validate()
|
||||||
|
isQueryLoading.value = true
|
||||||
|
|
||||||
|
const data = await getEnrollmentProcessStatus(queryForm)
|
||||||
|
const statusRes = data.data
|
||||||
|
|
||||||
|
if (statusRes) {
|
||||||
|
isQueryDialogOpen.value = false
|
||||||
|
submission.value = true
|
||||||
|
|
||||||
|
statusConfig.applicationNumber = statusRes.applicationNumber || ''
|
||||||
|
rejectionReason.value = statusRes.rejectionReason || ''
|
||||||
|
|
||||||
|
statusConfig.steps[0] = {
|
||||||
|
completed: statusRes.applicationNumber ? true : false,
|
||||||
|
time: statusRes.applicationTime || 0,
|
||||||
|
status: statusRes.applicationTime > 0 ? 1 : 0
|
||||||
|
}
|
||||||
|
statusConfig.steps[1] = {
|
||||||
|
completed: statusRes.approvalStatus === 1,
|
||||||
|
time: statusRes.approvalTime || 0,
|
||||||
|
status: statusRes.approvalStatus || 0
|
||||||
|
}
|
||||||
|
statusConfig.steps[2] = {
|
||||||
|
completed: statusRes.admissionStatus === 6,
|
||||||
|
time: statusRes.admissionTime || 0,
|
||||||
|
status: statusRes.admissionStatus || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success('查询成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.info('未查询到相关报名记录')
|
||||||
|
resetStatusConfig()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询进度失败:', error)
|
||||||
|
ElMessage.error('查询失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
isQueryLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载流程状态
|
||||||
|
const loadProcessStatus = async (idCard?: string) => {
|
||||||
|
const cardNo = idCard || studentData.baseInfo.idCard
|
||||||
|
if (!cardNo || typeof cardNo !== 'string') return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const statusRes = await getEnrollmentProcessStatus(cardNo)
|
||||||
|
if (statusRes) {
|
||||||
|
submission.value = true
|
||||||
|
statusConfig.applicationNumber = statusRes.data.applicationNumber || ''
|
||||||
|
rejectionReason.value = statusRes.data.rejectionReason || ''
|
||||||
|
|
||||||
|
statusConfig.steps[0] = {
|
||||||
|
completed: statusRes.data.applicationNumber ? true : false,
|
||||||
|
time: statusRes.data.applicationTime || 0,
|
||||||
|
status: statusRes.data.applicationTime > 0 ? 1 : 0
|
||||||
|
}
|
||||||
|
statusConfig.steps[1] = {
|
||||||
|
completed: statusRes.data.approvalStatus === 1,
|
||||||
|
time: statusRes.data.approvalTime || 0,
|
||||||
|
status: statusRes.data.approvalStatus || 0
|
||||||
|
}
|
||||||
|
statusConfig.steps[2] = {
|
||||||
|
completed: statusRes.data.admissionStatus === 1,
|
||||||
|
time: statusRes.data.admissionTime || 0,
|
||||||
|
status: statusRes.data.admissionStatus || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新状态失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置状态配置
|
||||||
|
const resetStatusConfig = () => {
|
||||||
|
statusConfig.steps = [
|
||||||
|
{ completed: false, time: 0, status: 0 },
|
||||||
|
{ completed: false, time: 0, status: 0 },
|
||||||
|
{ completed: false, time: 0, status: 0 }
|
||||||
|
]
|
||||||
|
statusConfig.applicationNumber = ''
|
||||||
|
rejectionReason.value = ''
|
||||||
|
submission.value = false
|
||||||
|
const graduationYear = studentData.baseInfo.graduationYear
|
||||||
|
Object.assign(studentData.baseInfo, {
|
||||||
|
name: '',
|
||||||
|
gender: 0,
|
||||||
|
idCard: '',
|
||||||
|
birthday: '',
|
||||||
|
nationality: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
homeAddress: '',
|
||||||
|
nativePlace: '',
|
||||||
|
politicalStatus: 0,
|
||||||
|
previousSchool: '',
|
||||||
|
schoolType: '',
|
||||||
|
graduationYear,
|
||||||
|
emergencyContact: '',
|
||||||
|
emergencyPhone: '',
|
||||||
|
relationship: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态推导当前激活的步骤
|
||||||
|
const activeStep = computed(() => {
|
||||||
|
if (
|
||||||
|
statusConfig.steps[1].status === 2 ||
|
||||||
|
statusConfig.steps[2].status === 2
|
||||||
|
) {
|
||||||
|
return statusConfig.steps[1].status === 2 ? 1 : 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = completedStepCount.value
|
||||||
|
return count >= statusConfig.steps.length
|
||||||
|
? statusConfig.steps.length - 1
|
||||||
|
: count
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取步骤状态
|
||||||
|
const getStepStatus = (index: number) => {
|
||||||
|
if (statusConfig.steps[index].completed) {
|
||||||
|
return 'finish'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(index === 1 && statusConfig.steps[index].status === 2) ||
|
||||||
|
(index === 2 && statusConfig.steps[index].status === 2)
|
||||||
|
) {
|
||||||
|
return 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
return index === activeStep.value ? 'process' : 'wait'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算已完成的步骤数量
|
||||||
|
const completedStepCount = computed(() => {
|
||||||
|
return statusConfig.steps.filter((step) => step.completed).length
|
||||||
|
})
|
||||||
|
|
||||||
|
// 页面加载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
// 新增:从路由参数获取身份证号并自动查询
|
||||||
|
const idCardFromRoute = route.query.idCard as string
|
||||||
|
if (idCardFromRoute) {
|
||||||
|
queryForm.idCard = idCardFromRoute
|
||||||
|
// 延迟执行避免表单验证时机问题
|
||||||
|
setTimeout(() => {
|
||||||
|
handleQuerySubmit()
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
const idCard = studentData.baseInfo.idCard
|
||||||
|
if (idCard && typeof idCard === 'string') {
|
||||||
|
loadProcessStatus(idCard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.el-tabs {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -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'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// 本地缓冲key
|
||||||
|
|
||||||
|
//token
|
||||||
|
export const TOKEN_KEY = 'token'
|
||||||
|
//账号
|
||||||
|
export const ACCOUNT_KEY = 'account'
|
||||||
|
//设置
|
||||||
|
export const SETTING_KEY = 'setting'
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export enum PageEnum {
|
||||||
|
//登录页面
|
||||||
|
LOGIN = '/login',
|
||||||
|
//无权限页面
|
||||||
|
ERROR_403 = '/403',
|
||||||
|
INDEX = '/'
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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')
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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<FetchOptions>) {
|
||||||
|
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 || {})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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<RequestOptions>) {
|
||||||
|
return this.request(
|
||||||
|
{ ...fetchOptions, method: RequestMethodsEnum.GET },
|
||||||
|
requestOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description post请求
|
||||||
|
*/
|
||||||
|
post(fetchOptions: FetchOptions, requestOptions?: Partial<RequestOptions>) {
|
||||||
|
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<RequestOptions>
|
||||||
|
): Promise<any> {
|
||||||
|
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<any>) => {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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, any>): 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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
|
@ -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'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue