uniappp少东西的问题

This commit is contained in:
Ledo 2025-01-12 13:11:56 +08:00
parent e47c4ce424
commit e2b651ce0a
22 changed files with 145 additions and 1977 deletions

View File

@ -83,6 +83,8 @@ public class ProcessBomDetailDO extends BaseDO {
private Integer status;
@TableField(exist = false)
private Integer materialTypeId;
@TableField(exist = false)
private Integer materialType;
/*
* BOM状态 0新增 1取消
* */
@ -142,4 +144,6 @@ public class ProcessBomDetailDO extends BaseDO {
private Decimal boomAmount;
@TableField(exist = false)
private String sortAttr;
@TableField(exist = false)
private String matType;
}

View File

@ -1,5 +1,6 @@
package com.chanko.yunxi.mes.module.heli.service.plantaskbom;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.chanko.yunxi.mes.framework.common.pojo.PageResult;
import com.chanko.yunxi.mes.framework.common.util.object.BeanUtils;
@ -153,21 +154,24 @@ public class PlanTaskBomServiceImpl implements PlanTaskBomService {
Map<Long, List<ProcessBomDetailDO>> groupedByMaterialId = list.stream()
.collect(Collectors.groupingBy(ProcessBomDetailDO::getMaterialId));
List<ProcessBomDetailDO> list1 = new ArrayList<>();
groupedByMaterialId.forEach((matId,listTemp)->{
ProcessBomDetailDO processBomDetailDO = listTemp.get(0);
int amount = 0;
for (ProcessBomDetailDO bo : listTemp) {
amount =amount+ bo.getProjectSubAmount() * bo.getAmount();
}
processBomDetailDO.setAmount(amount);
if (listTemp.size() == 1){
list1.add(processBomDetailDO);
}else {
processBomDetailDO.setProjectSubId(null);
processBomDetailDO.setProjectSubCode(null);
list1.add(processBomDetailDO);
}
});
if(CollUtil.isNotEmpty(groupedByMaterialId)){
groupedByMaterialId.forEach((matId,listTemp)->{
ProcessBomDetailDO processBomDetailDO = listTemp.get(0);
int amount = 0;
for (ProcessBomDetailDO bo : listTemp) {
amount =amount+ bo.getProjectSubAmount() * bo.getAmount();
}
processBomDetailDO.setAmount(amount);
if (listTemp.size() == 1){
list1.add(processBomDetailDO);
}else {
processBomDetailDO.setProjectSubId(null);
processBomDetailDO.setProjectSubCode(null);
list1.add(processBomDetailDO);
}
});
}
bomDetailDOPageResult.setList(list1);
return bomDetailDOPageResult;
}

View File

@ -225,18 +225,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
activeMenu: '/bpm/manager/form'
}
},
{
path: '/manager/model/edit',
component: () => import('@/views/bpm/model/editor/index.vue'),
name: 'BpmModelEditor',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '设计流程',
activeMenu: '/bpm/manager/model'
}
},
// {
// path: '/manager/model/edit',
// component: () => import('@/views/bpm/model/editor/index.vue'),
// name: 'BpmModelEditor',
// meta: {
// noCache: true,
// hidden: true,
// canTo: true,
// title: '设计流程',
// activeMenu: '/bpm/manager/model'
// }
// },
{
path: '/manager/definition',
component: () => import('@/views/bpm/definition/index.vue'),

View File

@ -792,14 +792,18 @@ const open = async (id: number,propertynew :number) => {
businessType: 'PROJECT_ORDER'
}
formData.value.operateLogs = (await getOperateLogPage(logParams)).list
if (formData.value.operateLogs && formData.value.operateLogs.length >= 3) {
const approveLog = formData.value.operateLogs[2]
formData.value.approveTime = approveLog.startTime
formData.value.approveUserName = approveLog.userNickname
if(formData.value.operateLogs.length ==4){
const auditLog = formData.value.operateLogs[3]
if (formData.value.operateLogs && formData.value.operateLogs.length >= 2) {
if(formData.value.operateLogs.length ==2){
const approveLog = formData.value.operateLogs[0]
formData.value.approveTime = approveLog.startTime
formData.value.approveUserName = approveLog.userNickname
}else{
const auditLog = formData.value.operateLogs[0]
formData.value.auditTime = auditLog.startTime
formData.value.auditUserName = auditLog.userNickname
const approveLog = formData.value.operateLogs[1]
formData.value.approveTime = approveLog.startTime
formData.value.approveUserName = approveLog.userNickname
}
// const auditLog = formData.value.operateLogs[3]
// formData.value.auditTime = auditLog.startTime

View File

@ -368,6 +368,19 @@ link type="primary" size="small" :disabled="ctrView || ctrDelete"
</el-row>
</el-card>
</el-form>
<el-dialog v-model="centerDialogVisible" title="提示" width="30%" center>
<span>
作废该单据后无法复原请确认是否作废
</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="centerDialogVisible = false">取消</el-button>
<el-button type="primary" @click="deleteStorage()">
确认
</el-button>
</span>
</template>
</el-dialog>
<div class="hl-footer text-center">
<el-button @click="() => router.go(-1)" size="large"> </el-button>
<el-button @click="submitForm" type="success" v-if="btnSave" size="large">
@ -418,7 +431,7 @@ const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const show = ref(false) //
const centerDialogVisible = ref(false)
const formData = ref({
id: undefined,
stockType: 2,
@ -496,7 +509,6 @@ const openProjectForm = (scope) => {
const getList = async (arrMat) => {
arrMat.forEach((row) => {
console.log(row)
if (
formData.value.matItemDOList.filter(
(item) =>
@ -602,6 +614,22 @@ const handleSelectedProjectOrder = (arr: ProjectOrderVO[]) => {
message.alertWarning('上游单据只允许选择一个!')
}
}
const deleteData = ref<StorageApi.StorageVO>();
const deleteStorage = async() =>{
await StorageApi.updateStorage(deleteData.value)
deleteData.value.cancel = useUserStore().getUser.id
commonResult = 'common.updateSuccess'
await StorageApi.updateStorageStatus(deleteData.value)
message.success(t('common.updateSuccess'))
query.id = formData.value.id
query.type = 'update'
if (sumbefore.value == 0) {
reload()
}
centerDialogVisible.value = false
}
const handleStatus = async (num) => {
formData.value.status = num
const data = formData.value as unknown as StorageApi.StorageVO
@ -674,6 +702,9 @@ const handleStatus = async (num) => {
await saveFormData()
data.outbound = useUserStore().getUser.id
}else if (num == 3){
deleteData.value = data;
centerDialogVisible.value = true;
} else {
if(formData.value.status == 2){
message.alertWarning(
@ -687,7 +718,8 @@ const handleStatus = async (num) => {
}
}
await StorageApi.updateStorageStatus(data)
if(num != 3){
await StorageApi.updateStorageStatus(data)
message.success(t('common.updateSuccess'))
@ -696,6 +728,8 @@ const handleStatus = async (num) => {
if (sumbefore.value == 0) {
reload()
}
}
}
const handleClick = (event) => {
openProjectOrderDialog()

View File

@ -361,12 +361,26 @@ link type="primary" size="small" :disabled="ctrView || ctrDelete"
<el-button @click="submitForm" type="success" v-if="btnSave" size="large">
</el-button>
<el-button @click="handleStatus(2)" type="primary" v-if="btnSave" size="large">
</el-button>
<el-button @click="handleStatus(4)" type="success" v-if="btnok " size="large">审核</el-button>
<el-button @click="handleStatus(3)" type="danger" v-if="btnCancel " size="large">
</el-button>
<el-button @click="handleStatus(4)" type="success" v-if="btnok " size="large">审核</el-button>
</div>
</el-card>
<el-dialog v-model="centerDialogVisible" title="提示" width="30%" center>
<span>
作废该单据后无法复原请确认是否作废
</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="centerDialogVisible = false">取消</el-button>
<el-button type="primary" @click="deleteStorage()">
确认
</el-button>
</span>
</template>
</el-dialog>
<!-- 表单弹窗物料列表 -->
<materials ref="matOpenFormRef" @success="getList" />
<!-- 表单弹窗物料列表 -->
@ -401,7 +415,7 @@ const { t } = useI18n() // 国际化
const message = useMessage() //
const { query } = useRoute()
const router = useRouter()
const centerDialogVisible = ref(false)
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
@ -549,6 +563,22 @@ const handleStockType = async (typeid) => {
enableHeadNo.value = true
}
}
const deleteData = ref<StorageApi.StorageVO>();
const deleteStorage = async() =>{
await StorageApi.updateStorage(deleteData.value)
deleteData.value.cancel = useUserStore().getUser.id
commonResult = 'common.updateSuccess'
await StorageApi.updateStorageStatus(deleteData.value)
message.success(t('common.updateSuccess'))
query.id = formData.value.id
query.type = 'update'
if (sumbefore.value == 0) {
reload()
}
centerDialogVisible.value = false
}
const handleStatus = async (num) => {
formData.value.status = num
const data = formData.value as unknown as StorageApi.StorageVO
@ -609,9 +639,8 @@ const handleStatus = async (num) => {
data.outbound = useUserStore().getUser.id
} else if(num ==3) {
await StorageApi.updateStorage(data)
data.cancel = useUserStore().getUser.id
commonResult = 'common.updateSuccess'
deleteData.value = data;
centerDialogVisible.value = true;
}else{
await StorageApi.updateStorageok(data)
if(formData.value.stockOutType == 1){
@ -620,14 +649,16 @@ const handleStatus = async (num) => {
}
}
await StorageApi.updateStorageStatus(data)
if(num != 3){
await StorageApi.updateStorageStatus(data)
message.success(t('common.updateSuccess'))
message.success(t('common.updateSuccess'))
query.id = formData.value.id
query.type = 'update'
if (sumbefore.value == 0) {
reload()
query.id = formData.value.id
query.type = 'update'
if (sumbefore.value == 0) {
reload()
}
}
}
@ -1050,7 +1081,7 @@ const initStatus = async (status) => {
ctrCancel.value = true
ctrDelete.value = false
btnSave.value = false
btnCancel.value = true
btnCancel.value = false
btnok.value=false
break
default:
@ -1065,11 +1096,11 @@ const initStatus = async (status) => {
btnSave.value = false
btnok.value=false
console.log('ss'+formData.value.status)
if(formData.value.status == 4){
btnCancel.value = true
}else{
btnCancel.value = false
}
// if(formData.value.status == 4){
// btnCancel.value = true
// }else{
// btnCancel.value = false
// }
}
}
@ -1091,8 +1122,8 @@ onMounted(async () => {
//
await getMatList('')
//
matLastData.value = await StorageMatApi.getStorageMatList(0)
// //
// matLastData.value = await StorageMatApi.getStorageMatList(0)
//
if (query.id) {
@ -1139,10 +1170,11 @@ onMounted(async () => {
item.matSpec = matEditList.value.find((record) => record.id === item.matId)?.spec
item.matType = matTypes.find( op => op.value == matEditList.value.find((record) => record.id === item.matId)?.material_type)?.label
item.matUnit = matUnits.find( op => op.value == matEditList.value.find((record) => record.id === item.matId)?.unit)?.label//matSimpList.value.find((record) => record.id === item.matId)?.unit
item.matRest = matLastData.value.find(
(row) => row.matId == item.matId && row.pnId == item.pnId
)?.matRest
item.matRest = item.matRest == undefined || item.matRest.length == 0 ? 0 : item.matRest
// item.matRest = matLastData.value.find(
// (row) => row.matId == item.matId && row.pnId == item.pnId
// )?.matRest
// console.log('ss'+item.matRest);
// item.matRest = item.matRest == undefined || item.matRest.length == 0 ? 0 : item.matRest
item.pnlist = pnList.value.filter((pn) => pn.wh_id === item.whId)
item.matList = matEditList.value.filter((record) => record.id === item.matId)
})

View File

@ -213,14 +213,12 @@ const submitForm = async () => {
}else{
item.matId = item.id
}
item.storageOkQty = ''
item.whId= item.whId??''
item.rgId= item.rgId??''
item.pnId= item.pnId??''
})
console.log(multipleSelection.value)
//
emit('success', multipleSelection.value)
} finally {

View File

@ -1,6 +1,6 @@
{
"name" : "合力机械小程序",
"appid" : "__UNI__06FD60C",
"appid" : "__UNI__F93316F",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
@ -100,7 +100,7 @@
},
/* */
"mp-weixin" : {
"appid" : "wx48fad418e2753b1c",
"appid" : "wxa29fe397274614b5",
"setting" : {
"minified" : true,
"urlCheck" : true,
@ -112,8 +112,7 @@
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
},
"_usePrivacyCheck_" : true
}
},
"mp-alipay" : {
"usingComponents" : true
@ -127,6 +126,6 @@
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2",
"vueVersion" : "3",
"fallbackLocale" : "zh-Hans"
}

View File

@ -36,7 +36,6 @@ const isShowEnd = computed(() => {
}
return flag
})
const productionTitle = ref('开始生产')
const amount = ref('')
const workTime = ref('')
//
@ -99,7 +98,6 @@ const handleComplate = async () => {
const params = {
id: detailInfo.value?.id,
active: 'FINISH',
ownerId: userId,
}
const data = await postOperateAPI(params)
const url = `/pages/productionReport/productionReport-detail?id=${detailInfo.value.id}`
@ -126,14 +124,12 @@ const handleStart = async () => {
const params = {
id: detailInfo.value.id,
active: 'START',
ownerId: userId,
}
const data = await postOperateAPI(params)
const url = `/pages/productionReport/productionReport-detail?id=${detailInfo.value.id}`
uni.redirectTo({
url,
})
productionTitle.value = '生产中'
}
//
const handleStop = async () => {
@ -148,7 +144,6 @@ const handleStop = async () => {
})
popup.value?.open()
}
</script>
<template>
<view class="data-detail">
@ -229,7 +224,7 @@ const handleStop = async () => {
</view>
</view>
<view class="action" v-if="detailInfo.procedureStatus !== 2">
<view class="action-item start" v-if="isShowStart" @click="handleStart">{{productionTitle}}</view>
<view class="action-item start" v-if="isShowStart" @click="handleStart">开始生产</view>
<view class="action-item stop" v-if="isShowEnd" @click="handleStop">结束生产</view>
</view>
</template>

View File

@ -8,6 +8,5 @@
*/
// export const serviceDomain = 'https://nxhs.cjyx.cc'
// export const serviceDomain = 'https://star.hz-hl.com'
// export const serviceDomain = 'http://222.71.165.187:9010'
export const serviceDomain = 'http://222.71.165.187:9010'
export const serviceDomain = 'http://localhost:8080'

View File

@ -11,7 +11,7 @@ import { http } from '@/utils/http'
export const getListAPI = (data: Object) => {
return http<any[]>({
method: 'GET',
url: '/heli/task-dispatch/task-dispatch-detail/pagewx',
url: '/heli/task-dispatch/task-dispatch-detail/page',
data,
})
}

View File

@ -1,8 +0,0 @@
## 1.0.32023-05-18
1.0.3
## 1.0.22023-05-18
解决视频上传问题
## 1.0.12023-05-15
更细说明
## 1.0.02023-05-12
1.0.0

View File

@ -1,425 +0,0 @@
<template>
<view class="l-upload">
<view class="upload-box">
<view class="image-item"
:style="{width:width+'rpx',height:height+'rpx','--margin-right':marginRight + 'px',marginRight:(index + 1) % lineNum == 0 ? 0 : marginRight + 'px'}"
v-for="(item,index) in fileList" :key="index">
<block v-if="item.split('.')[1] == 'mp4'">
<video :src="item" class="img" :style="{width:width+'rpx',height:height+'rpx'}"
></video>
<view class="img-del" @tap.stop="delVideo(index)"></view>
</block>
<block v-else>
<image :src="item" class="img" :style="{width:width+'rpx',height:height+'rpx'}"
@tap.stop="previewImage(index)" mode="aspectFill"></image>
<view class="img-del" @tap.stop="delImage(index)"></view>
</block>
</view>
<view v-if="isShowAdd" class="upload-add" :style="{'width':width+'rpx',height:height+'rpx'}"
@tap="chooseFile">
<view class="upload-icon icon-tianjia"></view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'lUpload',
props: {
//
width: {
type: [Number, String],
default: 220
},
//
height: {
type: [Number, String],
default: 220
},
// img/video
uploadType:{
type:String,
default:'all'
},
//
disableAdd: {
type: Boolean,
default: false
},
//
limit: {
type: Number,
default: 9
},
//original compressed
sizeType: {
type: Array,
default () {
return ['original', 'compressed']
}
},
//album camera 使使
sourceType: {
type: Array,
default () {
return ['album', 'camera']
}
},
// Array<String> ['jpg','png','gif']
imageFormat: {
type: Array,
default () {
return []
}
},
//
images: {
type: Array,
default () {
return []
}
},
//
videos: {
type: Array,
default () {
return []
}
},
//
serverUrl: {
type: String,
default: ""
},
//key file
fileKeyName: {
type: String,
default: "file"
},
//HTTP Header, header Referer
header: {
type: Object,
default () {
return {}
}
},
//HTTP form data
formData: {
type: Object,
default () {
return {}
}
},
},
data() {
return {
//
imageList: [...this.images],
//
videoList: [...this.videos],
fileList:[...this.videos,...this.images],
marginRight: 0,
lineNum: 0,
videoContext:null,
}
},
computed: {
isShowAdd() {
let isShow = true;
if (this.disableAdd || (this.limit && (this.imageList.length+this.videoList) >= this.limit)) {
isShow = false;
}
return isShow
}
},
created() {
this.initBoxData()
},
methods: {
change() {
console.log(this.fileList)
this.fileList = [...this.videoList,...this.imageList]
this.$emit('complete', {
imageArr: [...this.imageList],
videoArr: [...this.videoList],
})
},
initBoxData() {
const query = uni.createSelectorQuery().in(this);
query.select('.upload-box').fields({
rect: true, //
size: true //
}, res => {
let imgwidth = Math.floor(this.width / 2)
this.lineNum = Math.floor(res.width / imgwidth)
this.marginRight = (res.width - this.lineNum * imgwidth) / (this.lineNum - 1)
});
query.exec(); //
},
toast(text) {
text && uni.showToast({
title: text,
icon: "none"
});
},
chooseFile() {
if(this.uploadType == 'img'){
this.chooseImage()
}else if(this.uploadType == 'video'){
this.chooseVideo()
}else{
uni.showActionSheet({
itemList: ['图片','视频'],
success: (res)=> {
if(res.tapIndex + 1 == 1){
this.chooseImage()
}else{
this.chooseVideo()
}
},
fail: function (res) {
console.log(res.errMsg);
}
});
}
},
chooseVideo() {
uni.chooseVideo({
sizeType: this.sizeType,
camera: 'back', //
success: (e) => {
this.videoList.push(e.tempFilePath)
//
if (this.serverUrl) {
this.uploadImage(null,e.tempFilePath,'video').then(() => {
this.change()
}).catch(() => {
this.change()
})
} else {
//
this.change()
}
}
})
},
chooseImage() {
uni.chooseImage({
count: this.limit - this.imageList.length - this.videoList.length,
sizeType: this.sizeType,
sourceType: this.sourceType,
success: (e) => {
console.log(e)
let imageArr = [];
for (let i = 0; i < e.tempFiles.length; i++) {
let len = this.imageList.length + this.videoList.length;
if (len >= this.limit) {
this.toast(`最多可上传${this.limit}张图片/视频`);
break;
}
//
let path = e.tempFiles[i].path;
if (this.imageFormat.length > 0) {
let format = ""
// #ifdef H5
let type = e.tempFiles[i].type;
format = type.split('/')[1]
// #endif
// #ifndef H5
format = path.split(".")[(path.split(".")).length - 1];
// #endif
if (this.imageFormat.indexOf(format) == -1) {
let text = `只能上传 ${this.imageFormat.join(',')} 格式图片!`
this.toast(text);
continue;
}
}
imageArr.push(path)
this.imageList.push(path)
}
let start = this.imageList.length - imageArr.length
for (let j = 0; j < imageArr.length; j++) {
let index = start + j
//
if (this.serverUrl) {
this.uploadImage(index, imageArr[j],'image').then(() => {
this.change()
}).catch(() => {
this.change()
})
} else {
//
this.change()
}
}
}
})
},
uploadImage(index, url,type) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: this.serverUrl,
name: this.fileKeyName,
header: this.header,
formData: this.formData,
filePath: url,
success: (res) => {
if (res.statusCode == 200) {
//
let data = JSON.parse(res.data.replace(/\ufeff/g, "") || "{}")
if (data.code === '根据实际情况修改') {
if(type === 'image'){// -
this.imageList.push(...data.data)//-this.imageList
}else if(type === 'video'){// -
this.videoList.push(...data.data)//-this.videoList
}
} else {
//
}
resolve()
} else {
reject(res.msg)
}
},
fail: function(res) {
reject(res)
}
})
})
},
delVideo(index) {
uni.showModal({
title: '提示',
content: '确认删除该视频吗?',
success: (res) => {
if (res.confirm) {
this.videoList.splice(index, 1)
this.$emit("remove", {
index: index,
type:'video'
})
this.change()
}
}
})
},
delImage(index) {
uni.showModal({
title: '提示',
content: '确认删除该图片吗?',
success: (res) => {
if (res.confirm) {
this.imageList.splice(index, 1)
this.$emit("remove", {
index: index,
type:'image'
})
this.change()
}
}
})
},
previewImage(index) {
if (!this.imageList.length) return;
uni.previewImage({
current: this.imageList[index],
loop: true,
urls: this.imageList
})
},
}
}
</script>
<style lang="scss" scoped>
@font-face {
font-family: 'l-icon';
src: url('data:font/ttf;charset=utf-8;base64,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') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
.upload-icon {
font-family: "l-icon" !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 10rpx;
}
.icon-tianjia:before {
content: "\e63b";
}
.l-upload {
width: 100%;
.upload-box {
width: 100%;
display: flex;
flex-wrap: wrap;
.image-item {
position: relative;
margin-bottom: var(--margin-right);
.img {
display: block;
}
.img-del {
width: 36rpx;
height: 36rpx;
position: absolute;
right: -12rpx;
top: -12rpx;
background-color: #EB0909;
border-radius: 50%;
color: white;
font-size: 34rpx;
z-index: 200;
&::before {
content: '';
width: 16rpx;
height: 1px;
position: absolute;
left: 10rpx;
top: 18rpx;
background-color: #fff;
}
}
}
.upload-add {
font-size: 68rpx;
font-weight: 100;
color: #888;
background-color: #F7F7F7;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
}
}
</style>

View File

@ -1,84 +0,0 @@
{
"id": "l-upload",
"displayName": "文件上传 图片上传预览 视频上传预览",
"version": "1.0.3",
"description": "l-upload 图片视频上传组件,自适应布局,可单独上传图片或视频,也可都能上传 仅测过小程序其他自测",
"keywords": [
"文件上传",
"图片上传",
"视频上传",
"图片预览",
"视频预览"
],
"repository": "",
"engines": {
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"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",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
}
}

View File

@ -1,38 +0,0 @@
# l-upload
组件会根据图片大小自动调整每行图片数量图片之间的上下左右间距
# 使用方法
配置easycom规则后自动按需引入无需`import`组件,直接引用即可。
#
注意 使用接口地址需要自己在组件中的uploadImage方法中根据自己组件返回数据进行修改
```html
<l-upload @complete="complete" @remove="remove"></l-upload>
```
## 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|:---:|:---:|:---:|---|
| width | Number | 220 | 图片宽度单位rpx |
| height | Number | 220 | 图片高度单位rpx |
| uploadType | String | all | 上传类型 图片img 视频video 默认都可以 |
| 禁用添加 | Boolean | false | 禁用添加按钮,默认达到限制数量后禁用 |
| limit | Number | 9 | 图片视频限制数 |
| sizeType | Array<String> | ['original', 'compressed'] | original 原图compressed 压缩图,默认二者都有 |
| sourceType | Array<String> | ['album', 'camera'] | album 从相册选图camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项 |
| imageFormat | Array<String> | [] | 可上传图片类型,默认为空,不限制 ['jpg','png','gif'] |
| images | Array | [] | 初始图片,如果是修改需要回显内容就能用到 |
| videos | Array | [] | 初始视频 |
| serverUrl | String | '' | 服务器接口地址。当接口地址为空时,直接返回本地图片地址 |
| fileKeyName | String | 'file' | 文件对应的key默认为 file |
| header | Object | {} | HTTP 请求 Header, header 中不能设置 Referer。 |
| formData | Object | {} | HTTP 请求中其他额外的 form data |
## 组件事件
| 名称 | 触发时机 |
|:---:|---|
| complete | 上传完成事件 返回文件信息 |
| remove | 删除 返回index |

View File

@ -1,12 +0,0 @@
## 2.3.12024-04-09
修改第一次文件无法上传的bug
## 2.3.02023-11-06
方法名修改
## 2.2.92023-11-06
修改bug
## 2.2.82023-11-06
字段修改
## 2.2.72023-11-06
bug修改
## 2.2.62023-03-16

View File

@ -1,396 +0,0 @@
export class YtjFile {
constructor(data) {
this.dom = null;
// files.type = waiting等待上传|| loading上传中|| success成功 || fail失败
this.files = new Map();
this.debug = data.debug || false;
this.id = data.id;
this.width = data.width;
this.height = data.height;
this.option = data.option;
this.instantly = data.instantly;
this.prohibited = data.prohibited;
this.onchange = data.onchange;
this.onprogress = data.onprogress;
this.uploadHandle = this._uploadHandle;
// #ifdef MP-WEIXIN
this.uploadHandle = this._uploadHandleWX;
// #endif
}
/**
* 创建File节点
* @param {string}path webview地址
*/
create(path) {
if (!this.dom) {
// #ifdef H5
let dom = document.createElement('input');
dom.type = 'file'
dom.value = ''
dom.style.height = this.height
dom.style.width = this.width
dom.style.position = 'absolute'
dom.style.top = 0
dom.style.left = 0
dom.style.right = 0
dom.style.bottom = 0
dom.style.opacity = 0
dom.style.zIndex = 999
dom.accept = this.prohibited.accept;
if (this.prohibited.count > 1) {
dom.multiple = 'multiple';
}
dom.onchange = event => {
for (let file of event.target.files) {
if (this.files.size >= this.prohibited.count) {
this.toast(`只允许上传${this.prohibited.count}个文件`);
this.dom.value = '';
break;
}
this.addFile(file);
}
this._uploadAfter();
this.dom.value = '';
};
this.dom = dom;
// #endif
// #ifdef APP-PLUS
let styles = {
top: '-200px',
left: 0,
width: '1px',
height: '200px',
background: 'transparent'
};
let extras = {
debug: this.debug,
instantly: this.instantly,
prohibited: this.prohibited,
}
this.dom = plus.webview.create(path, this.id, styles,extras);
this.setData(this.option);
this._overrideUrlLoading();
// #endif
return this.dom;
}
}
/**
* 设置上传参数
* @param {object|string}name 上传参数,支持a.b a[b]
*/
setData() {
let [name,value = ''] = arguments;
if (typeof name === 'object') {
Object.assign(this.option,name);
}
else {
this._setValue(this.option,name,value);
}
this.debug&&console.log(JSON.stringify(this.option));
// #ifdef APP-PLUS
this.dom.evalJS(`vm.setData('${JSON.stringify(this.option)}')`);
// #endif
}
/**
* 上传
* @param {string}name 文件名称
*/
async upload(name='') {
if (!this.option.url) {
throw Error('未设置上传地址');
}
// #ifndef APP-PLUS
if (name && this.files.has(name)) {
await this.uploadHandle(this.files.get(name));
}
else {
for (let item of this.files.values()) {
if (item.type === 'waiting' || item.type === 'fail') {
await this.uploadHandle(item);
}
}
}
// #endif
// #ifdef APP-PLUS
this.dom&&this.dom.evalJS(`vm.upload('${name}')`);
// #endif
}
// 选择文件change
addFile(file,isCallChange) {
let name = file.name;
this.debug&&console.log('文件名称',name,'大小',file.size);
if (file) {
// 限制文件格式
let path = '';
let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
let formats = this.prohibited.formats.toLowerCase();
// #ifndef MP-WEIXIN
path = URL.createObjectURL(file);
// #endif
// #ifdef MP-WEIXIN
path = file.path;
// #endif
if (formats&&!formats.includes(suffix)) {
this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
return false;
}
// 限制文件大小
if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
this.toast(`附件大小请勿超过${this.prohibited.size}M`)
return false;
}
this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
return true;
}
}
/**
* 移除文件
* @param {string}name 不传name默认移除所有文件传入name移除指定name的文件
*/
clear(name='') {
// #ifdef APP-PLUS
this.dom&&this.dom.evalJS(`vm.clear('${name}')`);
// #endif
if (!name) {
this.files.clear();
}
else {
this.files.delete(name);
}
return this.onchange(this.files);
}
/**
* 提示框
* @param {string}msg 轻提示内容
*/
toast(msg) {
uni.showToast({
title: msg,
icon: 'none'
});
}
/**
* 微信小程序选择文件
* @param {number}count 可选择文件数量
*/
chooseMessageFile(type,count) {
wx.chooseMessageFile({
count: count,
type: type,
success: ({ tempFiles }) => {
for (let file of tempFiles) {
this.addFile(file);
}
this._uploadAfter();
},
fail: () => {
this.toast(`打开失败`);
}
})
}
_copyObject(obj) {
if (typeof obj !== "undefined") {
return JSON.parse(JSON.stringify(obj));
} else {
return obj;
}
}
/**
* 自动根据字符串路径设置对象中的值 支持.[]
* @param {Object} dataObj 数据源
* @param {String} name 支持a.b a[b]
* @param {String} value
* setValue(dataObj, name, value);
*/
_setValue(dataObj, name, value) {
// 通过正则表达式 查找路径数据
let dataValue;
if (typeof value === "object") {
dataValue = this._copyObject(value);
} else {
dataValue = value;
}
let regExp = new RegExp("([\\w$]+)|\\[(:\\d)\\]", "g");
const patten = name.match(regExp);
// 遍历路径 逐级查找 最后一级用于直接赋值
for (let i = 0; i < patten.length - 1; i++) {
let keyName = patten[i];
if (typeof dataObj[keyName] !== "object") dataObj[keyName] = {};
dataObj = dataObj[keyName];
}
// 最后一级
dataObj[patten[patten.length - 1]] = dataValue;
this.debug&&console.log('参数更新后',JSON.stringify(this.option));
}
_uploadAfter() {
this.onchange(this.files);
this.instantly&&this.upload();
}
_overrideUrlLoading() {
this.dom.overrideUrlLoading({ mode: 'reject' }, e => {
let {retype,item,files,end} = this._getRequest(
e.url
);
let _this = this;
switch (retype) {
case 'updateOption':
this.dom.evalJS(`vm.setData('${JSON.stringify(_this.option)}')`);
break
case 'change':
try {
_this.files = new Map([..._this.files,...JSON.parse(unescape(files))]);
} catch (e) {
return console.error('出错了,请检查代码')
}
_this.onchange(_this.files);
break
case 'progress':
try {
item = JSON.parse(unescape(item));
} catch (e) {
return console.error('出错了,请检查代码')
}
_this._changeFilesItem(item,end);
break
default:
break
}
})
}
_getRequest(url) {
let theRequest = new Object()
let index = url.indexOf('?')
if (index != -1) {
let str = url.substring(index + 1)
let strs = str.split('&')
for (let i = 0; i < strs.length; i++) {
theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
}
}
return theRequest
}
_changeFilesItem(item,end=false) {
this.debug&&console.log('onprogress',JSON.stringify(item));
this.onprogress(item,end);
this.files.set(item.name,item);
}
_uploadHandle(item) {
item.type = 'loading';
delete item.responseText;
return new Promise((resolve,reject)=>{
this.debug&&console.log('option',JSON.stringify(this.option));
let {url,name,method='POST',header,formData} = this.option;
let form = new FormData();
for (let keys in formData) {
form.append(keys, formData[keys])
}
form.append(name, item.file);
let xmlRequest = new XMLHttpRequest();
xmlRequest.open(method, url, true);
for (let keys in header) {
xmlRequest.setRequestHeader(keys, header[keys])
}
xmlRequest.upload.addEventListener(
'progress',
event => {
if (event.lengthComputable) {
let progress = Math.ceil((event.loaded * 100) / event.total)
if (progress <= 100) {
item.progress = progress;
this._changeFilesItem(item);
}
}
},
false
);
xmlRequest.ontimeout = () => {
console.error('请求超时')
item.type = 'fail';
this._changeFilesItem(item,true);
return resolve(false);
}
xmlRequest.onreadystatechange = ev => {
if (xmlRequest.readyState == 4) {
if (xmlRequest.status == 200) {
this.debug&&console.log('上传完成:' + xmlRequest.responseText)
item['responseText'] = xmlRequest.responseText;
item.type = 'success';
this._changeFilesItem(item,true);
return resolve(true);
} else if (xmlRequest.status == 0) {
console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配服务端已正确开启跨域并且nginx未拦截阻止请求')
}
console.error('--ERROR--status = ' + xmlRequest.status)
item.type = 'fail';
this._changeFilesItem(item,true);
return resolve(false);
}
}
xmlRequest.send(form)
});
}
_uploadHandleWX(item) {
item.type = 'loading';
delete item.responseText;
return new Promise((resolve,reject)=>{
this.debug&&console.log('option',JSON.stringify(this.option));
let form = {filePath: item.file.path,...this.option };
form['fail'] = ({ errMsg = '' }) => {
console.error('--ERROR--' + errMsg)
item.type = 'fail';
this._changeFilesItem(item,true);
return resolve(false);
}
form['success'] = res => {
if (res.statusCode == 200) {
this.debug&&console.log('上传完成,微信端返回不一定是字符串根据接口返回格式判断是否需要JSON.parse' + res.data)
item['responseText'] = res.data;
item.type = 'success';
this._changeFilesItem(item,true);
return resolve(true);
}
item.type = 'fail';
this._changeFilesItem(item,true);
return resolve(false);
}
let xmlRequest = uni.uploadFile(form);
xmlRequest.onProgressUpdate(({ progress = 0 }) => {
if (progress <= 100) {
item.progress = progress;
this._changeFilesItem(item);
}
})
});
}
}

View File

@ -1,313 +0,0 @@
<template>
<view class="yt-file" :style="[getStyles]">
<view ref="yt" class="hFile" :style="[getStyles]" @click="onClick">
<slot><view class="defview" :style="[getStyles]">附件上传</view></slot>
</view>
</view>
</template>
<script>
import {YtjFile} from './YtjFile.js'
export default {
name: 'yt-upload',
props: {
//
debug: {type: Boolean,default: false},
//
instantly: {type: Boolean,default: false},
//
option: {type: Object,default: ()=>{}},
//
size: { type: Number, default: 10 },
// ,
count: { type: Number, default: 9 },
//
formats: { type: String, default:''},
// input file
accept: {type: String,default: ''},
//
//all=
//video=
//image=
//file=
wxFileType: { type: String, default: 'all' },
// webviewIDId
childId: { type: String, default: 'ytUpload' },
//
width: { type: String, default: '100%' },
//
height: { type: String, default: '80rpx' },
// top,left,bottom,rightposition=absolute
top: { type: [String, Number], default: '' },
left: { type: [String, Number], default: '' },
bottom: { type: [String, Number], default: '' },
right: { type: [String, Number], default: '' },
// nvue
position: {
type: String,
// #ifdef APP-NVUE
default: 'absolute',
// #endif
// #ifndef APP-NVUE
default: 'static',
// #endif
},
},
data() {
return {
}
},
watch: {
option(v) {
// #ifdef APP-PLUS
this.ytFile&&this.show();
// #endif
}
},
updated() {
// #ifdef APP-PLUS
if (this.isShow) {
this.ytFile&&this.show();
}
// #endif
},
computed: {
getStyles() {
let styles = {
width: this.width,
height: this.height
}
if (this.position == 'absolute') {
styles['top'] = this.top
styles['bottom'] = this.bottom
styles['left'] = this.left
styles['right'] = this.right
styles['position'] = 'fixed'
}
return styles
}
},
mounted() {
this._size = 0;
let WEBID = this.childId + new Date().getTime();
this.ytFile = new YtjFile({
id: WEBID,
debug: this.debug,
width: this.width,
height: this.height,
option: this.option,
instantly: this.instantly,
//
prohibited: {
//
size: this.size,
//
formats: this.formats,
//
accept: this.accept,
count: this.count
},
onchange: this.onchange,
onprogress: this.onprogress,
});
this.create();
},
beforeDestroy() {
// #ifdef APP-PLUS
this.ytFile.dom.close();
// #endif
},
methods: {
setFiles(array) {
if (array instanceof Map) {
for (let [key, item] of array) {
item['progress'] = 100;
item['type'] = 'success';
this.ytFile.files.set(key,item);
}
}
else if (Array.isArray(array)) {
array.forEach(item=>{
if (item.name) {
item['progress'] = 100;
item['type'] = 'success';
this.ytFile.files.set(item.name,item);
}
});
}
this.onchange(this.ytFile.files);
},
setData() {
this.ytFile&&this.ytFile.setData(...arguments);
},
getDomStyles(callback) {
// #ifndef APP-NVUE
let view = uni
.createSelectorQuery()
.in(this)
.select('.yt-file')
view.fields(
{
size: true,
rect: true
},
({ height, width, top, left, right, bottom }) => {
uni.createSelectorQuery()
.selectViewport()
.scrollOffset(({ scrollTop }) => {
return callback({
top: parseInt(top) + parseInt(scrollTop) + 'px',
left: parseInt(left) + 'px',
width: parseInt(width) + 'px',
height: parseInt(height) + 'px'
})
})
.exec()
}
).exec()
// #endif
// #ifdef APP-NVUE
const dom = weex.requireModule('dom')
dom.getComponentRect(this.$refs.yt, ({ size: { height, width, top, left, right, bottom } }) => {
return callback({
top: parseInt(top) + 'px',
left: parseInt(left) + 'px',
width: parseInt(width) + 'px',
height: parseInt(height) + 'px',
right: parseInt(right) + 'px',
bottom: parseInt(bottom) + 'px'
})
})
// #endif
},
show() {
if (this._size && (this._size >= this.count)) {
return;
}
this.isShow = true;
// #ifdef APP-PLUS
this.ytFile&&this.getDomStyles(styles => {
this.ytFile.dom.setStyle(styles)
});
// #endif
// #ifdef H5
this.ytFile.dom.style.display = 'inline'
// #endif
},
hide() {
this.isShow = false;
// #ifdef APP-PLUS
this.ytFile&&this.ytFile.dom.setStyle({
top: '-100px',
left:'0px',
width: '1px',
height: '100px',
});
// #endif
// #ifdef H5
this.ytFile.dom.style.display = 'none'
// #endif
},
/**
* 手动提交上传
* @param {string}name 文件名称不传则上传所有type等于waiting和fail的文件
*/
upload(name) {
this.ytFile&&this.ytFile.upload(name);
},
/**
* @returns {Map} 已选择的文件Map集
*/
onchange(files) {
this.$emit('change',files);
this._size = files.size;
return files.size >= this.count ? this.hide() : this.show();
},
/**
* @returns {object} 当前上传中的对象
*/
onprogress(item,end=false) {
this.$emit('progress',item);
if (end) {
setTimeout(()=>{
this.$emit('uploadEnd',item);
},0);
}
},
/**
* 移除组件内缓存的某条数据
* @param {string}name 文件名称,不指定默认清除所有文件
*/
clear(name) {
this.ytFile.clear(name);
},
//
create() {
// iOShybridhtmlpath
let path = '/uni_modules/yt-upload/hybrid/html/uploadFile.html';
let dom = this.ytFile.create(path);
// #ifdef H5
this.$refs.yt.$el.appendChild(dom);
// #endif
// #ifndef APP-PLUS
this.show();
// #endif
// #ifdef APP-PLUS
dom.setStyle({position: this.position});
dom.loadURL(path);
setTimeout(()=>{
// #ifdef APP-NVUE
plus.webview.currentWebview().append(dom);
// #endif
// #ifndef APP-NVUE
this.$root.$scope.$getAppWebview().append(dom);
// #endif
this.show();
},500)
// #endif
},
//
onClick() {
if (this._size >= this.count) {
this.toast(`只允许上传${this.count}个文件`);
return;
}
// #ifdef MP-WEIXIN
if (!this.isShow) {return;}
let count = this.count - this._size;
this.ytFile.chooseMessageFile(this.wxFileType,count);
// #endif
},
toast(msg) {
uni.showToast({
title: msg,
icon: 'none'
});
}
}
}
</script>
<style scoped>
.yt-file {
display: inline-block;
}
.defview {
background-color: #007aff;
color: #fff;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
.hFile {
position: relative;
overflow: hidden;
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -1,191 +0,0 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title class="title">[文件管理器]</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<style type="text/css">
.content {background: transparent;}
.btn {position: relative;top: 0;left: 0;bottom: 0;right: 0;}
.btn .file {position: fixed;z-index: 93;left: 0;right: 0;top: 0;bottom: 0;width: 100%;opacity: 0;}
</style>
</head>
<body>
<div id="content" class="content">
<div class="btn">
<input :multiple="multiple" @change="onChange" :accept="accept" ref="file" class="file" type="file" />
</div>
</div>
<script type="text/javascript" src="js/vue.min.js"></script>
<script type="text/javascript">
let _this;
var vm = new Vue({
el: '#content',
data: {
accept: '',
multiple: true,
},
mounted() {
console.log('加载webview');
_this = this;
this.files = new Map();
document.addEventListener('plusready', (e)=>{
let {debug,instantly,prohibited} = plus.webview.currentWebview();
this.debug = debug;
this.instantly = instantly;
this.prohibited = prohibited;
this.accept = prohibited.accept;
this.multiple = prohibited.count > 1;
location.href = 'callback?retype=updateOption';
}, false);
},
methods: {
toast(msg) {
plus.nativeUI.toast(msg);
},
clear(name) {
if (!name) {
this.files.clear();
return;
}
this.files.delete(name);
},
setData(option='{}') {
this.debug&&console.log('更新参数:'+option);
try{
_this.option = JSON.parse(option);
}catch(e){
console.error('参数设置错误')
}
},
async upload(name=''){
if (name && this.files.has(name)) {
await this.createUpload(this.files.get(name));
}
else {
for (let item of this.files.values()) {
if (item.type === 'waiting' || item.type === 'fail') {
await this.createUpload(item);
}
}
}
},
onChange(e) {
let fileDom = this.$refs.file;
for (let file of fileDom.files) {
if (this.files.size >= this.prohibited.count) {
this.toast(`只允许上传${this.prohibited.count}个文件`);
fileDom.value = '';
break;
}
this.addFile(file);
}
this.uploadAfter();
fileDom.value = '';
},
addFile(file) {
if (file) {
let name = file.name;
this.debug&&console.log('文件名称',name,'大小',file.size);
// 限制文件格式
let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
let formats = this.prohibited.formats.toLowerCase();
if (formats&&!formats.includes(suffix)) {
this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
return;
}
// 限制文件大小
if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
this.toast(`附件大小请勿超过${this.prohibited.size}M`)
return;
}
let path = URL.createObjectURL(file);
this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
}
},
/**
* @returns {Map} 已选择的文件Map集
*/
callChange() {
location.href = 'callback?retype=change&files=' + escape(JSON.stringify([...this.files]));
},
/**
* @returns {object} 正在处理的当前对象
*/
changeFilesItem(item,end='') {
this.files.set(item.name,item);
location.href = 'callback?retype=progress&end='+ end +'&item=' + escape(JSON.stringify(item));
},
uploadAfter() {
this.callChange();
this.instantly&&this.upload();
},
createUpload(item) {
this.debug&&console.log('准备上传,option='+JSON.stringify(this.option));
item.type = 'loading';
delete item.responseText;
return new Promise((resolve,reject)=>{
let {url,name,method='POST',header={},formData={}} = this.option;
let form = new FormData();
for (let keys in formData) {
form.append(keys, formData[keys])
}
form.append(name, item.file);
let xmlRequest = new XMLHttpRequest();
xmlRequest.open(method, url, true);
for (let keys in header) {
xmlRequest.setRequestHeader(keys, header[keys])
}
xmlRequest.upload.addEventListener(
'progress',
event => {
if (event.lengthComputable) {
let progress = Math.ceil((event.loaded * 100) / event.total)
if (progress <= 100) {
item.progress = progress;
this.changeFilesItem(item);
}
}
},
false
);
xmlRequest.ontimeout = () => {
console.error('请求超时')
item.type = 'fail';
this.changeFilesItem(item,true);
return resolve(false);
}
xmlRequest.onreadystatechange = ev => {
if (xmlRequest.readyState == 4) {
if (xmlRequest.status == 200) {
this.debug && console.log('上传完成:' + xmlRequest.responseText)
item['responseText'] = xmlRequest.responseText;
item.type = 'success';
this.changeFilesItem(item,true);
return resolve(true);
} else if (xmlRequest.status == 0) {
console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配服务端已正确开启跨域并且nginx未拦截阻止请求')
}
console.error('--ERROR--status = ' + xmlRequest.status)
item.type = 'fail';
this.changeFilesItem(item,true);
return resolve(false);
}
}
xmlRequest.send(form)
});
}
}
});
</script>
</body>
</html>

View File

@ -1,79 +0,0 @@
{
"id": "yt-upload",
"displayName": "全文件上传选择",
"version": "2.3.1",
"description": "文件选择上传-支持APP-H5网页-微信小程序",
"keywords": [
"附件",
"file",
"upload",
"上传",
"文件管理器"
],
"repository": "",
"engines": {
"HBuilderX": "^3.3.7"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"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",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -1,347 +0,0 @@
# yt-upload
使用插件有任何问题欢迎加入QQ讨论群
- 作者QQ群906392632未满
若能帮到你请高抬贵手点亮5颗星~
------
## 重要提示
### 组件是窗口级滚动不要在scroll-view内使用
### 组件是窗口级滚动不要在scroll-view内使用
### 组件是窗口级滚动不要在scroll-view内使用
### 控件的height高度应与slot自定义内容高度保持一致
### nvue窗口只能使用固定模式position=absolute
### show() 当DOM重排后在this.$nextTick内调用show(),控件定位会更加准确
### hide() APP端webview层级比view高如不希望触发点击时应调用hide隐藏控件反之调用show
### 若iOS端跨域服务端同学实在配置不好可把hybrid下html目录放到服务器去同源则不存在跨域问题。
### 小程序端因hybrid不能使用本地HTML所以插件提供的是从微信消息列表拉取文件并选择请知悉。
### file对象不是object对象也不能转json字符串如果你打印file那就是{}可以打印file.name和file.size。
### 返回的path是个blob类型仅供用于文件回显插件已内置好上传函数调用上传会自动提交待上传文件若非要自己拿path去搞上传那你自己处理。
------
## 使用说明
| 属性 | 是否必填 | 值类型 | 默认值 | 说明 |
| --------- | -------- | -----: | --: | :------------:|
| width | 否 | String |100% | 容器宽度 |
| height | 是 | String |80rpx | 容器高度 |
| debug | 否 | Boolean |false | 打印调试日志 |
| option | 是 | Object |- | [文件上传接口相关参数](#p1)|
| instantly | 否 | Boolean |false | true=自动上传 |
| count | 否 | Number |10 | 附件选择上限(个)|
| size | 否 | Number |10 | 附件大小上限(M)|
| wxFileType | 否 | String |all | 微信小程序文件选择器格式限制(all=从所有文件选择video=只能选择视频文件image=只能选择图片文件file=可以选择除了图片和视频之外的其它的文件)|
| accept | 否 | String |- | 文件选择器input file格式限制(部分机型不兼容建议使用formats)|
| formats | 否 | String |- | 限制允许上传的格式,空串=不限制默认为空多个格式以逗号隔开例如png,jpg,pdf|
| childId | 否 | String |ytUpload| 控件的id(仅APP有效应用内每个控件命名一个唯一Id不同窗口也不要同名Id)|
| position | 否 | String |static | 控件的定位模式(static=控件随页面滚动;absolute=控件在页面中绝对定位,不随窗口内容滚动)|
| top,left,right,bottom | 否 | [Number,String] |0 | 设置控件绝对位置position=absolute时有效|
| @change | 否 | Function |Map | 选择文件触发返回所有已选择文件Map集合|
| @progress | 否 | Function |Object | 上传过程中发生状态变化的文件对象需通过set更新至Map集合|
| @uploadEnd| 否 | Function |Object | 上传结束回调返回参数与progress一致|
## <a id="p1">option说明</a>
|参数 | 是否必填 | 说明|
|---- | ---- | :--: |
|url | 是 | 上传接口地址|
|name| 否 |上传接口文件key默认为file|
|header| 否 |上传接口请求头|
|formData| 否 |上传接口额外参数|
## ref调用
|作用 | 方法名| 传入参数| 说明|
|---- | --------- | -------- | :--: |
|显示控件| show|-| 控件显示状态下可触发点击|
|隐藏控件| hide|-| 控件隐藏状态下不触发点击|
|动态设置文件列表| setFiles|[Array,Map] files| 传入格式请与组件选择返回格式保持一致且name为必须属性可查看下方演示|
|动态更新参数| setData|[String] name,[any] value| name支持a.b 和 a[b],可查看下方演示|
|移除选择的文件| clear|[String] name| 不传参数清空所有文件传入文件name时删除该name的文件|
|手动上传| upload|[String] name| 不传参数默认依次上传所有type=waiting的文件传入文件name时不关心type是否为waiting单独上传指定name的文件|
## progress返回对象字段说明
|字段 | 说明|
|---- | :--: |
|file | 文件对象|
|name |文件名称|
|size |文件大小|
|type |文件上传状态waiting等待上传、loading上传中、success成功 、fail失败|
|responseText|上传成功后服务端返回数据(仅type为success时存在)|
## 以下演示为vue窗口使用方式nvue使用区别是必须传入控件绝对位置如topbottomleftright且position只能为absolute如不清楚可点击右侧导入示例项目有详细演示代码。
### vue:
``` javascript
<yt-upload
ref="ytUpload"
childId="upload1"
:width="width"
:height="height"
:option="option"
:size="size"
:formats="formats"
:debug="debug"
:instantly="instantly"
@progress="onprogress"
@change="onChange">
<view class="btn" :style="{width: width,height: height}">选择附件</view>
</yt-upload>
<view class="padding">
<view>已选择文件列表:</view>
<!-- #ifndef MP-WEIXIN -->
<view v-for="(item,index) in files.values()" :key="index">
<image style="width: 100rpx;height: 100rpx;" :src="item.path" mode="widthFix"></image>
<text>提示【path主要用于图片视频类文件回显他用自行处理】{{item.path}}</text>
<text>{{item.name}}</text>
<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
<text style="margin-left: 10rpx;" v-if="item.responseText">服务端返回演示:{{item.responseText}}</text>
<text @click="resetUpload(item.name)" v-if="item.type=='fail'" style="margin-left: 10rpx;padding: 0 10rpx;border: 1rpx solid #007AFF;">重新上传</text>
<text @click="clear(item.name)" style="margin-left: 10rpx;padding: 0 10rpx;border: 1rpx solid #007AFF;">删除</text>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view v-for="(item,index) in wxFiles" :key="index">
<text>{{item.name}}</text>
<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
<view>
<button @click="resetUpload(item.name)">重新上传</button>
<button @click="clear(item.name)">删除</button>
</view>
</view>
<!-- #endif -->
</view>
```
---
* 函数说明
``` javascript
export default {
data() {
return {
// 上传接口参数
option: {
// 上传服务器地址,需要替换为你的接口地址
url: 'http://xxxxxxx/dropbox/document/upload', // 该地址非真实路径,需替换为你项目自己的接口地址
// 上传附件的key
name: 'file',
// 根据你接口需求自定义请求头,默认不要写content-type,让浏览器自适配
header: {
// 示例参数可删除
'Authorization': 'bearer eyJhbGciOiJSUzI1NiIsI',
'uid': '99',
'client': 'app',
'accountid': 'DP',
},
// 根据你接口需求自定义body参数
formData: {
// 'orderId': 1000
}
},
// 选择文件后是否立即自动上传true=选择后立即上传
instantly: true,
// 必传宽高且宽高应与slot宽高保持一致
width: '180rpx',
height: '180rpx',
// 限制允许上传的格式,空串=不限制,默认为空
formats: '',
// 文件上传大小限制
size: 30,
// 文件数量限制
count: 2,
// 文件回显列表
files: new Map(),
// 微信小程序Map对象for循环不显示所以转成普通数组不要问为什么我也不知道
wxFiles: [],
// 是否打印日志
debug: true,
// 演示用
tabIndex: 0,
list:[],
}
},
onReady() {
setTimeout(()=>{
console.log('----演示动态更新参数-----');
this.$refs['ytUpload'+this.tabIndex].setData('formData.orderId','动态设置的参数');
console.log('以下注释内容为-动态更新参数更多演示,放开后可查看演示效果');
// 修改option对象的name属性
// this.$refs.ytUpload.setData('name','myFile');
// 修改option对象的formData内的属性
// this.$refs.ytUpload.setData('formData.appid','1111');
// 替换option对象的formData
// this.$refs.ytUpload.setData('formData',{appid:'222'});
// option对象的formData新增属性
// this.$refs.ytUpload.setData('formData.newkey','新插入到formData的属性');
// ---------演示初始化值,用于已提交后再次编辑时需带入已上传文件-------
// 方式1=传入数组
// let files1 = [{name: '1.png'},{name: '2.png',}];
// 方式2=传入Map对象
// let files2 = new Map();
// files2.set('1.png',{name: '1.png'})
// 此处调用setFiles设置初始files
// this.$refs.ytUpload.setFiles(files1);
// 初始化tab
this.onTab(0);
},2000)
},
methods: {
// 某文件上传结束回调(成功失败都回调)
onuploadEnd(item) {
console.log(`${item.name}已上传结束,上传状态=${item.type}`);
// 更新当前窗口状态变化的文件
this.files.set(item.name,item);
// ---可删除--演示上传完成后取服务端数据
if (item['responseText']) {
console.log('演示服务器返回的字符串JSON转Object对象');
this.files.get(item.name).responseText = JSON.parse(item.responseText);
}
// 微信小程序Map对象for循环不显示所以转成普通数组
// 如果你用不惯Map对象也可以像这样转普通数组组件使用Map主要是避免反复文件去重操作
// #ifdef MP-WEIXIN
this.wxFiles = [...this.files.values()];
// #endif
// 强制更新视图
this.$forceUpdate();
// ---可删除--演示判断是否所有文件均已上传成功
let isAll = [...this.files.values()].find(item=>item.type!=='success');
if (!isAll) {
console.log('已全部上传完毕');
}
else {
console.log(isAll.name+'待上传');
}
},
// 上传进度回调
onprogress(item) {
// 更新当前状态变化的文件
this.files.set(item.name,item);
console.log('打印对象',JSON.stringify(this.files.get(item.name)));
// 微信小程序Map对象for循环不显示所以转成普通数组不要问为什么我也不知道
// #ifdef MP-WEIXIN
this.wxFiles = [...this.files.values()];
// #endif
// 强制更新视图
this.$forceUpdate();
},
// 文件选择回调
onChange(files) {
console.log('当前选择的文件列表:',JSON.stringify([...files.values()]));
// 更新选择的文件
this.files = files;
// 强制更新视图
this.$forceUpdate();
// 微信小程序Map对象for循环不显示所以转成普通数组不要问为什么我也不知道
// #ifdef MP-WEIXIN
this.wxFiles = [...this.files.values()];
// #endif
// ---可删除--演示重新定位覆盖层控件
this.$nextTick(()=>{
console.log('演示重新定位');
this.$refs.ytUpload0.show();
this.$refs.ytUpload1.show();
this.$refs.ytUpload2.show();
});
},
// 手动上传
upload() {
// name=指定文件名不指定则上传所有type等于waiting和fail的文件
this.$refs['ytUpload'+this.tabIndex].upload();
},
// 指定上传某个文件
resetUpload(name) {
this.$refs['ytUpload'+this.tabIndex].upload(name);
},
// 移除某个文件
clear(name) {
// name=指定文件名不传name默认移除所有文件
this.$refs['ytUpload'+this.tabIndex].clear(name);
},
/**
* ---可删除--演示在组件上方添加新内容DOM变化
* DOM重排演示重排后组件内部updated默认会触发show方法,若特殊情况未能触发updated也可以手动调用一次show()
* 什么是DOM重排自行百度去
*/
add() {
this.list.push('DOM重排测试');
},
/**
* ---可删除--演示Tab切换时覆盖层是否能被点击
* APP端因为是webview层级比view高此时若不希望点击触发选择文件需要手动调用hide()
* 手动调用hide后需要调用show()才能恢复覆盖层的点击
*/
onTab(tabIndex) {
this.$refs.ytUpload0.hide();
this.$refs.ytUpload1.hide();
this.tabIndex = tabIndex;
this.$nextTick(()=>{
this.$refs['ytUpload'+this.tabIndex].show();
})
},
/**
* 打开nvue窗口查看非跟随窗口滚动效果
*/
open() {
uni.navigateTo({
url: '/pages/nvue-demo/nvue-demo'
});
}
}
}
```
| `QQ交流群(906392632)` |
| ----------------------|
|![QQ交流群](https://7072-prod-4gapv4gl33a8a0ff-1305990777.tcb.qcloud.la/%E9%87%8D%E8%A6%81%E5%9B%BE%E7%89%87%E5%AD%98%E5%82%A8/Snipaste_2023-11-06_16-50-56.png?sign=ed27f09cfeabb33e24835fecdd4108db&t=1699260686)|
| QQ群号906392632 |
## 温馨提示
* 文件上传
0. 如说明表达还不够清楚,不清楚怎么使用可导入完整示例项目运行体验和查看
1. APP端请优先联调Android,上传成功后再运行iOS端如iOS返回status=0则需要后端开启允许跨域
2. header的Content-Type类型需要与服务端要求一致否则收不到附件服务端若没有明文规定则可不写使用默认匹配
3. 服务端不清楚怎么配置跨域可加群咨询,具体百度~
4. 若能帮到你还请点亮5颗小星星以作鼓励哈~
5. 若能帮到你还请点亮5颗小星星以作鼓励哈~
6. 若能帮到你还请点亮5颗小星星以作鼓励哈~