刘东阳 3 months ago
parent b5a768f774
commit 8f9f05b4ed

21
.gitignore vendored

@ -0,0 +1,21 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,5 @@
{
"cSpell.words": [
"courseselected"
]
}

@ -0,0 +1,588 @@
<Modal visible={change} style={{background: 'white'}} width="800px" title="编辑讨论内容"
onCancel={e=>{setChange(false)}}
onOk={e=>{setChange(false)}}
footer={<div>
<Button onClick={e=>{
const temp=[...taolonglist]
setyulan(taolonglist)
setChange(false)}}
>保存</Button>
<Button onClick={e=>setChange(false)}>关闭</Button>
</div>}
>
<Card
><table ><tbody>
{ taolonglist.map((value,index)=>(
<React.Fragment >
<tr><td style={{width:"550px"}}>
<React.Fragment >
{value.lx==="wb"?<h4 style={{width:"550px"}}> {value.wb}</h4>:null}
{value.lx==="pic"? <img
src={!value.show?value.pic:value.xiugaipic}
width="550px" height="auto"/> : null}
{value.lx==="video"?<video src={!value.show? value.sp:value.xiugaisp} width="550px" height="auto" controls={true}></video>:null}
{value.lx==="download"?<a href={value.url} style={{width:"550px"}} >{value.title}</a>:null}
</React.Fragment></td>
<td ><Button onClick={e=>{
let temp=[...taolonglist]
temp[index].show=!temp[index].show;
settaolonglist(temp);
}}>修改</Button><Popconfirm
title="确认要删除此项吗"
onConfirm={event => {
let temp=[...taolonglist]
temp.splice(index,1)
settaolonglist(temp);
}}
onCancel={event => alert("no")}
okText="确认"
cancelText="不"
>
<Button onClick={e=>{
}} href="#">删除</Button>
</Popconfirm>
</td>
</tr>
{value.show ?
<tr>
<td colSpan={3}>
<React.Fragment>
<h3>编辑</h3>
<Tabs activeKey={value.leixing} onChange={e => {
let temp = [...taolonglist]
temp[index].leixing = e;
settaolonglist(temp);
}} tabBarStyle={{}} style={{width:"720px"}}>
<Tabs.TabPane tab="视频" key="video">
<table>
<tbody>
<tr>
<td>
<video src={value.xiugaisp} width="600px"
height="auto"
style={{display: "none"}}/>
</td>
</tr>
<tr>
<td>
视频路径
<Input value={value.xiugailujing}
onChange={event => {
let temp = [...taolonglist];
temp[index].xiugailujing = event.target.value;
settaolonglist(temp);
}} width="550px"
style={{width:"550px"}}
></Input>
</td>
<td><Button onClick={e => {
let temp = [...taolonglist];
temp[index].xiugaisp = temp[index].xiugailujing;
settaolonglist(temp);
}}>打开</Button></td>
</tr>
<tr>
<td>
<Upload
name='file'
action={HOUDUAN+'api/uploadtm'}
headers={{
th: value.uuid,
xx: fujianuuid,
lx: "kaoshi"
}}
onChange={
(info) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
//setTms(uuid.v4())
message.success(`${info.file.name} file uploaded successfully`);
_.delay(() => {
console.log('Done after 1 second');
const temp = [...taolonglist];
temp[index].xiugaisp = ZHIYUE+ value.uuid + "/" + fujianuuid + path.extname(info.file.name);
settaolonglist(temp);
setFujianuuid(uuid.v4())
//settaolonglist(uuid.v4())
}, 200);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
}
}
showUploadList={false}><Button>
<Icon type="upload"/>上传附件
</Button>
</Upload>
</td>
</tr>
<tr>
<td>
<Button onClick={e => {
let temp = [...taolonglist];
temp[index].sp = temp[index].xiugaisp;
temp[index].lx = temp[index].leixing
settaolonglist(temp);
}}>确认</Button>
</td>
</tr>
</tbody>
</table>
</Tabs.TabPane>
<Tabs.TabPane tab="图片" key="pic">
<table>
<tbody>
<tr>
<td>
<img src={value.xiugaipic} width="600px"
height="auto"
style={{display: "none"}}/>
</td>
</tr>
<tr>
<td>
<span>图片路径
<Input value={value.xiugailujing} onChange={event => {
let temp = [...taolonglist];
temp[index].xiugailujing = event.target.value;
settaolonglist(temp);
}}
width="550px"
style={{width:"550px"}}
></Input></span>
</td>
<td><Button onClick={e => {
let temp = [...taolonglist];
temp[index].xiugaipic = temp[index].xiugailujing;
settaolonglist(temp);
//settaolonglist(uuid.v4())
}}>打开</Button></td>
</tr>
<tr>
<td>
<Upload
name='file'
action={HOUDUAN+'api/uploadtm'}
headers={{
th: value.uuid,
xx: fujianuuid,
lx: "kaoshi"
}}
onChange={
(info) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
//setTms(uuid.v4())
message.success(`${info.file.name} file uploaded successfully`);
_.delay(() => {
console.log('Done after 1 second');
const temp = [...taolonglist];
temp[index].xiugaipic = ZHIYUE+value.uuid + "/" + fujianuuid + path.extname(info.file.name);
settaolonglist(temp);
setFujianuuid(uuid.v4());
}, 200);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
}
}
showUploadList={false}><Button>
<Icon type="upload"/>上传附件
</Button>
</Upload>
</td>
</tr>
<tr>
<td><Button onClick={e => {
let temp = [...taolonglist];
temp[index].pic = temp[index].xiugaipic;
temp[index].lx = temp[index].leixing
settaolonglist(temp);
}}>保存</Button></td>
</tr>
</tbody>
</table>
</Tabs.TabPane>
<Tabs.TabPane tab="附件" key="download">
<table>
<tbody>
<tr>
<td>文件标题</td>
<td style={{width: "400px"}}>
<Input value={value.xiugai} onChange={e => {
let temp = [...taolonglist];
temp[index].xiugai = e.target.value;
settaolonglist(temp);
}}
width="550px"
style={{width:"550px"}}
></Input>
</td>
</tr>
<tr>
<td>
文件路径
</td>
<td>
<Input value={value.xiugaiwj}
onChange={event => {
let temp = [...taolonglist];
temp[index].xiugaiwj = event.target.value;
temp[index].lx = temp[index].leixing
settaolonglist(temp);
}}
width="550px"
style={{width:"550px"}}
></Input></td>
</tr>
<tr>
<td>
<Upload
name='file'
action={HOUDUAN+'api/uploadtm'}
headers={{
th: value.uuid,
xx: fujianuuid,
lx: "kaoshi"
}}
onChange={
(info) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
//setTms(uuid.v4())
message.success(`${info.file.name} file uploaded successfully`);
_.delay(() => {
console.log('Done after 1 second');
const temp = [...taolonglist];
temp[index].xiugaiwj = ZHIYUE+value.uuid + "/" + fujianuuid + path.extname(info.file.name);
temp[index].xiugai = info.file.name;
settaolonglist(temp);
setFujianuuid(uuid.v4())
}, 200);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
}
}
showUploadList={false}><Button>
<Icon type="upload"/>上传附件
</Button>
</Upload>
<Button onClick={e => {
let temp = [...taolonglist];
temp[index].url = temp[index].xiugaiwj;
temp[index].title = temp[index].xiugai;
temp[index].lx = temp[index].leixing
settaolonglist(temp);
}}>保存</Button>
</td>
</tr>
</tbody>
</table>
</Tabs.TabPane>
<Tabs.TabPane tab="文字" key="wb">
<Input.TextArea value={value.xiugaiwb}
onChange={event => {
let temp = [...taolonglist];
temp[index].xiugaiwb = event.target.value;
temp[index].lx = temp[index].leixing
settaolonglist(temp);
}} width="550px"
style={{width:"550px"}}
></Input.TextArea>
<Button onClick={e => {
let temp = [...taolonglist]
temp[index].wb = temp[index].xiugaiwb;
settaolonglist(temp);
}}>修改</Button>
</Tabs.TabPane>
</Tabs>
</React.Fragment>
</td>
</tr>
:null}
</React.Fragment>
))}
</tbody></table></Card>
<Card>
<Tabs activeKey={add.leixing} onChange={e=>{
let temp={...add};
temp.leixing=e;
setAdd(temp)
}}>
<Tabs.TabPane tab="视频" key="video">
<table><tbody>
<tr ><td >
<video src={add.xiugaisp} width="600px" height="auto" controls={true}/>
</td>
</tr>
<tr><td>
<Upload
name='file'
action={HOUDUAN+'api/uploadtm'}
headers={{
th: add.uuid ,
xx: fujianuuid,
lx: "kaoshi"
}}
onChange={
(info) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
//setTms(uuid.v4())
message.success(`${info.file.name} file uploaded successfully`);
_.delay(() => {
console.log('Done after 1 second');
const temp={...add};
temp.xiugaisp=ZHIYUE+add.uuid+"/"+fujianuuid + path.extname(info.file.name);
setAdd(temp);
setFujianuuid(uuid.v4())
}, 200);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
}
}
showUploadList={false}><Button>
<Icon type="upload"/>上传附件
</Button>
</Upload>
</td></tr>
<tr>
<td>文件路径
<Input value={add.url} width="400px" onChange={event => {
let temp={...add};
temp.url=event.target.value;
setAdd(temp);
}}></Input>
<Button onClick={e=>{
let temp={...add};
temp.xiugaisp=temp.url;
setAdd(temp);
}
}>应用</Button>
</td>
</tr>
</tbody></table>
</Tabs.TabPane>
<Tabs.TabPane tab="图片" key="pic">
<tr>
<td>
<img src={add.xiugaipic} width="600px" height="auto"/>
</td>
</tr>
<tr>
<td>
<Upload
name='file'
action={HOUDUAN+'api/uploadtm'}
headers={{
th: add.uuid,
xx: fujianuuid,
lx: "kaoshi"
}}
onChange={
(info) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
//setTms(uuid.v4())
message.success(`${info.file.name} file uploaded successfully`);
_.delay(() => {
console.log('Done after 1 second');
const temp = {...add};
temp.xiugaipic =ZHIYUE+ add.uuid + "/" + fujianuuid + path.extname(info.file.name);
setAdd(temp);
setFujianuuid(uuid.v4())
}, 200);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
}
}
showUploadList={false}><Button>
<Icon type="upload"/>上传附件
</Button>
</Upload>
</td>
</tr>
<tr>
<td>文件路径
<Input value={add.url} width="400px" onChange={event => {
let temp = {...add};
temp.url = event.target.value;
setAdd(temp);
}}></Input>
<Button onClick={e => {
let temp = {...add};
temp.xiugaipic = temp.url;
setAdd(temp);
}
}>应用</Button>
</td>
</tr>
</Tabs.TabPane>
<Tabs.TabPane tab="附件" key="download">
<tr>
<td><a href={add.xiugailujing}>{add.xiugai}</a></td>
</tr>
<tr>
<td>文件标题</td>
<td >
<Input value={add.xiugai} onChange={event => {
let temp={...add}
temp.xiugai=event.target.value;
setAdd(temp);
}}></Input>
</td>
</tr>
<tr>
<td>文件路径</td>
<td style={{width:"350px"}}>
<Input value={add.xiugailujing} onChange={event => {
let temp={...add};
temp.xiugailujing=event.target.value;
setAdd(temp);
}}></Input></td>
</tr>
<tr>
<td>
<Upload
name='file'
action={HOUDUAN+'api/uploadtm'}
headers={{
th: add.uuid ,
xx: fujianuuid,
lx: "kaoshi"
}}
onChange={
(info) => {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
//setTms(uuid.v4())
message.success(`${info.file.name} file uploaded successfully`);
_.delay(() => {
console.log('Done after 1 second');
const temp = {...add};
temp.xiugai=info.file.name;
temp.xiugailujing = ZHIYUE+ add.uuid + "/" + fujianuuid + path.extname(info.file.name)
setAdd(temp)
setFujianuuid(uuid.v4());
}, 200);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
}
}
showUploadList={false}><Button>
<Icon type="upload"/>上传附件
</Button>
</Upload>
</td></tr>
</Tabs.TabPane>
<Tabs.TabPane tab="文字" key="wb">
<Input.TextArea value={add.xiugaiwb} onChange={event => {
let temp={...add};
temp.xiugaiwb=event.target.value;
setAdd(temp);
}} style={{height:"250px"}}></Input.TextArea>
</Tabs.TabPane>
</Tabs>
<Button onClick={
// {value.lx==="wb"?<h4>{value.wb}</h4>:null}
// {value.lx==="pic"? <img
// src={value.pic}
// width="600px" height="auto"/> : null}
// {value.lx==="video"?<video src={value.sp} width="600px" height="auto" controls={true}></video>:null}
// {value.lx==="download"?<a href={value.url}>{value.title}</a>:null}
e=>{
const xiang={
lx:add.leixing,
leixing:add.leixing,
wb:add.xiugaiwb,
xiugaiwb:add.xiugaiwb,
pic:add.xiugaipic,
xiugaiwj:add.xiugailujing,//修改附件路径
xiugaipic:add.xiugaipic,
sp:add.xiugaisp,
xiugaisp:add.xiugaisp,
xiugailujing:add.xiugailujing,
url:add.xiugailujing,
xiugai:add.xiugai,
title:add.xiugai,
show:false
}
let temp=[...taolonglist]
temp.push(xiang);
settaolonglist(temp);
const addtemp={leixing:add.leixing,uuid:uuid.v4()}
setAdd(addtemp);
}
}>
添加
</Button>
</Card>
</Modal>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

