新闻初版

This commit is contained in:
huangge1199 2024-08-05 14:25:42 +08:00
commit 1b52c78fe7
31 changed files with 2942 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
# Mac
.DS_Store
**/.DS_Store
# vim/vi
*.swp
# JavaScript
node_modules/
.node_modules/
.eslintcache
unpackage/dist/build/
unpackage/dist/dev/
# python
*.pyc
/.hbuilderx/
/unpackage/

53
App.vue Normal file
View File

@ -0,0 +1,53 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch');
// #ifdef APP-NVUE
plus.screen.lockOrientation('portrait-primary');
let appid = plus.runtime.appid;
if (appid && appid.toLocaleLowerCase() != "hbuilder") {
uni.request({
url: 'https://uniapp.dcloud.io/update', //
data: {
appid: plus.runtime.appid,
version: plus.runtime.version
},
success: (res) => {
console.log('success', res);
if (res.statusCode == 200 && res.data.isUpdate) {
let openUrl = plus.os.name === 'iOS' ? res.data.iOS : res.data.Android;
//
uni.showModal({
title: '更新提示',
content: res.data.note ? res.data.note : '是否选择更新',
success: (showResult) => {
if (showResult.confirm) {
plus.runtime.openURL(openUrl);
}
}
})
}
}
})
}
var domModule = weex.requireModule('dom');
domModule.addRule('fontFace', {
'fontFamily': "texticons",
'src': "url('./static/text-icon.ttf')"
});
// #endif
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 DCloud
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# uni-template-news
基于uni-app开发的新闻/资讯类App模板
## 体验方式
同步/下载项目后,拖入 HBuilderX 中即可运行体验。
## 特点
* 兼容多个平台
* 自定义组件实现顶部选项卡切换
* App 平台使用 nvue 进一步优化体验
### 平台支持
* app-vue
* app-nvue
* H5
* 微信小程序
* 支付宝小程序
* 百度小程序
* 头条小程序
## 注意事项
* 非 App-nvue 平台暂不支持下拉刷新

23
changelog.md Normal file
View File

@ -0,0 +1,23 @@
## 3.1.72022-06-30
- 新增 支持 ios 安全区
## 3.1.62022-02-08
- 移除无效的H5平台百度统计
## 3.1.52021-11-26
- 新增 小程序平台支持VUE3 (HBuilderX 3.3+)
- 修复 某些情况下无法滚动的问题
## 3.1.42021-07-30
- 适配 VUE3 (需要 HBuilderX 3.2.0+)
## 3.1.32021-07-28
- 适配 VUE3 (需要 HBuilderX 3.2.0+)
## 3.0.02021-02-19
- 修复 某些操作导致上拉加载无效的问题
## 2.9.52020-10-23
- 修复 app 平台 ios 顶部页签高度显示不正确的问题
## 2.9.22020-09-27
- 修复app平台运行时出错的bug
## 2.9.12020-09-27
- 修复加载更多文字过大问题
## 2.9.02020-09-25
- 适配大屏
## 2.4.22019-11-18
- 重构代码,支持编译到所有平台

352
common/html-parser.js Normal file
View File

