提交 7af88002 authored 作者: 王鹏飞's avatar 王鹏飞

refactor: 适配移动端

上级 a28942b6
......@@ -8,8 +8,8 @@ export function getAdvertisementList(params) {
/**
* 获取文章列表
*/
export function getArticleList(params) {
return httpRequest.get('/api/cms/api/v1/articles', { params })
export function getArticleList(params, options = {}) {
return httpRequest.get('/api/cms/api/v1/articles', { ...options, params })
}
/**
......@@ -27,22 +27,8 @@ export function submitApplicationForm(data) {
/**
* 获取用户信息
*/
export function getUser(params = { learning_info: 1 }) {
return httpRequest.get('/api/usercenter/v2/frontend/user/get-user-info', { params })
}
/**
* 修改用户信息
*/
export function updateUser(data) {
return httpRequest.post('/api/usercenter/user/update-user', data)
}
/**
* 修改密码
*/
export function updatePassword(data) {
return httpRequest.post('/api/usercenter/user/update-pwd', data)
export function getUser(params = { learning_info: 1 }, options = {}) {
return httpRequest.get('/api/usercenter/v2/frontend/user/get-user-info', { ...options, params })
}
/**
......@@ -65,3 +51,17 @@ export function checkCode(params) {
export function logout() {
return httpRequest.get('/api/passport/rest/logout')
}
export function getArticleDetail(id) {
return httpRequest.get(`/api/cms/api/v1/article/${id}/detail`)
}
const sectionNewsProjects = {
internationalDegree: ['6808657689426526208', '6805762100502200320', '6808658580045037568', '6823789210604929024'],
studyAbroad: ['25634688011239424', '6886139536666001408', '62110451281973248', '147349767330201600', '210777554510323712'],
}
export function getSectionNewsParams(section, pagination = {}) {
return { project_ids: [...sectionNewsProjects[section]], type_tag: 'article_news_hot', ...pagination }
}
<template>
<NuxtLayout>
<NuxtPage />
<NuxtPage :page-key="pageKey" />
</NuxtLayout>
</template>
<script setup>
const route = useRoute()
const pageKey = computed(() => route.path + JSON.stringify(route.query))
</script>
差异被折叠。
@media (max-width: 900px) {
body { min-width: 0; }
body {
font-size: 14px;
color: #424242;
background-color: #f9f8f8;
}
.main-layout {
max-width: 750px;
min-height: 100vh;
margin: 0 auto;
overflow: hidden;
background-color: #f9f8f8;
box-shadow: 0 1px 20px rgba(0, 0, 0, 0.04);
}
.main-container {
margin: 0 15px 30px;
}
.main-banner {
display: block;
height: 120px;
object-fit: cover;
}
}
/* Page content and site frames respond to viewport width. */
@media (max-width: 900px) {
body { min-width: 0; }
.desktop-only { display: none !important; }
.mobile-only { display: list-item; }
div.mobile-only { display: block; }
}
@media (min-width: 901px) {
.mobile-only { display: none; }
.desktop-only { display: revert; }
}
.w1200 {
max-width: 1200px;
margin: 0 auto;
}
.banner-img {
display: block;
width: 100%;
height: 151px;
object-fit: cover;
}
:deep(.swiper-pagination-bullet) {
width: 2px;
height: 2px;
background-color: #fff;
opacity: 1;
}
:deep(.swiper-pagination-bullet-active) {
width: 10px;
border-radius: 2px;
}
.banner {
height: 156px !important;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.why {
margin: 0 15px;
}
.why-title {
padding: 20px 0 25px;
font-size: 17px;
font-weight: bold;
line-height: 1;
color: #424242;
text-align: center;
}
.why-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 10px;
column-gap: 10px;
}
.why-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 125px;
color: #262626;
background: #fff;
img {
height: 42px;
}
.why-item__icon_hover {
display: none;
}
.why-content {
margin-top: 16px;
padding: 0 18px;
font-size: 12px;
font-weight: 400;
line-height: 17px;
text-align: center;
}
&:hover {
color: #fff;
background: #a81840;
box-shadow: 0px 2px 20px rgba(168, 24, 64, 0.2);
.why-item__icon {
display: none;
}
.why-item__icon_hover {
display: block;
}
}
}
.qa-list2 {
background: rgba(255,255,255,0.39);
box-shadow: 0px 1px 11px rgba(0,0,0,0.1);
opacity: 1;
border-radius: 11px;
padding-top:22px;
overflow: hidden;
width: 327px;
min-height:700px;
box-sizing: border-box;
.item{
margin-top:22px;
}
.item:last-child{
.line{
border:0;
}
}
.tit{
width: 300px;
// height: 30px;
background: #A81840;
opacity: 1;
border-radius: 0px 15px 15px 0px;
color:#FFFFFF;
font-size: 12px;
line-height: 30px;
white-space: nowrap;
text-align: left;
padding-left:15px;
}
p {
position: relative;
margin-top: 18px;
margin-left: 33px;
margin-right: 36px;
font-size: 12px;
font-family: SourceHanSansCN-Regular;
line-height: 1.5;
color: #333333;
background-color: #f9f8f8;
border-radius: 22px;
padding: 14px;
&::after {
content: '';
position: absolute;
top: 50%;
right: -9px;
margin-top: -9px;
transform: translateY(50%);
transform: rotate(45deg);
width: 18px;
height: 18px;
background-color: #f9f8f8;
}
}
.line{
height: 0px;
border-bottom: 1px dashed #BABABA;
margin:23px 12px 0 12px;
}
}
.process {
img {
width: 100%;
}
}
const projects = {
'1005': { label: '教育学硕士(儿童)MED', section: 'internationalDegree' },
'1021': { label: '家庭教育硕士MS', section: 'internationalDegree' },
'1026': { label: '领航数字时代EMBA', section: 'internationalDegree' },
'1012': { label: '工商管理硕士MBA', mobileLabel: '数字领导力方向MBA', section: 'internationalDegree' },
'1006': { label: '应用心理学硕士MAP', section: 'internationalDegree' },
'1008': { label: '瑞士酒店管理大学MBA', mobileLabel: '酒店和旅游方向MBA', section: 'internationalDegree' },
'1018': { label: '纽约州立大学石溪分校金融学硕士项目', shortLabel: '金融硕士保研项目', section: 'studyAbroad' },
'1025': { label: '德保罗大学金融学硕士项目', section: 'studyAbroad' },
'1020': { label: '斯蒂文斯理工学院工程管理硕士项目', shortLabel: '工程管理硕士保研项目', section: 'studyAbroad' },
'1028': { label: '雪城大学硕士保研项目', section: 'studyAbroad' },
'1017': { label: '加州浸会大学全日制工商管理博士项目', shortLabel: '全日制工商管理博士项目', section: 'studyAbroad' },
'1023': { label: '加州浸会大学全球科技创新管理学者DBA项目', shortLabel: '工商管理博士项目', section: 'studyAbroad' },
}
export function getEnrollmentProjects(ids, { mobile = false, compact = false } = {}) {
return ids.map(value => {
const project = projects[value]
const label = mobile && project.mobileLabel
? project.mobileLabel
: compact && project.shortLabel ? project.shortLabel : project.label
return { value, label, section: project.section }
})
}
export function filterEnrollmentProjects(projectList, path) {
const section = path.split('/')[1]
return section === 'internationalDegree' || section === 'studyAbroad'
? projectList.filter(project => project.section === section)
: projectList
}
export default [
{
"question": "毕业生颁发的学位证书与在美国/欧洲颁发的证书有何不同?",
"answer": "没有不同,完全一样,具有相同的形式和效力。"
},
{
"question": "紫荆教育在线国际学位项目上课形式是怎么样?",
"answer": "项目采用在线学习和线下面授相结合的授课方式。在线学习时,学生可根据自己的时间灵活安排学习,线下面授采用周末或短期集中安排的方式。"
},
{
"question": "紫荆教育在线国际学位项目学制多久?",
"answer": "学制为16—24个月不等,具体因项目而异。"
},
{
"question": "紫荆教育在线国际学位项目能否顺利毕业拿到证书呢?",
"answer": "修满规定学分的学员可获得相应项目的学位证书。"
},
{
"question": "紫荆教育在线国际学位项目是在职还是全职?",
"answer": "项目为在职硕士项目,采用在线学习和线下面授相结合的学习方式。"
},
{
"question": "如何申请紫荆教育在线国际学位项目?大概流程是什么?",
"answer": "申请流程为:在线填写申请资料—初审通过—面试安排—综合评审—录取offer—学费缴纳及入学手续办理。"
},
{
"question": "紫荆教育在线国际学位项目面试多少人?录取多少人?",
"answer": "采取现场/视频面试的方式,面试人数和录取人数视申请人数和申请资质而定。"
},
{
"question": "申请紫荆教育在线国际学位项目需要提交哪些材料?",
"answer": "申请项目需提交:<br />1. 本科学历证书;<br />2. 身份证正、反面照片;<br />3. 一寸白底证件照。<br /><br />学籍注册需提交:<br />1. 本科阶段中、英文双语版成绩单;<br />2. 英文版《教育部学历证书电子注册备案表》;<br />3. 入学协议;<br />4. 英文简历;<br />5. 推荐信2封;<br />6. 缴费凭证。"
},
{
"question": "大专毕业通过自考获得本科毕业证和学士学位,能否报考?",
"answer": "可以报考,只要获得专科及以上学位证书,即可报考,特别优秀者可放宽条件。"
},
{
"question": "紫荆教育在线国际学位项目学费是多少? 可以分期吗?",
"answer": "目前在线国际学位项目的学费在6.9万元—12.6万元人民币,由学生在入学前向紫荆教育缴清(不可选择分期)。"
},
{
"question": "学校是否正规?学籍是否正规?",
"answer": "紫荆教育所有合作院校均为各国官方教育机构认证的合规大学,受到US.NEWS,QS,AACSB,ACBSP等数十个国际权威评级和教育评价组织认可。所有学生均注册正规学籍,终身可查。"
},
{
"question": "在线学习与出国留学颁发的证书有何不同?",
"answer": "颁发正规学位,与留学生完全一样,且具有相同的形式和法律效力。不存在在线学位、远程学位、校证、特殊学位等说法。"
},
{
"question": "关于上课形式,课程体系,老师水平",
"answer": "以网络授课为主。配合线下课,企业参访,工作坊,国际访学等多种授课形式。课程体系与授课老师完全按照在校生标准配置。"
},
{
"question": "学制多久?能否确保拿到学位证书?",
"answer": "国际在线学位项目的学制一般为16—24个月不等。只要按时上课,完成作业并修满规定学分就可获得所修项目的学位证书。"
}
]
export default [
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/yxsz_03.jpg',
name: "Sharon Hundley,Ed.D.",
project: "师资-CU | 副教授",
desc: "康博斯威尔大学教育学院早期儿童教育项目主任<br/>AMI认证3-6岁主教<br/>AMS授证导师",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/yxsz_04.jpg',
name: "胡晓毅",
project: "师资-CU | 教授",
desc: "北京师范大学教育学部教授、硕士生导师<br/>美国堪萨斯大学特殊教育系博士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/yxsz_05.jpg',
name: "Ash Soni",
project: "师资-KELLEY | 教授",
desc: "印第安纳大学工商管理博士<br/>KELLEY商学院副院长、教授",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/yxsz_06.jpg',
name: "肇越",
project: "师资-KELLEY | 博士",
desc: "清华五道口经济学博士<br/>清华大学五道口金融学院业界导师、经济学家兼研究院院长<br/>香港致富证券有限公司首席",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/yxsz_07.jpg',
name: "Chris Speicher",
project: "师资-MARYWOOD | 副教授",
desc: "天普大学博士副教授",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/yxsz_08.jpg',
name: "张恩忠",
project: "师资-MARYWOOD | 副教授",
desc: "美国普渡大学博士<br/>中国人民大学教授、博士生导师",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/yxsz_09.jpg',
name: "Andrea Leuenberger",
project: "师资-SHMS | 项目主管",
desc: "擅长于牧学领域的学习设计<br/>曾在纽约奢华酒店Swiss atel The Drake和莫斯科、中亚地区等多家奢华酒店担任运营总监和高管",
},
]
export const aboutNavigation = [
{ name: '紫荆简介', path: '/about', value: '1' },
{ name: '文化理念', path: '/about/culture', value: '2' },
{ name: '企业动态', path: '/about/news', value: '3' },
{ name: '校友故事', path: '/about/alumnus', value: '7' },
{ name: '公开课', path: '/about/lesson', value: '5' },
{ name: '联系我们', path: '/about/contact', value: '4' },
]
export const getSiteNavigation = (showDownloadPopup = () => {}) => [
{
name: '院校客户',
path: '/#school',
items: [
{
name: 'AI教学基础设施',
items: [
{ name: '人工智能基础:数据分析实践通识课', href: '/bi' },
{ name: '人工智能应用实践通识课', href: '/ai' },
{ name: '数字教材', href: '/book' },
{ name: '个性化智能学习平台', href: 'https://saas-learn.ezijing.com/' },
{ name: '紫荆e-SaaS', href: 'https://e-saas.ezijing.com/' },
],
},
{
name: 'AI+专业建设',
items: [
{
name: '新工科',
href: '/platform',
items: [
{ name: '大数据实验室', href: '/platform/new-engineering/1' },
{ name: '网络安全实验室', href: '/platform/new-engineering/4' },
{ name: '人工智能开发实验室', href: '/platform/new-engineering/2' },
{ name: '物联网实验室', href: '/platform/new-engineering/5' },
{ name: '信息安全实验室', href: '/platform/new-engineering/3' },
],
},
{
name: '新商科',
href: '/platform',
items: [
{ name: 'AIGC智能营销实验室', href: '/platform/new-business-studies/3' },
{ name: 'AIGC金融工程智能创新实验室', href: '/platform/new-business-studies/4' },
{ name: 'AIGC融媒体实验室', href: '/platform/new-business-studies/5' },
{ name: 'AIGC网络主播实验室', href: '/platform/new-business-studies/6' },
],
},
{
name: '低空经济',
href: '/platform/dk',
items: [
{ name: '低空技术', href: '/platform/dk' },
{ name: '低空文旅', href: '/platform/dk' },
{ name: '低空物流', href: '/platform/dk' },
],
},
],
},
{
name: '解决方案',
items: [
{ name: 'AI+专业改造', href: '/solution/ai' },
{ name: '新双高建设', href: '/solution/new-double-high' },
{ name: '应用型本科', href: '/solution/applied-undergraduate' },
// { name: '产教融合实训基地', href: '/solution/base' },
// { name: '国赛/省赛/行业赛', href: '/competition' },
// { name: '国际合作', href: '/solution/international' },
],
},
{ name: '紫荆优雇Agent', href: '/yougu' },
],
},
{
name: '个人客户',
path: '/#personal',
items: [
{
name: '国际留学',
href: '/studyAbroad',
groupItems: [
{
name: '雪城大学',
href: 'https://syracuse-plus.ezijing.com/',
items: [
{ name: '计算机工程硕士', href: 'https://syracuse-plus.ezijing.com/' },
{ name: '计算机科学硕士', href: 'https://syracuse-plus.ezijing.com/' },
{ name: '电气工程硕士', href: 'https://syracuse-plus.ezijing.com/' },
],
},
{
name: '斯蒂文斯理工学院',
href: 'https://sit-plus.ezijing.com/',
items: [{ name: '工程管理硕士', href: 'https://sit-plus.ezijing.com/' }],
},
{
name: '纽约州立大学石溪分校',
href: 'https://sbu-plus.ezijing.com/',
items: [{ name: '金融学硕士', href: 'https://sbu-plus.ezijing.com/' }],
},
{
name: '帕克大学',
href: 'https://park-plus.ezijing.com/',
items: [
{ name: '工商管理硕士', href: 'https://park-plus.ezijing.com/' },
{ name: '信息系统与商业数据分析硕士', href: 'https://park-plus.ezijing.com/' },
],
},
{
name: '德保罗大学',
href: 'https://depaul-plus.ezijing.com/',
items: [{ name: '金融学硕士', href: 'https://depaul-plus.ezijing.com/' }],
},
],
},
{
name: '国际学位',
href: '/internationalDegree',
groupItems: [
{
name: '硕士',
items: [
{ name: '教育学硕士(儿童)MED', href: 'https://cu.ezijing.com/' },
{ name: '教育学硕士(家庭)MED', href: 'https://cuw.ezijing.com/' },
],
},
{
name: 'EMBA',
items: [{ name: '玛丽伍德大学数字时代领航人才EMBA', href: 'https://marywood.ezijing.com/' }],
},
{
name: 'MBA',
items: [
{ name: '玛丽伍德大学综合管理方向MBA', href: 'https://marywood.ezijing.com/' },
{ name: '瑞士酒店管理大学MBA', href: 'https://shms.ezijing.com/' },
],
},
{ name: 'DBA', items: [{ name: '科创企业家学者项目DBA', href: 'https://ppai.ezijing.com/' }] },
],
},
{
name: '职业技能',
href: '/certificate',
items: [
{ name: '人工智能通识应用', href: 'https://eec.ezijing.com/AI' },
{ name: '智能数据分析与实战应用', href: 'https://eec.ezijing.com/product' },
{ name: '智能数据营销与实战应用', href: 'https://eec.ezijing.com' },
{ name: '1+X金融产品数字化营销', href: 'https://x.ezijing.com' },
{ name: '私人财富风险管理课程', href: 'https://prp.ezijing.com' },
// { name: '“未来管理者”MBA课程', href: 'https://cfflp.ezijing.com' },
],
},
{
name: '就业服务',
href: '/job',
// items: [
// { name: '1V1定制化', href: '/job' },
// { name: '全程辅导', href: '/job' },
// { name: '内推面试', href: '/job' },
// { name: '内推入职', href: '/job' },
// ],
},
],
},
{
name: '政企客户',
path: '/#enterprise',
items: [{ name: '产教融合实训基地', href: '/solution/base' }],
},
{
name: '关于紫荆',
path: '/about',
items: aboutNavigation.map((item) => ({ ...item, href: item.path })),
},
{ name: '下载APP', onClick: showDownloadPopup },
]
export const getH5Navigation = (showDownloadPopup) => {
const adapt = (item) => ({
...item,
...(item.href?.startsWith('/') ? { path: item.href, href: undefined } : {}),
children: (item.items || item.groupItems || []).map(adapt),
})
return getSiteNavigation(showDownloadPopup).map(adapt)
}
export default [
{
"question": "国际硕士保研项目对本科学校的专业有没有要求?非财经类能否申请?",
"answer": "纽约州立大学石溪分校金融硕士保研项目对专业无强制性要求,有金融学及数学,统计学,计算机等STEM相关专业背景的学生会更有优势,但项目学生第一年能通过金融硕士先修课程补充相关专业基础顺利适应未来金融硕士专业学习。玛丽伍德大学工商管理硕士保研项目对申请者本科专业也没有硬性规定,但申请管理信息系统硕士最好能有一定的数学与计算机基础。"
},
{
"question": "签证是自己办理吗?拒签了怎么办?",
"answer": "保研项目学生一般是首次赴美,签证需要学生面签。但紫荆教育会提供一定的申请签证辅导和协助(面试技巧等)。个别因回答技巧不成熟被拒签的情况也可以调整申请策略后再次申请签证。"
},
{
"question": "硕士保研项目面试难吗?怎么准备?",
"answer": "国际硕士保研项目面试环节着重考察同学们专业基础,研究能力,语言表达能力以及其他综合能力。学生提交的申请材料初审通过后招生办会邮件通知获得面试资格,并告知面试相关环节以及准备范围。 面试为中英文方式 ,包括自我介绍以及教授提问等环节。"
},
{
"question": "硕士保研项目有没有奖学金?",
"answer": "美国高校通常极少为商科类硕士项目提供奖学金,紫荆教育国际硕士保研项目基于对中国学子的认可提供最高16,000美元奖学金,学生入学后可以申请,校方会结合考生专业基础英语能力综合素养等综合评审。"
},
{
"question": "赴美留学生购买医疗保险是否方便?",
"answer": "去美国留学(F-1)或作访问学者 (J-1),一般都需要购买医疗保险。美国的多数学校也会为本校学生提供学生集体医疗保险,同学也可选择在校外自行购买医疗保险。"
},
{
"question": "在美国留学期间的住宿费和生活费大概预估是多少钱?是住学校宿舍吗?",
"answer": "留美一年住宿费和生活费预计15-20W人民币。石溪大学保研项目学员可根据石溪大学录取信上提供的校方房源选择住宿,玛丽伍德大学也为保研项目统一安排校内住宿。<br/>加州浸会大学工商管理博士项目也为项目学员提供了校区内的房屋,在校园安保范围内,每天可步行至教室,十分方便。"
},
{
"question": "英语基础不好会影响学习吗?",
"answer": "针对英语基础不足的学员,紫荆教育提供了英语强化课程,课程测试结果得到美国大学的官方认可,帮助学生顺利适应海外学习。"
},
{
"question": "博士项目最少出国几年?",
"answer": "CBU工商管理博士项目学制是三年:<br />第一年在美国CBU本校上课,6门课程,18学分,如果顺利完成学业,获得学分,12个月就可以了。<br />第二年在国内学习,也是6门课,18学分。<br />第三年主要写毕业论文,根据学生论文的选题决定研究和学习地点。如论文选题研究对象和方向在中国,那么也是在国内学习。"
},
{
"question": "博士项目国内学习的授课方式是线上还是线下?时长?每周学习几次?",
"answer": "按照学校规定,每门课程(3学分)包含24个教学课时。根据教学安排,学员需要每两个月完成一到两门课程的学习。国内授课以线下集中授课为主,根据疫情影响可能会采用线上直播的形式作为补充。线下课在3整天(每天8课时)内为学员集中授课,直播课每周一次,每次3课时,持续8周。"
},
{
"question": "疫情期间,赴美留学签证是否受影响?出国学习是否安全?",
"answer": "目前美国学校学生签证业务已重启,学校正常发放录取,正常签发 I-20 (用于赴美签证的材料)。 2021年中国赴美留学达到35万人,依然是美国最大的国际学生生源国。目前国内已全面放开出国留学手续申请;海外也已全面开放线下教学。"
},
{
"question": "请问必须有本科学历吗?对专业有没有要求?",
"answer": "是的,申请者必须要求本科学历或本科在读。但是对专业没有限制。"
},
{
"question": "英语基础不好会影响学习吗?",
"answer": "紫荆教育提供了英语强化课程,课程测试结果得到美国大学的官方认可。"
},
{
"question": "签证是自己办理吗?拒签了怎么办?",
"answer": "签证需要学生面签。紫荆教育会提供相应的办理辅导和协助。"
},
{
"question": "国内学习什么课程?在哪上课?",
"answer": "国内开设的是语言强化课、专业先修课、及少量学分课。采用线上授课+线下集中授课的方式。"
}
]
export default [
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_01.jpg',
name: "Tim Gramling",
project: "师资-CBU | 院长",
desc: "加州浸会大学商学院院长 <br/>美国东北大学法律与公共政策博士 <br/>哈佛大学计算机科学学士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_02.jpg',
name: "Dmytro Holod",
project: "师资-SBU | 副院长",
desc: "石溪大学商学院副院长 <br/>肯塔基大学博士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_03.jpg',
name: "Danling Jiang",
project: "师资-SBU | 副院长",
desc: "石溪大学商学院金融学教授<br/>俄亥俄州立大学金融学博士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_04.jpg',
name: "Yehia Massoud",
project: "师资-SIT | 院长",
desc: "教授、学院院长<br/>IEEE电气与电子工程师协会院士<br/>麻省理工学院博士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_05.jpg',
name: "Kathryn Abel",
project: "师资-SIT | 副教授",
desc: "史蒂文斯理工学院博士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_06.jpg',
name: "Steven Hoffenson",
project: "师资-SIT | 讲师",
desc: "密歇根大学博士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_07.jpg',
name: "Young Shin Kim",
project: "师资-SBU | 副教授",
desc: "石溪大学金融副教授<br/>Sogang大学博士",
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/studyAbroad/yxsz_08.jpg',
name: "Gokhan Torna",
project: "师资-SBU | 副教授",
desc: "石溪大学金融副教授<br/>堪萨斯大学商学院金融学博士",
},
]
export default [
{
question: '毕业生颁发的学位证书与在美国/欧洲颁发的证书有何不同?',
answer: '没有不同,完全一样,具有相同的形式和效力。'
},
{
question: '紫荆教育在线国际学位项目上课形式是怎么样?',
answer:
'项目采用在线学习和线下面授相结合的授课方式。在线学习时,学生可根据自己的时间灵活安排学习,线下面授采用周末或短期集中安排的方式。'
},
{
question: '紫荆教育在线国际学位项目学制多久?',
answer: '学制为16—24个月不等,具体因项目而异。'
},
{
question: '紫荆教育在线国际学位项目能否顺利毕业拿到证书呢?',
answer: '修满规定学分的学员可获得相应项目的学位证书。'
},
{
question: '紫荆教育在线国际学位项目是在职还是全职?',
answer: '项目为在职硕士项目,采用在线学习和线下面授相结合的学习方式。'
},
{
question: '如何申请紫荆教育在线国际学位项目?大概流程是什么?',
answer: '申请流程为:在线填写申请资料—初审通过—面试安排—综合评审—录取offer—学费缴纳及入学手续办理。'
},
{
question: '紫荆教育在线国际学位项目面试多少人?录取多少人?',
answer: '采取现场/视频面试的方式,面试人数和录取人数视申请人数和申请资质而定。'
},
{
question: '申请紫荆教育在线国际学位项目需要提交哪些材料?',
answer:
'申请项目需提交:<br />1. 本科学历证书;<br />2. 身份证正、反面照片;<br />3. 一寸白底证件照。<br /><br />学籍注册需提交:<br />1. 本科阶段中、英文双语版成绩单;<br />2. 英文版《教育部学历证书电子注册备案表》;<br />3. 入学协议;<br />4. 英文简历;<br />5. 推荐信2封;<br />6. 缴费凭证。'
},
{
question: '大专毕业通过自考获得本科毕业证和学士学位,能否报考?',
answer: '可以报考,只要获得专科及以上学位证书,即可报考,特别优秀者可放宽条件。'
},
{
question: '紫荆教育在线国际学位项目学费是多少? 可以分期吗?',
answer: '目前在线国际学位项目的学费在6.9万元—12.6万元人民币,由学生在入学前向紫荆教育缴清(不可选择分期)。'
}
]
export default [
{
question: '国际硕士保研项目对本科学校的专业有没有要求?非财经类能否申请?',
answer:
'纽约州立大学石溪分校金融硕士保研项目对专业无强制性要求,有金融学及数学,统计学,计算机等STEM相关专业背景的学生会更有优势,但项目学生第一年能通过金融硕士先修课程补充相关专业基础顺利适应未来金融硕士专业学习。玛丽伍德大学工商管理硕士保研项目对申请者本科专业也没有硬性规定,但申请管理信息系统硕士最好能有一定的数学与计算机基础。'
},
{
question: '签证是自己办理吗?拒签了怎么办?',
answer:
'保研项目学生一般是首次赴美,签证需要学生面签。但紫荆教育会提供一定的申请签证辅导和协助(面试技巧等)。个别因回答技巧不成熟被拒签的情况也可以调整申请策略后再次申请签证。'
},
{
question: '硕士保研项目面试难吗?怎么准备?',
answer:
'国际硕士保研项目面试环节着重考察同学们专业基础,研究能力,语言表达能力以及其他综合能力。学生提交的申请材料初审通过后招生办会邮件通知获得面试资格,并告知面试相关环节以及准备范围。 面试为中英文方式 ,包括自我介绍以及教授提问等环节。'
},
{
question: '硕士保研项目有没有奖学金?',
answer:
'美国高校通常极少为商科类硕士项目提供奖学金,紫荆教育国际硕士保研项目基于对中国学子的认可提供最高16,000美元奖学金,学生入学后可以申请,校方会结合考生专业基础英语能力综合素养等综合评审。'
},
{
question: '赴美留学生购买医疗保险是否方便?',
answer:
'去美国留学(F-1)或作访问学者 (J-1),一般都需要购买医疗保险。美国的多数学校也会为本校学生提供学生集体医疗保险,同学也可选择在校外自行购买医疗保险。'
},
{
question: '在美国留学期间的住宿费和生活费大概预估是多少钱?是住学校宿舍吗?',
answer:
'留美一年住宿费和生活费预计15-20W人民币。石溪大学保研项目学员可根据石溪大学录取信上提供的校方房源选择住宿,玛丽伍德大学也为保研项目统一安排校内住宿。<br/>加州浸会大学工商管理博士项目也为项目学员提供了校区内的房屋,在校园安保范围内,每天可步行至教室,十分方便。'
},
{
question: '英语基础不好会影响学习吗?',
answer:
'针对英语基础不足的学员,紫荆教育提供了英语强化课程,课程测试结果得到美国大学的官方认可,帮助学生顺利适应海外学习。'
},
{
question: '博士项目最少出国几年?',
answer:
'CBU工商管理博士项目学制是三年:<br />第一年在美国CBU本校上课,6门课程,18学分,如果顺利完成学业,获得学分,12个月就可以了。<br />第二年在国内学习,也是6门课,18学分。<br />第三年主要写毕业论文,根据学生论文的选题决定研究和学习地点。如论文选题研究对象和方向在中国,那么也是在国内学习。'
},
{
question: '博士项目国内学习的授课方式是线上还是线下?时长?每周学习几次?',
answer:
'按照学校规定,每门课程(3学分)包含24个教学课时。根据教学安排,学员需要每两个月完成一到两门课程的学习。国内授课以线下集中授课为主,根据疫情影响可能会采用线上直播的形式作为补充。线下课在3整天(每天8课时)内为学员集中授课,直播课每周一次,每次3课时,持续8周。'
},
{
question: '疫情期间,赴美留学签证是否受影响?出国学习是否安全?',
answer:
'目前美国学校学生签证业务已重启,学校正常发放录取,正常签发 I-20 (用于赴美签证的材料)。 2021年中国赴美留学达到35万人,依然是美国最大的国际学生生源国。目前国内已全面放开出国留学手续申请;海外也已全面开放线下教学。'
}
]
<template>
<div class="card">
<div class="app-card" :style="titleAlign ? { '--card-title-align': titleAlign } : undefined">
<div class="card-hd">
<div class="card-hd__title">
<slot name="title">{{ title }}</slot>
......@@ -13,12 +13,13 @@
<script setup>
defineProps({
title: String,
titleAlign: { type: String, default: 'left' },
titleAlign: String,
})
</script>
<style lang="scss">
.is-pc {
@media (min-width: 901px) {
.app-card {
.card-hd {
position: relative;
display: flex;
......@@ -28,16 +29,11 @@ defineProps({
}
.card-hd__title {
flex: 1;
// border-left: 7px solid #aa1941;
// padding-left: 20px;
font-size: 30px;
font-weight: bold;
line-height: 1;
// text-align: v-bind(titleAlign);
text-align: center;
a:hover {
color: var(--main-color);
}
text-align: var(--card-title-align, center);
a:hover { color: var(--main-color); }
}
.card-hd__aside {
position: absolute;
......@@ -47,27 +43,32 @@ defineProps({
cursor: pointer;
}
}
.is-h5 {
.card {
margin: 0.2rem 0.15rem;
}
}
@media (max-width: 900px) {
.app-card {
margin: 20px 15px;
.card-hd {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.15rem;
margin-bottom: 15px;
}
.card-hd__title {
flex: 1;
border-left: 0.03rem solid #aa1941;
padding-left: 0.05rem;
font-size: 0.17rem;
font-weight: bold;
line-height: 1;
min-width: 0;
border-left: 3px solid #aa1941;
padding-left: 5px;
font-size: 17px;
text-align: var(--card-title-align, left);
}
.card-hd__aside {
font-size: 0.1rem;
color: #9b9b9b;
flex: 0 0 auto;
margin-left: 8px;
font-size: 10px;
cursor: auto;
}
}
}
</style>
......@@ -44,3 +44,11 @@ defineProps({
}
}
</style>
<style scoped>
@media (max-width: 900px) {
.app-contact { height: auto; min-height: 180px; padding: 30px 20px; background-size: cover; }
.app-contact h4 { font-size: 22px; margin-bottom: 20px; }
.app-contact p { font-size: 16px; line-height: 1.8; overflow-wrap: anywhere; }
}
</style>
......@@ -30,8 +30,8 @@ const hoveredBtn = ref('')
<img
v-if="hoveredBtn !== 'android'"
src="https://webapp-pub.ezijing.com/www/pc/next/qrcode_ios.png"
alt="iOS下载二维码" />
<img v-else src="https://webapp-pub.ezijing.com/www/pc/next/qrcode_app.png?v=1" alt="Android下载二维码" />
alt="iOS 下载二维码" />
<img v-else src="https://webapp-pub.ezijing.com/www/pc/next/qrcode_app.png?v=1" alt="Android 下载二维码" />
</div>
<div class="app-download-btns">
<a
......@@ -166,3 +166,16 @@ const hoveredBtn = ref('')
}
}
</style>
<style lang="scss" scoped>
@media (max-width: 900px) {
.app-download-popup {
.app-download-dialog { width: calc(100% - 30px); max-width: 400px; height: auto; padding: 44px 20px 28px; }
.app-download-close { top: 12px; right: 12px; }
.app-download-pic { display: none; }
.app-download-content { margin: 0; h2 { font-size: 24px; } }
.app-download-entry { margin-top: 24px; flex-direction: column; gap: 20px; }
.app-download-btns { margin-left: 0; }
}
}
</style>
<script setup>
const { locale } = useI18n()
const items = [
{
......@@ -139,18 +138,14 @@ const items = [
<div class="copyright">
<div class="inner">
<p>
{{
locale.value === 'en-US'
? `Copyright © 2026 Zijing Education. All rights reserved.`
: `Copyright © 2026 Zijing Education. All rights reserved. 清控紫荆(北京)教育科技股份有限公司`
}}
版权所有 © 2026 清控紫荆(北京)教育科技股份有限公司
</p>
<a
target="_blank"
href="https://tsm.miit.gov.cn/dxxzsp/"
style="color: rgba(153, 153, 153, 1); text-decoration: none; margin-left: 10px">
<p>
{{ locale.value === 'en-US' ? 'Beijing ICP Certificate No.: 150431 ' : '京ICP证150431号' }}
京ICP证150431号
</p>
</a>
<a
......@@ -168,7 +163,7 @@ const items = [
src="https://zws-imgs-pub.ezijing.com/e0a0ec47dfdfc1e0797b1d5254021d00.png"
style="width: 20px; height: 20px; margin: 0 6px; display: block" />
<p>
{{ locale.value === 'en-US' ? `Beijing ICP Filing No.: 15016866-1` : '安备 11010802023681号' }}
京公网安备 11010802023681号
</p>
</a>
<a
......@@ -176,7 +171,7 @@ const items = [
href="https://beian.miit.gov.cn/#/Integrated/index"
style="color: rgba(153, 153, 153, 1); text-decoration: none">
<p>
{{ locale.value === 'en-US' ? 'Beijing ICP Filing No.: 15016866-1' : '京ICP备15016866号-1' }}
京ICP备15016866号-1
</p>
</a>
</div>
......@@ -277,4 +272,24 @@ const items = [
font-size: 12px;
}
}
@media (max-width: 900px) {
.app-footer .app-footer-inner {
width: 100% !important;
padding-right: 16px !important;
padding-left: 16px !important;
box-sizing: border-box;
flex-wrap: wrap !important;
gap: 16px !important;
}
.app-footer .app-footer-right,
.app-footer .copyright .inner {
flex-wrap: wrap !important;
gap: 16px !important;
}
.app-footer .app-footer-left,
.app-footer .app-footer-right {
width: 100% !important;
}
}
</style>
......@@ -3,7 +3,11 @@
<h1 class="app-main__title" v-if="title">{{ title }}</h1>
<div class="app-main-inner">
<ul class="app-main-tabs">
<li v-for="(item, index) in tabs" :class="genClassName(item)" :key="index" @click="handleClick(item.path)">
<li
v-for="(item, index) in tabs"
:key="index"
:class="[genClassName(item), { 'desktop-only': item.viewport === 'desktop', 'mobile-only': item.viewport === 'mobile' }]"
@click="handleClick(item.path)">
{{ item.name }}
</li>
</ul>
......@@ -26,9 +30,6 @@ export default {
title: { type: String },
tabs: { type: Array, default: () => [] },
},
data() {
return {}
},
computed: {
activeTab() {
const tabs = [...this.tabs].reverse()
......@@ -38,11 +39,7 @@ export default {
},
methods: {
handleClick(path) {
if (path === '/roadmap') {
window.location.href = path
} else {
this.$router.push({ path })
}
},
genClassName(data) {
if (!this.activeTab) return ''
......@@ -52,7 +49,8 @@ export default {
}
</script>
<style lang="scss">
.is-pc {
@media (min-width: 901px) {
.app-main {
max-width: 1200px;
margin: 0 auto;
......@@ -114,29 +112,37 @@ export default {
background: #fff;
}
}
.is-h5 {
@media (max-width: 900px) {
.app-main { min-width: 0; }
.app-main__title { padding: 20px 15px; font-size: 22px; line-height: 1.4; }
.app-main-container { min-width: 0; }
.app-main-tabs {
display: flex;
gap: 24px;
padding: 0 15px;
overflow-x: auto;
overscroll-behavior-x: contain;
li {
margin-left: 0.3rem;
font-size: 0.14rem;
line-height: 0.38rem;
margin-left: 0;
flex-shrink: 0;
font-size: 14px;
line-height: 44px;
color: #333;
white-space: nowrap;
&.is-active {
border-bottom: 0.02rem solid #aa1941;
border-bottom: 2px solid #aa1941;
}
}
}
.banner {
.app-main-container .banner {
display: block;
width: 100%;
height: 1.2rem;
height: 120px;
object-fit: cover;
}
.app-main-article {
padding: 0.2rem 0.15rem;
padding: 20px 15px;
background: #fff;
}
}
......@@ -144,4 +150,5 @@ export default {
.app-main-article img {
max-width: 100%;
}
</style>
<template>
<span class="app-link">
<component :is="wrapperTag" class="app-link">
<a :href="href" :target="target" v-if="href"><slot /></a>
<nuxt-link :to="path" v-else-if="path"><slot /></nuxt-link>
<div v-else-if="data.onClick" @click="data.onClick"><slot /></div>
<template v-else><slot /></template>
</span>
</component>
</template>
<script setup>
......@@ -12,6 +12,7 @@ const props = defineProps({
data: { type: Object, default: () => ({}) },
to: [String, Object, Function],
target: { type: String, default: '_blank' },
wrapperTag: { type: String, default: 'span' },
})
const href = computed(() => {
......
<template>
<div v-bind="$attrs" class="article-detail">
<div class="article-back" @click="emit('back')">
<div class="el-icon-arrow-left"></div>
<div class="text">返回</div>
<div class="article-detail-page">
<nav v-if="mobileTabs.length" class="article-detail-tabs" aria-label="栏目导航">
<NuxtLink
v-for="tab in mobileTabs"
:key="tab.path"
:to="tab.path"
:class="{ active: isActiveTab(tab.path) }">
{{ tab.name }}
</NuxtLink>
</nav>
<article class="article-detail">
<button class="article-back" type="button" @click="emit('back')">
<svg class="article-back-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg>
<span>返回</span>
</button>
<p v-if="status === 'pending'" class="article-status" role="status">正在加载…</p>
<p v-else-if="error" class="article-status" role="alert">文章加载失败,请稍后重试。<button type="button" @click="refresh()">重新加载</button></p>
<p v-else-if="!article.id" class="article-status">文章不存在或已下架。</p>
<template v-else>
<div class="article-time">{{ formatDate(article.start_time || article.created_at) }}</div>
<h1 class="article-title">{{ article.title }}</h1>
<div class="article-content" v-html="article.content"></div>
</template>
</article>
<section v-if="relatedArticles.length" class="article-related">
<h2>相关推荐</h2>
<div class="article-related-list">
<NuxtLink
v-for="item in relatedArticles"
:key="item.id"
class="article-related-item"
:to="`${relatedPath}/${item.id}`">
<img v-if="item.web_img_uri" :src="item.web_img_uri" alt="" loading="lazy" />
<span>{{ item.title }}</span>
</NuxtLink>
</div>
<div class="article-time">{{ formatDate(data.start_time) }}</div>
<h1 class="article-title">{{ data.title }}</h1>
<div class="article-content" v-html="data.content"></div>
<NuxtLink class="article-related-more" :to="relatedPath">查看更多</NuxtLink>
</section>
</div>
</template>
<script setup>
import httpRequest from '@/utils/httpRequest'
import { getArticleDetail, getArticleList, getSectionNewsParams } from '~/api'
const route = useRoute()
const emit = defineEmits(['back', 'loaded'])
const aboutTabs = useAboutNavList()
const alumnusTabs = useAlumnusNavList()
const degreeTabs = useInternationalDegreeNavList()
const studyAbroadTabs = useStudyAbroadNavList()
const config = useRuntimeConfig()
const relatedPath = computed(() => {
if (route.path.startsWith('/about/message/')) return '/about/message'
if (route.path.startsWith('/about/lesson/')) return '/about/lesson'
if (route.path.startsWith('/about/news/')) return '/about/news'
if (route.path.startsWith('/alumnus/story/')) return '/alumnus/story'
if (route.path.startsWith('/internationalDegree/news/')) return '/internationalDegree/news'
return '/studyAbroad/news'
})
const data = ref({})
const mobileTabs = computed(() => {
if (route.path.startsWith('/about/')) return aboutTabs.filter(tab => tab.viewport !== 'desktop')
if (route.path.startsWith('/alumnus/')) return alumnusTabs.filter(tab => tab.viewport !== 'desktop')
if (route.path.startsWith('/internationalDegree/')) return degreeTabs
if (route.path.startsWith('/studyAbroad/')) return studyAbroadTabs.filter(tab => tab.viewport !== 'desktop')
return []
})
const formatDate = (value) => {
const date = new Date(value * 1000)
return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate()
const isActiveTab = (path) => route.path === path || route.path.startsWith(`${path}/`)
const getRelatedParams = () => {
if (relatedPath.value === '/internationalDegree/news') {
return getSectionNewsParams('internationalDegree', { page: 1, limit: 3 })
}
if (relatedPath.value === '/studyAbroad/news') {
return getSectionNewsParams('studyAbroad', { page: 1, limit: 3 })
}
const typeTag = relatedPath.value === '/about/message'
? 'img_text_school'
: relatedPath.value === '/about/lesson'
? 'article_publish_class'
: relatedPath.value === '/alumnus/story'
? 'article_alumni'
: 'article_news'
return { project_id: config.public.projectId, type_tag: typeTag, page: 1, limit: 3 }
}
// Fetch article detail
onMounted(async () => {
const { data: payload, error, status, refresh } = await useAsyncData(
() => `article-detail:${route.path}:${route.params.id}`,
async () => {
const detailResponse = await getArticleDetail(route.params.id)
const currentArticle = detailResponse.data || {}
let recommendations = []
try {
const res = await httpRequest.get(`/api/cms/api/v1/article/${route.params.id}/detail`)
data.value = res.data
emit('loaded', data.value)
const listResponse = await getArticleList(getRelatedParams())
recommendations = listResponse.data?.data || []
} catch (error) {
console.error('Failed to fetch article detail:', error)
console.error('Failed to load related articles:', error)
}
return { article: currentArticle, recommendations }
},
{ default: () => ({ article: {}, recommendations: [] }) },
)
const article = computed(() => payload.value?.article || {})
const relatedArticles = computed(() => payload.value?.recommendations || [])
const formatDate = (value) => {
if (!value) return ''
const numeric = Number(value)
const date = new Date(Number.isNaN(numeric) ? String(value).replace(/-/g, '/') : numeric < 1e12 ? numeric * 1000 : numeric)
if (Number.isNaN(date.getTime())) return ''
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
}
onMounted(() => {
if (article.value?.id) emit('loaded', article.value)
})
</script>
<style lang="scss">
.is-pc {
.article-detail {
width: 1200px;
.article-detail-page {
max-width: 1200px;
margin: 30px auto;
}
.article-back {
margin-bottom: 20px;
display: flex;
}
.article-detail-tabs { display: none; }
.article-detail {
width: 100%;
}
.article-back {
display: inline-flex;
align-items: center;
font-size: 16px;
gap: 4px;
margin-bottom: 20px;
padding: 4px 0;
border: 0;
border-radius: 0;
appearance: none;
color: #999;
cursor: pointer;
}
.article-time {
background: transparent;
font: inherit;
font-size: 16px;
line-height: 100%;
margin: 35px 0 16px;
color: #999999;
}
.article-title {
font-size: 32px;
font-weight: bold;
color: #141414;
border-bottom: 1px solid #e6e6e6;
line-height: 1.5;
cursor: pointer;
}
.article-back-icon {
display: block;
flex-shrink: 0;
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 2;
}
.article-status { padding: 24px 0; color: #666; }
.article-status button { margin-left: 12px; color: #aa1941; cursor: pointer; }
.article-time { margin: 35px 0 16px; color: #999; font-size: 16px; }
.article-title {
padding-bottom: 10px;
}
.article-content {
padding-top: 25px;
font-size: 14px;
line-height: 28px;
color: #333;
img {
max-width: 100%;
}
}
border-bottom: 1px solid #e6e6e6;
color: #141414;
font-size: 32px;
font-weight: 700;
}
.is-h5 {
.article-back {
.article-content { padding-top: 25px; color: #333; font-size: 14px; line-height: 28px; }
.article-content img, .article-content video { max-width: 100%; height: auto; }
.article-related { margin-top: 48px; }
.article-related h2 { margin-bottom: 20px; font-size: 22px; }
.article-related-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
.article-related-item { display: flex; flex-direction: column; gap: 10px; min-width: 0; color: #333; }
.article-related-item img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.article-related-more { display: block; width: max-content; margin: 24px auto; color: #999; }
@media (max-width: 900px) {
.article-detail-page { width: 100%; margin: 0 auto; }
.article-detail-tabs {
display: flex;
align-items: center;
font-size: 0.1rem;
cursor: pointer;
}
.article-time {
margin-top: 0.15rem;
font-size: 0.1rem;
color: #9b9b9b;
}
.article-title {
margin: 0.1rem 0;
padding: 0 0 0.1rem;
font-size: 0.14rem;
font-weight: 700;
color: #333;
border-bottom: 1px solid #e6e6e6;
}
.article-content {
font-size: 0.12rem;
line-height: 0.24rem;
img {
max-width: 100%;
}
gap: 20px;
overflow-x: auto;
padding: 0 15px;
background: #fff;
white-space: nowrap;
}
.article-detail-tabs a { padding: 10px 0; font-size: 14px; }
.article-detail-tabs a.active { border-bottom: 2px solid #aa1941; }
.article-detail { padding: 16px 15px; box-sizing: border-box; background: #fff; }
.article-back { margin-bottom: 12px; font-size: 12px; }
.article-back-icon { width: 16px; height: 16px; }
.article-time { margin: 15px 0 10px; font-size: 10px; }
.article-title { margin: 10px 0; padding-bottom: 10px; font-size: 16px; }
.article-content { padding-top: 10px; font-size: 12px; line-height: 24px; overflow-wrap: anywhere; }
.article-content table { display: block; max-width: 100%; overflow-x: auto; }
.article-related { margin: 15px; }
.article-related h2 { margin-bottom: 12px; font-size: 14px; }
.article-related-list { grid-template-columns: 1fr; gap: 12px; }
.article-related-item { flex-direction: row; align-items: center; gap: 10px; font-size: 12px; }
.article-related-item img { flex: 0 0 100px; width: 100px; height: 65px; }
.article-related-more { margin: 16px auto; font-size: 11px; }
}
</style>
......@@ -2,41 +2,44 @@
<div class="article-list">
<!-- 骨架屏加载状态 -->
<template v-if="loading">
<div v-for="index in page.size" :key="`skeleton-${index}`" class="article-skeleton">
<el-skeleton animated>
<template #template>
<div class="skeleton-item">
<el-skeleton-item variant="image" class="skeleton-image" />
<div v-for="index in limit" :key="`skeleton-${index}`" class="article-skeleton">
<div class="skeleton-item" aria-hidden="true">
<div class="skeleton-image skeleton-block"></div>
<div class="skeleton-content">
<el-skeleton-item variant="text" class="skeleton-date" />
<el-skeleton-item variant="h3" class="skeleton-title" />
<el-skeleton-item variant="text" class="skeleton-desc" />
<el-skeleton-item variant="text" class="skeleton-desc" />
<el-skeleton-item variant="text" class="skeleton-desc" />
<el-skeleton-item variant="text" class="skeleton-desc skeleton-desc-short" />
<div class="skeleton-date skeleton-block"></div>
<div class="skeleton-title skeleton-block"></div>
<div class="skeleton-desc skeleton-block"></div>
<div class="skeleton-desc skeleton-block"></div>
<div class="skeleton-desc skeleton-block"></div>
<div class="skeleton-desc skeleton-desc-short skeleton-block"></div>
</div>
</div>
</template>
</el-skeleton>
</div>
</template>
<!-- 实际内容 -->
<p v-else-if="error" class="article-list-status" role="status">内容暂时无法加载,请稍后重试</p>
<p v-else-if="!dataList.length" class="article-list-status">暂无文章</p>
<template v-else>
<ArticleListItem v-for="(item, index) in dataList" :data="item" v-bind="$attrs" :key="index"></ArticleListItem>
<ArticleListItem v-for="(item, index) in dataList" :data="item" v-bind="$attrs" :key="item.id || index"></ArticleListItem>
</template>
<!-- 分页 -->
<el-pagination
class="article-list-pagination"
background
layout="prev, pager, next"
:page-size="page.size"
:total="page.total"
v-model:current-page="page.currentPage"
@current-change="fetchList"
:hide-on-single-page="true">
</el-pagination>
<nav v-if="pageCount > 1" class="article-list-pagination" aria-label="文章分页">
<button type="button" :disabled="page.currentPage <= 1" @click="goToPage(page.currentPage - 1)">上一页</button>
<span class="pagination-mobile-count" aria-live="polite">{{ page.currentPage }} / {{ pageCount }}</span>
<template v-for="pageNumber in pageCount" :key="pageNumber">
<button
class="pagination-desktop-number"
type="button"
:aria-current="pageNumber === page.currentPage ? 'page' : undefined"
:class="{ active: pageNumber === page.currentPage }"
@click="goToPage(pageNumber)">
{{ pageNumber }}
</button>
</template>
<button type="button" :disabled="page.currentPage >= pageCount" @click="goToPage(page.currentPage + 1)">下一页</button>
</nav>
</div>
</template>
......@@ -48,16 +51,14 @@ const props = defineProps({
})
const route = useRoute()
const page = ref({ total: 0, size: props.limit, currentPage: 1 })
// Generate unique key for useAsyncData based on remote config and route
// Key should be static, not include page number
const dataKey = computed(() => {
const paramsStr = JSON.stringify(props.remote?.params || {})
const typeTag = props.remote?.params?.type_tag || 'default'
// Use route path and params to create unique key for each ArticleList instance
return `article-list-${route.path}-${typeTag}-${paramsStr}`
})
const page = ref({ currentPage: 1 })
// 参数变更先回到第一页,再由请求 key 统一触发加载。
watch(() => JSON.stringify(props.remote?.params || {}), () => {
page.value.currentPage = 1
}, { flush: 'sync' })
const dataKey = computed(() => `article-list-${route.path}-${JSON.stringify(props.remote?.params || {})}-${props.hasPagination ? page.value.currentPage : 'all'}-${props.limit}`)
// Fetch list data function
const fetchListData = async () => {
......@@ -73,7 +74,7 @@ const fetchListData = async () => {
// Add pagination params
if (props.hasPagination) {
requestParams.page = page.value.currentPage
requestParams.limit = page.value.size
requestParams.limit = props.limit
}
// Before request hook
......@@ -88,78 +89,72 @@ const fetchListData = async () => {
}
}
try {
const res = await httpRequest(requestParams)
const { data = [], total = 0 } = res.data || {}
const processedData = callback ? callback(data) : data
return { data: processedData, total }
} catch (error) {
console.error('Failed to fetch article list:', error)
return { data: [], total: 0 }
}
return { data: callback ? callback(data) : data, total }
}
// Use useAsyncData for SSR
const {
data: listData,
pending,
refresh,
error,
} = useAsyncData(() => dataKey.value, fetchListData, {
watch: [() => page.value.currentPage, () => props.remote?.params],
default: () => ({ data: [], total: 0 }),
})
const dataList = computed(() => listData.value?.data || [])
const loading = computed(() => pending.value)
const pageCount = computed(() => props.hasPagination ? Math.ceil((listData.value?.total || 0) / props.limit) : 0)
// Update page total when data changes
watch(
() => listData.value?.total,
(total) => {
if (total !== undefined) {
page.value.total = total
}
},
{ immediate: true }
)
// Fetch list function for pagination
const fetchList = async () => {
await refresh()
const goToPage = (pageNumber) => {
if (pageNumber < 1 || pageNumber > pageCount.value || pageNumber === page.value.currentPage) return
page.value.currentPage = pageNumber
}
// Watch remote params changes
watch(
() => props.remote?.params,
() => {
// Reset to first page when params change
page.value.currentPage = 1
},
{ deep: true }
)
// Watch remote object to refetch when it changes
watch(
() => props.remote,
() => {
if (props.remote?.httpRequest && props.remote?.params) {
page.value.currentPage = 1
refresh()
}
},
{ deep: true }
)
</script>
<style lang="scss">
.article-list-status { padding: 24px 0; text-align: center; color: #777; }
.article-list {
overflow: hidden;
}
.article-list-pagination {
display: flex;
align-items: center;
gap: 8px;
padding: 40px 0;
text-align: center;
justify-content: center;
button {
min-width: 34px;
height: 34px;
padding: 0 10px;
border: 1px solid #dcdfe6;
background: #fff;
color: #606266;
cursor: pointer;
}
button.active {
border-color: #aa1941;
background: #aa1941;
color: #fff;
}
button:disabled {
color: #c0c4cc;
cursor: not-allowed;
}
}
.pagination-mobile-count { display: none; }
@media (max-width: 900px) {
.pagination-desktop-number { display: none; }
.pagination-mobile-count { display: inline; }
}
.article-skeleton {
......@@ -180,6 +175,17 @@ watch(
display: block;
}
.skeleton-block {
background: linear-gradient(90deg, #f2f3f5 25%, #e8eaed 37%, #f2f3f5 63%);
background-size: 400% 100%;
animation: article-skeleton-shimmer 1.4s ease infinite;
}
@keyframes article-skeleton-shimmer {
0% { background-position: 100% 0; }
100% { background-position: 0 0; }
}
.skeleton-content {
flex: 1;
display: flex;
......
......@@ -2,7 +2,7 @@
<div class="article-item">
<AppLink :data="data" v-bind="$attrs">
<div class="article-item-inner">
<el-image :src="data.web_img_uri" class="article-item-pic" lazy fit="cover" />
<img :src="data.web_img_uri" class="article-item-pic" loading="lazy" alt="" />
<div class="article-item-content">
<div class="article-item-content__date">{{ formatDate(data.start_time) }}</div>
<div class="article-item-content__title">{{ data.title }}</div>
......@@ -33,7 +33,8 @@ const formatDate = (value) => {
</script>
<style lang="scss">
.is-pc {
@media (min-width: 901px) {
.article-list {
.article-item {
border-bottom: 1px solid #e6e6e6;
padding: 36px 0;
......@@ -96,40 +97,43 @@ const formatDate = (value) => {
-webkit-box-orient: vertical;
}
}
.is-h5 {
}
@media (max-width: 900px) {
.article-list {
.article-list-inner {
display: flex;
}
.article-item {
margin: 0.2rem;
margin: 20px;
background-color: #fff;
}
.article-item-pic {
display: block;
width: 100%;
// height: fit-content;
// height: 2.99rem;
// height: 299px;
// object-fit: cover;
}
.article-item-content {
padding: 0.18rem;
padding: 18px;
}
.article-item-content__date {
font-size: 0.1rem;
font-size: 10px;
color: #aa1941;
line-height: 0.16rem;
line-height: 16px;
}
.article-item-content__title {
margin-top: 0.1rem;
font-size: 0.14rem;
margin-top: 10px;
font-size: 14px;
font-weight: 500;
color: #333;
line-height: 0.24rem;
line-height: 24px;
}
.article-item-content__text {
margin-top: 0.2rem;
font-size: 0.1rem;
line-height: 0.2rem;
margin-top: 20px;
font-size: 10px;
line-height: 20px;
}
}
}
</style>
<template>
<Swiper
class="mobile-carousel"
:modules="modules"
:autoplay="autoplay > 0 ? { delay: autoplay, disableOnInteraction: false } : false"
:direction="vertical ? 'vertical' : 'horizontal'"
:pagination="showIndicators ? { clickable: true } : false"
:rewind="rewind"
:watch-overflow="true"
>
<slot />
</Swiper>
</template>
<script setup>
import { Swiper } from 'swiper/vue'
import { Autoplay, Pagination } from 'swiper/modules'
defineProps({
autoplay: { type: Number, default: 0 },
vertical: { type: Boolean, default: false },
showIndicators: { type: Boolean, default: true },
rewind: { type: Boolean, default: true },
})
const modules = [Autoplay, Pagination]
</script>
<style scoped>
.mobile-carousel { width: 100%; --swiper-pagination-color: #fff; --swiper-pagination-bullet-inactive-color: #fff; }
:deep(.swiper-slide) { height: auto; }
:deep(.swiper-pagination-bullet) { opacity: 1; }
</style>
<template>
<AppCard :title="isEn ? 'Updates' : '最新动态'" class="news-card">
<AppCard title="最新动态" class="news-card">
<template #header-aside><nuxt-link :to="baseUrl">查看更多+</nuxt-link></template>
<p v-if="loadError" class="news-load-error" role="status">内容暂时无法加载,请稍后重试</p>
<div class="content-box" v-if="Object.keys(listData.first).length">
<div class="news-left">
<AppLink :data="listData.first" :to="`${baseUrl}/${listData.first.id}`">
......@@ -25,21 +26,33 @@
</template>
<script setup>
import httpRequest from '@/utils/httpRequest'
import { getArticleList } from '~/api'
import AppCard from '~/components/AppCard.vue'
import AppLink from '~/components/AppLink.vue'
const props = defineProps({
baseUrl: { type: String, default: '/about/news' },
params: Object,
})
const { locale } = useI18n()
const config = useRuntimeConfig()
const { projectId } = useRuntimeConfig().public
const route = useRoute()
const data = ref([])
const isEn = computed(() => locale.value === 'en-US')
const { data, error: loadError } = await useAsyncData(`news-card-${route.fullPath}-${JSON.stringify(props.params || {})}`, async () => {
const params = props.params || {
project_id: projectId,
type_tag: 'article_news_hot',
page: 1,
limit: 4,
}
const response = await getArticleList(params, {
headers: { 'Content-Type': 'application/json' },
})
return response.data.data || []
}, { dedupe: 'defer' })
const listData = computed(() => {
const [first = {}, ...list] = data.value
const [first = {}, ...list] = data.value || []
return { first, list: list || [] }
})
......@@ -47,30 +60,13 @@ const formatDate = (value) => {
const date = new Date(value * 1000)
return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate()
}
// 获取数据
onMounted(async () => {
const params = props.params || {
project_id: config.public.projectId,
type_tag: 'article_news_hot',
page: 1,
limit: 4,
}
try {
const res = await httpRequest.get('/api/cms/api/v1/articles', {
params,
headers: { 'Content-Type': 'application/json' },
})
data.value = res.data.data || []
} catch (error) {
console.error('Failed to fetch news:', error)
data.value = []
}
})
</script>
<style lang="scss" scoped>
.is-pc {
.news-load-error { padding: 20px 0; font-size: 14px; color: #666; }
@media (min-width: 901px) {
.news-card {
max-width: 1200px;
margin: 0 auto;
......@@ -156,10 +152,12 @@ onMounted(async () => {
}
}
}
.is-h5 {
@media (max-width: 900px) {
.news-card {
.tit { display: none; }
.content-box {
// height: 2.18rem;
// height: 218px;
position: relative;
img {
width: 100%;
......@@ -175,13 +173,13 @@ onMounted(async () => {
bottom: 0;
left: 0;
background: rgba(8, 8, 8, 0.45);
padding-bottom: 0.14rem;
padding-bottom: 14px;
.con-txt {
font-size: 0.12rem;
font-size: 12px;
font-weight: bold;
line-height: 0.17rem;
line-height: 17px;
color: #ffffff;
padding: 0.14rem 0.32rem 0 0.19rem;
padding: 14px 32px 0 19px;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
......@@ -191,27 +189,28 @@ onMounted(async () => {
}
.news-right {
li {
margin-top: 0.1rem;
padding: 0.13rem 0.14rem 0.11rem 0.12rem;
margin-top: 10px;
padding: 13px 14px 11px 12px;
background: #fff;
.time {
font-size: 0.1rem;
font-size: 10px;
line-height: 100%;
color: #ab0a3d;
}
.news-r-title {
font-size: 0.14rem;
font-size: 14px;
font-weight: bold;
color: #333333;
margin-top: 0.1rem;
line-height: 0.21rem;
margin-top: 10px;
line-height: 21px;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.del {
font-size: 0.11rem;
line-height: 0.2rem;
font-size: 11px;
line-height: 20px;
color: #666666;
overflow: hidden;
display: -webkit-box;
......
......@@ -10,21 +10,21 @@
</div>
<div class="form">
<div class="li">
<el-input v-model="form.name" placeholder="姓名"></el-input>
<input v-model="form.name" class="presence-input" type="text" placeholder="姓名" />
</div>
<div class="li">
<el-input v-model="f2" placeholder="性别"></el-input>
<input v-model="gender" class="presence-input" type="text" placeholder="性别" />
</div>
<div class="li">
<el-input v-model="form.phone" placeholder="手机"></el-input>
<input v-model="form.phone" class="presence-input" type="tel" placeholder="手机" />
</div>
<div class="li">
<el-input v-model="f3" placeholder="请告诉我您来自"></el-input>
<input v-model="source" class="presence-input" type="text" placeholder="请告诉我您来自" />
</div>
<div class="li">
<el-input v-model="f1" placeholder="请选择您要报名的证书"></el-input>
<input v-model="certificate" class="presence-input" type="text" placeholder="请选择您要报名的证书" />
</div>
<div class="btn" @click="submit">立即提交</div>
<button class="btn" type="button" :disabled="submitting" @click="handleSubmit">{{ submitting ? '提交中…' : '立即提交' }}</button>
</div>
</div>
</div>
......@@ -33,49 +33,21 @@
</template>
<script setup>
import { ref, reactive, getCurrentInstance } from 'vue'
import { submitApplicationForm } from '@/api/index'
import { notify } from '@/utils/notify'
// 表单字段
const f1 = ref('')
const f2 = ref('')
const f3 = ref('')
const certificate = ref('')
const gender = ref('')
const source = ref('')
const { form, errors, submitting, submit } = useEnrollmentForm({
name: '', phone: '', project_id: 5009, comment: '',
}, { draftKey: 'certificate-consultation' })
const form = reactive({
name: '',
phone: '',
project_id: 5009,
})
const { proxy } = getCurrentInstance()
const submit = () => {
const findNull = Object.values(form).findIndex((item) => item === '')
if (findNull !== -1) {
proxy?.$message('请完善信息')
return
} else {
const MOBILE_REG = /^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/
if (!MOBILE_REG.test(form.phone)) {
proxy?.$message('手机号格式错误')
return
}
}
const payload = {
...form,
comment: `#姓名:${f1.value};#性别:${f2.value};#请告诉我您来自:${f3.value}`,
}
submitApplicationForm(payload)
.then((res) => {
if (res && res.status === 200) {
proxy?.$message({ type: 'success', message: '提交成功' })
} else {
proxy?.$message.error('提交失败')
}
})
.catch(() => {})
const handleSubmit = async () => {
if (submitting.value) return
form.comment = `#姓名:${form.name.trim()};#报名证书:${certificate.value.trim()};#性别:${gender.value.trim()};#请告诉我您来自:${source.value.trim()}`
const result = await submit()
if (result) notify('提交成功', 'success')
else notify(Object.values(errors).find(Boolean) || '提交失败', 'error')
}
</script>
......@@ -126,8 +98,19 @@ const submit = () => {
margin-right: 116px;
.li {
margin-bottom: 20px;
.presence-input {
box-sizing: border-box;
width: 100%;
height: 44px;
padding: 0 12px;
border: 1px solid #dcdfe6;
border-radius: 0;
outline-color: #aa1941;
}
}
.btn {
border: 0;
font-family: inherit;
font-size: 18px;
line-height: 32px;
color: #ffffff;
......@@ -142,3 +125,22 @@ const submit = () => {
}
}
</style>
<style lang="scss" scoped>
@media (max-width: 900px) {
.presence-content-box {
padding: 30px 16px 0;
.content-main {
padding-top: 0;
.form-box {
width: 100%; height: auto; flex-direction: column; padding: 20px; border-radius: 12px;
.title-box { width: 100%; height: auto; margin-top: 0; background: transparent; box-shadow: none; }
.title-box .title-content { position: static; transform: none; width: 100%; height: auto; padding: 20px; }
.title-box .bt { margin-top: 0; }
.form { width: 100%; padding: 24px 16px; margin: 20px 0 0; }
.form .btn { width: 100%; min-height: 44px; line-height: 44px; }
}
}
}
}
</style>
<template>
<AppCard :title="title" style="margin-top: 50px">
<template #header-aside><nuxt-link :to="baseUrl" v-if="baseUrl">查看更多+</nuxt-link></template>
<div :class="className ? className : 'project-list'">
<div class="project-item" v-for="(item, index) in list" :key="index">
<!-- <AppLink :data="item"><img :src="item.imgUrl"/></AppLink> -->
<div
class="project-item-pic"
:style="
isEn
? `background:url(${item.imgUrlEn}) no-repeat center / 100% 100%;`
: `background:url(${item.imgUrl}) no-repeat center / 100% 100%; `
">
<!-- <img :src="item.imgUrl" /> -->
<AppCard :title="title" class="project-card">
<template #header-aside>
<NuxtLink v-if="baseUrl" :to="baseUrl">查看更多+</NuxtLink>
</template>
<div :class="className || 'project-list'">
<div v-for="(item, index) in list" :key="index" class="project-item">
<div class="project-item-pc">
<div class="project-item-pic" :style="{ backgroundImage: `url(${item.imgUrl})` }">
<ul class="tools">
<li @click="handleSubmit(item)">{{ isEn ? 'Register' : '我要报名' }}</li>
<li>
<AppLink :data="item">{{ isEn ? 'Learn more' : '了解详情' }}</AppLink>
</li>
<li @click="handleSubmit(item)">我要报名</li>
<li><AppLink :data="item">了解详情</AppLink></li>
</ul>
</div>
<!-- <div class="tools">
<el-button type="primary" @click="handleSubmit(item)">我要报名</el-button>
<AppLink :data="item" style="margin-left:20px;">
<el-button plain type="primary">了解详情</el-button>
</AppLink>
</div> -->
</div>
<div class="project-item-h5">
<AppLink :data="item"><img :src="item.imgUrl" alt="" /></AppLink>
</div>
</div>
</div>
</AppCard>
</template>
<script setup>
defineOptions({
name: 'ProjectCard',
})
import AppCard from '~/components/AppCard.vue'
import AppLink from '~/components/AppLink.vue'
const props = defineProps({
defineOptions({ name: 'ProjectCard' })
defineProps({
list: { type: Array, default: () => [] },
title: String,
baseUrl: String,
className: String,
})
const { locale } = useI18n()
const isEn = computed(() => locale.value === 'en-US')
const handleSubmit = (data) => {
if (data.onClick) {
data.onClick()
} else {
const formEl = document.querySelector('#form')
if (!formEl) return
window.scrollTo(0, formEl.offsetTop)
const handleSubmit = (item) => {
if (item.onClick) {
item.onClick()
return
}
const form = document.querySelector('#form')
if (form) window.scrollTo(0, form.offsetTop)
}
</script>
<style lang="scss" scoped>
.project-list {
@media (min-width: 901px) {
.project-card {
margin-top: 50px;
}
.project-item-h5 {
display: none;
}
.project-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
row-gap: 20px;
column-gap: 20px;
}
.project-list2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 37px;
column-gap: 37px;
}
.project-item {
background-color: #fff;
&:hover {
box-shadow: 0px 3px 24px rgba(0, 0, 0, 0.3);
}
}
.project-item-pic {
height: 360px;
overflow: hidden;
position: relative;
background-repeat: no-repeat;
background-position: center;
background-size: 100% 100%;
}
img {
width: 100%;
}
&:hover {
box-shadow: 0px 3px 24px rgba(0, 0, 0, 0.3);
.project-list2 .project-item-pic {
height: 230px;
}
.tools {
position: absolute;
bottom: 20px;
......@@ -90,65 +106,24 @@ const handleSubmit = (data) => {
color: #4d4d4d;
text-align: center;
cursor: pointer;
// &:hover {
// font-weight: 500;
// color: #333333;
// }
}
li:first-child {
color: #af1c40;
}
li + li {
border-left: 1px solid #c9c9c9;
}
}
}
}
.project-list2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 37px;
column-gap: 37px;
.project-item {
background-color: #fff;
.project-item-pic {
height: 230px;
overflow: hidden;
position: relative;
&:first-child { color: #af1c40; }
& + li { border-left: 1px solid #c9c9c9; }
}
img {
width: 100%;
}
&:hover {
box-shadow: 0px 3px 24px rgba(0, 0, 0, 0.3);
}
.tools {
position: absolute;
.project-list2 .tools {
bottom: 15px;
right: 0;
display: flex;
justify-content: center;
left: auto;
width: 350px;
li {
flex: 1;
font-size: 16px;
font-weight: 400;
color: #4d4d4d;
text-align: center;
cursor: pointer;
// &:hover {
// font-weight: 500;
// color: #333333;
// }
}
li:first-child {
color: #af1c40;
}
li + li {
border-left: 1px solid #c9c9c9;
}
}
}
}
@media (max-width: 900px) {
.project-item-pc { display: none; }
.project-item { margin-top: 10px; }
.project-item-h5 img { width: 100%; }
}
</style>
# COMPONENTS
**This directory is not required, you can delete it if you don't want to use it.**
The components directory contains your Vue.js Components.
_Nuxt.js doesn't supercharge these components._
<template>
<div v-if="enabled" :class="mobile ? 'mobile-only' : 'desktop-only'">
<slot />
</div>
</template>
<script setup>
const props = defineProps({ mobile: { type: Boolean, default: false } })
// Both variants have matching SSR/hydration HTML. CSS chooses the first paint.
const enabled = ref(true)
let query
const update = () => { enabled.value = query.matches === props.mobile }
onMounted(() => {
query = window.matchMedia('(max-width: 900px)')
update()
query.addEventListener('change', update)
})
onBeforeUnmount(() => query?.removeEventListener('change', update))
</script>
<template>
<nav>
<ul class="app-tab-nav">
<li
v-for="(item, index) in navItems"
:key="index"
:class="{ 'is-active': item.value === active }"
@click="tabClick(item)"
>
<div class="item_name">{{ item.name }}</div>
</li>
</ul>
</nav>
</template>
<script>
export default {
props: {
active: { type: String },
list: { type: Array, default: () => [] },
section: { type: String, default: '' }
},
computed: {
navItems() {
if (this.list.length) return this.list
const items = this.section === 'about' ? useAboutNavList()
: this.section === 'alumnus' ? useAlumnusNavList() : []
return items.filter(item => item.viewport !== 'desktop')
.map(item => ({ ...item, name: item.mobileName || item.name }))
}
},
methods: {
tabClick(item) {
this.$router.push(item.path)
}
}
}
</script>
<style lang="scss" scoped>
.app-tab-nav {
display: flex;
overflow-x: scroll;
&::-webkit-scrollbar {
display: none;
}
li {
margin-left: 30px;
font-size: 14px;
line-height: 38px;
color: #333;
.item_name {
white-space: nowrap;
cursor: pointer;
}
&.is-active {
border-bottom: 2px solid #aa1941;
}
}
}
</style>
<template>
<AppCard :title="isEn ? 'Excellent Teachers' : '优秀师资'">
<AppCard title="优秀师资">
<Swiper
ref="mySwiper"
:autoplay="{ delay: 3000 }"
:loop="true"
:slides-per-view="'auto'"
:space-between="20"
:modules="[Autoplay]"
v-bind="swiperOptions"
class="teacher-list">
<SwiperSlide v-for="(item, index) in list" :key="index" class="teacher-item">
<div class="teacher-item-inner">
......@@ -20,22 +18,19 @@
</Swiper>
</AppCard>
</template>
<script setup>
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Autoplay } from 'swiper/modules'
import AppCard from '~/components/AppCard.vue'
defineOptions({
name: 'TeacherCard',
})
defineOptions({ name: 'TeacherCard' })
defineProps({
list: {
type: Array,
default: () => [],
},
list: { type: Array, default: () => [] },
})
const lang = useCookie('lang')
const isEn = computed(() => lang.value === 'en-US')
const swiperOptions = { autoplay: { delay: 3000 }, rewind: true, slidesPerView: 'auto', spaceBetween: 10, breakpoints: { 901: { spaceBetween: 20 } } }
</script>
<style lang="scss" scoped>
......@@ -43,7 +38,9 @@ const isEn = computed(() => lang.value === 'en-US')
margin-bottom: 60px;
}
.teacher-item {
@media (min-width: 901px) {
.teacher-item {
width: 224px;
height: 430px;
background: rgba(255, 255, 255, 0.39);
......@@ -53,60 +50,95 @@ const isEn = computed(() => lang.value === 'en-US')
&:hover {
box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.1);
}
}
}
.teacher-item-inner {
.teacher-item-inner {
position: relative;
height: 200px;
padding: 13px 12px 0 12px;
padding: 13px 12px 0;
img {
width: 200px;
height: 100%;
object-fit: cover;
}
p {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 0 20px;
height: 34px;
font-size: 16px;
line-height: 34px;
color: #ffffff;
background: rgba(0, 0, 0, 0.39);
}
}
.teacher-item-cover {
.teacher-item-cover {
text-align: center;
margin-top: 21px;
// position: absolute;
// left: 0;
// top: 0;
// right: 0;
// bottom: 0;
// background: #a81840;
// color: #fff;
// padding: 10px;
// transform: translateY(100%);
// transition: all 0.3s;
h3 {
font-size: 16px;
font-weight: 500;
color: #000000;
color: #000;
}
p {
margin-top: 4px;
text-align: center;
font-size: 14px;
color: #333333;
color: #333;
}
.desc {
font-size: 14px;
margin-top: 20px;
color: #575964;
padding: 0 5px 0 5px;
padding: 0 5px;
}
}
}
@media (max-width: 900px) {
.teacher-item {
box-sizing: border-box;
padding: 12px 10px 23px;
width: 168px;
height: 340px;
background: rgba(255, 255, 255, 0.39);
box-shadow: 0px 1px 11px rgba(0, 0, 0, 0.1);
}
.teacher-item-inner {
display: flex;
justify-content: center;
img {
width: 148px;
height: 165px;
object-fit: cover;
}
}
.teacher-item-cover {
display: flex;
flex-direction: column;
align-items: center;
h3 {
margin-top: 13px;
font-size: 14px;
color: #000;
font-weight: bold;
}
p {
margin-top: 5px;
font-size: 12px;
color: #333;
white-space: nowrap;
}
.desc {
align-self: stretch;
margin-top: 17px;
font-size: 11px;
font-weight: 400;
color: #575964;
text-align: left;
}
}
}
</style>
......@@ -80,7 +80,7 @@ onBeforeUnmount(() => {
</div>
</header>
<!-- ================= 平台定位 ================= -->
<!-- ================= Positioning ================= -->
<section id="positioning" class="section intro">
<div class="container">
<div class="kicker reveal">Positioning</div>
......@@ -380,7 +380,7 @@ onBeforeUnmount(() => {
}
}
/* ============ Intro(平台定位) ============ */
/* ============ Intro(Positioning) ============ */
.intro {
padding: 88px 0 92px;
background:
......
差异被折叠。
<template>
<div class="home">
<HomeBanner />
<HomeMenu />
<HomeSchool />
<HomePersonal />
<HomeEnterprise />
<HomeHonor />
<HomePartner />
<HomeProjectNotice />
</div>
</template>
<style lang="scss">
.home {
background-color: #fff;
}
</style>
<template>
<div
style="
padding: 100px 0 40px;
background: url(https://webapp-pub.ezijing.com/www/pc/240531/lx-bg.png);
background-size: cover;
">
<AppCard title="$t('home.abroad.title')" titleAlign="center" style="margin: 0 auto">
<template #title>
<nuxt-link to="/studyAbroad">{{ $t('home.abroad.title') }}</nuxt-link>
</template>
<div class="box-row">
<div class="box-l">
<div class="box-red_title">学历项目</div>
<div class="box-l_master" v-for="(item, index) in abroadList" :key="index">
<div class="box-col__title" style="margin-top: 35px">{{ item.title }}</div>
<div class="box-l_list">
<div class="box-l_item" v-for="(cItem, cIndex) in item.children" :key="cIndex">
<a :href="cItem.href" target="_blank">
<div class="box-item_title">{{ cItem.title }}</div>
<img :src="cItem.img" class="box-item_logo" />
<div class="box-item_btns">
<span v-for="(btn, btnIndex) in cItem.btn" :key="btnIndex">
<div class="box-item_btn" v-html="btn.name"></div>
</span>
</div>
</a>
</div>
</div>
</div>
</div>
<div class="box-r">
<div class="box-red_title">非学历项目</div>
<div class="box-block" style="margin: 75px 0 30px">
<img src="https://webapp-pub.ezijing.com/www/pc/fxl-img1.png" />
<div style="margin-left: 10px" class="btn">交换生项目</div>
</div>
<div class="box-block">
<img src="https://webapp-pub.ezijing.com/www/pc/fxl-img2.png" />
<div style="margin-left: 10px" class="btn">教师访学项目</div>
</div>
</div>
</div>
</AppCard>
</div>
</template>
<script setup>
const abroadList = [
{
title: '硕士',
children: [
{
title: '雪城大学',
href: 'https://syracuse-plus.ezijing.com/',
img: 'https://webapp-pub.ezijing.com/www/pc/918/m1.png',
btn: [{ name: '计算机工程硕士' }, { name: '计算机科学硕士' }, { name: '电气工程硕士' }],
},
{
title: '斯蒂文斯理工学院',
href: 'https://sit-plus.ezijing.com/',
img: 'https://webapp-pub.ezijing.com/www/pc/918/m3.png',
btn: [{ name: '工程管理硕士' }],
},
{
title: '纽约州立大学石溪分校',
href: 'https://sbu-plus.ezijing.com/',
img: 'https://webapp-pub.ezijing.com/www/pc/918/m2.png',
btn: [{ name: '金融学硕士' }],
},
{
title: '德保罗大学',
href: 'https://depaul-plus.ezijing.com/',
img: 'https://webapp-pub.ezijing.com/www/pc/918/m4.png',
btn: [{ name: '金融学硕士' }, { name: '北理工金融学硕士' }],
},
],
},
]
</script>
<style lang="scss" scoped>
.box-row {
display: flex;
justify-content: space-between;
.box-l {
width: 810px;
}
.box-r {
width: 300px;
.box-block {
width: 300px;
height: 106px;
// background: #ffffff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px 12px 12px 12px;
img {
width: 150px;
height: 80px;
object-fit: cover;
}
&:hover {
background-color: #aa1941;
.btn {
color: #fff;
}
}
.btn {
min-width: 120px;
// line-height: 30px;
padding: 6px 0;
text-align: center;
font-size: 12px;
color: #333333;
// border: 1px solid #d5d5d5;
cursor: pointer;
&:hover {
// background-color: #aa1941;
// border: 1px solid #aa1941;
color: #fff;
}
}
}
}
.box-red_title {
line-height: 35px;
background-color: #aa1941;
font-size: 18px;
text-align: center;
color: #fff;
font-weight: bold;
}
.box-l_list {
display: flex;
flex-wrap: wrap;
gap: 30px;
}
.box-l_item {
margin-bottom: 30px;
// min-width: 180px;
// height: 230px;
// background-color: #fff;
padding: 14px 15px 0;
border-radius: 12px 12px 12px 12px;
&:hover {
background: #aa1941;
.box-item_title {
color: #fff;
}
.box-item_btn {
color: #fff !important;
}
}
.box-item_title {
font-size: 15px;
font-weight: bold;
color: #aa1941;
line-height: 100%;
margin-bottom: 10px;
text-align: center;
}
.box-item_btns {
width: 150px;
// border-top: 1px dashed #f47524;
// margin-top: 10px;
padding-top: 10px;
padding-bottom: 20px;
// height: 140px;
display: flex;
flex-wrap: wrap;
justify-content: center;
// align-items: center;
.box-item_btn {
min-width: 120px;
// line-height: 30px;
padding: 6px 0;
// margin-bottom: 8px;
text-align: center;
font-size: 12px;
color: #333333;
// border: 1px solid #d5d5d5;
cursor: pointer;
&:hover {
// background-color: #aa1941;
// border: 1px solid #aa1941;
color: #fff;
}
}
}
}
}
.box-col__title {
padding-left: 10px;
margin-bottom: 26px;
font-size: 16px;
line-height: 1;
border-left: 1px solid var(--main-color);
}
.box-item_logo {
width: 150px;
height: 80px;
object-fit: cover;
}
</style>
<template>
<AppCard :title="$t('menu.story')" class="home-alumni" titleAlign="center">
<AppCard title="校友故事" class="home-alumni" titleAlign="center">
<template #title>
<nuxt-link to="/alumnus/story">{{ $t('menu.story') }}</nuxt-link>
<nuxt-link to="/alumnus/story">校友故事</nuxt-link>
</template>
<template #header-aside>
<nuxt-link to="/alumnus/story">{{ $t('viewMore') }}</nuxt-link>
<nuxt-link to="/alumnus/story">查看更多+</nuxt-link>
</template>
<AppLink v-if="firstItem" :data="firstItem" :to="`/alumnus/story/${firstItem.id}`">
<div class="alumni_content">
......@@ -18,20 +18,22 @@
</AppCard>
</template>
<script setup>
import httpRequest from '@/utils/httpRequest'
import { getArticleList } from '~/api'
const config = useRuntimeConfig()
const listData = ref([])
const homeContent = inject('homeContent', null)
const fetchedList = ref([])
const listData = computed(() => homeContent?.value?.storyList || fetchedList.value)
const firstItem = computed(() => listData.value[0])
const fetchList = async () => {
const params = { project_id: config.public.projectId, type_tag: 'article_alumni' }
const res = await httpRequest.get('/api/cms/api/v1/articles', { params })
listData.value = res.data.data || []
const res = await getArticleList(params)
fetchedList.value = res.data.data || []
}
onMounted(() => {
fetchList()
if (!homeContent) fetchList()
})
</script>
<style lang="scss">
......
......@@ -3,7 +3,7 @@
<Swiper ref="mySwiper" v-bind="swiperOption">
<SwiperSlide v-for="(item, index) in data" :key="index">
<template v-if="item.uri">
<a :href="item.uri" target="_black">
<a :href="item.uri" target="_blank" rel="noopener noreferrer">
<img :src="item.web_img_uri" />
</a>
</template>
......@@ -19,7 +19,6 @@
<script setup>
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Navigation, Autoplay } from 'swiper/modules'
import { getAdvertisementList } from '@/api'
const config = useRuntimeConfig()
......@@ -45,11 +44,8 @@ const swiperOption = {
// { web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/next/banner3.jpg' },
// ]
// 使用 useAsyncData 进行服务端渲染
const { data } = await useAsyncData('banner-ads', async () => {
const params = { project_id: config.public.projectId, type_tag: 'ad_banner' }
const res = await getAdvertisementList(params)
return res.data.data || []
})
const content = inject('homeContent')
const data = computed(() => content.value?.bannerList || [])
</script>
<style lang="scss">
......
<template>
<AppCard titleAlign="center">
<template #title>
<nuxt-link to="/digital/yx_1">产教融合</nuxt-link>
</template>
<div class="row">
<div class="col" v-for="item in list" :key="item.title">
<div class="title">
<h2 v-html="item.title"></h2>
<p>{{ item.desc }}</p>
</div>
<ul>
<li class="item" v-for="item in item.children" :key="item.title" :style="item.style">
<AppLink :to="item.path">
<div class="item-inner">
<img :src="item.icon" class="img" />
<img :src="item.hoverIcon" class="hover-img" />
{{ item.title }}
</div>
</AppLink>
</li>
</ul>
</div>
</div>
<div class="col3" v-for="(item, index) in newList2" :key="index">
<div class="left">{{ item.title }}</div>
<div class="right">
<template v-for="(cItem, cIndex) in item.children" :key="cIndex">
<AppLink :to="cItem.path">
<div class="item">
<img :src="cItem.icon" class="img" />
<img :src="cItem.hoverIcon" class="imga" />
<div class="name">{{ cItem.name }}</div>
</div>
</AppLink>
</template>
</div>
</div>
</AppCard>
</template>
<script setup>
const list = [
{
title: '院&nbsp校',
desc: '数字人才核心能力培养解决方案',
children: [
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_4.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_4_hover.png',
title: '数字技能实训平台',
path: 'digital/yx_1',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_7.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_7_hover.png',
title: '数字经济实验室',
path: '',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_3.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_3_hover.png',
title: '专业数字化改造提升',
path: 'digital/yx_3',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_1.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_1_hover.png',
title: '数字经济新商科产业学院',
path: 'digital/yx_2',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_6.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_6_hover.png',
title: '未来技术产业学院',
path: 'digital/yx_5',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_5.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/yx_5_hover.png',
title: '1+X金融产品数字化营销',
path: 'https://x.ezijing.com',
},
],
},
{
title: '政&nbsp;府',
desc: '数字技能公共实训基地',
children: [
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_1.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_1_hover.png',
title: '1个数字展区',
path: 'digital/zf_1',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_2.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_2_hover.png',
title: '3个技能实训区',
path: 'digital/zf_1',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_3.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_3_hover.png',
title: '8个实训中心',
path: 'digital/zf_1',
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_4.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/digital/icon/zf_4_hover.png',
title: '5个运营中心',
path: 'digital/zf_1',
},
],
},
]
const newList2 = [
{
title: '职业技能大赛',
children: [
{
name: '国赛',
icon: 'https://webapp-pub.ezijing.com/www/pc/240531/ds-icon1.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/240531/ds-icon1a.png',
path: 'digital/ds',
},
{
name: '省赛',
icon: 'https://webapp-pub.ezijing.com/www/pc/240531/ds-icon2.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/240531/ds-icon2a.png',
path: 'digital/ds',
},
{
name: '行业赛',
icon: 'https://webapp-pub.ezijing.com/www/pc/240531/ds-icon3.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/240531/ds-icon3a.png',
path: 'digital/ds',
},
],
},
{
title: '数字教材',
children: [
{
name: '数字教材编辑器',
icon: 'https://webapp-pub.ezijing.com/www/pc/240531/jc-icon1.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/240531/jc-icon1a.png',
path: 'digital/sz',
},
{
name: '数字教材出版',
icon: 'https://webapp-pub.ezijing.com/www/pc/240531/jc-icon2.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/240531/jc-icon2a.png',
path: 'digital/sz',
},
{
name: '数智化教学',
icon: 'https://webapp-pub.ezijing.com/www/pc/240531/jc-icon3.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/240531/jc-icon3a.png',
path: 'digital/sz',
},
],
},
]
</script>
<style lang="scss" scoped>
.row {
display: flex;
column-gap: 30px;
}
.col {
flex: 1;
background-color: #fff;
box-shadow: 0px 3px 12px 1px rgba(0, 0, 0, 0.16);
&:first-child {
flex: 0 0 694px;
}
.title {
padding: 25px 0;
text-align: center;
background-color: var(--main-color);
h2 {
font-size: 26px;
font-family: Source Han Sans CN-Bold, Source Han Sans CN;
font-weight: bold;
line-height: 1;
color: #ffffff;
}
p {
margin-top: 12px;
font-size: 16px;
font-family: Source Han Sans CN-Regular, Source Han Sans CN;
line-height: 1;
color: #ffffff;
}
}
ul {
margin: 42px 30px;
display: flex;
flex-wrap: wrap;
gap: 20px;
}
li {
// border: 1px solid #d5d5d5;
width: 198px;
height: 128px;
font-size: 16px;
color: #666;
.hover-img {
display: none;
}
&:hover {
.img {
display: none;
}
.hover-img {
display: inline;
}
color: #fff;
background-color: var(--main-color);
border-color: var(--main-color);
}
}
.item-inner {
height: 128px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
margin-bottom: 14px;
}
}
}
.col3 {
display: flex;
background-color: #fff;
margin-top: 30px;
box-shadow: 0px 3px 12px 1px rgba(0, 0, 0, 0.16);
.left {
min-width: 259px;
line-height: 104px;
background: #aa1941;
font-weight: bold;
font-size: 26px;
color: #ffffff;
text-align: center;
}
.right {
display: flex;
justify-content: space-around;
align-items: center;
flex: 1;
.item {
display: flex;
align-items: center;
width: 220px;
line-height: 64px;
// border: 1px solid #d5d5d5;
&:hover {
background-color: #aa1941;
.name {
color: #fff;
}
.img {
display: none;
}
.imga {
display: block;
}
}
img {
margin-left: 30px;
}
.imga {
display: none;
}
.name {
margin-left: 5px;
font-size: 16px;
color: #6c6c6c;
letter-spacing: 2px;
}
}
}
}
</style>
<template>
<div style="background-color: #fff; overflow: hidden">
<AppCard title="$t('home.degree.title')" titleAlign="center">
<template #title>
<nuxt-link to="/internationalDegree">{{ $t('home.degree.title') }}</nuxt-link>
</template>
<div class="box-row">
<div class="box-col" v-for="(col, index) in degreeList" :key="index">
<div class="box-col__title">{{ col.title }}</div>
<ul class="box-wrap">
<li v-for="(item, index) in col.children" :key="index">
<AppLink :data="item">
<div class="box-inner">
<img :src="item.img" class="box-pic" />
<div class="box-desc" v-html="item.desc"></div>
</div>
</AppLink>
</li>
</ul>
</div>
</div>
</AppCard>
</div>
</template>
<script setup>
const { t } = useI18n()
const degreeList = [
{
title: t('home.degree.tabBtn1'),
children: [
{
img: 'https://webapp-pub.ezijing.com/www/pc/home/personal/master_med1.png',
desc: t('home.degree.med'),
href: 'https://cu.ezijing.com/',
},
{
img: 'https://webapp-pub.ezijing.com/www/pc/home/personal/master_med2.png',
desc: t('home.degree.cuw'),
href: 'https://cuw.ezijing.com/index',
},
],
},
{
title: t('home.degree.tabBtn2'),
children: [
{
img: 'https://webapp-pub.ezijing.com/www/pc/home/personal/mba1.png',
desc: t('home.degree.mba'),
href: 'https://marywood.ezijing.com/',
},
{
img: 'https://webapp-pub.ezijing.com/www/pc/home/personal/mba2.png',
desc: t('home.degree.international'),
href: 'https://shms.ezijing.com/',
},
],
},
]
</script>
<style lang="scss" scoped>
.box-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 20px;
column-gap: 100px;
}
.box-col {
position: relative;
}
.box-col:nth-child(even) {
&::before {
position: absolute;
top: 42px;
bottom: 0;
left: -50px;
content: '';
width: 0;
border: 1px dashed #c4c4c4;
}
}
.box-col__title {
padding-left: 10px;
margin-bottom: 26px;
font-size: 16px;
line-height: 1;
border-left: 1px solid var(--main-color);
}
.box-wrap {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
.box-inner {
display: flex;
align-items: center;
padding: 10px;
&:hover {
color: #fff;
background-color: var(--main-color);
}
.box-pic {
width: 120px;
height: 80px;
}
.box-desc {
flex: 1;
text-align: center;
}
}
</style>
<template>
<div class="home-news">
<AppCard :title="$t('menu.notice')" titleAlign="center">
<AppCard title="通知公告" titleAlign="center">
<template #title>
<nuxt-link to="/about/news">{{ $t('menu.notice') }}</nuxt-link>
<nuxt-link to="/about/news">通知公告</nuxt-link>
</template>
<template #header-aside>
<nuxt-link to="/about/news">{{ $t('viewMore') }}</nuxt-link>
<nuxt-link to="/about/news">查看更多+</nuxt-link>
</template>
<div class="news-content">
<div class="left-content">
......@@ -54,7 +54,6 @@
<script setup>
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Autoplay } from 'swiper/modules'
import httpRequest from '@/utils/httpRequest'
const config = useRuntimeConfig()
const projectId = config.public.projectId
......@@ -76,8 +75,9 @@ const swiperOption = {
spaceBetween: 0,
}
const listData = ref([])
const activityList = ref([])
const content = inject('homeContent')
const listData = computed(() => content.value?.newsList || [])
const activityList = computed(() => content.value?.futureList || [])
// 2021
const formatYY = (value) => {
......@@ -91,22 +91,6 @@ const formatMD = (value) => {
return date.getMonth() + 1 + '/' + date.getDate()
}
const fetchNewsList = async () => {
const params = { project_id: projectId, type_tag: 'article_news', page: 1, limit: 3 }
const res = await httpRequest.get('/api/cms/api/v1/articles', { params })
listData.value = res.data.data || []
}
const fetchActivityList = async () => {
const params = { project_id: projectId, type_tag: 'www_activity' }
const res = await httpRequest.get('/api/cms/api/v1/articles', { params })
activityList.value = res.data.data || []
}
onMounted(() => {
fetchNewsList()
fetchActivityList()
})
</script>
<style lang="scss">
.home-news {
......
<template>
<div>
<div class="w1200">
<CarouselH5 class="my-swipe" :autoplay="5000">
<CarouselItemH5 v-for="(item, index) in bannerList" :key="index">
<img :src="item.web_img_uri" class="banner-img" />
</CarouselItemH5>
</CarouselH5>
<FormSubmit mobile />
<ProjectCard title="热门专业" :list="projectList"></ProjectCard>
<AppCard title="申请流程">
<div class="process">
<img src="https://webapp-pub.ezijing.com/www/h5/internationalDegree/process.png" />
</div>
</AppCard>
<AppCard title="报考指南">
<swiper ref="mySwiper" v-bind="swiperOptions" :modules="swiperModules">
<swiper-slide v-for="(item, index) in qaSlides" :key="index" class="qa-list2">
<div v-for="(it, index1) in item" :key="index1" class="item">
<div class="tit">Q:{{ it.question }}</div>
<p>{{ it.answer }}</p>
<div class="line"></div>
</div>
</swiper-slide>
</swiper>
</AppCard>
<TeacherCard :list="teacherList"></TeacherCard>
<NewsCard baseUrl="/internationalDegree/news" :params="newsParams"></NewsCard>
<FormSubmit mobile />
</div>
</div>
</template>
<script>
import CarouselH5 from '~/components/CarouselH5.vue'
import { SwiperSlide as CarouselItemH5 } from 'swiper/vue'
import { getSectionNewsParams } from '~/api'
import { Swiper, SwiperSlide } from 'swiper/vue'
import { Autoplay } from 'swiper/modules'
import AppCard from '~/components/AppCard.vue'
import ProjectCard from '~/components/ProjectCard.vue'
import FormSubmit from '~/components/FormSubmit.vue'
import NewsCard from '~/components/NewsCard.vue'
import TeacherCard from '~/components/TeacherCard.vue'
import qaList from '~/assets/data/internationalDegreeQA.js'
import teachers from '~/assets/data/internationalDegreeTeachers.js'
export default {
setup() {
useHead({
title: '紫荆教育-在线国际学位项目-在职硕士-国际硕士-免联考硕士-应用心理学硕士-美国硕士',
meta: [
{
hid: 'keywords',
name: 'keywords',
content:
'在线硕士,国际硕士,免联考硕士,美国硕士,在职硕士,在职MBA,在职心理学,心理学硕士,应用心理学硕士,心理学,MBA,免联考MBA,国际MBA,酒店管理'
},
{
hid: 'description',
name: 'description',
content:
'紫荆教育-专注在线国际学位项目,在职硕士心理学硕士,国际硕士免联考硕士,酒店管理硕士,美国硕士,在线硕士,清控紫荆教育全称是清控紫荆(北京)教育科技股份有限公司,是2015年以清华大学五道口金融学院相关知识产权创设而成。'
}
]
})
},
components: { CarouselH5, CarouselItemH5, Swiper, SwiperSlide, AppCard, FormSubmit, ProjectCard, NewsCard, TeacherCard },
computed: {
qaSlides() {
const questions = qaList
return Array.from(
{ length: Math.ceil(questions.length / 4) },
(_, index) => questions.slice(index * 4, index * 4 + 4),
)
},
},
data() {
return {
swiperModules: [Autoplay],
swiperOptions: {
autoplay: true,
loop: false,
rewind: true,
slidesPerView: 'auto',
spaceBetween: 16
},
bannerList: [
{ web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner1.jpg' },
{ web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner2.jpg' },
{ web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner3.jpg' },
{ web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner4.jpg' }
],
// 热门专业
projectList: [
// {
// imgUrl: 'https://webapp-pub.ezijing.com/www/h5/internationalDegree/project_ciis.png',
// href: 'http://pages.ezijing.com/ciis/mobile-204001490929.html?channel_num=29758'
// },
{
imgUrl: 'https://webapp-pub.ezijing.com/www/h5/internationalDegree/project_cuw.png',
href: 'https://pages.ezijing.com/cuw/20220804.html?channel_num=29758'
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/h5/internationalDegree/project_cu.png',
href: 'https://pages.ezijing.com/campbellsville/mobile-204001490804.html?channel_num=29758'
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/h5/internationalDegree/project_marywood.png',
href: 'https://pages.ezijing.com/marywood/m204001490514.html?channel_num=29759'
},
{
imgUrl: 'https://webapp-pub.ezijing.com/www/h5/internationalDegree/project_shms.png',
href: 'https://pages.ezijing.com/seg/mobile202107190928.html?channel_num=29759'
},
// {
// imgUrl: 'https://webapp-pub.ezijing.com/www/h5/internationalDegree/project_caas.png',
// href: 'https://caas.ezijing.com/'
// }
],
// 常见问题
qaList,
// 最新动态请求参数
newsParams: getSectionNewsParams('internationalDegree', { page: 1, limit: 4 }),
teacherList: teachers,
}
}
}
</script>
<style lang="scss" scoped src="~/assets/css/responsive-study-abroad.scss"></style>
<template>
<div>
<div class="banner">
<!-- <img src="https://webapp-pub.ezijing.com/www/pc/studyAbroad/banner.jpg" /> -->
<Swiper
ref="mySwiper"
:speed="400"
:autoplay="true"
:loop="true"
:navigation="{
nextEl: '.next-button',
prevEl: '.prev-button',
}">
<SwiperSlide v-for="(item, index) in listData" :key="index">
<img :src="item.web_img_uri" />
</SwiperSlide>
</Swiper>
<div class="prev-button"></div>
<div class="next-button"></div>
</div>
<FormSubmit id="form" ref="formSubmit"></FormSubmit>
<div class="w1200">
<ProjectCard title="热门专业" :list="projectList"></ProjectCard>
<div class="why">
<h1 class="why-title">
为什么选择紫荆的国际免联考在线学位课程?
</h1>
<div class="why-list">
<div class="why-item" v-for="(item, index) in whyList" :key="index">
<img :src="item.icon" class="why-item__icon" />
<img :src="item.hoverIcon" class="why-item__icon_hover" />
<div class="why-content" v-html="item.desc"></div>
</div>
</div>
</div>
<app-card title="申请流程" style="margin-top: 68px">
<div class="process">
<img src="https://webapp-pub.ezijing.com/www/pc/studyAbroad/process.png" />
</div>
</app-card>
<!-- <app-card title="常见问题">
<template #header-aside><nuxt-link to="/internationalDegree/qa">查看更多+</nuxt-link></template>
<ul class="qa-list">
<li v-for="(item, index) in qaList" :key="index">
<nuxt-link to="/internationalDegree/qa">{{ item.question }}</nuxt-link>
</li>
</ul>
</app-card> -->
<app-card title="报考指南" style="margin-top: 74px">
<ul class="qa-list2">
<li v-for="(item, index) in qaList2" :key="index">
<h2>Q:{{ item.question }}</h2>
<p>{{ item.answer }}</p>
</li>
</ul>
</app-card>
<HomeAlumniStories style="margin-top: 96px" />
<NewsCard style="margin-top: 96px" baseUrl="/internationalDegree/news" :params="newsParams"></NewsCard>
<TeacherCard :list="teacherList"></TeacherCard>
</div>
<FormSubmit></FormSubmit>
</div>
</template>
<script setup>
import { getSectionNewsParams } from '~/api'
import { ref, computed } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import qaList from '@/assets/data/internationalDegreeQA.js'
import teachers from '@/assets/data/internationalDegreeTeachers.js'
const formSubmit = ref(null)
const mySwiper = ref(null)
useHead({
title: '紫荆教育-在线国际学位项目-在职硕士-国际硕士-免联考硕士-应用心理学硕士-美国硕士',
meta: [
{
hid: 'keywords',
name: 'keywords',
content:
'在线硕士,国际硕士,免联考硕士,美国硕士,在职硕士,在职MBA,在职心理学,心理学硕士,应用心理学硕士,心理学,MBA,免联考MBA,国际MBA,酒店管理',
},
{
hid: 'description',
name: 'description',
content:
'紫荆教育-专注在线国际学位项目,在职硕士心理学硕士,国际硕士免联考硕士,酒店管理硕士,美国硕士,在线硕士,清控紫荆教育全称是清控紫荆(北京)教育科技股份有限公司,是2015年以清华大学五道口金融学院相关知识产权创设而成。',
},
],
})
const listData = ref([
{
web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner1.jpg',
},
{
web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner2.jpg',
},
{
web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner3.jpg',
},
{
web_img_uri: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/banner4.jpg',
},
])
const whyList = computed(() => [
{
icon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_01.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_01_hover.png',
desc: "优选国际正规院校、学位受到全球普遍承认",
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_02.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_02_hover.png',
desc: "严格审核,正规面试, 以国际标准选拔优秀人才",
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_03.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_03_hover.png',
desc: "国企背景、专业口碑 国际化专业运营团队",
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_04.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_04_hover.png',
desc: "与在校生一样的授课老师,一样的专业课程体系",
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_05.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_05_hover.png',
desc: "针对在线学习特点的教学优化和授课安排",
},
{
icon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_06.png',
hoverIcon: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/icon_why_06_hover.png',
desc: "稳定的学习系统,365天X24小时全球登录",
},
])
// 热门专业
const projectList = ref([
// {
// project_id: '1006',
// imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/project_ciis.png',
// href: 'https://ciis.ezijing.com/'
// },
{
project_id: '1021',
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/project_cuw.png',
href: 'https://cuw.ezijing.com/',
},
{
project_id: '1005',
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/project_cu.png',
href: 'https://cu.ezijing.com/',
},
{
project_id: '1012',
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/project_marywood.png',
href: 'https://marywood.ezijing.com/',
},
{
project_id: '1008',
imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/project_shms.png',
href: 'https://shms.ezijing.com/',
},
// {
// project_id: '1015',
// imgUrl: 'https://webapp-pub.ezijing.com/www/pc/internationalDegree/project_caas.png',
// href: 'https://caas.ezijing.com/'
// }
])
// 最新动态请求参数
const newsParams = computed(() => getSectionNewsParams('internationalDegree', { page: 1, limit: 4 }))
const teacherList = teachers
const qaList2 = computed(() => [
{
question: "学校是否正规?学籍是否正规?",
answer: "紫荆教育所有合作院校均为各国官方教育机构认证的合规大学,受到US.NEWS,QS,AACSB,ACBSP等数十个国际权威评级和教育评价组织认可。所有学生均注册正规学籍,终身可查。",
},
{
question: "在线学习颁发的学位证与出国留学颁发的证书有何不同?",
answer: "颁发正规学位,与留学生完全一样,且具有相同的形式和法律效力。不存在在线学位、远程学位、校证、特殊学位等说法。",
},
{
question: "关于上课形式,课程体系,老师水平",
answer: "以网络授课为主。配合线下课,企业参访,工作坊,国际访学等多种授课形式。课程体系与授课老师完全按照在校生标准配置。",
},
{
question: "学制多久?能否确保拿到学位证书?",
answer: "国际在线学位项目的学制一般为16—24个月不等。只要按时上课,完成作业并修满规定学分就可获得所修项目的学位证书。",
},
{
question: "必须要本科吗?非统招本科学历是否可以,大专学历能上吗?",
answer: "部分项目针对大专学历少量择优录取,详情请咨询招生顾问老师。非统招,专升本都可以上。",
},
{
question: "申请紫荆教育在线国际学位项目需要提交哪些材料?",
answer: "需提交:本科学历证书、本科阶段(中、英文双语版成绩单)、英文版《教育部学历证书电子注册备案表》、及推荐信等个人能力证明文件。",
},
{
question: "每周要花多久用于学习?遇到急事可以请假或者补课吗?",
answer: "学习时长因人而异,大部分学员保持在每周上2-3次课。每次2个小时左右。可以请假,但部分公开课,线下课及直播课无法补课。",
},
{
question: "可以做中留服认证吗?",
answer: "因国家政策规定,不出国的在线学习项目都不能申请中留服认证。对于市场上承诺可以做中留服的项目请注意鉴别,避免受骗。",
},
])
</script>
<style lang="scss" scoped src="@/assets/css/studyAbroad.scss"></style>
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论