Initial commit

main
刘隽霖 4 months ago
parent b39cfad209
commit bc2786d736

@ -0,0 +1,35 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'prettier/prettier': [
'warn',
{
singleQuote: true, // 单引号
semi: false, // 无分号
printWidth: 80, // 每行宽度至多80字符
trailingComma: 'none', // 不加对象|数组最后逗号
endOfLine: 'auto' // 换行符号不限制win mac 不一致)
}
],
'vue/multi-word-component-names': [
'warn',
{
ignores: ['index'] // vue组件名称多单词组成忽略index.vue
}
],
'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
// 💡 添加未定义变量错误提示create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'error'
}
}

30
.gitignore vendored

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

@ -1,2 +1,35 @@
# quick-roll
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

@ -0,0 +1,43 @@
{
"name": "quick-roll",
"version": "0.0.0",
"private": true,
"type": "module",
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix"
]
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"prepare": "husky install",
"lint-staged": "lint-staged"
},
"dependencies": {
"axios": "^1.7.7",
"element-plus": "^2.8.4",
"mockjs": "^1.1.0",
"pinia": "^2.1.7",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"husky": "^8.0.0",
"lint-staged": "^15.2.10",
"pinia-plugin-persistedstate": "^4.1.1",
"prettier": "^3.2.5",
"sass": "^1.79.4",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.3.1"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,9 @@
<script setup></script>
<template>
<div>
<router-view></router-view>
</div>
</template>
<style scoped></style>

@ -0,0 +1,135 @@
<template>
<div class="scrolling-number">
<div
v-for="(item, index) in numbers"
:key="index"
class="number"
:class="{
active: currentNumber === index,
exiting: previousNumber === index
}"
>
{{ item }}
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
import { defineEmits, defineProps } from 'vue'
const props = defineProps({
duration: {
type: Number,
default: 100 //
},
isScrolling: {
type: Boolean,
default: true //
},
stopNumber: {
type: Number,
default: 0
}
})
const emit = defineEmits(['rollCompleted'])
const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const currentNumber = ref(0)
const previousNumber = ref(null)
let addTime = ref(0)
let animationFrameId
let currentDuration = ref(props.duration) //
let lastUpdateTime = 0
const changeNumber = () => {
previousNumber.value = currentNumber.value
currentNumber.value = (currentNumber.value + 1) % numbers.length
}
const startScrolling = () => {
addTime.value = 0
currentDuration.value = props.duration
const scroll = (timestamp) => {
if (timestamp - lastUpdateTime >= currentDuration.value) {
changeNumber() //
lastUpdateTime = timestamp
currentDuration.value += addTime.value // 使
}
animationFrameId = requestAnimationFrame(scroll)
}
animationFrameId = requestAnimationFrame(scroll)
}
const stopScrolling = () => {
cancelAnimationFrame(animationFrameId)
emit('rollCompleted') // rollCompleted
}
watch(
() => props.isScrolling,
(newVal) => {
if (newVal) {
startScrolling()
} else {
addTime.value = 160
const waitForOne = () => {
if (currentNumber.value === props.stopNumber) {
stopScrolling()
} else {
requestAnimationFrame(waitForOne)
}
}
waitForOne()
}
}
)
onMounted(() => {
if (props.isScrolling) {
startScrolling()
}
})
onBeforeUnmount(() => {
stopScrolling()
})
</script>
<style scoped>
.scrolling-number {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100px;
height: 120px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
background-color: #f8f8f8;
border: 3px solid #bcbaba;
}
.number {
font-size: 3rem;
position: absolute;
opacity: 0;
transform: translateY(-100%);
transition:
opacity 0.3s ease,
transform 0.3s ease;
color: #333;
}
.number.active {
opacity: 1;
transform: translateY(0);
}
.number.exiting {
opacity: 0;
transform: translateY(100%);
}
</style>

@ -0,0 +1,13 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
import '@/mock/upload.js'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia().use(persist))
app.use(router)
app.mount('#app')

@ -0,0 +1,6 @@
import Mock from 'mockjs'
Mock.mock('http://localhost:8080/upload', 'post', {
status: 200,
message: '文件上传成功!'
})

@ -0,0 +1,21 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('@/views/upload/UploadFilePage.vue')
},
{
path: '/roll-call',
component: () => import('@/views/roll/RollCallPage.vue')
},
{
path: '/rating',
component: () => import('@/views/rating/RatingPage.vue')
}
]
})
export default router

@ -0,0 +1,16 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
// 用户模块
export const useUserStore = defineStore(
'big-user',
() => {
const token = ref('') // 定义 token
const setToken = (t) => (token.value = t) // 设置 token
return { token, setToken }
},
{
persist: true // 持久化
}
)

@ -0,0 +1,46 @@
import { useUserStore } from '@/stores/user'
import axios from 'axios'
import router from '@/router'
import { ElMessage } from 'element-plus'
const baseURL = 'http://big-event-vue-api-t.itheima.net'
const instance = axios.create({
baseURL,
timeout: 100000
})
instance.interceptors.request.use(
(config) => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.Authorization = userStore.token
}
return config
},
(err) => Promise.reject(err)
)
instance.interceptors.response.use(
(res) => {
if (res.data.code === 0) {
return res
}
ElMessage({ message: res.data.message || '服务异常', type: 'error' })
return Promise.reject(res.data)
},
(err) => {
ElMessage({
message: err.response.data.message || '服务异常',
type: 'error'
})
console.log(err)
if (err.response?.status === 401) {
router.push('/login')
}
return Promise.reject(err)
}
)
export default instance
export { baseURL }

