├── README.md
├── Vue3-H5
├── README.md
├── index.html
├── jsconfig.json
├── package.json
├── pnpm-lock.yaml
├── public
│ └── vite.svg
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── css
│ │ │ ├── common.css
│ │ │ └── reset.css
│ │ ├── data
│ │ │ └── tabBarData.js
│ │ └── img
│ │ │ ├── common
│ │ │ └── icon_nav_back.png
│ │ │ ├── detail
│ │ │ ├── XiaoYu-image.png
│ │ │ ├── icon_chat.png
│ │ │ ├── icon_check.png
│ │ │ └── icon_order.png
│ │ │ ├── favor
│ │ │ ├── empty_favorite.44731802.png
│ │ │ └── favor.png
│ │ │ ├── home
│ │ │ ├── 1008641.png
│ │ │ ├── banner.webp
│ │ │ ├── full-screen-loading.gif
│ │ │ ├── header_bg.png
│ │ │ ├── home-sprite.png
│ │ │ ├── icon-right-menu.png
│ │ │ ├── icon_location.png
│ │ │ ├── loading-bg.png
│ │ │ └── location.png
│ │ │ ├── message
│ │ │ └── icon_message.png
│ │ │ ├── order
│ │ │ ├── icon-order.png
│ │ │ ├── icon-time.png
│ │ │ └── icon_right_orderlist.png
│ │ │ ├── search
│ │ │ ├── tj-mob-ui_unit-item_collected-new.png
│ │ │ └── tj-mob-ui_unit-item_comment-new.png
│ │ │ ├── sprite.png
│ │ │ └── tabbar
│ │ │ ├── tab_favor.png
│ │ │ ├── tab_favor_active.png
│ │ │ ├── tab_home.png
│ │ │ ├── tab_home_active.png
│ │ │ ├── tab_message.png
│ │ │ ├── tab_order.png
│ │ │ └── tab_order_active.png
│ ├── components
│ │ ├── detail-section
│ │ │ └── detail-section.vue
│ │ ├── home-item-v3
│ │ │ └── home-item-v3.vue
│ │ ├── home-item-v9
│ │ │ └── home-item-v9.vue
│ │ ├── loading
│ │ │ └── loading.vue
│ │ ├── search-bar
│ │ │ └── search-bar.vue
│ │ ├── tab-bar.vue
│ │ └── tab-control
│ │ │ └── tab-control.vue
│ ├── hooks
│ │ └── useScroll.js
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── service
│ │ ├── index.js
│ │ ├── modules
│ │ │ ├── city.js
│ │ │ ├── detail.js
│ │ │ ├── favor.js
│ │ │ ├── home.js
│ │ │ └── order.js
│ │ └── request
│ │ │ ├── config.js
│ │ │ └── index.js
│ ├── stores
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── city.js
│ │ │ ├── home.js
│ │ │ └── mian.js
│ ├── style.css
│ ├── utils
│ │ ├── format_date.js
│ │ └── get_assets_img.js
│ └── views
│ │ ├── city
│ │ ├── city.vue
│ │ └── cpns
│ │ │ └── city-group-list.vue
│ │ ├── details
│ │ ├── cpns
│ │ │ ├── detail-action-bar.vue
│ │ │ ├── detail_01-swipe.vue
│ │ │ ├── detail_02-infos.vue
│ │ │ ├── detail_03-facility.vue
│ │ │ ├── detail_04-landlord.vue
│ │ │ ├── detail_05-comment.vue
│ │ │ ├── detail_06-notice.vue
│ │ │ ├── detail_07-map.vue
│ │ │ └── detail_08-intro.vue
│ │ └── details.vue
│ │ ├── favor
│ │ └── favor.vue
│ │ ├── home
│ │ ├── cpns
│ │ │ ├── home_categories.vue
│ │ │ ├── home_content.vue
│ │ │ ├── home_nav_bar.vue
│ │ │ └── home_search_box.vue
│ │ └── home.vue
│ │ ├── message
│ │ └── message.vue
│ │ ├── order
│ │ └── order.vue
│ │ └── search
│ │ └── search.vue
└── vite.config.js
├── Vue3-H5_images
├── image-20230310010819996.png
├── image-20230310012624925.png
├── image-20230310024247705.png
├── image-20230310031417551.png
├── image-20230313101045927.png
├── image-20230314010149456.png
├── image-20230314013020872.png
├── image-20230314013129282.png
├── image-20230314094855967.png
├── image-20230314101829006.png
├── image-20230314102307847.png
├── image-20230314105504901.png
├── image-20230314214104488.png
├── image-20230315185854793.png
├── image-20230316031930269.png
├── image-20230316100830116.png
├── image-20230316102034404.png
├── image-20230316112601841.png
├── image-20230316113126975.png
├── image-20230317120317312.png
├── image-20230317120528867.png
├── image-20230317120749274.png
├── image-20230317121011369.png
├── image-20230318052736911.png
├── image-20230318183953257.png
├── image-20230318184009017.png
├── image-20230318184023698-1679136024832-2.png
├── image-20230318184023698.png
├── image-20230318184107255.png
├── image-20230322082644907-1679444807472-1.png
├── image-20230322082644907.png
├── image-20230322084507143.png
├── image-20230322084527227.png
├── image-20230322084539070.png
├── image-20230322084952224.png
├── image-20230322084958239.png
├── image-20230322085003459.png
├── image-20230322085009014.png
├── image-20230322085013952.png
├── image-20230322202515106.png
├── image-20230322202516451.png
├── image-20230323135554233.png
├── image-20230323135841429.png
├── image-20230323140555018.png
├── image-20230324232348206.png
├── image-20230324234536819.png
├── image-20230325004104681.png
├── image-20230325010935795.png
├── image-20230325010955925.png
├── image-20230325011011950.png
├── image-20230325224644046.png
├── image-20230327073628493.png
├── image-20230327074606478.png
├── image-20230327074621804.png
├── image-20230327074814520.png
├── image-20230327074840183.png
├── image-20230327074857740.png
├── image-20230327081554265.png
├── image-20230327082016620.png
├── image-20230327163318833.png
├── image-20230327163418978.png
├── image-20230327182809544.png
├── image-20230327224652847.png
├── image-20230328142448811.png
├── image-20230328143145378.png
├── image-20230329015232501.png
├── image-20230329015530170.png
├── image-20230329022457459.png
├── image-20230329022525238.png
├── image-20230329023110954.png
├── image-20230329023346952.png
├── image-20230329023655470.png
├── image-20230329023718655.png
├── image-20230329023827024.png
├── image-20230329024046685.png
├── image-20230329131614840.png
├── image-20230329152738200.png
├── image-20230329173859199.png
├── image-20230329174538670.png
├── image-20230330003258697.png
├── image-20230330003504231.png
└── image-20230330021354434.png
└── 接口文档.md
/README.md:
--------------------------------------------------------------------------------
1 | # Vue3项目 -- H5
2 |
3 | > 技术栈:Vue3 + Router4 + Pinia + ES6 + Vant4 + Git +axios以及各种类似dayjs的第三方库
4 | >
5 | > 下载项目后需要做的步骤:
6 | >
7 | > 1. `pnpm install`(下载依赖)
8 | > 2. `pnpm dev`(运行项目)
9 |
10 | ## 创建Vue项目
11 |
12 | - 方式一:Vue CLI
13 | - 基于`Webpack`工具
14 | - 创建命令:`vue create`
15 | - 方式二:create-vue
16 | - 基于`vite`工具
17 | - 创建命令:`npm init vue@latest`或者`pnpm create vite`
18 |
19 | - 以下是基于`pnpm create vite`创建的项目
20 |
21 | 
22 |
23 | 项目配置:
24 |
25 | - 配置项目的`icon`图标
26 |
27 | > 从静态文件夹`public`中找到文件,将原本不需要的脚手架默认图标进行删除,加入新的图标照片
28 | >
29 | > 记得遵守替换图标的规则:
30 | >
31 | > - 在public文件夹中找到favicon.ico文件,这是默认的项目图标文件。
32 | > - 准备你想要使用的新图标文件,并将其命名为favicon.ico
33 | > - 将新的favicon.ico文件替换默认的文件,保存更改。
34 | > - 在浏览器中重新加载项目,你应该能够看到新的图标已经生效了
35 | > - 如果你想要使用不同的文件名来保存你的图标文件,你需要在index.html文件中手动更改图标文件的引用。在index.html文件中,找到以下代码:(我的href比较不一样,不过道理都是差不多的)
36 | >
37 | > ```html
38 | >
39 | > //将//vite.svg替换为你的图标文件的相对路径即可
40 | > ```
41 |
42 | - 配置项目的标题
43 |
44 | 
45 |
46 | - 配置`jsconfig.json`
47 |
48 | > 这个`jsconfig.json`配置文件能给我们写代码更友好的提示(可以对你的文件所在目录下的所有js代码做出个性化支持),但是`Vite脚手架`并没有生成这个文件,需要我们自己进行添加
49 | >
50 | > 这个文件所需的内容我放在下方的代码块中
51 | >
52 | > - 使用命令`touch jsconfig.json`生成对应的`jsconfig.json`文件
53 | > - 一般情况下`.json`后缀的文件是不能写注释的,而`jsconfig.json`和`tsconfig.json`是例外,因为vscode编辑器额外单独做了处理
54 |
55 | ```javascript
56 | //一般来说选这个就够了,需要其他的根据下面参考大全自己加
57 | {"compilerOptions": {
58 | "target": "esnext",
59 | "checkJs": false,
60 | "strict": true,
61 | "allowSyntheticDefaultImports": true,
62 | "baseUrl": ".",
63 | "module": "commonjs",
64 | "paths": {
65 | "@/*": ["./src/*"],
66 | "@components/*": ["src/components/*"],
67 | "@views/*": ["src/views/*"]
68 | }
69 | },
70 | "exclude": ["node_modules", "dist", "temp"],
71 | "include": ["src/**/*", "types/**/*"]
72 | }
73 | ```
74 |
75 | - **以下是可供参考的`jsconfig.json`配置文件**
76 |
77 | ```json
78 | // jsconfig.json
79 | {
80 | "compilerOptions": {
81 | "target": "es2015", // 指定要使用的默认库,值为"es3","es5","es2015"...
82 | "module": "commonjs", // 在生成模块代码时指定模块系统
83 | "checkJs": false, // 启用javascript文件的类型检查
84 | "baseUrl": "*", // 解析非相关模块名称的基础目录
85 | "paths": {
86 | "utils": ["src/utils/*"] // 指定相对于baseUrl选项计算的路径映射,使用webpack别名,智能感知路径
87 | }
88 | },
89 | "exclude": [ // 要排除的文件
90 | "node_modules",
91 | "**/node_modules/*"
92 | ],
93 | "include": [ // 包含的文件
94 | "src/*.js"
95 | ]
96 | }
97 |
98 | "compilerOptions": {
99 | "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
100 | "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
101 | "diagnostics": true, // 打印诊断信息
102 | "target": "ES5", // 目标语言的版本
103 | "module": "CommonJS", // 生成代码的模板标准
104 | "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
105 | "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
106 | "allowJS": true, // 允许编译器编译JS,JSX文件
107 | "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
108 | "outDir": "./dist", // 指定输出目录
109 | "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
110 | "declaration": true, // 生成声明文件,开启后会自动生成声明文件
111 | "declarationDir": "./file", // 指定生成声明文件存放目录
112 | "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
113 | "sourceMap": true, // 生成目标文件的sourceMap文件
114 | "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
115 | "declarationMap": true, // 为声明文件生成sourceMap
116 | "typeRoots": [], // 声明文件目录,默认时node_modules/@types
117 | "types": [], // 加载的声明文件包
118 | "removeComments":true, // 删除注释
119 | "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
120 | "noEmitOnError": true, // 发送错误时不输出任何文件
121 | "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
122 | "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
123 | "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
124 | "strict": true, // 开启所有严格的类型检查
125 | "alwaysStrict": true, // 在代码中注入'use strict'
126 | "noImplicitAny": true, // 不允许隐式的any类型
127 | "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
128 | "strictFunctionTypes": true, // 不允许函数参数双向协变
129 | "strictPropertyInitialization": true, // 类的实例属性必须初始化
130 | "strictBindCallApply": true, // 严格的bind/call/apply检查
131 | "noImplicitThis": true, // 不允许this有隐式的any类型
132 | "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
133 | "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
134 | "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
135 | "noImplicitReturns": true, //每个分支都会有返回值
136 | "esModuleInterop": true, // 允许export=导出,由import from 导入
137 | "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
138 | "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
139 | "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
140 | "paths": { // 路径映射,相对于baseUrl
141 | // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
142 | "jquery": ["node_modules/jquery/dist/jquery.min.js"]
143 | },
144 | "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
145 | "listEmittedFiles": true, // 打印输出文件
146 | "listFiles": true// 打印编译的文件(包括引用的声明文件)
147 | }
148 | ```
149 |
150 | ### 项目目录结构划分
151 |
152 | - `src`文件夹下的目录图片:
153 |
154 | 
155 |
156 | | 文件夹 | 作用 |
157 | | :----------: | :----------------------------------------------------------: |
158 | | `assets` | 存放资源(图片、CSS静态资源、字体资源、MP3、MP4等等) |
159 | | `components` | 存放Vue抽取的组件(比较通用的部分)
可能在`components`文件夹中还会存在以下两个小文件夹
`common`:多个项目都会公用的组件
`content`当前项目多个页面公用的组件 |
160 | | `hooks` | 多个组件中用到的代码逻辑抽取到这个文件夹中 |
161 | | `mock` | 模拟数据(服务器接口的数据还未写好,此时模拟的数据放在这里) |
162 | | `router` | 路由配置 |
163 | | `service` | 网络请求 |
164 | | `stores` | 复杂数据的状态管理,Pinia或者VueX,这里Pinia采用stores,加上s,因为Pinia可以存在多个store,实现扁平化 |
165 | | `utils` | 抽出的工具函数放在这里(工具类) |
166 | | `views` | 一个主要的,大的页面通常会抽取成`views`,有时候也可能不叫`views`而是叫`pages` |
167 | | **文件** | **作用** |
168 | | `App.vue` | Vue父组件 |
169 | | `main.js` | 全局配置文件 |
170 | | `style.css` | 全局CSS,随Vite脚手架创建时一起创建的 |
171 |
172 | ### CSS样式的重置
173 |
174 | - 对默认CSS样式进行重置:(在GitHub中进行下载前人已经总结好的)
175 |
176 | `Github地址:`[necolas/normalize.css: A modern alternative to CSS resets (github.com)](https://github.com/necolas/normalize.css)
177 |
178 | - normalize.css(适配各种浏览器的CSS样式)
179 |
180 | 使用命令`pnpm install --save normalize.css`进行安装
181 |
182 | 然后在`main.js`文件中进行引入使用
183 |
184 | 
185 |
186 | - reset.css(常见配置的处理)
187 |
188 | > 这个你可以放在`assets`文件夹下,创建一个css文件夹专门进行存放,也可以说直接导入,看你所处的具体情况决定
189 | >
190 | > - 但通常我们项目会抽出很多的CSS文件出来,这些文件我们可以放在`assets`文件夹中的`css`文件夹中,然后统一使用`@import url()`导入全局CSS样式`style.css`文件中进行统一管理
191 |
192 | ```css
193 | /* reset.css,我们会配置一些如下基础设置。然后将此文件引入全局CSS文件中 */
194 |
195 | body,h1,h2,h3,h4,ul,li {
196 | padding: 0;
197 | margin: 0;
198 | }
199 |
200 | ul,li {
201 | list-style: none;
202 | }
203 |
204 | a {
205 | text-decoration: none;
206 | color:#333;
207 | }
208 |
209 | img {
210 | vertical-align: top;
211 | }
212 | ```
213 |
214 | ### 全家桶 – 路由配置
215 |
216 | > 1. 安装路由:`pnpm install vue-router`
217 | > 2. 在router文件夹中的index.js配置路由文件。导出内容
218 | >
219 | > 3. 在全局配置文件`main.js`中进行配置
220 |
221 | - 路由需要对应多个界面,界面是在`views`中进行配置,我们需要在`views`文件夹中创建多个文件夹
222 | - 一个文件夹对应一个页面,因为一个页面会对应多个组件。使用文件夹进行区分会更加合理
223 | - 在文件夹中创建路由要跳转的对应页面(可以直接创建页面 或者 创建一个`index.vue`文件当作主体,然后创建其他的vue文件引入主体中),这里采用第一种方式
224 |
225 | > 在views中创建的文件夹中的vue文件中使用setup语法糖会报警告的解决方案:
226 | >
227 | > - 在上面创建的`jsconfig.json`文件中的`compilerOptions`配置中加上这部分`"allowJs": true`
228 | > - 这个方法是更加标准的做法,即使你没有使用Typescript也需要这么做
229 |
230 | - 做完之后就把`views`页面**映射**进路由里面,然后在`App.vue`组件中使用`router-view`进行路由占位
231 | - 使用`页面信息`进行路由跳转
232 |
233 | ```js
234 | import {createRouter,createWebHashHistory} from "vue-router"
235 |
236 | const router = createRouter({
237 | history:createWebHashHistory(),//使用哈希路由
238 | routes:[
239 | {
240 | path:"/",
241 | redirect:"/home"//重定向到首页
242 | },
243 | {
244 | path:"/home",
245 | //路由懒加载,方便打包的时候进行分包处理
246 | component:()=>import("../views/home/home.vue")
247 | },
248 | {
249 | path:"/favor",
250 | component:()=>import("../views/favor/favor.vue")
251 | },
252 | {
253 | path:"/order",
254 | component:()=>import("../views/order/order.vue")
255 | },
256 | {
257 | path:"/message",
258 | component:()=>import("../views/message/message.vue")
259 | }
260 | ]
261 | })
262 |
263 | export default router
264 | ```
265 |
266 | ### 全家桶 – 状态管理
267 |
268 | - 状态管理的选择:
269 | - `vuex`: 目前依然使用较多的状态管理库
270 | - `pinia`: 强烈推荐, 未来趋势的状态管理库
271 |
272 | > - 使用命令安装:`pnpm install pinia`
273 | > - 导入stores文件夹下(创建一个`index.js`文件进行配置操作),并导出
274 | > - 在全局配置文件`main.js`文件下进行引入配置使用
275 | >
276 | > ---
277 | >
278 | > - 在Pinia中,我们是可以创建多个store的,`index.js`文件是我们最终汇总的store文件,其他大大小小的store文件我们按模块进行区分,(创建一个`modules`文件夹,其他模块的的store文件就放在该文件里面)
279 |
280 | ```js
281 | //stores里的index.js搭建
282 | import {createPinia} from "pinia"
283 |
284 | const useStore = createPinia("useStore",{
285 | state:()=>({
286 | count:1
287 | }),
288 | })
289 |
290 | export default useStore
291 | ```
292 |
293 | ## 首页搭建
294 |
295 | ### TabBar(底部)基本搭建
296 |
297 | > 将底部`TabBar`抽取出来封装成一个组件 => 写到`components`文件夹中
298 | >
299 | > 这里就需要使用到`编程式路由导航`了,然后就是写样式了,完成底部的组件效果
300 | >
301 | > - 涉及到的静态资源已经在`assets`文件夹中进行分类为 => `font`、`data`、`img`三个文件夹分别存放字体、数据、图片
302 | > - CSS样式使用到的`less`,使用命令`pnpm i less`安装
303 |
304 | - 这里需要注意的点:
305 |
306 | - 直接在`img的src`属性中加上图片地址是可以的,但是想要从`tabbarData`中动态获取`image`确是不行的。
307 |
308 | - 在`webpack`中我们可以通过`
`中的`require`来实现
309 |
310 | - 但是在`Vite`中需要进行封装一个工具类,然后丢进`utils`文件夹中,引入TabBar组件中使用
311 |
312 | ```javascript
313 | const getAssetURL = (image) =>{
314 | //参数1:相对路径 参数2:当前路径
315 | return new URL(`../assets/img/${image}`,import.meta.url).href
316 | //返回创建的URL语句
317 | //具体来说,这个语句使用了 URL 构造函数,它接受两个参数:第一个参数是一个字符串,表示要创建的 URL 地址;第二个参数是一个可选的基础 URL,用于解析相对路径。在这个语句中,第一个参数是一个模板字符串,其中包含一个占位符 ${image},它会被变量 image 的值所替换,生成一个字符串 ../assets/img/xxx,其中 xxx 是变量 image 的值。这个字符串表示了一个相对于当前模块的上级目录的 assets/img 目录中的某个文件的路径。
318 |
319 | //第二个参数是 import.meta.url,这是一个元数据属性,表示当前模块的绝对 URL 地址。它在解析相对路径时非常有用。
320 |
321 | //但是它本身并不是一个字符串,而是一个包含了 URL 信息的对象。如果想要使用这个 URL 地址,需要使用 href 属性获取它的字符串表示
322 | }
323 | ```
324 |
325 |
326 |
327 | - 对于底部`tabBar`,由于数据就一点,我就直接抽取出来放在这里,不从服务器中获取。写完抽取到提前创建好的`data`文件中,取名随意(但记得语义化,方便找)
328 |
329 | ```javascript
330 | //对于底部tabBar,由于数据就一点,我就直接抽取出来放在这里,不从服务器中获取
331 | const tabbarData = [
332 | {
333 | id:1,
334 | text:"首页",
335 | image:"tabbar/tab_home.png",
336 | imageActive:"tabbar/tab_home_active.png",//活跃的图片
337 | path:"/home"
338 | },
339 | {
340 | id:2,
341 | text:"收藏",
342 | image:"tabbar/tab_favor.png",
343 | imageActive:"tabbar/tab_favor_active.png",
344 | path:"/favor"
345 | },
346 | {
347 | id:3,
348 | text:"订单",
349 | image:"tabbar/tab_order.png",
350 | imageActive:"tabbar/tab_order_active.png",
351 | path:"/order"
352 | },
353 | {
354 | id:4,
355 | text:"消息",
356 | image:"tabbar/tab_message.png",
357 | imageActive:"tabbar/tab_message.png",
358 | path:"/message"
359 | }
360 | ]
361 |
362 | //将数据暴露出去
363 | export default tabbarData
364 | ```
365 |
366 | ```vue
367 |
1467 | ```
1468 |
1469 | - 然后就可以开始创建我们的`百度地图`组件了,命名为`detail_07-map.vue`
1470 | - 我们在使用百度地图相关内容的时候,js部分不能直接写在`setup`中,因为Vue3的`setup`语法糖用于替代 Vue 2 中的 `created()` 和 `beforeCreate()` 钩子函数,它在组件实例创建之前执行。所以我们如果写在setup中,内容的元素不一定已经挂载上去了
1471 | - 我们这部分内容需要写在`onMount`生命周期中
1472 |
1473 | - 具体的步骤跟API就记录在百度地图的文档中:[jspopularGL | 百度地图API SDK (baidu.com)](https://lbsyun.baidu.com/index.php?title=jspopularGL/guide/helloworld)
1474 |
1475 | ### TabControl的展示和监听滚动过程
1476 |
1477 | > 封装成通用组件`tab-control`,然后对原本`hook`文件夹中监听距离顶部的逻辑`useScroll.js`做出修改,原本的逻辑是监听窗口距离顶部的变化,现在我们需要用到里面的元素(也就是最外层的div)距离顶部的距离,所以进一步修改将其通用性增强.然后在`details`组件中使用
1478 |
1479 | - 还记得前面刚开始封装hook中这份逻辑中需要区分的点吗
1480 |
1481 | 
1482 |
1483 | - 需要注意的点:
1484 | - 在展示`TabControl`的时候,需要先使用CSS进行固定定位将其固定在当前视口最上面,不然就算进行判断显示出来,没有固定住会在原本的位置上,就算拖动到该显示出`TabControl`的时候也看不到他,不是没有展示出来,而是展示出来了但是没有固定住已经被滑动出可视范围外了
1485 |
1486 | ### TabControl的交互和滚动位置
1487 |
1488 | > 这个交互我们就需要将`TabControl`栏跟内容进行一个交互关联起来,才能够在点击按键的时候能够跳转到对应的位置中去。我们通过`defineEmit`将`TabControl`的索引拿到传递给`父组件`跟`父组件TabControl`的内容联系起来
1489 | >
1490 | >
1491 |
1492 | - 在所有的组件中加上一个`ref`,然后再js中获取对应的值(由于我们直接在组件中使用ref,如果直接.value获取到的是组件本身,需要在`.value`的基础上继续`.$el`拿到他的根元素)
1493 | - 如果单纯的`ref`的话,我们需要给每个组件(除了第一个)都绑定上,然后重复的进行上面的操作。这样的操作肯定是不好的,会产生大量重复的代码。所以我们使用`:ref`动态绑定函数
1494 | - 但是这里下拉的时候会不断的刷新,对于性能是一个比较大的浪费,优化的方式就是在刷新的这部分内容中加上`memo`,设置只有`mainPart`发生了变化才会更新
1495 |
1496 | > `memo` 函数是 Vue 3 的一个新特性,用于优化渲染性能。它在 Vue 3 的 Composition API 中提供,用于缓存并跟踪响应式值的更改,从而在值发生更改时仅重新计算依赖它的副作用。
1497 | >
1498 | > `memo` 是一个接受一个工厂函数作为参数的函数。工厂函数会在第一次调用时执行,其返回值将被缓存。后续调用将返回缓存的结果,直到依赖的响应式值发生变化。一旦依赖项发生变化,工厂函数将再次执行,缓存并返回新的结果
1499 |
1500 | - 我们`TabControl`的内容是由`:title`进行控制的,如果直接写入一个数组来显示内容的话,其实就跟下面`描述`、`设施`、`评论`等内容没有关联上了
1501 | - 所以我们可以给组件再加上一个`name`属性,然后将`name`属性跟`:ref`关联上,组成一对的键值对
1502 |
1503 | - **这一步比较复杂**,需要慢慢构思
1504 |
1505 | ## 移动适配-打包
1506 |
1507 | ### 详情页返回小bug逻辑判断
1508 |
1509 | > 目前的情况下在详情页中点击左上角的返回会报错。这是因为在点击返回的时候,详情页的组件都会进行销毁,但是里面的`getSectionRef`会被触发,但是组件已经销毁了,获取到组件的信息就是`null`,从`null`中获取值就会报错,所以只需要在这里加个判断就行
1510 |
1511 | 
1512 |
1513 | ### 页面滚动匹配TabControl索引
1514 |
1515 | - 目前有个问题还未解决,当我们详情页下拉的时候,`TabControl`索引应该要随着下拉内容的变化而变化,而不是只有点击的时候才进行变化
1516 | - 需求: 页面滚动, 滚动到一定的位置时, 显示正确的tabControl的索引(标题)
1517 |
1518 |
1519 |
1520 | - 其中用到的算法
1521 |
1522 | ```javascript
1523 | // 页面滚动,滚动时匹配对应的tabControl
1524 | const tabControlRef = ref()
1525 | watch(scrollTop, async (newValue) => {//距离顶点 新的值
1526 | const els = Object.values(sectionEls.value)//获取tabControl身上的根元素
1527 | const values = els.map(el => el.offsetTop)//映射根元素距离顶部的距离
1528 | // 根据newValue去匹配想要的索引
1529 | let index = values.length - 1//获取tabControl对应的索引值
1530 | for (let i = 0; i < values.length; i++) {
1531 | if(values[i] > newValue + 44){
1532 | index = i - 1
1533 | break
1534 | }
1535 | }
1536 | await nextTick();
1537 | // 调用子组件实例的方法
1538 | if(tabControlRef.value) tabControlRef.value.setCurrentIndex(index);
1539 | })
1540 | ```
1541 |
1542 | - 在父组件中的子组件上添加`ref`的作用是获取子组件的实例
1543 |
1544 | ### 点击tabs的跳动bug处理
1545 |
1546 | 在前面中,百度地图的API会报警告如下:
1547 |
1548 | 
1549 |
1550 | - 解决方法:
1551 |
1552 | - 在`index.html`中修改一下内容,警告就能消除掉
1553 |
1554 | 
1555 |
1556 | - 我们在点击`tabControl`的时候,如果从第一个跳转到第三个,或者就是说跳转的中间还有其他内容的话,会有一个颜色一格一格跳过去的停顿感,而不是直接一步到位跳转过去。
1557 | - 解决方案:声明一个变量`isClick`(存放布尔值),当我们触发点击事件的时候,就不执行过渡效果(跳过我们设置触发滑动索引的算法),滑动的时候就触发过渡效果
1558 | - 但是当我们通过点击滚动到对应位置的时候,需要将`isClick`设置回去,不然点击完后继续滑动`TabControl`就没办法动态颜色切换了。所以我们需要再设置一个变量`currentDistance`,然后在点击事件里面将当前距离顶部的距离赋值给这个变量,然后在滑动索引算法中判断`currentDistance`和通过滑动距离顶部的值是否相同,相同的话就将`isClick`重新设置为false
1559 |
1560 | ### 切换页面的keep-alive操作
1561 |
1562 | > 我们在首页浏览界面的时候,发送网络请求分页请求数据的时候,如果突然跳转到其他界面去的话,再重新回到首页,网络请求又会重新加载一遍,但是网络请求分页的时候,只会从我们刚刚跳转前请求第几个分页数据的基础上继续向后请求,忽略掉了前面分页1、2、3...的请求
1563 | >
1564 | > - 原因是:我们离开了首页组件的时候,会销毁组件的。但是其中的数据保存在`useHomeStore`中,这是全局的数据
1565 | > - 可以使用保持活跃的`keep-alive`让首页不被销毁,这个就可以在`App.vue`组件中定义这一点了,但是这个`keep-alive`是需要有name的,但是我们setup语法糖中是没办法定义name名字的,不过这个问题目前已经被`B站UP主小满zs`解决了,通过他写的插件可以实现`
12 |
13 |