前言
此 uniapp-blog 项目是基于 uni-app + uview-ui 开发
效果图
完整效果请看:sdjBlog.cn
功能描述
已经实现功能
- 注册登录
- 文章列表
- 文章点赞评论
- 文章分类
- 留言列表
- 个人中心
项目结构
- public index.html首页
- src
- components
- ArticleList 文章列表
- LoginModal 注册登录弹框
- MessageBarrage 留言弹幕
- NavHeader 头部导航
- pages
- classify
- articleTag 分类标签列表
- index 文章分类列表
- home
- answer 你的答案
- articleDetail 文章详情
- index 博客首页
- search 文章搜索页
- view 文章每日一看
- message 留言列表
- person
- index 个人中心
- infoArticle 历史足迹
- infoData 个人信息
- static 字体图标和图片
- store Vuex状态管理
- utils API请求和路由跳转封装
- App.vue 配置App全局样式以及监听应用生命周期
- main.js Vue初始化入口文件
- manifest.json 配置应用名称、appid, logo, 版本等打包信息
- pages.json 页面路由、选项卡等页面信息
- uni.scss scss全局样式
- unpackage app打包图片
注册登录
注册登录使用弹出框封装,通过 modelShow 来控制弹出框显示隐藏,组件内部通过 show 来同步控制弹出框状态
<u-popup mode="center" width='85%' v-model="show" :closeable='true'
:mask-close-able='false' border-radius='16' @close='popupClose'></u-popup>
props: {
modelShow: {
type: Boolean,
default: false,
}
},
data(){
return {
show: false
}
},
methods: {
popupClose() {
this.$emit("closeModal", "");
}
},
watch: {
modelShow(val) {
if (val) {
this.show = val;
}
}
}
文章评论
评论输入框固定在页面底部,当input
聚焦唤起键盘影响移动端底部fixed
定位,可以通过窗口变化来重新计算赋值高度
try {
const res = uni.getSystemInfoSync();
this.windowHeight = res.windowHeight;
} catch (e) {
// error
}
uni.onWindowResize((res) => {
if(this.windowHeight - res.size.windowHeight > 80){
this.heightChange = true
}else{
this.heightChange = false
}
})
文章搜索
-
记录搜索历史,并存储在本地,最多存储最新搜索的8个记录
-
搜索文字高亮,通过正则替换标题样式
// 存储历史搜索
let keyword = this.keyword;
this.historyList = uni.getStorageSync("historyList") || [];
if(keyword && selectBool == '0'){
if(uni.getStorageSync("historyList")){
let historyList = uni.getStorageSync("historyList");
historyList.unshift(keyword);
historyList = [...new Set(historyList)];
if(historyList.length > 8){
historyList = historyList.slice(0,8);
}
this.historyList = historyList;
uni.setStorageSync("historyList", historyList)
}else{
let historyList = [keyword];
this.historyList = historyList;
uni.setStorageSync("historyList", historyList);
}
}
//搜索文字高亮
let replaceReg = new RegExp(keyword, 'g');
dataList.forEach(item=>{
item.image = `${baseURL}/blogAdmin/file/down?downId=${item.imgId}`;
item.createTime = item.createTime.split(' ')[0];
item.title = item.title.replace(replaceReg, `<span style='color: #1BA2E8;'>${keyword}</span>`)
})
Vuex实现
uView
简化Vuex
在页面中使用,通过mixin全局混入,避免频繁的手动引入
//state声明
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let lifeData = {};
try{
// 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的
lifeData = uni.getStorageSync('lifeData');
}catch(e){
}
// 需要永久存储,且下次APP启动需要取出的,在state中的变量名
let saveStateKeys = ['vuex_userInfo', 'vuex_token'];
// 保存变量到本地存储中
const saveLifeData = function(key, value){
// 判断变量名是否在需要存储的数组中
if(saveStateKeys.indexOf(key) != -1) {
// 获取本地存储的lifeData对象,将变量添加到对象中
let tmp = uni.getStorageSync('lifeData');
// 第一次打开APP,不存在lifeData变量,故放一个{}空对象
tmp = tmp ? tmp : {};
tmp[key] = value;
// 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中
uni.setStorageSync('lifeData', tmp);
}
}
const store = new Vuex.Store({
state: {
// 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量
// 加上vuex_前缀,是防止变量名冲突,也让人一目了然
vuex_userInfo: lifeData.vuex_userInfo ? lifeData.vuex_userInfo : {},
vuex_token: lifeData.vuex_token ? lifeData.vuex_token : '',
// 如果vuex_version无需保存到本地永久存储,无需lifeData.vuex_version方式
vuex_version: '1.0.1',
},
mutations: {
$storeLife(state, payload) {
// 判断是否多层级调用,state中为对象存在的情况,诸如vuex_userInfo.info.score = 1
let nameArr = payload.name.split('.');
let saveKey = '';
let len = nameArr.length;
if(nameArr.length >= 2) {
let obj = state[nameArr[0]];
for(let i = 1; i < len - 1; i ++) {
obj = obj[nameArr[i]];
}
obj[nameArr[len - 1]] = payload.value;
saveKey = nameArr[0];
} else {
// 单层级变量,在state就是一个普通变量的情况
state[payload.name] = payload.value;
saveKey = payload.name;
}
// 保存变量到本地,见顶部函数定义
saveLifeData(saveKey, state[saveKey])
}
}
})
export default store
//mixin
import { mapState } from 'vuex'
import store from "@/store"
// 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中
let $uStoreKey = [];
try{
$uStoreKey = store.state ? Object.keys(store.state) : [];
}catch(e){
}
module.exports = {
created() {
// 将vuex方法挂在到$u中
// 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗')
// 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1')
this.$u.vuex = (name, value) => {
this.$store.commit('$storeLife', {
name,value
})
}
},
computed: {
// 将vuex的state中的所有变量,解构到全局混入的mixin中
...mapState($uStoreKey)
}
}
问题记录
u-popup组件只有打开才会开始渲染,所以需要监听打开事件open,来对弹框中的u-form进行校验初始化
<u-popup mode="center" width='85%' v-model="show" :closeable='true'
:mask-close-able='false' border-radius='16' @open='popupOpen' @close='popupClose'></u-popup>
popupOpen() {
//触发校验
this.$refs.uLoginForm.setRules(this.loginRules);
this.$refs.uRegisterForm.setRules(this.registerRules);
}
微信小程序中u-form表单重置只清理了校验,没初始化数据,需要手动清空数据
popupClose() {
this.$emit("closeModal", "");
// #ifdef MP-WEIXIN
this.loginForm = {
name: "",
password: ""
}
this.registerForm = {
name: "",
password: "",
confirPwd: "",
email: ""
}
// #endif
this.$refs.uLoginForm.resetFields();
this.$refs.uRegisterForm.resetFields();
}
微信小程序v-for中u-image组件会出现宽度设置100%无法继承撑开,可以使用/deep/ u-image来设置100%
.box-title{
width: 100%;
height: 200rpx;
color: #fff;
border-radius: 20rpx;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
position: relative;
/* #ifdef MP-WEIXIN */
/deep/ u-image{
width: 100%;
}
/* #endif */
}
v-show的隐藏display:none层级不高。可以设置:style或v-if来代替v-show,组件中使用可以,页面中会出现无法隐藏
<view class="classify-empty" v-if='tagLsit.length == 0'>
<u-empty mode="list" text='文章分类数据为空' margin-top='100'></u-empty>
</view>
数组arr设置v-for中的:key为item时,当数组新增元素时,点击事件item传值获取不对,会错位,所以把key修改成index,当数组对象中存在index为key时,不能设置:key为index
<view class="list-item" v-for='(item,index) in historyList' :key='index'>
<u-tag :text="item" @click='itemClick(item)' type="info" shape='circle' />
</view>
微信小程序不支持v-html,考虑兼容性,所以使用rich-text富文本代替
<view class='box-title u-line-1'>
<rich-text :nodes="item.title"></rich-text>
</view>
说明
-
项目通过
vue-cli
命令行方式创建 -
安卓APP通过
HBuilderX
工具打包
建立安装
# 安装依赖
$ npm install
# web端开发环境
$ npm run dev
# web端打包生产环境
$ npm run build
# 微信小程序端打包生产环境
$ npm run build:mp-weixin
微信小程序
项目地址:
项目系列文章: