@ -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>
|
@ -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 ...
|
||||
]
|
||||
}
|
@ -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>
|
@ -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,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 {}
|
||||
}
|
After Width: | Height: | Size: 677 B |
After Width: | Height: | Size: 636 B |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 976 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
@ -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') // 关键配置
|
||||
|
||||
}
|
||||
}
|
||||
})
|