12015
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,73 @@
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-xhs": "uni -p mp-xhs",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-xhs": "uni build -p mp-xhs",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4030620241128001",
"@dcloudio/uni-app-harmony": "3.0.0-4030620241128001",
"@dcloudio/uni-app-plus": "3.0.0-4030620241128001",
"@dcloudio/uni-components": "3.0.0-4030620241128001",
"@dcloudio/uni-h5": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-alipay": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-baidu": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-jd": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-lark": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-qq": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-weixin": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-xhs": "3.0.0-4030620241128001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4030620241128001",
"@dcloudio/uni-ui": "^1.5.7",
"@hyoga/uni-socket.io": "^3.0.4",
"dayjs": "^1.11.13",
"vue": "^3.4.21",
"vue-i18n": "^9.1.9"
},
"devDependencies": {
"@dcloudio/types": "^3.4.8",
"@dcloudio/uni-automator": "3.0.0-4030620241128001",
"@dcloudio/uni-cli-shared": "3.0.0-4030620241128001",
"@dcloudio/uni-stacktracey": "3.0.0-4030620241128001",
"@dcloudio/vite-plugin-uni": "3.0.0-4030620241128001",
"@vue/runtime-core": "^3.4.21",
"@vue/tsconfig": "^0.1.3",
"sass": "^1.69.0",
"sass-loader": "^10.1.1",
"typescript": "^4.9.4",
"vite": "5.2.8",
"vue-tsc": "^1.0.24"
}
}

@ -0,0 +1,7 @@
{
"pages": [
// ... existing code ...
// ... existing code ...
]
}

File diff suppressed because it is too large Load Diff

10
shims-uni.d.ts vendored

@ -0,0 +1,10 @@
/// <reference types='@dcloudio/types' />
import 'vue'
declare module '@vue/runtime-core' {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {
}
}

@ -0,0 +1,14 @@
<script setup lang="ts">
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
onLaunch(() => {
console.log("App Launch");
});
onShow(() => {
console.log("App Show");
});
onHide(() => {
console.log("App Hide");
});
</script>
<style></style>

@ -0,0 +1,87 @@
<template>
<view
class="tech-button"
:class="[type, size]"
@click="$emit('click')"
>
<slot></slot>
</view>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'primary', // primary | secondary
validator: (value) => ['primary', 'secondary'].includes(value)
},
size: {
type: String,
default: 'normal', // normal | small
validator: (value) => ['normal', 'small'].includes(value)
}
})
</script>
<style lang="scss" scoped>
.tech-button {
display: inline-flex;
justify-content: center;
align-items: center;
gap: 10rpx;
border-radius: 50rpx;
font-weight: 500;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #00f2fe 0%, #4facfe 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
&.primary {
background: linear-gradient(135deg, #00f2fe 0%, #4facfe 100%);
color: #fff;
box-shadow: 0 4rpx 20rpx rgba(74, 144, 226, 0.3);
&:active {
transform: translateY(2rpx);
box-shadow: 0 2rpx 10rpx rgba(74, 144, 226, 0.3);
}
}
&.secondary {
background: rgba(16, 24, 40, 0.8);
color: #4facfe;
border: 1rpx solid rgba(74, 144, 226, 0.5);
&:active {
background: rgba(31, 47, 71, 0.8);
}
}
&.normal {
height: 80rpx;
padding: 0 40rpx;
font-size: 30rpx;
}
&.small {
height: 60rpx;
padding: 0 30rpx;
font-size: 26rpx;
}
&:active::before {
opacity: 0.2;
}
}
</style>

@ -0,0 +1,89 @@
<template>
<view
class="tech-card"
:class="{ active }"
@click="$emit('click')"
>
<view class="card-bg"></view>
<view class="card-content">
<uni-icons
:type="icon"
size="28"
:color="active ? '#4facfe' : 'rgba(255,255,255,0.7)'"
/>
<text class="card-title">{{ title }}</text>
</view>
<view class="card-decoration"></view>
</view>
</template>
<script setup>
defineProps({
title: String,
icon: String,
active: Boolean
})
</script>
<style lang="scss" scoped>
.tech-card {
height: 140rpx;
border-radius: 16rpx;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
.card-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(16, 24, 40, 0.8);
border: 1px solid rgba(74, 144, 226, 0.2);
backdrop-filter: blur(5px);
z-index: 1;
}
.card-content {
position: relative;
z-index: 2;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20rpx;
.card-title {
font-size: 28rpx;
margin-top: 15rpx;
color: rgba(255, 255, 255, 0.9);
}
}
.card-decoration {
position: absolute;
bottom: 0;
right: 0;
width: 40rpx;
height: 40rpx;
background: radial-gradient(circle, rgba(74, 144, 226, 0.3) 0%, transparent 70%);
z-index: 1;
}
&.active {
transform: translateY(-5rpx);
box-shadow: 0 10rpx 20rpx rgba(0, 242, 254, 0.2);
.card-bg {
background: rgba(31, 47, 71, 0.8);
border-color: rgba(74, 144, 226, 0.5);
}
.card-decoration {
background: radial-gradient(circle, rgba(74, 144, 226, 0.6) 0%, transparent 70%);
}
}
}
</style>

@ -0,0 +1,59 @@
<template>
<view class="tech-progress">
<view
class="progress-bar"
:style="{ width: `${percent}%` }"
>
<view class="progress-highlight"></view>
</view>
<text class="progress-text">{{ percent }}%</text>
</view>
</template>
<script setup>
defineProps({
percent: {
type: Number,
default: 0,
validator: (value) => value >= 0 && value <= 100
}
})
</script>
<style lang="scss" scoped>
.tech-progress {
height: 16rpx;
background: rgba(16, 24, 40, 0.8);
border-radius: 8rpx;
position: relative;
overflow: hidden;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.progress-bar {
height: 100%;
border-radius: 8rpx;
background: linear-gradient(90deg, #00f2fe 0%, #4facfe 100%);
position: relative;
transition: width 0.6s ease;
.progress-highlight {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, rgba(255,255,255,0.3) 0%, transparent 100%);
border-radius: 8rpx;
}
}
.progress-text {
position: absolute;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
}
}
</style>

8
src/env.d.ts vendored

@ -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,8 @@
import { createSSRApp } from "vue";
import App from "./App.vue";
export function createApp() {
const app = createSSRApp(App);
return {
app,
};
}

@ -0,0 +1,72 @@
{
"name" : "",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics": {
"enable": false
},
"vueVersion" : "3"
}

@ -0,0 +1,158 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/learning/learning",
"style": {
"navigationBarTitleText": "课程学习"
}
},
{
"path": "pages/management/management",
"style": {
"navigationBarTitleText": "课程管理"
}
},
{
"path": "pages/schedule/schedule",
"style": {
"navigationBarTitleText": "智能课程表",
"navigationBarBackgroundColor": "#0a192f",
"navigationBarTextStyle": "white",
"backgroundColor": "#0a192f"
}
},
{
"path": "pages/user/user",
"style": {
"navigationBarTitleText": "个人中心"
}
},
{
"path": "pages/leave/leave",
"style": {
"navigationBarTitleText": "leave"
}
},
{
"path": "pages/plan/plan",
"style": {
"navigationBarTitleText": "plan"
}
},
{
"path": "pages/course/course",
"style": {
"navigationBarTitleText": "course"
}
},
{
"path": "pages/score/score",
"style": {
"navigationBarTitleText": "score"
}
},
{
"path": "pages/settings/settings",
"style": {
"navigationBarTitleText": "settings"
}
},
{
"path": "pages/management/leave",
"style": {
"navigationBarTitleText": "leave"
}
},
{
"path": "pages/management/program",
"style": {
"navigationBarTitleText": "program"
}
},
{
"path": "pages/management/transcript",
"style": {
"navigationBarTitleText": "transcript"
}
},
{
"path": "pages/management/selection",
"style": {
"navigationBarTitleText": "selection"
}
},
{
"path": "pages/list/list",
"style": {
"navigationBarTitleText": "list"
}
},
{
"path": "pages/discussion/discussion",
"style": {
"navigationBarTitleText": "discussion"
}
},
{
"path": "pages/checkin/checkin",
"style": {
"navigationBarTitleText": "checkin"
}
},
{
"path": "pages/quiz/quiz",
"style": {
"navigationBarTitleText": "quiz"
}
},
{
"path": "pages/exam/exam",
"style": {
"navigationBarTitleText": "exam"
}
},
{
"path": "pages/shangke/shangke",
"style": {
"navigationBarTitleText": "shnagke"
}
}
],
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#007AFF",
"list": [
{
"pagePath": "pages/learning/learning",
"text": "课程学习",
"iconPath": "static/edu-tabs/learning.png",
"selectedIconPath": "static/edu-tabs/learning-active.png"
},
{
"pagePath": "pages/schedule/schedule",
"text": "课程表",
"iconPath": "/static/edu-tabs/calendar.png",
"selectedIconPath": "/static/edu-tabs/calendar-active.png"
},
{
"pagePath": "pages/management/management",
"text": "课程管理",
"iconPath": "/static/edu-tabs/dashboard.png",
"selectedIconPath": "/static/edu-tabs/dashboard-active.png"
},
{
"pagePath": "pages/user/user",
"text": "我的",
"iconPath": "/static/edu-tabs/user.png",
"selectedIconPath": "/static/edu-tabs/user-active.png"
}
]
}
}

@ -0,0 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<view>签到</view>
</template>
<style scoped>
</style>

