首页:
个人中心:
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,即同时具有item
和active
两个类名的元素。- 典型应用场景:导航菜单高亮、选项卡(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 域名备案过期,导致浏览器可以访问,但但服务器和微信访问报错。