master
13285100733@qq.com 2 years ago
parent ee0efce9e9
commit 99b4a0b153

@ -1,51 +0,0 @@
# weapp-wechat-zhihu
微信中的知乎--微信小程序 demo // Zhihu in Wechat
![](images/v_index.png)
### description
- 界面及交互设计来自知乎 Android 版本
- _工具_: [微信 web 开发者工具](https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=1477579747265)
- _数据_: 没有开放 API, 所以使用伪造本地数据
#### 功能及使用的组件等
* 列表式渲染数据
* 自定义顶部 tabbar
* 下拉刷新
* 上拉加载更多
* 轮播图
<br/>等...
### Setup
```
git@github.com:RebeccaHanjw/weapp-wechat-zhihu.git
```
下载安装Wechat DEV Tools, 并导入项目
### 演示
首页下拉刷新等
![](images/index_scroll.gif)
底部 tab 切换
![](images/bottom_tab.gif)
顶部自定义 tab 切换
![](images/top_tab.gif)
页面跳转
![](images/navigation.gif)
##### Demo 用于学习交流, 转载请注明出处

@ -1,35 +0,0 @@
//app.js
App({
onLaunch: function () {
wx.cloud.init({
env:"cloud1-8g5wmepxce8a3b8a",
traceUser:true//是否访问权限可见
})}})
/*
//调用API从本地缓存中获取数据
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
},
getUserInfo:function(cb){
var that = this
if(this.globalData.userInfo){
typeof cb == "function" && cb(this.globalData.userInfo)
}else{
//调用登录接口
wx.login({
success: function () {
wx.getUserInfo({
success: function (res) {
that.globalData.userInfo = res.userInfo
typeof cb == "function" && cb(that.globalData.userInfo)
}
})
}
})
}
},
globalData:{
userInfo:null
}
})*/

@ -1,44 +0,0 @@
{
"pages":[
"pages/login/login",
"pages/index/index",
"pages/submit/submit",
"pages/notify/notify",
"pages/searchShow/searchShow",
"pages/change/change",
"pages/answer/answer"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#0068C4",
"navigationBarTitleText": "论坛",
"navigationBarTextStyle":"white",
"enablePullDownRefresh":true
},
"tabBar": {
"color": "#626567",
"selectedColor": "#2A8CE5",
"backgroundColor": "#FBFBFB",
"borderStyle": "white",
"list": [{
"pagePath": "pages/index/index",
"text": "",
"iconPath": "images/index.png",
"selectedIconPath": "images/index_focus.png"
},{
"pagePath": "pages/notify/notify",
"text": "",
"iconPath": "images/ring.png",
"selectedIconPath": "images/ring_focus.png"
}
]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true,
"style": "v2",
"sitemapLocation": "sitemap.json"
}

@ -1,127 +0,0 @@
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
/*padding: 200rpx 0;*/
box-sizing: border-box;
background: #F0F4F3;
}
.container.withtab{
margin: 105rpx 0 0 0;
/*top: 105rpx;*/
}
.flex-wrp{
display: flex;
}
.flex-tab{
flex-flow: row nowrap;
justify-content: space-around;
align-items: stretch;
}
.flex-item{
flex-grow: 1;
text-align: center;
}
.top-tab{
width: 750rpx;
height: 100rpx;
background: #298DE5;
color: #8CCEFD;
font-size: 28rpx;
line-height: 100rpx;
box-shadow: 0 2px 2px #bebebe;
margin: 0 0 8rpx 0;
position: fixed;
top: 0;
z-index: 9999;
}
.toptab.active{
color: #ffffff;
border-bottom: solid 2px #ffffff;
}
.container{
padding: 0;
font-size: 14rpx;
color: #000;
}
.container .feed-item{
width: 690rpx;
padding: 30rpx 30rpx 20rpx;
margin: 7rpx 0 6rpx 0;
background: #ffffff;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
box-shadow: 0 2px 5px #eeeeee;
}
.container .feed-item .feed-source{
width: 690rpx;
left: 0;
height: 50rpx;
}
.container .feed-item .feed-source .avatar{
position: relative;
display: inline-block;
}
.container .feed-item .feed-source a{
display: inline-block;
height: 40rpx;
}
.container .feed-item .feed-source .avatar image{
/*position: absolute;*/
display: inline-block;
width: 45rpx;
height: 45rpx;
border-radius: 45rpx;
top: 10rpx;
vertical-align: middle;
}
.container .feed-item .feed-source text{
/*position: absolute;*/
display: inline-block;
height: 40rpx;
line-height: 40rpx;
vertical-align: middle;
margin: 0 0 0 15rpx;
color: #a0acac;
font-size: 26rpx;
}
.container .feed-item .feed-source .item-more{
display: inline-block;
width: 40rpx;
height: 45rpx;
float: right;
}
.container .feed-item .feed-content{
padding: 10rpx 0 0 0;
}
.container .feed-item .feed-content .question text{
font-size: 28rpx;
font-weight: 600px;
line-height: 40rpx;
word-spacing: 5rpx;
}
.container .feed-item .feed-content .answer-body{
padding: 10rpx 0 0 0;
/*height: 10rpx;*/
font-size: 24rpx;
line-height: 28rpx;
color: #5b5b5b;
}
.container .feed-item .feed-content .answer-actions{
width: 690rpx;
padding: 10rpx 0 0;
color: #a0acac;
}
.container .feed-item .feed-content .answer-actions view{
display: inline-block;
vertical-align: text-bottom;
padding: 0 10rpx 0 0;
font-size: 24rpx;
}
.container .feed-item .feed-content .answer-actions .dot ::after{
content: "•";
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 969 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

@ -1,297 +0,0 @@
//answer.js
let dianzan=false
var app = getApp()
Page({
data: {
motto: '知乎--微信小程序版',
userInfo: {},
feed: [],
//修改
pinglun:[], //评论数组
content:'',
detail: '',
dianzanUrl: "../../images/heart2.png",
comment_num:0
},
//事件处理函数
// toQuestion: function() {
// wx.navigateTo({
// url: '../question/question'
// })
// },
onLoad: function (options) {
var aid=options.aid
console.log(options);
console.log(options.aid);
console.log('onLoad')
wx.setStorageSync('aid', aid)
//修改
wx.cloud.database().collection("tiezi")
.doc(aid)
.get()
.then(res => {
console.log("详情页成功", res);
//将收藏添加到数据库
//shoucang = res.data.shoucang
//dianzan = res.data.dianzan
//console.log(shoucang, dianzan);
//再次显示数据
this.setData({
detail: res.data,
pinglun:res.data.pinglun
})
})
.catch(err => {
console.log("详情页失败", err);
})
var getId = options.aid;
// 让接收到的id值传递到data:{}里面
this.setData({
currentId: getId
});
// 读取所有的文章列表点赞缓存状态
var cache = wx.getStorageSync('cache_key');
// 如果缓存状态存在
if (cache) {
// 拿到所有缓存状态中的1个
var currentCache = cache[getId];
// 把拿到的缓存状态中的1个赋值给data中的collection如果当前文章没有缓存状态currentCache 的值就是 false如果当前文章的缓存存在那么 currentCache 就是有值的,有值的说明 currentCache 的值是 true
this.setData({
collection: currentCache
})
} else {
// 如果所有的缓存状态都不存在 就让不存在的缓存存在
var cache = {};
// 既然所有的缓存都不存在,那么当前这个文章点赞的缓存也不存在,我们可以把当前这个文章点赞的缓存值设置为 false
cache[getId] = false;
// 把设置的当前文章点赞放在整体的缓存中
wx.setStorageSync('cache_key',cache);
}
},
// 点击图片的点赞事件 这里使用的是同步的方式
toCollect: function(options) {
let aid= wx.getStorageSync ("aid")
console.log(aid);
// 获取所有的缓存
var cache = wx.getStorageSync('cache_key');
// 获取当前文章是否被点赞的缓存
var currentCache = cache[this.data.currentId];
// 取反,点赞的变成未点赞 未点赞的变成点赞
currentCache = !currentCache;
// 更新cache中的对应的1个的缓存值使其等于当前取反的缓存值
cache[this.data.currentId] = currentCache;
// 重新设置缓存
wx.setStorageSync('cache_key',cache);
// 更新数据绑定,从而切换图片
this.setData({
// collection 默认的是 false
collection: currentCache
});
// 交互反馈
wx.showToast({
title: currentCache?'点赞':'取消',
icon: 'success',
duration: 2000
});
if(currentCache==true){
wx.cloud.database().collection("tiezi")
.doc(aid)
.update({
data: {
// 前面为数据库字段,后面为修改之后的值
good_num: wx.cloud.database().command.inc(1),
}
})
.then(res => {
console.log("添加评论成功", res);
return res
})
.catch(err => {
console.log("添加评论失败", err);
return err
})
}
else{
wx.cloud.database().collection("tiezi")
.doc(aid)
.update({
data: {
// 前面为数据库字段,后面为修改之后的值
good_num: wx.cloud.database().command.inc(-1),
}
})
.then(res => {
console.log("成功", res);
return res
})
.catch(err => {
console.log("失败", err);
return err
})
}
},
/*
onLoad: function (options) {
},*/
tapName: function(event){
console.log(event)
},
//获取输入的值
getContent(event){
this.setData({
content:event.detail.value
})
},
//发表评论
fabiao(options){
let content=this.data.content
if(content.length<4){
wx.showLoading({
icon:"none",
title: '评论太短了',
})
return
}
let pinglunItem={}
pinglunItem.nickName=wx.getStorageSync ("nickName")
pinglunItem.avatarUrl=wx.getStorageSync ("avatarUrl")
pinglunItem.content=content
let pinglunArr=this.data.pinglun
pinglunArr.push(pinglunItem)
let l= pinglunArr.length
console.log("评论数",l)
console.log("添加后的评论数组",pinglunArr)
wx.showLoading({
title: '发表中',
})
wx.cloud.init({
env:"cloud1-8g5wmepxce8a3b8a",
})
let aid= wx.getStorageSync ("aid")
console.log(aid);
//异步操作
wx.cloud.database().collection("tiezi")
.doc(aid)
.update({
data: {
// 前面为数据库字段,后面为修改之后的值
pinglun: pinglunArr,
comment_num:pinglunArr.length
}
})
.then(res => {
console.log("添加评论成功", res);
return res
})
.catch(err => {
console.log("添加评论失败", err);
return err
})
let that = this;
wx.hideLoading({
success: (res) => {
that.setData({
pinglun:pinglunArr,
content:''
})
wx.showToast({
title: '发布成功',
})}})
},
//点赞切换
clickDianzan() {
this.setData({
dianzanUrl: dianzan ? "../../images/heart2.png" : "../../images/heart1.png"
})
dianzan = !dianzan
wx.setStorageSync ("dianzan",dianzan)
console.log("改变点赞状态", dianzan)
},
amend: function(e) {
let aid= wx.getStorageSync ("aid")
console.log(aid);
wx.showModal({
title: '编辑',
content: '确定要修改已发布的信息吗?',
success(res) {
if(res.confirm) {
wx.redirectTo({
url: '../change/change?aid='+aid,
})
console.log('用户点击了确定')
}else if(res.cancel) {
console.log('用户点击了取消')
}
}
})
},
delete(event){
let aid= wx.getStorageSync ("aid")
console.log(aid);
wx.cloud.database().collection('tiezi')
.doc(aid)
.remove()
.then(res => {
console.log(res);
}).catch(res => {
console.log(res);
})
wx.switchTab({
url: '../index/index'
})
},
delete_p: function(event) {
/*wx.navigateTo({
url: '../answer/answer'
})*/
let aid= wx.getStorageSync ("aid")
console.log(aid);
var aidc=event.currentTarget.dataset.aidc;
console.log(aidc)
//console.log("1")
wx.cloud.database().collection('tiezi')
.where({
'_id':aid,
'pinglun.content':aidc
})
.update({
data:{
pinglun:wx.cloud.database().command.pull({
content:aidc
}),
comment_num:wx.cloud.database().command.inc(-1)
}
})
.then(res => {
console.log(res);
console.log(res);
wx.showLoading({
title: '删除成功...',
})
wx.redirectTo({
url: '../answer/answer?aid='+aid,
})
wx.hideLoading()
}).catch(res => {
console.log(res);
})
}
})

@ -1,3 +0,0 @@
{
"navigationBarTitleText": "回答"
}

@ -1,93 +0,0 @@
<!--answer.wxml-->
<!--<block wx:for="{{feed}}" wx:key="index">-->
<view class="mycontainer">
<view class="a" >
<view class="question" >
<text class="question-title">{{detail.title}}</text>
</view>
<view class="answerer-wrp">
<view class="bg-half"></view>
<view class="answerer flex-wrp">
<view class="avatar flex-item">
<image src="{{detail.feed_source_img}}"></image>
</view>
<view class="answerer-info flex-item">
<text class="answerer-name">{{detail.feed_source_name}}</text>
<text class="answerer-des">喜欢就点个赞吧~</text>
</view>
<!-- <view class="follow flex-item"> -->
<view class="follow flex-item">
<!-- 删除 -->
<image data-aid="{{detail._id}}" bindtap="delete" class="a3" src="/images/delete.png"></image>
<text>删除</text>
<!-- </view> -->
</view>
<view class="follow flex-item">
<!-- 修改 -->
<image data-aid="{{detail._id}}" bindtap="amend" class="image" src="/images/write.png"></image>
<text>编辑</text>
<!-- </view> -->
</view>
<view class="follow flex-item">
<image wx:if="{{collection}}" src="../../images/heart1.png" bindtap="toCollect"></image>
<image wx:else src="../../images/heart2.png" bindtap="toCollect"></image>
<text>点赞</text>
<!-- </view> -->
</view>
</view>
</view>
<view class="answer-content">
<text class="desc" style="white-space:pre-wrap;">{{detail.content}}</text>
<image webp wx:for="{{detail.url}}" mode='widthFix' src="{{item}}" ></image>
</view>
</view>
<!--</block>-->
<!-- 评论 -->
<block wx:for="{{pinglun}}" wx:key="index">
<view class="pinglunItem ">
<view>
<image class="a1" src="{{item.avatarUrl}} "></image>
</view>
<view >
<view class="desc" >{{item.nickName}}{{item.content}}</view>
</view>
<view>
<image data-aidc="{{item.content}}" bindtap="delete_p" class="a2" src="/images/delete.png"></image>
</view>
</view>
</block>
<view class="search flex-wrp">
<view class="search-left flex-item">
<input placeholder="请输入评论的内容" placeholder-class="search-placeholder" bindinput="getContent" value="{{content}}"/>
</view>
<view class="search-right flex-item" bindtap="fabiao">
<view class="desc">发表</view>
</view>
</view>
<view class="standView"></view>
</view>

@ -1,300 +0,0 @@
/**answer.wxss**/
.mycontainer{
margin-bottom: 200rpx;
}
.a {
padding: 0;
font-size: 14rpx;
background: #F0F4F3;
color: #000;
}
.question {
position: relative;
width: 650rpx;
padding: 40rpx 50rpx 30rpx;
background: #298DE5;
color: #fff;
font-size: 38rpx;
line-height: 48rpx;
}
.answerer-wrp{
position: relative;
width: 750rpx;
height: 150rpx;
background: #fff;
}
.answerer-wrp .bg-half{
position: absolute;
top: 0;
width: 750rpx;
height: 75rpx;
background: #298DE5;
}
.answerer {
position: relative;
margin: 0 auto;
width: 630rpx;
height: 90rpx;
padding: 30rpx;
background: #fff;
border: solid 1px #ebebeb;
border-radius: 3px;
box-shadow: 0 1px 2px #bebebe;
}
.answerer .avatar {
flex: 1;
width: 90rpx;
height: 90rpx;
}
.answerer .avatar image {
display: inline-block;
width: 90rpx;
height: 90rpx;
border-radius: 90rpx;
}
.answerer .answerer-info{
flex: 5;
text-align: left;
padding: 10rpx 20rpx;
line-height: 38rpx;
}
.answerer .answerer-info text{
display: block;
}
.answerer .answerer-info .answerer-name{
font-size: 32rpx;
}
.answerer .answerer-info .answerer-des{
font-size: 22rpx;
color: #808080;
line-height: 28rpx;
}
.answerer .follow image{
/* flex: 2;
padding: 15rpx 0;
font-size: 22rpx; */
display: block;
margin: 0 auto;
width: 40rpx;
height: 40rpx;
}
.answerer .follow text{
/* display: inline-block;
padding: 15rpx 20rpx;
color: #40bcd0;
border: solid 2px #40bcd0;
border-radius: 6rpx; */
display: block;
font-size: 14rpx;
color: #bebebe;
}
.answerer .follow .a3{
/* flex: 2;
padding: 15rpx 0;
font-size: 22rpx; */
display: block;
margin: 0 auto;
width: 30rpx;
height: 35rpx;
margin-top: 9rpx;
}
.answer-content{
padding: 30rpx 40rpx;
background: #ffffff;
}
.answer-content text{
font-size: 32rpx;
color: #454545;
line-height: 44rpx;
word-break: break-all;
}
.answer-content image{
width: 100%;
margin: 20rpx 0;
}
.answer-footer{
width: 750rpx;
height: 70rpx;
font-size: 28rpx;
box-shadow: 0 100px 7px #bebebe;
margin: 0 200rpx 0 0;
position: sticky ;
bottom: 0;
z-index: 9999;
}
.answer-footer{
position: fixed;
bottom: 0;
height: 70rpx;
border-top: solid 1px #ebebeb;
background: #ffffff;
width: 670rpx;
padding: 20rpx 40rpx;
}
.answer-footer .good{
flex: 1;
/*display: inline-block;*/
height: 40rpx;
border: solid 1px #d1d1d1;
border-radius: 3px;
padding: 10rpx 8rpx;
margin: 10rpx 0;
}
.answer-footer .good .good-bad{
display: inline-block;
}
.answer-footer .good image{
display: inline-block;
width: 38rpx;
height: 38rpx;
vertical-align: middle;
}
.answer-footer .good .good-num{
display: inline-block;
padding: 10rpx 4rpx;
/*font-size: 24rpx;*/
}
.answer-footer .operation-wrp{
flex: 5;
}
.answer-footer .operation{
justify-content: space-between;
padding: 0 0 0 60rpx;
}
.answer-footer .operation-btn{
flex: 1;
text-align: center;
}
.answer-footer .operation image{
display: block;
margin: 0 auto;
width: 50rpx;
height: 50rpx;
}
.answer-footer .operation-btn text{
display: block;
font-size: 14rpx;
color: #bebebe;
}
/* .tip{
position: relative;
width: 650rpx;
padding: 30rpx 50rpx 30rpx;
/*background: #298DE5;*/
/* color: rgb(0, 0, 0);
font-size: 38rpx;
line-height: 48rpx;
} */
.input{
display:block;
border:2px solid gainsboro;
margin-top: 60rpx;
margin-bottom: 60rpx;
}
.pinglunItem{
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
border-top: 2px solid gainsboro;
margin-left: 0rpx;
margin-top: 0rpx;
}
.pinglunItem .a1{
margin-left: 30rpx;
margin-right: 30rpx;
margin-top: 20rpx;
margin-bottom: 20rpx;
width: 60rpx;
height: 60rpx;
border-radius: 90rpx;
}
.pinglunItem .desc{
font-size: 30rpx;
height: 40rpx;
}
.pinglunItem .a2 {
margin-left: 100rpx;
margin-right: 30rpx;
margin-top: -20rpx;
width: 30rpx;
height: 35rpx;
position: absolute; /* 要约束所在位置的子元素的位置要设置成绝对 */
right: 0;
}
.search{
position: fixed;
bottom: 0;
width: 735rpx;
height: 65rpx;
padding: 12.5rpx 0 12.5rpx 15rpx;
background: #2A8CE5;
}
.search-left{
flex: 8;
background: #4EA3E7;
text-align: left;
}
.search-left input{
display: inline-block;
height: 65rpx;
font-size: 26rpx;
}
.search-placeholder{
color: #8CCEFD;
line-height: 20rpx;
}
.search .search-left image{
display: inline-block;
width: 35rpx;
height: 35rpx;
padding: 15rpx 15rpx 15rpx 20rpx;
}
.search .search-right{
flex: 1;
}
.search .search-right .desc{
font-size: 25rpx;
color: #ffffff;
margin:15rpx
}
/* 解决底部导航栏遮挡的占位view的样式 */
.standView{
width: 100%;
height: 100rpx; /* 自定义一下需要的高度 */
/* background-color: black; */
}

@ -1,241 +0,0 @@
// pages/change/change.js
var app = getApp()
Page({
data: {
feed: [],
change1: '',
change2: '',
tempFilePaths: [],
t:'',
c:'',
nowCount:0,//当前的图片上传个数
index:0,
url:[]
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var that = this
var aid=options.aid
console.log(options);
console.log(options.aid);
wx.cloud.database().collection('tiezi')
.doc(aid)
.get({
success(res) {
console.log("请求成功", res.data)
console.log("请求成功", res.data.url.length)
// 将查询返回的结果赋值给本地数组
that.setData({
feed: res.data,
nowCount:res.data.url.length,
tempFilePaths: res.data.url,
url:res.data.url,
t:res.data.title,
c:res.data.content
})
},
fail(res) {
console.log("请求失败", res)
}
})
},
// 获取修改后的内容
change1: function(e) {
this.setData({
change1: e.detail.value
})
},
change: function(e) {
this.setData({
change2: e.detail.value
})
},
//图片的上传
chooseImage:function(e){
let that = this;
wx.chooseImage({
count: 3, // 默认最多3张图片可自行更改
sizeType: ['original', 'compressed'],// 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: res => {
wx.showToast({
title: '正在上传...',
icon: 'loading',
mask: true,
duration: 1000
})
// 返回选定照片的本地文件路径列表tempFilePath可以作为img标签的src属性显示图片
let tempFilePath = res.tempFilePaths;
console.log(tempFilePath);
let nowCount = that.data.nowCount;
let tempFilePaths = that.data.tempFilePaths;
if(that.data.nowCount + tempFilePath.length >=3){
let i = 0;
while(nowCount<3){//还可以继续上传图片
tempFilePaths.push(tempFilePath[i]);
i++;
nowCount++;
}
that.setData({
nowCount:3,
tempFilePaths:tempFilePaths
})
}else{
let i = 0;
while(i<tempFilePath.length){
tempFilePaths.push(tempFilePath[i]);
i++;
}
that.setData({
nowCount:that.data.nowCount + tempFilePath.length,
tempFilePaths:tempFilePaths
})
}
console.log(that.data.nowCount,that.data.tempFilePaths);
}
})
},
//长按删除图片
DeleteImg: function (e) {
let that = this;
let tempFilePaths = that.data.tempFilePaths;
let index = e.currentTarget.dataset.index;//获取当前长按图片下标
wx.showModal({
title: '提示',
content: '确定要删除此图片吗?',
success: function (res) {
if (res.confirm) {
//console.log('点击确定了');
let nowCount = that.data.nowCount;
that.setData({
nowCount:nowCount-1
})
tempFilePaths.splice(index, 1);
} else if (res.cancel) {
//console.log('点击取消了');
return false;
}
that.setData({
tempFilePaths:tempFilePaths
});
}
})
},
// 提交修改
sure: function(event) {
var that = this
let content1
let content
// if(this.data.change1==''){console.log("1")}
// if(this.data.change2==''){console.log("2")}
if(this.data.change1==''){
content1=this.data.t
}else{
content1=this.data.change1
}
if(this.data.change2==''){
content=this.data.c
}else{
content=this.data.change2
}
console.log(content1,"1",content,"1")
//图片修改
let i;
let count=0;
let url=this.data.url
let l=url.length
console.log(l)
if(that.data.nowCount==0){
wx.cloud.init({
env:"cloud1-8g5wmepxce8a3b8a",
})
let aid=wx.getStorageSync ("aid")
console.log(aid);
wx.cloud.database().collection('tiezi')
.doc(aid)
.update({
data:{
title:content1,
content: content,
}
}).then(res=>{
console.log(res);
wx.showLoading({
title: '修改成功...',
})
wx.redirectTo({
url: '../answer/answer?aid='+aid,
})
wx.hideLoading()
}).catch(res=>{
console.log(res);
})
}else{
//将所有的内容上传到云端去
for(i=l;i<that.data.nowCount;i++){
console.log(1);
let extName = that.data.tempFilePaths[i].split(".").pop();
let cloudPath = "love/" + new Date().getTime() + "." + extName;
wx.cloud.uploadFile({
cloudPath: cloudPath,
filePath: that.data.tempFilePaths[i], // 文件路径
success: res => {
count++;
url.push(res.fileID);
console.log('上传图片');
console.log(i,url);
if(count==that.data.nowCount){
console.log(url);
}
},fail:res=>{
console.log(res);
}
})
}
wx.cloud.init({
env:"cloud1-8g5wmepxce8a3b8a",
})
let aid=wx.getStorageSync ("aid")
console.log(aid);
wx.cloud.database().collection('tiezi')
.doc(aid)
.update({
data:{
title:content1,
content: content,
url:url
}
}).then(res=>{
console.log(res);
wx.showLoading({
title: '修改成功...',
})
wx.redirectTo({
url: '../answer/answer?aid='+aid,
})
wx.hideLoading()
}).catch(res=>{
console.log(res);
})
}
},
})