@ -0,0 +1,284 @@
<template>
<view class="tech-container">
<!-- 科技风顶部导航 -->
<view class="tech-header">
<view class="header-content">
<text class="header-title">我的课程中心</text>
<view class="header-line"></view>
</view>
<view class="tech-pattern"></view>
</view>
<!-- 课程内容区域 -->
<view class="course-content">
<!-- 课程分类导航 -->
<scroll-view class="category-nav" scroll-x>
<view
v-for="(category, index) in categories"
:key="index"
class="category-item"
:class="{ active: activeCategory === index }"
@click="switchCategory(index)"
>
<text>{{ category }}</text>
</view>
</scroll-view>
<!-- 课程列表 -->
<view class="course-list">
<view
v-for="(course, index) in filteredCourses"
:key="index"
class="course-card"
>
<image class="course-cover" :src="course.cover" mode="aspectFill" />
<view class="course-info">
<view class="title-wrapper">
<text class="course-title">{{ course.title }}</text>
<text class="course-teacher">{{ course.teacher }}</text>
</view>
<view class="progress-bar">
<view
class="progress-fill"
:style="{ width: `${course.progress}%` }"
></view>
</view>
<text class="progress-text">已学 {{ course.progress }}%</text>
</view>
<view class="tech-corner"></view>
</view>
</view>
</view>
<!-- 科技风底部装饰 -->
<view class="tech-footer">
<view class="footer-line"></view>
<view class="footer-dots">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
//
const categories = ref(['全部', '进行中', '已完成', '未开始', '收藏'])
const activeCategory = ref(0)
//
const courses = ref([
{
id: 1,
title: 'Vue3高级开发实战',
teacher: '张老师',
cover: '/static/student-course/bg.png',
progress: 65,
category: '进行中'
},
{
id: 2,
title: 'React全栈开发',
teacher: '李老师',
cover: '/static/student-course/bg.png',
progress: 30,
category: '进行中'
},
{
id: 3,
title: 'Node.js后端开发',
teacher: '王老师',
cover: '/static/course3.jpg',
progress: 100,
category: '已完成'
}
])
//
const switchCategory = (index) => {
activeCategory.value = index
}
//
const filteredCourses = computed(() => {
if (activeCategory.value === 0) return courses.value
const categoryMap = ['全部', '进行中', '已完成', '未开始', '收藏']
return courses.value.filter(course =>
course.category === categoryMap[activeCategory.value]
)
})
</script>
<style lang="scss">
.tech-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #0f1621;
color: #e0e0e0;
}
.tech-header {
position: relative;
padding: 40rpx 30rpx 30rpx;
background: linear-gradient(135deg, #1a2a3a 0%, #0f1621 100%);
overflow: hidden;
z-index: 1;
.header-content {
position: relative;
z-index: 2;
}
.header-title {
font-size: 42rpx;
font-weight: bold;
background: linear-gradient(to right, #4facfe, #00f2fe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header-line {
height: 4rpx;
width: 80rpx;
background: linear-gradient(to right, #4facfe, #00f2fe);
margin-top: 15rpx;
border-radius: 2rpx;
}
.tech-pattern {
position: absolute;
top: 0;
right: 0;
width: 300rpx;
height: 100%;
background: linear-gradient(135deg, rgba(79, 172, 254, 0.1) 0%, transparent 70%);
opacity: 0.3;
}
}
.course-content {
flex: 1;
padding: 20rpx;
}
.category-nav {
white-space: nowrap;
margin-bottom: 30rpx;
.category-item {
display: inline-block;
padding: 15rpx 30rpx;
margin-right: 20rpx;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
border-radius: 40rpx;
transition: all 0.3s;
&.active {
background: linear-gradient(to right, #4facfe, #00f2fe);
color: #fff;
box-shadow: 0 5rpx 15rpx rgba(79, 172, 254, 0.3);
}
}
}
.course-list {
display: flex;
flex-direction: column;
gap: 30rpx;
}
.course-card {
position: relative;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.2);
.course-cover {
width: 100%;
height: 300rpx;
}
.course-info {
padding: 25rpx;
}
.title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.course-title {
font-size: 32rpx;
font-weight: bold;
flex: 1;
}
.course-teacher {
font-size: 26rpx;
color: #4facfe;
margin-left: 20rpx;
}
.progress-bar {
height: 8rpx;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 4rpx;
margin-bottom: 10rpx;
overflow: hidden;
.progress-fill {
height: 100%;
background: linear-gradient(to right, #4facfe, #00f2fe);
border-radius: 4rpx;
}
}
.progress-text {
font-size: 24rpx;
color: rgba(79, 172, 254, 0.8);
}
.tech-corner {
position: absolute;
top: 0;
right: 0;
width: 0;
height: 0;
border-style: solid;
border-width: 0 60rpx 60rpx 0;
border-color: transparent rgba(79, 172, 254, 0.2) transparent transparent;
}
}
.tech-footer {
padding: 30rpx 0;
position: relative;
.footer-line {
height: 1rpx;
background: linear-gradient(to right, transparent, rgba(79, 172, 254, 0.3), transparent);
}
.footer-dots {
display: flex;
justify-content: center;
gap: 20rpx;
margin-top: 20rpx;
.dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background-color: rgba(79, 172, 254, 0.5);
}
}
}
</style>

@ -0,0 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<view>讨论</view>
</template>
<style scoped>
</style>

@ -0,0 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<view>课程考试</view>
</template>
<style scoped>
</style>

@ -0,0 +1,253 @@
<template>
<view class="tech-welcome-container">
<!-- 科技风背景元素 -->
<view class="tech-background">
<view class="tech-circle circle-1"></view>
<view class="tech-circle circle-2"></view>
<view class="tech-line line-1"></view>
<view class="tech-line line-2"></view>
<view class="tech-dots">
<view class="dot" v-for="i in 20" :key="i" :style="getDotStyle(i)"></view>
</view>
</view>
<!-- 主要内容区 -->
<view class="welcome-content">
<!-- 动态科技感标题 -->
<view class="tech-title">
<text class="title-text">只为遇见</text>
<text class="title-text highlight">更好的你</text>
</view>
<!-- 加载进度条 -->
<view class="tech-progress">
<view class="progress-bar">
<view class="progress-fill" :style="{width: progress + '%'}"></view>
</view>
<text class="progress-text">{{progress}}%</text>
</view>
</view>
<!-- 底部科技风装饰 -->
<view class="tech-footer">
<view class="footer-line"></view>
<text class="footer-text">智慧教育平台</text>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const progress = ref(0)
onMounted(() => {
//
const timer = setInterval(() => {
progress.value += Math.floor(Math.random() * 10) + 5
if (progress.value >= 100) {
progress.value = 100
clearInterval(timer)
//
uni.switchTab({
url: '/pages/learning/learning'
})
}
}, 300)
})
//
const getDotStyle = (index) => {
const size = Math.random() * 6 + 2
return {
width: `${size}px`,
height: `${size}px`,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
opacity: Math.random() * 0.5 + 0.3,
animationDelay: `${index * 0.1}s`
}
}
</script>
<style lang="scss">
.tech-welcome-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #0a0e21 0%, #121a3a 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
color: #fff;
}
/* 科技风背景元素 */
.tech-background {
position: absolute;
width: 100%;
height: 100%;
.tech-circle {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(0, 240, 255, 0.05), transparent 70%);
&.circle-1 {
width: 600rpx;
height: 600rpx;
top: -300rpx;
right: -300rpx;
}
&.circle-2 {
width: 500rpx;
height: 500rpx;
bottom: -250rpx;
left: -250rpx;
}
}
.tech-line {
position: absolute;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(0, 240, 255, 0.2), transparent);
&.line-1 {
top: 30%;
left: -100px;
right: -100px;
transform: rotate(-5deg);
}
&.line-2 {
top: 60%;
left: -100px;
right: -100px;
transform: rotate(5deg);
}
}
.tech-dots {
position: absolute;
width: 100%;
height: 100%;
.dot {
position: absolute;
background-color: #00f0ff;
border-radius: 50%;
animation: float 3s infinite ease-in-out;
}
}
}
.welcome-content {
position: relative;
width: 80%;
display: flex;
flex-direction: column;
align-items: center;
z-index: 1;
}
/* 科技感标题 */
.tech-title {
text-align: center;
margin-bottom: 80rpx;
.title-text {
display: block;
font-size: 48rpx;
font-weight: bold;
letter-spacing: 2rpx;
&.highlight {
background: linear-gradient(to right, #00f0ff, #0088ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-top: 20rpx;
font-size: 56rpx;
}
}
}
/* 科技风进度条 */
.tech-progress {
width: 100%;
margin-top: 60rpx;
.progress-bar {
height: 8rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 4rpx;
overflow: hidden;
position: relative;
&::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(0, 240, 255, 0.3), transparent);
animation: shine 2s infinite;
}
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #00f0ff, #0088ff);
border-radius: 4rpx;
transition: width 0.3s ease;
}
.progress-text {
display: block;
text-align: center;
margin-top: 20rpx;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
}
/* 底部装饰 */
.tech-footer {
position: absolute;
bottom: 60rpx;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
.footer-line {
width: 100px;
height: 2px;
background: linear-gradient(90deg, transparent, #00f0ff, transparent);
margin-bottom: 20rpx;
}
.footer-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 2rpx;
}
}
/* 动画效果 */
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes shine {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
</style>

@ -0,0 +1,285 @@
<template>
<view class="tech-container">
<!-- 科技风背景装饰 -->
<view class="tech-bg">
<view class="tech-line line-1"></view>
<view class="tech-line line-2"></view>
<view class="tech-dot dot-1"></view>
<view class="tech-dot dot-2"></view>
</view>
<!-- 功能导航入口 -->
<view class="nav-grid">
<view
v-for="(item, index) in navItems"
:key="index"
class="nav-item"
@click="navigateTo(item.page)"
>
<view class="nav-icon" :style="{ background: item.bgColor }">
<!-- 使用uni-icons或图片作为图标 -->
<image v-if="item.icon.includes('.png')" :src="item.icon" mode="aspectFit" style="width: 32rpx; height: 32rpx;"/>
<uni-icons v-else :type="item.icon" size="40" color="#fff"></uni-icons>
</view>
<text class="nav-text">{{ item.name }}</text>
<text class="nav-action">立即进入</text>
</view>
</view>
<!-- 我的课程 -->
<view class="my-course">
<view class="section-header">
<text class="section-title">我的课程 (1)</text>
<text class="section-more">查看全部 </text>
</view>
<view class="course-card">
<image class="course-cover" src="/static/student-course/bg.png" mode="aspectFill"></image>
<view class="course-info">
<text class="course-name">军事理论</text>
<text class="course-subtitle">军事理论-综合版</text>
<view class="progress-bar">
<view class="progress-active" style="width: 100%"></view>
</view>
<text class="progress-text">已完成 100%</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
//
const navItems = ref([
{
name: '讨论',
icon: '/static/student-course/discussion.png', //
page: '/pages/discussion/discussion'
},
{
name: '签到',
icon: '/static/student-course/checkin.png',
page: '/pages/checkin/checkin'
},
{
name: '答题',
icon: '/static/student-course/quiz.png',
page: '/pages/quiz/quiz'
},
{
name: '考试',
icon: '/static/student-course/exam.png',
page: '/pages/exam/exam'
},
{
name: '扫码',
icon: '/static/student-course/scan.png',
page: 'scan'
}
])
//
const navigateTo = (url) => {
if(url === 'scan') {
uni.scanCode({
success: function (res) {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
console.log('/pages/shangke/shangke?uuid='+JSON.parse(res.result).uuid+'&kch='+JSON.parse(res.result).kch+'&kctime='+JSON.parse(res.result).kctime)
uni.navigateTo({
url: '/pages/shangke/shangke?uuid='+JSON.parse(res.result).uuid+'&kch='+JSON.parse(res.result).kch+'&kctime='+JSON.parse(res.result).kctime
});
}
});
} else {
uni.navigateTo({
url: url
});
}
}
</script>
<style lang="scss">
.tech-container {
position: relative;
padding: 30rpx;
background-color: #0a0e21;
color: #fff;
min-height: 100vh;
overflow: hidden;
}
/* 科技风背景 */
.tech-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
overflow: hidden;
.tech-line {
position: absolute;
height: 1rpx;
background: linear-gradient(90deg, transparent, rgba(0, 240, 255, 0.2), transparent);
&.line-1 {
top: 30%;
left: -100rpx;
right: -100rpx;
transform: rotate(-5deg);
}
&.line-2 {
top: 60%;
left: -100rpx;
right: -100rpx;
transform: rotate(5deg);
}
}
.tech-dot {
position: absolute;
border-radius: 50%;
background-color: rgba(0, 240, 255, 0.05);
&.dot-1 {
width: 400rpx;
height: 400rpx;
top: -200rpx;
right: -200rpx;
}
&.dot-2 {
width: 300rpx;
height: 300rpx;
bottom: -150rpx;
left: -150rpx;
}
}
}
/* 导航网格 */
.nav-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 40rpx;
margin-bottom: 30rpx;
.nav-item {
position: relative;
background-color: rgba(20, 30, 50, 0.6);
border-radius: 16rpx;
padding: 30rpx;
border: 1rpx solid rgba(0, 240, 255, 0.1);
overflow: hidden;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
}
}
.nav-icon {
width: 75rpx;
height: 75rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.3);
}
.nav-text {
font-size: 28rpx;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.nav-action {
font-size: 24rpx;
color: #00f0ff;
}
}
/* 我的课程 */
.my-course {
background-color: rgba(20, 30, 50, 0.6);
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
border: 1rpx solid rgba(0, 240, 255, 0.1);
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
.section-title {
font-size: 32rpx;
font-weight: bold;
}
.section-more {
font-size: 24rpx;
color: #00f0ff;
}
}
.course-card {
display: flex;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 12rpx;
overflow: hidden;
.course-cover {
width: 200rpx;
height: 150rpx;
}
.course-info {
flex: 1;
padding: 20rpx;
.course-name {
font-size: 28rpx;
font-weight: bold;
display: block;
margin-bottom: 5rpx;
}
.course-subtitle {
font-size: 24rpx;
color: #aaa;
display: block;
margin-bottom: 15rpx;
}
.progress-bar {
height: 8rpx;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 4rpx;
margin-bottom: 10rpx;
overflow: hidden;
.progress-active {
height: 100%;
background: linear-gradient(to right, #00f0ff, #0088ff);
border-radius: 4rpx;
}
}
.progress-text {
font-size: 22rpx;
color: #00f0ff;
}
}
}
}
</style>