@ -0,0 +1,67 @@
<template>
<div class="container">
<h1 class="heading">
就决定是你了<br />
田所浩二
</h1>
<h2 class="sub-heading">你成功的复述了所提的问题吗</h2>
<div class="buttons">
<el-button class="button"></el-button>
<el-button class="button"></el-button>
</div>
</div>
</template>
<script></script>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 40vh;
text-align: center;
}
.heading {
font-size: 2em;
margin-bottom: 20px;
}
.sub-heading {
font-size: 1.5em;
margin-bottom: 30px;
}
.buttons {
display: flex;
gap: 20px;
}
.button {
cursor: pointer;
background-color: #007bff;
width: 150px;
height: 60px;
border-radius: 22px;
border: none;
color: white;
font-size: 18px;
transition:
background-color 0.3s,
box-shadow 0.3s,
transform 0.1s;
}
.button:hover {
background-color: #1e90ff;
box-shadow: inset -4px -4px 10px rgba(0, 0, 0, 0.2);
}
.button:active {
background-color: #0056b3;
box-shadow: inset 2px 2px 8px rgba(0, 0, 0, 0.3);
transform: translateY(2px);
}
</style>

@ -0,0 +1,111 @@
<template>
<div class="center-container">
<div class="rolling-numbers">
<RollingNumber
v-for="(stopNumber, index) in stopNumbers"
:key="index"
:isScrolling="isScrolling"
:stopNumber="stopNumber"
:duration="durations[index]"
@rollCompleted="handleRollCompleted(index)"
/>
</div>
<button class="button" @click="toggleScrolling">
{{ isScrolling ? '停止点名' : '开始点名' }}
</button>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import RollingNumber from '@/components/scrollingNumber.vue'
const router = useRouter()
const isScrolling = ref(true)
const stopNumbers = ref([1, 0, 1, 1, 4, 5, 1, 4, 1]) // stopNumber
// duration 80-120
const durations = ref(
stopNumbers.value.map(() => Math.floor(Math.random() * (120 - 80 + 1)) + 80)
)
const completedRolls = ref(new Array(stopNumbers.value.length).fill(false))
const confirmed = ref(false)
const toggleScrolling = () => {
isScrolling.value = !isScrolling.value // isScrolling
if (isScrolling.value) {
completedRolls.value = new Array(stopNumbers.value.length).fill(false) // completedRolls
confirmed.value = false //
}
}
const handleRollCompleted = (index) => {
completedRolls.value[index] = true
if (
completedRolls.value.every((completed) => completed) &&
!confirmed.value
) {
setTimeout(() => {
if (
!confirmed.value &&
confirm(`抽到了: ${stopNumbers.value.join('')},确定后跳转`)
) {
confirmed.value = true
router.push('/rating')
}
}, 800)
}
}
onUnmounted(() => {
completedRolls.value = new Array(stopNumbers.value.length).fill(false) //
confirmed.value = false //
})
</script>
<style scoped>
.center-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
gap: 20px; /* 设置按钮与RollingNumber部分之间的间距 */
}
.rolling-numbers {
display: flex;
gap: 10px;
justify-content: center;
align-items: center;
}
.button {
cursor: pointer;
background-color: #007bff;
width: 184px;
height: 85px;
border-radius: 22px;
border: none;
color: white;
font-size: 18px;
transition:
background-color 0.3s,
box-shadow 0.3s,
transform 0.1s;
}
.button:hover {
background-color: #1e90ff;
box-shadow: inset -4px -4px 10px rgba(0, 0, 0, 0.2);
}
.button:active {
background-color: #0056b3;
box-shadow: inset 2px 2px 8px rgba(0, 0, 0, 0.3);
transform: translateY(2px);
}
</style>

@ -0,0 +1,107 @@
<template>
<div id="base" class="center-container">
<!-- 标题部分 -->
<h1 id="u0" class="title-text">点击上传文件开始点名</h1>
<!-- 上传文件按钮 -->
<input
type="file"
id="fileInput"
accept=".xlsx, .xls"
style="display: none"
@change="handleFileUpload"
/>
<el-button id="u1" class="ax_default button" @click="selectFile"
><span class="button-text">上传文件</span>
</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import axios from 'axios'
const router = useRouter()
//
const selectFile = () => {
document.getElementById('fileInput').click()
}
//
const handleFileUpload = async (event) => {
const file = event.target.files[0]
if (!file) return
const formData = new FormData()
formData.append('file', file)
try {
const response = await axios.post(
'http://localhost:8080/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
)
console.log('上传成功:', response.data)
router.push('/roll-call')
} catch (error) {
console.error('上传失败:', error)
}
}
</script>
<style scoped>
.center-container {
height: 75vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.button {
cursor: pointer;
background-color: #007bff;
width: 184px;
height: 85px;
border-radius: 22px;
}
.button:hover {
background-color: #1e90ff;
box-shadow: inset -4px -4px 10px rgba(0, 0, 0, 0.2);
}
.button:active {
background-color: #0056b3;
box-shadow: inset 2px 2px 8px rgba(0, 0, 0, 0.3);
transform: translateY(2px);
}
.title-text {
font-size: 48px;
color: #4a4a4a;
font-weight: bold;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.button-text {
font-size: 18px;
color: #ffffff;
}
</style>

@ -0,0 +1,25 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Loading…
Cancel
Save