uni-app小案例2-新闻

首页:
在这里插入图片描述

个人中心:
在这里插入图片描述

1. 全局css

App.vue:

<style>
	/*每个页面公共css */
    view{
		box-sizing: border-box;
    }
</style>

全局盒子模型

2. pages.json

{
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
			"path": "pages/index/index",
			"style": {
				"navigationBarTitleText": "新闻"
			}
		},
		{
			"path" : "pages/detail/detail",
			"style" : {
				"navigationBarTitleText" : "新闻详情"
			}
		},
		{
			"path" : "pages/user/user",
			"style" : 
			{
				"navigationBarTitleText" : "个人中心"
			}
		}
	],
	"globalStyle": {
		"navigationBarTextStyle": "white",
		"navigationBarTitleText": "新闻",
		"navigationBarBackgroundColor": "#31C27C",
		"backgroundColor": "#F8F8F8"
	},
	"uniIdRouter": {},
	"tabBar": {
		"color":"#666",
		"selectedColor": "#31C27C",
		"list": [
			{
				"text": "首页",
				"pagePath": "pages/index/index",
				"iconPath": "static/images/home.png",
				"selectedIconPath": "static/images/home-h.png"
			},{
				"text": "个人",
				"pagePath": "pages/user/user",
				"iconPath": "static/images/user.png",
				"selectedIconPath": "static/images/user-h.png"
			}
		]
	}
}

3. 工具类

utils/tool.js:

//时间间隔函数
export function timeInterval(timesData) {
  //如果时间格式是正确的,那下面这一步转化时间格式就可以不用了
  var dateBegin = timesData;//将-转化为/,使用new Date    
  var dateEnd = new Date();//获取当前时间   
  var dateDiff = Math.abs( dateEnd.getTime() - dateBegin );  //时间差的毫秒数
  var yearDiff = Math.floor(dateDiff / (24 * 3600 * 1000*365));
  var dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000));  //计算出相差天数
  var leave1 = dateDiff % (24 * 3600 * 1000)    //计算天数后剩余的毫秒数
  var hours = Math.floor(leave1 / (3600 * 1000))//计算出小时数
  //计算相差分钟数
  var leave2 = leave1 % (3600 * 1000)    //计算小时数后剩余的毫秒数
  var minutes = Math.floor(leave2 / (60 * 1000))//计算相差分钟数
  //计算相差秒数
  var leave3 = leave2 % (60 * 1000)      //计算分钟数后剩余的毫秒数
  var seconds = Math.round(leave3 / 1000);
  var timesString = '';
  if (yearDiff!=0){
    timesString = yearDiff + '年前';
  } else if (yearDiff == 0   && dayDiff != 0) {
    timesString = dayDiff + '天前';
  } else if (dayDiff == 0 && hours != 0) {
    timesString = hours + '小时前';
  } else if (hours == 0 && minutes != 0) {      
    timesString = minutes + '分钟前';      
  } else if (minutes == 0 && seconds<60){ 
    timesString = '刚刚'; 
  } 
  return timesString 
}


// 日期格式化
export function parseTime(time, pattern) {
  if (arguments.length === 0 || !time) {
    return null
  }
  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
      time = parseInt(time)
    } else if (typeof time === 'string') {
      time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
    }
    if ((typeof time === 'number') && (time.toString().length === 10)) {
      time = time * 1000
    }
    date = new Date(time)
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
    let value = formatObj[key]
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
    if (result.length > 0 && value < 10) {
      value = '0' + value
    }
    return value || 0
  })
  return time_str
}

4. tabBar 图片

放置目录 static\images\

5. 共有组件

components\newsbox\newsbox.vue:

新闻:显示图片、标题、作者、浏览量
在这里插入图片描述

个人中心:显示图片、标题、浏览时间
在这里插入图片描述

<template>
	<view class="newsbox">
		<view class="pic">
			<image :src="item.picurl" mode="aspectFill"></image>
		</view>
		<view class="text">
			<view class="title">				
				{{item.title}}	
			</view>
			<view class="info" v-if="!item.looktime">
				<text>{{item.author}}</text>
				<text>{{item.hits}}浏览</text>
			</view>
			<view class="info" v-else>
				<text>浏览时间:{{item.looktime}}</text>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		name:"newsbox",
		props:{
			item:{
				type:Object,
				default(){
					return {
						title:"组件内默认的标题",
						author:"张三",
						hits:668,
						picurl:"../../static/images/nopic.jpg"
					}
				}
			}
		},
		data() {
			return {
				
			};
		}
	}
</script>

<style lang="scss">
.newsbox{
	display: flex;
	.pic{
		width: 230rpx;
		height: 160rpx;
		image{
			width: 100%;
			height: 100%;
		}
	}
	.text{		
		flex:1;
		padding-left:20rpx;  // 距离左边pic留出 20rpx 空白
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		.title{
			font-size: 36rpx;
			color:#333;
			text-overflow: -o-ellipsis-lastline;
			overflow: hidden;				//溢出内容隐藏
			text-overflow: ellipsis;		//文本溢出部分用省略号表示
			display: -webkit-box;			//特别显示模式
			-webkit-line-clamp: 2;			//行数
			line-clamp: 2;					
			-webkit-box-orient: vertical;	//盒子中内容竖直排列			
		}
		.info{
			font-size: 26rpx;
			color:#999;
			text{
				padding-right: 30rpx;  // 文本内容右边留出 30rpx 空白
			}
		}
	}
}
</style>