@ -1,4 +0,0 @@
{
"component": true,
"usingComponents": {}
}

@ -1,25 +0,0 @@
<!--pages/change/change.wxml-->
<!-- 用户自行修改数据的页面 -->
<view class="wall-item">
<!-- 内容 -->
<textarea class="title" bindinput="change1"placeholder="{{change1}}" decode="{{true}}" value="{{feed.title}}" style="height: 25px;"></textarea>
<textarea bindinput="change" style="white-space:pre-wrap;" value="{{feed.content}}" placeholder="{{change}}" data-aid="{{feed._id}}" class="content"></textarea>
<!--帖子的图片-->
<view class="img_box">
<view class="imgs" wx:for="{{tempFilePaths}}" wx:key="index">
<image src='{{item}}' bindlongpress="DeleteImg" bindtap="listenerButtonPreviewImage" data-index="{{index}}" mode='widthFix' />
</view>
<view class="imgs" wx:if="{{feed.url.length<3}}">
<view class="images" bindtap="chooseImage">
<image src='../../images/upload.png' mode='widthFix' />
</view>
</view>
</view>
</view>
<!-- 确认修改 -->
<button class="publish" bindtap="sure">确认修改</button>

@ -1,73 +0,0 @@
/* pages/change/change.wxss */
.title{
font-size: 38rpx;
display: flex;
margin-top: 25rpx;
padding: 13px 0;
margin-bottom: 30rpx;
border-bottom: 1px solid #ccc;
}
.content{
margin-bottom: 30rpx;
border-bottom: 1px solid #ccc;
padding-right: 50rpx;
margin-top: 30rpx;
display: block;
width: 100%;
height: 550rpx !important;
box-sizing: border-box;
font-size: 32rpx;
color: #454545;
line-height: 44rpx;
/*word-break: break-all;*/
}
.picker{
display: flex;
padding: 13px 0;
margin-bottom: 35rpx;
border-bottom: 1px solid #ccc;
}
.img_box{
position:relative;
display: flex;
flex-wrap: wrap;
margin:0 auto;
}
.imgs{
width:33.33333333%;
display: flex;
justify-content: center;
margin-bottom:20rpx;
}
.imgs image{
width:90%;
max-height:212rpx;
border:1px solid rgba(214, 212, 212, 0.1);
/* box-shadow: 5rpx 5rpx 1rpx 3rpx #e2e0e0; */
}
.imgs .images{
position:relative;
}
.images button{
width:100%;
height:100%;
position:absolute;
top:0;
left:0;
}
.img_box .images{
width:90%;
height: 212rpx;
border:1px solid #E8E8E8;
border-radius:4rpx;
display: flex;
align-items: center;
justify-content: center;
}
.img_box .images>image{
width:60rpx;
height:60rpx;
}

