first commit
This commit is contained in:
commit
bc94c75b54
31 files changed
+3483
No files matched your search
@@ -0,0 +1,11 @@
|
||||
FROM node:24-alpine as builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,66 @@
|
||||
#0 building with "desktop-linux" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 329B done
|
||||
#1 WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 1)
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/nginx:alpine
|
||||
#2 DONE 0.4s
|
||||
|
||||
#3 [internal] load metadata for docker.io/library/node:24-alpine
|
||||
#3 DONE 0.4s
|
||||
|
||||
#4 [internal] load .dockerignore
|
||||
#4 transferring context: 2B done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [internal] load build context
|
||||
#5 transferring context: 1.03kB done
|
||||
#5 DONE 0.0s
|
||||
|
||||
#6 [stage-1 1/3] FROM docker.io/library/nginx:alpine@sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752
|
||||
#6 resolve docker.io/library/nginx:alpine@sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752 0.1s done
|
||||
#6 DONE 0.1s
|
||||
|
||||
#7 [builder 1/6] FROM docker.io/library/node:24-alpine@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43
|
||||
#7 resolve docker.io/library/node:24-alpine@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43
|
||||
#7 resolve docker.io/library/node:24-alpine@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43 0.1s done
|
||||
#7 DONE 0.1s
|
||||
|
||||
#6 [stage-1 1/3] FROM docker.io/library/nginx:alpine@sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752
|
||||
#6 CACHED
|
||||
|
||||
#8 [builder 2/6] WORKDIR /app
|
||||
#8 CACHED
|
||||
|
||||
#9 [builder 3/6] COPY package*.json ./
|
||||
#9 DONE 0.1s
|
||||
|
||||
#10 [builder 4/6] RUN npm install --registry=https://registry.npmmirror.com
|
||||
#10 0.488 npm error code EJSONPARSE
|
||||
#10 0.489 npm error JSON.parse Invalid package.json: JSONParseError: Unexpected end of JSON input while parsing empty string
|
||||
#10 0.489 npm error JSON.parse Failed to parse JSON data.
|
||||
#10 0.489 npm error JSON.parse Note: package.json must be actual JSON, not just JavaScript.
|
||||
#10 0.490 npm error A complete log of this run can be found in: /root/.npm/_logs/2026-08-05T11_12_45_107Z-debug-0.log
|
||||
#10 ERROR: process "/bin/sh -c npm install --registry=https://registry.npmmirror.com" did not complete successfully: exit code: 1
|
||||
------
|
||||
> [builder 4/6] RUN npm install --registry=https://registry.npmmirror.com:
|
||||
0.488 npm error code EJSONPARSE
|
||||
0.489 npm error JSON.parse Invalid package.json: JSONParseError: Unexpected end of JSON input while parsing empty string
|
||||
0.489 npm error JSON.parse Failed to parse JSON data.
|
||||
0.489 npm error JSON.parse Note: package.json must be actual JSON, not just JavaScript.
|
||||
0.490 npm error A complete log of this run can be found in: /root/.npm/_logs/2026-08-05T11_12_45_107Z-debug-0.log
|
||||
------
|
||||
|
||||
1 warning found (use docker --debug to expand):
|
||||
- FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 1)
|
||||
Dockerfile:4
|
||||
--------------------
|
||||
2 | WORKDIR /app
|
||||
3 | COPY package*.json ./
|
||||
4 | >>> RUN npm install --registry=https://registry.npmmirror.com
|
||||
5 | COPY . .
|
||||
6 | RUN npm run build
|
||||
--------------------
|
||||
ERROR: failed to build: failed to solve: process "/bin/sh -c npm install --registry=https://registry.npmmirror.com" did not complete successfully: exit code: 1
|
||||
@@ -0,0 +1,12 @@
|
||||
server {
|
||||
listen 80;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8000;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,26 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE || '/api',
|
||||
})
|
||||
|
||||
apiClient.interceptors.request.use(config => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default apiClient
|
||||
Whitespace-only changes.
@@ -0,0 +1,31 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import Home from '../views/Home.vue'
|
||||
import Records from '../views/Records.vue'
|
||||
import Stats from '../views/Stats.vue'
|
||||
import Mine from '../views/Mine.vue'
|
||||
|
||||
const routes = [
|
||||
{ path: '/login', component: Login, meta: { public: true } },
|
||||
{ path: '/', component: Home, meta: { title: '首页' } },
|
||||
{ path: '/records', component: Records, meta: { title: '记录' } },
|
||||
{ path: '/stats', component: Stats, meta: { title: '统计' } },
|
||||
{ path: '/mine', component: Mine, meta: { title: '我的' } },
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const isPublic = to.matched.some(r => r.meta.public)
|
||||
const token = localStorage.getItem('token')
|
||||
if (!isPublic && !token) {
|
||||
next('/login')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
export default router
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<el-card class="stats-card">
|
||||
<div class="weekly-count">本周违规:{{ weeklyCount }}</div>
|
||||
<el-tag :type="sanctionColor">{{ sanctionLabel }}</el-tag>
|
||||
</el-card>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8" v-for="action in quickActions" :key="action.text">
|
||||
<div class="quick-action" @click="action.handler">
|
||||
<span>{{ action.icon }}</span>
|
||||
<span>{{ action.text }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const weeklyCount = ref(2)
|
||||
|
||||
const sanctionLabel = computed(() => {
|
||||
if (weeklyCount.value >= 3) return '三级制裁'
|
||||
if (weeklyCount.value >= 2) return '二级制裁'
|
||||
return '一级制裁'
|
||||
})
|
||||
|
||||
const quickActions = [
|
||||
{ icon: '📝', text: '记录违规', handler: () => router.push('/records?action=violation') },
|
||||
{ icon: '📋', text: '登记债务', handler: () => router.push('/records?action=debt') },
|
||||
{ icon: '🎁', text: '兑换奖励', handler: () => router.push('/records?action=reward') },
|
||||
]
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<h2>自律公约系统</h2>
|
||||
<el-form :model="form" @submit.prevent="login">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form.username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.password" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="login" :loading="loading">登录</el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const form = ref({ username: 'parent', password: 'family2026' })
|
||||
|
||||
async function login() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
params.append('username', form.value.username)
|
||||
params.append('password', form.value.password)
|
||||
const res = await axios.post('/api/auth/token', params)
|
||||
const token = res.data.access_token
|
||||
localStorage.setItem('token', token)
|
||||
// 存储用户角色
|
||||
const payload = JSON.parse(atob(token.split('.')[1]))
|
||||
localStorage.setItem('role', payload.role)
|
||||
router.push('/')
|
||||
} catch (e) {
|
||||
ElMessage.error('登录失败,请检查用户名和密码')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Reference in new issue
Block a user