公司C端官网项目
# 项目主要框架
vue 2.5.17 vue-router 3.0.1 vuex 3.0.1 element-ui 2.15.14 axios 0.18.0 @vue/cli-service 3.2.0
# 首屏加载优化
# 2k\4k\5k屏幕适配
定义$breakpoints端点数组和respondTo混合器,封装冗余的媒体查询,实际使用的时候直接使用 @include respondTo('lg',‘xl’) 即可,可以省去冗长的@media表达式,对于不同断点相同的样式不再需要重复书写,具体代码如下:
$breakpoints:(
"xs": (320px, 480px),
"sm": (481px, 768px),
"md": (769px, 1024px),
"lg": (1025px, 1440px),
"xl": (1441px, 2000px),
"2k": (2001px, 3770px),
"4k": (3771px, 5119px),
"5k": (5120px, 99999px)
);
@mixin respondTo($breaknames){
@each $breakname in $breaknames {
$bp: map-get($breakpoints, $breakname);
@if type-of($bp == 'list') {
$min: nth($bp, 1);
$max: nth($bp, 2);
@media (min-width: $min) and (max-width: $max) {
@content;
}
}
@else {
@media (min-width: $bp) {
@content;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.wrapper {
min-width: 1400px;
@include respondTo('lg') {
width: 1290px !important;
min-width: 1290px !important;
}
@include respondTo(xl) {
width: 1440px !important;
}
@include respondTo('2k') {
width: 1920px !important;
}
@include respondTo('4k') {
width: 2880px !important;
}
@include respondTo('5k') {
width: 3840px !important;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
媒体查询更多适用于不同设备间展示布局有较大变化的情况。除非不同断点间的元素不不是等比缩放的,需要一定调整才使用。 如果是按视口宽度百分比缩放的,不建议使用,可能会导致很多类名下都会有大量的针对不同断点的混入,难以维护,使用rem、vw之类的可能会更好,不同平台之间的兼容性也会更好,px这个单位不同设备间展示的效果都有较大差异。
# 移动端、PC端多端适配及访问设备检测
# 多语言
// setup/i18n-setup.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from '@/lang/en_US.js'
import axios from 'axios'
Vue.use(VueI18n)
// 追踪已加载的语言文件
export const loadedLanguages = ['en_US']
// 将语言代码转换为文件名格式
export const getLanguageFile = (locale) => {
// 从完整的 locale (如 'zh-CN') 中提取基础语言代码 ('zh')
const baseLanguage = locale.split('-')[0]
// 根据你的文件命名规则返回对应的文件名
switch(baseLanguage) {
case 'en': return 'en_US' // 英语
case 'ja': return 'ja_JP' // 日语
case 'zh': return 'zh_CN' // 中文(简体)
default: return baseLanguage
}
}
// 获取浏览器语言和地区
const getBrowserLocale = () => {
const browserLang = navigator.language || navigator.userLanguage
// 返回完整的语言-地区代码,如 'zh-CN', 'en-US'
return browserLang
}
const savedLocale = localStorage.getItem('selectedLocale') || getBrowserLocale() || 'en-US'
const languageFile = getLanguageFile(savedLocale)
const loadInitialLanguage = async () => {
if (languageFile !== 'en_US') {
try {
const messages = await import(/* webpackChunkName: "lang-[request]" */ `@/lang/${languageFile}.js`)
i18n.setLocaleMessage(languageFile, messages.default)
loadedLanguages.push(languageFile)
} catch (error) {
console.error(`Could not load initial language ${languageFile}:`, error)
}
}
}
export const i18n = new VueI18n({
locale: languageFile,
fallbackLocale: 'en_US',
messages: {
en_US: messages
},
silentTranslationWarn: true,
// 添加自定义 modifier
modifiers: {
lowercase: str => str.toLowerCase(),
uppercase: str => str.toUpperCase(),
capitalize: str => str.charAt(0).toUpperCase() + str.slice(1)
},
missing: (locale, key) => {
// 当找不到翻译时,尝试用小写key再次查找
const messages = i18n.messages[locale]
const lowerKey = key.toLowerCase()
for (let k in messages) {
if (k.toLowerCase() === lowerKey) {
return messages[k]
}
}
return key
}
})
export function setI18nLanguage(locale) {
const languageFile = getLanguageFile(locale)
i18n.locale = languageFile
axios.defaults.headers.common['Accept-Language'] = locale
document.querySelector('html').setAttribute('lang', locale)
localStorage.setItem('selectedLocale', locale)
return languageFile
}
export async function loadLanguageAsync(locale) {
const langFile = getLanguageFile(locale)
// 如果语言已经加载
if (i18n.locale === langFile) {
return Promise.resolve(setI18nLanguage(locale))
}
// 如果语言还没有加载
if (!loadedLanguages.includes(langFile)) {
try {
const messages = await import(/* webpackChunkName: "lang-[request]" */ `@/lang/${langFile}.js`)
i18n.setLocaleMessage(langFile, messages.default)
loadedLanguages.push(langFile)
return setI18nLanguage(locale)
} catch (error) {
console.error(`Failed to load language ${langFile}:`, error)
return Promise.reject(error)
}
}
return Promise.resolve(setI18nLanguage(locale))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// store/modules/lang.js
import { loadLanguageAsync, getLanguageFile, setI18nLanguage, loadedLanguages, i18n } from '@/setup/i18n-setup'
const state = {
currentLang: localStorage.getItem('selectedLocale') || 'en-US'
}
const mutations = {
SET_LANG(state, lang) {
state.currentLang = lang
}
}
const actions = {
async changeLang({ commit }, locale) {
const languageFile = getLanguageFile(locale)
if (!loadedLanguages.includes(languageFile)) {
const messages = await import(/* webpackChunkName: "lang-[request]" */ `@/lang/${languageFile}.js`)
i18n.setLocaleMessage(languageFile, messages.default)
loadedLanguages.push(languageFile)
}
setI18nLanguage(locale)
commit('SET_LANG', locale)
}
}
const getters = {
currentLang: state => state.currentLang
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// lang/zh_CN.js
export default {
"Condition": "\u6761\u4ef6",
"SMART HOME SCENE": "\u667a\u80fd\u5bb6\u5c45\u573a\u666f",
"Smart Scenarios of Human Body Sensor": "\u4eba\u4f53\u4f20\u611f\u5668\u7684\u667a\u80fd\u573a\u666f",
'Smart Applications': '智能应用',
'Product Series': '产品系列',
'Technical Support': '技术支持',
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
// main.js
import store from './store' // vuex配置
// 初始化时加载保存的语言
store.dispatch('lang/changeLang', store.state.lang.currentLang)
1
2
3
4
2
3
4
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
data() {
selectedLang: '',
},
created() {
this.selectedLang = localStorage.getItem('selectedLocale') || this.$i18n.locale || 'en'
},
computed: {
...mapGetters('lang', ['currentLang'])
},
methods: {
...mapActions('lang', ['changeLang']),
handleLangChange(langCode) {
this.selectedLang = langCode;
this.changeLang(langCode);
this.isDropdownVisible = false;
},
getCurrentLangName() {
const currentLang = this.langList.find(lang => lang.code === this.selectedLang);
return currentLang ? currentLang.name : 'Language';
},
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 实际效果与原型对比分析组件
<template>
<div>
<!-- 设计稿覆盖层 - 跟随页面滚动 -->
<div v-if="showDesignBackground && designDraftPath" class="design-overlay">
<img :src="designDraftPath" alt="设计稿" class="design-image">
</div>
<!-- 设计稿切换按钮 - 始终显示,用于调试 -->
<div class="design-toggle" @click="toggleDesignBackground">
<span class="toggle-text">
{{ showDesignBackground ? '隐藏设计稿' : '显示设计稿' }}
<br>
<small style="font-size: 10px; opacity: 0.7;">
{{ designDraftPath || '无设计稿' }}
</small>
</span>
</div>
</div>
</template>
<script>
import { getDesignDraftPath } from '@/config/designDrafts'
export default {
name: 'DesignOverlay',
data() {
return {
showDesignBackground: false
}
},
computed: {
// 从配置文件获取设计稿路径
designDraftPath() {
// 构建完整的路径,包括查询参数
const fullPath = this.$route.path + (this.$route.query && Object.keys(this.$route.query).length > 0
? '?' + new URLSearchParams(this.$route.query).toString()
: '')
const path = getDesignDraftPath(fullPath)
console.log('当前路径:', this.$route.path, '查询参数:', this.$route.query, '完整路径:', fullPath, '设计稿路径:', path)
return path
}
},
mounted() {
// 从本地存储读取设计稿显示状态
const savedState = localStorage.getItem('showDesignBackground')
if (savedState !== null) {
this.showDesignBackground = JSON.parse(savedState)
}
console.log('DesignOverlay 组件已挂载,当前路径:', this.$route.path)
},
methods: {
toggleDesignBackground() {
this.showDesignBackground = !this.showDesignBackground
// 保存状态到本地存储
localStorage.setItem('showDesignBackground', JSON.stringify(this.showDesignBackground))
console.log('切换设计稿显示状态:', this.showDesignBackground)
}
}
}
</script>
<style scoped lang="scss">
// 设计稿覆盖层 - 跟随页面滚动
.design-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%; // 使用100%而不是100vw,避免包含滚动条宽度
min-height: 100vh; // 确保覆盖整个页面高度
z-index: 9998; // 比切换按钮低一层,但比网站内容高
pointer-events: none; // 不阻止用户交互
opacity: 0.8;
.design-image {
width: 100%;
height: 100%;
object-fit: contain; // 保持设计稿完整显示
object-position: top center;
}
}
// 设计稿切换按钮样式 - 保持固定定位
.design-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999; // 确保按钮在最顶层
background: #323232;
color: white;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
user-select: none;
pointer-events: auto; // 确保按钮可以点击
&:hover {
background: #555;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.toggle-text {
white-space: nowrap;
line-height: 1.2;
}
}
// 响应式调整
@media (max-width: 768px) {
.design-toggle {
top: 10px;
right: 10px;
padding: 8px 12px;
font-size: 12px;
}
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// App.vue
<template>
<div id="app">
<router-view />
<!-- 全局设计稿覆盖层 - 只在开发环境且在特定的开发路由下显示 -->
<DesignOverlay v-if="isDevelopment && $route.path.includes('/story')" />
</div>
</template>
<script>
import DesignOverlay from '@/components/DesignOverlay.vue'
export default {
name: "App",
components: {
DesignOverlay
},
computed: {
isDevelopment () {
return process.env.NODE_ENV === 'development'
}
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// config/designDrafts.js
// 设计稿配置文件
export const designDrafts = {
'/': '/img/design_drafts/home.png',
'/story': '/img/design_drafts/story.jpg',
// 带参数的路径配置
'/function?type=electronic_types': '/img/design_drafts/function#type=electronic_types.png',
'/panel?activePanel=0': '/img/design_drafts/panel#activePanel=0.jpg',
}
// 获取当前页面的设计稿路径
export function getDesignDraftPath(routePath) {
console.log(`正在查找设计稿: ${routePath}`)
// 首先尝试精确匹配
if (designDrafts[routePath]) {
console.log(`精确匹配成功: ${routePath} -> ${designDrafts[routePath]}`)
return designDrafts[routePath]
}
// 如果精确匹配失败,尝试智能匹配
// 解析路径和查询参数
const [pathname, search] = routePath.split('?')
const queryParams = new URLSearchParams(search || '')
console.log(`解析路径: pathname=${pathname}, search=${search}`)
// 构建可能的匹配键
const possibleKeys = []
// 1. 基础路径
possibleKeys.push(pathname)
// 2. 带查询参数的完整路径
if (search) {
possibleKeys.push(routePath)
}
// 3. 通用查询参数匹配:尝试所有可能的查询参数组合
if (search && queryParams.size > 0) {
// 构建所有可能的查询参数组合
const paramEntries = Array.from(queryParams.entries())
// 尝试单个参数
paramEntries.forEach(([key, value]) => {
possibleKeys.push(`${pathname}?${key}=${value}`)
})
// 尝试多个参数组合(按原始顺序)
if (paramEntries.length > 1) {
const sortedParams = new URLSearchParams()
paramEntries.forEach(([key, value]) => {
sortedParams.append(key, value)
})
possibleKeys.push(`${pathname}?${sortedParams.toString()}`)
}
}
console.log(`尝试匹配的键:`, possibleKeys)
// 查找匹配的设计稿
for (const key of possibleKeys) {
if (designDrafts[key]) {
console.log(`设计稿匹配成功: ${routePath} -> ${key} -> ${designDrafts[key]}`)
return designDrafts[key]
}
}
console.log(`未找到设计稿: ${routePath}`)
return null
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# SEO
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="title" content="Home page">
<meta name="description" content="XXX is known around the world as a leader in smart home devices that include electric switches and sockets." />
<meta name="keywords" content="XXX,XXX touch switch,XXX socket,smart switch,app control switch,led indicate switch,light switch,wall socket"/>
<!-- Twitter Cards -->
<meta property="twitter:url" content="https://www.xxx.com" />
<meta name="twitter:title" content="Home page -- XXX experts in electric switches" />
<meta name="twitter:description" content="XXX is known around the world as a leader in smart home devices that include electric switches and sockets." />
<meta name="twitter:site" content="https://www.xxx.com" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="" />
<!-- Facebook Open Graph -->
<meta property="og:url" content="https://www.xxx.com" />
<meta property="og:title" content="Home page -- XXX experts in electric switches" />
<meta property="og:description" content="XXX is known around the world as a leader in smart home devices that include electric switches and sockets." />
<meta property="og:image" content="" />
<meta property="og:type" content="website" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>XXX</title>
<!-- End Google Tag Manager -->
<!-- 生产环境使用 CDN -->
<!-- <% if (process.env.NODE_ENV === 'production') { %>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/lib/theme-chalk/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/lib/style.min.css">
<% } %> -->
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXX');
</script>
<!-- 添加结构化数据 -->
<script type="application/ld+json">
[
{
"@context": "https://schema.org",
"@type": "Organization",
"url": "https://www.xxx.com",
"logo": "https://www.xxx.com/favicon.ico",
"name": "XXX"
},
{
"@context": "https://schema.org",
"@type": "WebSite",
"url": "https://www.xxx.com",
"name": "XXX",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://www.xxx.com/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
},
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [{
"@type": "Question",
"name": "What is XXX Function?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Explore our smart home functions including touch switches, sockets, and smart control systems",
"url": "https://www.xxx.com/function"
}
}, {
"@type": "Question",
"name": "What is XXX Story?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Learn about XXX's history, development and our commitment to smart home innovation",
"url": "https://www.xxx.com/story"
}
}, {
"@type": "Question",
"name": "How to Contact XXX?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Get in touch with XXX team for business cooperation or technical support through our contact page",
"url": "https://www.xxx.com/contact"
}
}]
},
{
"@context": "https://schema.org",
"@type": "ItemList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"item": {
"@type": "Article",
"name": "Smart Home Functions",
"headline": "Explore XXX Smart Home Solutions",
"description": "Discover our innovative smart home solutions including touch switches, smart sockets, and intelligent control systems for modern living",
"url": "https://www.xxx.com/function",
"image": {
"@type": "ImageObject",
"url": "https://www.xxx.com/img/contact_1.png",
"width": 1200,
"height": 630
},
"author": {
"@type": "Organization",
"name": "XXX",
"url": "https://www.xxx.com"
},
"publisher": {
"@type": "Organization",
"name": "XXX",
"logo": {
"@type": "ImageObject",
"url": "https://www.xxx.com/favicon.ico"
}
},
"datePublished": "2025-01-01T00:00:00+08:00",
"dateModified": "2025-03-21T00:00:00+08:00"
}
},
{
"@type": "ListItem",
"position": 2,
"item": {
"@type": "Article",
"name": "XXX Story",
"headline": "Our Journey of Innovation",
"description": "From our founding principles to becoming a global leader in smart home technology - explore the XXX story of continuous innovation and growth",
"url": "https://www.xxx.com/story",
"image": {
"@type": "ImageObject",
"url": "https://www.xxx.com/img/contact_3.png",
"width": 1200,
"height": 630
},
"author": {
"@type": "Organization",
"name": "XXX",
"url": "https://www.xxx.com"
},
"publisher": {
"@type": "Organization",
"name": "XXX",
"logo": {
"@type": "ImageObject",
"url": "https://www.xxx.com/favicon.ico"
}
},
"datePublished": "2025-01-01T00:00:00+08:00",
"dateModified": "2025-03-21T00:00:00+08:00"
}
},
{
"@type": "ListItem",
"position": 3,
"item": {
"@type": "Article",
"name": "Contact XXX",
"headline": "Get in Touch with XXX",
"description": "Connect with XXX for business opportunities, technical support, or customer service. Your smart home journey starts here",
"url": "https://www.xxx.com/contact",
"image": {
"@type": "ImageObject",
"url": "https://www.xxx.com/img/contact_4.png",
"width": 1200,
"height": 630
},
"author": {
"@type": "Organization",
"name": "XXX",
"url": "https://www.xxx.com"
},
"publisher": {
"@type": "Organization",
"name": "XXX",
"logo": {
"@type": "ImageObject",
"url": "https://www.xxx.com/favicon.ico"
}
},
"datePublished": "2025-01-01T00:00:00+08:00",
"dateModified": "2025-03-21T00:00:00+08:00"
}
}
]
}
]
</script>
</head>
<body>
<noscript>
<strong>We're sorry but vue_template doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<!-- End Google Tag Manager (noscript) -->
<!-- 生产环境使用 CDN -->
<!-- <% if (process.env.NODE_ENV === 'production') { %>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://unpkg.com/[email protected]/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-i18n.min.js"></script>
<% } %> -->
<!-- <script async src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.5.207/pdf.min.js" data-body="true"></script> -->
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# 预渲染
编辑 (opens new window)