基本网页结构(write by tongyi)

This commit is contained in:
2025-11-11 16:37:12 +08:00
parent df3b859868
commit d6401bc901
18 changed files with 856 additions and 14 deletions

36
.gitignore vendored Normal file
View File

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

View File

@@ -1,11 +1,135 @@
<script setup></script>
<script setup>
import { RouterView } from 'vue-router'
import AppHeader from './components/AppHeader.vue'
import AppFooter from './components/AppFooter.vue'
</script>
<template>
<h1>You did it!</h1>
<p>
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
documentation
</p>
<div class="app-container">
<AppHeader />
<router-view />
<AppFooter />
</div>
</template>
<style scoped></style>
<style scoped lang="less">
// .app-container {
// max-width: 750px;
// margin: 0 auto;
// padding-bottom: 50px;
// font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
// }
// .app-header {
// display: flex;
// align-items: center;
// justify-content: space-between;
// padding: 10px 15px;
// background: linear-gradient(to right, #ff6b00, #ff8c00);
// color: white;
// position: sticky;
// top: 0;
// z-index: 100;
// }
// .header-city {
// display: flex;
// align-items: center;
// font-size: 14px;
// }
// .arrow-down {
// display: inline-block;
// width: 0;
// height: 0;
// border-left: 4px solid transparent;
// border-right: 4px solid transparent;
// border-top: 4px solid white;
// margin-left: 5px;
// }
// .header-search {
// flex: 1;
// margin: 0 10px;
// position: relative;
// }
// .search-icon {
// position: absolute;
// left: 10px;
// top: 50%;
// transform: translateY(-50%);
// width: 16px;
// height: 16px;
// background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ccc'%3E%3Cpath d='M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat;
// background-size: contain;
// }
// .search-input {
// width: 100%;
// padding: 8px 10px 8px 30px;
// border-radius: 15px;
// border: none;
// font-size: 12px;
// background-color: rgba(255, 255, 255, 0.9);
// }
// .header-user {
// width: 24px;
// height: 24px;
// background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E") no-repeat;
// background-size: contain;
// }
// .app-footer {
// position: fixed;
// bottom: 0;
// left: 0;
// right: 0;
// display: flex;
// justify-content: space-around;
// align-items: center;
// height: 50px;
// background-color: white;
// border-top: 1px solid #eee;
// max-width: 750px;
// margin: 0 auto;
// }
// .footer-tab {
// display: flex;
// flex-direction: column;
// align-items: center;
// justify-content: center;
// flex: 1;
// height: 100%;
// font-size: 12px;
// color: #999;
// text-decoration: none;
// }
// .footer-tab.active {
// color: #ff6b00;
// }
// .tab-icon {
// width: 20px;
// height: 20px;
// margin-bottom: 2px;
// }
// .movie-icon {
// background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff6b00'%3E%3Cpath d='M18 3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8 17H6v-2h2v2zm0-4H6v-2h2v2zm0-4H6V7h2v2zm10 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V7h2v2z'/%3E%3C/svg%3E") no-repeat;
// background-size: contain;
// }
// .cinema-icon {
// background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999'%3E%3Cpath d='M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 14l1.5-2 1.5 2 1.5-2 1.5 2 1.5-2 1.5 2 1.5-2 1.5 2h1v2h-1.5l-1.5-2-1.5 2-1.5-2-1.5 2-1.5-2-1.5 2-1.5-2H5v-2z'/%3E%3C/svg%3E") no-repeat;
// background-size: contain;
// }
// .profile-icon {
// background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E") no-repeat;
// background-size: contain;
// }
</style>

View File

@@ -0,0 +1,73 @@
/* reset.css */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* Custom reset additions */
*, *:before, *:after {
box-sizing: border-box;
}
a {
text-decoration: none;
color: inherit;
}
button, input, textarea {
outline: none;
border: none;
background: none;
font-family: inherit;
}
img {
max-width: 100%;
height: auto;
display: block;
}

