Browse Source

纸商城界面接口对接

devlop
杨阁辉 4 years ago
parent
commit
0bce137dbd
13 changed files with 2633 additions and 88 deletions
  1. 25
      apis/add-paper.js
  2. 12
      pages.json
  3. 643
      pages/add-paper/index.vue
  4. 111
      pages/mall/index.vue
  5. 172
      pages/store-settings/index.vue
  6. 61
      uni_modules/uni-file-picker/changelog.md
  7. 224
      uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js
  8. 650
      uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue
  9. 325
      uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue
  10. 292
      uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue
  11. 109
      uni_modules/uni-file-picker/components/uni-file-picker/utils.js
  12. 86
      uni_modules/uni-file-picker/package.json
  13. 11
      uni_modules/uni-file-picker/readme.md

25
apis/add-paper.js

@ -6,4 +6,27 @@ export function createProduct(data) {
url: '/base-paper-trading/create/product',
data
})
}
}
// 纸品种类列表
export function getCategoryList(data) {
return http.get({
url: '/base-paper-trading/get/product/category-list',
data
})
}
// 纸品详情
export function getSupplierDteail(data) {
return http.get({
url: '/base-paper-trading/get/product-detail/for/supplier',
data
})
}
// 店铺纸品列表(供应商端)
export function SupplierList(data) {
return http.get({
url: '/base-paper-trading/get/store/product-list/for/supplier',
data
})
}

12
pages.json