@ -0,0 +1,26 @@
<template>
<view class="container">
<text class="title">请假</text>
<!-- 页面内容 -->
</view>
</template>
<script setup>
import {
ref
} from 'vue'
//
const pageTitle = ref('请假')
</script>
<style>
.container {
padding: 20px;
}
.title {
font-size: 18px;
color: #333;
}
</style>

@ -0,0 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<view>课程列表</view>
</template>
<style scoped>
</style>

@ -0,0 +1,396 @@
<template>
<view class="leave-page">
<!-- 表单区域 -->
<view class="form-section">
<view class="section-title">
<text class="title-text">请假申请</text>
<view class="title-decoration"></view>
</view>
<view class="form-container">
<view class="form-item">
<text class="item-label">请假类型</text>
<picker
mode="selector"
:range="leaveTypes"
range-key="name"
@change="handleTypeChange"
>
<view class="picker">
{{ selectedType.name || '请选择请假类型' }}
<uni-icons type="arrowdown" size="16" color="#4facfe"></uni-icons>
</view>
</picker>
</view>
<view class="form-item">
<text class="item-label">请假时间</text>
<view class="time-picker">
<picker
mode="date"
fields="datetime"
@change="handleStartTimeChange"
>
<view class="time-input">
{{ startTime || '开始时间' }}
</view>
</picker>
<text class="time-separator"></text>
<picker
mode="date"
fields="datetime"
@change="handleEndTimeChange"
>
<view class="time-input">
{{ endTime || '结束时间' }}
</view>
</picker>
</view>
</view>
<view class="form-item">
<text class="item-label">请假原因</text>
<textarea
v-model="reason"
placeholder="请输入请假原因"
class="textarea"
placeholder-class="placeholder"
></textarea>
</view>
<view class="form-item">
<text class="item-label">上传证明</text>
<uni-file-picker
limit="3"
title="最多选择3个文件"
file-mediatype="image"
@select="handleFileSelect"
></uni-file-picker>
</view>
</view>
<TechButton class="submit-btn" @click="handleSubmit"></TechButton>
</view>
<!-- 历史记录 -->
<view class="history-section">
<view class="section-title">
<text class="title-text">请假记录</text>
<view class="title-decoration"></view>
</view>
<view class="history-list">
<view
v-for="(item, index) in historyList"
:key="index"
class="history-item"
>
<view class="item-header">
<text class="type">{{ item.type }}</text>
<view class="status-badge" :class="item.status">
{{ item.statusText }}
</view>
</view>
<view class="item-content">
<view class="time-range">
<uni-icons type="calendar" size="16" color="#4facfe"></uni-icons>
<text>{{ item.startTime }} {{ item.endTime }}</text>
</view>
<text class="reason">{{ item.reason }}</text>
</view>
<view class="item-footer" v-if="item.status === 'approved'">
<text class="approver">审批人: {{ item.approver }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import TechButton from '@/components/managemet/TechButton.vue'
const leaveTypes = ref([
{ id: 1, name: '病假', icon: 'medkit' },
{ id: 2, name: '事假', icon: 'home' },
{ id: 3, name: '公假', icon: 'briefcase' },
{ id: 4, name: '其他', icon: 'help' }
])
const selectedType = ref({})
const startTime = ref('')
const endTime = ref('')
const reason = ref('')
const files = ref([])
const historyList = ref([
{
id: 1,
type: '病假',
startTime: '2023-05-10 08:00',
endTime: '2023-05-12 18:00',
reason: '感冒发烧体温38.5℃医生建议休息3天',
status: 'approved',
statusText: '已批准',
approver: '李老师'
},
{
id: 2,
type: '事假',
startTime: '2023-04-15 13:00',
endTime: '2023-04-15 17:00',
reason: '家中有急事需要处理',
status: 'rejected',
statusText: '已拒绝'
},
{
id: 3,
type: '公假',
startTime: '2023-03-20 09:00',
endTime: '2023-03-21 17:00',
reason: '参加学校组织的学术竞赛',
status: 'approved',
statusText: '已批准',
approver: '王老师'
}
])
const handleTypeChange = (e) => {
selectedType.value = leaveTypes.value[e.detail.value]
}
const handleStartTimeChange = (e) => {
startTime.value = e.detail.value
}
const handleEndTimeChange = (e) => {
endTime.value = e.detail.value
}
const handleFileSelect = (e) => {
files.value = e.tempFilePaths
}
const handleSubmit = () => {
if (!selectedType.value.id) {
uni.showToast({ title: '请选择请假类型', icon: 'none' })
return
}
if (!startTime.value || !endTime.value) {
uni.showToast({ title: '请选择请假时间', icon: 'none' })
return
}
if (!reason.value) {
uni.showToast({ title: '请输入请假原因', icon: 'none' })
return
}
uni.showLoading({ title: '提交中...' })
// API
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '提交成功', icon: 'success' })
//
historyList.value.unshift({
id: Date.now(),
type: selectedType.value.name,
startTime: startTime.value,
endTime: endTime.value,
reason: reason.value,
status: 'pending',
statusText: '审核中'
})
//
selectedType.value = {}
startTime.value = ''
endTime.value = ''
reason.value = ''
files.value = []
}, 1500)
}
</script>
<style lang="scss" scoped>
.leave-page {
.section-title {
margin-bottom: 30rpx;
position: relative;
.title-text {
font-size: 32rpx;
color: #4facfe;
font-weight: 600;
position: relative;
display: inline-block;
padding-right: 20rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.title-decoration {
position: absolute;
bottom: -10rpx;
left: 0;
width: 60rpx;
height: 4rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
border-radius: 2rpx;
}
}
.form-container {
margin-bottom: 40rpx;
.form-item {
margin-bottom: 30rpx;
.item-label {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 15rpx;
}
.picker {
height: 80rpx;
line-height: 80rpx;
padding: 0 20rpx;
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 28rpx;
}
.time-picker {
display: flex;
align-items: center;
gap: 20rpx;
.time-input {
flex: 1;
height: 80rpx;
line-height: 80rpx;
padding: 0 20rpx;
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
color: #fff;
font-size: 28rpx;
}
.time-separator {
color: rgba(255, 255, 255, 0.5);
font-size: 28rpx;
}
}
.textarea {
width: 100%;
height: 180rpx;
padding: 20rpx;
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
color: #fff;
font-size: 28rpx;
box-sizing: border-box;
}
.placeholder {
color: rgba(255, 255, 255, 0.3);
}
}
}
.submit-btn {
margin-top: 20rpx;
}
.history-list {
.history-item {
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
padding: 25rpx;
margin-bottom: 20rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.type {
font-size: 28rpx;
color: #4facfe;
font-weight: 500;
}
.status-badge {
font-size: 24rpx;
padding: 5rpx 15rpx;
border-radius: 20rpx;
&.approved {
background: rgba(0, 200, 83, 0.15);
color: #00c853;
border: 1rpx solid rgba(0, 200, 83, 0.3);
}
&.rejected {
background: rgba(255, 82, 82, 0.15);
color: #ff5252;
border: 1rpx solid rgba(255, 82, 82, 0.3);
}
&.pending {
background: rgba(255, 171, 0, 0.15);
color: #ffab00;
border: 1rpx solid rgba(255, 171, 0, 0.3);
}
}
}
.item-content {
.time-range {
display: flex;
align-items: center;
gap: 10rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 15rpx;
}
.reason {
display: block;
font-size: 28rpx;
color: #fff;
line-height: 1.5;
}
}
.item-footer {
margin-top: 15rpx;
padding-top: 15rpx;
border-top: 1rpx solid rgba(74, 144, 226, 0.1);
.approver {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.5);
}
}
}
}
}
</style>

@ -0,0 +1,134 @@
<template>
<view class="tech-container">
<!-- 科技风格头部 -->
<view class="tech-header">
<view class="header-content">
<text class="header-title">课程管理中心</text>
<view class="header-subtitle">Course Management System</view>
</view>
<view class="header-decoration"></view>
</view>
<!-- 内容区域 -->
<view class="content-wrapper">
<!-- 导航卡片 -->
<view class="nav-grid">
<TechCard
v-for="(item, index) in navItems"
:key="index"
:title="item.title"
:icon="item.icon"
:active="activeIndex === index"
@click="switchTab(index)"
/>
</view>
<!-- 修改后的子页面容器 -->
<view class="page-container">
<LeavePage v-if="activeIndex === 0" />
<ProgramPage v-if="activeIndex === 1" />
<TranscriptPage v-if="activeIndex === 2" />
<SelectionPage v-if="activeIndex === 3" />
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import TechCard from '@/components/managemet/TechCard.vue'
import LeavePage from './leave.vue'
import ProgramPage from './program.vue'
import TranscriptPage from './transcript.vue'
import SelectionPage from './selection.vue'
//
const navItems = ref([
{ title: '请假申请', icon: 'calendar-remove' },
{ title: '培养方案', icon: 'book' },
{ title: '成绩单', icon: 'document-text' },
{ title: '选课中心', icon: 'checkmark-circle' }
])
const activeIndex = ref(0)
//
const switchTab = (index) => {
activeIndex.value = index
}
</script>
<style lang="scss" scoped>
.tech-container {
min-height: 100vh;
background: #0a0e17;
color: #fff;
font-family: 'PingFang SC', 'Helvetica Neue', sans-serif;
position: relative;
overflow: hidden;
}
.tech-header {
position: relative;
padding: 40rpx 30rpx 60rpx;
background: linear-gradient(135deg, #1a2a3a 0%, #0a0e17 100%);
z-index: 1;
overflow: hidden;
.header-content {
position: relative;
z-index: 2;
.header-title {
font-size: 42rpx;
font-weight: 600;
letter-spacing: 1rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: inline-block;
}
.header-subtitle {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
margin-top: 10rpx;
letter-spacing: 1rpx;
}
}
.header-decoration {
position: absolute;
top: 0;
right: 0;
width: 200rpx;
height: 200rpx;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
z-index: 1;
}
}
.content-wrapper {
padding: 0 20rpx 40rpx;
position: relative;
z-index: 2;
margin-top: -30rpx;
}
.nav-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
margin-bottom: 30rpx;
}
.page-container {
background: rgba(16, 24, 40, 0.8);
border-radius: 16rpx;
padding: 30rpx;
min-height: 600rpx;
backdrop-filter: blur(10px);
border: 1px solid rgba(74, 144, 226, 0.2);
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.3);
}
</style>