View File

@@ -0,0 +1,64 @@
// 主色调
@primary-color: #ff6b00;
@primary-color-light: #ff8c00;
@primary-color-dark: #e55a00;
// 辅助色
@success-color: #00cc66;
@warning-color: #ff9900;
@error-color: #ff3333;
// 中性色
@white: #ffffff;
@black: #000000;
@gray-1: #f5f5f5;
@gray-2: #eeeeee;
@gray-3: #dddddd;
@gray-4: #cccccc;
@gray-5: #999999;
@gray-6: #666666;
@gray-7: #333333;
// 文字颜色
@text-color-primary: @gray-7;
@text-color-secondary: @gray-6;
@text-color-placeholder: @gray-5;
@text-color-disabled: @gray-4;
// 边框颜色
@border-color-base: @gray-3;
@border-color-split: @gray-2;
// 背景色
@bg-color-base: @gray-1;
@bg-color-light: @white;
// 字体大小
@font-size-xs: 10px;
@font-size-sm: 12px;
@font-size-md: 14px;
@font-size-lg: 16px;
@font-size-xl: 18px;
@font-size-xxl: 20px;
// 间距
@spacing-xs: 4px;
@spacing-sm: 8px;
@spacing-md: 12px;
@spacing-lg: 16px;
@spacing-xl: 20px;
@spacing-xxl: 24px;
// 圆角
@border-radius-sm: 2px;
@border-radius-md: 4px;
@border-radius-lg: 8px;
@border-radius-xl: 16px;
@border-radius-circle: 50%;
// 阴影
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.1);
@box-shadow-up: 0 -2px 8px rgba(0, 0, 0, 0.1);
@box-shadow-down: 0 2px 8px rgba(0, 0, 0, 0.1);
@box-shadow-left: -2px 0 8px rgba(0, 0, 0, 0.1);
@box-shadow-right: 2px 0 8px rgba(0, 0, 0, 0.1);

View File