@ -1,252 +0,0 @@
//index.js
let currentPage = 0 // 当前第几页,0代表第一页
let pageSize = 6
var util = require('../../utils/util.js')
var app = getApp()
Page({
data: {
search:'',
re:[],
feed: [],
feed_length: 0,
loadMore: false, //"上拉加载"的变量默认false隐藏
loadAll: false //“没有数据”的变量默认false隐藏
//imgList:[]
},
//事件处理函数
bindItemTap: function(event) {
/*wx.navigateTo({
url: '../answer/answer'
})*/
var aid=event.currentTarget.dataset.aid;
console.log(aid)
//console.log("1")
wx.navigateTo({
url: '../answer/answer?aid='+aid,//要跳转到的页面路径
})
},
onLoad: function () {
console.log('onLoad')
this.clearCache();
let that = this
//调用应用实例的方法获取全局数据
this.getData();
this.refresh()
},
onShow: function (){
console.log('onshow')
this.upper()
},
upper: function () {//下滑刷新
wx.showNavigationBarLoading()
this.refresh();
console.log("upper");
setTimeout(function(){wx.hideNavigationBarLoading();wx.stopPullDownRefresh();}, 1000);
},
lower: function (e) {//触底加载
wx.showNavigationBarLoading();
var that = this;
setTimeout(function(){wx.hideNavigationBarLoading();that.nextLoad();}, 1000);
console.log("lower")
},
scroll: function (e) {
console.log("scroll")
},
//网络请求数据, 实现首页刷新
refresh0: function(){
this.getData;
/*var index_api = '';
util.getData(index_api)
.then(function(data){
//this.setData({
//
//});
console.log(data);
});*/
},
//使用本地 fake 数据实现刷新效果
getData() {
let that = this;
//第一次加载数据
if (currentPage == 1) {
this.setData({
loadMore: true, //把"上拉加载"的变量设为true显示
loadAll: false //把“没有数据”设为false隐藏
})
}
//云数据的请求
wx.cloud.database().collection("tiezi")
.orderBy('createTime', 'desc') //按发布视频排序
.skip(currentPage * pageSize) //从第几个数据开始
.limit(pageSize)
.get({
success(res) {
if (res.data && res.data.length > 0) {
console.log("请求成功", res.data)
currentPage++
//把新请求到的数据添加到dataList里
let list = that.data.feed.concat(res.data)
that.setData({
feed: list, //获取数据数组
loadMore: false //把"上拉加载"的变量设为false显示
});
if (res.data.length < pageSize) {
that.setData({
loadMore: false, //隐藏加载中。。
loadAll: true //所有数据都加载完了
});
}
} else {
that.setData({
loadAll: true, //把“没有数据”设为true显示
loadMore: false //把"上拉加载"的变量设为false隐藏
});
}
},
fail(res) {
console.log("请求失败", res)
that.setData({
loadAll: false,
loadMore: false
});
}
})
},
refresh: function(){
this.clearCache();
this.getData()
let that = this
if (!that.data.loadMore) {
that.setData({
loadMore: true, //加载中
loadAll: false //是否加载完所有数据
});}
},
//使用本地 fake 数据实现继续加载效果
nextLoad: function(){
console.log("上拉触底事件")
let that = this
if (!that.data.loadMore) {
that.setData({
loadMore: true, //加载中
loadAll: false //是否加载完所有数据
});
//加载更多,这里做下延时加载
setTimeout(function() {
that.getData()
}, 500)
}
},
goto(){
wx.navigateTo({
url: '../submit/submit',//要跳转到的页面路径
})
},
// 清缓存
clearCache:function(){
currentPage = 0;//分页标识归零
this.setData({
feed: [] //文章列表数组清空
});
},
GetSearchInput: function(e) {
this.setData({
search: e.detail.value
})
},
ToSearch: function(e) {
//let search = e.detail.value;
var that = this;
if(this.data.search == '') {
wx.showToast({
title: '请输入',
icon: 'none'
})
return
}
wx.showLoading({
title: '搜索中',
})
const _ = wx.cloud.database().command
wx.cloud.database().collection('tiezi').where(_.or([
{
content: wx.cloud.database().RegExp({
regexp: this.data.search,
options: 'i',
}),
},
{
title: wx.cloud.database().RegExp({
regexp: this.data.search,
options: 'i',
}),
}
]))
// wx.cloud.database().collection('tiezi').where({
// content: wx.cloud.database().RegExp({
// regexp: this.data.search,
// options: 'i',
// }),
// })
.get()
.then(res => {
if (res.data.length != 0) {
this.setData({
re: res.data,
})
wx.setStorageSync('re', res.data)
let re= wx.getStorageSync('re')
console.log(re)
let that = this;
wx.hideLoading({
success: (res) => {
that.setData({
search: '',
})
}})
wx.navigateTo({
url: '../searchShow/searchShow?re='+JSON.stringify(re),//要跳转到的页面路径
})
} else {
wx.showToast({
title: '未找到',
icon: 'none'
})
}
console.log(res.data)
})
.catch(res => {
console.log("查询失败",res)
})
},
})

@ -1,71 +0,0 @@
<!--index.wxml-->
<scroll-view scroll-y="true" class="container" bindscrolltoupper="upper" upper-threshold="10" lower-threshold="5" bindscrolltolower="lower" scroll-into-view="{{toView}}" scroll-top="{{scrollTop}}">
<view class="search flex-wrp">
<view class="search-left flex-item">
<input placeholder="点击右侧闪电发帖" placeholder-class="search-placeholder"/>
</view>
<view class="search-right flex-item" bindtap="goto">
<image src="../../images/lighting.png"></image>
</view>
</view>
<view class="sousuokuang">
<view class="sousuo">
<view class="shurukuang">
<input placeholder="搜索" value="{{inputValue}}" bindinput="GetSearchInput"></input>
</view>
<view class="sousuo_anniu" bindtap="ToSearch">
<text>搜索</text>
<icon type="search" size="20"></icon>
</view>
</view>
</view>
<block wx:for="{{feed}}" wx:for-index="idx" wx:for-item="item" data-idx="{{idx}}">
<view class="feed-item">
<view class="feed-source">
<a class="">
<view class="avatar">
<image src="{{item.feed_source_img}}"></image>
<!--<open-data type="userAvatarUrl"></open-data>-->
</view>
<text>{{item.feed_source_name}}</text>
<!-- <open-data type="userNickName"></open-data>-->
</a>
<image class="item-more" mode="aspectFit" bindtap="delete_p" data-aid="{{item._id}}" src="../../images/more.png"></image>
</view>
<view class="feed-content">
<view class="question" bindtap="bindItemTap" data-aid="{{item._id}}">
<a class="question-link">
<text>{{item.title}}</text>
</a>
</view>
<view class="answer-body">
<view >
<text class="answer-txt" bindtap="bindItemTap" data-aid="{{item._id}}">{{item.content}}</text>
</view>
<view class="answer-actions" bindtap="bindItemTap">
<view class="like dot">
<a>{{item.good_num}} 赞同 </a>
</view>
<view class="follow-it">
<text decode="{{true}}">&emsp;&emsp;</text>
</view>
<view class="comments dot">
<a>{{item.comment_num}} 评论 </a>
</view>
</view>
</view>
</view>
</view>
</block>
<view class="loading" hidden="{{!loadMore}}">正在载入更多...</view>
<view class="loading" hidden="{{!loadAll}}">已加载全部</view>
</scroll-view>