@ -0,0 +1,289 @@
<template>
<view class="program-page">
<!-- 基本信息 -->
<view class="info-section">
<view class="section-title">
<text class="title-text">培养方案</text>
<view class="title-decoration"></view>
</view>
<view class="info-grid">
<view class="info-item">
<uni-icons type="medal" size="20" color="#4facfe"></uni-icons>
<text class="info-label">专业名称</text>
<text class="info-value">计算机科学与技术</text>
</view>
<view class="info-item">
<uni-icons type="flag" size="20" color="#4facfe"></uni-icons>
<text class="info-label">培养层次</text>
<text class="info-value">本科</text>
</view>
<view class="info-item">
<uni-icons type="calendar" size="20" color="#4facfe"></uni-icons>
<text class="info-label">学制</text>
<text class="info-value">4</text>
</view>
<view class="info-item">
<uni-icons type="star" size="20" color="#4facfe"></uni-icons>
<text class="info-label">毕业学分</text>
<text class="info-value">160学分</text>
</view>
</view>
</view>
<!-- 课程体系 -->
<view class="course-section">
<view class="section-title">
<text class="title-text">课程体系</text>
<view class="title-decoration"></view>
</view>
<uni-collapse>
<uni-collapse-item
v-for="(item, index) in courseSystem"
:key="index"
:title="item.name"
:show-animation="true"
:border="false"
>
<view class="course-list">
<view
v-for="(course, cIndex) in item.courses"
:key="cIndex"
class="course-item"
>
<text class="course-name">{{ course.name }}</text>
<view class="course-meta">
<text class="course-credit">{{ course.credit }}学分</text>
<text class="course-type">{{ course.type }}</text>
</view>
</view>
</view>
</uni-collapse-item>
</uni-collapse>
</view>
<!-- 学分进度 -->
<view class="progress-section">
<view class="section-title">
<text class="title-text">学分进度</text>
<view class="title-decoration"></view>
</view>
<view class="progress-list">
<view
v-for="(item, index) in creditProgress"
:key="index"
class="progress-item"
>
<view class="progress-header">
<text class="category">{{ item.category }}</text>
<text class="value">{{ item.completed }}/{{ item.total }}学分</text>
</view>
<TechProgress :percent="item.percent" />
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import TechProgress from '@/components/managemet/TechProgress.vue'
const courseSystem = ref([
{
name: '通识教育课程',
courses: [
{ name: '大学英语', credit: 8, type: '必修' },
{ name: '高等数学', credit: 10, type: '必修' },
{ name: '大学物理', credit: 6, type: '必修' },
{ name: '思想政治理论', credit: 12, type: '必修' },
{ name: '人文艺术选修', credit: 4, type: '选修' }
]
},
{
name: '专业基础课程',
courses: [
{ name: '程序设计基础', credit: 4, type: '必修' },
{ name: '数据结构', credit: 4, type: '必修' },
{ name: '计算机组成原理', credit: 4, type: '必修' },
{ name: '操作系统', credit: 4, type: '必修' },
{ name: '离散数学', credit: 3, type: '必修' }
]
},
{
name: '专业核心课程',
courses: [
{ name: '算法设计与分析', credit: 4, type: '必修' },
{ name: '计算机网络', credit: 4, type: '必修' },
{ name: '数据库系统', credit: 4, type: '必修' },
{ name: '软件工程', credit: 4, type: '必修' },
{ name: '人工智能基础', credit: 3, type: '选修' }
]
},
{
name: '实践环节',
courses: [
{ name: '课程设计', credit: 6, type: '必修' },
{ name: '毕业实习', credit: 4, type: '必修' },
{ name: '毕业论文', credit: 8, type: '必修' },
{ name: '创新创业实践', credit: 2, type: '选修' }
]
}
])
const creditProgress = ref([
{ category: '通识教育', completed: 32, total: 40, percent: 80 },
{ category: '专业基础', completed: 15, total: 20, percent: 75 },
{ category: '专业核心', completed: 8, total: 16, percent: 50 },
{ category: '实践环节', completed: 4, total: 20, percent: 20 }
])
</script>
<style lang="scss" scoped>
.program-page {
.section-title {
margin-bottom: 30rpx;
position: relative;
.title-text {
font-size: 32rpx;
color: #4facfe;
font-weight: 600;
position: relative;
display: inline-block;
padding-right: 20rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.title-decoration {
position: absolute;
bottom: -10rpx;
left: 0;
width: 60rpx;
height: 4rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
border-radius: 2rpx;
}
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
margin-bottom: 40rpx;
.info-item {
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
padding: 25rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.info-label {
display: block;
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
margin: 10rpx 0 5rpx;
}
.info-value {
display: block;
font-size: 28rpx;
color: #fff;
font-weight: 500;
}
}
}
.course-section {
margin-bottom: 40rpx;
:deep(.uni-collapse) {
background-color: transparent;
border: none;
}
:deep(.uni-collapse-item__title) {
background: rgba(16, 24, 40, 0.8);
color: #fff;
border-radius: 12rpx;
margin-bottom: 10rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
padding: 20rpx 25rpx;
}
:deep(.uni-collapse-item__wrap) {
background: rgba(16, 24, 40, 0.6);
border: none;
border-radius: 0 0 12rpx 12rpx;
}
.course-list {
padding: 0 25rpx 20rpx;
.course-item {
padding: 20rpx 0;
border-bottom: 1rpx solid rgba(74, 144, 226, 0.1);
&:last-child {
border-bottom: none;
}
.course-name {
display: block;
font-size: 28rpx;
color: #fff;
margin-bottom: 10rpx;
}
.course-meta {
display: flex;
justify-content: space-between;
.course-credit {
font-size: 24rpx;
color: #4facfe;
}
.course-type {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
.progress-list {
.progress-item {
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
padding: 25rpx;
margin-bottom: 20rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.progress-header {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
.category {
font-size: 28rpx;
color: #fff;
}
.value {
font-size: 26rpx;
color: #4facfe;
}
}
}
}
}
</style>

@ -0,0 +1,614 @@
<template>
<view class="selection-page">
<!-- 头部信息 -->
<view class="header-section">
<view class="header-content">
<text class="title">选课中心</text>
<text class="subtitle">Course Selection System</text>
</view>
<view class="header-decoration"></view>
</view>
<!-- 选课状态卡片 -->
<view class="status-cards">
<view class="status-card">
<view class="card-content">
<text class="card-label">可选学分</text>
<text class="card-value">{{ availableCredits }}</text>
<text class="card-unit">学分</text>
</view>
<view class="card-decoration"></view>
</view>
<view class="status-card">
<view class="card-content">
<text class="card-label">已选学分</text>
<text class="card-value">{{ selectedCredits }}</text>
<text class="card-unit">学分</text>
</view>
<view class="card-decoration"></view>
</view>
</view>
<!-- 选课进度 -->
<view class="progress-section">
<view class="section-header">
<text class="section-title">选课进度</text>
<view class="section-decoration"></view>
</view>
<TechProgress :percent="selectionProgress" />
</view>
<!-- 课程筛选 -->
<view class="filter-section">
<view class="filter-tabs">
<view
v-for="(tab, index) in tabs"
:key="index"
class="filter-tab"
:class="{ active: activeTab === index }"
@click="changeTab(index)"
>
<text>{{ tab }}</text>
<view class="tab-indicator"></view>
</view>
</view>
<view class="filter-options">
<picker
mode="selector"
:range="semesters"
@change="handleSemesterChange"
>
<view class="filter-option">
<text>{{ currentSemester || '选择学期' }}</text>
<uni-icons type="arrowdown" size="16" color="#4facfe"></uni-icons>
</view>
</picker>
<picker
mode="selector"
:range="categories"
range-key="name"
@change="handleCategoryChange"
>
<view class="filter-option">
<text>{{ currentCategory.name || '课程类别' }}</text>
<uni-icons type="arrowdown" size="16" color="#4facfe"></uni-icons>
</view>
</picker>
</view>
</view>
<!-- 课程列表 -->
<view class="course-list">
<view
v-for="(course, index) in filteredCourses"
:key="index"
class="course-card"
>
<view class="course-header">
<text class="course-name">{{ course.name }}</text>
<text class="course-credit">{{ course.credit }}学分</text>
</view>
<view class="course-meta">
<view class="meta-item">
<uni-icons type="person" size="16" color="rgba(255,255,255,0.6)"></uni-icons>
<text>{{ course.teacher }}</text>
</view>
<view class="meta-item">
<uni-icons type="calendar" size="16" color="rgba(255,255,255,0.6)"></uni-icons>
<text>{{ course.time }}</text>
</view>
<view class="meta-item">
<uni-icons type="location" size="16" color="rgba(255,255,255,0.6)"></uni-icons>
<text>{{ course.location }}</text>
</view>
</view>
<view class="course-footer">
<text class="remaining">剩余名额: {{ course.remaining }}/{{ course.capacity }}</text>
<TechButton
size="small"
:type="course.selected ? 'secondary' : 'primary'"
@click="toggleSelection(course)"
>
{{ course.selected ? '已选' : '选课' }}
</TechButton>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="action-bar">
<view class="selected-info">
<text>已选 {{ selectedCount }} 门课程</text>
<text>{{ selectedCredits }}学分</text>
</view>
<TechButton @click="handleSubmit"></TechButton>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import TechProgress from '@/components/managemet/TechProgress.vue'
import TechButton from '@/components/managemet/TechButton.vue'
//
const availableCredits = ref(25)
const selectedCredits = ref(0)
const selectedCount = ref(0)
//
const tabs = ref(['全部课程', '专业选修', '公共选修', '通识教育'])
const activeTab = ref(0)
const semesters = ref(['2023-2024学年 第一学期', '2022-2023学年 第二学期', '2022-2023学年 第一学期'])
const currentSemester = ref('2023-2024学年 第一学期')
const categories = ref([
{ id: 1, name: '全部类别' },
{ id: 2, name: '人文社科' },
{ id: 3, name: '自然科学' },
{ id: 4, name: '工程技术' },
{ id: 5, name: '艺术体育' }
])
const currentCategory = ref({})
//
const courses = ref([
{
id: 1,
name: '人工智能基础',
credit: 3,
teacher: '张教授',
time: '周一 3-4节',
location: '信息楼201',
remaining: 15,
capacity: 60,
category: '工程技术',
type: '专业选修',
selected: false
},
{
id: 2,
name: '数据可视化',
credit: 2,
teacher: '李副教授',
time: '周三 5-6节',
location: '计算机中心305',
remaining: 8,
capacity: 40,
category: '工程技术',
type: '专业选修',
selected: false
},
{
id: 3,
name: '西方艺术史',
credit: 2,
teacher: '王教授',
time: '周二 7-8节',
location: '人文楼101',
remaining: 5,
capacity: 80,
category: '人文社科',
type: '公共选修',
selected: false
},
{
id: 4,
name: '环境科学导论',
credit: 2,
teacher: '赵教授',
time: '周四 1-2节',
location: '理学院203',
remaining: 12,
capacity: 100,
category: '自然科学',
type: '通识教育',
selected: false
},
{
id: 5,
name: '羽毛球',
credit: 1,
teacher: '陈老师',
time: '周五 5-6节',
location: '体育馆A',
remaining: 3,
capacity: 30,
category: '艺术体育',
type: '公共选修',
selected: false
}
])
//
const selectionProgress = computed(() => {
return Math.round((selectedCredits.value / availableCredits.value) * 100)
})
const filteredCourses = computed(() => {
let result = [...courses.value]
//
if (activeTab.value > 0) {
result = result.filter(course => course.type === tabs.value[activeTab.value])
}
//
if (currentCategory.value.id > 1) {
result = result.filter(course => course.category === currentCategory.value.name)
}
return result
})
//
const changeTab = (index) => {
activeTab.value = index
}
const handleSemesterChange = (e) => {
currentSemester.value = semesters.value[e.detail.value]
}
const handleCategoryChange = (e) => {
currentCategory.value = categories.value[e.detail.value]
}
const toggleSelection = (course) => {
if (course.selected) {
//
course.selected = false
selectedCredits.value -= course.credit
selectedCount.value--
course.remaining++
} else {
//
if (selectedCredits.value + course.credit > availableCredits.value) {
uni.showToast({
title: '已超过可选学分限制',
icon: 'none'
})
return
}
//
if (course.remaining <= 0) {
uni.showToast({
title: '该课程已无剩余名额',
icon: 'none'
})
return
}
//
course.selected = true
selectedCredits.value += course.credit
selectedCount.value++
course.remaining--
uni.showToast({
title: '选课成功',
icon: 'success'
})
}
}
const handleSubmit = () => {
if (selectedCount.value === 0) {
uni.showToast({
title: '请至少选择一门课程',
icon: 'none'
})
return
}
uni.showLoading({ title: '提交中...' })
// API
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '选课提交成功',
icon: 'success'
})
//
courses.value.forEach(course => {
if (course.selected) {
course.capacity--
course.selected = false
}
})
selectedCredits.value = 0
selectedCount.value = 0
}, 1500)
}
</script>
<style lang="scss" scoped>
.selection-page {
padding-bottom: 120rpx;
background: linear-gradient(to bottom, #0a0e17 0%, #1a2a3a 100%);
min-height: 100vh;
}
.header-section {
position: relative;
padding: 40rpx 30rpx 30rpx;
overflow: hidden;
.header-content {
position: relative;
z-index: 2;
.title {
font-size: 42rpx;
font-weight: 600;
background: linear-gradient(to right, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
margin-top: 8rpx;
}
}
.header-decoration {
position: absolute;
top: 0;
right: 0;
width: 200rpx;
height: 200rpx;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
z-index: 1;
}
}
.status-cards {
display: flex;
gap: 20rpx;
padding: 0 30rpx;
margin-bottom: 30rpx;
.status-card {
flex: 1;
background: rgba(16, 24, 40, 0.8);
border-radius: 16rpx;
padding: 25rpx;
position: relative;
overflow: hidden;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.card-content {
position: relative;
z-index: 2;
.card-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
display: block;
margin-bottom: 10rpx;
}
.card-value {
font-size: 40rpx;
font-weight: 600;
color: #fff;
display: block;
background: linear-gradient(to right, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.card-unit {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
}
.card-decoration {
position: absolute;
bottom: -20rpx;
right: -20rpx;
width: 80rpx;
height: 80rpx;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
z-index: 1;
}
}
}
.progress-section {
padding: 0 30rpx;
margin-bottom: 30rpx;
.section-header {
margin-bottom: 20rpx;
position: relative;
.section-title {
font-size: 32rpx;
font-weight: 500;
color: #4facfe;
position: relative;
display: inline-block;
padding-right: 20rpx;
}
.section-decoration {
position: absolute;
bottom: -8rpx;
left: 0;
width: 60rpx;
height: 4rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
border-radius: 2rpx;
}
}
}
.filter-section {
padding: 0 30rpx;
margin-bottom: 30rpx;
.filter-tabs {
display: flex;
margin-bottom: 25rpx;
border-bottom: 1rpx solid rgba(74, 144, 226, 0.2);
.filter-tab {
padding: 15rpx 25rpx;
position: relative;
text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.6);
}
.tab-indicator {
position: absolute;
bottom: -1rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 4rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
border-radius: 2rpx;
transition: width 0.3s ease;
}
&.active {
text {
color: #fff;
font-weight: 500;
}
.tab-indicator {
width: 60rpx;
}
}
}
}
.filter-options {
display: flex;
gap: 20rpx;
.filter-option {
flex: 1;
height: 70rpx;
line-height: 70rpx;
padding: 0 25rpx;
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
color: #fff;
font-size: 26rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
}
}
.course-list {
padding: 0 30rpx;
.course-card {
background: rgba(16, 24, 40, 0.8);
border-radius: 16rpx;
padding: 25rpx;
margin-bottom: 20rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.course-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.course-name {
font-size: 30rpx;
color: #fff;
font-weight: 500;
}
.course-credit {
font-size: 26rpx;
color: #4facfe;
font-weight: 500;
}
}
.course-meta {
margin-bottom: 25rpx;
.meta-item {
display: flex;
align-items: center;
gap: 10rpx;
margin-bottom: 15rpx;
text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
}
}
}
.course-footer {
display: flex;
justify-content: space-between;
align-items: center;
.remaining {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
}
}
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
background: rgba(16, 24, 40, 0.95);
backdrop-filter: blur(10px);
border-top: 1rpx solid rgba(74, 144, 226, 0.3);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
.selected-info {
display: flex;
flex-direction: column;
text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
&:last-child {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
}
}
:deep(.tech-button) {
width: 200rpx;
}
}
</style>

@ -0,0 +1,450 @@
<template>
<view class="transcript-page">
<!-- 头部信息 -->
<view class="header-section">
<view class="header-content">
<text class="title">成绩单</text>
<text class="subtitle">Academic Transcript</text>
</view>
<view class="header-decoration"></view>
</view>
<!-- 学期选择器 -->
<view class="semester-selector">
<picker
mode="selector"
:range="semesters"
range-key="name"
@change="handleSemesterChange"
>
<view class="selector-box">
<text class="current-semester">{{ currentSemester.name }}</text>
<uni-icons type="arrowdown" size="18" color="#4facfe"></uni-icons>
</view>
</picker>
</view>
<!-- GPA统计卡片 -->
<view class="stats-grid">
<view class="stat-card">
<view class="stat-content">
<text class="stat-label">学期GPA</text>
<text class="stat-value">{{ currentSemester.gpa }}</text>
<view class="stat-trend" :class="gpaTrendClass">
<uni-icons :type="gpaTrendIcon" size="16" color="currentColor"></uni-icons>
<text>{{ gpaTrendText }}</text>
</view>
</view>
<view class="stat-decoration"></view>
</view>
<view class="stat-card">
<view class="stat-content">
<text class="stat-label">累计GPA</text>
<text class="stat-value">{{ cumulativeGPA }}</text>
<text class="stat-meta">专业排名 {{ rank }}</text>
</view>
<view class="stat-decoration"></view>
</view>
</view>
<!-- 成绩列表 -->
<view class="grades-section">
<view class="section-header">
<text class="section-title">课程成绩</text>
<view class="section-decoration"></view>
</view>
<view class="grades-list">
<view
v-for="(course, index) in currentSemester.courses"
:key="index"
class="grade-item"
>
<view class="course-info">
<text class="course-name">{{ course.name }}</text>
<text class="course-credit">{{ course.credit }}学分</text>
</view>
<view class="grade-score" :class="getScoreClass(course.score)">
<text>{{ course.score }}</text>
<text class="grade-level">{{ getGradeLevel(course.score) }}</text>
</view>
</view>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="action-buttons">
<TechButton type="primary" @click="handleDownload">
<uni-icons type="download" size="18" color="#fff"></uni-icons>
<text>下载成绩单</text>
</TechButton>
<TechButton type="secondary" @click="handleShare">
<uni-icons type="share" size="18" color="#4facfe"></uni-icons>
<text>分享成绩</text>
</TechButton>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import TechButton from '@/components/managemet/TechButton.vue'
const semesters = ref([
{
id: 1,
name: '2023-2024学年 第一学期',
gpa: '3.82',
trend: 'up',
courses: [
{ name: '人工智能原理', credit: 4, score: 94 },
{ name: '机器学习', credit: 4, score: 89 },
{ name: '大数据技术', credit: 3, score: 91 },
{ name: '计算机视觉', credit: 3, score: 87 },
{ name: '自然语言处理', credit: 3, score: 92 }
]
},
{
id: 2,
name: '2022-2023学年 第二学期',
gpa: '3.75',
trend: 'up',
courses: [
{ name: '操作系统', credit: 4, score: 88 },
{ name: '计算机网络', credit: 4, score: 85 },
{ name: '数据库系统', credit: 4, score: 90 },
{ name: '软件工程', credit: 3, score: 87 },
{ name: '编译原理', credit: 4, score: 83 }
]
},
{
id: 3,
name: '2022-2023学年 第一学期',
gpa: '3.68',
trend: 'down',
courses: [
{ name: '数据结构', credit: 4, score: 85 },
{ name: '算法分析', credit: 4, score: 82 },
{ name: '计算机组成', credit: 4, score: 84 },
{ name: '离散数学', credit: 3, score: 79 },
{ name: '概率统计', credit: 3, score: 88 }
]
}
])
const currentSemesterIndex = ref(0)
const currentSemester = computed(() => semesters.value[currentSemesterIndex.value])
const cumulativeGPA = computed(() => '3.75')
const rank = computed(() => '8/120')
// GPA
const gpaTrendIcon = computed(() => {
return currentSemester.value.trend === 'up' ? 'arrow-up' : 'arrow-down'
})
const gpaTrendClass = computed(() => {
return currentSemester.value.trend === 'up' ? 'trend-up' : 'trend-down'
})
const gpaTrendText = computed(() => {
return currentSemester.value.trend === 'up' ? '较上学期提升' : '较上学期下降'
})
//
const getGradeLevel = (score) => {
if (score >= 90) return 'A'
if (score >= 80) return 'B'
if (score >= 70) return 'C'
if (score >= 60) return 'D'
return 'F'
}
const getScoreClass = (score) => {
if (score >= 90) return 'excellent'
if (score >= 80) return 'good'
if (score >= 70) return 'average'
if (score >= 60) return 'pass'
return 'fail'
}
//
const handleSemesterChange = (e) => {
currentSemesterIndex.value = e.detail.value
}
const handleDownload = () => {
uni.showLoading({ title: '生成成绩单中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '成绩单已保存到相册',
icon: 'success'
})
}, 1500)
}
const handleShare = () => {
uni.showActionSheet({
itemList: ['微信好友', 'QQ好友', '保存图片'],
success: (res) => {
uni.showToast({
title: `已分享到${['微信好友', 'QQ好友', '保存图片'][res.tapIndex]}`,
icon: 'success'
})
}
})
}
</script>
<style lang="scss" scoped>
.transcript-page {
padding-bottom: 40rpx;
background: linear-gradient(to bottom, #0a0e17 0%, #1a2a3a 100%);
min-height: 100vh;
}
.header-section {
position: relative;
padding: 40rpx 30rpx 30rpx;
overflow: hidden;
.header-content {
position: relative;
z-index: 2;
.title {
font-size: 42rpx;
font-weight: 600;
background: linear-gradient(to right, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
margin-top: 8rpx;
}
}
.header-decoration {
position: absolute;
top: 0;
right: 0;
width: 200rpx;
height: 200rpx;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
z-index: 1;
}
}
.semester-selector {
padding: 0 30rpx;
margin-bottom: 30rpx;
.selector-box {
height: 80rpx;
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
border: 1rpx solid rgba(74, 144, 226, 0.3);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 25rpx;
.current-semester {
font-size: 28rpx;
color: #fff;
}
}
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
padding: 0 30rpx;
margin-bottom: 40rpx;
.stat-card {
background: rgba(16, 24, 40, 0.8);
border-radius: 16rpx;
padding: 25rpx;
position: relative;
overflow: hidden;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.stat-content {
position: relative;
z-index: 2;
.stat-label {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
display: block;
margin-bottom: 10rpx;
}
.stat-value {
font-size: 40rpx;
font-weight: 600;
color: #fff;
display: block;
margin-bottom: 15rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-trend {
font-size: 24rpx;
display: flex;
align-items: center;
gap: 8rpx;
&.trend-up {
color: #00c853;
}
&.trend-down {
color: #ff5252;
}
}
.stat-meta {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
}
.stat-decoration {
position: absolute;
bottom: -20rpx;
right: -20rpx;
width: 80rpx;
height: 80rpx;
background: radial-gradient(circle, rgba(74, 144, 226, 0.2) 0%, transparent 70%);
z-index: 1;
}
}
}
.grades-section {
padding: 0 30rpx;
.section-header {
margin-bottom: 25rpx;
position: relative;
.section-title {
font-size: 32rpx;
font-weight: 500;
color: #4facfe;
position: relative;
display: inline-block;
padding-right: 20rpx;
}
.section-decoration {
position: absolute;
bottom: -8rpx;
left: 0;
width: 60rpx;
height: 4rpx;
background: linear-gradient(to right, #00f2fe, #4facfe);
border-radius: 2rpx;
}
}
.grades-list {
.grade-item {
background: rgba(16, 24, 40, 0.8);
border-radius: 12rpx;
padding: 25rpx;
margin-bottom: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
border: 1rpx solid rgba(74, 144, 226, 0.3);
.course-info {
flex: 1;
.course-name {
font-size: 28rpx;
color: #fff;
display: block;
margin-bottom: 10rpx;
}
.course-credit {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
}
.grade-score {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-weight: 600;
&.excellent {
background: rgba(0, 200, 83, 0.15);
border: 2rpx solid rgba(0, 200, 83, 0.3);
color: #00c853;
}
&.good {
background: rgba(74, 144, 226, 0.15);
border: 2rpx solid rgba(74, 144, 226, 0.3);
color: #4facfe;
}
&.average {
background: rgba(255, 171, 0, 0.15);
border: 2rpx solid rgba(255, 171, 0, 0.3);
color: #ffab00;
}
&.pass {
background: rgba(255, 82, 82, 0.15);
border: 2rpx solid rgba(255, 82, 82, 0.3);
color: #ff5252;
}
&.fail {
background: rgba(255, 82, 82, 0.3);
border: 2rpx solid rgba(255, 82, 82, 0.5);
color: #ff5252;
}
.grade-level {
font-size: 24rpx;
margin-top: 5rpx;
}
}
}
}
}
.action-buttons {
display: flex;
gap: 20rpx;
padding: 0 30rpx;
margin-top: 40rpx;
:deep(.tech-button) {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
gap: 10rpx;
}
}
</style>

@ -0,0 +1,26 @@
<template>
<view class="container">
<text class="title">培养方案</text>
<!-- 页面内容 -->
</view>
</template>
<script setup>
import {
ref
} from 'vue'
//
const pageTitle = ref('培养方案')
</script>
<style>
.container {
padding: 20px;
}
.title {
font-size: 18px;
color: #333;
}
</style>

@ -0,0 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
</script>
<template>
<view>课程答题</view>
</template>
<style scoped>
</style>

@ -0,0 +1,45 @@
<template>
<view class="role-select-page">
<view class="title">请选择您的身份</view>
<view class="role-buttons">
<button @click="selectStudent"></button>
<button @click="selectTeacher"></button>
</view>
</view>
</template>
<script setup>
import { uni } from '@dcloudio/uni-app';
const selectStudent = () => {
uni.reLaunch({
url: '/pages/Student/learning/learning'
});
};
const selectTeacher = () => {
uni.reLaunch({
url: '/pages/Teacher/teacherCourseManagement/teacherCourseManagement'
});
};
</script>
<style scoped>
.role-select-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.title {
font-size: 24px;
margin-bottom: 20px;
}
.role-buttons {
display: flex;
gap: 20px;
}
</style>

@ -0,0 +1,433 @@
<template>
<view class="cyber-container">
<!-- 霓虹风格顶部栏 -->
<view class="cyber-header">
<view class="header-glitch" data-text="">智能课程表</view>
<view class="week-display">
<text class="week-label">当前周次</text>
<text class="week-value">{{ currentWeek }}</text>
</view>
</view>
<!-- 全息投影风格课表 -->
<view class="hologram-grid">
<!-- 星期表头 -->
<view class="grid-header">
<view class="time-slot">时间</view>
<view
v-for="day in weekDays"
:key="day.value"
class="day-header"
:class="{ today: isToday(day.value) }"
>
{{ day.label }}
</view>
</view>
<!-- 课程行 -->
<view
v-for="(timeSlot, slotIndex) in timeSlots"
:key="slotIndex"
class="grid-row"
>
<view class="time-slot">
<text class="time">{{ timeSlot }}</text>
<text class="slot-num">{{ slotIndex + 1 }}</text>
</view>
<!-- <view>测试{{ coursesData.mon[0].name }}</view> -->
<view
v-for="day in weekDays"
:key="day.value"
class="course-slot"
@click="showCourseDetail(day.value, slotIndex)"
>
<!-- <course-hologram
v-if="hasCourse(day.value, slotIndex)"
:course="getCourse(day.value, slotIndex)"
:active="isCurrentWeekCourse(day.value, slotIndex)"
/> -->
<course-hologram
v-if="hasCourse(day.value, slotIndex)"
:course="getCourse(day.value, slotIndex)"
:active="isCurrentWeekCourse(day.value, slotIndex)"
/>
</view>
</view>
</view>
<!-- 全息弹窗 -->
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog
v-if="currentCourse"
:title="currentCourse.name"
:message="courseDetail"
confirmText="确定"
@confirm="closePopup"
/>
</uni-popup>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
// dayjs
dayjs.locale('zh-cn')
//
const COLOR_SCHEME = {
math: '#00ff9d',
programming: '#00b8ff',
english: '#ff2d75',
physics: '#ff9d00',
default: '#8a2be2'
}
//
const weekDays = [
{ label: '周一', value: 'mon' },
{ label: '周二', value: 'tue' },
{ label: '周三', value: 'wed' },
{ label: '周四', value: 'thu' },
{ label: '周五', value: 'fri' },
{ label: '周六', value: 'sat' },
{ label: '周日', value: 'sun' }
]
//
const timeSlots = [
'08:20-09:05', '09:10-09:55', '10:15-11:00',
'11:05-11:50', '14:00-14:45', '14:50-15:35',
'15:55-16:40', '16:45-17:30', '18:30-19:15',
'19:20-20:05', '20:10-20:55', '21:00-21:45'
]
//
const coursesData = {
mon: [
{
section: 0,
name: '高等数学A(一)',
teacher: '李教授',
location: 'A101',
weeks: '1-16',
type: 'math'
},
{
section: 1,
name: '高等数学A(一)',
teacher: '李教授',
location: 'A101',
weeks: '1-19',
type: 'math'
}
],
tue: [
{
section: 3,
name: '程序设计基础',
teacher: '王老师',
location: '计科实验室',
weeks: '1-12',
type: 'programming'
}
],
wed: [
{
section: 2,
name: '大学英语',
teacher: '张老师',
location: 'C203',
weeks: '1-18',
type: 'english'
}
]
}
//
const currentWeek = ref(1)
const currentCourse = ref(null)
const popup = ref(null)
//
const courseDetail = computed(() => {
if (!currentCourse.value) return ''
return `教师: ${currentCourse.value.teacher}\n地点: ${currentCourse.value.location}\n周次: ${currentCourse.value.weeks}`
})
//
const calculateCurrentWeek = () => {
const startDate = dayjs('2025-03-1') //
const weeks = dayjs().diff(startDate, 'week') + 1
currentWeek.value = Math.max(1, Math.min(weeks, 18)) // 1-18
}
//
const isToday = (dayValue) => {
const dayMap = { mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6, sun: 0 }
return dayMap[dayValue] === dayjs().day()
}
//
const hasCourse = (day, section) => {
return coursesData[day]?.some(c => c.section === section) ?? false
}
//
const getCourse = (day, section) => {
return coursesData[day]?.find(c => c.section === section) || null
}
//
const isCurrentWeekCourse = (day, section) => {
const course = getCourse(day, section)
if (!course) return false
const weeks = course.weeks
if (weeks.includes('-')) {
const [start, end] = weeks.split('-').map(Number)
return currentWeek.value >= start && currentWeek.value <= end
} else if (weeks.includes(',')) {
return weeks.split(',').map(Number).includes(currentWeek.value)
}
return Number(weeks) === currentWeek.value
}
//
const showCourseDetail = (day, section) => {
currentCourse.value = getCourse(day, section)
if (currentCourse.value) {
popup.value.open()
}
}
//
const closePopup = () => {
popup.value.close()
}
//
onMounted(() => {
calculateCurrentWeek()
})
//
const CourseHologram = {
props: ['course', 'active'],
computed: {
courseColor() {
return COLOR_SCHEME[this.course.type] || COLOR_SCHEME.default
}
},
template: `
<view
class="hologram-card"
:style="{
'--hologram-color': courseColor,
opacity: active ? 1 : 0.6
}"
>
<text class="course-name">{{ course.name }}</text>
<text class="course-location">{{ course.location }}</text>
<view class="hologram-effect"></view>
</view>
`
}
</script>
<style lang="scss">
.cyber-container {
background: #0a0e17;
color: #00ff9d;
min-height: 100vh;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
.cyber-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background: rgba(0, 20, 40, 0.8);
border-bottom: 1px solid rgba(0, 255, 157, 0.3);
.header-glitch {
position: relative;
font-size: 44rpx;
font-weight: bold;
color: #00ff9d;
text-shadow: 0 0 10px rgba(0, 255, 157, 0.5);
}
.week-display {
display: flex;
align-items: center;
.week-label {
font-size: 28rpx;
margin-right: 10rpx;
color: rgba(0, 255, 157, 0.7);
}
.week-value {
font-size: 36rpx;
font-weight: bold;
color: #00ff9d;
}
}
}
.hologram-grid {
margin: 20rpx;
border: 1px solid rgba(0, 255, 157, 0.2);
border-radius: 8rpx;
overflow: hidden;
.grid-header {
display: flex;
background: rgba(0, 30, 60, 0.8);
.time-slot {
width: 160rpx;
padding: 20rpx;
text-align: center;
border-right: 1px solid rgba(0, 255, 157, 0.2);
font-size: 28rpx;
}
.day-header {
flex: 1;
padding: 20rpx;
text-align: center;
border-right: 1px solid rgba(0, 255, 157, 0.2);
font-size: 28rpx;
&:last-child {
border-right: none;
}
&.today {
color: #00ff9d;
position: relative;
font-weight: bold;
&::after {
content: '';
position: absolute;
bottom: 10rpx;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 3rpx;
background: #00ff9d;
}
}
}
}
.grid-row {
display: flex;
border-bottom: 1px solid rgba(0, 255, 157, 0.1);
&:last-child {
border-bottom: none;
}
.time-slot {
width: 160rpx;
padding: 15rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-right: 1px solid rgba(0, 255, 157, 0.1);
font-size: 24rpx;
color: #00b8ff;
.slot-num {
font-size: 20rpx;
color: rgba(0, 184, 255, 0.6);
}
}
.course-slot {
flex: 1;
min-height: 120rpx;
padding: 10rpx;
border-right: 1px solid rgba(0, 255, 157, 0.1);
&:last-child {
border-right: none;
}
}
}
}
.hologram-card {
position: relative;
height: 100%;
padding: 15rpx;
border-radius: 6rpx;
background: rgba(var(--hologram-color-rgb), 0.1);
border: 1px solid var(--hologram-color);
color: white;
overflow: hidden;
.course-name {
font-size: 26rpx;
font-weight: bold;
margin-bottom: 5rpx;
display: block;
}
.course-location {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
}
.hologram-effect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
135deg,
rgba(var(--hologram-color-rgb), 0.1) 0%,
rgba(var(--hologram-color-rgb), 0.3) 50%,
rgba(var(--hologram-color-rgb), 0.1) 100%
);
opacity: 0.5;
z-index: -1;
}
}
/* 颜色变量转换 */
.hologram-card {
--hologram-color-rgb: 0, 255, 157;
}
.hologram-card[style*="math"] {
--hologram-color-rgb: 0, 255, 157;
}
.hologram-card[style*="programming"] {
--hologram-color-rgb: 0, 184, 255;
}
.hologram-card[style*="english"] {
--hologram-color-rgb: 255, 45, 117;
}
.hologram-card[style*="physics"] {
--hologram-color-rgb: 255, 157, 0;
}
.hologram-card[style*="default"] {
--hologram-color-rgb: 138, 43, 226;
}
</style>

@ -0,0 +1,26 @@
<template>
<view class="container">
<text class="title">成绩</text>
<!-- 页面内容 -->
</view>
</template>
<script setup>
import {
ref
} from 'vue'
//
const pageTitle = ref('成绩')
</script>
<style>
.container {
padding: 20px;
}
.title {
font-size: 18px;
color: #333;
}
</style>

@ -0,0 +1,26 @@
<template>
<view class="container">
<text class="title">设置</text>
<!-- 页面内容 -->
</view>
</template>
<script setup>
import {
ref
} from 'vue'
//
const pageTitle = ref('设置')
</script>
<style>
.container {
padding: 20px;
}
.title {
font-size: 18px;
color: #333;
}
</style>

@ -0,0 +1,52 @@
<template>
<view class="container">
<!-- 页面内容 -->
<h3 >
上课
</h3>
</view>
</template>
<script setup>
//
</script>
<script >
import io from '@hyoga/uni-socket.io'
export default {
onLoad(options) {
//
console.log(options)
this.socket = io('ws://localhost:3400', { transports: ['websocket'],auth:{
xuehao:"202413501062",
sf:"stu",
kch:options.kch,
kctime:options.kctime,
k_id:options.uuid,
} })
//
this.socket.on('connect', () => console.log('Socket 已连接'))
this.socket.on('server', data => {
console.log(data)
})
this.socket.on('message', data => this.handleMessage(data))
},
onUnload() {
//
this.socket.off('message')
this.socket.disconnect()
},
methods: {
handleMessage(data) { /* 处理消息 */ },
sendData() {
this.socket.emit('chat', { text: 'Hello' })
}
}
}
</script>
<style scoped>
.container {
padding: 20rpx;
}
</style>

@ -0,0 +1,376 @@
<template>
<view class="tech-container">
<!-- 科技风背景元素 -->
<view class="tech-bg">
<view class="tech-grid"></view>
<view class="tech-circle circle-1"></view>
<view class="tech-circle circle-2"></view>
</view>
<!-- 用户信息卡片 -->
<view class="tech-card user-card">
<view class="user-info">
<view class="avatar-wrapper" @click="chooseAvatar">
<image class="avatar" :src="userInfo.avatar || '/static/user/avatar.png'" mode="aspectFill"></image>
<view class="avatar-border"></view>
</view>
<view class="info-right">
<view class="name-line" @click="editName">
<text class="username">{{ userInfo.name || '张三' }}</text>
<uni-icons type="compose" size="18" color="#00f0ff"></uni-icons>
</view>
<view class="tech-input-line" @click="editStudentId">
<text class="tech-label">学号:</text>
<text class="student-id">{{ userInfo.studentId || '20230001' }}</text>
<uni-icons type="arrowright" size="18" color="#7f8fa6"></uni-icons>
</view>
</view>
</view>
<view class="tech-divider"></view>
</view>
<!-- 功能卡片 -->
<view class="tech-card function-card">
<view class="function-grid">
<view class="function-item" v-for="item in functionList" :key="item.type" @click="navigateTo(item.type)">
<view class="tech-icon-wrapper">
<image class="function-icon" :src="item.icon"></image>
<view class="icon-halo"></view>
</view>
<text class="function-text">{{ item.name }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
//
const userInfo = ref({
name: '张三',
studentId: '20230001',
avatar: ''
})
//
const functionList = ref([
{ type: 'leave', name: '请假', icon: '/static/user/leave.png' },
{ type: 'plan', name: '培养方案', icon: '/static/user/plan.png' },
{ type: 'course', name: '选课', icon: '/static/user/course.png' },
{ type: 'score', name: '成绩', icon: '/static/user/score.png' },
{ type: 'settings', name: '设置', icon: '/static/user/settings.png' }
])
//
const navigateTo = (type) => {
const pages = {
'leave': '/pages/leave/leave',
'plan': '/pages/plan/plan',
'course': '/pages/course/course',
'score': '/pages/score/score',
'settings': '/pages/settings/settings'
}
uni.navigateTo({ url: pages[type] })
}
//
const chooseAvatar = () => {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
userInfo.value.avatar = res.tempFilePaths[0]
saveUserInfo()
uni.showToast({ title: '头像更新成功', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '取消选择', icon: 'none' })
}
})
}
//
const editName = () => {
uni.showModal({
title: '修改姓名',
content: userInfo.value.name,
editable: true,
placeholderText: '请输入姓名',
confirmText: '保存',
cancelText: '取消',
success: (res) => {
if (res.confirm && res.content) {
userInfo.value.name = res.content.trim()
saveUserInfo()
uni.showToast({ title: '姓名修改成功', icon: 'success' })
}
}
})
}
//
const editStudentId = () => {
uni.showModal({
title: '修改学号',
content: userInfo.value.studentId,
editable: true,
placeholderText: '请输入学号',
confirmText: '保存',
cancelText: '取消',
success: (res) => {
if (res.confirm && res.content) {
userInfo.value.studentId = res.content.trim()
saveUserInfo()
uni.showToast({ title: '学号修改成功', icon: 'success' })
}
}
})
}
//
const saveUserInfo = () => {
uni.setStorageSync('userInfo', userInfo.value)
}
//
onLoad(() => {
const cachedUserInfo = uni.getStorageSync('userInfo')
if (cachedUserInfo) {
userInfo.value = cachedUserInfo
}
})
</script>
<style lang="scss" scoped>
.tech-container {
padding: 30rpx;
background: linear-gradient(135deg, #0a0e21 0%, #121a3a 100%);
min-height: 100vh;
position: relative;
overflow: hidden;
}
/* 科技风背景 */
.tech-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
overflow: hidden;
.tech-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(0, 240, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.05) 1px, transparent 1px);
background-size: 40rpx 40rpx;
}
.tech-circle {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(0, 240, 255, 0.05), transparent 70%);
&.circle-1 {
width: 400rpx;
height: 400rpx;
top: -200rpx;
right: -200rpx;
}
&.circle-2 {
width: 300rpx;
height: 300rpx;
bottom: -150rpx;
left: -150rpx;
}
}
}
.tech-card {
background: rgba(30, 41, 59, 0.6);
border-radius: 20rpx;
margin-bottom: 30rpx;
overflow: hidden;
position: relative;
backdrop-filter: blur(10px);
border: 1rpx solid rgba(0, 240, 255, 0.1);
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2rpx;
background: linear-gradient(90deg, transparent, #00f0ff, transparent);
}
}
.user-card {
padding: 40rpx;
.user-info {
display: flex;
align-items: center;
.avatar-wrapper {
position: relative;
width: 140rpx;
height: 140rpx;
margin-right: 30rpx;
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
}
.avatar-border {
position: absolute;
top: -4rpx;
left: -4rpx;
right: -4rpx;
bottom: -4rpx;
border-radius: 50%;
border: 2rpx solid #00f0ff;
animation: pulse 2s infinite;
}
}
.info-right {
flex: 1;
.name-line {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.username {
font-size: 40rpx;
font-weight: bold;
color: #fff;
margin-right: 20rpx;
background: linear-gradient(to right, #00f0ff, #0088ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
.tech-input-line {
display: flex;
align-items: center;
padding: 16rpx 0;
.tech-label {
font-size: 30rpx;
color: #7f8fa6;
margin-right: 16rpx;
}
.student-id {
font-size: 30rpx;
color: #fff;
flex: 1;
}
}
}
}
.tech-divider {
height: 2rpx;
background: linear-gradient(90deg, transparent, rgba(0, 240, 255, 0.3), transparent);
margin-top: 20rpx;
}
}
.function-card {
padding: 30rpx;
.function-grid {
display: flex;
flex-direction: column;
.function-item {
display: flex;
align-items: center;
padding: 30rpx 0;
position: relative;
&:not(:last-child)::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1rpx;
background: linear-gradient(90deg, transparent, rgba(0, 240, 255, 0.1), transparent);
}
.tech-icon-wrapper {
width: 90rpx;
height: 90rpx;
margin-right: 30rpx;
background: rgba(0, 240, 255, 0.1);
border-radius: 22rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
border: 1rpx solid rgba(0, 240, 255, 0.2);
.function-icon {
width: 50rpx;
height: 50rpx;
z-index: 1;
}
.icon-halo {
position: absolute;
width: 100%;
height: 100%;
border-radius: 22rpx;
background: radial-gradient(circle, rgba(0, 240, 255, 0.3), transparent 70%);
opacity: 0;
transition: opacity 0.3s;
}
}
.function-text {
font-size: 32rpx;
color: #fff;
flex: 1;
}
&:active {
.tech-icon-wrapper .icon-halo {
opacity: 1;
}
}
}
}
}
/* 动画 */
@keyframes pulse {
0%, 100% {
opacity: 0.7;
transform: scale(0.98);
}
50% {
opacity: 1;
transform: scale(1.02);
}
}
</style>

@ -0,0 +1,6 @@
export {}
declare module "vue" {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -0,0 +1 @@
请准备一个二维码扫描图标PNG文件尺寸建议128x128像素蓝色科技风格与现有图标风格保持一致。

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@ -0,0 +1,76 @@
/**
* uni-app
*
* uni-app https://ext.dcloud.net.cn使
* 使scss使 import 便App
*
*/
/**
* App使
*
* 使scss scss 使 import
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color: #333; //
$uni-text-color-inverse: #fff; //
$uni-text-color-grey: #999; //
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color: #fff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; //
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //
/* 边框颜色 */
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm: 12px;
$uni-font-size-base: 14px;
$uni-font-size-lg: 16;
/* 图片尺寸 */
$uni-img-size-sm: 20px;
$uni-img-size-base: 26px;
$uni-img-size-lg: 40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; //
/* 文章场景相关 */
$uni-color-title: #2c405a; //
$uni-font-size-title: 20px;
$uni-color-subtitle: #555; //
$uni-font-size-subtitle: 18px;
$uni-color-paragraph: #3f536e; //
$uni-font-size-paragraph: 15px;

@ -0,0 +1,13 @@
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"lib": ["esnext", "dom"],
"types": ["@dcloudio/types"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

@ -0,0 +1,15 @@
// vite.config.ts
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import path from 'path'
export default defineConfig({
plugins: [uni()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'/static': path.resolve(__dirname, 'src/static') // 关键配置
}
}
})
Loading…
Cancel
Save