@@ -0,0 +1,73 @@
<template>
<footer class="app-footer">
<router-link to="/main/movie/film" class="footer-tab" active-class="active">
<i class="tab-icon movie-icon"></i>
<span>电影</span>
</router-link>
<router-link to="/main/movie/cinema" class="footer-tab" active-class="active">
<i class="tab-icon cinema-icon"></i>
<span>影院</span>
</router-link>
<router-link to="/main/movie/my" class="footer-tab" active-class="active">
<i class="tab-icon profile-icon"></i>
<span>我的</span>
</router-link>
</footer>
</template>
<script setup>
</script>
<style scoped>
.app-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
align-items: center;
height: 50px;
background-color: white;
border-top: 1px solid #eee;
max-width: 750px;
margin: 0 auto;
}
.footer-tab {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
font-size: 12px;
color: #999;
text-decoration: none;
}
.footer-tab.active {
color: #ff6b00;
}
.tab-icon {
width: 20px;
height: 20px;
margin-bottom: 2px;
}
.movie-icon {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ff6b00'%3E%3Cpath d='M18 3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8 17H6v-2h2v2zm0-4H6v-2h2v2zm0-4H6V7h2v2zm10 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V7h2v2z'/%3E%3C/svg%3E") no-repeat;
background-size: contain;
}
.cinema-icon {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999'%3E%3Cpath d='M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 14l1.5-2 1.5 2 1.5-2 1.5 2 1.5-2 1.5 2 1.5-2 1.5 2h1v2h-1.5l-1.5-2-1.5 2-1.5-2-1.5 2-1.5-2-1.5 2-1.5-2H5v-2z'/%3E%3C/svg%3E") no-repeat;
background-size: contain;
}
.profile-icon {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E") no-repeat;
background-size: contain;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<header class="app-header">
<div class="header-city">
<span>北京</span>
<i class="arrow-down"></i>
</div>
<div class="header-search">
<i class="search-icon"></i>
<input type="text" placeholder="搜影院、搜影院" class="search-input">
</div>
<div class="header-user">
<i class="user-icon"></i>
</div>
</header>
</template>
<script setup>
</script>
<style scoped>
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 15px;
background: linear-gradient(to right, #ff6b00, #ff8c00);
color: white;
position: sticky;
top: 0;
z-index: 100;
}
.header-city {
display: flex;
align-items: center;
font-size: 14px;
}
.arrow-down {
display: inline-block;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid white;
margin-left: 5px;
}
.header-search {
flex: 1;
margin: 0 10px;
position: relative;
}
.search-icon {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ccc'%3E%3Cpath d='M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") no-repeat;
background-size: contain;
}
.search-input {
width: 100%;
padding: 8px 10px 8px 30px;
border-radius: 15px;
border: none;
font-size: 12px;
background-color: rgba(255, 255, 255, 0.9);
}
.header-user {
width: 24px;
height: 24px;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E") no-repeat;
background-size: contain;
}
</style>

View File

@@ -2,8 +2,10 @@ import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "amfe-flexible";
import "./assets/styles/reset.css";
const app = createApp(App);
app.use(router);
app.mount("#app");
app.mount("#app");

View File

@@ -1,8 +1,73 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory } from "vue-router";
import MainLayout from "@/views/MainLayout.vue";
import MoviesLayout from "@/views/movies/MoviesLayout.vue";
import MovieView from "@/views/movies/MovieView.vue";
import VideoView from "@/views/movies/VideoView.vue";
import ShowView from "@/views/movies/ShowView.vue";
import ProfileView from "@/views/movies/ProfileView.vue";
import AddressView from "@/views/AddressView.vue";
import SearchView from "@/views/SearchView.vue";
import DetailView from "@/views/DetailView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [],
})
routes: [
{
path: "/main",
component: MainLayout,
children: [
{
path: "/main",
redirect: "/main/movie",
},
{
path: "movie",
component: MoviesLayout,
children: [
{
path: "",
redirect: "/main/movie/film",
},
{
path: "/main/movie/film",
name: "movie",
component: MovieView,
},
{
path: "/main/movie/cinema",
name: "video",
component: VideoView,
},
{
path: "/main/movie/show",
name: "show",
component: ShowView,
},
{
path: "/main/movie/my",
name: "profile",
component: ProfileView,
},
],
},
],
},
{
path: "/address",
name: "address",
component: AddressView,
},
{
path: "/search",
name: "search",
component: SearchView,
},
{
path: "/detail",
name: "detail",
component: DetailView,
},
],
});
export default router
export default router;

19
src/views/AddressView.vue Normal file
View File

@@ -0,0 +1,19 @@
<template>
<div class="address-page">
<h1>地址</h1>
<p>这里是地址管理页面</p>
</div>
</template>
<script setup>
</script>
<style scoped>
.address-page {
padding: 20px;
}
h1 {
color: #e5484d;
}
</style>

19
src/views/DetailView.vue Normal file
View File

@@ -0,0 +1,19 @@
<template>
<div class="detail-page">
<h1>详情页面</h1>
<p>这里是详情页面内容</p>
</div>
</template>
<script setup>
</script>
<style scoped>
.detail-page {
padding: 20px;
}
h1 {
color: #d07cff;
}
</style>

21
src/views/MainLayout.vue Normal file
View File

@@ -0,0 +1,21 @@
<template>
<div class="main-layout">
<div class="content">
<router-view />
</div>
</div>
</template>
<script setup>
</script>
<style scoped>
.main-layout {
height: 100%;
}
.content {
padding: 20px;
padding-bottom: 70px;
}
</style>

19
src/views/SearchView.vue Normal file
View File

