first commit
commit
3934b1f866
|
|
@ -0,0 +1,28 @@
|
||||||
|
# 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?
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/dist/*
|
||||||
|
.local
|
||||||
|
.output.js
|
||||||
|
.github
|
||||||
|
.vscode
|
||||||
|
/node_modules/**
|
||||||
|
|
||||||
|
*.html
|
||||||
|
**/*.svg
|
||||||
|
**/*.sh
|
||||||
|
|
||||||
|
/public/**
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"printWidth": 160,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"semi": true,
|
||||||
|
"singleAttributePerLine": false,
|
||||||
|
"vueIndentScriptAndStyle": true,
|
||||||
|
"htmlWhitespaceSensitivity": "ignore"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Vue 3 + TypeScript + Vite 6.x + VueRouter 4.x + Pinia 模板
|
||||||
|
|
||||||
|
pnpm作为包管理,主要可以解决幽灵依赖包的问题
|
||||||
|
|
||||||
|
1. Pinia 状态管理库带加密功能
|
||||||
|
本地建立.env
|
||||||
|
文件中`VITE_SECRET_KEY=xxx`
|
||||||
|
|
||||||
|
2. 自定义Axios 加密参数 错误日志记录
|
||||||
|
|
||||||
|
3. VueRouter 4.x 路由 权限控制 动态缓存
|
||||||
|
import { removeKeepAliveCache, resetKeepAliveCache } from '@/router/keepAlive'
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
onBeforeRouteLeave((to) => {
|
||||||
|
if (to.path === '/C') {
|
||||||
|
removeKeepAliveCache(instance)
|
||||||
|
} else {
|
||||||
|
resetKeepAliveCache(instance)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
通过 removeKeepAliveCache, resetKeepAliveCache 来维护exclude数组里面的内容达到 a 进入C后返回到A页面时,a页面的缓存被清楚,进入其他页面返回则不被清除缓存
|
||||||
|
|
||||||
|
权限控制
|
||||||
|
有身份权限的写在privateRoutes.ts中
|
||||||
|
没有身份权限的写在publicRoutes.ts中
|
||||||
|
|
||||||
|
4. 项目版本检测,确保启动顺利。检测node版本和工具包的版本是否符合要求。
|
||||||
|
可以根据项目需求修改package.json中的engines字段。
|
||||||
|
``` shell
|
||||||
|
pnpm run check-env
|
||||||
|
```
|
||||||
|
5. prettier 统一的代码格式,这样保存格式化的时候就不会因为不同的格式化工具导致其他的内容也出现修改。vscode配合prettier插件
|
||||||
|
.prettierrc.json的内容就是指定代码的格式的配置文件
|
||||||
|
|
||||||
|
6. SvgIcon组件,更加优雅的使用svg图片
|
||||||
|
将图片放入到 /public/icons 下面,name就是图片的名字例如certificate.svg
|
||||||
|
``` html
|
||||||
|
<SvgIcon name="certificate" class="w-10 h-10"></SvgIcon>
|
||||||
|
|
||||||
|
```
|
||||||
|
推荐使用较少的svg代码图片,不然html的体积会进一步增大
|
||||||
|
|
||||||
|
如果是大的背景图推荐使用`vite`的动态导入
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
|
||||||
|
const getPath = async () => {
|
||||||
|
const { default: svg } = await import("/src/assets/svg-img/certificate.svg?raw");
|
||||||
|
test.value = svg;
|
||||||
|
};
|
||||||
|
|
||||||
|
getPath();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
7. unocss 即时原子 CSS 引擎。主要想解决遍地css文件的麻烦。预设了tailwind css的预算值。所以可以直接对着tailwind css的官方文档编写
|
||||||
|
|
||||||
|
8. composables 这个文件夹推荐将业务逻辑写在此处的x.ts文件内,然后搭配 vue3的组合式语法。解决视图层和业务层的代码分离。
|
||||||
|
|
||||||
|
9. 异步加载动态组件, 针对大的页面要加载很多的JS,可以异步错开或者等待条件允许再加载。看个人业务需求
|
||||||
|
```html
|
||||||
|
|
||||||
|
<component :is="currentComponent"></component>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
|
||||||
|
let currentComponent = ref(defineAsyncComponent(() => import('@/components/TestComponent.vue')))
|
||||||
|
let flag = false
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (flag) {
|
||||||
|
currentComponent.value = defineAsyncComponent(() => import('@/components/TestComponent2.vue'));
|
||||||
|
} else {
|
||||||
|
currentComponent.value = defineAsyncComponent(() => import('@/components/TestComponent.vue'));
|
||||||
|
}
|
||||||
|
flag = !flag
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<!-- WX JS SDK 微信小程序一定要引入这个文件 可以自己去官方下载最新的-->
|
||||||
|
<script type="text/javascript" src="/js/jweixin-1.6.0.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/uni.webview.1.5.6.js" defer></script>
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "vue-app-template",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc -b && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check-env": "node ./scripts/checkVersions.cjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/node": "^22.10.1",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"pinia": "^2.3.0",
|
||||||
|
"pinia-plugin-persistedstate": "^4.1.3",
|
||||||
|
"semver": "^7.6.3",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "4",
|
||||||
|
"vue-virtual-draglist": "^3.3.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@iconify-json/carbon": "^1.2.8",
|
||||||
|
"@types/qs": "^6.9.17",
|
||||||
|
"@unocss-applet/preset-rem-rpx": "^0.9.0",
|
||||||
|
"@unocss/preset-wind": "^0.65.2",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"prettier": "3.4.2",
|
||||||
|
"sass-embedded": "^1.86.0",
|
||||||
|
"svg-sprite-loader": "^6.0.11",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"unocss": "^0.65.2",
|
||||||
|
"unplugin-auto-import": "^0.19.0",
|
||||||
|
"vite": "^6.0.1",
|
||||||
|
"vue-tsc": "^2.1.10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.11.0",
|
||||||
|
"pnpm": ">=9.13.2"
|
||||||
|
},
|
||||||
|
"preinstall": "npx only-allow pnpm"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
|
||||||
|
// /plugins/svgBuilder.ts
|
||||||
|
import {readFileSync, readdirSync} from 'fs'
|
||||||
|
import {join as pathJoin} from 'path'
|
||||||
|
import {Plugin} from 'vite'
|
||||||
|
|
||||||
|
let idPrefix = ''
|
||||||
|
const svgTitle = /<svg([^>+].*?)>/
|
||||||
|
const clearHeightWidth = /(width|height)="([^>+].*?)"/g
|
||||||
|
const clearFill = /fill="[^>+].*?"/g;
|
||||||
|
|
||||||
|
const hasViewBox = /(viewBox="[^>+].*?")/g
|
||||||
|
|
||||||
|
const clearReturn = /(\r)|(\n)/g
|
||||||
|
|
||||||
|
const findSvgFile = (dir: string) => {
|
||||||
|
const svgRes: string[] = []
|
||||||
|
const directory = readdirSync(dir, {withFileTypes: true})
|
||||||
|
for (const dirent of directory) {
|
||||||
|
if (dirent?.isDirectory()) {
|
||||||
|
svgRes.push(...findSvgFile(pathJoin(dir, dirent.name, '/')))
|
||||||
|
} else {
|
||||||
|
const svg = readFileSync(pathJoin(dir, dirent.name))
|
||||||
|
.toString()
|
||||||
|
.replace(clearReturn, '')
|
||||||
|
.replace(clearFill,'')
|
||||||
|
.replace(svgTitle, (_$1: string, $2: string) => {
|
||||||
|
let width = '0'
|
||||||
|
let height = '0'
|
||||||
|
let content = $2.replace(
|
||||||
|
clearHeightWidth,
|
||||||
|
(_s1, s2:string, s3:string) => {
|
||||||
|
if (s2 === 'width') {
|
||||||
|
width = s3
|
||||||
|
} else if (s2 === 'height') {
|
||||||
|
height = s3
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!hasViewBox.test($2)) {
|
||||||
|
content += `viewBox="0 0 ${width} ${height}"`
|
||||||
|
}
|
||||||
|
return `<symbol id="${idPrefix}-${dirent.name.replace(
|
||||||
|
'.svg',
|
||||||
|
''
|
||||||
|
)}" ${content}>`
|
||||||
|
})
|
||||||
|
.replace('</svg>', '</symbol>')
|
||||||
|
svgRes.push(svg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return svgRes
|
||||||
|
}
|
||||||
|
|
||||||
|
const svgBuilder = (path: string, prefix = 'icon'): Plugin => {
|
||||||
|
idPrefix = prefix
|
||||||
|
const res = findSvgFile(path)
|
||||||
|
return {
|
||||||
|
name: 'svg-transform',
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
return html.replace(
|
||||||
|
'<body>',
|
||||||
|
`<body><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">${res.join('')}</svg>`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default svgBuilder
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,18 @@
|
||||||
|
const semver = require('semver')
|
||||||
|
const { engines } = require('../package')// 这里的package是你的package.json文件路径
|
||||||
|
const version = engines.node
|
||||||
|
|
||||||
|
if (!semver.satisfies(process.version, version)) {
|
||||||
|
console.log(
|
||||||
|
[
|
||||||
|
'node环境变量版本错误',
|
||||||
|
'你的node环境启动位置' + process.execPath + '.',
|
||||||
|
'要求环境版本' + version,
|
||||||
|
'你的环境版本' + process.version
|
||||||
|
].join('\n')
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}else{
|
||||||
|
console.log(`echo check environment successfully`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<router-view v-slot="{ Component,route }">
|
||||||
|
<keep-alive :include="include" :exclude="excludes">
|
||||||
|
<transition name="fade-slide" mode="out-in">
|
||||||
|
<component :is="Component" :key="route.path" class="w-screen h-screen"/>
|
||||||
|
</transition>
|
||||||
|
</keep-alive>
|
||||||
|
</router-view>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { excludes } from '@/router/keepAlive'
|
||||||
|
|
||||||
|
const include:string[] = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 过渡动画:左右滑动并且淡入淡出 */
|
||||||
|
|
||||||
|
.fade-slide-enter-active, .fade-slide-leave-active {
|
||||||
|
transition: transform 1s ease, opacity 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进入动画:从右侧进入 */
|
||||||
|
.fade-slide-enter {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 离开动画:从左侧离开 */
|
||||||
|
.fade-slide-leave-to {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
import axios, { AxiosRequestConfig } from 'axios'
|
||||||
|
import { handleUrl } from './encryptUrl'
|
||||||
|
|
||||||
|
// 自定义判断元素类型JS
|
||||||
|
function toType(obj: any): string {
|
||||||
|
return {}.toString
|
||||||
|
.call(obj)
|
||||||
|
.match(/\s([a-zA-Z]+)/)![1]
|
||||||
|
.toLowerCase()
|
||||||
|
}
|
||||||
|
// 参数过滤函数
|
||||||
|
function filterNull(o: any) {
|
||||||
|
for (var key in o) {
|
||||||
|
if (o[key] === null) {
|
||||||
|
delete o[key]
|
||||||
|
}
|
||||||
|
if (toType(o[key]) === 'string') {
|
||||||
|
o[key] = o[key].trim()
|
||||||
|
} else if (toType(o[key]) === 'object') {
|
||||||
|
o[key] = filterNull(o[key])
|
||||||
|
} else if (toType(o[key]) === 'array') {
|
||||||
|
o[key] = filterNull(o[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
接口处理函数
|
||||||
|
这个函数每个项目都是不一样的,我现在调整的是适用于
|
||||||
|
https://cnodejs.org/api/v1 的接口,如果是其他接口
|
||||||
|
需要根据接口的参数进行调整。参考说明文档地址:
|
||||||
|
https://cnodejs.org/topic/5378720ed6e2d16149fa16bd
|
||||||
|
主要是,不同的接口的成功标识和失败提示是不一致的。
|
||||||
|
另外,不同的项目的处理方法也是不一致的,这里出错就是简单的alert
|
||||||
|
*/
|
||||||
|
|
||||||
|
function apiAxios(
|
||||||
|
method: string,
|
||||||
|
url: string,
|
||||||
|
params: null | string | object,
|
||||||
|
success: any,
|
||||||
|
failure: any,
|
||||||
|
unEncrypt: boolean = false // 是否不加密
|
||||||
|
) {
|
||||||
|
let contentTypeIsJson = false
|
||||||
|
if (params && typeof params != 'string') {
|
||||||
|
params = filterNull(params)
|
||||||
|
} else contentTypeIsJson = true
|
||||||
|
|
||||||
|
axios({
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
data: method === 'POST' || method === 'PUT' ? params : null,
|
||||||
|
params: method === 'GET' || method === 'DELETE' ? params : null,
|
||||||
|
withCredentials: true,
|
||||||
|
crossDomain: true,
|
||||||
|
unEncrypt,
|
||||||
|
transformRequest: [
|
||||||
|
function (data) {
|
||||||
|
if (contentTypeIsJson) return data
|
||||||
|
let ret = ''
|
||||||
|
for (let it in data) {
|
||||||
|
ret +=
|
||||||
|
encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
},
|
||||||
|
],
|
||||||
|
headers: {
|
||||||
|
'Content-Type': contentTypeIsJson
|
||||||
|
? 'application/json'
|
||||||
|
: 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
} as AxiosRequestConfig<any>)
|
||||||
|
.then(function (res) {
|
||||||
|
let response = res.data
|
||||||
|
if (response.code == 200) {
|
||||||
|
if (success) {
|
||||||
|
success(response)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (failure) {
|
||||||
|
failure(response)
|
||||||
|
} else {
|
||||||
|
if (response.code == 2) {
|
||||||
|
//错误处理
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload()
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
//错误处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
let res = err.response
|
||||||
|
console.error(res || err)
|
||||||
|
if (res) {
|
||||||
|
// 清楚所有的错误提示
|
||||||
|
clearTimeout(timeObj)
|
||||||
|
if (res.data.msg) {
|
||||||
|
//错误处理
|
||||||
|
} else {
|
||||||
|
//错误处理
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let requestCount = 0
|
||||||
|
let timeObj: NodeJS.Timeout
|
||||||
|
// http request 拦截器
|
||||||
|
axios.interceptors.request.use((config) => {
|
||||||
|
requestCount++
|
||||||
|
if (requestCount == 1) {
|
||||||
|
timeObj = setTimeout(() => {
|
||||||
|
//加载中提示
|
||||||
|
}, 800)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.data &&
|
||||||
|
Object.prototype.toString.call(config.data) == '[object FormData]'
|
||||||
|
) {
|
||||||
|
config.headers!!['Content-Type'] = 'multipart/form-data;charset=utf-8'
|
||||||
|
config.transformRequest = [
|
||||||
|
function (data) {
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// 拦截配置,有新的配置,在这里新增函数处理,然后合并config
|
||||||
|
let _config = handleUrl(config)
|
||||||
|
config = Object.assign(config, _config)
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
// http response 拦截器
|
||||||
|
axios.interceptors.response.use((response) => {
|
||||||
|
requestCount--
|
||||||
|
if (requestCount === 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 关闭所有提示
|
||||||
|
}, 1500)
|
||||||
|
clearTimeout(timeObj)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
},error =>{
|
||||||
|
let xhrErrL = { type: "XHRERR", data: error.response };
|
||||||
|
if (error.response) {
|
||||||
|
const { status, data } = error.response;
|
||||||
|
if (status === 422) {
|
||||||
|
alert(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 返回在vue模板中的调用接口
|
||||||
|
export default {
|
||||||
|
get: function (
|
||||||
|
url: string,
|
||||||
|
params: string | object | null,
|
||||||
|
success: any,
|
||||||
|
failure: any
|
||||||
|
) {
|
||||||
|
return apiAxios('GET', url, params, success, failure)
|
||||||
|
},
|
||||||
|
post: function (
|
||||||
|
url: string,
|
||||||
|
params: string | object,
|
||||||
|
success: any,
|
||||||
|
failure: any
|
||||||
|
) {
|
||||||
|
return apiAxios('POST', url, params, success, failure)
|
||||||
|
},
|
||||||
|
put: function (
|
||||||
|
url: string,
|
||||||
|
params: string | object,
|
||||||
|
success: any,
|
||||||
|
failure: any
|
||||||
|
) {
|
||||||
|
return apiAxios('PUT', url, params, success, failure)
|
||||||
|
},
|
||||||
|
delete: function (
|
||||||
|
url: string,
|
||||||
|
params: string | object,
|
||||||
|
success: any,
|
||||||
|
failure: any
|
||||||
|
) {
|
||||||
|
return apiAxios('DELETE', url, params, success, failure)
|
||||||
|
},
|
||||||
|
// 不加密的post请求
|
||||||
|
unEncryptPost: function (
|
||||||
|
url: string,
|
||||||
|
params: string | object,
|
||||||
|
success: any,
|
||||||
|
failure: any,
|
||||||
|
) {
|
||||||
|
return apiAxios('POST', url, params, success, failure,true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 对指定请求链接进行加密
|
||||||
|
|
||||||
|
import { AxiosRequestConfig } from "axios";
|
||||||
|
import { useUserStore } from "@/store/user";
|
||||||
|
|
||||||
|
export const handleUrl = async (config: (AxiosRequestConfig & {unEncrypt?: boolean})) => {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
if(userStore.getToken){
|
||||||
|
config.headers!!["Authorization"] = `Bearer ${userStore.getToken}`
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
|
const computed: typeof import('vue')['computed']
|
||||||
|
const createApp: typeof import('vue')['createApp']
|
||||||
|
const createPinia: typeof import('pinia')['createPinia']
|
||||||
|
const customRef: typeof import('vue')['customRef']
|
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||||
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
|
const defineStore: typeof import('pinia')['defineStore']
|
||||||
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const h: typeof import('vue')['h']
|
||||||
|
const inject: typeof import('vue')['inject']
|
||||||
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const mapActions: typeof import('pinia')['mapActions']
|
||||||
|
const mapGetters: typeof import('pinia')['mapGetters']
|
||||||
|
const mapState: typeof import('pinia')['mapState']
|
||||||
|
const mapStores: typeof import('pinia')['mapStores']
|
||||||
|
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||||
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
|
const onMounted: typeof import('vue')['onMounted']
|
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
|
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||||
|
const provide: typeof import('vue')['provide']
|
||||||
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
const readonly: typeof import('vue')['readonly']
|
||||||
|
const ref: typeof import('vue')['ref']
|
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
|
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||||
|
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
|
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||||
|
const toRaw: typeof import('vue')['toRaw']
|
||||||
|
const toRef: typeof import('vue')['toRef']
|
||||||
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
|
const toValue: typeof import('vue')['toValue']
|
||||||
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
|
const unref: typeof import('vue')['unref']
|
||||||
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
|
const useId: typeof import('vue')['useId']
|
||||||
|
const useLink: typeof import('vue-router')['useLink']
|
||||||
|
const useModel: typeof import('vue')['useModel']
|
||||||
|
const useRoute: typeof import('vue-router')['useRoute']
|
||||||
|
const useRouter: typeof import('vue-router')['useRouter']
|
||||||
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
|
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||||
|
const watch: typeof import('vue')['watch']
|
||||||
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<div class="block">
|
||||||
|
<i v-html="content"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
|
||||||
|
content:{
|
||||||
|
type:String,
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="h-[16rpx] bg-[#f8f8f8]"></div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col items-center gap-[16rpx] custom-background px-[32rpx]"
|
||||||
|
:style="`--background-color:${calcTypeName(college.type).style.backgroundColor}`">
|
||||||
|
<div class="flex py-[32rpx] gap-[30rpx] w-full">
|
||||||
|
<div class="flex flex-col items-center gap-[16rpx]">
|
||||||
|
<div class="flex items-center gap-[8rpx] text-[#303030] text-[28rpx]" @touchstart="toggleCollapse">
|
||||||
|
{{ collegeIndex+1 }}
|
||||||
|
<div class="i-carbon-chevron-down text-[16rpx] transition-transform duration-300" :class="{ 'rotate-180': isCollapsed }"></div>
|
||||||
|
</div>
|
||||||
|
<div class="w-[52rpx] h-[52rpx] rounded-[8rpx] font-semibold text-[28rpx] flex items-center justify-center" :style="calcTypeName(college.type).style">
|
||||||
|
{{ calcTypeName(college.type).text }}
|
||||||
|
</div>
|
||||||
|
<span class="text-[32rpx] font-semibold">
|
||||||
|
{{ Math.round(college.vItems.reduce((a:number, b:any) => a + Number(b.percentAge.replace("%", "")), 0) / college.vItems.length) }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col justify-between flex-1">
|
||||||
|
<div class="flex justify-between mb-[14rpx]">
|
||||||
|
<div class="flex justify-between flex-col gap-[6rpx]">
|
||||||
|
<span class="text-[32rpx] font-semibold">{{ college.name }}</span>
|
||||||
|
<span class="text-[22rpx] text-[#505050]">{{ college.ownership }}·{{ college.educationCategory }}</span>
|
||||||
|
<div class="text-[22rpx] text-[#8F959E] flex items-center">
|
||||||
|
<span class="truncate max-w-[300rpx]" v-show="college.features.length > 0">{{ college.features.slice(0, 3).join("/") }}/</span>
|
||||||
|
<span>排名{{ college.rank }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[22rpx] text-[#1F2329] mt-[8rpx] flex gap-[10rpx]">
|
||||||
|
<span class="">代码{{ college.collegeCode }}</span>
|
||||||
|
<span>{{ college.year }}计划{{ college.vItems.reduce((a:number, b:any) => a + b.planCount, 0) }}人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-[40rpx]">
|
||||||
|
<div class="i-carbon-move"></div>
|
||||||
|
<div class="i-carbon-trash-can"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable :data="college.childItems" :score="score" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-[2rpx] bg-[#EDEDED] w-full"></div>
|
||||||
|
<div class="overflow-auto transition-all duration-300 w-full" :style="{ maxHeight: isCollapsed ? '100vh' : '0' }" v-if="isCollapsed">
|
||||||
|
<virtual-list v-model="college.vItems" data-key="sort" lock-axis="x">
|
||||||
|
<template v-slot:item="{ record, index, dataKey }">
|
||||||
|
<MajorItem :major="record" :major-index="index" :score="score" :year="2024" />
|
||||||
|
</template>
|
||||||
|
</virtual-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { calcTypeName } from '@/composables/useSortCollege';
|
||||||
|
import VirtualList from "vue-virtual-draglist";
|
||||||
|
import MajorItem from './MajorItem.vue'
|
||||||
|
import DataTable from './DataTable.vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
college: any
|
||||||
|
score: number
|
||||||
|
collegeIndex: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['toggleCollapse', 'updateHeight', 'move', 'delete', 'deleteMajor'])
|
||||||
|
|
||||||
|
const isCollapsed = ref(false)
|
||||||
|
const toggleCollapse = () => {
|
||||||
|
isCollapsed.value = !isCollapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMove = () => {
|
||||||
|
emit('move')
|
||||||
|
}
|
||||||
|
const handleDelete = () => {
|
||||||
|
emit('delete')
|
||||||
|
}
|
||||||
|
|
||||||
|
const majorDrop = ref()
|
||||||
|
|
||||||
|
const handleMajorMove = (index:number) => {
|
||||||
|
majorDrop.value.handleLongpress(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMajorList = (list:any[]) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMajorDelete = (index:number) => {
|
||||||
|
majorDrop.value.initList(props.college.vItems, true).then(() => {
|
||||||
|
emit('deleteMajor', index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.custom-background {
|
||||||
|
background: linear-gradient(180deg, var(--background-color) 0%, #fff 30%, #fff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加过渡效果
|
||||||
|
.transition-all {
|
||||||
|
transition-property: all;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<template>
|
||||||
|
<!-- Table -->
|
||||||
|
<table class="w-full">
|
||||||
|
<!-- Table Header -->
|
||||||
|
<thead>
|
||||||
|
<tr class="text-[22rpx] text-[#505050]">
|
||||||
|
<th
|
||||||
|
v-for="(headItem, index) in columns"
|
||||||
|
:key="index"
|
||||||
|
:style="{ width: headItem.width }"
|
||||||
|
class="text-left px-[8rpx] py-[12rpx]"
|
||||||
|
>
|
||||||
|
{{ headItem.label }}
|
||||||
|
<span
|
||||||
|
class="i-carbon-help text-[#86909C]"
|
||||||
|
v-if="headItem.key === 'lineDiff' || headItem.key === 'rankDiff'"
|
||||||
|
></span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<!-- Table Body -->
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(item, index) in recompileData"
|
||||||
|
:key="index"
|
||||||
|
class="text-[22rpx] text-[#505050] border-t border-[#EDEDED]"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
v-for="(col, colIndex) in columns"
|
||||||
|
:key="colIndex"
|
||||||
|
:style="{ width: col.width }"
|
||||||
|
class="px-[8rpx] py-[12rpx]"
|
||||||
|
>
|
||||||
|
<div v-if="col.key === 'lineDiff'">
|
||||||
|
{{ item['lineDiff'] }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-[8rpx]" v-else-if="col.key === 'rankDiff'">
|
||||||
|
{{ item['rankDiff'] }}
|
||||||
|
<img
|
||||||
|
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAATCAYAAACORR0GAAAAAXNSR0IArs4c6QAAATlJREFUSEu11eFNwzAQBeB3gQEyAIV2g67QDewJgBGYgHYCRmhH8AbABHSDRhSJvxmA5sEFtTKuk7jIza9EivP57l0SQcZjY8z0ApgSGAtwI0ApInpdSkYH78bcicjy6JnkOiukQAwj8Jod+rD29qd1K78qkqusUAxRsCEX2aAQIXmviGam58mQTtTEuXVseGLItXNt+zSzAkgbhq0xjxCZg5yPnFv4WB/i3zdY0QHZr/KwAKlB2pFzL7Gqe6EjxMNEpPKmqxZydtXR2jarrhc2QGoNtBB50rc+WDOIdEIhst/tpzHjncizhyUhUagL2VfhYeVQuzqHYQj5gwFlXyZhJIeMUpH/foRb6NxIm5GPEKgK0p7SktQKZWvtG35/VtWOnE2cq1IXn3KfbIwpL4ti+dU0D+dCdEPf+I3M1u2H9e8AAAAASUVORK5CYII="
|
||||||
|
alt="rankDiff"
|
||||||
|
class="w-[16rpx] h-[16rpx]"
|
||||||
|
v-if="item['rankDiff'] > 0"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAATCAYAAACORR0GAAAAAXNSR0IArs4c6QAAARRJREFUSEu91EFOhDAYhuH370TXHGGOwMaEuNJM4jnKCYwnEE4yeAoTE0LcsZwb6BE8wFC1OBBGO6UmZboikPTJ9/Uvkj3rtbqkMUqK9nb7xEJLrmv9Bqzt/kaJXgqTrNZawXYIshQmFjgH1kPnwEboP9jVi05XkLZ3VRU6O0dQCGaRixUNkBgo2k1VhmB/IB82RcbhCcSckBMzlEpxb5MckI/hOSTZSciFTVLkXcduqLC/gzPJvJAzGeTt5mcIflfpw2ah6YYGHgZkSBeKBUFzU+XDslo/YniNAp2q0b5XUNCRR4Nc2NiEoYwKebAqOmTPpK/reMU7o8NVcCH203vURDeNTvZ7UhGS7pMUIUFI5ftv8gVvqo+bwdfwaAAAAABJRU5ErkJggg=="
|
||||||
|
alt="rankDiff"
|
||||||
|
class="w-[16rpx] h-[16rpx]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ item[col.key] }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: true,
|
||||||
|
});
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array<any>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
score: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = ref([
|
||||||
|
{ label: "年份", key: "year", width: "62rpx" },
|
||||||
|
{ label: "录取", key: "planCount", width: "54rpx" },
|
||||||
|
{ label: "线差", key: "lineDiff", width: "86rpx" },
|
||||||
|
{ label: "最低分", key: "score", width: "76rpx" },
|
||||||
|
{ label: "最低位次", key: "rankLine", width: "98rpx" },
|
||||||
|
{ label: "位次差", key: "rankDiff", width: "max-content" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const rankDiff = (index: number, item: any) => {
|
||||||
|
return index === props.data.length - 1 ? item["rankLine"] : item["rankLine"] - props.data[index + 1]["rankLine"];
|
||||||
|
};
|
||||||
|
|
||||||
|
const recompileData = computed(() => {
|
||||||
|
if (!props.data) return [];
|
||||||
|
let _data = props.data.map((item: any, index) => {
|
||||||
|
item["rankDiff"] = rankDiff(index, item);
|
||||||
|
item["lineDiff"] = item["score"] - props.score;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return _data;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bg-[#E2EDF9] text-[#1580FF] text-[22rpx] flex justify-between items-center px-[32rpx] py-[10rpx]"
|
||||||
|
>
|
||||||
|
<div class="flex gap-[16rpx]">
|
||||||
|
<span>{{ score }}分</span>
|
||||||
|
<span>
|
||||||
|
{{ subjectGroup.split(',').join('/') }}
|
||||||
|
</span>
|
||||||
|
<span>{{ batchName }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex items-center gap-[26rpx] text-[22rpx] text-[#000]">
|
||||||
|
<div v-for="(model) in tModels" :key="model.type" class="flex items-center gap-[6rpx]">
|
||||||
|
<div
|
||||||
|
class="w-[16rpx] h-[16rpx] rounded-full"
|
||||||
|
:style="{ backgroundColor: calcTypeName(model.type).roundedBgColor }"
|
||||||
|
></div>
|
||||||
|
<span class="text-[22rpx]">{{ calcTypeName(model.type)?.text }}({{ model.count }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {calcTypeName, countModel} from "@/composables/useSortCollege"
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
subjectGroup:{
|
||||||
|
type:String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
batchName:{
|
||||||
|
type:String,
|
||||||
|
default:''
|
||||||
|
},
|
||||||
|
score:{
|
||||||
|
type:[Number,String],
|
||||||
|
default:0
|
||||||
|
},
|
||||||
|
wishList:{
|
||||||
|
type:Array,
|
||||||
|
default:() => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const userWhishList = ref([])
|
||||||
|
const tModels = ref([])
|
||||||
|
|
||||||
|
watch(() => props.wishList, (newVal) => {
|
||||||
|
userWhishList.value = newVal
|
||||||
|
const {tModel} = countModel(userWhishList.value)
|
||||||
|
tModels.value = tModel
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.t-model-base {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<div class="pt-[32rpx] pb-[30rpx] flex gap-[30rpx] not-last:border-b border-[#EDEDED]">
|
||||||
|
<div class="flex flex-col gap-[16rpx]">
|
||||||
|
<span class="text-[32rpx] font-semibold text-[#000]">{{ major.percentAge || "0%" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-[16rpx] flex-1">
|
||||||
|
<div class="flex justify-between flex-auto">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[32rpx] text-[#000] font-semibold truncate max-w-[400rpx]">
|
||||||
|
{{ major.major.replace(/(\r\n|\n|\r)/g, "") }}
|
||||||
|
</span>
|
||||||
|
<span class="text-[22rpx] text-[#1F2329] mt-[14rpx]">{{ major.remark }}</span>
|
||||||
|
<div class="flex justify-between text-[22rpx] text-[#1F2329] mt-[14rpx]">
|
||||||
|
<div class="flex flex-col gap-[6rpx]">
|
||||||
|
<span>代码{{ major.majorCode }}</span>
|
||||||
|
<span>{{ year }}计划{{ major.planCount }}人</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-[6rpx]">
|
||||||
|
<span>选科:{{ major.subjectClam }}</span>
|
||||||
|
<span>学费/学制:{{ major.fee }}/{{ major.academic }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-[40rpx]">
|
||||||
|
<div class="i-carbon-move" @touchstart="handleMove"></div>
|
||||||
|
<div class="i-carbon-trash-can" @click="handleDelete"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DataTable :data="major.planItems" :score="score" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import DataTable from './DataTable.vue';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
major: any;
|
||||||
|
score: number;
|
||||||
|
year: number;
|
||||||
|
majorIndex: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(["move", "delete"]);
|
||||||
|
|
||||||
|
const handleMove = () => {
|
||||||
|
emit("move");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
emit("delete");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
export interface SvgIconProps {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<SvgIconProps>()
|
||||||
|
|
||||||
|
const styles = useCssModule()
|
||||||
|
const attrs = useAttrs()
|
||||||
|
|
||||||
|
const iconName = computed(() => `#icon-${props.name}`);
|
||||||
|
const svgClass = computed(() => {
|
||||||
|
const className = [styles['svg-icon']]
|
||||||
|
if (props.name) className.push(`icon-${props.name}`)
|
||||||
|
return className
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg :class="svgClass" v-bind="attrs">
|
||||||
|
<use :xlink:href="iconName"></use>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
fill: currentColor;
|
||||||
|
vertical-align: middle;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import SvgIcon from './SvgIcon.vue'
|
||||||
|
import type {SvgIconProps} from './SvgIcon.vue'
|
||||||
|
|
||||||
|
export {
|
||||||
|
SvgIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
export type {
|
||||||
|
SvgIconProps
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
export const calcTypeName = (type: number) => {
|
||||||
|
const style = {
|
||||||
|
2: {
|
||||||
|
text: "冲",
|
||||||
|
color: "#EB5241",
|
||||||
|
bgColor: "rgba(235,82,65,0.15)",
|
||||||
|
roundedBgColor: "#E75859",
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
text: "稳",
|
||||||
|
color: "#FA8E23",
|
||||||
|
bgColor: "rgba(250,142,35,0.15)",
|
||||||
|
roundedBgColor: "#FF8800",
|
||||||
|
},
|
||||||
|
0: {
|
||||||
|
text: "保",
|
||||||
|
color: "#15C496",
|
||||||
|
bgColor: "rgba(21,196,150,0.15)",
|
||||||
|
roundedBgColor: "#34C724",
|
||||||
|
},
|
||||||
|
}[type] || { text: "保", color: "#15C496", bgColor: "rgba(21,196,150,0.15)" };
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: style.text,
|
||||||
|
style: {
|
||||||
|
color: style.color,
|
||||||
|
backgroundColor: style.bgColor,
|
||||||
|
},
|
||||||
|
roundedBgColor: style.roundedBgColor,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countModel = (list: any[]) => {
|
||||||
|
const tModel = [
|
||||||
|
{ type: 2, count: 0 },
|
||||||
|
{ type: 1, count: 0 },
|
||||||
|
{ type: 0, count: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
list.forEach((item) => {
|
||||||
|
item.vItems.forEach((vItem:{type:number}) => {
|
||||||
|
const target = tModel.find((t) => t.type === vItem.type);
|
||||||
|
if (target) target.count++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { tModel };
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import './style.css'
|
||||||
|
import 'uno.css';
|
||||||
|
import App from './App.vue'
|
||||||
|
// Pinia 持久化
|
||||||
|
import store from '@/store/index'
|
||||||
|
//导入路由
|
||||||
|
import router from '@/router/index'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
// 注册已经加上了持久化的pinia
|
||||||
|
app.use(store)
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
|
import { publicRoutes } from "./publicRoutes";
|
||||||
|
import { privateRoutes } from "./privateRoutes";
|
||||||
|
function getRoutes() {
|
||||||
|
const routes = [
|
||||||
|
// 私有路由,请在这里添加
|
||||||
|
...privateRoutes,
|
||||||
|
|
||||||
|
// 公共路由
|
||||||
|
...publicRoutes,
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* 如果要对 routes 做一些处理,请在这里修改
|
||||||
|
*/
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes: getRoutes(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 全局前置守卫,这边可以对身份进行验证
|
||||||
|
router.beforeEach((to, _from, next) => {
|
||||||
|
|
||||||
|
let userRole = "admin";
|
||||||
|
// 如果目标路由没有角色限制
|
||||||
|
if (!to.meta.role) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
// 判断当前用户角色是否在目标路由的允许角色列表中
|
||||||
|
if ((to.meta.role as string[]).includes(userRole)) {
|
||||||
|
// 如果角色匹配,允许进入目标路由
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
// 如果角色不匹配,跳转到 unauthorized 页面
|
||||||
|
next({ path: "/unauthorized" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听路由变化,动态设置网页标题
|
||||||
|
router.afterEach((to) => {
|
||||||
|
if (to.meta.title) {
|
||||||
|
document.title = to.meta.title as string;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { ComponentInternalInstance, ref } from 'vue'
|
||||||
|
|
||||||
|
export const excludes = ref<string[]>([])
|
||||||
|
|
||||||
|
export function removeKeepAliveCache(instance: ComponentInternalInstance) {
|
||||||
|
if (!excludes.value.includes(instance.type.name!)) {
|
||||||
|
excludes.value.push(instance.type.name!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetKeepAliveCache(instance: ComponentInternalInstance) {
|
||||||
|
excludes.value = excludes.value.filter((item) => item !== instance.type.name)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const privateRoutes = [];
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
export const publicRoutes = [
|
||||||
|
{
|
||||||
|
path:"/sort-college",
|
||||||
|
name:'sort',
|
||||||
|
meta:{
|
||||||
|
title:'排序'
|
||||||
|
},
|
||||||
|
component:()=>import('../views/sort-college.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path:"/unauthorized",
|
||||||
|
name:'unauthorized',
|
||||||
|
meta:{
|
||||||
|
title:'unauthorized'
|
||||||
|
},
|
||||||
|
component:()=>import('../views/unauthorized.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// pinia数据持久化存储
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import { createPersistedState } from 'pinia-plugin-persistedstate'
|
||||||
|
import { SelfStorage } from './secureStore'
|
||||||
|
|
||||||
|
// 第一个参数是应用程序中 store 的唯一 id
|
||||||
|
const store = createPinia()
|
||||||
|
store.use(
|
||||||
|
createPersistedState({
|
||||||
|
storage: SelfStorage
|
||||||
|
})
|
||||||
|
)
|
||||||
|
export default store
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { StorageLike } from 'pinia-plugin-persistedstate'
|
||||||
|
import CryptoJS from 'crypto-js'
|
||||||
|
|
||||||
|
const SECRET_KEY = import.meta.env.VITE_SECRET_KEY // 自定义加密密钥
|
||||||
|
|
||||||
|
// 自定义加密存储
|
||||||
|
export const SelfStorage: StorageLike = {
|
||||||
|
setItem(key: string, value: string) {
|
||||||
|
// 加密值
|
||||||
|
const encryptedValue = CryptoJS.AES.encrypt(value, SECRET_KEY).toString()
|
||||||
|
|
||||||
|
// 存储加密后的值
|
||||||
|
localStorage.setItem(key, encryptedValue)
|
||||||
|
},
|
||||||
|
getItem(key: string): string | null {
|
||||||
|
// 获取加密值
|
||||||
|
const encryptedValue = localStorage.getItem(key)
|
||||||
|
if (encryptedValue) {
|
||||||
|
// 解密
|
||||||
|
const bytes = CryptoJS.AES.decrypt(encryptedValue, SECRET_KEY)
|
||||||
|
const decryptedValue = bytes.toString(CryptoJS.enc.Utf8)
|
||||||
|
return decryptedValue || null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
export const useUserStore = defineStore("user", {
|
||||||
|
persist:true,
|
||||||
|
state:()=>({
|
||||||
|
token:""
|
||||||
|
}),
|
||||||
|
getters:{
|
||||||
|
getToken(state){
|
||||||
|
return state.token;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions:{
|
||||||
|
setToken(token:string){
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
html {
|
||||||
|
line-height: 1.15;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: none;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
[type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"],
|
||||||
|
button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner,
|
||||||
|
button::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
[type="button"]:-moz-focusring,
|
||||||
|
[type="reset"]:-moz-focusring,
|
||||||
|
[type="submit"]:-moz-focusring,
|
||||||
|
button:-moz-focusring {
|
||||||
|
outline: 1px dotted ButtonText;
|
||||||
|
}
|
||||||
|
fieldset {
|
||||||
|
padding: 0.35em 0.75em 0.625em;
|
||||||
|
}
|
||||||
|
legend {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: inherit;
|
||||||
|
display: table;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
[type="checkbox"],
|
||||||
|
[type="radio"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
[type="search"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
details {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* uni-app API 类型声明
|
||||||
|
*/
|
||||||
|
declare const uni: {
|
||||||
|
postMessage: (options: { data: Record<string, any> }) => void;
|
||||||
|
getEnv: (callback: (res: any) => void) => void;
|
||||||
|
switchTab: (options: { url: string }) => void;
|
||||||
|
reLaunch: (options: { url: string }) => void;
|
||||||
|
navigateBack: (options: { delta: number }) => void;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
// 埋点
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-screen flex flex-col">
|
||||||
|
<HeaderTip :score="score" :batch-name="batchName" :subject-group="subjectGroup" :wishList="wishList" />
|
||||||
|
<div class="flex-1 h-0 overflow-y-auto">
|
||||||
|
<virtual-list v-model="wishList" data-key="sort" lock-axis="x">
|
||||||
|
<template v-slot:item="{ record, index, dataKey }">
|
||||||
|
<CollegeItem :college="record" :college-index="index" :score="score" />
|
||||||
|
</template>
|
||||||
|
</virtual-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import HeaderTip from "@/components/sort-college/HeaderTip.vue";
|
||||||
|
import { useUserStore } from "@/store/user";
|
||||||
|
import VirtualList from "vue-virtual-draglist";
|
||||||
|
import CollegeItem from "@/components/sort-college/CollegeItem.vue"
|
||||||
|
import api from "@/api/customAxios";
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const vId = ref(0);
|
||||||
|
const score = ref(0);
|
||||||
|
const batchName = ref("");
|
||||||
|
const subjectGroup = ref("");
|
||||||
|
const locationCode = ref("");
|
||||||
|
const wishList = ref([]);
|
||||||
|
const handleClick = () => {
|
||||||
|
alert(localStorage.getItem("userInfo"));
|
||||||
|
uni.postMessage({
|
||||||
|
data: {
|
||||||
|
action: "message",
|
||||||
|
message: location.search,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
getWishList();
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
let _mapParams: { [key: string]: any } = {};
|
||||||
|
let params = decodeURIComponent(location.search).slice(1, location.search.length).split("&");
|
||||||
|
params.forEach((param) => {
|
||||||
|
let _param = param.split("=");
|
||||||
|
_mapParams[_param[0]] = _param[1];
|
||||||
|
});
|
||||||
|
vId.value = _mapParams.id;
|
||||||
|
|
||||||
|
userStore.setToken(_mapParams.token);
|
||||||
|
|
||||||
|
score.value = +_mapParams.score;
|
||||||
|
batchName.value = _mapParams.batchName;
|
||||||
|
subjectGroup.value = _mapParams.subjectGroup;
|
||||||
|
locationCode.value = _mapParams.location;
|
||||||
|
|
||||||
|
getWishList();
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWishList = () => {
|
||||||
|
api.get(
|
||||||
|
`https://api.v3.ycymedu.com/api/volunTb/get/${locationCode.value}`,
|
||||||
|
{ id: vId.value },
|
||||||
|
(resp: any) => {
|
||||||
|
if (resp.code == 200) {
|
||||||
|
wishList.value = resp.result.tbDetails;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<div>401 - Unauthorized</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import { DefineComponent } from 'vue'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
interface Window {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts","./plugins/*"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { defineConfig, presetIcons} from 'unocss'
|
||||||
|
import presetWind from '@unocss/preset-wind'
|
||||||
|
import { presetRemRpx } from '@unocss-applet/preset-rem-rpx'
|
||||||
|
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetRemRpx({ baseFontSize: 16, screenWidth: 375, mode: 'rpx2rem' }),
|
||||||
|
presetWind(),
|
||||||
|
presetIcons({
|
||||||
|
scale: 1.2,
|
||||||
|
warn: true,
|
||||||
|
extraProperties: {
|
||||||
|
display: 'inline-block',
|
||||||
|
'vertical-align': 'middle',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
rules:[]
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import { resolve } from "path";
|
||||||
|
import AutoComplete from "unplugin-auto-import/vite";
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
|
||||||
|
import svgBuilder from "./plugins/svgBuilder";
|
||||||
|
|
||||||
|
const pathSrc = resolve(__dirname, "src");
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
assetsInclude: ["**/*.svg"],
|
||||||
|
base: "./" /* 这个就是webpack里面的publicPath */,
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
// 最小化拆分包
|
||||||
|
manualChunks: (id) => {
|
||||||
|
if (id.includes("node_modules")) {
|
||||||
|
return id.toString().split("node_modules/")[1].split("/")[0].toString();
|
||||||
|
}
|
||||||
|
}, // 用于从入口点创建的块的打包输出格式[name]表示文件名,[hash]表示该文件内容hash值
|
||||||
|
entryFileNames: "js/[name].[hash].js", // 用于命名代码拆分时创建的共享块的输出命名
|
||||||
|
chunkFileNames: "js/[name].[hash].js", // 用于输出静态资源的命名,[ext]表示文件扩展名
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
UnoCSS(),
|
||||||
|
svgBuilder(resolve("./public/icons")), // svg存储的名字
|
||||||
|
AutoComplete({
|
||||||
|
imports: [
|
||||||
|
"vue",
|
||||||
|
"vue-router",
|
||||||
|
"pinia", // 自动导入 Pinia API(如 defineStore)
|
||||||
|
],
|
||||||
|
dts: resolve(pathSrc, "auto-imports.d.ts"),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@/": `${pathSrc}/`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: "localhost",
|
||||||
|
port: 3001,
|
||||||
|
proxy:{
|
||||||
|
'/api':{
|
||||||
|
target:'http://192.168.31.149:5006',
|
||||||
|
changeOrigin:true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
esbuild: {
|
||||||
|
drop: ["debugger"],
|
||||||
|
},
|
||||||
|
optimizeDeps: { // 在开发服务器启动时预构建依赖,将 CommonJS/UMD 模块转换为 ESM 格式,并缓存结果以提高性能。
|
||||||
|
include: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue