Explorar o código

上传附件组件

sunlupeng hai 1 ano
pai
achega
0560e47b9d

+ 10 - 0
api/system/user.js

@@ -40,3 +40,13 @@ export function uploadAvatar(data) {
     filePath: data.filePath
   })
 }
+// 上传文件
+export function uploadFile(data) {
+  return upload({
+    url: '/infra/file/uploaData',
+    method: 'PUT',
+    name: data.name,
+    filePath: data.filePath
+  })
+}
+

+ 2 - 2
config.js

@@ -1,7 +1,7 @@
 // 应用全局配置
 module.exports = {
-  // baseUrl: 'http://api-dashboard.yudao.iocoder.cn',
-  baseUrl: 'https://isaas.dgtiscloud.com',
+  baseUrl: 'http://47.103.79.143:48081',
+  // baseUrl: 'https://isaas.dgtiscloud.com',
   baseApi: '/admin-api',
   // 应用信息
   appInfo: {

+ 331 - 0
pages/oa/universal/formCreate - 副本.vue

@@ -0,0 +1,331 @@
+<template>
+	<view class="container">
+		<uni-section title="">
+			<view class="example">
+				<!-- 基础用法,不包含校验规则 -->
+				<uni-forms ref="baseForm" :rules="rules" :model="baseFormData" labelWidth="80px" :label-position="alignment">
+					<uni-forms-item label="姓名" required>
+						<uni-easyinput v-model="baseFormData.name" placeholder="请输入姓名" disabled />
+					</uni-forms-item>
+					<uni-forms-item label="年龄" required>
+						<uni-easyinput v-model="baseFormData.age" placeholder="请输入年龄" />
+					</uni-forms-item>
+					<uni-forms-item label="性别" required>
+						<uni-data-checkbox v-model="baseFormData.sex" :localdata="sexs" />
+					</uni-forms-item>
+					<uni-forms-item label="兴趣爱好" required>
+						<uni-data-checkbox v-model="baseFormData.hobby" multiple :localdata="hobbys" />
+					</uni-forms-item>
+					<uni-forms-item label="自我介绍">
+						<uni-easyinput type="textarea" v-model="baseFormData.introduction" placeholder="请输入自我介绍" />
+					</uni-forms-item>
+					<uni-forms-item label="日期时间">
+						<uni-datetime-picker type="datetime" return-type="timestamp"
+							v-model="baseFormData.datetimesingle" />
+					</uni-forms-item>
+					<uni-forms-item label="选择城市">
+						<uni-data-picker v-model="baseFormData.city" :localdata="cityData" popup-title="选择城市">
+						</uni-data-picker>
+					</uni-forms-item>
+
+					<uni-forms-item label="选择技能">
+						<uni-data-select v-model="baseFormData.skills" :localdata="skillsRange" >
+						</uni-data-select>
+					</uni-forms-item>
+				</uni-forms>
+			</view>
+		</uni-section>
+
+		<uni-section title="表单校验" type="line">
+			<view class="example">
+				<!-- 基础表单校验 -->
+				<uni-forms ref="valiForm" :rules="rules" :model="valiFormData" labelWidth="80px" :label-position="alignment">
+					<uni-forms-item label="姓名" required name="name">
+						<uni-easyinput v-model="valiFormData.name" placeholder="请输入姓名" />
+					</uni-forms-item>
+					<uni-forms-item label="年龄" required name="age">
+						<uni-easyinput v-model="valiFormData.age" placeholder="请输入年龄" />
+					</uni-forms-item>
+					<uni-forms-item label="自我介绍">
+						<uni-easyinput type="textarea" v-model="valiFormData.introduction" placeholder="请输入自我介绍" />
+					</uni-forms-item>
+				</uni-forms>
+				<button type="primary" @click="submit('valiForm')">提交</button>
+			</view>
+		</uni-section>
+
+		<uni-section title="自定义校验规则" type="line">
+			<view class="example">
+				<!-- 自定义表单校验 -->
+				<uni-forms ref="customForm" :rules="customRules" labelWidth="80px" :modelValue="customFormData" :label-position="alignment">
+					<uni-forms-item label="姓名" required name="name">
+						<uni-easyinput v-model="customFormData.name" placeholder="请输入姓名" />
+					</uni-forms-item>
+					<uni-forms-item label="年龄" required name="age">
+						<uni-easyinput v-model="customFormData.age" placeholder="请输入年龄" />
+					</uni-forms-item>
+					<uni-forms-item label="兴趣爱好" required name="hobby">
+						<uni-data-checkbox v-model="customFormData.hobby" multiple :localdata="hobbys" />
+					</uni-forms-item>
+				</uni-forms>
+				<button type="primary" @click="submit('customForm')">提交</button>
+			</view>
+		</uni-section>
+
+
+		<uni-section title="动态表单" type="line">
+			<view class="example">
+				<!-- 动态表单校验 -->
+				<uni-forms ref="dynamicForm" :rules="dynamicRules" :model="dynamicFormData" labelWidth="80px" :label-position="alignment">
+					<uni-forms-item label="邮箱" required name="email">
+						<uni-easyinput v-model="dynamicFormData.email" placeholder="请输入姓名" />
+					</uni-forms-item>
+					<uni-forms-item v-for="(item,index) in dynamicFormData.domains" :key="item.id"
+						:label="item.label+' '+index" required :rules="item.rules" :name="['domains',index,'value']">
+						<view class="form-item">
+							<uni-easyinput v-model="dynamicFormData.domains[index].value" placeholder="请输入域名" />
+							<button class="button" size="mini" type="default" @click="del(item.id)">删除</button>
+						</view>
+					</uni-forms-item>
+				</uni-forms>
+				<view class="button-group">
+					<button type="primary" size="mini" @click="add">新增域名</button>
+					<button type="primary" size="mini" @click="submit('dynamicForm')">提交</button>
+				</view>
+			</view>
+		</uni-section>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				// 基础表单数据
+				baseFormData: {
+					name: '',
+					age: '',
+					introduction: '',
+					sex: 2,
+					hobby: [5],
+					datetimesingle: 1627529992399,
+					city: '',
+					skills: 0
+				},
+				// 城市数据
+				cityData: [{
+					text: "北京",
+					value: "10001",
+				}, {
+					text: "上海",
+					value: "10002",
+				}, {
+					text: "深圳",
+					value: "10004",
+				}],
+				skillsRange: [{
+						value: 0,
+						text: "编程"
+					},
+					{
+						value: 1,
+						text: "绘画"
+					},
+					{
+						value: 2,
+						text: "运动"
+					},
+				],
+				// 表单数据
+				alignmentFormData: {
+					name: '',
+					age: '',
+				},
+				// 单选数据源
+				sexs: [{
+					text: '男',
+					value: 0
+				}, {
+					text: '女',
+					value: 1
+				}, {
+					text: '保密',
+					value: 2
+				}],
+				// 多选数据源
+				hobbys: [{
+					text: '跑步',
+					value: 0
+				}, {
+					text: '游泳',
+					value: 1
+				}, {
+					text: '绘画',
+					value: 2
+				}, {
+					text: '足球',
+					value: 3
+				}, {
+					text: '篮球',
+					value: 4
+				}, {
+					text: '其他',
+					value: 5
+				}],
+				// 分段器数据
+				current: 0,
+				items: ['左对齐', '顶部对齐'],
+				// 校验表单数据
+				valiFormData: {
+					name: '',
+					age: '',
+					introduction: '',
+				},
+				// 校验规则
+				rules: {
+					name: {
+						rules: [{
+							required: true,
+							errorMessage: '姓名不能为空'
+						}]
+					},
+					age: {
+						rules: [{
+							required: true,
+							errorMessage: '年龄不能为空'
+						}, {
+							format: 'number',
+							errorMessage: '年龄只能输入数字'
+						}]
+					}
+				},
+				// 自定义表单数据
+				customFormData: {
+					name: '',
+					age: '',
+					hobby: []
+				},
+				// 自定义表单校验规则
+				customRules: {
+					name: {
+						rules: [{
+							required: true,
+							errorMessage: '姓名不能为空'
+						}]
+					},
+					age: {
+						rules: [{
+							required: true,
+							errorMessage: '年龄不能为空'
+						}]
+					},
+					hobby: {
+						rules: [{
+								format: 'array'
+							},
+							{
+								validateFunction: function(rule, value, data, callback) {
+									if (value.length < 2) {
+										callback('请至少勾选两个兴趣爱好')
+									}
+									return true
+								}
+							}
+						]
+					}
+
+				},
+				dynamicFormData: {
+					email: '',
+					domains: []
+				},
+				dynamicLists: [],
+				dynamicRules: {
+					email: {
+						rules: [{
+							required: true,
+							errorMessage: '域名不能为空'
+						}, {
+							format: 'email',
+							errorMessage: '域名格式错误'
+						}]
+					}
+				}
+			}
+		},
+		computed: {
+			// 处理表单排列切换
+			alignment() {
+				return 'top'
+			}
+		},
+		onLoad() {},
+		onReady() {
+			// 设置自定义表单校验规则,必须在节点渲染完毕后执行
+			this.$refs.customForm.setRules(this.customRules)
+		},
+		methods: {
+			onClickItem(e) {
+				console.log(e);
+				this.current = e.currentIndex
+			},
+			add() {
+				this.dynamicFormData.domains.push({
+					label: '域名',
+					value: '',
+					rules: [{
+						'required': true,
+						errorMessage: '域名项必填'
+					}],
+					id: Date.now()
+				})
+			},
+			del(id) {
+				let index = this.dynamicLists.findIndex(v => v.id === id)
+				this.dynamicLists.splice(index, 1)
+			},
+			submit(ref) {
+				console.log(this.baseFormData);
+				this.$refs[ref].validate().then(res => {
+					console.log('success', res);
+					uni.showToast({
+						title: `校验通过`
+					})
+				}).catch(err => {
+					console.log('err', err);
+				})
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	.example {
+		padding: 15px;
+		background-color: #fff;
+	}
+
+	.segmented-control {
+		margin-bottom: 15px;
+	}
+
+	.button-group {
+		margin-top: 15px;
+		display: flex;
+		justify-content: space-around;
+	}
+
+	.form-item {
+		display: flex;
+		align-items: center;
+		flex: 1;
+	}
+
+	.button {
+		display: flex;
+		align-items: center;
+		height: 35px;
+		line-height: 35px;
+		margin-left: 10px;
+	}
+</style>

+ 184 - 283
pages/oa/universal/formCreate.vue

@@ -1,315 +1,128 @@
 <template>
 	<view class="container">
-		<uni-card :is-shadow="false" is-full>
-			<text class="uni-h6">uni-forms 组件一般由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据。</text>
-		</uni-card>
-		<uni-section title="基本用法" type="line">
-			<view class="example">
-				<!-- 基础用法,不包含校验规则 -->
-				<uni-forms ref="baseForm" :model="baseFormData" labelWidth="80px">
-					<uni-forms-item label="姓名" required>
-						<uni-easyinput v-model="baseFormData.name" placeholder="请输入姓名" />
-					</uni-forms-item>
-					<uni-forms-item label="年龄" required>
-						<uni-easyinput v-model="baseFormData.age" placeholder="请输入年龄" />
-					</uni-forms-item>
-					<uni-forms-item label="性别" required>
-						<uni-data-checkbox v-model="baseFormData.sex" :localdata="sexs" />
-					</uni-forms-item>
-					<uni-forms-item label="兴趣爱好" required>
-						<uni-data-checkbox v-model="baseFormData.hobby" multiple :localdata="hobbys" />
-					</uni-forms-item>
-					<uni-forms-item label="自我介绍">
-						<uni-easyinput type="textarea" v-model="baseFormData.introduction" placeholder="请输入自我介绍" />
-					</uni-forms-item>
-					<uni-forms-item label="日期时间">
-						<uni-datetime-picker type="datetime" return-type="timestamp"
-							v-model="baseFormData.datetimesingle" />
-					</uni-forms-item>
-					<uni-forms-item label="选择城市">
-						<uni-data-picker v-model="baseFormData.city" :localdata="cityData" popup-title="选择城市">
-						</uni-data-picker>
-					</uni-forms-item>
-
-					<uni-forms-item label="选择技能">
-						<uni-data-select v-model="baseFormData.skills" :localdata="skillsRange" >
-						</uni-data-select>
-					</uni-forms-item>
-				</uni-forms>
-			</view>
-		</uni-section>
-
-		<uni-section title="对齐方式" type="line">
-			<view class="example">
-				<view class="segmented-control">
-					<uni-segmented-control :current="current" :values="items" @clickItem="onClickItem"
-						styleType="button">
-					</uni-segmented-control>
+		<uni-forms ref="form" :rules="rules" :model="form" labelWidth="80px" :label-position="alignment">
+			<uni-forms-item label="申请人">
+				<uni-easyinput v-model="form.employeeName" disabled />
+			</uni-forms-item>
+			<uni-forms-item label="部门">
+				<uni-easyinput v-model="form.deptName" disabled />
+			</uni-forms-item>
+			<uni-forms-item label="职位">
+				<uni-easyinput v-model="form.position" disabled />
+			</uni-forms-item>
+			<uni-forms-item label="手机号">
+				<uni-easyinput v-model="form.employeePhone" disabled />
+			</uni-forms-item>
+			<uni-forms-item label="事项标题" required name="title">
+				<uni-easyinput maxlength="20" v-model="form.title" placeholder="请输入申请的简要标题" />
+			</uni-forms-item>
+			<uni-forms-item label="详细描述" required name="description">
+				<uni-easyinput maxlength="200" type="textarea" v-model="form.description" placeholder="请输入详细描述" />
+			</uni-forms-item>
+			<uni-forms-item label="附件">
+				<view class="upload-wrap">
+					<button type="primary" size="mini" @click="handleUploadClick">点击上传</button>
+					<xe-upload ref="XeUpload" :options="uploadOptions" @callback="handleUploadCallback"></xe-upload>
+					<view class="mgb-16 file-wrap" v-for="(item, index) in fileList" :key="index">
+					  <view class="btn-click file-line" @click="handlePreview(item)">
+					    <view class="file-info">
+					      <image :src="icons.file" mode="aspectFill" class="file-icon" />
+					      <text class="file-name">{{ item.name || title[type] }}</text>
+					    </view>
+					    <image :src="icons.close" mode="aspectFill" class="file-icon"
+					      @click.stop="handleDeleteFile(index)" />
+					  </view>
+					</view>
 				</view>
-				<!-- 展示不同的排列方式 -->
-				<uni-forms ref="baseForm" :modelValue="alignmentFormData" :label-position="alignment">
-					<uni-forms-item label="姓名" required>
-						<uni-easyinput v-model="baseFormData.name" placeholder="请输入姓名" />
-					</uni-forms-item>
-					<uni-forms-item label="年龄" required>
-						<uni-easyinput v-model="baseFormData.age" placeholder="请输入年龄" />
-					</uni-forms-item>
-				</uni-forms>
-			</view>
-		</uni-section>
-
-		<uni-section title="表单校验" type="line">
-			<view class="example">
-				<!-- 基础表单校验 -->
-				<uni-forms ref="valiForm" :rules="rules" :model="valiFormData" labelWidth="80px">
-					<uni-forms-item label="姓名" required name="name">
-						<uni-easyinput v-model="valiFormData.name" placeholder="请输入姓名" />
-					</uni-forms-item>
-					<uni-forms-item label="年龄" required name="age">
-						<uni-easyinput v-model="valiFormData.age" placeholder="请输入年龄" />
-					</uni-forms-item>
-					<uni-forms-item label="自我介绍">
-						<uni-easyinput type="textarea" v-model="valiFormData.introduction" placeholder="请输入自我介绍" />
-					</uni-forms-item>
-				</uni-forms>
-				<button type="primary" @click="submit('valiForm')">提交</button>
-			</view>
-		</uni-section>
-
-		<uni-section title="自定义校验规则" type="line">
-			<view class="example">
-				<!-- 自定义表单校验 -->
-				<uni-forms ref="customForm" :rules="customRules" labelWidth="80px" :modelValue="customFormData">
-					<uni-forms-item label="姓名" required name="name">
-						<uni-easyinput v-model="customFormData.name" placeholder="请输入姓名" />
-					</uni-forms-item>
-					<uni-forms-item label="年龄" required name="age">
-						<uni-easyinput v-model="customFormData.age" placeholder="请输入年龄" />
-					</uni-forms-item>
-					<uni-forms-item label="兴趣爱好" required name="hobby">
-						<uni-data-checkbox v-model="customFormData.hobby" multiple :localdata="hobbys" />
-					</uni-forms-item>
-				</uni-forms>
-				<button type="primary" @click="submit('customForm')">提交</button>
-			</view>
-		</uni-section>
-
-
-		<uni-section title="动态表单" type="line">
-			<view class="example">
-				<!-- 动态表单校验 -->
-				<uni-forms ref="dynamicForm" :rules="dynamicRules" :model="dynamicFormData" labelWidth="80px">
-					<uni-forms-item label="邮箱" required name="email">
-						<uni-easyinput v-model="dynamicFormData.email" placeholder="请输入姓名" />
-					</uni-forms-item>
-					<uni-forms-item v-for="(item,index) in dynamicFormData.domains" :key="item.id"
-						:label="item.label+' '+index" required :rules="item.rules" :name="['domains',index,'value']">
-						<view class="form-item">
-							<uni-easyinput v-model="dynamicFormData.domains[index].value" placeholder="请输入域名" />
-							<button class="button" size="mini" type="default" @click="del(item.id)">删除</button>
-						</view>
-					</uni-forms-item>
-				</uni-forms>
-				<view class="button-group">
-					<button type="primary" size="mini" @click="add">新增域名</button>
-					<button type="primary" size="mini" @click="submit('dynamicForm')">提交</button>
-				</view>
-			</view>
-		</uni-section>
+				
+			</uni-forms-item>
+			<uni-forms-item label="备注">
+				<uni-easyinput maxlength="200" type="textarea" v-model="form.remarks" placeholder="请输入备注" />
+			</uni-forms-item>
+		</uni-forms>
+		<view class="button-group">
+			<button type="primary" size="mini" @click="submit('form')">提交</button>
+		</view>
 	</view>
 </template>
 
 <script>
+  	import { uploadFile } from "@/api/system/user"
 	export default {
 		data() {
 			return {
-				// 基础表单数据
-				baseFormData: {
-					name: '',
-					age: '',
-					introduction: '',
-					sex: 2,
-					hobby: [5],
-					datetimesingle: 1627529992399,
-					city: '',
-					skills: 0
+				alignment:'top',
+				uploadOptions: {},
+				fileList: [],
+				icons: {
+				  close: '/static/icon_close.png',
+				  file: '/static/icon_file.png',
 				},
-				// 城市数据
-				cityData: [{
-					text: "北京",
-					value: "10001",
-				}, {
-					text: "上海",
-					value: "10002",
-				}, {
-					text: "深圳",
-					value: "10004",
-				}],
-				skillsRange: [{
-						value: 0,
-						text: "编程"
-					},
-					{
-						value: 1,
-						text: "绘画"
-					},
-					{
-						value: 2,
-						text: "运动"
-					},
-				],
 				// 表单数据
-				alignmentFormData: {
-					name: '',
-					age: '',
-				},
-				// 单选数据源
-				sexs: [{
-					text: '男',
-					value: 0
-				}, {
-					text: '女',
-					value: 1
-				}, {
-					text: '保密',
-					value: 2
-				}],
-				// 多选数据源
-				hobbys: [{
-					text: '跑步',
-					value: 0
-				}, {
-					text: '游泳',
-					value: 1
-				}, {
-					text: '绘画',
-					value: 2
-				}, {
-					text: '足球',
-					value: 3
-				}, {
-					text: '篮球',
-					value: 4
-				}, {
-					text: '其他',
-					value: 5
-				}],
-				// 分段器数据
-				current: 0,
-				items: ['左对齐', '顶部对齐'],
-				// 校验表单数据
-				valiFormData: {
-					name: '',
-					age: '',
-					introduction: '',
+				form: {
+					employeeName: undefined,
+					deptName: undefined,
+					position: undefined,
+					employeePhone: undefined,
+					title: undefined,
+					fileIdList: undefined,
+					remarks: undefined,
+					peopleList: '',
 				},
 				// 校验规则
 				rules: {
-					name: {
+					title: {
 						rules: [{
 							required: true,
-							errorMessage: '姓名不能为空'
-						}]
+							errorMessage: '请输入申请的简要标题'
+						}, ]
 					},
-					age: {
+					description: {
 						rules: [{
 							required: true,
-							errorMessage: '年龄不能为空'
-						}, {
-							format: 'number',
-							errorMessage: '年龄只能输入数字'
-						}]
+							errorMessage: '请输入详细描述'
+						}, ]
 					}
 				},
-				// 自定义表单数据
-				customFormData: {
-					name: '',
-					age: '',
-					hobby: []
-				},
-				// 自定义表单校验规则
-				customRules: {
-					name: {
-						rules: [{
-							required: true,
-							errorMessage: '姓名不能为空'
-						}]
-					},
-					age: {
-						rules: [{
-							required: true,
-							errorMessage: '年龄不能为空'
-						}]
-					},
-					hobby: {
-						rules: [{
-								format: 'array'
-							},
-							{
-								validateFunction: function(rule, value, data, callback) {
-									if (value.length < 2) {
-										callback('请至少勾选两个兴趣爱好')
-									}
-									return true
-								}
-							}
-						]
-					}
-
-				},
-				dynamicFormData: {
-					email: '',
-					domains: []
-				},
-				dynamicLists: [],
-				dynamicRules: {
-					email: {
-						rules: [{
-							required: true,
-							errorMessage: '域名不能为空'
-						}, {
-							format: 'email',
-							errorMessage: '域名格式错误'
-						}]
-					}
-				}
-			}
-		},
-		computed: {
-			// 处理表单排列切换
-			alignment() {
-				if (this.current === 0) return 'left'
-				if (this.current === 1) return 'top'
-				return 'left'
 			}
 		},
 		onLoad() {},
-		onReady() {
-			// 设置自定义表单校验规则,必须在节点渲染完毕后执行
-			this.$refs.customForm.setRules(this.customRules)
-		},
 		methods: {
-			onClickItem(e) {
-				console.log(e);
-				this.current = e.currentIndex
+			handleUploadClick() {
+			  this.$refs.XeUpload.upload('file');
 			},
-			add() {
-				this.dynamicFormData.domains.push({
-					label: '域名',
-					value: '',
-					rules: [{
-						'required': true,
-						errorMessage: '域名项必填'
-					}],
-					id: Date.now()
-				})
+			handleUploadCallback(e) {
+			  console.log('UploadCallback', e);
+			  let data = { filePath: e.data[0].tempFilePath}
+			  uploadFile(data).then(response => {
+				const tmpFiles = ([response.data] || []).map(({ id, url, name, type }) => {
+				  return {
+					id,
+				    url,
+				    name,
+				    type,
+				  };
+				});
+				this.fileList.push(...tmpFiles);
+			  })
+			},
+			// 预览
+			handlePreview(val) {
+			  console.log('PreviewFile', val);
+			  if (val.type == 'image/png' || val.type == 'image/jpg') {
+			    return uni.previewImage({
+			      current: 0,
+			      urls: [val.url],
+			    });
+			  }else{
+				  
+			  }
 			},
-			del(id) {
-				let index = this.dynamicLists.findIndex(v => v.id === id)
-				this.dynamicLists.splice(index, 1)
+			handleDeleteFile(index) {
+			  this.fileList.splice(index, 1);
 			},
 			submit(ref) {
-				console.log(this.baseFormData);
+				console.log(this.form);
 				this.$refs[ref].validate().then(res => {
 					console.log('success', res);
 					uni.showToast({
@@ -324,7 +137,7 @@
 </script>
 
 <style lang="scss">
-	.example {
+	.container {
 		padding: 15px;
 		background-color: #fff;
 	}
@@ -353,3 +166,91 @@
 		margin-left: 10px;
 	}
 </style>
+<style lang="scss" scoped>
+.btn-click {
+  transition: all 0.3s;
+  opacity: 1;
+}
+
+.btn-click:active {
+  opacity: 0.5;
+}
+
+.mgb-16 {
+  margin-bottom: 16rpx;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.upload-wrap {
+  width: 100%;
+  border-radius: 16rpx;
+  background: white;
+  // padding: 32rpx;
+
+  .upload-btn {
+    width: 100%;
+    height: 176rpx;
+    border: 2rpx dashed #AAAAAA;
+    background: #FAFAFA;
+    border-radius: 16rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+
+    .upload-icon {
+      width: 48rpx;
+      height: 48rpx;
+      margin-bottom: 8rpx;
+    }
+
+    .upload-text {
+      font-size: 26rpx;
+      color: #9E9E9E;
+      line-height: 40rpx;
+    }
+  }
+
+  .file-wrap {
+    .file-line {
+      width: 100%;
+      background: #F5F5F5;
+      border-radius: 8rpx;
+      padding: 16rpx;
+      font-size: 26rpx;
+      color: #1A1A1A;
+      line-height: 40rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+
+      .file-info {
+        width: 90%;
+        display: flex;
+        align-items: center;
+
+        .file-name {
+          max-width: 80%;
+          padding-left: 16rpx;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+
+      .file-icon {
+        width: 40rpx;
+        height: 40rpx;
+        flex-shrink: 0;
+      }
+
+      .file-empty {
+        color: #999999;
+      }
+    }
+  }
+}
+</style>

+ 1 - 1
pages/work/index.vue

@@ -70,7 +70,7 @@
 								title:v.name,
 								status: v.status,
 								time:v.startTime,
-								nickname:v.currentAuditUser.nickname
+								nickname:v.currentAuditUser?v.currentAuditUser.nickname:''
 							})
 						})
 						done(list);

BIN=BIN
static/icon_close.png


BIN=BIN
static/icon_file.png


+ 7 - 0
uni_modules/xe-upload/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-11-01)
+更换App端转换本地链接的方法;
+添加App端文件拓展名过滤;
+## 1.0.1(2023-09-04)
+优化部分逻辑
+## 1.0.0(2023-09-03)
+支持图片、视频选择上传;H5、微信小程序,App支持文件选择上传

+ 285 - 0
uni_modules/xe-upload/components/xe-upload/xe-upload.vue

@@ -0,0 +1,285 @@
+<!-- eslint-disable -->
+<template>
+  <view>
+    <!-- #ifdef APP-PLUS -->
+    <view class="xe-upload" v-html="renderInput" :props="mergeProps" :change:props="XeUpload.renderProps"></view>
+    <!-- #endif -->
+  </view>
+</template>
+
+<script>
+import {
+  chooseMedia,
+  chooseFile,
+  chooseMessageFile,
+  uploadFile,
+} from '../../tools/apis';
+import {
+  deepMerge,
+  awaitWrap,
+  base64ToPath,
+  isArray,
+} from '../../tools/tools';
+
+export default {
+  name: 'XeUpload',
+  props: {
+    options: {
+      default: () => ({}),
+      type: Object,
+    },
+  },
+  data() {
+    return {
+      id: 0, // APP上传框ID
+      renderInput: '', // APP上传框
+    };
+  },
+  computed: {
+    mergeOptions({ options = {} }) {
+      const tmpOptions = {
+        name: 'file',
+      };
+      return deepMerge(tmpOptions, options);
+    },
+    mergeProps({ id, renderInput, mergeOptions }) {
+      return {
+        id,
+        renderInput,
+        upload: mergeOptions,
+      };
+    },
+  },
+  methods: {
+    // 上传事件
+    async upload(type, config = {}) {
+      let tmpResult = [];
+      if (['image', 'video'].includes(type)) {
+        const [err, res] = await chooseMedia(type, config);
+        if (err) return this.handleError(err);
+        tmpResult = res?.tempFiles || [];
+      }
+      // H5 || APP-PLUS || MP-WEIXIN
+      if (['file'].includes(type)) {
+        let tmpFiles = {};
+        let tmpErr = null;
+        // #ifdef H5
+        [tmpErr, tmpFiles] = await chooseFile(config);
+        // #endif
+        // #ifdef MP-WEIXIN
+        [tmpErr, tmpFiles] = await chooseMessageFile(config);
+        // #endif
+        // #ifdef APP-PLUS
+        this.id = Math.floor(Math.random() * 100000000 + 1);
+        this.initInput(config.extension);
+        // #endif
+        if (tmpErr) return this.handleError(tmpErr);
+        tmpResult = tmpFiles?.tempFiles || [];
+      }
+      this.handleUpload(tmpResult);
+    },
+    // 初始化上传框
+    initInput(extension) {
+      const { id } = this;
+      let accept = extension;
+      if (isArray(extension)) {
+        accept = extension.join(',');
+      }
+      this.renderInput = `<input type="file" id="xe-upload-${id}" name="xe-upload" ${accept ? 'accept="' + accept + '"' : ''} />`;
+    },
+    // 文件上传(没有传入上传url时返回本地链接)
+    async handleUpload(files = []) {
+      if (files.filter((e) => Boolean(e)).length === 0) return;
+      const { mergeOptions } = this;
+      if (!mergeOptions.url) {
+        return this.handleEmits({
+          type: 'choose',
+          data: files,
+        });
+      }
+      const tmpUploads = files.map((e) =>
+        uploadFile(
+          {
+            ...mergeOptions,
+            filePath: e.tempFilePath,
+          },
+          e,
+        )
+      );
+      const [err, res] = await awaitWrap(Promise.all(tmpUploads));
+      if (err) return this.handleError(err);
+      this.handleEmits({
+        type: 'success',
+        data: res,
+      });
+    },
+    // 处理失败事件
+    handleError(error) {
+      this.handleEmits({
+        type: 'warning',
+        data: error,
+      });
+    },
+    // 处理响应事件
+    async handleEmits(e) {
+      // #ifdef APP-PLUS
+      if (e.type === 'choose') {
+        // 将base64转为本地链接
+        for (let i = 0; i < e.data.length; i += 1) {
+          const item = e.data[i];
+          if (!item.base64Url) {
+            continue;
+          }
+          const [parseError, parseUrl] = await awaitWrap(base64ToPath(item.base64Url, item.name));
+          if (!parseError) {
+            e.data[i].tempFilePath = parseUrl;
+          } else {
+            e.data[i].tempFilePath = item.base64Url;
+          }
+          delete e.data[i].base64Url;
+        }
+      }
+      // #endif
+      this.$emit('callback', e);
+    },
+  },
+};
+</script>
+
+<!-- #ifdef APP-PLUS -->
+<script module="XeUpload" lang="renderjs">
+import {
+  appUploadFile,
+} from '../../tools/apis';
+import {
+  awaitWrap,
+  fileToBase64,
+} from '../../tools/tools';
+
+export default {
+  data() {
+    return {
+      id: 0, // 上传框ID
+      uploadOptions: {}, // 上传配置
+    };
+  },
+  methods: {
+    // 处理 XeUpload 传入 renderjs 数据,以及调起上传框
+    renderProps(info) {
+      const { id, renderInput, upload } = info;
+      if (!renderInput) return;
+      this.id = id;
+      this.uploadOptions = upload;
+      this.$nextTick(() => {
+        const dom = document.getElementById(`xe-upload-${id}`);
+        dom.addEventListener('change', () => {
+          this.handleUpload();
+        });
+        dom?.click?.();
+      });
+    },
+    // 处理文件上传(没有传入url时返回本地链接)
+    async handleUpload() {
+      const {
+        url,
+        name,
+        header = {},
+        formData = {},
+      } = this.uploadOptions || {};
+      const dom = document.getElementById(`xe-upload-${this.id}`);
+      if (!dom.files[0]) return;
+      const tmpFileList = Array.from(dom.files);
+      const tmpUploads = [];
+      for (let i = 0; i < tmpFileList.length; i += 1) {
+        const e = tmpFileList[i];
+        let tmpType = 'file';
+        if (e.type.includes('image')) {
+          tmpType = 'image';
+        }
+        if (e.type.includes('video')) {
+          tmpType = 'video';
+        }
+        const tmpExts = {
+          size: e.size,
+          name: e.name,
+          type: e.type,
+          fileType: tmpType,
+          tempFilePath: '',
+          base64Url: '',
+        };
+        // 没有传入上传url时,直接返回本地链接
+        if (!url) {
+          const [parseError, parseUrl] = await awaitWrap(fileToBase64(dom.files[i]));
+          if (!parseError) {
+            tmpExts.base64Url = parseUrl;
+          }
+          tmpUploads.push(tmpExts);
+          continue;
+        };
+        const tmpData = new FormData();
+        tmpData.append(name, dom.files[i], e.name);
+        for (let key in formData) {
+          tmpData.append(key, formData[key]);
+        }
+        // 上传进度
+        const onprogress = (ev) => {
+          if(ev.lengthComputable) {
+            var result = (ev.loaded / ev.total) * 100;
+            this.handleRenderEmits({
+              type: 'onprogress',
+              data: {
+                progress: Math.floor(result),
+                current: i + 1,
+                total: tmpFileList.length,
+              },
+            });
+          };
+        }
+        tmpUploads.push(appUploadFile({
+          url,
+          header,
+          formData: tmpData
+        }, tmpExts, onprogress ));
+      }
+      // 没有传入上传url时,直接返回本地链接列表
+      if (!url) {
+        return this.handleRenderEmits({
+          type: 'choose',
+          data: tmpUploads,
+        });
+      }
+      this.handleRenderEmits({
+        type: 'onprogress',
+        data: {
+          progress: 0,
+          current: 1,
+          total: tmpFileList.length,
+        },
+      });
+      // 处理上传文件
+      const [err, res] = await awaitWrap(Promise.all(tmpUploads));
+      if (err) {
+        return this.handleRenderEmits({
+          type: 'warning',
+          data: err,
+        });
+      }
+      this.handleRenderEmits({
+        type: 'success',
+        data: res,
+      });
+    },
+    // 数据传输到XeUpload组件
+    handleRenderEmits(data) {
+      this.$ownerInstance.callMethod('handleEmits', data);
+    },
+  },
+};
+</script>
+<!-- #endif -->
+
+<style scoped>
+.xe-upload {
+  display: none;
+}
+</style>

+ 80 - 0
uni_modules/xe-upload/package.json

@@ -0,0 +1,80 @@
+{
+  "id": "xe-upload",
+  "displayName": "文件选择、文件上传组件(图片,视频,文件等)",
+  "version": "1.0.2",
+  "description": "H5、微信小程序、App端支持图片,视频,文件选择上传;其他端暂不支持文件选择上传",
+  "keywords": [
+    "App、H5、微信小程序、图片,视频,文件上传"
+],
+  "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": "u"
+        },
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "u",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y",
+          "钉钉": "y",
+          "快手": "y",
+          "飞书": "y",
+          "京东": "y"
+        },
+        "快应用": {
+          "华为": "y",
+          "联盟": "y"
+        }
+      }
+    }
+  }
+}

+ 78 - 0
uni_modules/xe-upload/readme.md

@@ -0,0 +1,78 @@
+# xe-upload
+
+## 说明
+
+不占用页面位置的上传组件;
+
+H5、APP、微信小程序中可上传图片,视频和文件;其他端暂时只能上传图片和视频
+
+> 上传图片通过[chooseMedia](https://uniapp.dcloud.net.cn/api/media/video.html#choosemedia)及[chooseImage](https://uniapp.dcloud.net.cn/api/media/image.html#chooseimage)实现
+
+> 上传视频通过[chooseMedia](https://uniapp.dcloud.net.cn/api/media/video.html#choosemedia)及[chooseVideo](https://uniapp.dcloud.net.cn/api/media/video.html#choosevideo)实现
+
+> H5端上传文件通过[chooseFile](https://uniapp.dcloud.net.cn/api/media/file.html#wx-choosemessagefile)实现
+
+> APP上传文件通过[renderjs](https://uniapp.dcloud.net.cn/tutorial/renderjs.html#renderjs)实现
+
+> 微信小程序上传文件通过[chooseMessageFile](https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseMessageFile.html)实现
+
+
+## 使用
+
+Attributes
+
+| 参数      | 说明 | 类型	 | 默认值 |
+| ----------- | ----------- | ----------- | ----------- |
+| options   | 请求配置(参数与uni.uploadFile的参数一致)        | object | { name: 'file' } |
+
+Events
+
+| 事件名      | 说明 | 参数	 | 
+| ----------- | ----------- | ----------- |
+| callback | 接收数据 | { type, data } |
+
+callback type
+
+| 参数      | 说明 |
+| ----------- | ----------- |
+| warning | 提示信息(下文称warning回调) |
+| success | 上传成功(下文称success回调) |
+| choose | 选择文件(下文称choose回调) |
+
+callback data
+
+```
+'callback.type === success' : [
+    {
+        "size": 176579, // 选择的文件的大小
+        "name": "Kafka.pdf", // 选择的文件的名称(小程序端可能会没有)
+        "type": "application/pdf",
+        "tempFilePath": "blob:http://192.168.137.1:8080/2585769b-3195-4f3d-b9f8-d9e99f55deec", // 临时路路径
+        "fileType": "file", // 文件类型[image, video, file]
+        "response": {
+            "result": {
+                "fileName": "Kafka.pdf",
+                "filePath": `http://localhost:3000/upload/e51d814b649122fc64892d0bc6383d07.pdf`,
+            },
+            "success": true,
+        }, // 上传返回的信息
+    }
+]
+
+'callback.type === choose' : [
+    {
+        "size": 176579, // 选择的文件的大小
+        "name": "Kafka.pdf", // 选择的文件的名称(小程序端可能会没有)
+        "type": "application/pdf",
+        "tempFilePath": "blob:http://192.168.137.1:8080/4204e460-f185-4fc9-9f4d-1bc50ab06981", // 文件临时路径
+        "fileType": "file", // 文件类型[image, video, file]
+    }
+]
+```
+
+## 注意事项
+#### 1、options入参中如果url为空,则choose回调的data列表中只有选择文件能得到的信息和临时路径,临时路径可用于自定义上传方法(APP除外);传入url选择文件后会自动上传到服务器,此时choose回调不会触发,而是执行success回调,success回调的data列表会包括选择文件能得到的信息
+#### 2、APP端文件建议直接上传到服务器,拿到文件上传后的地址再进行其他操作(目前测试APP端file转换后的Blob Url无法用于uni.uploadFile,所以建议APP文件直接上传)
+#### 3、APP端文件暂时支持单个上传
+#### 4、当uni.chooseMedia可用时,会优先使用uni.chooseMedia
+#### 5、具体使用可下载示例项目运行查看完整示例

+ 177 - 0
uni_modules/xe-upload/tools/apis.js

@@ -0,0 +1,177 @@
+// eslint-disable
+import { awaitWrap } from './tools';
+/**
+ * 从本地相册选择图片或使用相机拍照
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/image.html#chooseimage
+ * @returns
+ */
+export const chooseImage = (config) => {
+  return awaitWrap(
+    new Promise((r, j) => {
+      uni.chooseImage({
+        ...config,
+        success: (res) => {
+          const tmpFiles = res?.tempFiles.map((e) => ({
+            tempFilePath: e.path,
+            tempFile: e,
+            size: e.size,
+            name: e.name,
+            type: e.type,
+            fileType: 'image',
+          }));
+          return r({ type: 'image', ...res, tempFiles: tmpFiles });
+        },
+        fail: (err) => j({ mode: 'chooseImage', data: err }),
+      });
+    })
+  );
+};
+
+/**
+ * 拍摄视频或从手机相册中选视频,返回视频的临时文件路径
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/video.html#choosevideo
+ * @returns
+ */
+export const chooseVideo = (config) => {
+  return awaitWrap(
+    new Promise((r, j) => {
+      uni.chooseVideo({
+        ...config,
+        success: (res) => {
+          const tmpFiles = [{
+            ...res,
+            tempFilePath: res.tempFilePath,
+            tempFile: res.tempFile ?? {},
+            size: res.size,
+            name: res.name,
+            type: res.tempFile?.type,
+            fileType: 'video',
+          }];
+          return r({ type: 'video', tempFiles: tmpFiles });
+        },
+        fail: (err) => j({ mode: 'chooseVideo', data: err }),
+      });
+    })
+  );
+};
+
+/**
+ * 拍摄或从手机相册中选择图片或视频
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/video.html#choosemedia
+ * @returns
+ */
+export const chooseMedia = (type, config) => {
+  if (!type) return console.error('chooseMedia type cannot be empty');
+  if (!uni.chooseMedia && type === 'image') return chooseImage(config);
+  if (!uni.chooseMedia && type === 'video') return chooseVideo(config);
+  return awaitWrap(
+    new Promise((r, j) => {
+      uni.chooseMedia({
+        ...config,
+        mediaType: [type],
+        success: (res) => r(res),
+        fail: (err) => j({ mode: 'chooseMedia', data: err }),
+      });
+    })
+  );
+};
+
+/**
+ * 从本地选择文件(h5)
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/file.html#wx-choosemessagefile
+ * @returns
+ */
+export const chooseFile = (config) => {
+  return awaitWrap(
+    new Promise((r, j) => {
+      uni.chooseFile({
+        ...config,
+        success: (res) => {
+          const tmpFiles = res?.tempFiles.map((e) => {
+            let tmpType = 'file';
+            if (e.type.includes('image')) {
+              tmpType = 'image';
+            }
+            if (e.type.includes('video')) {
+              tmpType = 'video';
+            }
+            return {
+              tempFilePath: e.path,
+              tempFile: e,
+              size: e.size,
+              name: e.name,
+              type: e.type,
+              fileType: tmpType,
+            };
+          });
+          return r({ type: 'file', ...res, tempFiles: tmpFiles });
+        },
+        fail: (err) => j({ mode: 'chooseFile', data: err }),
+      });
+    })
+  );
+};
+
+/**
+ * 从本地选择文件(微信小程序)
+ * @param {object} config 参数详情 => https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseMessageFile.html
+ * @returns
+ */
+export const chooseMessageFile = (config) => {
+  return awaitWrap(
+    new Promise((r, j) => {
+      wx.chooseMessageFile({
+        ...config,
+        success: (res) => {
+          const tmpFiles = res?.tempFiles.map((e) => ({
+            ...e,
+            tempFilePath: e.path,
+            fileType: e.type ?? 'file',
+          }));
+          return r({ type: 'file', ...res, tempFiles: tmpFiles });
+        },
+        fail: (err) => j({ mode: 'chooseMessageFile', data: err }),
+      });
+    })
+  );
+};
+
+/**
+ * 上传
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
+ * @param {object} exts 选择的文件的数据
+ * @returns {object} exts + response
+ */
+export const uploadFile = (config, exts = {}) => {
+  return new Promise((r, j) => {
+    uni.uploadFile({
+      ...config,
+      success: (res) => r({ ...exts, response: JSON.parse(res.data) }),
+      fail: (err) => j({ mode: 'uploadFile', data: err }),
+    });
+  });
+};
+
+export const appUploadFile = (config, exts = {}, onprogress) => {
+  const { url, header, formData } = config;
+  return new Promise((r, j) => {
+    const xhr = new XMLHttpRequest();
+    xhr.open('POST', url, true);
+    for (let key in header) {
+      xhr.setRequestHeader(key, header[key]);
+    }
+    if (onprogress) {
+      xhr.upload.onprogress = onprogress;
+    }
+    xhr.onreadystatechange = function() {
+      if (xhr.readyState === 4) {
+        if (xhr.status === 200) {
+          r({ ...exts, response: JSON.parse(xhr.responseText) });
+        } else {
+          j({ mode: 'uploadFile', data: { data: xhr.responseText, errMsg: 'uploadFile fail.' } });
+        }
+      }
+    }
+    xhr.send(formData);
+  });
+};

+ 180 - 0
uni_modules/xe-upload/tools/tools.js

@@ -0,0 +1,180 @@
+// eslint-disable
+export const isObject = (obj) => {
+  return obj
+    ? Object.prototype.toString.call(obj) === "[object Object]"
+    : false;
+};
+export const isArray = (arr) => {
+  return arr ? Array.isArray(arr) : false;
+};
+/**
+ * handle async await
+ * @param {*} promise promise
+ */
+export const awaitWrap = (promise) =>
+  promise.then((res) => [null, res]).catch((err) => [err, {}]);
+/**
+ * 深拷贝
+ * @param {*} source
+ */
+export const deepClone = (source) => {
+  if (!isObject(source) && !isArray(source)) return source;
+  const targetObj = isArray(source) ? [] : {}; // 判断复制的目标是数组还是对象
+  for (let keys in source) {
+    // 遍历目标
+    if (source.hasOwnProperty(keys)) {
+      if (source[keys] && typeof source[keys] === "object") {
+        // 如果值是对象,就递归一下
+        targetObj[keys] = isArray(source[keys]) ? [] : {};
+        targetObj[keys] = deepClone(source[keys]);
+      } else {
+        // 如果不是,就直接赋值
+        targetObj[keys] = source[keys];
+      }
+    }
+  }
+  return targetObj;
+};
+/**
+ * @description JS对象深度合并
+ * @param {object} target 需要拷贝的对象
+ * @param {object} source 拷贝的来源对象
+ * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
+ */
+export const deepMerge = (target = {}, source = {}) => {
+  target = deepClone(target);
+  if (typeof target !== "object" || typeof source !== "object") return false;
+  for (const prop in source) {
+    if (!source.hasOwnProperty(prop)) continue;
+    if (prop in target) {
+      if (typeof target[prop] !== "object") {
+        target[prop] = source[prop];
+      } else if (typeof source[prop] !== "object") {
+        target[prop] = source[prop];
+      } else if (target[prop].concat && source[prop].concat) {
+        target[prop] = target[prop].concat(source[prop]);
+      } else {
+        target[prop] = deepMerge(target[prop], source[prop]);
+      }
+    } else {
+      target[prop] = source[prop];
+    }
+  }
+  return target;
+};
+/**
+ * 将File对象转为 Blob Url
+ * @param {File} File对象
+ * @returns Blob Url
+ */
+export const fileToBlob = (file) => {
+  if (!file) return;
+  const fileType = file.type;
+  const blob = new Blob([file], { type: fileType || 'application/*' });
+  const blobUrl = window.URL.createObjectURL(blob);
+  return blobUrl;
+};
+/**
+ * 将File对象转为 base64
+ * @param {File} File对象
+ * @returns base64
+ */
+export const fileToBase64 = (file) => {
+  if (!file) return;
+  return new Promise((r, j) => {
+    const reader = new FileReader();
+    reader.onloadend = () => {
+      const base64String = reader.result;
+      r(base64String);
+    };
+    reader.onerror = () => {
+      j({ mode: 'fileToBase64', data: { errMsg: 'File to base64 fail.' } });
+    };
+    reader.readAsDataURL(file);
+  });
+};
+/**
+ * base64转临时路径(改自https://github.com/zhetengbiji/image-tools/blob/master/index.js)
+ * @param base64
+ * @returns
+ */
+function dataUrlToBase64(str) {
+  var array = str.split(',');
+  return array[array.length - 1];
+};
+function biggerThan(v1, v2) {
+  var v1Array = v1.split('.');
+  var v2Array = v2.split('.');
+  var update = false;
+  for (var index = 0; index < v2Array.length; index++) {
+    var diff = v1Array[index] - v2Array[index];
+    if (diff !== 0) {
+      update = diff > 0;
+      break;
+    }
+  }
+  return update;
+};
+var index = 0;
+function getNewFileId() {
+  return Date.now() + String(index++);
+};
+export const base64ToPath = (base64, name = '') => {
+  return new Promise((r, j) => {
+    if (typeof plus !== 'object') {
+      return j(new Error('not support'));
+    }
+    var fileName = '';
+    if (name) {
+      const names = name.split('.');
+      const extName = names.splice(-1);
+      fileName = `${names.join('.')}-${getNewFileId()}.${extName}`;
+    } else {
+      const names = base64.split(',')[0].match(/data\:\S+\/(\S+);/);
+      if (!names) {
+        j(new Error('base64 error'));
+      }
+      const extName = names[1];
+      fileName = `${getNewFileId()}.${extName}`;
+    }
+    var basePath = '_doc';
+    var dirPath = 'uniapp_temp';
+    var filePath = `${basePath}/${dirPath}/${fileName}`;
+    if (!biggerThan(plus.os.name === 'Android' ? '1.9.9.80627' : '1.9.9.80472', plus.runtime.innerVersion)) {
+      plus.io.resolveLocalFileSystemURL(basePath, function (entry) {
+        entry.getDirectory(dirPath, {
+          create: true,
+          exclusive: false,
+        }, function (entry) {
+          entry.getFile(fileName, {
+            create: true,
+            exclusive: false,
+          }, function (entry) {
+            entry.createWriter(function (writer) {
+              writer.onwrite = function () {
+                r(filePath);
+              }
+              writer.onerror = j;
+              writer.seek(0);
+              writer.writeAsBinary(dataUrlToBase64(base64));
+            }, j)
+          }, j)
+        }, j)
+      }, j)
+      return;
+    }
+    var bitmap = new plus.nativeObj.Bitmap(fileName);
+    bitmap.loadBase64Data(base64, function () {
+      bitmap.save(filePath, {}, function () {
+        bitmap.clear();
+        r(filePath);
+      }, function (error) {
+        bitmap.clear();
+        j(error);
+      });
+    }, function (error) {
+      bitmap.clear();
+      j(error);
+    });
+  });
+};

+ 1 - 1
utils/request.js

@@ -15,7 +15,7 @@ const request = config => {
     config.header['Authorization'] = 'Bearer ' + getAccessToken()
   }
   // 设置租户 TODO 芋艿:强制 1 先
-  config.header['tenant-id'] = '323';
+  config.header['tenant-id'] = '293';
   // get请求映射params参数
   if (config.params) {
     let url = config.url + '?' + tansParams(config.params)

+ 2 - 2
utils/upload.js

@@ -5,7 +5,7 @@ import errorCode from '@/utils/errorCode'
 import { toast, showConfirm, tansParams } from '@/utils/common'
 
 let timeout = 10000
-const baseUrl = config.baseUrl
+const baseUrl = config.baseUrl + config.baseApi;
 
 const upload = config => {
   // 是否需要设置 token
@@ -21,7 +21,7 @@ const upload = config => {
     config.url = url
   }
   // 设置租户 TODO 芋艿:强制 1 先
-  config.header['tenant-id'] = '1';
+  config.header['tenant-id'] = '293';
   return new Promise((resolve, reject) => {
       uni.uploadFile({
         timeout: config.timeout || timeout,