@ -1,109 +0,0 @@
/**index.wxss**/
.container{
height: 1500rpx;
}
.container .search{
width: 735rpx;
height: 65rpx;
padding: 12.5rpx 0 12.5rpx 15rpx;
background: #2A8CE5;
}
.container .search-left{
flex: 8;
background: #4EA3E7;
text-align: left;
}
.container .search-left input{
display: inline-block;
height: 65rpx;
font-size: 26rpx;
}
.search-placeholder{
color: #8CCEFD;
line-height: 20rpx;
}
.container .search .search-left image{
display: inline-block;
width: 35rpx;
height: 35rpx;
padding: 15rpx 15rpx 15rpx 20rpx;
}
.container .search .search-right{
flex: 1;
}
.container .search .search-right image{
width: 45rpx;
height: 45rpx;
padding: 10rpx;
}
.container{
padding: 0;
font-size: 14rpx;
background: #F0F4F3;
color: #000;
}
/*feed-item part is in app.wxss for multiplexing*/
.answer-txt{
width:700rpx;
height:49rpx;
font-size:25rpx;
overflow:hidden;
text-overflow: ellipsis;
display:-webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.loading {
font-size: 32rpx;
position: relative;
bottom: 5rpx;
padding: 10rpx;
text-align: center;
}
.sousuokuang {
width: 100%;
height: 100rpx;
display: flex;
flex-direction: column;
align-items: center;
background-color: white;
}
.sousuo {
width: 92%;
height: 100rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-items: center;
}
.shurukuang {
width: 80%;
height: 64rpx;
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f6f6f6;
}
.shurukuang input {
width: 90%;
height: 100%;
font-size: 32rpx;
}
.sousuo_anniu {
width: 20%;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
}
.sousuo_anniu text {
font-size: 30rpx;
}

@ -1,55 +0,0 @@
// pages/login/login.js
Page({
data:{
userInfo:'',
avatarUrl:'',
nickName:''
},
onLoad(){
/*
let user=wx.getStorageSync('user')
this.setData({
userInfo:user
})
wx.switchTab({
url: '/pages/index/index'
})
*/
},
login(){
let that = this;
console.log('点击事件执行了')
wx.getUserProfile({
desc: '必须授权才能使用',
success:res=>{
let user=res.userInfo
let avatarUrl=res.userInfo.avatarUrl
let nickName=res.userInfo.nickName
wx.setStorageSync('user', user)
wx.setStorageSync('avatarUrl', avatarUrl)
wx.setStorageSync('nickName', nickName)
console.log('成功',res)
wx.cloud.database().collection('user').add({
data: {
userInfo:user,
avatarUrl:user.avatarUrl,
nickName:user.nickName
}
})
},
fall:res=>{
console.log('失败',res)
}
})
wx.switchTab({
url: '/pages/index/index'
})
},
nologin(){
this.setData({
userInfo:''
})
wx.setStorageSync('user', null)
},
})

@ -1,3 +0,0 @@
{
"usingComponents": {}
}

@ -1,13 +0,0 @@
<!--pages/login/login.wxml-->
<!--登录-->
<button wx:if="{{!userInfo}}" bindtap="login">登录</button>
<view wx:else class="root">
<image class="touxiang" src="{{userInfo.avatarUrl}}"></image>
<text class="nicheng">{{userInfo.nickName}}</text>
</view>
<!--退出登录-->
<view wx:if="{{userInfo}}">
<button bindtap="nologin" >
<text>退出登录</text>
</button>
</view>

@ -1,12 +0,0 @@
/* pages/login/login.wxss */
.touxiang {
width: 150rpx;
height: 150rpx;
border-radius: 50%;
margin-top: 20rpx;
margin-bottom: 10rpx;
margin-left: 40%;
}
.nicheng{
color:white;
}

@ -1,32 +0,0 @@
//logs.js
var util = require('../../utils/util.js')
var app=getApp()
Page({
data: {
navTab: ["通知", "赞与感谢", "关注"],
currentNavtab: "0",
imgList:[]
},
onLoad: function () {
var that = this
wx.cloud.database().collection('food')
/*.where({
food_name:"烤盘饭",
_openid:undefined
})*/
.get({
success(res) {
console.log("请求成功", res)
that.setData({
imgList:res.data
}),
console.log(res.data)
},
fail(res) {
console.log("请求失败", res)
}
})
},
})

@ -1,3 +0,0 @@
{
"navigationBarTitleText": ""
}

@ -1,7 +0,0 @@
<text class="question-title1"></text>
<block wx:for="{{imgList}}" wx:key="index">
<view class="question" >
<text class="question-title">{{item.food_name}}</text>
<image wx:for="{{item.url}}" src="{{item}}" mode="heightFix" ></image>
</view>
</block>

@ -1,149 +0,0 @@
.a {
padding: 0;
font-size: 14rpx;
background: #F0F4F3;
color: #000;
}
.question {
position: relative;
width: 650rpx;
padding: 40rpx 50rpx 30rpx;
background: #298DE5;
color: #fff;
font-size: 38rpx;
line-height: 48rpx;
}
.answerer-wrp{
position: relative;
width: 750rpx;
height: 150rpx;
background: #fff;
}
.answerer-wrp .bg-half{
position: absolute;
top: 0;
width: 750rpx;
height: 75rpx;
background: #298DE5;
}
.answerer {
position: relative;
margin: 0 auto;
width: 630rpx;
height: 90rpx;
padding: 30rpx;
background: #fff;
border: solid 1px #ebebeb;
border-radius: 3px;
box-shadow: 0 1px 2px #bebebe;
}
.answerer .avatar {
flex: 1;
width: 90rpx;
height: 90rpx;
}
.answerer .avatar image {
display: inline-block;
width: 90rpx;
height: 90rpx;
border-radius: 90rpx;
}
.answerer .answerer-info{
flex: 5;
text-align: left;
padding: 10rpx 20rpx;
line-height: 38rpx;
}
.answerer .answerer-info text{
display: block;
}
.answerer .answerer-info .answerer-name{
font-size: 32rpx;
}
.answerer .answerer-info .answerer-des{
font-size: 22rpx;
color: #808080;
line-height: 28rpx;
}
.answerer .follow{
flex: 2;
padding: 15rpx 0;
font-size: 22rpx;
}
.answerer .follow text{
display: inline-block;
padding: 15rpx 20rpx;
color: #40bcd0;
border: solid 2px #40bcd0;
border-radius: 6rpx;
}
.answer-content{
padding: 30rpx 40rpx;
background: #ffffff;
}
.answer-content text{
font-size: 32rpx;
color: #454545;
line-height: 44rpx;
word-break: break-all;
}
.answer-content image{
width: 100%;
margin: 20rpx 0;
}
.answer-footer{
position: fixed;
bottom: 0;
height: 70rpx;
border-top: solid 1px #ebebeb;
background: #ffffff;
width: 670rpx;
padding: 20rpx 40rpx;
}
.answer-footer .good{
flex: 1;
/*display: inline-block;*/
height: 40rpx;
border: solid 1px #d1d1d1;
border-radius: 3px;
padding: 10rpx 8rpx;
margin: 10rpx 0;
}
.answer-footer .good .good-bad{
display: inline-block;
}
.answer-footer .good image{
display: inline-block;
width: 38rpx;
height: 38rpx;
vertical-align: middle;
}
.answer-footer .good .good-num{
display: inline-block;
padding: 10rpx 4rpx;
/*font-size: 24rpx;*/
}
.answer-footer .operation-wrp{
flex: 5;
}
.answer-footer .operation{
justify-content: space-between;
padding: 0 0 0 60rpx;
}
.answer-footer .operation-btn{
flex: 1;
text-align: center;
}
.answer-footer .operation image{
display: block;
margin: 0 auto;
width: 50rpx;
height: 50rpx;
}
.answer-footer .operation-btn text{
display: block;
font-size: 14rpx;
color: #bebebe;
}

@ -1,30 +0,0 @@
//answer.js
var util = require('../../utils/util.js')
var app = getApp()
Page({
data: {
motto: '知乎--微信小程序版',
userInfo: {}
},
//事件处理函数
bindItemTap: function() {
wx.navigateTo({
url: '../answer/answer'
})
},
onLoad: function () {
console.log('onLoad')
var that = this
//调用应用实例的方法获取全局数据
app.getUserInfo(function(userInfo){
//更新数据
that.setData({
userInfo:userInfo
})
})
},
tapName: function(event){
console.log(event)
}
})

@ -1,3 +0,0 @@
{
"navigationBarTitleText": "问题"
}

@ -1,225 +0,0 @@
<!--question.wxml-->
<view class="container">
<view class="question-wrp">
<view class="question-item">
<view class="que-tag">
<text class="tag">阅读</text>
<text class="tag">电子书</text>
<text class="tag">Kindle</text>
<text class="tag">书籍</text>
<text class="tag">文学</text>
</view>
<view class="que-title">
选择 Kindle 而不是纸质书的原因是什么?
</view>
<view class="que-content">
WEB前端*不靠谱天气预报员*想做代码小仙女
</view>
<view class="que-follow">
<view class="left">
<view class="watch">
<image src="../../images/eye.png"></image>
<text>3316</text>
</view>
<view class="comment">
<image src="../../images/comment2.png"></image>
<text>27</text>
</view>
</view>
<view class="right">
关注
</view>
</view>
</view>
<view class="que-operate flex-wrp">
<view class="invite flex-item">
<image src="../../images/invite.png"></image>
<text>邀请回答</text>
</view>
<view class="write flex-item">
<image src="../../images/write.png"></image>
<text>写回答</text>
</view>
</view>
</view>
<view class="answer-feed">
<view bindtap="bindItemTap" class="feed-item">
<view class="feed-source">
<a class="" bindTap="">
<view class="avatar">
<image src="../../images/icon1.jpeg"></image>
</view>
<text>Rebecca</text>
</a>
</view>
<view class="feed-content">
<view class="answer-body">
<view>
<text class="answer-txt">难道不明白纸质书更贵啊!!! 若觉得kindle更贵我觉得要么阅读量太少那确实没有买kindle的必要。要么买的都是盗版的纸质书我不清楚不加以评论。。。 另外用kindle看小说的... </text>
</view>
<view class="answer-actions">
<view class="like dot">
<a>3.9K 赞同 </a>
</view>
<view class="comments dot">
<a>254 评论 </a>
</view>
<view class="time">
<a>2 个月前</a>
</view>
</view>
</view>
</view>
</view>
<view bindtap="bindItemTap" class="feed-item">
<view class="feed-source">
<a class="" bindTap="">
<view class="avatar">
<image src="../../images/icon1.jpeg"></image>
</view>
<text>Rebecca</text>
</a>
</view>
<view class="feed-content">
<view class="answer-body">
<view>
<text class="answer-txt">难道不明白纸质书更贵啊!!! 若觉得kindle更贵我觉得要么阅读量太少那确实没有买kindle的必要。要么买的都是盗版的纸质书我不清楚不加以评论。。。 另外用kindle看小说的... </text>
</view>
<view class="answer-actions">
<view class="like dot">
<a>3.9K 赞同 </a>
</view>
<view class="comments dot">
<a>254 评论 </a>
</view>
<view class="time">
<a>2 个月前</a>
</view>
</view>
</view>
</view>
</view>
<view bindtap="bindItemTap" class="feed-item">
<view class="feed-source">
<a class="" bindTap="">
<view class="avatar">
<image src="../../images/icon1.jpeg"></image>
</view>
<text>Rebecca</text>
</a>
</view>
<view class="feed-content">
<view class="answer-body">
<view>
<text class="answer-txt">难道不明白纸质书更贵啊!!! 若觉得kindle更贵我觉得要么阅读量太少那确实没有买kindle的必要。要么买的都是盗版的纸质书我不清楚不加以评论。。。 另外用kindle看小说的... </text>
</view>
<view class="answer-actions">
<view class="like dot">
<a>3.9K 赞同 </a>
</view>
<view class="comments dot">
<a>254 评论 </a>
</view>
<view class="time">
<a>2 个月前</a>
</view>
</view>
</view>
</view>
</view>
<view bindtap="bindItemTap" class="feed-item">
<view class="feed-source">
<a class="" bindTap="">
<view class="avatar">
<image src="../../images/icon1.jpeg"></image>
</view>
<text>Rebecca</text>
</a>
</view>
<view class="feed-content">
<view class="answer-body">
<view>
<text class="answer-txt">难道不明白纸质书更贵啊!!! 若觉得kindle更贵我觉得要么阅读量太少那确实没有买kindle的必要。要么买的都是盗版的纸质书我不清楚不加以评论。。。 另外用kindle看小说的... </text>
</view>
<view class="answer-actions">
<view class="like dot">
<a>3.9K 赞同 </a>
</view>
<view class="comments dot">
<a>254 评论 </a>
</view>
<view class="time">
<a>2 个月前</a>
</view>
</view>
</view>
</view>
</view>
<view bindtap="bindItemTap" class="feed-item">
<view class="feed-source">
<a class="" bindTap="">
<view class="avatar">
<image src="../../images/icon1.jpeg"></image>
</view>
<text>Rebecca</text>
</a>
</view>
<view class="feed-content">
<view class="answer-body">
<view>
<text class="answer-txt">难道不明白纸质书更贵啊!!! 若觉得kindle更贵我觉得要么阅读量太少那确实没有买kindle的必要。要么买的都是盗版的纸质书我不清楚不加以评论。。。 另外用kindle看小说的... </text>
</view>
<view class="answer-actions">
<view class="like dot">
<a>3.9K 赞同 </a>
</view>
<view class="comments dot">
<a>254 评论 </a>
</view>
<view class="time">
<a>2 个月前</a>
</view>
</view>
</view>
</view>
</view>
<view bindtap="bindItemTap" class="feed-item">
<view class="feed-source">
<a class="" bindTap="">
<view class="avatar">
<image src="../../images/icon1.jpeg"></image>
</view>
<text>Rebecca</text>
</a>
</view>
<view class="feed-content">
<view class="answer-body">
<view>
<text class="answer-txt">难道不明白纸质书更贵啊!!! 若觉得kindle更贵我觉得要么阅读量太少那确实没有买kindle的必要。要么买的都是盗版的纸质书我不清楚不加以评论。。。 另外用kindle看小说的... </text>
</view>
<view class="answer-actions">
<view class="like dot">
<a>3.9K 赞同 </a>
</view>
<view class="comments dot">
<a>254 评论 </a>
</view>
<view class="time">
<a>2 个月前</a>
</view>
</view>
</view>
</view>
</view>
</view>
</view>

@ -1,180 +0,0 @@
/**answer.wxss**/
.answer-feed {
padding: 0;
font-size: 14rpx;
background: #F0F4F3;
color: #000;
}
.question-wrp{
border-radius: 3px;
box-shadow: 0 1px 2px #bebebe;
}
.question-item{
width: 710rpx;
padding: 40rpx 20rpx 10rpx;
background: #fff;
}
.question-item .que-tag{
}
.question-item .que-tag .tag{
height: 28rpx;
padding: 15rpx 20rpx;
border-radius: 28rpx;
margin: 0 10rpx;
background: #EEF5F8;
color: #2186E0;
font-size: 28rpx;
vertical-align: middle;
}
.question-item .que-title{
padding: 40rpx 20rpx 30rpx;
font-size: 38rpx;
}
.question-item .que-content{
padding: 0 20rpx;
font-size: 26rpx;
}
.question-item .que-follow{
margin: 20rpx;
height: 68rpx;
}
.question-item .que-follow view{
display: inline-block;
margin: 0 40rpx 0 0;
}
.question-item .que-follow image{
width: 50rpx;
height: 50rpx;
margin: 0 10rpx 0 0;
vertical-align: middle;
}
.question-item .que-follow .left{
float: left;
padding: 10rpx 0;
}
.question-item .que-follow .left text{
color: #AFAFAF;
height: 50rpx;
vertical-align: middle;
padding: 18rpx 0;
font-size: 24rpx;
}
.question-item .que-follow .right{
float: right;
padding: 20rpx 55rpx;
color: #ffffff;
background: #52C980;
border-radius: 3px;
font-size: 26rpx;
margin: 0;
}
.que-operate{
width: 750rpx;
border-top: solid 1px #ebebeb;
border-bottom: solid 1px #ebebeb;
color: #889091;
vertical-align: middle;
background: #ffffff;
}
.que-operate .flex-item{
padding: 20rpx 0;
font-size: 26rpx;
}
.que-operate .invite{
border-right: solid 2px #ebebeb;
}
.que-operate image{
width: 50rpx;
height: 50rpx;
vertical-align: middle;
margin: 0 20rpx 0 0;
}
/*.answer-feed .feed-item{*/
/*width: 690rpx;*/
/*padding: 30rpx 30rpx 20rpx;*/
/*margin: 7rpx 0 6rpx 0;*/
/*background: #ffffff;*/
/*border-top: 1px solid #eee;*/
/*border-bottom: 1px solid #eee;*/
/*box-shadow: 0 2px 5px #eeeeee;*/
/*}*/
/*.answer-feed .feed-item .feed-source{*/
/*width: 690rpx;*/
/*left: 0;*/
/*height: 50rpx;*/
/*}*/
/*.answer-feed .feed-item .feed-source .avatar{*/
/*position: relative;*/
/*display: inline-block;*/
/*}*/
/*.answer-feed .feed-item .feed-source a{*/
/*display: inline-block;*/
/*height: 40rpx;*/
/*}*/
/*.answer-feed .feed-item .feed-source .avatar image{*/
/*/!*position: absolute;*!/*/
/*display: inline-block;*/
/*width: 45rpx;*/
/*height: 45rpx;*/
/*border-radius: 45rpx;*/
/*top: 10rpx;*/
/*vertical-align: middle;*/
/*}*/
/*.answer-feed .feed-item .feed-source text{*/
/*/!*position: absolute;*!/*/
/*display: inline-block;*/
/*height: 40rpx;*/
/*line-height: 40rpx;*/
/*vertical-align: middle;*/
/*margin: 0 0 0 15rpx;*/
/*color: #a0acac;*/
/*font-size: 16rpx;*/
/*}*/
/*.answer-feed .feed-item .feed-source .item-more{*/
/*display: inline-block;*/
/*width: 40rpx;*/
/*height: 45rpx;*/
/*float: right;*/
/*}*/
/*.answer-feed .feed-item .feed-content{*/
/*padding: 10rpx 0 0 0;*/
/*}*/
/*.answer-feed .feed-item .feed-content .question text{*/
/*font-size: 28rpx;*/
/*font-weight: 600px;*/
/*line-height: 40rpx;*/
/*text-space: 5rpx;*/
/*}*/
.answer-feed .feed-item .feed-content .answer-body{
padding: 0;
/*height: 10rpx;*/
font-size: 24rpx;
line-height: 28rpx;
color: #5b5b5b;
}
/*.answer-feed .feed-item .feed-content .answer-actions{*/
/*width: 690rpx;*/
/*padding: 10rpx 0 0;*/
/*color: #a0acac;*/
/*}*/
/*.answer-feed .feed-item .feed-content .answer-actions view{*/
/*display: inline-block;*/
/*vertical-align: text-bottom;*/
/*padding: 0 10rpx 0 0;*/
/*font-size: 24rpx;*/
/*}*/
/*.answer-feed .feed-item .feed-content .answer-actions .dot ::after{*/
/*content: "•";*/
/*}*/

@ -1,33 +0,0 @@
// pages/searchShow/searchShow.js
Page({
/**
* 组件的初始数据
*/
data: {
},
onLoad: function(options) {
console.log("1")
console.log(JSON.parse(options.re),"1")
let re = JSON.parse(options.re);
let that = this
that.setData({
re: re
})
console.log(re)
},
bindItemTap: function(event) {
/*wx.navigateTo({
url: '../answer/answer'
})*/
var aid=event.currentTarget.dataset.aid;
console.log(aid)
//console.log("1")
wx.navigateTo({
url: '../answer/answer?aid='+aid,//要跳转到的页面路径
})
}
})

@ -1,4 +0,0 @@
{
"component": true,
"usingComponents": {}
}

@ -1,45 +0,0 @@
<!--pages/searchShow/searchShow.wxml-->
<scroll-view scroll-y="true" class="container" bindscrolltoupper="upper" upper-threshold="10" lower-threshold="5" bindscrolltolower="lower" scroll-into-view="{{toView}}" scroll-top="{{scrollTop}}">
<!-- 搜索结果展示 -->
<block wx:for="{{re}}" wx:for-index="idx" wx:for-item="item" data-idx="{{idx}}">
<view class="feed-item">
<view class="feed-source">
<a class="">
<view class="avatar">
<image src="{{item.feed_source_img}}"></image>
<!--<open-data type="userAvatarUrl"></open-data>-->
</view>
<text>{{item.feed_source_name}}</text>
<!-- <open-data type="userNickName"></open-data>-->
</a>
<image class="item-more" mode="aspectFit" src="../../images/more.png"></image>
</view>
<view class="feed-content">
<view class="question" bindtap="bindItemTap" data-aid="{{item._id}}">
<a class="question-link">
<text>{{item.title}}</text>
</a>
</view>
<view class="answer-body">
<view >
<text class="answer-txt" bindtap="bindItemTap" data-aid="{{item._id}}">{{item.content}}</text>
</view>
<view class="answer-actions" bindtap="bindItemTap">
<view class="like dot">
<a>{{item.good_num}} 赞同 </a>
</view>
<view class="follow-it">
<text decode="{{true}}">&emsp;&emsp;</text>
</view>
<view class="comments dot">
<a>{{item.comment_num}} 评论 </a>
</view>
</view>
</view>
</view>
</view>
</block>
</scroll-view>

@ -1,10 +0,0 @@
.answer-txt{
width:700rpx;
height:49rpx;
font-size:25rpx;
overflow:hidden;
text-overflow: ellipsis;
display:-webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}

@ -1,412 +0,0 @@
Page({
/**
* 页面的初始数据
*/
//分类1
data: {
title: '',
content: '',
question_id:'',
feed_source_img:'',
feed_source_name:'',
tempFilePaths: [],
nowCount:0,//当前的图片上传个数
index:0,
nickName:"",
avatarUrl:"",
feed:[],
pinglun:[],
comment_num:0,
good_num:0
},
bindPickerChange:function(e){
console.log(e)
this.setData({
index:e.detail.value
})
},
//图片的上传
chooseImage:function(e){
let that = this;
wx.chooseImage({
count: 3, // 默认最多3张图片可自行更改
sizeType: ['original', 'compressed'],// 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: res => {
wx.showToast({
title: '正在上传...',
icon: 'loading',
mask: true,
duration: 1000
})
// 返回选定照片的本地文件路径列表tempFilePath可以作为img标签的src属性显示图片
let tempFilePath = res.tempFilePaths;
console.log(tempFilePath);
let nowCount = that.data.nowCount;
let tempFilePaths = that.data.tempFilePaths;
if(that.data.nowCount + tempFilePath.length >=3){
let i = 0;
while(nowCount<3){//还可以继续上传图片
tempFilePaths.push(tempFilePath[i]);
i++;
nowCount++;
}
that.setData({
nowCount:3,
tempFilePaths:tempFilePaths
})
}else{
let i = 0;
while(i<tempFilePath.length){
tempFilePaths.push(tempFilePath[i]);
i++;
}
that.setData({
nowCount:that.data.nowCount + tempFilePath.length,
tempFilePaths:tempFilePaths
})
}
console.log(that.data.nowCount,that.data.tempFilePaths);
}
})
},
//长按删除图片
DeleteImg: function (e) {
let that = this;
let tempFilePaths = that.data.tempFilePaths;
let index = e.currentTarget.dataset.index;//获取当前长按图片下标
wx.showModal({
title: '提示',
content: '确定要删除此图片吗?',
success: function (res) {
if (res.confirm) {
//console.log('点击确定了');
let nowCount = that.data.nowCount;
that.setData({
nowCount:nowCount-1
})
tempFilePaths.splice(index, 1);
} else if (res.cancel) {
//console.log('点击取消了');
return false;
}
that.setData({
tempFilePaths:tempFilePaths
});
}
})
},
//上传文件到云端
submit:function(e){
let i;
let count=0;
let that = this;
let url = [];
let pl=[];
console.log(e);
let title = e.detail.value.name;
let content = e.detail.value.content;
if(title.length<3){
wx.showToast({
title: '标题少于3个字',
icon:"none"
})
return false
}
if(content.length<5){
wx.showToast({
title: '内容少于5个字',
icon:"none"
})
return false
}
wx.showLoading({
title: '发布中',
})
if(that.data.nowCount==0){
/*wx.cloud.callFunction({
name:'love_upload',
data:{
title:title,
content:content,
openid:that.data.openid,
url:[],
},success:function(e){
console.log(e);
wx.hideLoading({
success: (res) => {
that.setData({
value1:'',
value2:'',
tempFilePaths:[],
nowCount:0
})
wx.showToast({
title: '发布成功',
})
},
})
},fail:function(e){
wx.hideLoading({
success: (res) => {
wx.showToast({
title: '网络异常',
})
that.setData({
value1:'',
value2:'',
tempFilePaths:[],
nowCount:0
})
},
})
console.log(e);
}
})*/
let a=wx.getStorageSync ("nickName")
let b=wx.getStorageSync ("avatarUrl")
wx.cloud.database().collection('tiezi').add({
data: {
title:title,
content:content,
openid:that.data.openid,
url:url,
createTime: wx.cloud.database().serverDate(),
feed_source_img:b,
feed_source_name:a,
pinglun:pl,
comment_num:0,
good_num:0
}
})
wx.hideLoading({
success: (res) => {
that.setData({
value1:'',
value2:'',
tempFilePaths:[],
nowCount:0
})
wx.showToast({
title: '发布成功',
})}})
}else{
//将所有的内容上传到云端去
for(i=0;i<that.data.nowCount;i++){
console.log(1);
let extName = that.data.tempFilePaths[i].split(".").pop();
let cloudPath = "love/" + new Date().getTime() + "." + extName;
wx.cloud.uploadFile({
cloudPath: cloudPath,
filePath: that.data.tempFilePaths[i], // 文件路径
success: res => {
count++;
url.push(res.fileID);
console.log('上传图片');
console.log(i,url);
if(count==that.data.nowCount){
console.log(url);
/*
wx.cloud.callFunction({
name:'love_upload',
data:{
title:title,
content:content,
openid:that.data.openid,
url:url,
type:parseInt(that.data.index)+1
},success:function(e){
console.log(e);
wx.hideLoading({
success: (res) => {
that.setData({
value1:'',
value2:'',
tempFilePaths:[],
nowCount:0
})
wx.showToast({
title: '发布成功',
})
},
})
},fail:function(e){
wx.hideLoading({
success: (res) => {
wx.showToast({
title: '网络异常',
})
that.setData({
value1:'',
value2:'',
tempFilePaths:[],
nowCount:0
})
},
})
console.log(e);
}
})*/
let a=wx.getStorageSync ("nickName")
let b=wx.getStorageSync ("avatarUrl")
let pl=[];
wx.cloud.database().collection('tiezi').add({
data: {
title:title,
content:content,
openid:that.data.openid,
url:url,
createTime: wx.cloud.database().serverDate(),
feed_source_img:b,
feed_source_name:a,
pinglun:pl,
comment_num:0,
good_num:0
}
})
wx.hideLoading({
success: (res) => {
that.setData({
value1:'',
value2:'',
tempFilePaths:[],
nowCount:0
})
wx.showToast({
title: '发布成功',
})}})
}
},fail:res=>{
console.log(res);
}
})
}
}
wx.switchTab({
url: '../index/index',
})
},
blur:function(e){
console.log(e);
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
let loveSort = wx.getStorageSync('loveSort')
console.log(loveSort)
this.setData({
loveSort:loveSort
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
},
onAdd_s: function () {
wx.cloud.init({
env: 'cloud1-8g5wmepxce8a3b8a'
});
const db = wx.cloud.database()
db.collection('tiezi').add({
data: {
title:title,
content:content,
openid:that.data.openid,
url:[],
},
success: res => {
// 在返回结果中会包含新创建的记录的 _id
this.setData({
counterId: res._id,
count: 1
})
wx.showToast({
title: '成功扔进树洞~',
})
console.log('[数据库] [留言] 成功,记录 _id: ', res._id)
},
fail: err => {
wx.showToast({
icon: 'none',
title: '种种原因树洞拒绝了~'
})
console.error('[数据库] [新增记录] 失败:', err)
}
})
}
})

@ -1,3 +0,0 @@
{
"navigationBarTitleText": "我要发帖"
}

@ -1,31 +0,0 @@
<!--上传图片演示-->
<form bindsubmit="submit">
<!--帖子的标题-->
<view class="title">
<view class="title_right"><input value="{{value1}}" type="text" name='name' maxlength='100' placeholder='输入标题'/></view>
</view>
<!--帖子的内容-->
<textarea bindblur="blur" value="{{value2}}" auto-height maxlength="500" name='content' placeholder='输入内容'>
</textarea>
<!--帖子的图片-->
<view class="img_box">
<view class="imgs" wx:for="{{tempFilePaths}}" wx:key="index">
<image src='{{item}}' bindlongpress="DeleteImg" bindtap="listenerButtonPreviewImage" data-index="{{index}}" mode='widthFix' />
</view>
<view class="imgs" wx:if="{{nowCount<3}}">
<view class="images" bindtap="chooseImage">
<image src='../../images/upload.png' mode='widthFix' />
</view>
</view>
</view>
<!--提交按钮-->
<button class="tijiao" type="primary" formType="submit">提交</button>
</form>

@ -1,73 +0,0 @@
/* pages/submit/submit.wxss */
form{
display: block;
width: 100%;
padding: 20rpx 17rpx;
}
.title{
font-size: 38rpx;
display: flex;
margin-top: 25rpx;
padding: 13px 0;
margin-bottom: 30rpx;
border-bottom: 1px solid #ccc;
}
textarea{
margin-bottom: 30rpx;
border-bottom: 1px solid #ccc;
padding-right: 50rpx;
margin-top: 30rpx;
display: block;
width: 100%;
height: 550rpx !important;
box-sizing: border-box;
}
.picker{
display: flex;
padding: 13px 0;
margin-bottom: 35rpx;
border-bottom: 1px solid #ccc;
}
.img_box{
position:relative;
display: flex;
flex-wrap: wrap;
margin:0 auto;
}
.imgs{
width:33.33333333%;
display: flex;
justify-content: center;
margin-bottom:20rpx;
}
.imgs image{
width:90%;
max-height:212rpx;
border:1px solid rgba(214, 212, 212, 0.1);
/* box-shadow: 5rpx 5rpx 1rpx 3rpx #e2e0e0; */
}
.imgs .images{
position:relative;
}
.images button{
width:100%;
height:100%;
position:absolute;
top:0;
left:0;
}
.img_box .images{
width:90%;
height: 212rpx;
border:1px solid #E8E8E8;
border-radius:4rpx;
display: flex;
align-items: center;
justify-content: center;
}
.img_box .images>image{
width:60rpx;
height:60rpx;
}

@ -1,30 +0,0 @@
{
"compileType": "miniprogram",
"setting": {
"coverView": true,
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmRelationList": [],
"ignoreDevUnusedFiles": false,
"ignoreUploadUnusedFiles": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
},
"libVersion": "2.27.0",
"packOptions": {
"ignore": [],
"include": []
},
"appid": "wxcc745ea897810e52"
}

@ -1,7 +0,0 @@
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "lt2",
"setting": {
"compileHotReLoad": true
}
}

@ -1,67 +0,0 @@
function formatTime(date) {
var year = date.getFullYear()
var month = date.getMonth() + 1
var day = date.getDate()
var hour = date.getHours()
var minute = date.getMinutes()
var second = date.getSeconds()
return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
function formatNumber(n) {
n = n.toString()
return n[1] ? n : '0' + n
}
module.exports = {
formatTime: formatTime
};
/*
var index = require('../data/data_index.js')
var index_next = require('../data/data_index_next.js')
function getData(url){
return new Promise(function(resolve, reject){
wx.request({
url: url,
data: {},
header: {
//'Content-Type': 'application/json'
},
success: function(res) {
console.log("success")
resolve(res)
},
fail: function (res) {
reject(res)
console.log("failed")
}
})
})
}
function getData2(){
return index.index;
}
function getNext(){
return index_next.next;
}
module.exports.getData = getData;
module.exports.getData2 = getData2;
module.exports.getNext = getNext;
module.exports.getDiscovery = getDiscovery;
module.exports.discoveryNext = discoveryNext;
*/

@ -1,7 +0,0 @@
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "%E5%A4%96%E5%8D%96",
"setting": {
"compileHotReLoad": true
}
}

@ -1,2 +0,0 @@
.idea
node_modules

@ -1,549 +0,0 @@
相机导购专家系统
====================================
如今单反相机的高度发达为摄影爱好者带来了无限的可能,让每个人都可以在花不是那么多钱的情况下创造艺术或是记录生活。我认为,这是科技给人带来的福音。
然而,正是因为单反市场太过发达,初入单反坑的玩家往往被搞得晕头转向。常见的单反厂商有尼康、索尼、佳能。高端市场有哈苏、徕卡,低端市场有理光、富士等。每个品牌又有很多个产品线,各有不同,设计给不同需求的用户使用。
如果对单反相机的参数没有深入的研究,仅仅是听商家的吹捧,那很难在最高的性价比上买到合适自己的那款相机。往往会花冤枉钱,或发现相机的主打功能自己根本就用不上。
本专家系统就为了解决这个问题而创建,担任一个细心公正的相机导购师,通过询问用户更在意哪方面的内容、平时使用情景、预算、对重量的承受能力等,为用户推荐最合适的相机,让用户少花冤枉钱,用最少的钱买到最有用的相机。
# 数据库的采集
对专家系统而言不仅要有规则数据库相机本身各项指标的数据库也非常重要。本专家系统采集权威平台DxOMark的数据库来获得详细的相机各项指标数据。
由于没有现成的数据可供下载我自己使用NodeJS写了采集程序来做这件事情。代码如下
```javascript
let request = require('request').defaults({ 'proxy': "http://127.0.0.1:1080" });
let Queue = require('promise-queue');
let fs = require('fs');
let path = require('path');
let queue = new Queue(10); //最多同时10线程采集
request('https://www.dxomark.com/daksensor/ajax/jsontested', //获得相机列表
(error, response, body) => {
let cameraList = JSON.parse(body).data;
let finishedCount = 0;
cameraList.forEach(cameraMeta => {
let camera = Object.assign({}, cameraMeta);
let link = `https://www.dxomark.com${camera.link}---Specifications`; //拼合对应的规格网址
queue.add(() => new Promise(res => { //把request放入队列以保证同时http请求不超过10个
let doRequest = () => {
request(link, (error, response, body) => {
if (error) { //如果失败则重试
console.log("retrying.." + camera.name);
doRequest();
return;
}
let specMatcherRegexp = /descriptifgauche.+?>([\s\S]+?)<\/td>[\s\S]+?descriptif_data.+?>([\s\S]+?)<\/td>/img;
let match = specMatcherRegexp.exec(body);
while (match) { //用正则表达式匹配table里的每一个项目并添加至采集结果中
camera[match[1]] = match[2];
match = specMatcherRegexp.exec(body);
}
fs.writeFileSync(path.join('./scraped', camera.name + '.txt'), JSON.stringify(camera, null, 4), { encoding: "UTF8" });
finishedCount++;
console.log(`Finished ${finishedCount}/${cameraList.length}: ${camera.name}`);
res();
})
};
doRequest();
}));
});
});
```
采集结果如图所示总共采集到357款单反产品
![scrape](imgs/scrape.gif)
# 数据清洗
数据采集完了,但有些数据比较脏(如含有&nbsp有些数据是我们不需要的有些数据用起来不方便如分辨率是 1234 x 1234形式的字符串属性名中有空格和大写字符也不美观。因此额外添加一步数据清洗。
数据清洗之后,希望留下以下属性:
> 相机名称、相机图片、价格、像素数量、每秒连拍数量、重量、对焦系统质量、最大感光度、模型出厂日期、触屏存在、可录视频、有闪光灯、有蓝牙、有GPS、防水、机身材质质量
数据清洗的代码实现为:
```javascript
let fs = require("fs");
let path = require("path");
function parseNumberFunctionFactory(keyMatcher, valueMatcher = /(\d+\.?\d*)/im,
returnValueDecider = match => +match[1]) {
return (data) => {
let key = Object.keys(data).filter(_ => keyMatcher.test(_.trim()))[0];
if (!key) return null;
let match = data[key].toString().match(valueMatcher);
if (match)
return returnValueDecider(match);
else
return null;
};
}
let parseResolution = parseNumberFunctionFactory(/^resolution$/im, /(\d+)\s*x\s*(\d+)/im, match => [+match[1], +match[2]]);
let parseFrameRate = parseNumberFunctionFactory(/frame rate/im);
let parseWeight = parseNumberFunctionFactory(/weight/im);
let parseAutoFocus = parseNumberFunctionFactory(/number of autofocus points/im);
let parseISO = parseNumberFunctionFactory(/ISO latitude/im, /(\d+)\s*-\s*(\d+)/im, match => [+match[1], +match[2]]);
let parseLaunchDate = parseNumberFunctionFactory(/launchDateGraph/im, /(\d+)-(\d+)-(\d+)/im, match => new Date(match[1], match[2] - 1, match[3]));
let parseTouchScreen = parseNumberFunctionFactory(/Touch screen/im, /yes/im, match => !!match);
let parseVideo = parseNumberFunctionFactory(/^Video$/m, /yes/im, match => !!match);
let parseFlash = parseNumberFunctionFactory(/^flash$/im, /yes/im, match => !!match);
let parseWaterproof = parseNumberFunctionFactory(/^waterproof$/im, /yes/im, match => !!match);
let parseBluetooth = parseNumberFunctionFactory(/^Bluetooth$/im, /yes/im, match => !!match);
let parseGps = parseNumberFunctionFactory(/^GPS$/im, /yes/im, match => !!match);
let parseIsMetal = parseNumberFunctionFactory(/^camera material$/im, /metal/im, match => !!match);
let files = fs.readdirSync("./scraped");
files.forEach(file => {
let data = JSON.parse(fs.readFileSync(path.join('./scraped', file), { encoding: "utf8" }));
let resolution = parseResolution(data);
//机身材质质量
let frameRate = parseFrameRate(data);
let weight = parseWeight(data);
let autoFocus = parseAutoFocus(data);
let iso = parseISO(data);
let launchDate = parseLaunchDate(data);
let touchScreen = parseTouchScreen(data);
let video = parseVideo(data);
let flash = parseFlash(data);
let waterproof = parseWaterproof(data);
let bluetooth = parseBluetooth(data);
let gps = parseGps(data);
let isMetal = parseIsMetal(data);
let cleanedData = {
name: data.name,
image: data.image,
brand: data.brand,
price: data.price,
pixelDepth: data.pixelDepth,
pixels: resolution ? (resolution[0] * resolution[1]) : 0,
ISO: iso,
maxISO: iso ? iso[1] : 0,
launchDate: +launchDate,
touchScreen,
video,
flash,
waterproof,
bluetooth,
gps,
isMetal,
frameRate,
resolution,
weight,
autoFocus,
};
fs.writeFileSync(path.join('./cleaned', file), JSON.stringify(cleanedData, null, 4), { encoding: "utf8" });
});
```
清洗结果演示:
```json
{
"name": "Nikon D5",
"image": "//cdn.dxomark.com/dakdata/xml/D5/vignette3.png",
"brand": "Nikon",
"price": 6500,
"pixelDepth": 20.8,
"pixels": 20817152,
"ISO": [
50,
3280000
],
"maxISO": 3280000,
"launchDate": 1452009600000,
"touchScreen": true,
"video": true,
"flash": null,
"waterproof": null,
"bluetooth": null,
"gps": true,
"isMetal": true,
"frameRate": 14,
"resolution": [
5584,
3728
],
"weight": 1225,
"autoFocus": 153
}
```
看起来好多了。
# 问题的设计
斟酌一番后,我决定将询问用户的问题定为:
1. 您将如何使用本相机(多选)
* 记录旅行
* 拍摄学校或公司活动
* 拍摄体育比赛
* 拍摄自然风景
* 拍摄人像
* 拍摄天文
2. 您看中哪些额外功能吗(多选)
* 内置闪光灯
* 可录制视频
* 可蓝牙传输照片
* 可触屏
* 内置GPS
* 防水
3. 您是否愿意承受单反的重量
* 没问题3公斤的机器都扛得住
* 在能避免负重的情况下尽可能避免负重
* 不愿意接受重的单反,必须较为轻便
4. 您愿意在单反上投入的经济
* 很多,一步到位买高端设备
* 普通,好用实用的设备
* 经济,请推荐入门基本款
5. 您有什么别的要求吗(多选)
* 尽量购买新的型号
* 机身材质要好
# 模糊专家系统的设计
一个显著的问题是:模糊专家系统一般只能用于数值的计算(如评估房价),但我这里做的,却是根据用户的输入推荐产品。用模糊专家系统如何做产品推荐呢?
绕一个弯,不难解决这个问题:使用模糊专家系统对每一款相机产品计算出一个“推荐度”,根据推荐度排序,给用户推荐排名前三的产品。
具体的,我使用专家系统,计算出以下几个指标:
> 适合拍摄旅行,适合拍摄活动,适合拍摄体育,适合拍摄风景,适合拍摄人像,适合拍摄天文,型号新,机身材质好,重量轻,价格低
用户的要求可以和这几个指标对号入座,来计算要求满足度。
对于另外一些如“有内置闪光灯”等指标,用不着用模糊专家系统,直接布尔判断来算满足度。
满足度根据规则加权平均,就是产品的总推荐度,排序后,前几名就是推荐给用户的相机。
这样做的好处除了推荐相机之外还可以列出相机的Pros & Cons (如:+ 适合拍摄天文 + 可录制视频 - 重量较重),只要去取满足度最高/最低的几名就可以了。
为实现模糊专家系统我使用jFuzzyLogic框架它是Java下最完整的模糊逻辑框架支持fuzzy language (FCL)IEC 61131-7标准。
# 规则的建立
模糊专家系统在相机推荐中,最重要的工作就是:建立相机参数和相机适合拍摄的照片种类之间的联系,比如:
> 适合拍摄体育类型相片的相机,需要有**很高**的连拍速度、**较好**的对焦系统。
在这个实验性的导购系统中,由我自己担任“领域专家”的角色。
首先,要定义术语,如:
* 价格(美金)便宜: \$0-1000 中等: \$600-1700 贵: \$1500-
翻译成FCL之后
```
FUZZIFY price
TERM low := (0, 1) (1000, 0) ;
TERM medium := (600, 0) (800,1) (1500,1) (1700,0);
TERM high := (1500, 0) (1700, 1);
END_FUZZIFY
FUZZIFY weight
TERM light := (0, 1) (500, 0) ;
TERM medium := (300, 0) (500,1) (700,1) (1000,0);
TERM heavy := (800, 0) (1000, 1);
END_FUZZIFY
...
```
每个术语的定义如图所示:
![fuzzify](imgs/fuzzify.gif)
然后要定义defuzzify规则这里方便起见均只定义“veryGood”、“good”、"average"、"bad"、"veryBad"。
```
DEFUZZIFY travel
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
```
最后,要定义规则,举例如下:
* 如果 感光度范围 很高 而且 像素 高 那么 适合拍天文 高
* 如果 连拍速度 很高 而且 对焦系统 好 那么 适合拍体育 高
* 如果 重量 轻 且 有GPS 那么 适合记录旅行 高
* ……
翻译成FCL之后
```
RULEBLOCK travel
AND : MIN;
RULE 1 : IF weight IS light THEN travel IS good;
RULE 2 : IF video IS yes THEN travel IS good;
RULE 3 : IF gps IS yes THEN travel IS good;
RULE 4 : IF flash IS no THEN travel IS bad;
RULE 5 : IF weight IS heavy THEN travel IS veryBad;
END_RULEBLOCK
RULEBLOCK sports
AND : MIN;
RULE 1 : IF frameRate IS high THEN sports IS veryGood;
RULE 2 : IF autoFocus IS high THEN sports IS veryGood;
RULE 3 : IF pixels IS high THEN sports IS good;
RULE 4 : IF frameRate IS low THEN sports IS veryBad;
RULE 5 : IF autoFocus IS low THEN sports IS veryBad;
END_RULEBLOCK
RULEBLOCK astronomy
AND : MIN;
RULE 1 : IF pixels IS high THEN astronomy IS good;
RULE 2 : IF pixelDepth IS high THEN astronomy IS good;
RULE 3 : IF maxISO IS high THEN astronomy IS good;
RULE 4 : IF maxISO IS low THEN astronomy IS veryBad;
RULE 5 : IF pixels IS low THEN astronomy IS veryBad;
END_RULEBLOCK
...
```
# 打分的具体实现
数据和规则都准备就绪后,就可以开始进行模糊推理。
Java程序框架如下
```java
package ai.fuzzy;
import com.alibaba.fastjson.JSON;
import net.sourceforge.jFuzzyLogic.FIS;
import net.sourceforge.jFuzzyLogic.FunctionBlock;
import net.sourceforge.jFuzzyLogic.plot.JFuzzyChart;
import net.sourceforge.jFuzzyLogic.rule.Variable;
import java.io.*;
import java.nio.file.Paths;
class CameraData {
public CameraAssessment assessment;
public String name;
public String image;
public String brand;
public Integer price;
public Integer pixelDepth;
public Integer pixels;
public Integer maxISO;
public Integer weight;
public Integer autoFocus;
public Long launchDate;
public Float frameRate;
public Integer[] resolution;
public Integer[] ISO;
public boolean touchScreen;
public boolean video;
public boolean flash;
public boolean waterproof;
public boolean bluetooth;
public boolean gps;
public boolean isMetal;
}
class CameraAssessment {
public double travel;
public double event;
public double sports;
public double scenery;
public double portrait;
public double astronomy;
public double newModel;
public double durableBuild;
public double lightBuild;
public double lowPrice;
}
public class Main {
public static void main(String[] args) throws IOException {
File rootFolder = new File("input");
for (final File fileEntry : rootFolder.listFiles()) {
if (fileEntry.isFile()) {
CameraData camera = JSON.parseObject(readFileContents(fileEntry), CameraData.class);
camera.assessment = assess(camera);
writeFile(fileEntry, JSON.toJSONString(camera, true));
}
}
}
private static void writeFile(File fileEntry, String jsonString) throws FileNotFoundException {
...
}
private static String readFileContents(File fileEntry) throws IOException {
...
}
static CameraAssessment assess(CameraData cameraData) {
...
}
}
```
```assess```函数如下:
```java
static CameraAssessment assess(CameraData cameraData) {
CameraAssessment cameraAssessment = new CameraAssessment();
String fileName = "fcl/camera.fcl";
FIS fis = FIS.load(fileName, true);
// Set inputs
fis.setVariable("price", cameraData.price);
fis.setVariable("pixelDepth", cameraData.pixelDepth);
fis.setVariable("pixels", cameraData.pixels);
fis.setVariable("maxISO", cameraData.maxISO);
fis.setVariable("weight", cameraData.weight);
fis.setVariable("autoFocus", cameraData.autoFocus);
fis.setVariable("launchDate", cameraData.launchDate);
fis.setVariable("frameRate", cameraData.frameRate);
fis.setVariable("touchScreen", cameraData.touchScreen ? 1 : 0);
fis.setVariable("video", cameraData.video ? 1 : 0);
fis.setVariable("flash", cameraData.flash ? 1 : 0);
fis.setVariable("waterproof", cameraData.waterproof ? 1 : 0);
fis.setVariable("bluetooth", cameraData.bluetooth ? 1 : 0);
fis.setVariable("gps", cameraData.gps ? 1 : 0);
fis.setVariable("isMetal", cameraData.isMetal ? 1 : 0);
// Evaluate
fis.evaluate();
// Save results to cameraAssessment
cameraAssessment.travel = fis.getVariable("travel").defuzzify();
cameraAssessment.event = fis.getVariable("event").defuzzify();
cameraAssessment.sports = fis.getVariable("sports").defuzzify();
cameraAssessment.scenery = fis.getVariable("scenery").defuzzify();
cameraAssessment.portrait = fis.getVariable("portrait").defuzzify();
cameraAssessment.astronomy = fis.getVariable("astronomy").defuzzify();
cameraAssessment.newModel = fis.getVariable("newModel").defuzzify();
cameraAssessment.durableBuild = fis.getVariable("durableBuild").defuzzify();
cameraAssessment.lightBuild = fis.getVariable("lightBuild").defuzzify();
cameraAssessment.lowPrice = fis.getVariable("lowPrice").defuzzify();
return cameraAssessment;
}
```
调用后,模糊专家系统就会给每个相机做评测,并作出如下输出:
```json
"assessment": {
"astronomy": 0.07206054514320881,
"durableBuild": 0.1999999999999999,
"event": 0.1999999999999999,
"lightBuild": 0.1999999999999999,
"lowPrice": 0.1999999999999999,
"newModel": 0.20107756449438624,
"portrait": 0.2280106572609703,
"scenery": 0.2280106572609703,
"sports": 0.07875190169689873,
"travel": 0.16167332382310975,
},
```
分别是相机对于每一项情景的适合度在0-1之间。
接下来,需要把用户输入和适合度整合,得出总评分。
这里,总评分直接通过用户输入和适合度做内积的方法获得,评分的同时,也记录分数变动记录,这样给用户推荐就可以说明推荐原因。
```javascript
function evaluate(item, tags) {
let score = 0, changes = [];
tags.forEach(tag => {
let reverse = false;
let normalizedTag = tag;
if (tag.startsWith("!")) {
//允许使用!开头,表示相反。如:!lowPrice时lowPrice原本加分现在变成减分
reverse = true;
normalizedTag = tag.substr(1);
}
let scoreChange = 0;
if (item.assessment[normalizedTag]) { // 如果是专家系统assess出来的结果一个占20分
scoreChange = (reverse ? -1 : 1) * (item.assessment[normalizedTag] * 20) * (weight[normalizedTag] || 1);
} else { // 如果不是那么是“防水”等基本要求一个占3分
scoreChange = (reverse ? -1 : 1) * (item[normalizedTag] ? 1 : -1) * 3 * (weight[normalizedTag] || 1);
}
if (scoreChange) {
score += scoreChange;
changes.push([scoreChange, tag]); // 记录评分变化之后好出pros & cons
}
});
return { score, changes };
}
```
系统核心算法大致就完成了。
# 参数调整
参数调整方面,花了不少心思。
原来的专家系统规则中用了比较多的OR导致多款相机某个指标打分非常相近没有区分度。因此我后来重写了FCL尽量避免使用OR。为了拉开差距我还把原来的“good”、"average"、"bad"改成了“veryGood”、“good”、"average"、"bad"、"veryBad",以说明某些规格的重要性大于其他规格。
由于RULES的good、bad不平衡会导致一些assessment普遍偏低另一些普遍偏高因此在后续处理中我把这些assessment进行了normalization让他们的平均值归一到0。
又出现了新的问题:推荐的相机,基本都是“价格低”、“型号新”,即这两个特性给相机总体加分太多,掩盖了别的优点,因此,我人工指定了指标的权重,以削弱价格和型号新旧对总体评分的影响,彰显“适合拍的类型”在推荐中的占比。
但还有很多问题,其中最主要的是:数据区分度不明显,高端相机的数据都差不多;指标内部有较强的关联性(合适拍天文的,一般也合适拍风景)。数据内部关联性不好解决。
经过一番调整,系统可以产生尚可的结果。
# 前端与运行效果
为了获得更好的效果我为专家系统做了一个用户友好的前端界面可以引导用户输入信息、以用户友好的方式给出推荐结果。前端是使用React + antd做的基于Web。代码省略界面长这样
![ui](imgs/ui.png)
以上用户选择会被转换为tags:
```json
["travel", "scenery", "video", "gps", "!lowPrice", "durableBuild"]
```
然后这些tags会被送到之前设计好的系统中进行运算内积rank获得结果。
![ui2](imgs/ui2.png)
可以看出本系统可以根据用户的需要推荐相机Pros和Cons都与用户第一步选择的“需求”有关
并能以一种易于理解的方式说清楚每一项“需求”是如何影响最终打分的。
# 结论
本导购系统只是一个试水,效果还过得去。
在进行导购系统制作的过程中,我充分体验了制作一个专家系统需要的每一个步骤。从采集数据,到模糊集定义,到模糊集推理,到整合结果并输出给用户看。
这个专家系统与普通的估测房价/工资/小费的模糊专家系统不同必须对模糊专家系统defuzzify之后的结果进行进一步的整合并给用户做出推荐。这个整合算法应该也能算是广义专家系统的一部分吧。
效果比预期的稍微差一些,我认为原因主要在:
1. 原始数据不够完整DxOMark上仍然很多型号缺很多数据
2. 数据区分度不高、内部关联性太强(高端相机的指标都差不多;合适拍天文的,一般也合适拍风景)
进一步完善数据库、调整参数、或换一种性价比的模型之后,有望提升整体效果。
但无论如何,本专家系统至少做到了往正确的方向响应用户的输入,有理有据地为用户推荐符合要求的相机。
这种专家系统是有价值的,在进一步的优化后,说不定能产生一些商业效益。
现在我仅仅对机身做了推荐,如果加上镜头的组合,那么规则更加复杂、要考虑的因素更加多。
但按照规则解决这些复杂的问题,或许就是专家系统真正意义所在。真的解决之后,专家系统就可以和一个专业的人类导购师一样,耐心服务每一位顾客,让每个人都把钱花在刀刃上。

Binary file not shown.

@ -1,2 +0,0 @@
node_modules
.idea

@ -1,32 +0,0 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server"
},
"dependencies": {
"antd": "^3.1.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"css-loader": "^0.28.7",
"html-webpack-plugin": "^2.30.1",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"style-loader": "^0.19.0",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.4"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-plugin-import": "^1.6.3",
"less": "^2.7.3",
"less-loader": "^4.0.5"
}
}

@ -1,154 +0,0 @@
import React from 'react';
import "./index.less";
import rank from "./rank";
import translateTag from "./translateTag";
import { Layout, Menu, Breadcrumb, Button, Checkbox, Divider } from 'antd';
const { Header, Content, Footer } = Layout;
import questions from "./questions";
export default class App extends React.Component {
constructor() {
super();
this.state = {
selection: [[1, 0, 1, 0, 1, 1], [1, 1, 0, 0, 1], 0, 0, [1, 0]],
recommendations: [],
};
}
render() {
return <Layout>
<Header style={{ position: 'fixed', width: '100%' }}>
<div className="logo"/>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['1']}
style={{ lineHeight: '64px' }}
>
<Menu.Item key="1">相机推荐专家系统</Menu.Item>
</Menu>
</Header>
<Content style={{ padding: '0 50px', marginTop: 64 }}>
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item> 相机推荐专家系统
</Breadcrumb.Item>
</Breadcrumb>
{!this.state.recommendations.length &&
<div style={{ background: '#fff', padding: 24, minHeight: 380 }}>
{questions.map((question, questionIndex) =>
<div key={questionIndex}>
<h4>{question.text}</h4>
{
question.options.map((option, optionIndex) =>
<div key={optionIndex}><Checkbox
checked={(question.multiple && this.state.selection[questionIndex][optionIndex]) || (!question.multiple && this.state.selection[questionIndex] === optionIndex)}
onChange={_ => {
let newSelection = [...this.state.selection];
if (question.multiple) {
newSelection[questionIndex] = [...newSelection[questionIndex]];
newSelection[questionIndex][optionIndex] = _.target.checked;
} else {
if (!_.target.checked) return;
newSelection[questionIndex] = optionIndex;
}
this.setState({ selection: newSelection });
}}>{option.text}</Checkbox></div>,
)
}
<Divider/>
</div>,
)}
<Button type="primary" onClick={() => {
this.setState({ recommendations: rank(this.state.selection) })
}}>获得推荐</Button>
</div>}
{!!this.state.recommendations.length &&
<div style={{ background: '#fff', padding: 24, minHeight: 380 }}>
{this.state.recommendations.map((recommendation, index) =>
<div key={index} className="recommendation-item">
<span className={"place place-" + index}>#{index + 1}</span>
<div className="camera">
<div className="image">
<div>
<img src={recommendation.image} alt=""/>
</div>
<div className="name">
{recommendation.name}
</div>
<div className="score">
推荐指数{(recommendation.score+ 5).toFixed(2) }
</div>
</div>
<table>
<tr>
<td>
价格: ${recommendation.price}
</td>
<td>
重量: {recommendation.weight}g
</td>
<td>
自动对焦: {recommendation.autoFocus}
</td>
</tr>
<tr>
<td>
fps: {recommendation.frameRate}
</td>
<td>
分辨率: {recommendation.resolution[0]} * {recommendation.resolution[1]} px
</td>
<td>
ISO: {recommendation.ISO[0]} ~ {recommendation.ISO[1]}
</td>
</tr>
<tr>
<td>
色彩位数: {recommendation.pixelDepth} px
</td>
<td>
出厂日期: {new Date(recommendation.launchDate).toLocaleDateString()}
</td>
<td>
其他功能: {recommendation.bluetooth ? "蓝牙" : ""} {recommendation.gps ? "GPS" : ""} {recommendation.touchScreen ? "触屏" : ""} {recommendation.video ? "录像" : ""} {recommendation.flash ? "闪光灯" : ""} {recommendation.waterproof ? "防水" : ""}
</td>
</tr>
</table>
<div className="pros-cons">
<div className="pros">
<b>Pros</b>
<div className="list">
{recommendation.pros.map(_ =>
<div>+ {translateTag(_[1])} <span
className="score-hint">+{_[0].toFixed(2)}</span></div>,
)}
</div>
</div>
<div className="cons">
<b>Cons</b>
<div className="list">
{recommendation.cons.map(_ =>
<div>- {translateTag(_[1], true)} <span
className="score-hint">{_[0].toFixed(2)}</span></div>,
)}
</div>
</div>
</div>
</div>
<Divider/>
</div>,
)}
</div>}
</Content>
<Footer style={{ textAlign: 'center' }}>
人工智能Project by 王轲(14307130048)
</Footer>
</Layout>;
}
}

File diff suppressed because it is too large Load Diff

@ -1,8 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
let root=document.createElement('div');
root.className='root';
document.body.appendChild(root);
ReactDOM.render(React.createElement(App), root);

@ -1,88 +0,0 @@
* {
font-family: "Microsoft YaHei UI", "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}
.place {
font-size: 30px;
font-weight: 900;
position: absolute;
left: -2px;
top: -2px;
opacity: .3;
}
.place-0 {
font-size: 50px;
}
.place-1 {
font-size: 40px;
}
.recommendation-item {
position: relative;
padding: 20px;
.camera {
position: relative;
padding-left: 160px;
table {
width: 100%;
}
.image {
position: absolute;
left: 0;
width: 120px;
text-align: center;
padding-top: 18px;
img {
width: 120px;
}
.name {
font-weight: bold;
width: 120px;
white-space: normal;
display: block;
margin-top: 10px;
}
.score {
opacity: .5;
font-size: 11px;
}
}
}
}
.table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid #e8e8e8;
}
th, td {
padding: 8px;
text-align: left;
}
.pros-cons {
display: flex;
padding: 10px 0;
.score-hint {
font-size: 9px;
opacity: .3;
color:black;
}
.pros {
color: #1890ff;
flex: 1;
}
.cons {
color: #F5222D;
flex: 1;
}
.list {
font-size: 11px;
}
}

@ -1,47 +0,0 @@
let questions = [
{
text: "您将如何使用本相机(多选)", multiple: true,
options: [
{ text: '记录旅行', value: 'travel' },
{ text: '拍摄学校或公司活动', value: 'event' },
{ text: '拍摄体育比赛', value: 'sports' },
{ text: '拍摄自然风景', value: 'scenery' },
{ text: '拍摄人像', value: 'portrait' },
{ text: '拍摄天文', value: 'astronomy' }],
},
{
text: "您看中哪些额外功能吗(多选)", multiple: true,
options: [
{ text: '内置闪光灯', value: "flash" },
{ text: '可录制视频', value: "video" },
{ text: '可蓝牙传输照片', value: "bluetooth" },
{ text: '可触屏', value: "touchScreen" },
{ text: '内置GPS', value: "gps" },
{ text: '防水', value: "waterproof" }],
},
{
text: "您是否愿意承受单反的重量",
options: [
{ text: '没问题3公斤的机器都扛得住', value: "!lightBuild" },
{ text: '在能避免负重的情况下尽可能避免负重' },
{ text: '不愿意接受重的单反,必须较为轻便', value: "lightBuild" },
],
},
{
text: "您愿意在单反上投入的经济",
options: [
{ text: '很多,一步到位买高端设备', value: "!lowPrice" },
{ text: '普通,好用实用的设备' },
{ text: '经济,请推荐入门基本款', value: "lowPrice" },
],
},
{
text: "您有什么别的要求吗(多选)", multiple: true,
options: [
{ text: '尽量购买新的型号', value: "newModel" },
{ text: '机身材质要好', value: "durableBuild" },
],
},
];
export default questions;

@ -1,86 +0,0 @@
import questions from "./questions";
import data from "./data";
export default function rank(selection) {
let tags = [];
questions.forEach((question, index) => {
if (question.multiple) {
selection[index].forEach((sel, ind) => {
if (sel) {
tags.push(question.options[ind].value);
}
})
} else {
tags.push(question.options[selection[index]].value);
}
});
tags = tags.filter(_ => _);
let sortedData = [...data];
sortedData.forEach(item => {
let { score, changes } = evaluate(item, tags);
item.score = score;
item.changes = changes;
});
sortedData.sort((a, b) => {
return b.score - a.score;
});
let retData = sortedData.slice(0, 5);
retData.forEach(item => {
item.pros = item.changes.filter(_ => _[0] > 0);
item.cons = item.changes.filter(_ => _[0] < 0);
item.pros.sort((a, b) => {
return b[0] - a[0];
});
item.cons.sort((a, b) => {
return Math.abs(b[0]) - Math.abs(a[0]);
});
});
console.log(sortedData);
return retData;
}
const weight = {
newModel: 0.5,
lowPrice: 0.2,
lightBuild: 0.4,
travel: 2,
event: 3,
sports: 2,
scenery: 3,
portrait: 5,
astronomy: 3,
};
const shift = {
travel: -1.5,
event: -1.5,
sports: -1.5,
scenery: -1.5,
portrait: -1.5,
astronomy: -1.5,
};
function evaluate(item, tags) {
let score = 0, changes = [];
tags.forEach(tag => {
let reverse = false;
let normalizedTag = tag;
if (tag.startsWith("!")) {
//允许使用!开头,表示相反。如:!lowPrice时lowPrice原本加分现在变成减分
reverse = true;
normalizedTag = tag.substr(1);
}
let scoreChange = 0;
if (item.assessment[normalizedTag]) { // 如果是专家系统assess出来的结果一个占20分
scoreChange = (reverse ? -1 : 1) * (item.assessment[normalizedTag] * 20 + (shift[normalizedTag] || 0)) * (weight[normalizedTag] || 1);
} else { // 如果不是那么是“防水”等基本要求一个占3分
scoreChange = (reverse ? -1 : 1) * (item[normalizedTag] ? 1 : -1) * (3 + (shift[normalizedTag] || 0)) * (weight[normalizedTag] || 1);
}
if (scoreChange) {
score += scoreChange;
changes.push([scoreChange, tag]); // 记录评分变化之后好出pros & cons
}
});
return { score, changes };
}

@ -1,43 +0,0 @@
export default function translateTag(tag, inCons = false) {
let prosDict = {
'!lowPrice': '专业级别一步到位',
'lowPrice': '价格较低',
'!lightBuild': '手感扎实',
'lightBuild': '相机轻',
'scenery': '拍摄风景效果好',
'travel': '合适旅行时使用',
'event': '合适记录活动',
'sports': '拍摄体育比赛效果好',
'portrait': '拍摄人像效果好',
'astronomy': '天文摄影效果好',
'flash': '有闪光灯',
'video': '可摄像',
'bluetooth': '可蓝牙传输照片',
'touchScreen': '有触摸屏',
'gps': '内置GPS',
'waterproof': '防水',
'newModel': '型号新',
'durableBuild': '机身质量好',
};
let consDict = {
'!lowPrice': '总体价格低于预算',
'lowPrice': '价格较高',
'!lightBuild': '相机重量偏轻',
'lightBuild': '相机较重',
'scenery': '不适合拍摄风景',
'travel': '不适合旅行时使用',
'event': '不适合记录活动',
'sports': '不适合拍摄体育比赛',
'portrait': '不适合拍摄人像',
'astronomy': '不适合天文摄影',
'flash': '没闪光灯',
'video': '不能摄像',
'bluetooth': '没有蓝牙',
'touchScreen': '没有触摸屏',
'gps': '没有GPS',
'waterproof': '不防水',
'newModel': '型号比较旧',
'durableBuild': '机身质量一般',
};
return inCons ? consDict[tag] : prosDict[tag];
}

@ -1,50 +0,0 @@
let HtmlWebpackPlugin = require('html-webpack-plugin');
let path = require('path');
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}],
},
{
test: /\.jsx?/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ["es2015", "react"],
plugins: ["transform-object-rest-spread", ["import", {
"libraryName": "antd",
"style": true, // or 'css'
}]],
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
entry: './src/index.js',
resolve: {
extensions: ['.js', '.jsx'],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
devServer: {
contentBase: path.join(__dirname, "dist"),
port: 9000,
},
plugins: [new HtmlWebpackPlugin()],
};

@ -1,3 +0,0 @@
.idea
/out
*.class

@ -1,304 +0,0 @@
FUNCTION_BLOCK camera // Block definition (there may be more than one block per file)
VAR_INPUT // Define input variables
price : REAL;
pixelDepth : REAL;
pixels : REAL;
maxISO : REAL;
weight : REAL;
autoFocus : REAL;
launchDate : REAL;
frameRate : REAL;
touchScreen : REAL;
video : REAL;
flash : REAL;
waterproof : REAL;
bluetooth : REAL;
gps : REAL;
isMetal : REAL;
END_VAR
VAR_OUTPUT // Define output variable
travel : REAL;
event : REAL;
sports : REAL;
scenery : REAL;
portrait : REAL;
astronomy : REAL;
newModel : REAL;
durableBuild : REAL;
lightBuild : REAL;
lowPrice : REAL;
END_VAR
FUZZIFY price
TERM low := (0, 1) (1000, 0) ;
TERM medium := (600, 0) (800,1) (1500,1) (1700,0);
TERM high := (1500, 0) (1700, 1);
END_FUZZIFY
FUZZIFY pixelDepth
TERM low := (0, 1) (16, 0) ;
TERM medium := (10, 0) (12,1) (20,1) (26,0);
TERM high := (18, 0) (36, 1);
END_FUZZIFY
FUZZIFY pixels
TERM low := (0, 1) (12000000, 0) ;
TERM medium := (8000000, 0) (15000000,1) (20000000,1) (26000000,0);
TERM high := (20000000, 0) (40000000, 1);
END_FUZZIFY
FUZZIFY maxISO
TERM low := (0, 1) (6400, 0) ;
TERM medium := (3200, 0) (6400,1) (12800,1) (25600,0);
TERM high := (12800, 0) (51200, 1);
END_FUZZIFY
FUZZIFY weight
TERM light := (0, 1) (500, 0) ;
TERM medium := (300, 0) (500,1) (700,1) (1000,0);
TERM heavy := (800, 0) (1000, 1);
END_FUZZIFY
FUZZIFY autoFocus
TERM low := (0, 1) (15, 0) ;
TERM medium := (10, 0) (15,1) (25,1) (30,0);
TERM high := (25, 0) (50, 1);
END_FUZZIFY
FUZZIFY launchDate
TERM antique := (1000000000000, 1) (1300000000000, 0) ;
TERM earlier := (1200000000000, 0) (1300000000000,1) (1350000000000,1) (1450000000000,0);
TERM recent := (1400000000000, 0) (1500000000000, 1);
END_FUZZIFY
FUZZIFY frameRate
TERM low := (0, 1) (4, 0) ;
TERM medium := (3, 0) (8,1) (10,1) (12,0);
TERM high := (10, 0) (12, 1);
END_FUZZIFY
FUZZIFY touchScreen
TERM no := (0, 1) (1, 0) ;
TERM yes := (0, 0) (1, 1) ;
END_FUZZIFY
FUZZIFY video
TERM no := (0, 1) (1, 0) ;
TERM yes := (0, 0) (1, 1) ;
END_FUZZIFY
FUZZIFY flash
TERM no := (0, 1) (1, 0) ;
TERM yes := (0, 0) (1, 1) ;
END_FUZZIFY
FUZZIFY waterproof
TERM no := (0, 1) (1, 0) ;
TERM yes := (0, 0) (1, 1) ;
END_FUZZIFY
FUZZIFY bluetooth
TERM no := (0, 1) (1, 0) ;
TERM yes := (0, 0) (1, 1) ;
END_FUZZIFY
FUZZIFY gps
TERM no := (0, 1) (1, 0) ;
TERM yes := (0, 0) (1, 1) ;
END_FUZZIFY
FUZZIFY isMetal
TERM no := (0, 1) (1, 0) ;
TERM yes := (0, 0) (1, 1) ;
END_FUZZIFY
DEFUZZIFY travel
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY event
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY sports
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY scenery
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY portrait
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY astronomy
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY newModel
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY durableBuild
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY lightBuild
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
DEFUZZIFY lowPrice
TERM veryBad := (0,1) (0.2,0);
TERM bad := (0,0) (0.1,1) (0.5,0);
TERM average:= (0,0) (0.5,1) (1,0);
TERM good:= (0.5,0) (0.9,1) (1,0);
TERM veryGood:= (0.8,0) (1,1);
METHOD : COG;
DEFAULT := 0.5;
END_DEFUZZIFY
RULEBLOCK travel
AND : MIN;
RULE 1 : IF weight IS light THEN travel IS good;
RULE 2 : IF video IS yes THEN travel IS good;
RULE 3 : IF gps IS yes THEN travel IS good;
RULE 4 : IF flash IS no THEN travel IS bad;
RULE 5 : IF weight IS heavy THEN travel IS veryBad;
END_RULEBLOCK
RULEBLOCK event
AND : MIN;
RULE 1 : IF frameRate IS high THEN event IS good;
RULE 2 : IF gps IS yes THEN event IS good;
RULE 3 : IF bluetooth IS yes THEN event IS good;
RULE 4 : IF flash IS no THEN event IS bad;
END_RULEBLOCK
RULEBLOCK sports
AND : MIN;
RULE 1 : IF frameRate IS high THEN sports IS veryGood;
RULE 2 : IF autoFocus IS high THEN sports IS veryGood;
RULE 3 : IF pixels IS high THEN sports IS good;
RULE 4 : IF frameRate IS low THEN sports IS veryBad;
RULE 5 : IF autoFocus IS low THEN sports IS veryBad;
END_RULEBLOCK
RULEBLOCK scenery
AND : MIN;
RULE 1 : IF pixels IS high THEN scenery IS veryGood;
RULE 2 : IF pixelDepth IS high THEN scenery IS veryGood;
RULE 3 : IF maxISO IS high THEN scenery IS good;
RULE 4 : IF gps IS yes THEN scenery IS good;
RULE 5 : IF pixelDepth IS low THEN scenery IS bad;
END_RULEBLOCK
RULEBLOCK portrait
AND : MIN;
RULE 1 : IF pixels IS high THEN portrait IS good;
RULE 2 : IF pixelDepth IS high THEN portrait IS good;
RULE 3 : IF pixelDepth IS low THEN portrait IS bad;
END_RULEBLOCK
RULEBLOCK astronomy
AND : MIN;
RULE 1 : IF pixels IS high THEN astronomy IS good;
RULE 2 : IF pixelDepth IS high THEN astronomy IS good;
RULE 3 : IF maxISO IS high THEN astronomy IS good;
RULE 4 : IF maxISO IS low THEN astronomy IS veryBad;
RULE 5 : IF pixels IS low THEN astronomy IS veryBad;
END_RULEBLOCK
RULEBLOCK newModel
AND : MIN;
RULE 1 : IF launchDate IS antique THEN newModel IS bad;
RULE 2 : IF launchDate IS earlier THEN newModel IS average;
RULE 3 : IF launchDate IS recent THEN newModel IS good;
END_RULEBLOCK
RULEBLOCK durableBuild
AND : MIN;
RULE 1 : IF isMetal IS no THEN durableBuild IS bad;
RULE 2 : IF isMetal IS yes THEN durableBuild IS good;
END_RULEBLOCK
RULEBLOCK lightBuild
AND : MIN;
RULE 1 : IF weight IS heavy THEN lightBuild IS bad;
RULE 1 : IF weight IS medium THEN lightBuild IS average;
RULE 2 : IF weight IS light THEN lightBuild IS good;
END_RULEBLOCK
RULEBLOCK lowPrice
AND : MIN;
RULE 1 : IF price IS high THEN lowPrice IS bad;
RULE 1 : IF price IS medium THEN lowPrice IS average;
RULE 2 : IF price IS low THEN lowPrice IS good;
END_RULEBLOCK
END_FUNCTION_BLOCK

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save