6. 首页

pages\index\index.vue:

显示固定导航栏、新闻列表数据
在这里插入图片描述

<template>
	<view>
		<scroll-view scroll-x class="navigationScroll">
			<view class="item" :class="activeId == item.id ? 'active' : ''" 
					v-for="(item,index) in navArr" :key="item.id"
					@click="clickNav(item.id)">
				{{item.classname}}
			</view>			
		</scroll-view>
		
		<view class="content" >
			<div class="row" v-for="item in newsArr" :key="item.id">
				<newsbox :item="item" @click.native="goDetail(item)"></newsbox>
			</div>
		</view>
		
		<view class="nodata" v-if="!newsArr.length">
			<image src="../../static/images/nodata.png" mode="widthFix"></image>
		</view>
		
		<view class="loading" v-if="newsArr.length">			
			<view v-if="loading == 1">数据加载中...</view>
			<view v-if="loading == 2">没有更多了~~</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				navArr: [],
				activeId: '',
				newsArr: [],
				currentPage: 1,
				loading: 0       // 0默认  1加载中  2没有更多了
			}
		},
		onLoad() {
			this.getNavData();
		},
		onReachBottom(){
			console.log("到底部了")
			if(this.loading == 2){
				return;
			}
			this.loading = 1;
			this.currentPage++;
			this.getNewsData();
		},
		methods: {
			// 点击导航切换
			clickNav(id){
				this.activeId = id;
				this.newsArr = [];
				this.loading = 0;
				this.currentPage = 1;
				this.getNewsData();
			},
			//跳转到详情页
			goDetail(item){				
				uni.navigateTo({
					url:`/pages/detail/detail?cid=${item.classid}&id=${item.id}`
				})
			},
			// 获取导航列表数据
			getNavData(){
				uni.request({
					url: "https://ku.qingnian8.com/dataApi/news/navlist.php",
					success: (res) => {
						this.navArr = res.data;
						// 获取导航后,默认选取第一个导航加载数据
						this.activeId = this.navArr[0].id;
						this.currentPage = 1;
						this.getNewsData();
					}
				})
			},
			// 获取新闻列表数据
			getNewsData(){
				uni.request({
					url:"https://ku.qingnian8.com/dataApi/news/newslist.php",
					data:{						
						cid:this.activeId,
						page:this.currentPage
					},
					success: (res) => {
						// 如果触底获取内容为空,显示没有更多了~~
						if(res.data.length == 0){
							this.loading = 2;
						}
						// 拼接上次查询结果
						this.newsArr = [...this.newsArr, ...res.data]
					}
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
.navigationScroll{
	height: 100rpx;
	background: #F7F8FA;
	white-space: nowrap;
	position: fixed;        // 固定元素
	top: var(--window-top); // 元素距离视口顶部的距离
	left: 0;
	z-index: 10;            // 避免被其他元素遮挡
	::-webkit-scrollbar {   // 隐藏滚动条
	  display: none;
	}
	.item{
		font-size: 40rpx;
		display: inline-block;
		line-height: 100rpx;
		padding:0 30rpx;
		color:#333;		
		&.active{
			color:#31C27C;
		}
	}
}
.content{
	padding:30rpx;
	padding-top:130rpx;	    // 固定导航栏的高度 100rpx + padding:30rpx,避免向上滑动遮挡内容
	.row{
		border-bottom:1px dotted #efefef;  // 分割线
		padding:20rpx 0;
	}
}
.nodata{
	display: flex;
	justify-content: center;
	image{
		width: 360rpx;
	}
}
.loading{
	text-align: center;
	font-size: 26rpx;
	color:#888;
	line-height: 2em;
}
</style>
  • &.active& 通常出现在 Sass/Less 等 CSS 预处理器中,相当于 .item.active,即同时具有 itemactive 两个类名的元素。
    • 典型应用场景:导航菜单高亮选项卡(Tabs)或步骤条
  • position: fixed:是 CSS 中用于将元素 固定相对于浏览器视口 进行定位的属性。常结合 top、right、bottom、left 控制位置。z-index 控制层叠顺序。
    • 典型应用场景:固定导航栏/页眉/页脚、悬浮按钮(如返回顶部)
  • z-index:使用 z-index 调整层叠顺序,避免被其他元素遮挡。
  • var(–window-top):动态计算元素距离视口顶部的距离
  • ::-webkit-scrollbar:控制滚动条

7. 详情页

pages\detail\detail.vue:
在这里插入图片描述

<template>
	<view class="detail">
		<view class="title">{{detail.title}}</view>
		<view class="info">
			<view class="author">编辑:{{detail.author}}</view>
			<view class="time">发布日期:{{detail.posttime}}</view>
		</view>
		<view class="content">
			<rich-text :nodes="detail.content"></rich-text>			
		</view>
		<view class="description">
			声明:本站的内容均采集与腾讯新闻,如果侵权请联系管理(513894357@qq.com)进行整改删除,本站进行了内容采集不代表本站及作者观点,若有侵犯请及时联系管理员,谢谢您的支持。
		</view>
	</view>
</template>

<script>
import {parseTime} from "@/utils/tool.js"
	
	export default {
		data() {
			return {
				options: null,
				detail: {}
			}
		},
		onLoad(e) {
			this.options = e;
			this.getDetail();
		},
		methods: {
			// 获取详情
			getDetail(){
				uni.request({
					url: "https://ku.qingnian8.com/dataApi/news/detail.php",
					data: this.options,
					success: (res) => {
						// 格式化时间戳
						res.data.posttime = parseTime(res.data.posttime)
						// 在所有 <img> 标签中添加内联样式 style="max-width:100%",通常用于实现图片的自适应宽度。
						res.data.content = res.data.content.replace(/<img/gi, '<img style="max-width:100%"')			
						this.detail = res.data
						
						this.saveHistory()
						// 设置导航文字
						uni.setNavigationBarTitle({
							title: this.detail.title
						})
					}
				})
			},
			// 保存浏览历史
			saveHistory() {
				let item = {
					id: this.detail.id,
					classid: this.detail.classid,
					picurl: this.detail.picurl,
					title: this.detail.title,
					looktime: parseTime(Date.now())
				}
				// 获取原有数组
				let historyArr = uni.getStorageSync("historyArr") || [];
				// 查找删除重复项
				let index = historyArr.findIndex(x => x.id == item.id);
				if (index>=0) {
					historyArr.splice(index, 1);
				}
				// 将浏览项添加到数组头部
				historyArr.unshift(item);
				// 截取数组前十位
				historyArr = historyArr.slice(0, 10);
				uni.setStorageSync("historyArr", historyArr);
			}
		}
	}
</script>

<style lang="scss">
.detail{
	padding:30rpx;
	.title{
		font-size: 46rpx;
		color:#333;
	}
	.info{
		background: #F6F6F6;
		padding:20rpx;
		font-size: 25rpx;
		color:#666;
		display: flex;
		justify-content: space-between;  // 两端对齐
		margin:40rpx 0;
	}
	.content{
		padding-bottom:50rpx;		
	}
	.description{
		background: #FEF0F0;
		font-size: 26rpx;
		padding:20rpx;
		color:#F89898;
		line-height: 1.8em;
	}
}
</style>
  • replace():用于替换匹配的内容。正则表达式 /<img/gi
    • <img:匹配 HTML 中的 <img 标签开头。
    • g:全局匹配(替换所有出现的 <img)。
    • i:不区分大小写(同时匹配 <IMG<Img 等)。
  • push(item):数组尾部添加一个或多个元素
  • pop:移除尾部元素
  • unshift(item):头部添加一个或多个元素
  • shift:移除头部元素
  • splice(start, deleteCount, …items):删除/替换元素(万能方法)
  • slice(start, end):截取子数组(不修改原数组)
  • findIndex(callback):第一个匹配的索引

8. 个人中心

pages\user\user.vue:

显示浏览记录
在这里插入图片描述

<template>
	<view class="user">
		<view class="top">
			<image src="../../static/images/history.png" mode=""></image>
			<view class="text">浏览历史</view>
		</view>
		<view class="content">
			<view class="row" v-for="item in listArr">
				<newsbox :item="item" @click.native="goDetail(item)"></newsbox>
			</view>			
		</view>
		
		<view class="nohistory" v-if="!listArr.length">
			<image src="../../static/images/nohis.png" mode="widthFix"></image>
			<view class="text">暂无浏览记录</view>
		</view>
	</view>
</template>


<script>
	export default {
		data() {
			return {
				listArr:[]
			}
		},
		// 每次页面显示时都会触发,读取缓存,重新刷新加载内容。
		onShow(){
			this.getData()
		},
		methods: {
			//获取缓存浏览记录
			getData(){
				let historyArr = uni.getStorageSync("historyArr") || []
				this.listArr = historyArr
			},
			//跳转到详情页
			goDetail(item){
				uni.navigateTo({
					url:`/pages/detail/detail?cid=${item.classid}&id=${item.id}`
				})
			}
		}
	}
</script>

<style lang="scss">
.user{
	.top{
		padding:50rpx 0;
		background: #F8F8F8;
		color:#666;
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		image{
			width: 150rpx;
			height: 150rpx;
		}
		.text{
			font-size: 38rpx;		
			padding-top: 20rpx;
		}
	}
	.content{
		padding:30rpx;
		.row{
			border-bottom:1px dotted #efefef;
			padding:20rpx 0;
		}
	}
	.nohistory{
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		image{
			width: 450rpx;
		}
		.text{
			font-size: 26rpx;
			color:#888;
		}
	}
}
</style>

9. 域名问题

https://ku.qingnian8.com/dataApi/news/navlist.php 域名备案过期,导致浏览器可以访问,但但服务器和微信访问报错。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值