前端
html
<view class="container">
<view class="chose-image" bindtap="uploadImage">
<image src="/static/images/icon/picupload_icon_show.png"></image>
<text>+图片</text>
</view>
<view class="image-list" wx:if="{{imageList.length > 0}}">
<view class="item" wx:for="{{imageList}}" wx:key="key">
<image src="{{item.path}}"></image>
<icon wx:if="{{item.percent==100}}" class="rm" type="clear" color="red" data-index="{{index}}" data-item="{{item}}" bindtap="removeImage"></icon>
<progress percent="{{item.percent}}" wx:if="{{item.error}}" color="#FF0000" />
<progress percent="{{item.percent}}" wx:else />
</view>
</view>
<view class="text">
<textarea placeholder=" 来呀,写下你的心情" value="{{content}}" bindinput="bindContentInput" />
</view>
<view class="function-view">
<view class="row" bindtap="getLocation">
<view class="left" wx:if="{{address}}">{{address}}</view>
<view class="left" wx:else>请选择位置</view>
<view class="right">
<image class="go-icon" src='/static/images/icon/to_icon_show_small.png'></image>
</view>
</view>
<navigator url="/pages/topic/topic" class="row" >
<view class="left">{{topicTitle}}</view>
<view class="right">
<image class="go-icon" src='/static/images/icon/to_icon_show_small.png'></image>
</view>
</navigator>
</view>
</view>
<view class="publish-btn" bindtap="publishNews">发 布</view>
css
/* pages/publish/publish.wxss */
.container{
padding: 40rpx;
padding-bottom: 140rpx;
}
.chose-image{
width: 100rpx;
padding: 10rpx 0;
border: 1rpx solid #ddd;
display: flex;
flex-direction: column;
align-items: center;
color: #8c8c8c;
font-size: 28rpx;
}
.chose-image image{
width: 60rpx;
height: 60rpx;
}
.image-list{
margin-top: 20rpx;
display: flex;
flex-direction: row;
justify-content: start;
flex-wrap: wrap;
}
.image-list .item{
position:relative;
padding: 15rpx;
}
.image-list .item image{
width: 150rpx;
height: 150rpx;
}
.image-list .item .rm{
position: absolute;
right: -10rpx;
top: -10rpx;
}
.text{
margin: 30rpx 0;
}
.function-view{
font-size: 28rpx;
}
.function-view .row{
padding: 30rpx 0;
border-bottom: 1px solid #efefef;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.function-view .row .right{
color: #8c8c8c;
}
.function-view .row .go-icon{
margin: 0 20rpx;
width: 16rpx;
height: 16rpx;
}
.publish-btn{
position: fixed;
background-color: #4cc5b6;
color:#fff;
width: 100%;
bottom: 0;
height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
}
js
// pages/release/release.js
var popup = require('../../config/Popup')
var api = require('../../config/api')
var app = getApp();
var COS = require('../../utils/cos-wx-sdk-v5.js')
var cos;
Page({
/**
* 页面的初始数据
*/
data: {
imageList: [],
content: "",
address: "",
topicId: null,
topicTitle: "选择合适的话题",
},
// 清空数据
resetData: function () {
this.setData({
imageList: [],
content: "",
address: "",
topicId: null,
topicTitle: "选择合适的话题",
});
},
// 获取位置
getLocation: function () {
console.log('123')
wx.chooseLocation({
success: (res) => {
this.setData({
address: res.address
})
}
});
},
// 获取话题
updateTopic: function (item) {
this.setData({
topicId: item.id,
topicTitle: item.title
})
},
// 获取内容
bindContentInput: function (e) {
this.setData({
content: e.detail.value
});
},
// 预览并上传图片
uploadImage: function () {
// 选择图片并上传
wx.chooseImage({
count: 9,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: res => {
var oldLength = parseInt(this.data.imageList.length);
// 最多上传9张
let totalCount = res.tempFiles.length + this.data.imageList.length;
if (totalCount > 9) {
popup.popup('图片最多选择9张', 'none')
return false
};
// 这个数据是内存的图片地址
// console.log(this.data.imageList.concat(res.tempFiles));
// 本地图片在页面预览
this.setData({
imageList: this.data.imageList.concat(res.tempFiles)
});
// 获取腾讯对象存储上传文件临时秘钥,并设置到全局变量中。
cos = new COS({
getAuthorization: function (options, callback) {
wx.request({
url: api.TemporaryKeyAPI,
header: {
'authorization': app.globalData.userinfo.token
},
success: function (result) {
var data = result.data;
var credentials = data.credentials;
callback({
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
XCosSecurityToken: credentials.sessionToken,
ExpiredTime: data.expiredTime,
});
}
});
}
});
// 上传新挑选的图片(原图片无需再上传)
for (var index in res.tempFiles) {
let imageFilePath = res.tempFiles[index].path;
var filePathSplit = imageFilePath.split('.');
var ext = filePathSplit[filePathSplit.length - 1];
// 创建随机字符串
let randowString = Math.random().toString(36).slice(-8) + String(new Date().getTime());
var fileKey = randowString + "." + ext;
var targetIndex = parseInt(oldLength) + parseInt(index);
this.setData({
["imageList[" + targetIndex + "].key"]: fileKey
});
var that = this;
// 上传文件(通过闭包做一个上传文件的操作)
(function (idx) {
cos.postObject({
Bucket: app.globalData.userinfo.bucket,
Region: app.globalData.userinfo.region,
Key: fileKey,
FilePath: imageFilePath,
onProgress: (info) => {
// console.log('info', info)
that.setData({
["imageList[" + idx + "].percent"]: info.percent * 100
})
}
}, (err, data) => {
// 上传成功或失败
if (err) {
popup.popup('上传失败', 'none')
// wx.showToast({
// title: '上传失败',
// icon: 'none'
// });
that.setData({
["imageList[" + idx + "].error"]: true,
["imageList[" + idx + "].percent"]: 100
})
return false
} else {
// console.log('data', data)
// console.log(idx, 'idx')
// data是上传成功时候的数据,这里没有赋值成功,导致后面娶不到数据
that.setData({
// 问题出在这
// ["imageList[" + idx + "].cos_path"]: data.headers.Location,
["imageList[" + idx + "].path"]: data.headers.location
});
// console.log(that.data.imageList.imageList[1])
}
// console.log("imageList", that.data.imageList)
});
})(targetIndex)
}
}
})
},
// 点击删除图片
removeImage: function (event) {
// 判断是否正在上传,如果正在上传就终止,否则就删除;
// 删除图片,终止 & 删除
var index = event.currentTarget.dataset['index'];
var item = event.currentTarget.dataset['item'];
if (item.percent == 100) {
cos.deleteObject({
Bucket: app.globalData.userinfo.bucket,
Region: app.globalData.userinfo.region,
Key: item.key
}, (err, data) => {
if (err) {
popup.popup('删除失败', 'none')
// wx.showToast({
// title: '删除失败',
// icon: 'none'
// });
} else {
var imageList = this.data.imageList;
imageList.splice(index, 1);
this.setData({
imageList: imageList
});
}
});
}
},
// 提交到后台
publishNews: function () {
//发布至少需要一张图片
if (this.data.imageList.length < 1) {
popup.popup('至少选择一张图片', 'none')
// wx.showToast({
// title: '至少选择一张图片',
// icon: 'none'
// });
return
}
// 发布内容不能为空
if (this.data.content.length < 1) {
popup.popup('内容不能为空', 'none')
// wx.showToast({
// title: '内容不能为空',
// icon: 'none'
// });
return
}
wx.showLoading({
title: '发布中...',
})
// 提交数据
wx.request({
url: api.ReleaseAPI,
header: {
'authorization': app.globalData.userinfo.token
},
data: {
position: this.data.address,
content: this.data.content,
cover: this.data.imageList[0].path,
topic: this.data.topicId,
imageList: this.data.imageList
},
method: 'POST',
dataType: 'json',
responseType: 'text',
success: (res) => {
if (res.data.status === 1000) {
// 发布成功,跳转到一个页面进行提示
// console.log('cover', this.data.imageList[0].path)
// console.log('imageList', this.data.imageList)
wx.navigateTo({
url: '/pages/publishSuccess/publishSuccess',
})
} else {
wx.showToast({
title: '发布失败,服务器错误',
icon: 'none'
});
}
},
fail: (res) => {
wx.showToast({
title: '发布失败,客户端错误',
icon: 'none'
});
},
complete: (res) => {
wx.hideLoading();
},
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})
app.json配置
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
},
cos上传js
cos-jshttps://download.csdn.net/download/qq_52385631/81776135
后端
临时密钥
url
# cos临时密钥
path('temporary_key/', TemporaryKeyAPIView.as_view()),
view
from rest_framework.views import APIView
from utils.centent.cos import credential
from rest_framework.response import Response
class TemporaryKeyAPIView(APIView):
"""cos临时密钥生成"""
def get(self, request, *args, **kwargs):
bucket = ''
ret = credential(bucket=bucket)
return Response(data=ret)
credential
def credential(bucket, region='ap-chengdu'):
'''
获取cos上传临时凭证
pip install -U qcloud-python-sts
# 下面的可以
pip3 install qcloud-python-sts
'''
from sts.sts import Sts
config = {
# 临时密钥有效时长,单位是秒(30分钟=1800秒)
'duration_seconds': 1800,
# 固定密钥 id
'secret_id': settings.TENCENT_COS_ID,
# 固定密钥 key
'secret_key': settings.TENCENT_COS_KEY,
# 换成你的 bucket
'bucket': bucket,
# 换成 bucket 所在地区
'region': region,
# 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径
# 例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
'allow_prefix': '*',
# 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
'allow_actions': [
# "name/cos:PutObject",
# 'name/cos:PostObject',
# 'name/cos:DeleteObject',
# "name/cos:UploadPart",
# "name/cos:UploadPartCopy",
# "name/cos:CompleteMultipartUpload",
# "name/cos:AbortMultipartUpload",
"*",
],
}
sts = Sts(config)
result_dict = sts.get_credential()
return result_dict
发布界面
url
# 发布保存视图
path('release/', ReleaseAPIView.as_view()),
view
from utils.centent.cos import credential
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView, ListAPIView, ListCreateAPIView
from api import models
from api.serializer import business
from utils.common.auth import JWTAuthentication
class ReleaseAPIView(CreateAPIView):
"""发布保存视图"""
authentication_classes = [JWTAuthentication, ]
serializer_class = business.ReleaseModelSerializer
def post(self, request, *args, **kwargs):
response = super(ReleaseAPIView, self).post(request, *args, **kwargs)
# 进行自定义返回数
data = {
"status": 1000,
'msg': '成功'
}
response.data = data
return response
serializer
from rest_framework import serializers
from api import models
class MediaModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Media
fields = ['path', 'key']
class ReleaseModelSerializer(serializers.ModelSerializer):
"""发布保存序列化器"""
"""
{
"position": "xx",
"content": "xx",
"topic": 1,
"cover": "xx",
"imageList": [
{
"path": "xx",
"key": "xx"
},
{
"path": "xx",
"key": "xx"
}
]
}
"""
imageList = MediaModelSerializer(many=True, write_only=True)
class Meta:
model = models.Release
fields = ['position', 'content', 'topic', 'imageList', 'cover']
def validate(self, attrs):
"""校验数据"""
# 校验数据
# print(attrs.get('imageList'))
# print(self.context['request'].data)
self._is_legitimate(attrs)
return attrs
def _is_legitimate(self, attrs):
"""进行校验"""
position = attrs.get('position')
user_obj = self.context['request'].user
content = attrs.get('content')
topic_obj = attrs.get('topic')
if not position:
raise serializers.ValidationError('必须选择一个区域哦')
if not user_obj:
raise serializers.ValidationError('请先登录哦')
if not content:
raise serializers.ValidationError('内容不能为空哦')
if not topic_obj:
raise serializers.ValidationError('必须选一个话题哦')
return position, user_obj, content, topic_obj
def create(self, validated_data):
"""创建"""
# pop数据库没有字段
# print("validated_data", validated_data)
imageList = validated_data.pop('imageList')
# print('imageList', imageList)
# 保存release
release_obj = self._save_release(validated_data)
# 保存Media
self._save_media(release_obj, imageList)
# 添加话题人数
self._add_topic_num(release_obj)
return release_obj
def _save_release(self, validated_data):
"""保存release"""
release_obj = models.Release.objects.create(
**validated_data,
userinfo=self.context['request'].user
)
return release_obj
def _save_media(self, release_obj, imageList):
"""保存图片"""
media_obj = [models.Media(release=release_obj, **media_item) for media_item in imageList]
models.Media.objects.bulk_create(media_obj)
def _add_topic_num(self, release_obj):
"""话题人数加一"""
topic = models.Topic.objects.filter(id=release_obj.topic.id).first()
if not topic:
raise serializers.ValidationError('无当前话题')
topic.follow_num += 1
topic.save()