@ -206,7 +206,17 @@
"enablePullDownRefresh": false
}
}
],
,{
"path" : "pages/store-settings/index",
"style" :
{
"navigationBarTitleText": "店铺设置",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",

643
pages/add-paper/index.vue

@ -1,100 +1,474 @@
<template>
<view class="add-paper">
<template>
<view class="add-paper">
<view>
<uni-nav-bar left-icon="back" @clickLeft="back" statusBar fixed title="">
<view class="add-paper-title">{{title}}</view>
<view class="add-paper-title">{{ title }}</view>
<view slot="left"></view>
<view slot="right"></view>
</uni-nav-bar>
</view>
<view class="">
<view class="add-paper-list">
<view >
<text class="add-paper-start">*</text>
<view>
<text class="add-paper-start"><uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons></text>
<text class="add-paper-text">纸品名称</text>
</view>
<view class="add-paper-input">
<uni-easyinput :placeholderStyle='placeholderStyle' :inputBorder="false" v-model="form.name" placeholder="请输入纸品名称"></uni-easyinput>
<qn-easyinput type="digit" :styles="{ disableColor: '#fff' }" v-model="form.name" :inputBorder="false" text="right" placeholder="请输入纸品名称"></qn-easyinput>
</view>
</view>
<view class="add-paper-border"></view>
<view class="add-paper-list">
<view >
<text class="add-paper-start">*</text>
<view>
<text class="add-paper-start"><uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons></text>
<text class="add-paper-text">纸厂信息</text>
</view>
<view class="add-paper-input">
<uni-easyinput :placeholderStyle='placeholderStyle' :inputBorder="false" v-model="form.manufacturerName" placeholder="请输入纸厂信息"></uni-easyinput>
<qn-easyinput
type="digit"
:styles="{ disableColor: '#fff' }"
v-model="form.manufacturerName"
:inputBorder="false"
text="right"
placeholder="请输入纸厂信息"
></qn-easyinput>
</view>
</view>
<view class="add-paper-border"></view>
<view class="add-paper-list">
<view >
<text class="add-paper-start">*</text>
<view>
<text class="add-paper-start"><uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons></text>
<text class="add-paper-text">品牌信息</text>
</view>
<view class="add-paper-input">
<uni-easyinput :placeholderStyle='placeholderStyle' :inputBorder="false" v-model="form.brandName" placeholder="请输入纸品品牌名称"></uni-easyinput>
<qn-easyinput
type="digit"
:styles="{ disableColor: '#fff' }"
v-model="form.brandName"
:inputBorder="false"
text="right"
placeholder="请输入纸品品牌名称"
></qn-easyinput>
</view>
</view>
<view class="add-paper-border"></view>
<view class="add-paper-list">
<view >
<text class="add-paper-start">*</text>
<view>
<text class="add-paper-start"><uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons></text>
<text class="add-paper-text">纸种信息</text>
</view>
<view class="add-paper-input">
<uni-easyinput :placeholderStyle='placeholderStyle' :inputBorder="false" v-model="form.manufacturerName" placeholder="请输入纸品名称"></uni-easyinput>
<!-- categoryId -->
<view class="" @click="paperpopupShow()">
<text v-if="form.categoryId === null" class="add-paph-text">请选择纸种信息</text>
<text v-else>{{ form.categoryName }}</text>
<text><uni-icons type="right" color="#999" size="18"></uni-icons></text>
</view>
</view>
<view class="add-paper-border"></view>
<view class="add-paper-list">
<view >
<text class="add-paper-start">*</text>
<view>
<text class="add-paper-start"><uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons></text>
<text class="add-paper-text">是否主营</text>
</view>
<view>
<uni-data-checkbox v-model="form.isMainProduct" :localdata="range" @change="change"></uni-data-checkbox>
<view><uni-data-checkbox v-model="form.isMainProduct" :localdata="range" @change="change"></uni-data-checkbox></view>
</view>
</view>
<uGap></uGap>
<view class="paper-price ">
<view class="paper-price-main">
<text class="paper-price-title">纸品售价</text>
<text class="paper-price-subtitle">(不设置最低起送量则不限制其送量要求)</text>
</view>
<view class="paper-price-form" v-for="(item, index) in skuList" :key="index">
<view class="paper-price-item">
<view class="label flex">
<view class="dian"></view>
<view class="paper-price-title">纸品售价{{ index + 1 }}</view>
</view>
<view class="delete-text" @click="deletePrice()">删除</view>
</view>
<view class="paper-price-item">
<view class="label">
<uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons>
<text class="label_text">纸品克重</text>
</view>
<view class="label_value">
<qn-easyinput
type="digit"
:styles="{ disableColor: '#F7F8FA', borderBottom: '1px solid red' }"
v-model="item.weight"
:inputBorder="false"
text="right"
placeholder="请输入纸品克重"
></qn-easyinput>
</view>
</view>
<view class="paper-price-item">
<view class="label">
<uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons>
<text class="label_text">是否特价</text>
</view>
<view class="label_value"><uni-data-checkbox style="float: right;padding-top: 24rpx" v-model="item.isPromoting" :localdata="range"></uni-data-checkbox></view>
</view>
<view class="paper-price-item">
<view class="label">
<uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons>
<text class="label_text">库存单位</text>
</view>
<view class="label_value label-select" @click="popueShow(index)">
<text class="paper-price-title text-right">{{ item.stockUnit === '1' ? '张' : '吨' }}</text>
<image class="image" src="../../static/imgs/client/down-arrow-icon.png" mode=""></image>
</view>
</view>
<view class="paper-price-item">
<view class="label">
<uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons>
<text class="label_text">账期售价</text>
</view>
<view class="label_value flex">
<qn-easyinput
type="digit"
:styles="{ disableColor: '#F7F8FA', borderBottom: '1px solid red' }"
v-model="item.listPrice"
:inputBorder="false"
text="right"
placeholder="请输入账期售价"
></qn-easyinput>
<view class="paper-price-title text-left">/</view>
</view>
</view>
<view class="paper-price-item">
<view class="label">
<uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons>
<text class="label_text">纸品库存</text>
</view>
<view class="label_value flex">
<qn-easyinput
type="digit"
:styles="{ disableColor: '#F7F8FA', borderBottom: '1px solid red' }"
v-model="item.stock"
:inputBorder="false"
text="right"
placeholder="请输入纸品库存"
></qn-easyinput>
<view class="paper-price-title text-left"></view>
</view>
</view>
<view class="paper-price-item">
<view class="label">
<uni-icons custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons>
<text class="label_text">最低起送量</text>
</view>
<view class="label_value flex">
<qn-easyinput
type="digit"
:styles="{ disableColor: '#F7F8FA', borderBottom: '1px solid red' }"
v-model="item.minimum"
:inputBorder="false"
text="right"
placeholder="请输入最低起送量"
></qn-easyinput>
<view class="paper-price-title text-left"></view>
</view>
</view>
</view>
<button type="primary" class="add-Btn" plain="true" @click="addPriceClick()"> 新增售价</button>
</view>
<uGap></uGap>
<view class="">
<view class="">
<text>纸品售价</text>
<text>(不设置最低起送量则不限制其送量要求)</text>
<view class="paper-price">
<view class="paper-price-image-title"><text class="paper-price-title">纸品图片</text></view>
<view class="paper-price-image">
<uni-file-picker
v-model="imgList"
file-mediatype="image"
mode="grid"
file-extname="png,jpg"
:limit="6"
@progress="progress"
@success="success"
@fail="fail"
@select="select"
@delete="deleteImage"
/>
</view>
</view>
<uGap></uGap>
<view class="paper-price">
<view class="paper-price-image-title"><text class="paper-price-title">商品卖点</text></view>
<view class="paper-price-image ">
<qn-easyinput
class="paper-price-textArea"
:maxlength="200"
:styles="{ disableColor: '#F7F8FA' }"
v-model="form.remark"
:inputBorder="false"
type="textarea"
placeholder="请输入商品卖点"
></qn-easyinput>
</view>
</view>
<uGap></uGap>
<view class="paper-price">
<view class="paper-price-image-title"><text class="paper-price-title">商品描述</text></view>
<view class="paper-price-image ">
<qn-easyinput
class="paper-price-textArea"
:maxlength="200"
:styles="{ disableColor: '#F7F8FA' }"
v-model="form.remark"
:inputBorder="false"
type="textarea"
placeholder="请输入商品描述"
></qn-easyinput>
</view>
</view>
<uGap></uGap>
<view class="paper-price">
<view class="paper-price-image-title"><text class="paper-price-title">送货说明</text></view>
<view class="paper-price-image ">
<qn-easyinput
class="paper-price-textArea"
:maxlength="200"
:styles="{ disableColor: '#F7F8FA' }"
v-model="form.remark"
:inputBorder="false"
type="textarea"
placeholder="请输入送货说明"
></qn-easyinput>
</view>
</view>
</view>
</template>s
<uGap></uGap>
<view class="paper-price">
<view class="paper-price-image-title"><text class="paper-price-title">其他说明</text></view>
<view class="paper-price-image ">
<qn-easyinput
class="paper-price-textArea"
:maxlength="200"
:styles="{ disableColor: '#F7F8FA' }"
v-model="form.remark"
:inputBorder="false"
type="textarea"
placeholder="请输入其他说明"
></qn-easyinput>
</view>
</view>
<uGap></uGap>
<view class="uploud-btn">
<view class=""><button type="primary" class="btn-class" plain="true" @click="addPriceClick()">取消</button></view>
<view class=""><button type="primary" class="btn-class-upload" @click="uploadPrice()">上架出售</button></view>
</view>
<uni-popup ref="popup" type="bottom">
<view class="popup_modal">
<slot name="title"><view class="popup_modal-title">可选择已录入公司</view></slot>
<scroll-view scroll-y="true" class="popup_modal-scroll">
<view @click="selectValeu(item)" class="popup_modal-scroll-item" v-for="item in searchList" :key="item.id">{{ item.name }}</view>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref="paperpopup" type="bottom">
<view class="popup_modal">
<slot name="title"><view class="popup_modal-title">选择纸种</view></slot>
<scroll-view scroll-y="true" class="popup_modal-scroll">
<view @click="paperSelectChange(item)" class="popup_modal-scroll-item" v-for="item in paperList" :key="item.id">{{ item.name }}</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script>
import { back, go2 } from '@/utils/hook.js'
import uGap from '@/components/u-gap/u-gap.vue'
export default {
components:{
uGap
},
data() {
return {
title:'添加纸品',
value:'',
radio:0,
form:{
isMainProduct:true
import { back, go2 } from '@/utils/hook.js'
import qnEasyinput from '@/components/qn-easyinput/qn-easyinput.vue'
import uGap from '@/components/u-gap/u-gap.vue'
import { getCategoryList, createProduct,getSupplierDteail } from '@/apis/add-paper.js'
const validateFields = ['name', 'isMainProduct', 'manufacturerName', 'brandName', 'categoryId']
const validatePriceFields = ['isPromoting', 'listPrice', 'minimum', 'stock', 'stockUnit', 'weight']
export default {
components: {
uGap,
qnEasyinput
},
data() {
return {
title: '添加纸品',
form: {
name: null,
isMainProduct: 0,
manufacturerName: null,
brandName: null,
categoryId: null
},
range: [{ value: 0, text: '是' }, { value: 1, text: '否' }],
placeholderStyle: 'text-align: right;',
searchList: [
{
name: '张',
id: '1'
},
range: [{"value": true,"text": "是" },{"value": false,"text": "否"}],
placeholderStyle:'text-align: right;'
}
},
methods: {
back,
}
}
</script>
<style lang="scss">
.add-paper{
{
name: '吨',
id: '2'
}
],
paperList: [],
skuList: [
{
isPromoting: 0,
listPrice: null,
minimum: null,
stock: null,
stockUnit: '1',
weight: null
}
],
imgList: [],
priceIndex: 0
}
},
onLoad(option) {
if (option) {
this.title = option.title
}
if(this.title === '编辑纸品'){
this.getDteailList()
}
this.getCategoryListQuery()
},
watch: {
['form.name'](val) {
this.form.id = null
console.log(111)
}
},
methods: {
back,
popueShow(item) {
this.priceIndex = item
this.$refs.popup.open('bottom')
},
selectValeu(item) {
this.$set(this.skuList[this.priceIndex], 'stockUnit', item.id)
this.$refs.popup.close()
},
paperSelectChange(item) {
this.$set(this.form, 'categoryName', item.name)
this.$set(this.form, 'categoryId', item.id)
this.$refs.paperpopup.close()
},
paperpopupShow() {
this.$refs.paperpopup.open('bottom')
},
//
getDteailList(res){
this.getSupplierDteail(this.form).then(res => {
if (res) {
this.form = res
this.skuList = res.skuList
this.imgList =res.imgList
}
})
},
//
addPriceClick() {
this.skuList.push({
isPromoting: 0,
listPrice: null,
minimum: null,
stock: null,
stockUnit: '1',
weight: null,
categoryId: null
})
},
//
deletePrice(value) {
this.skuList.splice(value, 1)
},
//
progress() {},
//
success(item) {
console.log(item)
},
//
fail(item) {
console.log(item)
},
//
select(item) {
console.log(item)
console.log(this.imgList)
this.imgList.push(item.tempFiles[0])
},
//
deleteImage(item) {
console.log('deleteImage', item)
for (let i = 0; i < this.imgList.length; i++) {
if (this.imgList[i].uuid === item.tempFile.uuid) {
this.imgList.splice(i, 1)
}
}
console.log(this.imgList)
},
uploadPrice() {
for (let i = 0; i < validateFields.length; i++) {
if (this.form[validateFields[i]] === null || this.form[validateFields[i]] === '') {
uni.showToast({
title: '请完善信息',
icon: 'none'
})
return
}
}
for (let j = 0; j < validatePriceFields.length; j++) {
for (let z = 0; z < this.skuList.length; z++) {
if (this.skuList[z][validatePriceFields[j]] === null || this.skuList[z][validatePriceFields[j]] === '') {
uni.showToast({
title: '请完善信息',
icon: 'none'
})
return
}
}
}
var params = {
...this.form,
skuList:this.skuList,
imgList:this.imgList
}
createProduct(this.form).then(res => {
if (res) {
uni.showToast({
title: '添加成功',
icon: 'success'
})
setTimeout(() => {
back()
}, 1000)
}
})
},
//
getCategoryListQuery() {
getCategoryList().then(res => {
if (res) {
this.paperList = res
console.log(res)
}
})
}
}
}
</script>
<style lang="scss">
.add-paper {
.image {
width: 28rpx;
height: 28rpx;
}
.add-paper-title {
width: 100%;
font-size: 36rpx;
@ -103,33 +477,180 @@
text-align: center;
font-weight: 500;
}
.add-paper-list{
.add-paper-list {
height: 88rpx;
background: #FFFFFF;
background: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 32rpx;
padding-right: 32rpx;
}
.add-paper-text{
.add-paper-text {
font-size: 28rpx;
color: #000000;
letter-spacing: 0;
font-weight: 400;
}
.add-paper-start{
.add-paper-start {
font-size: 28rpx;
color: #F5222D;
color: #f5222d;
letter-spacing: 0;
font-weight: 400;
}
.add-paper-input{
.add-paper-input {
width: 40%;
}
.add-paper-border{
border-bottom: 2rpx solid #D8D8D8;
.add-paph-text {
font-size: 28rpx;
color: #888888;
letter-spacing: 0;
text-align: right;
font-weight: 400;
}
.add-paper-border {
border-bottom: 2rpx solid #d8d8d8;
margin-left: 16px;
}
}
.paper-price {
background-color: #ffffff;
padding-bottom: 20rpx;
.paper-price-form {
background: #f7f8fa;
border-radius: 8rpx;
margin: 22rpx 32rpx;
padding: 22rpx 32rpx;
line-height: 60rpx;
margin-bottom: 30rpx;
}
.paper-price-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.flex {
display: flex;
margin-left: 4px;
align-items: center;
}
}
.delete-text {
font-size: 28rpx;
color: #f5222d;
letter-spacing: 0;
font-weight: 400;
}
.paper-price-main {
padding: 22rpx 32rpx;
height: 88rpx;
align-items: center;
border-bottom: 2rpx solid #dddddd;
}
.paper-price-image-title {
padding: 22rpx 32rpx;
align-items: center;
}
.paper-price-image {
padding: 22rpx 32rpx;
}
.paper-price-textArea {
background: #f7f8fa;
border-radius: 20rpx;
padding: 10rpx;
}
.paper-price-title {
font-size: 28rpx;
color: #000000;
letter-spacing: 0;
font-weight: 600;
}
.paper-price-subtitle {
font-size: 24rpx;
color: #888888;
font-weight: 400;
}
.dian {
background: #adbac9;
width: 10rpx;
height: 10rpx;
border-radius: 50%;
// margin-top: 28rpx;
margin-right: 10rpx;
}
.label_text {
font-size: 28rpx;
color: #888888;
letter-spacing: 0;
font-weight: 400;
}
.label_value {
padding-bottom: 5rpx;
border-bottom: 2rpx solid #d8d8d8;
width: 65%;
text-align: right;
}
.label-select {
display: flex;
align-items: center;
justify-content: flex-end;
}
.text-left {
margin-left: 10rpx;
}
.text-right {
margin-right: 10rpx;
}
}
.popup_modal {
width: 750rpx;
height: 600rpx;
background-color: #fff;
border-radius: 10rpx 10rpx 0 0;
.popup_modal-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 750rpx;
height: 88rpx;
font-weight: 600;
border-bottom: 2rpx solid #d8d8d8;
}
.popup_modal-scroll {
width: 750rpx;
height: 600rpx;
.popup_modal-scroll-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 750rpx;
height: 88rpx;
padding: 0rpx 32rpx;
border-bottom: 2rpx solid #d8d8d8;
}
}
}
.add-Btn {
margin: 10rpx 32rpx;
}
.btn-class {
height: 88rpx;
border-radius: 10rpx;
line-height: 80rpx;
width: 150px;
}
.btn-class-upload {
height: 88rpx;
border-radius: 10rpx;
line-height: 80rpx;
width: 200px;
}
.uploud-btn {
display: flex;
justify-content: space-between;
padding: 10rpx 32rpx;
background-color: #ffffff;
}
}
</style>

111
pages/mall/index.vue

@ -4,47 +4,46 @@
<view slot="left" class="left-title">纸商城</view>
<view slot="right" class="right-title">分享</view>
</uni-nav-bar>
<scroll-list ref="list" :option="option" @load="upCallback" @refresh="downCallback" style="background-color: #FFFFFF;">
<view class="top-box">
<view class=""><image class="image" src="https://qncloud.oss-cn-shenzhen.aliyuncs.com/ztb_pic/testPic.jfif" mode=""></image></view>
<view class="center">
<view class="title">东莞市隆兴纸业有限公司</view>
<view class="desc">全部商品 39 &nbsp; | &nbsp;上新 9</view>
</view>
<view class="right">
<view class="cut" @click="checkMall()">
切换商城
<image class="cut-icon" src="/static/imgs/mall/cut-icon.png" mode=""></image>
</view>
<view class="set">设置</view>
<view class="top-box">
<view class=""><image class="image" src="https://qncloud.oss-cn-shenzhen.aliyuncs.com/ztb_pic/testPic.jfif" mode=""></image></view>
<view class="center">
<view class="title">东莞市隆兴纸业有限公司</view>
<view class="desc">全部商品 39 &nbsp; | &nbsp;上新 9</view>
</view>
<view class="right">
<view class="cut" @click="checkMall()">
切换商城
<image class="cut-icon" src="/static/imgs/mall/cut-icon.png" mode=""></image>
</view>
<view class="set" @click="setting()">设置</view>
</view>
<uGap></uGap>
<view v-for="(item, index) in 6" :key="index">
</view>
<uGap></uGap>
<scroll-list ref="list" :option="option" @load="upCallback" @refresh="downCallback" style="background-color: #FFFFFF;">
<view v-for="(item, index) in listData" :key="index">
<view class="content">
<image class="image" src="https://qncloud.oss-cn-shenzhen.aliyuncs.com/ztb_pic/testPic.jfif" mode=""></image>
<view class="">
<view class="title">
<view class="">金蝶蓝白卡</view>
<view class="">{{item.brandName}}</view>
<view class="">
<image class="icon" :src="1 === 1 ? '/static/imgs/mall/grounding-icon.png' : '/static/imgs/mall/sold-out-icon.png'" mode=""></image>
<image class="icon" :src="item.status === 1 ? '/static/imgs/mall/grounding-icon.png' : '/static/imgs/mall/sold-out-icon.png'" mode=""></image>
</view>
</view>
<view class="sub-title">金桂/白卡纸/金蝶蓝</view>
<view class="sub-title">{{item.categoryName}}/{{item.name}}/{{item.manufacturerName}}</view>
<view class="desc">克重(价格)200(3900) 235(3900) 255(390 0)255(390</view>
</view>
</view>
<view class="other">
<view class="time">5分钟前</view>
<view class="time">{{item.updateTime}}</view>
<view class="btn">
<view class="">编辑</view>
<view class="" @click="editPaper()">编辑</view>
<view class="divide"></view>
<view class="">下架</view>
</view>
</view>
<uGap></uGap>
</view>
</scroll-list>
<view>
<view class="offer-prices" @click="nativeTo()">
@ -57,6 +56,7 @@
<script>
import uGap from '@/components/u-gap/u-gap.vue'
import { back, go2 } from '@/utils/hook.js'
import { SupplierList } from '@/apis/add-paper.js'
export default {
components: { uGap },
data() {
@ -75,20 +75,81 @@ export default {
content: [],
horizontal: 'right',
vertical: 'bottom',
direction: 'vertical'
direction: 'vertical',
orderPagination: {
pageNum: 0, //
pageSize: 10,
storeId:null,
name:null
},
listData:[{}]
}
},
onLoad() {
},
methods: {
back,
downCallback() {},
upCallback() {},
//
quertData() {
return new Promise((resolve, reject) => {
SupplierList({ ...this.orderPagination })
.then(res => {
if (res) {
if (this.orderPagination.pageNum == 1) {
this.listData = res.records
} else {
this.listData = this.listData.concat(res.records)
}
resolve({ list: this.listData, total: res.total })
}
})
.catch(err => {
reject(err)
})
})
},
downCallback() {
this.orderPagination.pageNum = 1
this.quertData()
.then(({ list, total }) => {
this.$refs.list.refreshSuccess({ list, total })
})
.catch(() => {
this.$refs.list.refreshFail()
})
},
upCallback() {
this.orderPagination.pageNum++
this.quertData()
.then(({ list, total }) => {
this.$refs.list.loadSuccess({ list, total })
})
.catch(() => {
this.$refs.list.loadFail()
})
},
//
checkMall() {
go2('switching-mall')
},
//
nativeTo() {
go2('add-paper')
var params = {
title: '添加纸品'
}
go2('add-paper',params)
},
//
editPaper(){
var params = {
title: '编辑纸品'
}
go2('add-paper',params)
},
//
setting(){
go2('store-settings')
}
}
}

172
pages/store-settings/index.vue

@ -0,0 +1,172 @@
<template>
<view class="store-setting">
<view>
<uni-nav-bar left-icon="back" @clickLeft="back" statusBar fixed title="">
<view class="store-setting-title">店铺设置</view>
<view slot="left"></view>
<view slot="right"></view>
</uni-nav-bar>
</view>
<view class="store-setting-main">
<view class="store-setting-name"><text>店铺名称</text></view>
<view class="">
<qn-easyinput
class="store-setting-textArea"
:maxlength="100"
:styles="{ disableColor: '#F7F8FA' }"
v-model="storeName"
:inputBorder="false"
type="textarea"
placeholder="请输入店铺名称"
></qn-easyinput>
</view>
<view class="store-log">
<view class="tore-setting-name"><text>店铺logo</text></view>
<view class="store-log store-image-flex">
<uni-file-picker
class="stotr-image-width"
v-model="imageValue"
file-mediatype="image"
mode="grid"
file-extname="png,jpg"
:limit="1"
:image-styles="imageStyles"
@progress="progress"
@success="success"
@fail="fail"
@select="select"
@delete="deleteImage"
/>
<view class="store-image-text ">建议尺寸200*200像素尺寸不匹配时图片将被压缩或拉伸以铺满画面</view>
</view>
</view>
<view class="store-log">
<view class="tore-setting-name"><text>背景图片</text></view>
<view class="store-log store-image-flex">
<uni-file-picker class="stotr-image-widtht" :limit="1" fileMediatype="image" :image-styles="imageStylest" />
<view class="store-image-text">建议尺寸750*370像素尺寸不匹配时图片将被压缩或拉伸以铺满画面</view>
</view>
</view>
</view>
<view class="">
<view class="upload-btn"><button type="primary" class="btn-class">保存设置</button></view>
</view>
</view>
</template>
<script>
import { back, go2 } from '@/utils/hook.js'
export default {
data() {
return {
storeName: '',
imageValue: [],
imageStyles: {
width: 100,
height: 100,
border: {
color: '#f1f1f1',
width: 1,
style: 'dashed',
radius: '4rpx'
}
},
imageStylest: {
width: 183,
height: 94,
border: {
color: '#f1f1f1',
width: 1,
style: 'dashed',
radius: '4rpx'
}
}
}
},
methods: {
back,
//
progress() {},
//
success(item) {
console.log(item)
},
//
fail(item) {
console.log(item)
},
//
select(item) {
console.log(item)
console.log(this.imageValue)
this.imageValue.push(item.tempFiles[0])
},
//
deleteImage(item) {
console.log('deleteImage', item)
for (let i = 0; i < this.imageValue.length; i++) {
if (this.imageValue[i].uuid === item.tempFile.uuid) {
this.imageValue.splice(i, 1)
}
}
console.log(this.imageValue)
}
}
}
</script>
<style lang="scss">
.store-setting {
background-color: #FFFFFF;
.store-setting-title {
width: 100%;
font-size: 36rpx;
color: #000000;
letter-spacing: 0;
text-align: center;
font-weight: 500;
}
.store-setting-main {
padding: 20rpx 32rpx;
background-color: #ffffff;
}
.store-setting-name {
// font-size: 28rpx;
color: #000000;
letter-spacing: 0;
font-weight: 500;
}
.store-setting-textArea {
background: #f7f8fa;
border-radius: 20rpx;
padding: 10rpx;
margin-top: 20rpx;
}
.store-log {
margin-top: 32rpx;
}
.store-image-flex{
display: flex;
}
.store-image-text {
font-size: 24rpx;
color: #888888;
letter-spacing: 0;
font-weight: 400;
line-height: 40rpx;
margin-left: 10rpx;
}
.stotr-image-widtht{
width: 100%;
}
.stotr-image-width{
width: 50%;
}
.upload-btn{
position: absolute;
width: 92%;
bottom: 32rpx;
left: 32rpx;
}
}
</style>

61
uni_modules/uni-file-picker/changelog.md

@ -0,0 +1,61 @@
## 1.0.1(2021-11-23)
- 修复 参数为对象的情况下,url在某些情况显示错误的bug
## 1.0.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
## 0.2.16(2021-11-08)
- 修复 传入空对象 ,显示错误的Bug
## 0.2.15(2021-08-30)
- 修复 return-type="object" 时且存在v-model时,无法删除文件的Bug
## 0.2.14(2021-08-23)
- 新增 参数中返回 fileID 字段
## 0.2.13(2021-08-23)
- 修复 腾讯云传入fileID 不能回显的bug
- 修复 选择图片后,不能放大的问题
## 0.2.12(2021-08-17)
- 修复 由于 0.2.11 版本引起的不能回显图片的Bug
## 0.2.11(2021-08-16)
- 新增 clearFiles(index) 方法,可以手动删除指定文件
- 修复 v-model 值设为 null 报错的Bug
## 0.2.10(2021-08-13)
- 修复 return-type="object" 时,无法删除文件的Bug
## 0.2.9(2021-08-03)
- 修复 auto-upload 属性失效的Bug
## 0.2.8(2021-07-31)
- 修复 fileExtname属性不指定值报错的Bug
## 0.2.7(2021-07-31)
- 修复 在某种场景下图片不回显的Bug
## 0.2.6(2021-07-30)
- 修复 return-type为object下,返回值不正确的Bug
## 0.2.5(2021-07-30)
- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题
## 0.2.3(2021-07-28)
- 优化 调整示例代码
## 0.2.2(2021-07-27)
- 修复 vue3 下赋值错误的Bug
- 优化 h5平台下上传文件导致页面卡死的问题
## 0.2.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.1.1(2021-07-02)
- 修复 sourceType 缺少默认值导致 ios 无法选择文件
## 0.1.0(2021-06-30)
- 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改
## 0.0.11(2021-06-30)
- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题
## 0.0.10(2021-06-29)
- 优化 文件上传后进度条消失时机
## 0.0.9(2021-06-29)
- 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug
## 0.0.8(2021-06-15)
- 修复 删除文件时无法触发 v-model 的Bug
## 0.0.7(2021-05-12)
- 新增 组件示例地址
## 0.0.6(2021-04-09)
- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug
## 0.0.5(2021-04-09)
- 优化 更新组件示例
## 0.0.4(2021-04-09)
- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔
## 0.0.3(2021-02-05)
- 调整为uni_modules目录规范
- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug

224
uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js

@ -0,0 +1,224 @@
'use strict';
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
function chooseImage(opts) {
const {
count,
sizeType = ['original', 'compressed'],
sourceType = ['album', 'camera'],
extension
} = opts
return new Promise((resolve, reject) => {
uni.chooseImage({
count,
sizeType,
sourceType,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res, 'image'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseVideo(opts) {
const {
camera,
compressed,
maxDuration,
sourceType = ['album', 'camera'],
extension
} = opts;
return new Promise((resolve, reject) => {
uni.chooseVideo({
camera,
compressed,
maxDuration,
sourceType,
extension,
success(res) {
const {
tempFilePath,
duration,
size,
height,
width
} = res;
resolve(normalizeChooseAndUploadFileRes({
errMsg: 'chooseVideo:ok',
tempFilePaths: [tempFilePath],
tempFiles: [
{
name: (res.tempFile && res.tempFile.name) || '',
path: tempFilePath,
size,
type: (res.tempFile && res.tempFile.type) || '',
width,
height,
duration,
fileType: 'video',
cloudPath: '',
}, ],
}, 'video'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseAll(opts) {
const {
count,
extension
} = opts;
return new Promise((resolve, reject) => {
let chooseFile = uni.chooseFile;
if (typeof wx !== 'undefined' &&
typeof wx.chooseMessageFile === 'function') {
chooseFile = wx.chooseMessageFile;
}
if (typeof chooseFile !== 'function') {
return reject({
errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。',
});
}
chooseFile({
type: 'all',
count,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL),
});
},
});
});
}
function normalizeChooseAndUploadFileRes(res, fileType) {
res.tempFiles.forEach((item, index) => {
if (!item.name) {
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
}
if (fileType) {
item.fileType = fileType;
}
item.cloudPath =
Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
});
if (!res.tempFilePaths) {
res.tempFilePaths = res.tempFiles.map((file) => file.path);
}
return res;
}
function uploadCloudFiles(files, max = 5, onUploadProgress) {
files = JSON.parse(JSON.stringify(files))
const len = files.length
let count = 0
let self = this
return new Promise(resolve => {
while (count < max) {
next()
}
function next() {
let cur = count++
if (cur >= len) {
!files.find(item => !item.url && !item.errMsg) && resolve(files)
return
}
const fileItem = files[cur]
const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
fileItem.url = ''
delete fileItem.errMsg
uniCloud
.uploadFile({
filePath: fileItem.path,
cloudPath: fileItem.cloudPath,
fileType: fileItem.fileType,
onUploadProgress: res => {
res.index = index
onUploadProgress && onUploadProgress(res)
}
})
.then(res => {
fileItem.url = res.fileID
fileItem.index = index
if (cur < len) {
next()
}
})
.catch(res => {
fileItem.errMsg = res.errMsg || res.message
fileItem.index = index
if (cur < len) {
next()
}
})
}
})
}
function uploadFiles(choosePromise, {
onChooseFile,
onUploadProgress
}) {
return choosePromise
.then((res) => {
if (onChooseFile) {
const customChooseRes = onChooseFile(res);
if (typeof customChooseRes !== 'undefined') {
return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ?
res : chooseRes);
}
}
return res;
})
.then((res) => {
if (res === false) {
return {
errMsg: ERR_MSG_OK,
tempFilePaths: [],
tempFiles: [],
};
}
return res
})
}
function chooseAndUploadFile(opts = {
type: 'all'
}) {
if (opts.type === 'image') {
return uploadFiles(chooseImage(opts), opts);
}
else if (opts.type === 'video') {
return uploadFiles(chooseVideo(opts), opts);
}
return uploadFiles(chooseAll(opts), opts);
}
export {
chooseAndUploadFile,
uploadCloudFiles
};

650
uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue

@ -0,0 +1,650 @@
<template>
<view class="uni-file-picker">
<view v-if="title" class="uni-file-picker__header">
<text class="file-title">{{ title }}</text>
<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
</view>
<upload-image v-if="fileMediatype === 'image' && showType === 'grid'" :readonly="readonly"
:image-styles="imageStyles" :files-list="filesList" :limit="limitLength" :disablePreview="disablePreview"
:delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
<slot>
<view class="is-add">
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</view>
</slot>
</upload-image>
<upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly"
:list-styles="listStyles" :files-list="filesList" :showType="showType" :delIcon="delIcon"
@uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
<slot><button type="primary" size="mini">选择文件</button></slot>
</upload-file>
</view>
</template>
<script>
import {
chooseAndUploadFile,
uploadCloudFiles
} from './choose-and-upload-file.js'
import {
get_file_ext,
get_extname,
get_files_and_is_max,
get_file_info,
get_file_data
} from './utils.js'
import uploadImage from './upload-image.vue'
import uploadFile from './upload-file.vue'
let fileInput = null
/**
* FilePicker 文件选择上传
* @description 文件选择上传组件可以选择图片视频等任意文件并上传到当前绑定的服务空间
* @tutorial https://ext.dcloud.net.cn/plugin?id=4079
* @property {Object|Array} value 组件数据通常用来回显 ,类型由return-type属性决定
* @property {Boolean} disabled = [true|false] 组件禁用
* @value true 禁用
* @value false 取消禁用
* @property {Boolean} readonly = [true|false] 组件只读不可选择不显示进度不显示删除按钮
* @value true 只读
* @value false 取消只读
* @property {String} return-type = [array|object] 限制 value 格式当为 object 组件只能单选且会覆盖
* @value array 规定 value 属性的类型为数组
* @value object 规定 value 属性的类型为对象
* @property {Boolean} disable-preview = [true|false] 禁用图片预览 mode:grid 时生效
* @value true 禁用图片预览
* @value false 取消禁用图片预览
* @property {Boolean} del-icon = [true|false] 是否显示删除按钮
* @value true 显示删除按钮
* @value false 不显示删除按钮
* @property {Boolean} auto-upload = [true|false] 是否自动上传值为true则只触发@select,可自行上传
* @value true 自动上传
* @value false 取消自动上传
* @property {Number|String} limit 最大选择个数 h5 会自动忽略多选的部分
* @property {String} title 组件标题右侧显示上传计数
* @property {String} mode = [list|grid] 选择文件后的文件列表样式
* @value list 列表显示
* @value grid 宫格显示
* @property {String} file-mediatype = [image|video|all] 选择文件类型
* @value image 只选择图片
* @value video 只选择视频
* @value all 选择所有文件
* @property {Array} file-extname 选择文件后缀根据 file-mediatype 属性而不同
* @property {Object} list-style mode:list 时的样式
* @property {Object} image-styles 选择文件后缀根据 file-mediatype 属性而不同
* @event {Function} select 选择文件后触发
* @event {Function} progress 文件上传时触发
* @event {Function} success 上传成功触发
* @event {Function} fail 上传失败触发
* @event {Function} delete 文件从列表移除时触发
*/
export default {
name: 'uniFilePicker',
components: {
uploadImage,
uploadFile
},
emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
props: {
// #ifdef VUE3
modelValue: {
type: [Array, Object],
default () {
return []
}
},
// #endif
// #ifndef VUE3
value: {
type: [Array, Object],
default () {
return []
}
},
// #endif
disabled: {
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
delIcon: {
type: Boolean,
default: true
},
//
autoUpload: {
type: Boolean,
default: true
},
// h5
limit: {
type: [Number, String],
default: 9
},
// grid | list | list-card
mode: {
type: String,
default: 'grid'
},
// image/video/all
fileMediatype: {
type: String,
default: 'image'
},
//
fileExtname: {
type: [Array, String],
default () {
return []
}
},
title: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
//
border: true,
// 线
dividline: true,
// 线
borderStyle: {}
}
}
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto'
}
}
},
readonly: {
type: Boolean,
default: false
},
returnType: {
type: String,
default: 'array'
},
sizeType: {
type: Array,
default () {
return ['original', 'compressed']
}
}
},
data() {
return {
files: [],
localValue: []
}
},
watch: {
// #ifndef VUE3
value: {
handler(newVal, oldVal) {
this.setValue(newVal, oldVal)
},
immediate: true
},
// #endif
// #ifdef VUE3
modelValue: {
handler(newVal, oldVal) {
this.setValue(newVal, oldVal)
},
immediate: true
},
// #endif
},
computed: {
filesList() {
let files = []
this.files.forEach(v => {
files.push(v)
})
return files
},
showType() {
if (this.fileMediatype === 'image') {
return this.mode
}
return 'list'
},
limitLength() {
if (this.returnType === 'object') {
return 1
}
if (!this.limit) {
return 1
}
if (this.limit >= 9) {
return 9
}
return this.limit
}
},
created() {
// TODO
if (!(uniCloud.config && uniCloud.config.provider)) {
this.noSpace = true
uniCloud.chooseAndUploadFile = chooseAndUploadFile
}
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.form && this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
},
methods: {
/**
* 公开用户使用清空文件
* @param {Object} index
*/
clearFiles(index) {
if (index !== 0 && !index) {
this.files = []
this.$nextTick(() => {
this.setEmit()
})
} else {
this.files.splice(index, 1)
}
this.$nextTick(() => {
this.setEmit()
})
},
/**
* 公开用户使用继续上传
*/
upload() {
let files = []
this.files.forEach((v, index) => {
if (v.status === 'ready' || v.status === 'error') {
files.push(Object.assign({}, v))
}
})
this.uploadFiles(files)
},
async setValue(newVal, oldVal) {
const newData = async (v) => {
const reg = /cloud:\/\/([\w.]+\/?)\S*/
let url = ''
if(v.fileID){
url = v.fileID
}else{
url = v.url
}
if (reg.test(url)) {
v.fileID = url
v.url = await this.getTempFileURL(url)
}
if(v.url) v.path = v.url
return v
}
if (this.returnType === 'object') {
if (newVal) {
await newData(newVal)
} else {
newVal = {}
}
} else {
if (!newVal) newVal = []
for(let i =0 ;i < newVal.length ;i++){
let v = newVal[i]
await newData(v)
}
}
this.localValue = newVal
if (this.form && this.formItem &&!this.is_reset) {
this.is_reset = false
this.formItem.setValue(this.localValue)
}
let filesData = Object.keys(newVal).length > 0 ? newVal : [];
this.files = [].concat(filesData)
},
/**
* 选择文件
*/
choose() {
if (this.disabled) return
if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
'array') {
uni.showToast({
title: `您最多选择 ${this.limitLength} 个文件`,
icon: 'none'
})
return
}
this.chooseFiles()
},
/**
* 选择文件并上传
*/
chooseFiles() {
const _extname = get_extname(this.fileExtname)
//
uniCloud
.chooseAndUploadFile({
type: this.fileMediatype,
compressed: false,
sizeType: this.sizeType,
// TODO video
extension: _extname.length > 0 ? _extname : undefined,
count: this.limitLength - this.files.length, //9
onChooseFile: this.chooseFileCallback,
onUploadProgress: progressEvent => {
this.setProgress(progressEvent, progressEvent.index)
}
})
.then(result => {
this.setSuccessAndError(result.tempFiles)
})
.catch(err => {
console.log('选择失败', err)
})
},
/**
* 选择文件回调
* @param {Object} res
*/
async chooseFileCallback(res) {
const _extname = get_extname(this.fileExtname)
const is_one = (Number(this.limitLength) === 1 &&
this.disablePreview &&
!this.disabled) ||
this.returnType === 'object'
//
if (is_one) {
this.files = []
}
let {
filePaths,
files
} = get_files_and_is_max(res, _extname)
if (!(_extname && _extname.length > 0)) {
filePaths = res.tempFilePaths
files = res.tempFiles
}
let currentData = []
for (let i = 0; i < files.length; i++) {
if (this.limitLength - this.files.length <= 0) break
files[i].uuid = Date.now()
let filedata = await get_file_data(files[i], this.fileMediatype)
filedata.progress = 0
filedata.status = 'ready'
this.files.push(filedata)
currentData.push({
...filedata,
file: files[i]
})
}
this.$emit('select', {
tempFiles: currentData,
tempFilePaths: filePaths
})
res.tempFiles = files
//
if (!this.autoUpload || this.noSpace) {
res.tempFiles = []
}
},
/**
* 批传
* @param {Object} e
*/
uploadFiles(files) {
files = [].concat(files)
uploadCloudFiles.call(this, files, 5, res => {
this.setProgress(res, res.index, true)
})
.then(result => {
this.setSuccessAndError(result)
})
.catch(err => {
console.log(err)
})
},
/**
* 成功或失败
*/
async setSuccessAndError(res, fn) {
let successData = []
let errorData = []
let tempFilePath = []
let errorTempFilePath = []
for (let i = 0; i < res.length; i++) {
const item = res[i]
const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index
if (index === -1 || !this.files) break
if (item.errMsg === 'request:fail') {
this.files[index].url = item.path
this.files[index].status = 'error'
this.files[index].errMsg = item.errMsg
// this.files[index].progress = -1
errorData.push(this.files[index])
errorTempFilePath.push(this.files[index].url)
} else {
this.files[index].errMsg = ''
this.files[index].fileID = item.url
const reg = /cloud:\/\/([\w.]+\/?)\S*/
if (reg.test(item.url)) {
this.files[index].url = await this.getTempFileURL(item.url)
}else{
this.files[index].url = item.url
}
this.files[index].status = 'success'
this.files[index].progress += 1
successData.push(this.files[index])
tempFilePath.push(this.files[index].fileID)
}
}
if (successData.length > 0) {
this.setEmit()
//
this.$emit('success', {
tempFiles: this.backObject(successData),
tempFilePaths: tempFilePath
})
}
if (errorData.length > 0) {
this.$emit('fail', {
tempFiles: this.backObject(errorData),
tempFilePaths: errorTempFilePath
})
}
},
/**
* 获取进度
* @param {Object} progressEvent
* @param {Object} index
* @param {Object} type
*/
setProgress(progressEvent, index, type) {
const fileLenth = this.files.length
const percentNum = (index / fileLenth) * 100
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
let idx = index
if (!type) {
idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
}
if (idx === -1 || !this.files[idx]) return
// fix by mehaotian 100 -1
this.files[idx].progress = percentCompleted - 1
//
this.$emit('progress', {
index: idx,
progress: parseInt(percentCompleted),
tempFile: this.files[idx]
})
},
/**
* 删除文件
* @param {Object} index
*/
delFile(index) {
this.$emit('delete', {
tempFile: this.files[index],
tempFilePath: this.files[index].url
})
this.files.splice(index, 1)
this.$nextTick(() => {
this.setEmit()
})
},
/**
* 获取文件名和后缀
* @param {Object} name
*/
getFileExt(name) {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
},
/**
* 处理返回事件
*/
setEmit() {
let data = []
if (this.returnType === 'object') {
data = this.backObject(this.files)[0]
this.localValue = data?data:null
} else {
data = this.backObject(this.files)
if (!this.localValue) {
this.localValue = []
}
this.localValue = [...data]
}
// #ifdef VUE3
this.$emit('update:modelValue', this.localValue)
// #endif
// #ifndef VUE3
this.$emit('input', this.localValue)
// #endif
},
/**
* 处理返回参数
* @param {Object} files
*/
backObject(files) {
let newFilesData = []
files.forEach(v => {
newFilesData.push({
extname: v.extname,
fileType: v.fileType,
image: v.image,
name: v.name,
path: v.path,
size: v.size,
fileID:v.fileID,
url: v.url
})
})
return newFilesData
},
async getTempFileURL(fileList) {
fileList = {
fileList: [].concat(fileList)
}
const urls = await uniCloud.getTempFileURL(fileList)
return urls.fileList[0].tempFileURL || ''
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
}
}
}
</script>
<style>
.uni-file-picker {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
/* #endif */
}
.uni-file-picker__header {
padding-top: 5px;
padding-bottom: 10px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: space-between;
}
.file-title {
font-size: 14px;
color: #333;
}
.file-count {
font-size: 14px;
color: #999;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
</style>

325
uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue

@ -0,0 +1,325 @@
<template>
<view class="uni-file-picker__files">
<view v-if="!readonly" class="files-button" @click="choose">
<slot></slot>
</view>
<!-- :class="{'is-text-box':showType === 'list'}" -->
<view v-if="list.length > 0" class="uni-file-picker__lists is-text-box" :style="borderStyle">
<!-- ,'is-list-card':showType === 'list-card' -->
<view class="uni-file-picker__lists-box" v-for="(item ,index) in list" :key="index" :class="{
'files-border':index !== 0 && styles.dividline}"
:style="index !== 0 && styles.dividline &&borderLineStyle">
<view class="uni-file-picker__item">
<!-- :class="{'is-text-image':showType === 'list'}" -->
<!-- <view class="files__image is-text-image">
<image class="header-image" :src="item.logo" mode="aspectFit"></image>
</view> -->
<view class="files__name">{{item.name}}</view>
<view v-if="delIcon&&!readonly" class="icon-del-box icon-files" @click="delFile(index)">
<view class="icon-del icon-files"></view>
<view class="icon-del rotate"></view>
</view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadFile",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
delIcon: {
type: Boolean,
default: true
},
limit: {
type: [Number, String],
default: 9
},
showType: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
//
border: true,
// 线
dividline: true,
// 线
borderStyle: {}
}
}
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
list() {
let files = []
this.filesList.forEach(v => {
files.push(v)
})
return files
},
styles() {
let styles = {
border: true,
dividline: true,
'border-style': {}
}
return Object.assign(styles, this.listStyles)
},
borderStyle() {
let {
borderStyle,
border
} = this.styles
let obj = {}
if (!border) {
obj.border = 'none'
} else {
let width = (borderStyle && borderStyle.width) || 1
width = this.value2px(width)
let radius = (borderStyle && borderStyle.radius) || 5
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (borderStyle && borderStyle.style) || 'solid',
'border-color': (borderStyle && borderStyle.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
},
borderLineStyle() {
let obj = {}
let {
borderStyle
} = this.styles
if (borderStyle && borderStyle.color) {
obj['border-color'] = borderStyle.color
}
if (borderStyle && borderStyle.width) {
let width = borderStyle && borderStyle.width || 1
let style = borderStyle && borderStyle.style || 0
if (typeof width === 'number') {
width += 'px'
} else {
width = width.indexOf('px') ? width : width + 'px'
}
obj['border-width'] = width
if (typeof style === 'number') {
style += 'px'
} else {
style = style.indexOf('px') ? style : style + 'px'
}
obj['border-top-style'] = style
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", {
item,
index
})
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__files {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: flex-start;
}
.files-button {
// border: 1px red solid;
}
.uni-file-picker__lists {
position: relative;
margin-top: 5px;
overflow: hidden;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.4);
}
.uni-file-picker__lists-box {
position: relative;
}
.uni-file-picker__item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding: 8px 10px;
padding-right: 5px;
padding-left: 10px;
}
.files-border {
border-top: 1px #eee solid;
}
.files__name {
flex: 1;
font-size: 14px;
color: #666;
margin-right: 25px;
/* #ifndef APP-NVUE */
word-break: break-all;
word-wrap: break-word;
/* #endif */
}
.icon-files {
/* #ifndef APP-NVUE */
position: static;
background-color: initial;
/* #endif */
}
// .icon-files .icon-del {
// background-color: #333;
// width: 12px;
// height: 1px;
// }
.is-list-card {
border: 1px #eee solid;
margin-bottom: 5px;
border-radius: 5px;
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.1);
padding: 5px;
}
.files__image {
width: 40px;
height: 40px;
margin-right: 10px;
}
.header-image {
width: 100%;
height: 100%;
}
.is-text-box {
border: 1px #eee solid;
border-radius: 5px;
}
.is-text-image {
width: 25px;
height: 25px;
margin-left: 5px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
margin: auto 0;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 0px;
bottom: 0;
right: 5px;
height: 26px;
width: 26px;
// border-radius: 50%;
// background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 1px;
background-color: #333;
// border-radius: 1px;
}
/* #ifdef H5 */
@media all and (min-width: 768px) {
.uni-file-picker__files {
max-width: 375px;
}
}
/* #endif */
</style>

292
uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue

@ -0,0 +1,292 @@
<template>
<view class="uni-file-picker__container">
<view class="file-picker__box" v-for="(item,index) in filesList" :key="index" :style="boxStyle">
<view class="file-picker__box-content" :style="borderStyle">
<image class="file-image" :src="item.url" mode="aspectFill" @click.stop="prviewImage(item,index)"></image>
<view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
<view class="icon-del"></view>
<view class="icon-del rotate"></view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.errMsg" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
<view v-if="filesList.length < limit && !readonly" class="file-picker__box" :style="boxStyle">
<view class="file-picker__box-content is-add" :style="borderStyle" @click="choose">
<slot>
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadImage",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
disabled:{
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
limit: {
type: [Number, String],
default: 9
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto',
border: {}
}
}
},
delIcon: {
type: Boolean,
default: true
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
styles() {
let styles = {
width: 'auto',
height: 'auto',
border: {}
}
return Object.assign(styles, this.imageStyles)
},
boxStyle() {
const {
width = 'auto',
height = 'auto'
} = this.styles
let obj = {}
if (height === 'auto') {
if (width !== 'auto') {
obj.height = this.value2px(width)
obj['padding-top'] = 0
} else {
obj.height = 0
}
} else {
obj.height = this.value2px(height)
obj['padding-top'] = 0
}
if (width === 'auto') {
if (height !== 'auto') {
obj.width = this.value2px(height)
} else {
obj.width = '33.3%'
}
} else {
obj.width = this.value2px(width)
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
},
borderStyle() {
let {
border
} = this.styles
let obj = {}
const widthDefaultValue = 1
const radiusDefaultValue = 3
if (typeof border === 'boolean') {
obj.border = border ? '1px #eee solid' : 'none'
} else {
let width = (border && border.width) || widthDefaultValue
width = this.value2px(width)
let radius = (border && border.radius) || radiusDefaultValue
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (border && border.style) || 'solid',
'border-color': (border && border.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", item)
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
prviewImage(img, index) {
let urls = []
if(Number(this.limit) === 1&&this.disablePreview&&!this.disabled){
this.$emit("choose")
}
if(this.disablePreview) return
this.filesList.forEach(i => {
urls.push(i.url)
})
uni.previewImage({
urls: urls,
current: index
});
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
if (value.indexOf('%') === -1) {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__container {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-wrap: wrap;
margin: -5px;
}
.file-picker__box {
position: relative;
// flex: 0 0 33.3%;
width: 33.3%;
height: 0;
padding-top: 33.33%;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.file-picker__box-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: 5px;
border: 1px #eee solid;
border-radius: 5px;
overflow: hidden;
}
.file-picker__progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
/* border: 1px red solid; */
z-index: 2;
}
.file-picker__progress-item {
width: 100%;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.4);
}
.file-image {
width: 100%;
height: 100%;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 3px;
right: 3px;
height: 26px;
width: 26px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 2px;
background-color: #fff;
border-radius: 2px;
}
</style>

109
uni_modules/uni-file-picker/components/uni-file-picker/utils.js

@ -0,0 +1,109 @@
/**
* 获取文件名和后缀
* @param {String} name
*/
export const get_file_ext = (name) => {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
}
/**
* 获取扩展名
* @param {Array} fileExtname
*/
export const get_extname = (fileExtname) => {
if (!Array.isArray(fileExtname)) {
let extname = fileExtname.replace(/(\[|\])/g, '')
return extname.split(',')
} else {
return fileExtname
}
return []
}
/**
* 获取文件和检测是否可选
*/
export const get_files_and_is_max = (res, _extname) => {
let filePaths = []
let files = []
if(!_extname || _extname.length === 0){
return {
filePaths,
files
}
}
res.tempFiles.forEach(v => {
let fileFullName = get_file_ext(v.name)
const extname = fileFullName.ext.toLowerCase()
if (_extname.indexOf(extname) !== -1) {
files.push(v)
filePaths.push(v.path)
}
})
if (files.length !== res.tempFiles.length) {
uni.showToast({
title: `当前选择了${res.tempFiles.length}个文件 ,${res.tempFiles.length - files.length} 个文件格式不正确`,
icon: 'none',
duration: 5000
})
}
return {
filePaths,
files
}
}
/**
* 获取图片信息
* @param {Object} filepath
*/
export const get_file_info = (filepath) => {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: filepath,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
/**
* 获取封装数据
*/
export const get_file_data = async (files, type = 'image') => {
// 最终需要上传数据库的数据
let fileFullName = get_file_ext(files.name)
const extname = fileFullName.ext.toLowerCase()
let filedata = {
name: files.name,
uuid: files.uuid,
extname: extname || '',
cloudPath: files.cloudPath,
fileType: files.fileType,
url: files.path || files.path,
size: files.size, //单位是字节
image: {},
path: files.path,
video: {}
}
if (type === 'image') {
const imageinfo = await get_file_info(files.path)
delete filedata.video
filedata.image.width = imageinfo.width
filedata.image.height = imageinfo.height
filedata.image.location = imageinfo.path
} else {
delete filedata.image
}
return filedata
}

86
uni_modules/uni-file-picker/package.json

@ -0,0 +1,86 @@
{
"id": "uni-file-picker",
"displayName": "uni-file-picker 文件选择上传",
"version": "1.0.1",
"description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间",
"keywords": [
"uni-ui",
"uniui",
"图片上传",
"文件上传"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

11
uni_modules/uni-file-picker/readme.md

@ -0,0 +1,11 @@
## FilePicker 文件选择上传
> **组件名:uni-file-picker**
> 代码块: `uFilePicker`
文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
Loading…
Cancel
Save