@ -0,0 +1,352 @@
/*
* HTML5 Parser By Sam Blowes
*
* Designed for HTML5 documents
*
* Original code by John Resig (ejohn.org)
* http://ejohn.org/blog/pure-javascript-html-parser/
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* ----------------------------------------------------------------------------
* License
* ----------------------------------------------------------------------------
*
* This code is triple licensed using Apache Software License 2.0,
* Mozilla Public License or GNU Public License
*
* ////////////////////////////////////////////////////////////////////////////
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* ////////////////////////////////////////////////////////////////////////////
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Simple HTML Parser.
*
* The Initial Developer of the Original Code is Erik Arvidsson.
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
* Reserved.
*
* ////////////////////////////////////////////////////////////////////////////
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ----------------------------------------------------------------------------
* Usage
* ----------------------------------------------------------------------------
*
* // Use like so:
* HTMLParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* // or to get an XML string:
* HTMLtoXML(htmlString);
*
* // or to get an XML DOM Document
* HTMLtoDOM(htmlString);
*
* // or to inject into an existing document/DOM node
* HTMLtoDOM(htmlString, document);
* HTMLtoDOM(htmlString, document.body);
*
*/
// Regular Expressions for parsing tags and attributes
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
// fixed by xxx 将 ins 标签从块级名单中移除
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
var special = makeMap('script,style');
function HTMLParser(html, handler) {
var index;
var chars;
var match;
var stack = [];
var last = html;
stack.last = function () {
return this[this.length - 1];
};
while (html) {
chars = true; // Make sure we're not in a script or style element
if (!stack.last() || !special[stack.last()]) {
// Comment
if (html.indexOf('<!--') == 0) {
index = html.indexOf('-->');
if (index >= 0) {
if (handler.comment) {
handler.comment(html.substring(4, index));
}
html = html.substring(index + 3);
chars = false;
} // end tag
} else if (html.indexOf('</') == 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
} // start tag
} else if (html.indexOf('<') == 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf('<');
var text = index < 0 ? html : html.substring(0, index);
html = index < 0 ? '' : html.substring(index);
if (handler.chars) {
handler.chars(text);
}
}
} else {
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
if (handler.chars) {
handler.chars(text);
}
return '';
});
parseEndTag('', stack.last());
}
if (html == last) {
throw 'Parse Error: ' + html;
}
last = html;
} // Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag('', stack.last());
}
}
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag('', tagName);
}
unary = empty[tagName] || !!unary;
if (!unary) {
stack.push(tagName);
}
if (handler.start) {
var attrs = [];
rest.replace(attr, function (match, name) {
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
if (!tagName) {
var pos = 0;
} // Find the closest opened tag of the same type
else {
for (var pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) {
break;
}
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--) {
if (handler.end) {
handler.end(stack[i]);
}
} // Remove the open elements from the stack
stack.length = pos;
}
}
}
function makeMap(str) {
var obj = {};
var items = str.split(',');
for (var i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
function removeDOCTYPE(html) {
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
}
function parseAttrs(attrs) {
return attrs.reduce(function (pre, attr) {
var value = attr.value;
var name = attr.name;
if (pre[name]) {
pre[name] = pre[name] + " " + value;
} else {
pre[name] = value;
}
return pre;
}, {});
}
function parseHtml(html) {
html = removeDOCTYPE(html);
var stacks = [];
var results = {
node: 'root',
children: []
};
HTMLParser(html, {
start: function start(tag, attrs, unary) {
var node = {
name: tag
};
if (attrs.length !== 0) {
node.attrs = parseAttrs(attrs);
}
if (unary) {
var parent = stacks[0] || results;
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
} else {
stacks.unshift(node);
}
},
end: function end(tag) {
var node = stacks.shift();
if (node.name !== tag) console.error('invalid state: mismatch end tag');
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
chars: function chars(text) {
var node = {
type: 'text',
text: text
};
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
comment: function comment(text) {
var node = {
node: 'comment',
text: text
};
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
});
return results.children;
}
export default parseHtml;

52
common/util.js Normal file
View File

@ -0,0 +1,52 @@
function friendlyDate(timestamp) {
var formats = {
'year': '%n% 年前',
'month': '%n% 月前',
'day': '%n% 天前',
'hour': '%n% 小时前',
'minute': '%n% 分钟前',
'second': '%n% 秒前',
};
var now = Date.now();
var seconds = Math.floor((now - timestamp) / 1000);
var minutes = Math.floor(seconds / 60);
var hours = Math.floor(minutes / 60);
var days = Math.floor(hours / 24);
var months = Math.floor(days / 30);
var years = Math.floor(months / 12);
var diffType = '';
var diffValue = 0;
if (years > 0) {
diffType = 'year';
diffValue = years;
} else {
if (months > 0) {
diffType = 'month';
diffValue = months;
} else {
if (days > 0) {
diffType = 'day';
diffValue = days;
} else {
if (hours > 0) {
diffType = 'hour';
diffValue = hours;
} else {
if (minutes > 0) {
diffType = 'minute';
diffValue = minutes;
} else {
diffType = 'second';
diffValue = seconds === 0 ? (seconds = 1) : seconds;
}
}
}
}
}
return formats[diffType].replace('%n%', diffValue);
}
export {
friendlyDate
}

188
components/nodata.nvue Normal file
View File

@ -0,0 +1,188 @@
<template>
<view class="nodata">
<view class="nodata-content">
<view class="text-view a-i-c j-c-c t-a-c">
<text class="title">{{textTypes[networkType]}}</text>
</view>
<view class="icon-view"></view>
<view class="opera-view">
<view class="btn btn-default" v-if="networkType!='none'" @click="retry">
<text class="btn-text">重试</text>
</view>
<view class="btn btn-default" v-if="networkType=='none'" @click="openSettings">
<text class="btn-text">设置</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'nodata',
data() {
return {
textTypes: {
none: "暂无网络"
},
isConnected: false,
networkType: "none"
}
},
mounted() {
this.isIOS = (uni.getSystemInfoSync().platform === 'ios');
uni.onNetworkStatusChange((res) => {
this.isConnected = res.isConnected;
this.networkType = res.networkType;
});
uni.getNetworkType({
success: (res) => {
this.networkType = res.networkType;
}
});
},
methods: {
retry() {
this.$emit('retry');
},
async openSettings() {
if (this.networkType == "none") {
this.openSystemSettings();
return;
}
},
openAppSettings() {
this.gotoAppSetting();
},
openSystemSettings() {
if (this.isIOS) {
this.gotoiOSSetting();
} else {
this.gotoAndroidSetting();
}
},
network() {
var result = null;
var cellularData = plus.ios.newObject("CTCellularData");
var state = cellularData.plusGetAttribute("restrictedState");
if (state == 0) {
result = null;
console.log("StateUnknown");
} else if (state == 2) {
result = 1;
console.log("已经开启了互联网权限:NotRestricted");
} else if (state == 1) {
result = 2;
console.log("Restricted");
}
plus.ios.deleteObject(cellularData);
return result;
},
gotoAppSetting() {
if (this.isIOS) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
},
gotoiOSSetting() {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
var setting2 = NSURL2.URLWithString("App-prefs:root=General");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
},
gotoAndroidSetting() {
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent(Settings.ACTION_SETTINGS);
mainActivity.startActivity(intent);
}
}
}
</script>
<style>
.a-i-c {
align-items: center;
}
.j-c-c {
justify-content: center;
}
.t-a-c {
text-align: center;
}
.nodata {
flex: 1;
flex-direction: column;
/* #ifndef APP-PLUS */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
padding: 30px;
background-color: #f8f8f8;
}
.nodata-content {
transform: translateY(-50px);
}
.text-view {
margin-bottom: 40px;
}
.title {
color: #999999;
font-size: 18px;
}
.opera-view {
flex-direction: column;
align-items: center;
justify-content: center;
}
.btn {
padding: 5px 10px;
width: 128px;
flex-direction: row;
align-items: center;
justify-content: center;
text-align: center;
}
.btn-text {
color: #999999;
font-size: 15px;
}
.btn-default {
border-color: #999999;
border-style: solid;
border-width: 1px;
border-radius: 3px;
}
</style>

130
components/uni-list.vue Normal file
View File

@ -0,0 +1,130 @@
<template>
<!-- #ifdef APP-VUE -->
<view class="uni-list">
<slot />
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<list ref="list" class="uni-list" :enableBackToTop="enableBackToTop" loadmoreoffset="15" :scroll-y="scrollY" @loadmore="loadMore">
<slot />
</list>
<!-- #endif -->
<!-- #ifdef H5 || MP-WEIXIN || MP-QQ -->
<scroll-view class="uni-list" :enableBackToTop="enableBackToTop" :scroll-y="scrollY" @scrolltolower="loadMore">
<slot />
</scroll-view>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO -->
<scroll-view class="uni-list" :scroll-y="scrollY" @scrolltolower="loadMore">
<slot />
</scroll-view>
<!-- #endif -->
</template>
<script>
export default {
name: 'UniList',
'mp-weixin': {
options: {
multipleSlots: false
}
},
data() {
return {}
},
props: {
enableBackToTop: {
type: [Boolean, String],
default: false
},
scrollY: {
type: [Boolean, String],
default: true
}
},
created() {
// #ifndef APP-NVUE
this.pullDown = {
threshold: 95,
maxHeight: 200,
callRefresh: 'onrefresh',
callPullingDown: 'onpullingdown',
refreshSelector: '.uni-refresh'
};
this.height = 0;
this.canPullDown = false;
this.refreshInstance = {};
// #endif
},
methods: {
loadMore(e) {
this.$emit("scrolltolower");
},
resetLoadmore() {
this.$refs.list.resetLoadmore();
},
ontouchstart(e) {
if (!this.canPullDown) {
console.log("canPullDown", this.canPullDown);
return
}
this.height = 0;
this.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
this._updateRefresh(0);
this.refreshInstance.callMethod("onchange", true);
},
ontouchmove(e) {
if (!this.canPullDown) {
return
}
var oldHeight = this.height;
var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
var newHeight = endY - this.touchStartY;
if (newHeight > this.pullDown.maxHeight) {
return;
}
this._updateRefresh(newHeight);
newHeight = newHeight < this.pullDown.maxHeight ? newHeight : this.pullDown.maxHeight;
this.height = newHeight;
this.refreshInstance.callMethod(this.pullDown.callPullingDown, {
height: newHeight
});
},
ontouchend(e) {
if (!this.canPullDown) {
return
}
this.refreshInstance.callMethod("onchange", false);
if (this.height > this.pullDown.threshold) {
refreshInstance.callMethod(this.pullDown.callRefresh);
return;
}
this._updateRefresh(0);
},
_updateRefresh(height) {
this.refreshInstance.setStyle({
'height': height
});
}
}
}
</script>
<style scoped>
.uni-list {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
position: relative;
flex-direction: column;
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,85 @@
var pullDown = {
threshold: 95,
maxHeight: 200,
callRefresh: 'onrefresh',
callPullingDown: 'onpullingdown',
refreshSelector: '.uni-refresh'
};
function ready(newValue, oldValue, ownerInstance, instance) {
var state = instance.getState()
state.canPullDown = newValue;
console.log(newValue);
}
function touchStart(e, instance) {
var state = instance.getState();
state.refreshInstance = instance.selectComponent(pullDown.refreshSelector);
state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined);
if (!state.canPullDown) {
return
}
state.height = 0;
state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
state.refreshInstance.setStyle({
'height': 0
});
state.refreshInstance.callMethod("onchange", true);
}
function touchMove(e, ownerInstance) {
var instance = e.instance;
var state = instance.getState();
if (!state.canPullDown) {
return
}
var oldHeight = state.height;
var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
var height = endY - state.touchStartY;
if (height > pullDown.maxHeight) {
return;
}
var refreshInstance = state.refreshInstance;
refreshInstance.setStyle({
'height': height + 'px'
});
height = height < pullDown.maxHeight ? height : pullDown.maxHeight;
state.height = height;
refreshInstance.callMethod(pullDown.callPullingDown, {
height: height
});
}
function touchEnd(e, ownerInstance) {
var state = e.instance.getState();
if (!state.canPullDown) {
return
}
state.refreshInstance.callMethod("onchange", false);
var refreshInstance = state.refreshInstance;
if (state.height > pullDown.threshold) {
refreshInstance.callMethod(pullDown.callRefresh);
return;
}
refreshInstance.setStyle({
'height': 0
});
}
function propObserver(newValue, oldValue, instance) {
pullDown = newValue;
}
module.exports = {
touchmove: touchMove,
touchstart: touchStart,
touchend: touchEnd,
propObserver: propObserver
}

45
h5.template.html Normal file
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<script>
var UA = window.navigator.userAgent.toLowerCase();
var isAndroid = UA.indexOf('android') > 0;
var isIOS = /iphone|ipad|ipod|ios/.test(UA);
if (!(isAndroid || isIOS)) {
// 正式发布的时候使用,开发期间不启用。
// window.location.href = '/demo/news/website/pcguide.html';
}
</script>
<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
</head>
<body>
<!-- 该文件为 H5 平台的模板 HTML并非应用入口请勿直接运行此文件。 -->
<!-- 详见文档https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
<noscript>
<strong>Please enable JavaScript to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?fe3b7a223fc08c795f0f4b6350703e6f";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html>

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

26
main.js Normal file
View File

@ -0,0 +1,26 @@
import App from './App.vue'
const host = 'https://unidemo.dcloud.net.cn/';
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
Vue.prototype.$host = host;
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
createSSRApp
} from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

108
manifest.json Normal file
View File

@ -0,0 +1,108 @@
{
"name" : "news",
"appid" : "__UNI__8C5575B",
"description" : "基于uni-app开发的新闻/资讯类App模板",
"versionName" : "3.1.23",
"versionCode" : 1,
"transformPx" : false,
"uniStatistics" : {
"enable" : false
},
"compatConfig" : {
"MODE" : 2 // 2Vue23Vue2
},
"app-plus" : {
"usingComponents" : true,
"nvueCompiler" : "uni-app",
/* 5+App */
"modules" : {},
/* */
"distribute" : {
/* */
"android" : {
/* android */
"permissions" : [
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"permissionPhoneState" : {
"request" : "none"
},
"abiFilters" : [ "armeabi-v7a" ]
},
"ios" : {
"idfa" : false
},
/* ios */
"sdkConfigs" : {
"ad" : {}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
}
}
},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wxabb93d365d4475a4",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"h5" : {
"router" : {
"base" : "./",
"mode" : "hash"
},
"template" : "h5.template.html",
"optimization" : {
"treeShaking" : {
"enable" : true
}
}
},
"vueVersion" : "3"
}

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"id": "uni-template-news",
"name": "新闻资讯App模板",
"displayName": "新闻资讯App模板",
"version": "3.1.7",
"description": "基于uni-app开发的新闻/资讯类App模板",
"keywords": [
"模板",
"新闻",
"资讯",
"nvue",
"新闻模板"
],
"dcloudext": {
"category": [
"前端页面模板",
"uni-app前端项目模板"
]
}
}

32
pages.json Normal file
View File

@ -0,0 +1,32 @@
{
"pages": [{
"path": "pages/news/index",
"style": {}
},
{
"path": "pages/news/detail",
"style": {
"app-plus": {
"titleNView": {
"type": "transparent"
}
}
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "新闻资讯",
"navigationBarBackgroundColor": "#2F85FC",
"backgroundColor": "#FFFFFF"
},
"rightWindow": {
"path": "responsive/right-window.vue",
"style": {
"width": "calc(100vw - 450px)"
},
"matchMedia": {
"minWidth": 768
}
}
}

178
pages/news/detail.nvue Normal file
View File

@ -0,0 +1,178 @@
<template>
<view class="content">
<view class="banner" auto-focus>
<image class="banner-img" :src="banner.image_url"></image>
<view class="title-area">
<text class="title-text">{{banner.title}}</text>
</view>
</view>
<view class="article-meta">
<text class="article-meta-text article-author">{{banner.source}}</text>
<text class="article-meta-text article-text">发表于</text>
<text class="article-meta-text article-time">{{banner.datetime}}</text>
</view>
<view class="article-content">
<rich-text :nodes="content" style="font-size: 14px;"></rich-text>
</view>
<view class="comment-wrap"></view>
</view>
</template>
<script>
import htmlParser from '../../common/html-parser'
const FAIL_CONTENT = '<p>获取信息失败1</p>';
function parseImgs(nodes) {
nodes.forEach(node => {
if (
node.name === 'img' &&
node.attrs &&
node.attrs['data-img-size-val']
) {
const sizes = node.attrs['data-img-size-val'].split(',')
const width = uni.upx2px(720 * 0.9)
const height = parseInt(width * (sizes[1] / sizes[0]))
node.attrs.style = `width:${width};height:${height};`
}
if (Array.isArray(node.children)) {
parseImgs(node.children)
}
})
return nodes
}
export default {
data() {
return {
banner: {},
content: []
}
},
onShareAppMessage() {
return {
title: this.banner.title,
path: '/pages/detail/detail?query=' + JSON.stringify(this.banner)
}
},
onLoad(event) {
// 目前在某些平台参数会被主动 decode暂时这样处理。
this.load(event.query);
},
methods: {
load(e) {
var p = decodeURIComponent(e);
try {
this.banner = JSON.parse(p);
} catch (error) {
this.banner = JSON.parse(p);
}
uni.setNavigationBarTitle({
title: this.banner.title
});
this.getDetail();
},
getDetail() {
uni.request({
url: 'https://unidemo.dcloud.net.cn/api/news/36kr/' + this.banner.post_id,
success: (result) => {
let content = FAIL_CONTENT
if (result.statusCode == 200) {
content = result.data.content
}
const nodes = htmlParser(content);
// #ifdef APP-PLUS-NVUE
parseImgs(nodes)
// #endif
this.content = nodes
}
});
}
}
}
</script>
<style>
/* #ifndef APP-PLUS */
page {
min-height: 100%;
}
/* #endif */
.banner {
height: 180px;
position: relative;
background-color: #ccc;
flex-direction: row;
overflow: hidden;
}
.banner-img {
flex: 1;
}
.title-area {
position: absolute;
left: 15px;
right: 15px;
bottom: 15px;
z-index: 11;
}
.title-text {
font-size: 16px;
font-weight: 400;
line-height: 20px;
lines: 2;
color: #ffffff;
overflow: hidden;
}
.article-meta {
padding: 10px 15px;
flex-direction: row;
align-items: center;
justify-content: flex-start;
}
.article-meta-text {
color: gray;
}
.article-text {
font-size: 12px;
line-height: 25px;
margin: 0 10px;
}
.article-author {
font-size: 15px;
}
.article-time {
font-size: 15px;
}
.article-content {
font-size: 15px;
padding: 0 15px;
margin-bottom: 15px;
overflow: hidden;
}
/* #ifdef H5 */
.article-content {
line-height: 1.8;
}
.article-content img {
max-width: 100%;
}
/* #endif */
</style>

381
pages/news/index.nvue Normal file
View File

@ -0,0 +1,381 @@
<template>
<view class="tabs">
<scroll-view ref="tabbar1" id="tab-bar" class="tab-bar" :scroll="false" :scroll-x="true" :show-scrollbar="false"
:scroll-into-view="scrollInto" :enable-flex="true">
<view style="flex-direction: column;">
<view style="flex-direction: row;">
<view class="uni-tab-item" v-for="(tab,index) in tabList" :key="tab.newsid" :id="tab.newsid" :ref="'tabitem'+tab.newsid"
:data-id="index" :data-current="index" @click="ontabtap">
<text class="uni-tab-item-title" :class="tabIndex==index ? 'uni-tab-item-title-active' : ''">{{tab.name}}</text>
</view>
</view>
<view class="scroll-view-indicator">
<view ref="underline" class="scroll-view-underline" :class="isTap ? 'scroll-view-animation':''" :style="{left: indicatorLineLeft + 'px', width: indicatorLineWidth + 'px'}"></view>
</view>
</view>
</scroll-view>
<view class="tab-bar-line"></view>
<swiper class="tab-box" ref="swiper1" :current="tabIndex" :duration="300" @change="onswiperchange" @transition="onswiperscroll"
@animationfinish="animationfinish" @onAnimationEnd="animationfinish">
<swiper-item class="swiper-item" v-for="(page, index) in tabList" :key="index">
<newsPage class="page-item" :nid="page.newsid" :ref="'page' + index"></newsPage>
</swiper-item>
</swiper>
</view>
</template>
<script>
// #ifdef APP-PLUS
const dom = weex.requireModule('dom');
// #endif
import newsPage from './news-page.nvue';
// 缓存每页最多
const MAX_CACHE_DATA = 100;
// 缓存页签数量
const MAX_CACHE_PAGE = 3;
const TAB_PRELOAD_OFFSET = 1;
export default {
components: {
newsPage
},
data() {
return {
tabList: [{
name: '互联网',
newsid: 34
}, {
name: 'IT',
newsid: 22
}, {
name: '科技',
newsid: 13
}, {
name: '苹果',
newsid: 19
}, {
name: '旅游',
newsid: 18
}, {
name: '影视',
newsid: 40
}, {
name: '汽车',
newsid: 35
}, {
name: '动漫',
newsid: 33
}, {
name: '游戏',
newsid: 31
}, {
name: '军事',
newsid: 27
}],
tabIndex: 0,
cacheTab: [],
scrollInto: "",
navigateFlag: false,
indicatorLineLeft: 0,
indicatorLineWidth: 0,
isTap: false
}
},
onReady() {
this._lastTabIndex = 0;
this.swiperWidth = 0;
this.tabbarWidth = 0;
this.tabListSize = {};
this._touchTabIndex = 0;
this.pageList = [];
for (var i = 0; i < this.tabList.length; i++) {
let item = this.$refs['page' + i]
if (Array.isArray(item)) {
this.pageList.push(item[0])
} else {
this.pageList.push(item)
}
}
this.switchTab(this.tabIndex);
this.selectorQuery();
},
methods: {
ontabtap(e) {
let index = e.target.dataset.current || e.currentTarget.dataset.current;
//let offsetIndex = this._touchTabIndex = Math.abs(index - this._lastTabIndex) > 1;
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
this.isTap = true;
var currentSize = this.tabListSize[index];
this.updateIndicator(currentSize.left, currentSize.width);
this._touchTabIndex = index;
// #endif
this.switchTab(index);
},
onswiperchange(e) {
// 注意百度小程序会触发2次
// #ifndef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
let index = e.target.current || e.detail.current;
this.switchTab(index);
// #endif
},
onswiperscroll(e) {
if (this.isTap) {
return;
}
var offsetX = e.detail.dx;
var preloadIndex = this._lastTabIndex;
if (offsetX > TAB_PRELOAD_OFFSET) {
preloadIndex++;
} else if (offsetX < -TAB_PRELOAD_OFFSET) {
preloadIndex--;
}
if (preloadIndex === this._lastTabIndex || preloadIndex < 0 || preloadIndex > this.pageList.length - 1) {
return;
}
if (this.pageList[preloadIndex].dataList.length === 0) {
this.loadTabData(preloadIndex);
}
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
var percentage = Math.abs(this.swiperWidth / offsetX);
var currentSize = this.tabListSize[this._lastTabIndex];
var preloadSize = this.tabListSize[preloadIndex];
var lineL = currentSize.left + (preloadSize.left - currentSize.left) / percentage;
var lineW = currentSize.width + (preloadSize.width - currentSize.width) / percentage;
this.updateIndicator(lineL, lineW);
// #endif
},
animationfinish(e) {
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ
let index = e.detail.current;
if (this._touchTabIndex === index) {
this.isTap = false;
}
this._lastTabIndex = index;
this.switchTab(index);
this.updateIndicator(this.tabListSize[index].left, this.tabListSize[index].width);
// #endif
},
selectorQuery() {
// #ifdef APP-NVUE
dom.getComponentRect(this.$refs.tabbar1, res => {
this.tabbarWidth = res.size.width;
});
dom.getComponentRect(this.$refs['swiper1'], res => {
this.swiperWidth = res.size.width;
});
// for (var i = 0; i < this.tabList.length; i++) {
// this.getElementSize(dom, this.$refs['tabitem' + i][0], i);
// }
// 因 nvue 暂不支持 class 查询
var queryTabSize = uni.createSelectorQuery().in(this);
for (var i = 0; i < this.tabList.length; i++) {
queryTabSize.select('#' + this.tabList[i].newsid).boundingClientRect();
}
queryTabSize.exec(rects => {
rects.forEach((rect) => {
this.tabListSize[rect.dataset.id] = rect;
})
this.updateIndicator(this.tabListSize[this.tabIndex].left, this.tabListSize[this.tabIndex].width);
this.switchTab(this.tabIndex);
});
// #endif
// #ifdef MP-WEIXIN || H5 || MP-QQ
uni.createSelectorQuery().in(this).select('.tab-box').fields({
dataset: true,
size: true,
}, (res) => {
this.swiperWidth = res.width;
}).exec();
uni.createSelectorQuery().in(this).selectAll('.uni-tab-item').boundingClientRect((rects) => {
rects.forEach((rect) => {
this.tabListSize[rect.dataset.id] = rect;
})
this.updateIndicator(this.tabListSize[this.tabIndex].left, this.tabListSize[this.tabIndex].width);
}).exec();
// #endif
},
getElementSize(dom, ref, id) {
dom.getComponentRect(ref, res => {
this.tabListSize[id] = res.size;
});
},
updateIndicator(left, width) {
this.indicatorLineLeft = left;
this.indicatorLineWidth = width;
},
switchTab(index) {
if (this.pageList[index].dataList.length === 0) {
this.loadTabData(index);
}
if (this.tabIndex === index) {
return;
}
// 缓存 tabId
if (this.pageList[this.tabIndex].dataList.length > MAX_CACHE_DATA) {
let isExist = this.cacheTab.indexOf(this.tabIndex);
if (isExist < 0) {
this.cacheTab.push(this.tabIndex);
}
}
this.tabIndex = index;
// #ifdef APP-NVUE
this.scrollTabTo(index);
// #endif
// #ifndef APP-NVUE
this.scrollInto = "tab" + this.tabList[index].newsid;
// #endif
// 释放 tabId
if (this.cacheTab.length > MAX_CACHE_PAGE) {
let cacheIndex = this.cacheTab[0];
this.clearTabData(cacheIndex);
this.cacheTab.splice(0, 1);
}
},
scrollTabTo(index) {
const el = this.$refs['tabitem' + index][0];
let offset = 0;
// TODO fix ios offset
if (index > 0) {
offset = this.tabbarWidth / 2 - this.tabListSize[index].width / 2;
if (this.tabListSize[index].right < this.tabbarWidth / 2) {
offset = this.tabListSize[0].width;
}
}
dom.scrollToElement(el, {
offset: -offset
});
},
loadTabData(index) {
this.pageList[index].loadData();
},
clearTabData(index) {
this.pageList[index].clear();
}
}
}
</script>
<style>
/* #ifndef APP-PLUS */
page {
width: 100%;
min-height: 100%;
display: flex;
}
/* #endif */
.tabs {
flex: 1;
flex-direction: column;
overflow: hidden;
background-color: #ffffff;
/* #ifdef MP-ALIPAY || MP-BAIDU */
height: 100vh;
/* #endif */
}
.tab-bar {
/* #ifdef APP-PLUS */
width: 750rpx;
/* #endif */
height: 42px;
flex-direction: row;
/* #ifndef APP-PLUS */
white-space: nowrap;
/* #endif */
}
/* #ifndef APP-NVUE */
.tab-bar ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-view-indicator {
position: relative;
height: 2px;
background-color: transparent;
}
.scroll-view-underline {
position: absolute;
top: 0;
bottom: 0;
width: 0;
background-color: #007AFF;
}
.scroll-view-animation {
transition-duration: 0.2s;
transition-property: left;
}
.tab-bar-line {
height: 1px;
background-color: #cccccc;
}
.tab-box {
flex: 1;
}
.uni-tab-item {
/* #ifndef APP-PLUS */
display: inline-block;
/* #endif */
flex-wrap: nowrap;
padding-left: 20px;
padding-right: 20px;
}
.uni-tab-item-title {
color: #555;
font-size: 15px;
height: 40px;
line-height: 40px;
flex-wrap: nowrap;
/* #ifndef APP-PLUS */
white-space: nowrap;
/* #endif */
}
.uni-tab-item-title-active {
color: #007AFF;
}
.swiper-item {
flex: 1;
flex-direction: column;
}
.page-item {
flex: 1;
flex-direction: row;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
</style>

198
pages/news/news-item.nvue Normal file
View File

@ -0,0 +1,198 @@
<template>
<view class="media-item view" @click="click">
<view class="view"
:style="{flexDirection: (newsItem.article_type === 1 || newsItem.article_type === 2)?(newsItem.article_type === 2 ?'row':'row-reverse'):'column' }">
<text class="media-title"
:class="{'media-title2': newsItem.article_type === 1 || newsItem.article_type === 2}">{{newsItem.title}}</text>
<view v-if="newsItem.image_list || newsItem.image_url" class="image-section flex-row"
:class="{'image-section-right': newsItem.article_type === 2, 'image-section-left': newsItem.article_type === 1}">
<image :fade-show="false" class="image-list1"
:class="{'image-list2': newsItem.article_type === 1 || newsItem.article_type === 2}"
v-if="newsItem.image_url" :src="newsItem.image_url"></image>
<template v-if="newsItem.image_list">
<image :fade-show="false" class="image-list3" :src="source.url"
v-for="(source, i) in newsItem.image_list" :key="i" />
</template>
</view>
</view>
<view class="media-foot flex-row">
<view class="media-info flex-row">
<text class="info-text">{{newsItem.source}}</text>
<text class="info-text">{{newsItem.comment_count}}条评论</text>
<text class="info-text">{{newsItem.datetime}}</text>
</view>
<view class="close-view" @click.stop="close">
<view class="close-l close-h"></view>
<view class="close-l close-v"></view>
</view>
</view>
<view class="media-item-line" style="position: absolute;"></view>
</view>
</template>
<script>
export default {
props: {
newsItem: {
type: Object,
default: function(e) {
return {}
}
}
},
methods: {
click() {
this.$emit('click');
},
close(e) {
e.stopPropagation();
this.$emit('close');
}
}
}
</script>
<style scoped>
.view {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.flex-col {
flex-direction: column;
}
.list-cell {
padding: 0 15px;
}
.uni-list-cell-hover {
background-color: #eeeeee;
}
.media-item {
position: relative;
flex: 1;
flex-direction: column;
padding: 10px 15px 10px 15px;
}
.media-item-line {
position: absolute;
left: 15px;
right: 15px;
bottom: 0;
height: 1px;
background-color: #ebebeb;
}
.media-image-right {
flex-direction: row;
}
.media-image-left {
flex-direction: row-reverse;
}
.media-title {
flex: 1;
}
.media-title {
lines: 3;
text-overflow: ellipsis;
font-size: 15px;
color: #555555;
}
.media-title2 {
flex: 1;
margin-top: 3px;
line-height: 20px;
}
.image-section {
margin-top: 10px;
flex-direction: row;
justify-content: space-between;
}
.image-section-right {
margin-top: 0;
margin-left: 5px;
width: 112px;
height: 73px;
}
.image-section-left {
margin-top: 0;
margin-right: 5px;
width: 112px;
height: 73px;
}
.image-list1 {
height: 240px;
}
.image-list2 {
width: 112px;
height: 73px;
}
.image-list3 {
width: 112px;
height: 73px;
}
.media-info {
flex-direction: row;
align-items: center;
}
.info-text {
margin-right: 10px;
color: #999999;
font-size: 12px;
}
.media-foot {
margin-top: 12px;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.close-view {
position: relative;
align-items: center;
flex-direction: row;
width: 20px;
height: 15px;
line-height: 15px;
border-width: 1upx;
border-style: solid;
border-color: #aaaaaa;
border-radius: 4px;
justify-content: center;
text-align: center;
}
.close-l {
position: absolute;
width: 9px;
height: 1px;
background-color: #aaaaaa;
}
.close-h {
transform: rotate(45deg);
}
.close-v {
transform: rotate(-45deg);
}
</style>

316
pages/news/news-page.nvue Normal file
View File

@ -0,0 +1,316 @@
<template>
<view class="page-news">
<!-- #ifdef APP-NVUE -->
<list class="listview">
<refresh :display="refreshing" @refresh="onrefresh" @pullingdown="onpullingdown"></refresh>
<cell v-for="(item, index) in dataList" :key="item.newsid">
<news-item :newsItem="item" @close="closeItem(index)" @click="goDetail(item)"></news-item>
</cell>
<cell v-if="isLoading || dataList.length > 4">
<view class="loading-more">
<text class="loading-more-text">{{loadingText}}</text>
</view>
</cell>
</list>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<scroll-view class="listview" style="flex: 1;" enableBackToTop="true" scroll-y @scrolltolower="loadMore()">
<view v-for="(item, index) in dataList" :key="item.newsid">
<news-item :newsItem="item" @close="closeItem(index)" @click="goDetail(item)"></news-item>
</view>
<view class="loading-more" v-if="isLoading || dataList.length > 4">
<text class="loading-more-text">{{loadingText}}</text>
</view>
</scroll-view>
<!-- #endif -->
<no-data class="no-data" v-if="isNoData" @retry="loadMore"></no-data>
</view>
</template>
<script>
import {
friendlyDate
} from '@/common/util.js';
import newsItem from './news-item.nvue';
import uniLoadMore from '@/components/uni-load-more.vue';
import noData from '@/components/nodata.nvue';
export default {
components: {
uniLoadMore,
noData,
newsItem
},
props: {
nid: {
type: [Number, String],
default: ''
}
},
data() {
return {
dataList: [],
navigateFlag: false,
pulling: false,
refreshing: false,
refreshFlag: false,
refreshText: "",
isLoading: false,
loadingText: '加载中...',
isNoData: false,
pulling: false,
angle: 0,
loadingMoreText: {
contentdown: '',
contentrefresh: '',
contentnomore: ''
},
refreshIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAB5QTFRFcHBw3Nzct7e39vb2ycnJioqK7e3tpqam29vb////D8oK7wAAAAp0Uk5T////////////ALLMLM8AAABxSURBVHja7JVBDoAgDASrjqj//7CJBi90iyYeOHTPMwmFZrHjYyyFYYUy1bwUZqtJIYVxhf1a6u0R7iUvWsCcrEtwJHp8MwMdvh2amHduiZD3rpWId9+BgPd7Cc2LIkPyqvlQvKxKBJ//Qwq/CacAAwDUv0a0YuKhzgAAAABJRU5ErkJggg=="
}
},
created() {
this.pullTimer = null;
this.requestParams = {
key: 'c65b090e6589cc2c07e873c4a4ea60fd',
num: 50,
col: this.nid,
page: 1
};
this._isWidescreen = false;
// #ifdef H5
var mediaQueryOb = uni.createMediaQueryObserver(this)
mediaQueryOb.observe({
minWidth: 768
}, matches => {
this._isWidescreen = matches;
})
// #endif
},
methods: {
loadData(refresh) {
if (this.isLoading) {
return;
}
this.isLoading = true;
this.isNoData = false;
var startTime = new Date();
uni.request({
// url: this.$host + 'api/news',
url: 'https://apis.tianapi.com/allnews/index',
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: this.requestParams,
success: (result) => {
var endTime = new Date();
const data = result.data.result.newslist;
this.isNoData = (data.length <= 0);
const data_list = data.map((news) => {
return {
id: this.newGuid() + news.newsid,
newsid: news.newsid,
article_type: 1,
datetime: friendlyDate(new Date(news.ctime.replace(/\-/g, '/'))
.getTime()),
title: news.title,
image_url: news.picUrl,
source: news.source
};
});
if (refresh) {
this.dataList = data_list;
} else {
this.dataList = this.dataList.concat(data_list);
}
if (this.dataList.length > 0 && this._isWidescreen && this.dataList.length <= 50) {
this.goDetail(this.dataList[0]);
}
},
fail: (err) => {
if (this.dataList.length == 0) {
this.isNoData = true;
}
},
complete: (e) => {
this.isLoading = false;
if (refresh) {
this.refreshing = false;
this.refreshFlag = false;
this.refreshText = "已刷新";
if (this.pullTimer) {
clearTimeout(this.pullTimer);
}
this.pullTimer = setTimeout(() => {
this.pulling = false;
}, 1000);
}
}
});
},
loadMore(e) {
this.requestParams.page = this.requestParams.page + 1
this.loadData();
},
clear() {
this.dataList.length = 0;
},
goDetail(detail) {
if (this._isWidescreen) { //若为宽屏,则触发右侧详情页的自定义事件
uni.$emit('updateDetail', {
detail: encodeURIComponent(JSON.stringify(detail))
})
} else {
uni.navigateTo({
url: './detail?query=' + encodeURIComponent(JSON.stringify(detail))
});
}
},
closeItem(index) {
uni.showModal({
content: '不感兴趣?',
success: (res) => {
if (res.confirm) {
this.dataList.splice(index, 1);
}
}
})
},
refreshData() {
if (this.isLoading) {
return;
}
this.pulling = true;
this.refreshing = true;
this.refreshText = "正在刷新...";
this.loadData(true);
},
onrefresh(e) {
this.refreshData();
// #ifdef APP-NVUE
this.$refs.list.resetLoadmore();
// #endif
},
onpullingdown(e) {
if (this.refreshing) {
return;
}
this.pulling = false;
if (Math.abs(e.pullingDistance) > Math.abs(e.viewHeight)) {
this.refreshFlag = true;
this.refreshText = "释放立即刷新";
} else {
this.refreshFlag = false;
this.refreshText = "下拉可以刷新";
}
},
newGuid() {
let s4 = function() {
return (65536 * (1 + Math.random()) | 0).toString(16).substring(1);
}
return (s4() + s4() + "-" + s4() + "-4" + s4().substr(0, 3) + "-" + s4() + "-" + s4() + s4() + s4())
.toUpperCase();
}
}
}
</script>
<style scoped>
.no-data {
flex: 1;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 10;
}
.page-news {
flex: 1;
flex-direction: column;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.listview {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
display: flex;
flex-direction: column;
/* #endif */
/* #ifndef MP-ALIPAY */
flex-direction: column;
/* #endif */
}
.refresh {
justify-content: center;
}
.refresh-view {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
width: 750rpx;
height: 64px;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
}
.refresh-icon {
width: 32px;
height: 32px;
transition-duration: .5s;
transition-property: transform;
transform: rotate(0deg);
transform-origin: 15px 15px;
}
.refresh-icon-active {
transform: rotate(180deg);
}
.loading-icon {
width: 28px;
height: 28px;
margin-right: 5px;
color: gray;
}
.loading-text {
margin-left: 2px;
font-size: 16px;
color: #999999;
}
.loading-more {
align-items: center;
justify-content: center;
padding-top: 14px;
padding-bottom: 14px;
text-align: center;
}
.loading-more-text {
font-size: 28upx;
color: #999;
}
</style>

188
pages/ucenter/ucenter.nvue Normal file
View File

@ -0,0 +1,188 @@
<template>
<view class="center">
<view class="logo" @click="goLogin" :hover-class="!login ? 'logo-hover' : ''">
<image class="logo-img" :src="login ? uerInfo.avatarUrl :avatarUrl"></image>
<view class="logo-title">
<text class="uer-name">Hi{{login ? uerInfo.name : '您未登录'}}</text>
<text class="go-login-navigat-arrow navigat-arrow" v-if="!login">&#xe65e;</text>
</view>
</view>
<view class="center-list">
<view class="center-list-item border-bottom">
<text class="list-icon">&#xe60f;</text>
<text class="list-text">账号管理</text>
<text class="navigat-arrow">&#xe65e;</text>
</view>
<view class="center-list-item">
<text class="list-icon">&#xe639;</text>
<text class="list-text">新消息通知</text>
<text class="navigat-arrow">&#xe65e;</text>
</view>
</view>
<view class="center-list">
<view class="center-list-item border-bottom">
<text class="list-icon">&#xe60b;</text>
<text class="list-text">帮助与反馈</text>
<text class="navigat-arrow">&#xe65e;</text>
</view>
<view class="center-list-item">
<text class="list-icon">&#xe65f;</text>
<text class="list-text">服务条款及隐私</text>
<text class="navigat-arrow">&#xe65e;</text>
</view>
</view>
<view class="center-list">
<view class="center-list-item">
<text class="list-icon">&#xe614;</text>
<text class="list-text">关于应用</text>
<text class="navigat-arrow">&#xe65e;</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
login: false,
avatarUrl: '/static/logo.png',
uerInfo: {}
}
},
methods: {
goLogin() {
if (!this.login) {
console.log('点击前往登录');
}
}
}
}
</script>
<style>
/* #ifndef APP-PLUS-NVUE */
@font-face {
font-family: texticons;
font-weight: normal;
font-style: normal;
src: url('~@/static/text-icon.ttf') format('truetype');
}
page {
background-color: #f8f8f8;
}
/* #endif*/
/* 解决头条小程序字体图标不显示问题因为头条运行时自动插入了span标签且有全局字体 */
/* #ifdef MP-TOUTIAO */
text :not(view) {
font-family: texticons;
}
/* #endif */
.center {
flex: 1;
flex-direction: column;
background-color: #f8f8f8;
}
.logo {
width: 750upx;
height: 240upx;
padding: 20upx;
background-color: #2F85FC;
flex-direction: row;
align-items: center;
}
.logo-hover {
opacity: 0.8;
}
.logo-img {
width: 150upx;
height: 150upx;
border-radius: 150upx;
}
.logo-title {
height: 150upx;
flex: 1;
align-items: center;
justify-content: space-between;
flex-direction: row;
margin-left: 20upx;
}
.uer-name {
height: 60upx;
line-height: 60upx;
font-size: 38upx;
color: #FFFFFF;
}
.go-login-navigat-arrow {
font-size: 38upx;
color: #FFFFFF;
}
.login-title {
height: 150upx;
flex-direction: column;
align-items: center;
justify-content: center;
margin-left: 20upx;
}
.center-list {
flex-direction: column;
background-color: #FFFFFF;
margin-top: 20upx;
width: 750upx;
}
.center-list-item {
height: 90upx;
width: 750upx;
flex-direction: row;
padding: 0upx 20upx;
}
.border-bottom {
border-bottom-width: 1upx;
border-color: #c8c7cc;
border-bottom-style: solid;
}
.list-icon {
width: 40upx;
height: 90upx;
line-height: 90upx;
font-size: 34upx;
color: #2F85FC;
text-align: center;
font-family: texticons;
margin-right: 20upx;
}
.list-text {
height: 90upx;
line-height: 90upx;
font-size: 34upx;
color: #555;
flex: 1;
}
.navigat-arrow {
height: 90upx;
width: 40upx;
line-height: 90upx;
font-size: 34upx;
color: #555;
text-align: right;
font-family: texticons;
}
</style>

View File

@ -0,0 +1,29 @@
<template>
<view>
<newDetail ref="detailPage"></newDetail>
</view>
</template>
<script>
import newDetail from '../pages/news/detail.nvue'
export default {
components:{
newDetail
},
data() {
return {
title: 'Hello'
}
},
created(e) {
//
uni.$on('updateDetail', (e) => {
// /pages/news/detail.nvueload
this.$refs.detailPage.load(e.detail);
})
}
}
</script>
<style>
</style>

BIN
static/app-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
static/center-active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/center.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/home-active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
static/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
static/text-icon.ttf Normal file

Binary file not shown.

76
uni.scss Normal file
View File

@ -0,0 +1,76 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:24upx;
$uni-font-size-base:28upx;
$uni-font-size-lg:32upx;
/* 图片尺寸 */
$uni-img-size-sm:40upx;
$uni-img-size-base:52upx;
$uni-img-size-lg:80upx;
/* Border Radius */
$uni-border-radius-sm: 4upx;
$uni-border-radius-base: 6upx;
$uni-border-radius-lg: 12upx;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 10px;
$uni-spacing-row-base: 20upx;
$uni-spacing-row-lg: 30upx;
/* 垂直间距 */
$uni-spacing-col-sm: 8upx;
$uni-spacing-col-base: 16upx;
$uni-spacing-col-lg: 24upx;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:40upx;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:36upx;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:30upx;