@@ -0,0 +1,19 @@
<template>
<div class="search-page">
<h1>搜索</h1>
<p>这里是搜索页面</p>
</div>
</template>
<script setup>
</script>
<style scoped>
.search-page {
padding: 20px;
}
h1 {
color: #0091ff;
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div class="movie-page">
<h1>电影/影院</h1>
<p>这里是电影和影院信息页面</p>
</div>
</template>
<script setup>
</script>
<style scoped>
.movie-page {
padding: 20px;
}
h1 {
color: #e5484d;
}
</style>

View File

@@ -0,0 +1,170 @@
<template>
<div class="movies-layout">
<div class="swipe-banner">
<div class="banner-item" style="background-color: #ff6b6b; color: white;">
<h2>热门电影推荐</h2>
<p>最新大片抢先看</p>
</div>
</div>
<div class="movie-tabs">
<div class="tab-item active">正在热映</div>
<div class="tab-item">即将上映</div>
</div>
<div class="movies-list">
<div class="movie-item" v-for="i in 6" :key="i">
<div class="movie-poster">
<div class="poster-placeholder" :style="{ backgroundColor: `hsl(${i * 60}, 70%, 60%)` }"></div>
<div class="movie-score">8.{{ i }}</div>
</div>
<div class="movie-info">
<div class="movie-title">电影标题{{ i }}</div>
<div class="movie-meta">
<span class="movie-type">剧情, 动作</span>
</div>
<div class="movie-actions">
<button class="buy-ticket">购票</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
</script>
<style scoped>
.movies-layout {
padding: 0 10px;
}
.swipe-banner {
height: 150px;
border-radius: 8px;
overflow: hidden;
margin: 10px 0;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.banner-item {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.banner-item h2 {
font-size: 20px;
margin-bottom: 10px;
}
.banner-item p {
font-size: 14px;
opacity: 0.9;
}
.movie-tabs {
display: flex;
margin: 15px 0;
border-bottom: 1px solid #eee;
}
.tab-item {
padding: 10px 20px;
font-size: 16px;
color: #666;
position: relative;
}
.tab-item.active {
color: #ff6b00;
font-weight: bold;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background-color: #ff6b00;
}
.movies-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.movie-item {
display: flex;
padding: 10px;
background: white;
border-radius: 8px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
}
.movie-poster {
position: relative;
width: 80px;
height: 110px;
margin-right: 10px;
}
.poster-placeholder {
width: 100%;
height: 100%;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.movie-score {
position: absolute;
top: 4px;
right: 4px;
background: rgba(0, 0, 0, 0.7);
color: #ffd700;
padding: 2px 4px;
border-radius: 2px;
font-size: 12px;
}
.movie-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.movie-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
}
.movie-meta {
font-size: 12px;
color: #999;
margin-bottom: 10px;
}
.buy-ticket {
align-self: flex-start;
background: linear-gradient(to right, #ff6b00, #ff8c00);
color: white;
border: none;
padding: 6px 12px;
border-radius: 15px;
font-size: 12px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div class="profile-page">
<h1>我的</h1>
<p>这里是个人中心页面</p>
</div>
</template>
<script setup>
</script>
<style scoped>
.profile-page {
padding: 20px;
}
h1 {
color: #30a46c;
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div class="show-page">
<h1>演出</h1>
<p>这里是演出信息页面</p>
</div>
</template>
<script setup>
</script>
<style scoped>
.show-page {
padding: 20px;
}
h1 {
color: #d07cff;
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div class="video-page">
<h1>视频</h1>
<p>这里是视频页面</p>
</div>
</template>
<script setup>
</script>
<style scoped>
.video-page {
padding: 20px;
}
h1 {
color: #0091ff;
}
</style>

View File

@@ -10,7 +10,7 @@ export default defineConfig({
css: {
preprocessorOptions: {
less: {
additionalData: `@import "${path.resolve(__dirname, "src/assets/css/var.less")}";`,
additionalData: `@import "${path.resolve(__dirname, "src/assets/styles/var.less")}";`,
},
},
},
@@ -22,4 +22,4 @@ export default defineConfig({
server: {
host: "0.0.0.0",
},
});
});