Prechádzať zdrojové kódy

教师发展接口对接完成

liurongli 1 týždeň pred
rodič
commit
ef6784b8fe
58 zmenil súbory, kde vykonal 5172 pridanie a 60 odobranie
  1. 44 0
      common/common.js
  2. 2 2
      components/customPopupDialog.vue
  3. 25 9
      components/navBar.vue
  4. 1 1
      components/pageTabbar.vue
  5. 1 1
      components/popupDialog.vue
  6. 1 1
      manifest.json
  7. 36 1
      pages.json
  8. 1 1
      pages/studentStudy/videoDetails.vue
  9. 265 0
      pages/teacherHonor/honorAudit/honorAuditDetail.vue
  10. 224 2
      pages/teacherHonor/honorAudit/index.vue
  11. 342 0
      pages/teacherHonor/honorAudit/teacherDetail.vue
  12. 287 0
      pages/teacherHonor/honorOverview/honorTypeAuditDetail.vue
  13. 17 10
      pages/teacherHonor/honorOverview/honorTypeDetail.vue
  14. 10 3
      pages/teacherHonor/honorOverview/index.vue
  15. 191 0
      pages/teacherHonor/honorTeacher/honorDetail.vue
  16. 5 4
      pages/teacherHonor/honorTeacher/honorTeacherDetail.vue
  17. 1 1
      pages/teacherHonor/index.vue
  18. 158 17
      pages/teacherHonor/myHonor/index.vue
  19. 435 0
      pages/teacherHonor/myHonor/uploadEditMyHonor.vue
  20. 1 1
      pages/teacherStudy/videoDetails.vue
  21. 36 0
      reqApi/teacherHonor.js
  22. BIN
      static/image/icon/plusempty.png
  23. 101 5
      style/common.scss
  24. 10 0
      uni_modules/uni-dateformat/changelog.md
  25. 200 0
      uni_modules/uni-dateformat/components/uni-dateformat/date-format.js
  26. 88 0
      uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue
  27. 88 0
      uni_modules/uni-dateformat/package.json
  28. 11 0
      uni_modules/uni-dateformat/readme.md
  29. 9 0
      uni_modules/uv-loading-icon/changelog.md
  30. 67 0
      uni_modules/uv-loading-icon/components/uv-loading-icon/props.js
  31. 347 0
      uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue
  32. 87 0
      uni_modules/uv-loading-icon/package.json
  33. 19 0
      uni_modules/uv-loading-icon/readme.md
  34. 9 0
      uni_modules/uv-overlay/changelog.md
  35. 25 0
      uni_modules/uv-overlay/components/uv-overlay/props.js
  36. 85 0
      uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue
  37. 88 0
      uni_modules/uv-overlay/package.json
  38. 11 0
      uni_modules/uv-overlay/readme.md
  39. 18 0
      uni_modules/uv-popup/changelog.md
  40. 45 0
      uni_modules/uv-popup/components/uv-popup/keypress.js
  41. 539 0
      uni_modules/uv-popup/components/uv-popup/uv-popup.vue
  42. 92 0
      uni_modules/uv-popup/package.json
  43. 21 0
      uni_modules/uv-popup/readme.md
  44. 7 0
      uni_modules/uv-status-bar/changelog.md
  45. 8 0
      uni_modules/uv-status-bar/components/uv-status-bar/props.js
  46. 54 0
      uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue
  47. 87 0
      uni_modules/uv-status-bar/package.json
  48. 10 0
      uni_modules/uv-status-bar/readme.md
  49. 1 1
      uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue
  50. 17 0
      uni_modules/uv-upload/changelog.md
  51. 52 0
      uni_modules/uv-upload/components/uv-preview-video/uv-preview-video.vue
  52. 22 0
      uni_modules/uv-upload/components/uv-upload/mixin.js
  53. 130 0
      uni_modules/uv-upload/components/uv-upload/props.js
  54. 151 0
      uni_modules/uv-upload/components/uv-upload/utils.js
  55. 488 0
      uni_modules/uv-upload/components/uv-upload/uv-upload.vue
  56. 90 0
      uni_modules/uv-upload/package.json
  57. 11 0
      uni_modules/uv-upload/readme.md
  58. 1 0
      vite.config.js

+ 44 - 0
common/common.js

@@ -141,4 +141,48 @@ export function remainingDays(endTime) {
 
     if (diffDays < 0) return null;   // 已过期:不显示剩余时间
     return diffDays + 1;             // 18号到22号:22-18=4,加1后=5天
+}
+export function unioformDateTransform(date, param = "time"){
+    if(date){
+        let strDate = new Date(date).toLocaleString('zh-CN')
+        let days = strDate.split(" ")[0]
+        let time = strDate.split(" ")[1]
+        let year = days.split("/")[0]
+        let month = days.split("/")[1]
+        let day = days.split("/")[2]
+
+        if(Number(month)<10){
+            month = '0'+ month
+        }
+        if(Number(day)<10){
+            day = '0'+day
+        }
+        days =  year+'-'+month+'-'+day
+
+        switch(param){
+            case "day":
+                return days
+            case "time":
+                return days + " " + time
+        }
+    }else{
+        return ''
+    }
+}
+export const fileSizeConvert = (byte)=>{
+    let kbSize = 0
+    let mbSize = 0
+    let gbSize = 0
+    kbSize = (byte / 1024)
+    if(kbSize > 1024){
+        mbSize = kbSize / 1024
+        if(mbSize > 1024){
+            gbSize = mbSize / 1024
+            return gbSize.toFixed(2) + 'GB'
+        }else {
+            return mbSize.toFixed(2) + 'MB'
+        }
+    }else{
+        return kbSize.toFixed(2) + 'KB'
+    }
 }

+ 2 - 2
components/customPopupDialog.vue

@@ -14,7 +14,7 @@
 			</view>
 			<!-- 自定义底部按钮区域 -->
 			<view class="dialog_footer" v-if="showDialogFooter">
-				<view v-if="showCloseBtn" class="dialog-btn cancel-btn" @click="CloseDialog">取消</view>
+				<view v-if="showCancelBtn" class="dialog-btn cancel-btn" @click="CloseDialog">取消</view>
 				<view class="dialog-btn confirm-btn" @click="handleConfirm">确定</view>
 			</view>
 		</view>
@@ -52,7 +52,7 @@
 			type: Boolean,
 			default: true
 		},
-		showCloseBtn: {//是否显示取消按钮
+		showCancelBtn: {//是否显示取消按钮
 			type: Boolean,
 			default: true
 		}

+ 25 - 9
components/navBar.vue

@@ -1,18 +1,23 @@
-<template>
+<template>
 	<!-- 头部 -->
-	<uni-nav-bar :class="{'custom_uni_nav_bar':!onlyTitle,'custom_navbar_only_title':onlyTitle}"
+	<uni-nav-bar :class="{'custom_uni_nav_bar':!onlyTitle,'custom_navbar_only_title':onlyTitle,'custom_only_title_back_icon':!showHeadSearchInput}"
 		:height="`${height}rpx`" :backgroundColor="backgroundColor" :title="title" color="#333333" :fixed="true"
 		:border="showHeaderborder" :statusBar="true" :leftWidth="leftWidth" :rightWidth="rightWidth" left-icon="left"
 		@clickLeft="GoBack()">
 		<!-- 表头内容 -->
 		<view :class="['nav_head',{'only_title':!showHeadSearchInput}]" v-if="!onlyTitle">
 			<text class="nav_title">{{title}}</text>
-			<uni-easyinput v-if="showHeadSearchInput" class="search_box" v-model="state.searchWord" trim="all" :styles="state.searchInputStyles"
-				:placeholderStyle="state.searchInputPlaceholderStyle" placeholder="请输入关键字搜索" @input="HandleSearchInput">
-				<template #left>
-					<image src="/static/image/overview/search.png" class="search_icon"></image>
+			<template v-if="showHeadSearchInput">
+				<uni-easyinput v-if="rightType=='searchBox'" class="search_box" v-model="state.searchWord" trim="all" :styles="state.searchInputStyles"
+					:placeholderStyle="state.searchInputPlaceholderStyle" placeholder="请输入关键字搜索" @input="HandleSearchInput">
+					<template #left>
+						<image src="@/static/image/overview/search.png" class="search_icon"></image>
+					</template>
+				</uni-easyinput>
+				<template v-else>
+					<slot name="headRightIcon" />
 				</template>
-			</uni-easyinput>
+			</template>
 		</view>
 		<!-- 条件选择器 -->
 		<view class="filter_picker" v-if="showFilterPicker">
@@ -25,6 +30,7 @@
 				</picker>
 			</template>
 		</view>
+		<slot name="tabs_top_content" />
 		<!-- tabs -->
 		<scroll-view v-if="showTabs && tabList.length > 0" class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
 			<view :class="['tab_item',{'selected':item.value==state.tabsSelected}]" v-for="item in tabList" :key="item.value" @click="SelectTabsValue(item.value)">
@@ -92,6 +98,10 @@
 			type: Array,
 			default: () => []
 		},
+		defaultTabValue:{
+			type: [String,Number],
+			default: () => ''
+		},//默认值
 		tabBtns: { //tabBtns
 			type: Array,
 			default: () => []
@@ -100,6 +110,10 @@
 			type: Boolean,
 			default: true
 		},
+		rightType:{//头部右侧内容
+			type: String,
+			default: 'searchBox'
+		},
 		showFilterPicker: { //是否显示条件选择器
 			type: Boolean,
 			default: false
@@ -130,7 +144,8 @@
 	})
 	watch(()=>props.tabList,(newVal, oldVal)=>{
 		if(newVal){
-			state.tabsSelected = props?.tabList?.[0]?.value || ''; //tab 默认值
+			const firstValue = props?.tabList?.[0]?.value || '';
+			state.tabsSelected = props.defaultTabValue!==''?props.defaultTabValue : firstValue; //tab 默认值
 		}
 	})
 	watch(()=>props.tabBtns,(newVal, oldVal)=>{
@@ -192,7 +207,8 @@
 		}
 	}
 	onMounted(() => {
-		state.tabsSelected = props?.tabList?.[0]?.value || ''; //tab 默认值
+		const firstValue = props?.tabList?.[0]?.value || '';
+		state.tabsSelected = props.defaultTabValue!==''?props.defaultTabValue : firstValue; //tab 默认值
 		state.tabBtnSelected = props?.tabBtns?.[0]?.value || ''; //tabBtns 默认值
 		state.filterOpts.filterPickerValue = props.filterPickerData.map(item => item?.defaultValue ?? '');
 	})

+ 1 - 1
components/pageTabbar.vue

@@ -1,6 +1,6 @@
 <template>
 	<!-- 底部标签栏 -->
-	<uv-tabbar :value="tabBarValue" activeColor="#2E64FA" inactiveColor="#C7CBD6" @change="changeTabBarValue">
+	<uv-tabbar :value="tabBarValue" :safeAreaInsetBottom="false" activeColor="#2E64FA" inactiveColor="#C7CBD6" @change="changeTabBarValue">
 		<uv-tabbar-item v-for="item in tabBarList" :key="item.pathName" :name="item.pathName" :text="item.text">
 			<template v-slot:active-icon>
 				<image class="icon" :src="item.selectedIconPath" style="width:48rpx;height:48rpx"></image>

+ 1 - 1
components/popupDialog.vue

@@ -4,7 +4,7 @@
 		<uni-popup-dialog :class="{'white':!showTitle,'footBorder':showFootBorder}" :showClose="showCloseBtn" :style="{'width': `${dialogWidth}rpx`}" @confirm="DialogConfirm" @close="CloseDialog">
 			<view class="dialog_title" v-if="showTitle">
 				<view class="dialog_title_lt">
-					<image v-if="popType=='alert'" src="@/static/image/icon/icon_warn.png" style="width: 40rpx;height: 40rpx;"></image>
+					<image v-if="popType=='alert'" src="@/static/image/icon/icon_warn.png" style="width: 40rpx;height: 40rpx;margin-left: 16rpx;"></image>
 					<text class="title">{{title}}</text>
 				</view>
 				<image v-if="showDialogClose" src="@/static/image/icon/close.png" style="width: 48rpx;height: 48rpx;" @click="CloseDialog"></image>

+ 1 - 1
manifest.json

@@ -1,5 +1,5 @@
 {
-    "name" : "教师端",
+    "name" : "贯通慧教研",
     "appid" : "__UNI__07352BD",
     "description" : "",
     "versionName" : "1.2.0",

+ 36 - 1
pages.json

@@ -179,7 +179,14 @@
 		{
 			"path": "pages/teacherHonor/honorOverview/honorTypeDetail",
 			"style": {
-				"navigationBarTitleText": "教师发展-新建/修改荣誉类型",
+				"navigationBarTitleText": "荣誉总览-新建/修改荣誉类型",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/teacherHonor/honorOverview/honorTypeAuditDetail",
+			"style": {
+				"navigationBarTitleText": "荣誉总览-查看荣誉",
 				"navigationStyle": "custom"
 			}
 		},
@@ -190,6 +197,34 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/teacherHonor/honorTeacher/honorDetail",
+			"style": {
+				"navigationBarTitleText": "教师发展-荣誉教师-教师荣誉详情-荣誉详情",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/teacherHonor/myHonor/uploadEditMyHonor",
+			"style": {
+				"navigationBarTitleText": "我得荣誉-上传/编辑荣誉",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/teacherHonor/honorAudit/honorAuditDetail",
+			"style": {
+				"navigationBarTitleText": "教师发展-荣誉审核-荣誉审核详情",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/teacherHonor/honorAudit/teacherDetail",
+			"style": {
+				"navigationBarTitleText": "教师发展-荣誉审核-荣誉审核详情-教师详情",
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/materialCollection/taskOverview/index",
 			"style": {

+ 1 - 1
pages/studentStudy/videoDetails.vue

@@ -51,7 +51,7 @@
 			</view>
 		</view>
 		<!-- 提示框 -->
-		<customPopupDialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCloseBtn="false" @DialogConfirm="DialogConfirm">
+		<customPopupDialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCancelBtn="false" @DialogConfirm="DialogConfirm">
 			<view class="dialog_questions_input">
 				<view class="questions_title">{{state?.question?.codeName}}:{{state?.question?.questionsName}}</view>
 				<uni-easyinput type="textarea" v-model="state.questionsAnswer" :style="state.easyinputStyle" placeholder="请输入你的回答" placeholderStyle="font-weight: 400;font-size: 28rpx;color: #999999;"></uni-easyinput>

+ 265 - 0
pages/teacherHonor/honorAudit/honorAuditDetail.vue

@@ -0,0 +1,265 @@
+<template>
+	<view class="page_body">
+		<!-- 头部 -->
+		<nav-bar height="200" :title="state.honorTypeName" :defaultTabValue="state.honorStatus" :tabList="state.tabList" :onlyTitle="false" :showHeadSearchInput="false" :showFilterPicker="false" :showTabs="true" :showTabBtns="false" rightWidth="0rpx" @CommonFilterData="CommonFilterData" @GoBack="GoBack"></nav-bar>
+		<view class="page_content">
+			<scroll-view
+			    class="scroll_view_y"
+				:scroll-top="state.scrollTop"
+				scroll-y="true"
+				:refresher-enabled="false" 
+				:enable-back-to-top="true"
+				:show-scrollbar="false" 
+				:scroll-with-animation="true" 
+				refresher-default-style="none"
+				refresher-background="#F8F9FD" 
+				:refresher-triggered="state.isRefreshing" 
+				@scrolltolower="OnLoadMore"
+				@refresherrefresh="OnRefresh"
+				@scroll="OnScroll">
+				<view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
+				<view class="page_list">
+					<view class="count">
+						<template v-if="state.honorStatus === '0'">共 {{state.reViewCount}} 人</template>
+						<template v-if="state.honorStatus === '1'">共 {{state.overCount}} 人</template>
+						<template v-if="state.honorStatus === '2'">共 {{state.overruleCount}} 人</template>
+					</view>
+					<view class="list_item" v-for="item in state.pageList" :key="item.id" @click="GoTeacherDetail(item.id,item.teacherName)">
+						<view class="name_btn">
+							<view class="teacher_name">{{item.teacherName}}</view>
+							<view class="btn">{{state.honorStatus === '0'?'审核':'查看'}}</view>
+						</view>
+						<view class="created_at">上传时间:{{item.createdAt}}</view>
+					</view>
+				</view>
+				<view class="no_data" v-if="!state.isLoading && state.pageList.length == 0">
+					<image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
+					<text class="no_data_text">暂无数据</text>
+				</view>
+				<uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import navBar from '@/components/navBar.vue';
+	import {queryHonorReviewDetailList} from '@/reqApi/teacherHonor.js';
+	import {
+		reactive,
+		onUnmounted,
+		nextTick
+	} from 'vue';
+	import { onLoad,onShow } from '@dcloudio/uni-app';
+	const state = reactive({
+		tabList:[{
+            label:'待审核',
+            value:'0'
+        },{
+            label:'已审核',
+            value:'1'
+        },{
+            label:'已驳回',
+            value:'2',
+        }],
+		pageLists:[],//全部数据
+		pageList:[],//列表数据
+		reViewCount:0,//待审核数量
+		overCount:0,//已审核数量
+		overruleCount:0,//驳回数量
+		honorTypeId:'',//荣誉类型id
+		honorTypeName:'',//荣誉类型
+		honorStatus:'',//审核状态
+		pageSize: 30,
+		pageNum: 1,
+		pages:0,
+		scrollTop:0,
+		oldScrollTop:0,
+		isRefreshing:false,//设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
+		isLoading:true,//加载中
+		noMoreData:false,//没有更多数据了
+		loadStatus:'more',//loading状态
+	})
+	onShow(()=>{
+		const isLoadHonorAuditList = uni.getStorageSync('isLoadHonorAuditList');
+		if(isLoadHonorAuditList){//审核驳回通过返回刷新列表
+			OnLoadData(true);
+		}
+	})
+	onLoad((option) => {
+		state.honorTypeId = option.honorTypeId;
+		state.honorTypeName = decodeURIComponent(option.honorTypeName);
+		state.honorStatus = option.honorStatus;
+		OnLoadData(true);
+	});
+	onUnmounted(()=>{
+		uni.setStorageSync('isLoadHonorAuditList', false);
+	})
+	//切换条件选择器
+	const CommonFilterData = (filterOptions,type) => {
+		const { tabsSelected } = filterOptions;
+		state.honorStatus = tabsSelected;
+		OnLoadData(true)
+	}
+	/*
+	*获取数据
+	* initPage:初始分页 从第一开始
+	*/
+	const OnLoadData = (initPage) => {
+		state.isLoading = true;
+		state.loadStatus = 'loading';
+		if (initPage) {
+			// uni.showLoading({
+			// 	title: '加载中'
+			// });
+			state.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
+			queryHonorReviewDetailList({
+				honorTypeId:state.honorTypeId,
+				honorStatus:state.honorStatus
+			}).then(res=>{
+				if(res.code == 200){
+					const tableData = res?.data?.detailList || [];
+					state.reViewCount = res?.data?.reViewCount || 0;//待审核数量
+					state.overCount = res?.data?.overCount || 0;//已审核数量
+					state.overruleCount = res?.data?.overruleCount || 0;//驳回数量
+					state.pageLists = tableData;//全部数据
+					const total = tableData?.length || 0;//总数
+					state.pages = Math.ceil(total / state.pageSize);//总页数
+					//默认加载
+					state.pageList = state.pageLists.slice(0, state.pageSize);
+					//没有更多了
+					state.noMoreData = state.pageNum == state.pages;
+					//分页+1
+					state.pageNum++;
+					state.isLoading = false;
+					state.isRefreshing = false;//下拉刷新未被触发
+					state.loadStatus = state.noMoreData?'no-more':'more';
+				}
+			}).finally(()=>{
+				// uni.hideLoading();
+				//返回到页面顶部
+				if(initPage){
+					GoTop();
+				}
+			})
+		}else{
+			const start = (state.pageNum - 1) * state.pageSize;
+			const end = start + state.pageSize;
+			const list = state.pageLists.slice(start, end);
+			state.pageList = [...state.pageList, ...list];
+			state.noMoreData = state.pageNum == state.pages;
+			state.pageNum++;
+			setTimeout(() => {
+				state.isLoading = false;
+				state.isRefreshing = false; //下拉刷新未被触发
+				state.loadStatus = state.noMoreData?'no-more':'more';
+			}, 0)
+		}
+	}
+	// 自定义下拉刷新被触发	 下拉刷新
+	const OnRefresh = () =>{
+		state.isRefreshing = true;//下拉刷新已经被触发
+		OnLoadData(true);
+	}
+	//滚动到底部/右边 触发上拉刷新
+	const OnLoadMore = () => {
+		if (state.isLoading || state.noMoreData) return;
+		OnLoadData(false);
+	}
+	//	滚动时触发
+	const OnScroll = (e) => {
+		state.oldScrollTop = e.detail.scrollTop;
+	}
+	//返回到顶部
+	const GoTop = () => {
+		// 解决view层不同步的问题
+		state.scrollTop = state.oldScrollTop;
+		nextTick(() => {
+			state.scrollTop = 0
+		});
+	}
+	//返回
+	const GoBack = () => {
+		uni.navigateBack({
+			delta: 1
+		});
+	}
+	const GoTeacherDetail = (id,teacherName) => {
+		uni.navigateTo({
+			url: `/pages/teacherHonor/honorAudit/teacherDetail?id=${id}&pageTitle=${teacherName}&pageFoot=1`
+		});
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page_body{
+		height: 100%;
+		min-height: auto;
+		.page_content{
+			height: calc(100% - 200rpx);
+			.scroll_view_y{
+				height: 100%;
+				.refresh_loading{
+					padding: 10rpx 0;
+				}
+			}
+			.page_list{
+				display: flex;
+				width: 100%;
+				flex-direction: column;
+				.count{
+					display: flex;
+					align-items: center;
+					width: 100%;
+					height: 88rpx;
+					border-top: 2rpx solid #F3F3F3;
+					border-bottom: 2rpx solid #F3F3F3;
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #333333;
+				}
+				.list_item{
+					display: flex;
+					width: 100%;
+					flex-direction: column;
+					padding:24rpx 0;
+					border-bottom: 2rpx solid #F3F3F3;
+					.name_btn{
+						display: flex;
+						width: 100%;
+						align-items: center;
+						.teacher_name{
+							flex: 1;
+							font-weight: 500;
+							font-size: 32rpx;
+							color: #333333;
+							white-space: nowrap;
+							overflow: hidden;
+							text-overflow: ellipsis;
+						}
+						.btn{
+							display: inline-flex;
+							align-items: center;
+							justify-content: center;
+							width: 104rpx;
+							height: 64rpx;
+							border-radius: 8rpx;
+							border: 2rpx solid #2E64FA;
+							font-weight: 400;
+							font-size: 28rpx;
+							color: #2E64FA;
+						}
+					}
+					.created_at{
+						display: flex;
+						width: 100%;
+						margin-top: 16rpx;
+						font-weight: 400;
+						font-size: 24rpx;
+						color: #999999;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 224 - 2
pages/teacherHonor/honorAudit/index.vue

@@ -1,8 +1,230 @@
 <template>
+	<!-- 头部 -->
+	<nav-bar height="224" backPath="workspace" title="教师专业发展" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="false" :showTabs="true" :showTabBtns="false" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
+	<view class="page_content">
+		<scroll-view
+		    class="scroll_view_y"
+			:scroll-top="state.scrollTop"
+			scroll-y="true"
+			:refresher-enabled="false" 
+			:enable-back-to-top="true"
+			:show-scrollbar="false" 
+			:scroll-with-animation="true" 
+			refresher-default-style="none"
+			refresher-background="#F8F9FD" 
+			:refresher-triggered="state.isRefreshing" 
+			@scrolltolower="OnLoadMore"
+			@refresherrefresh="OnRefresh"
+			@scroll="OnScroll">
+			<view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
+			<view class="page_list">
+				<view class="list_item" v-for="item in state.pageList" :key="item.honorTypeId" @click="HonorAuditDetail(item.honorTypeId,item.honorTypeName)">
+					<view class="honor_name">{{item.honorTypeName}}</view>
+					<view class="audit_status">
+						<view class="status wait" v-if="state.honorStatus==='0'">待审核:{{item.number}}</view>
+						<view class="status pass" v-if="state.honorStatus==='1'">已审核:{{item.number}}</view>
+						<view class="status reject" v-if="state.honorStatus==='2'">已驳回:{{item.number}}</view>
+						<view class="btn">{{state.honorStatus==='0'?'开始审核':'查看详情'}}</view>
+					</view>
+				</view>
+			</view>
+			<view class="no_data" v-if="!state.isLoading && state.pageList.length == 0">
+				<image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
+				<text class="no_data_text">暂无数据</text>
+			</view>
+			<uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
+		</scroll-view>
+	</view>
 </template>
 
-<script>
+<script setup>
+	import navBar from '@/components/navBar.vue';
+	import {queryHonorReviewDataList} from '@/reqApi/teacherHonor.js';
+	import {
+		reactive,
+		nextTick,
+		onMounted
+	} from 'vue';
+	const state = reactive({
+		tabList:[{
+            label:'待审核',
+            value:'0'
+        },{
+            label:'已审核',
+            value:'1'
+        },{
+            label:'已驳回',
+            value:'2',
+        }],
+		pageLists:[],//全部数据
+		pageList:[],//列表数据
+		queryStr:'',
+		honorStatus:'0',//0-待审核,1-已审核(审核通过),2-已驳回(审核失败)
+		pageSize: 30,
+		pageNum: 1,
+		pages:0,
+		scrollTop:0,
+		oldScrollTop:0,
+		isRefreshing:false,//设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
+		isLoading:true,//加载中
+		noMoreData:false,//没有更多数据了
+		loadStatus:'more',//loading状态
+	})
+	onMounted(() => {
+		OnLoadData(true);
+	});
+	//切换条件选择器
+	const CommonFilterData = (filterOptions,type) => {
+		const { searchWord,tabsSelected } = filterOptions;
+		state.queryStr = searchWord;
+		state.honorStatus = tabsSelected;
+		OnLoadData(true)
+	}
+	/*
+	*获取数据
+	* initPage:初始分页 从第一开始
+	*/
+	const OnLoadData = (initPage) => {
+		state.isLoading = true;
+		state.loadStatus = 'loading';
+		if (initPage) {
+			// uni.showLoading({
+			// 	title: '加载中'
+			// });
+			state.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
+			state.pageLists = [];
+			state.pageList = [];
+			queryHonorReviewDataList({
+				honorStatus:state.honorStatus,
+				queryStr:state.queryStr
+			}).then(res=>{
+				if(res.code == 200){
+					const tableData = res?.data || [];
+					state.pageLists = tableData;//全部数据
+					const total = tableData?.length || 0;//总数
+					state.pages = Math.ceil(total / state.pageSize);//总页数
+					//默认加载
+					state.pageList = state.pageLists.slice(0, state.pageSize);
+					//没有更多了
+					state.noMoreData = state.pageNum == state.pages;
+					//分页+1
+					state.pageNum++;
+					state.isLoading = false;
+					state.isRefreshing = false;//下拉刷新未被触发
+					state.loadStatus = state.noMoreData?'no-more':'more';
+				}
+			}).finally(()=>{
+				// uni.hideLoading();
+				//返回到页面顶部
+				if(initPage){
+					GoTop();
+				}
+			})
+		}else{
+			const start = (state.pageNum - 1) * state.pageSize;
+			const end = start + state.pageSize;
+			const list = state.pageLists.slice(start, end);
+			state.pageList = [...state.pageList, ...list];
+			state.noMoreData = state.pageNum == state.pages;
+			state.pageNum++;
+			setTimeout(() => {
+				state.isLoading = false;
+				state.isRefreshing = false; //下拉刷新未被触发
+				state.loadStatus = state.noMoreData?'no-more':'more';
+			}, 0)
+		}
+	}
+	// 自定义下拉刷新被触发	 下拉刷新
+	const OnRefresh = () =>{
+		state.isRefreshing = true;//下拉刷新已经被触发
+		OnLoadData(true);
+	}
+	//滚动到底部/右边 触发上拉刷新
+	const OnLoadMore = () => {
+		if (state.isLoading || state.noMoreData) return;
+		OnLoadData(false);
+	}
+	//	滚动时触发
+	const OnScroll = (e) => {
+		state.oldScrollTop = e.detail.scrollTop;
+	}
+	//返回到顶部
+	const GoTop = () => {
+		// 解决view层不同步的问题
+		state.scrollTop = state.oldScrollTop;
+		nextTick(() => {
+			state.scrollTop = 0
+		});
+	}
+	const HonorAuditDetail = (honorTypeId,honorTypeName) => {
+		uni.navigateTo({
+			url: `/pages/teacherHonor/honorAudit/honorAuditDetail?honorTypeId=${honorTypeId}&honorStatus=${state.honorStatus}&honorTypeName=${encodeURIComponent(honorTypeName)}`
+		});
+	}
 </script>
 
-<style>
+<style lang="scss" scoped>
+	.page_content{
+		height: calc(100% - 360rpx);
+		.scroll_view_y{
+			height: 100%;
+			.refresh_loading{
+				padding: 10rpx 0;
+			}
+		}
+		.page_list{
+			display: flex;
+			width: 100%;
+			flex-wrap: wrap;
+			gap: 24rpx 22rpx;
+			.list_item{
+				display: flex;
+				flex-direction: column;
+				width: calc((100% - 22rpx) / 2);
+				min-height: 156rpx;
+				padding: 24rpx;
+				background: linear-gradient( 180deg, #EDF4FF 0%, #FFFFFF 100%);
+				border-radius: 20rpx;
+				border: 2rpx solid #E4E7ED;
+				box-sizing: border-box;
+				.honor_name{
+					width: 100%;
+					font-weight: 500;
+					font-size: 36rpx;
+					color: #333333;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+				.audit_status{
+					margin-top: 24rpx;
+					width: 100%;
+					display: flex;
+					justify-content: space-between;
+					.status{
+						font-weight: 400;
+						font-size: 24rpx;
+						flex: 1;
+						word-break: break-all;
+						margin-right: 5rpx;
+						&.wait{
+							color: #2E64FA;
+						}
+						&.pass{
+							color: #2BC644;
+						}
+						&.reject{
+							color: #F56C6C;
+						}
+					}
+				}
+				.btn{
+					font-weight: 400;
+					font-size: 24rpx;
+					color: #2E64FA;
+					flex-shrink: 0;
+				}
+			}
+		}
+	}
 </style>

+ 342 - 0
pages/teacherHonor/honorAudit/teacherDetail.vue

@@ -0,0 +1,342 @@
+<template>
+	<view class="page_body">
+		<!-- 头部 -->
+		<nav-bar height="96" :title="state.pageTitle" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
+		<view :class="['page_content',{'no_show_ft':state.pageFoot=='0'}]">
+			<view class="row_item">
+				<view class="row_label">荣誉类型:</view>
+				<view class="row_value">{{state.reviewDetail?.honorTypeName}}</view>
+			</view>
+			<view class="row_item" v-for="item in (state.reviewDetail?.honorDataList || [])" :key="item.id">
+				<view class="row_label">{{item?.honorDictLibName ?? ''}}:</view>
+				<view class="row_value">{{item?.honorDictLibValue ?? ''}}</view>
+			</view>
+			<view class="row_item">
+				<view class="row_label">荣誉证书:</view>
+				<view class="row_value img_list">
+					<!-- <view class="img_item" v-for="item in (state.reviewDetail?.honorDataCertList || [])" :key="item.id">
+						<image class="pic" mode="aspectFit" :src="item.picUrl"></image>
+					</view> -->
+					<uv-upload :maxCount="state.reviewDetail?.honorDataCertList?.length || 0" :deletable="false" :fileList="state.reviewDetail?.honorDataCertList" width="240rpx" height="140rpx" multiple :previewFullImage="true">
+					</uv-upload>
+				</view>
+			</view>
+			<view class="row_item">
+				<view class="row_label">佐证材料:</view>
+				<view class="row_value document_list">
+					<view class="document_item" v-for="item in (state.reviewDetail?.supportDocumentList || [])" :key="item.id">
+						<image class="file" src="@/static/image/icon/file.png"></image>
+						<view class="file_name">
+							<view class="name">{{item.documentName}}</view>
+							<view class="file_size_date">
+								<view class="file_size">{{item.fileSize}}</view>
+								<view class="file_date">{{item.createdAt}}</view>
+							</view>
+						</view>
+						<view class="preview_btn">预览</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="page_foot" v-if="pageFoot=='1'">
+			<button v-if="state.reviewDetail?.honorStatus != 2" class="btn btn_reject" type="primary" plain="true" :loading="state.loading" @click="HandleRejectHonor">驳回</button>
+			<button v-if="state.reviewDetail?.honorStatus != 1" class="btn btn_pass" type="primary" :loading="state.loading" @click="HandlePassHonor">通过</button>
+		</view>
+		<customPopupDialog ref="popupDelDialogRef" title="驳回" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" @DialogConfirm="DialogConfirm">
+			<view class="dialog_content_input">
+				<view class="dialog_desc">请填写驳回原因方便教师修改</view>
+				<uni-easyinput type="textarea" v-model="state.rejectReason" :style="state.easyinputStyle" placeholderStyle="font-weight: 400;font-size: 28rpx;color: #999999;" placeholder="请输入驳回原因…"></uni-easyinput>
+			</view>
+		</customPopupDialog>
+	</view>
+</template>
+
+<script setup>
+	import navBar from '@/components/navBar.vue';
+	import uvUpload from '@/uni_modules/uv-upload/components/uv-upload/uv-upload.vue';
+	import customPopupDialog from '@/components/customPopupDialog.vue';
+	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
+	import {queryReviewDetail,audioHonorData} from '@/reqApi/teacherHonor.js';
+	import {
+		reactive,
+		ref,
+		nextTick
+	} from 'vue';
+	import { onLoad } from '@dcloudio/uni-app';
+	const state = reactive({
+		id:'',//id
+		pageTitle:'',
+		pageFoot:'0',
+		loading:false,
+		reviewDetail:{},
+		rejectReason:'',//驳回原因
+		easyinputStyle:{
+			color: '#303133',
+			borderColor: '#E9E9E9'
+		}
+	})
+	const popupDelDialogRef = ref(null);
+	onLoad((option) => {
+		state.id = option.id;
+		state.pageTitle = option.pageTitle;
+		state.pageFoot = option.pageFoot;//是否显示页脚
+		uni.setStorageSync('isLoadHonorAuditList', false);
+		GetReviewDetail();
+	})
+	//查询荣誉审核资质信息详情数据接口
+	const GetReviewDetail = () => {
+		queryReviewDetail(state.id).then(res=>{
+			if(res.code == 200){
+				const resdata = res.data || null;
+				const honorDataCertList = resdata?.honorDataCertList || [];
+				resdata.honorDataCertList = honorDataCertList.map(item=>({
+					...item,
+					url:item.picUrl
+				}))
+				state.reviewDetail = resdata;
+			}
+		})
+	}
+	//通过
+	const HandlePassHonor = () => {
+		HandleRejectOrPass(1)
+	}
+	//驳回
+	const HandleRejectHonor = () => {
+		popupDelDialogRef.value.OpenDialog();
+	}
+	//驳回 确定按钮
+	const DialogConfirm = () => {
+		if(state.rejectReason == '' || state.rejectReason == null){
+			uni.showToast({
+				title: '请填写驳回原因!',
+				icon: 'none'
+			})
+			return false
+		}
+		HandleRejectOrPass(2)
+	}
+	const HandleRejectOrPass = (status) => {
+		const params = {
+			honorDataId: state.id,
+			honorStatus: status
+		}
+		if(status == 2){
+			params.content = state.rejectReason;
+		}
+		audioHonorData(params).then(res=>{
+			if(res.code == 200){
+				uni.showToast({
+					title: '审核通过!',
+					icon: 'none'
+				})
+				uni.setStorageSync('isLoadHonorAuditList', true);//刷新荣誉审核列表
+				GoBack();
+			}
+		})
+	}
+	const GoBack = () => {
+		uni.navigateBack({
+			delta: 1
+		});
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page_body{
+		height: 100%;
+		min-height: auto;
+	}
+	.page_content{
+		height: calc(100% - 290rpx);
+		&.no_show_ft{
+			height: calc(100% - 96rpx);
+		}
+		flex-direction: column;
+		flex-wrap: initial;
+		overflow: auto;
+		.row_item{
+			display: flex;
+			width: 100%;
+			padding: 32rpx 0;
+			border-bottom: 2rpx solid #F3F3F3;
+			.row_label{
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #666666;
+				flex-shrink: 0;
+				margin-right: 10rpx;
+			}
+			.row_value{
+				flex: 1;
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #333333;
+				text-align: right;
+				display: flex;
+				justify-content: flex-end;
+				word-break: break-all;
+				flex-wrap: wrap;
+				&.img_list{
+					// gap: 32rpx 24rpx;
+					// .img_item{
+					// 	width: 240rpx;
+					// 	height: 140rpx;
+					// 	background-color: #FFFFFF;
+					// 	border-radius: 12rpx;
+					// 	border: 2rpx solid #CED1D8;
+					// 	display: inline-flex;
+					// 	align-items: center;
+					// 	justify-content: center;
+					// 	.pic{
+					// 		width: 240rpx;
+					// 		height: 140rpx;
+					// 	}
+					// }
+					:deep(.uv-upload__wrap){
+						gap: 24rpx 32rpx;
+						.uv-upload__wrap__preview{
+							background-color: #FFFFFF;
+							border-radius: 20rpx;
+							border: 2rpx solid #CED1D8;
+							box-sizing: border-box;
+							margin: 0;
+							.uv-upload__deletable{
+								top:10rpx;
+								right: 10rpx;
+								background-color: transparent;
+								height: 32rpx;
+								width: 32rpx;
+								border: 2rpx solid #999999;
+								border-radius: 50%;
+								.uvicon-close{
+									color:#999999 !important;
+									font-size: 11rpx !important;
+								}
+							}
+							.uv-upload__deletable__icon{
+								transform: scale(1);
+								top:50%;
+								right: 50%;
+								transform: translate(50%, -50%);
+							}
+							.uv-upload__success{
+								border-width: 20rpx;
+							}
+						}
+					}
+				}
+				&.document_list{
+					gap: 24rpx;
+					.document_item{
+						width: 100%;
+						padding: 20rpx 0 18rpx 15rpx;
+						display: flex;
+						background-color: #FFFFFF;
+						border-radius: 20rpx;
+						border: 2rpx solid #EBEEF5;
+						.file{
+							width: 80rpx;
+							height: 80rpx;
+							margin-right: 16rpx;
+						}
+						.file_name{
+							flex: 1;
+							display: flex;
+							flex-direction: column;
+							.name{
+								font-weight: 500;
+								font-size: 28rpx;
+								text-align: left;
+								color: #333333;
+							}
+							.file_size_date{
+								display: flex;
+								width: 100%;
+								text-align: left;
+								font-weight: 400;
+								font-size: 24rpx;
+								color: #999999;
+								margin-top: 8rpx;
+								gap: 20rpx;
+								.file_size{
+									white-space: nowrap;
+								}
+								.file_date{
+									white-space: nowrap;
+								}
+							}
+						}
+						.preview_btn{
+							width: 96rpx;
+							box-sizing: border-box;
+							text-align: left;
+							font-weight: 400;
+							font-size: 28rpx;
+							color: #2E64FA;
+							padding:20rpx 0 0 16rpx;
+						}
+					}
+				}
+			}
+		}
+	}
+	.page_foot{
+		position: fixed;
+		left:0;
+		right: 0;
+		bottom: 0;
+		padding: 40rpx 24rpx 38rpx 24rpx;
+		box-shadow: inset 0px 2rpx 0px 0px #F3F3F3;
+		background-color:#FFFFFF;
+		gap: 30rpx;
+		display: flex;
+		.btn{
+			height: 90rpx;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			border-radius: 8rpx;
+			font-weight: 500;
+			font-size: 28rpx;
+			flex: 1;
+			box-sizing: border-box;
+		}
+		.btn_reject{
+			border: 2rpx solid #F56C6C;
+			color: #F56C6C;
+		}
+		.btn_pass{
+			background-color: #2E64FA !important;
+			color: #FFFFFF;
+		}
+	}
+	.dialog_content_input{
+		width: 100%;
+		display: flex;
+		flex-direction: column;
+		box-sizing: border-box;
+		.dialog_desc{
+			font-weight: 400;
+			font-size: 32rpx;
+			color: #303133;
+		}
+		:deep(.uni-easyinput){
+			margin-top: 20rpx;
+			font-size: 28rpx;
+			.is-input-border{
+				border-radius: 8rpx;
+				border-color: #E9E9E9 !important;
+			}
+			.uni-easyinput__content-textarea{
+				height: 400rpx;
+				min-height: 400rpx;
+				font-size: 28rpx;
+				margin:20rpx 24rpx 20rpx 14rpx;
+			}
+			.input-padding{
+				padding-left: 10rpx;
+			}
+		}
+	}
+</style>

+ 287 - 0
pages/teacherHonor/honorOverview/honorTypeAuditDetail.vue

@@ -0,0 +1,287 @@
+<template>
+	<view class="page_body">
+		<!-- 头部 -->
+		<nav-bar :height="state.reviewStatus=='1'?'224':'312'" :title="state.honorTypeName" :tabList="state.tabList" :onlyTitle="false" :showHeadSearchInput="false" :showFilterPicker="false" :showTabs="state.reviewStatus=='1'?false:true" :showTabBtns="false" rightWidth="0rpx" @CommonFilterData="CommonFilterData" @GoBack="GoBack">
+			<template #tabs_top_content>
+				<!-- 搜索框单独一行 -->
+				<view class="nav_head row_search_box">
+					<uni-easyinput class="search_box" v-model="state.searchWord" trim="all" :styles="state.searchInputStyles"
+						:placeholderStyle="state.searchInputPlaceholderStyle" placeholder="请输入关键字搜索" @input="HandleSearchInput">
+						<template #left>
+							<image src="@/static/image/overview/search.png" class="search_icon"></image>
+						</template>
+					</uni-easyinput>
+				</view>
+			</template>
+		</nav-bar>
+		<view :class="['page_content',{'no_review':state.reviewStatus=='1'}]">
+			<scroll-view
+			    class="scroll_view_y"
+				:scroll-top="state.scrollTop"
+				scroll-y="true"
+				:refresher-enabled="false" 
+				:enable-back-to-top="true"
+				:show-scrollbar="false" 
+				:scroll-with-animation="true" 
+				refresher-default-style="none"
+				refresher-background="#F8F9FD" 
+				:refresher-triggered="state.isRefreshing" 
+				@scrolltolower="OnLoadMore"
+				@refresherrefresh="OnRefresh"
+				@scroll="OnScroll">
+				<view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
+				<view class="page_list">
+					<view class="list_item" v-for="item in state.pageList" :key="item.id" @click="PreviewHonorDetail(item.id,item?.honorTypeDictBOS?.[1]?.name)">
+						<view class="list_item_top">
+							<image class="pic" mode="aspectFit" :src="item?.picUrls?.[0]"></image>
+							<view class="title">{{item?.honorTypeDictBOS?.[1]?.name}}</view>
+						</view>
+						<view class="name_time">
+							<view class="name">获奖人:{{item?.honorTypeDictBOS?.[0]?.name}}</view>
+							<view class="time">{{item?.honorTypeDictBOS?.[2]?.name}}</view>
+						</view>
+					</view>
+				</view>
+				<view class="no_data" v-if="!state.isLoading && state.pageList.length == 0">
+					<image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
+					<text class="no_data_text">暂无数据</text>
+				</view>
+				<uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import navBar from '@/components/navBar.vue';
+	import {queryHonorTableDataList} from '@/reqApi/teacherHonor.js';
+	import {
+		reactive,
+		nextTick
+	} from 'vue';
+	import { onLoad } from '@dcloudio/uni-app';
+	const state = reactive({
+		tabList:[{
+			label:'已审核',
+			value:'1',
+		},{
+			label:'待审核',
+			value:'0',
+		},{
+			label:'已驳回',
+			value:'2',
+		}],
+		searchWord:'',//搜索
+		searchInputStyles: { //搜素框样式
+			borderColor: '#DCDFE6',
+			color: '#333333'
+		},
+		searchInputPlaceholderStyle: 'color: #C0C4CC;fontSize: 28rpx', //搜素框样式
+		pageLists:[],//全部数据
+		pageList:[],//列表数据
+		honorTypeId: '',//荣誉类型id
+		honorTypeName:'',//荣誉类型名称
+		reviewStatus:'0',//0 需审核 1无审核
+		honorStatus:'1',//审核状态
+		pageSize: 30,
+		pageNum: 1,
+		pages:0,
+		scrollTop:0,
+		oldScrollTop:0,
+		isRefreshing:false,//设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
+		isLoading:true,//加载中
+		noMoreData:false,//没有更多数据了
+		loadStatus:'more',//loading状态
+	})
+	onLoad((option) => {
+		state.honorTypeId = option.honorTypeId;
+		state.reviewStatus = option.reviewStatus;//0 需审核 1无审核
+		if(state.reviewStatus=='1'){
+			state.tabList = [];
+		}
+		state.honorTypeName = decodeURIComponent(option.honorTypeName);
+		OnLoadData(true);
+	});
+	//切换条件选择器
+	const CommonFilterData = (filterOptions,type) => {
+		const { tabsSelected } = filterOptions;
+		state.honorStatus = tabsSelected;
+		OnLoadData(true)
+	}
+	//搜索
+	const HandleSearchInput = (e) => {
+		state.searchWord = e;
+		OnLoadData(true)
+	}
+	/*
+	*获取数据
+	* initPage:初始分页 从第一开始
+	* honorTypeId  -- 荣誉类型id
+	* honorStatus--  0 待审核  1 已审核  2已驳回
+	* queryStr: "", 
+	* dictValueIds   --查询条件中 ,下拉选选择中的明细的id数组
+	* startDate ---  获奖开始时间 
+	* endDate: ""
+	*/
+	const OnLoadData = (initPage) => {
+		state.isLoading = true;
+		state.loadStatus = 'loading';
+		if (initPage) {
+			// uni.showLoading({
+			// 	title: '加载中'
+			// });
+			state.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
+			queryHonorTableDataList({
+				dictNameIds:[],
+				dictValueIds:[],
+				honorTeacherIds:[],
+				honorTypeId:state.honorTypeId,//荣誉类型id
+				honorStatus:state.reviewStatus=='1'?'':state.honorStatus,//审核状态
+				queryStr:state.searchWord,
+				startDate:'',
+				endDate:''
+			}).then(res=>{
+				if(res.code == 200){
+					const tableData = res?.data?.tableDataList || [];
+					state.pageLists = tableData;//全部数据
+					const total = tableData?.length || 0;//总数
+					state.pages = Math.ceil(total / state.pageSize);//总页数
+					//默认加载
+					state.pageList = state.pageLists.slice(0, state.pageSize);
+					console.log(state.pageList,12121)
+					//没有更多了
+					state.noMoreData = state.pageNum == state.pages;
+					//分页+1
+					state.pageNum++;
+					state.isLoading = false;
+					state.isRefreshing = false;//下拉刷新未被触发
+					state.loadStatus = state.noMoreData?'no-more':'more';
+				}
+			}).finally(()=>{
+				// uni.hideLoading();
+				//返回到页面顶部
+				if(initPage){
+					GoTop();
+				}
+			})
+		}else{
+			const start = (state.pageNum - 1) * state.pageSize;
+			const end = start + state.pageSize;
+			const list = state.pageLists.slice(start, end);
+			state.pageList = [...state.pageList, ...list];
+			state.noMoreData = state.pageNum == state.pages;
+			state.pageNum++;
+			setTimeout(() => {
+				state.isLoading = false;
+				state.isRefreshing = false; //下拉刷新未被触发
+				state.loadStatus = state.noMoreData?'no-more':'more';
+			}, 0)
+		}
+	}
+	// 自定义下拉刷新被触发	 下拉刷新
+	const OnRefresh = () =>{
+		state.isRefreshing = true;//下拉刷新已经被触发
+		OnLoadData(true);
+	}
+	//滚动到底部/右边 触发上拉刷新
+	const OnLoadMore = () => {
+		if (state.isLoading || state.noMoreData) return;
+		OnLoadData(false);
+	}
+	//	滚动时触发
+	const OnScroll = (e) => {
+		state.oldScrollTop = e.detail.scrollTop;
+	}
+	//返回到顶部
+	const GoTop = () => {
+		// 解决view层不同步的问题
+		state.scrollTop = state.oldScrollTop;
+		nextTick(() => {
+			state.scrollTop = 0
+		});
+	}
+	//返回
+	const GoBack = () => {
+		uni.navigateBack({
+			delta: 1
+		});
+	}
+	const PreviewHonorDetail = (id,honorName) => {
+		uni.navigateTo({
+			url: `/pages/teacherHonor/honorAudit/teacherDetail?id=${id}&pageTitle=${honorName}&pageFoot=0`
+		});
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page_body{
+		height: 100%;
+		min-height: auto;
+		.page_content{
+			height: calc(100% - 312rpx);
+			&.no_review{
+				height: calc(100% - 224rpx);
+			}
+			.scroll_view_y{
+				height: 100%;
+				.refresh_loading{
+					padding: 10rpx 0;
+				}
+			}
+			.page_list{
+				display: flex;
+				flex-wrap: wrap;
+				width: 100%;
+				gap: 24rpx 22rpx;
+				.list_item{
+					display: flex;
+					flex-direction: column;
+					justify-content: space-between;
+					width: calc((100% - 22rpx) / 2);
+					padding: 6rpx 14rpx 24rpx;
+					background-color: #F8F9FD;
+					border-radius: 20rpx;
+					box-sizing: border-box;
+					.list_item_top{
+						display: flex;
+						flex-direction: column;
+						.pic{
+							width: 100%;
+							height: 198rpx;
+							border-radius: 8rpx;
+						}
+						.title{
+							width: 100%;
+							font-weight: 500;
+							font-size: 28rpx;
+							margin-top: 8rpx;
+							color: #333333;
+							display: -webkit-box;
+							-webkit-line-clamp: 2;
+							-webkit-box-orient: vertical;
+							overflow: hidden;
+							text-overflow: ellipsis;
+						}
+					}
+					.name_time{
+						width: 100%;
+						display: flex;
+						margin-top: 16rpx;
+						font-weight: 400;
+						font-size: 20rpx;
+						color: #333333;
+						.name{
+							flex:1;
+							white-space: nowrap;
+							overflow: hidden;
+							text-overflow: ellipsis;
+						}
+						.time{
+							flex-shrink: 0;
+						}
+					}
+				}
+			}
+		}
+	}
+</style>

+ 17 - 10
pages/teacherHonor/honorOverview/honorTypeDetail.vue

@@ -1,16 +1,16 @@
-<template>
+<template>
 	<view class="page_body">
 		<!-- 头部 -->
 		<nav-bar height="96" :title="state.title" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
 		<view class="page_content">
-			<uni-forms ref="valiForm" class="forms_style" :model="state.formData" :rules="rules" border labelWidth="140rpx" label-align="right">
+			<uni-forms ref="valiForm" class="forms_style" :model="state.formData" border labelWidth="140rpx" label-align="right">
 				<uni-forms-item label="类型名称:">
 					<uni-easyinput v-model="state.formData.honorTypeName" :maxlength="30" placeholder="请输入类型名称" />
 				</uni-forms-item>
 				<uni-forms-item label="审核机制:">
 					<uni-data-checkbox v-model="state.formData.reviewStatus" selectedColor="#2E64FA" selectedTextColor="#333333" :localdata="state.radioList"></uni-data-checkbox>
 				</uni-forms-item>
-				<uni-forms-item label="审核人:">
+				<uni-forms-item label="审核人:" v-if="state.formData.reviewStatus===0">
 					<zxz-uni-data-select v-model="state.formData.auditPeople" collapse-tags :collapse-tags-num="1" filterable multiple dataKey="dataKey" dataValue="dataValue" :clear="false" :localdata="state.teacherData"></zxz-uni-data-select>
 				</uni-forms-item>
 				<uni-forms-item label-width="0px">
@@ -72,10 +72,6 @@
 		dictionaryData:[],//字典库
 		loading:false
 	})
-	const rules = ref({
-		honorTypeName: { rules: [{ required: true, errorMessage: '请输入类型名称' }] },
-		auditPeople: { rules: [{ required: true, errorMessage: '请选择审核人' }] },
-	});
 	onLoad((option) => {
 		state.id = option.id;
 		state.title = state.id?'修改荣誉类型':'新建荣誉类型';
@@ -142,7 +138,7 @@
 		queryHonorDetailData(state.id).then(res=>{
 			if(res.code == 200){
 				state.formData.honorTypeName = res?.data?.honorTypeName || '';
-				const reviewStatusList = res?.data?.reviewStatus || 0;
+				state.formData.reviewStatus = res?.data?.reviewStatus || 0;
 				const auditPeopleList = res?.data?.reviewTeacherData || [];
 				state.formData.auditPeople = auditPeopleList.map(item=>`${item.reviewTeacherId}——${item.reviewTeacherName}`);//审核人
 				//提交信息
@@ -177,9 +173,20 @@
 	}
 	//提交
 	const honorTypeConfirm = () => {
-		for (const key in rules.value) {
+		let rulesList = {}
+		if(state.formData.reviewStatus===0){
+			rulesList = {
+				honorTypeName: { rules: [{ required: true, errorMessage: '请输入类型名称' }] },
+				auditPeople: { rules: [{ required: true, errorMessage: '请选择审核人' }] },
+			}
+		}else{
+			rulesList = {
+				honorTypeName: { rules: [{ required: true, errorMessage: '请输入类型名称' }] },
+			}
+		}
+		for (const key in rulesList) {
 			if (state.formData?.[key] === "" || state.formData?.[key] === null || state.formData?.[key]&&state.formData?.[key]?.length === 0) {
-				const errorMessage = rules.value[key].rules[0].errorMessage;
+				const errorMessage = rulesList[key].rules[0].errorMessage;
 				uni.showToast({
 					title: errorMessage,
 					icon: 'none'

+ 10 - 3
pages/teacherHonor/honorOverview/index.vue

@@ -29,10 +29,10 @@
 		</view>
 		<view class="honor_list">
 			<template v-if="state.tableData.length > 0">
-				<view class="list_item" v-for="item in state.tableData" :key="item.honorTypeId">
+				<view class="list_item" v-for="item in state.tableData" :key="item.honorTypeId" @click="HonorTypeAuditDetail(item.honorTypeId,item.reviewStatus,item.honorTypeName)">
 					<view class="item_name">
 						<text class="name">{{item.honorTypeName}}</text>
-						<view class="more_icon" @click="ShowMorePopupDialog(item.honorTypeName,item.honorTypeId,item.deleteStatus)">
+						<view class="more_icon" @click.stop="ShowMorePopupDialog(item.honorTypeName,item.honorTypeId,item.deleteStatus)">
 							<uni-icons type="more-filled" style="color: #999999;font-size: 32rpx;"></uni-icons>
 						</view>
 					</view>
@@ -58,7 +58,7 @@
 				</view>
 				<uni-icons class="content_item_right" type="right" style="color: #333333;font-size: 36rpx;"></uni-icons>
 			</view>
-			<view class="content_item" @click="DeleteHonorTypeItem">
+			<view class="content_item" v-if="state.popupDelDialog.deleteStatus!=1" @click="DeleteHonorTypeItem">
 				<view class="content_item_left">
 					<image class="img" src="/static/image/icon/delete.png"></image>
 					<text :class="['text',{'red':state.popupDelDialog.deleteStatus!=1,'disabled':state.popupDelDialog.deleteStatus==1}]">删除</text>
@@ -189,6 +189,13 @@
 			url: `/pages/teacherHonor/honorOverview/honorTypeDetail?id=${id}`
 		});
 	}
+	//查看荣誉详情
+	const HonorTypeAuditDetail = (honorTypeId,reviewStatus,honorTypeName) => {
+		uni.navigateTo({
+			url: `/pages/teacherHonor/honorOverview/honorTypeAuditDetail?honorTypeId=${honorTypeId}&reviewStatus=${reviewStatus}&honorTypeName=${encodeURIComponent(honorTypeName)}`
+		});
+	}
+	
 </script>
 
 <style lang="scss" scoped>

+ 191 - 0
pages/teacherHonor/honorTeacher/honorDetail.vue

@@ -0,0 +1,191 @@
+<template>
+	<view class="page_body">
+		<!-- 头部 -->
+		<nav-bar height="96" :title="state.honorName" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
+		<view class="page_content">
+			<view class="row_item">
+				<view class="row_label">荣誉类型:</view>
+				<view class="row_value">{{state.detail?.honorTypeName}}</view>
+			</view>
+			<view class="row_item" v-for="item in (state.detail?.honorDataList || [])" :key="item.id">
+				<view class="row_label">{{item?.honorDictLibName ?? ''}}:</view>
+				<view class="row_value">{{item?.honorDictLibValue ?? ''}}</view>
+			</view>
+			<view class="row_item">
+				<view class="row_label">荣誉证书:</view>
+				<view class="row_value img_list">
+					<!-- <view class="img_item" v-for="item in (state.detail?.honorDataCertList || [])" :key="item.id">
+						<image class="pic" mode="aspectFit" :src="item.picUrl"></image>
+					</view> -->
+					<uv-upload :maxCount="state.detail?.honorDataCertList?.length || 0" :deletable="false" :fileList="state.detail?.honorDataCertList" width="240rpx" height="140rpx" multiple :previewFullImage="true">
+					</uv-upload>
+				</view>
+			</view>
+			<view class="row_item">
+				<view class="row_label">佐证材料:</view>
+				<view class="row_value document_list">
+					<view class="document_item" v-for="item in (state.detail?.supportDocumentList || [])" :key="item.id">
+						<image class="file" src="@/static/image/icon/file.png"></image>
+						<view class="file_name">
+							<view class="name">{{item.documentName}}</view>
+							<view class="file_size_date">
+								<view class="file_size">{{item.fileSize}}</view>
+								<view class="file_date">{{item.createdAt}}</view>
+							</view>
+						</view>
+						<view class="preview_btn">预览</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import navBar from '@/components/navBar.vue';
+	import uvUpload from '@/uni_modules/uv-upload/components/uv-upload/uv-upload.vue';
+	import {queryHonorTeacherDetail} from '@/reqApi/teacherHonor.js';
+	import {
+		reactive,
+		ref,
+		nextTick
+	} from 'vue';
+	import { onLoad } from '@dcloudio/uni-app';
+	const state = reactive({
+		id:'',//id
+		honorName:'',
+		loading:false,
+		detail:{}
+	})
+	const popupDelDialogRef = ref(null);
+	onLoad((option) => {
+		state.id = option.id;
+		state.honorName = decodeURIComponent(option.honorName);
+		GetHonorTeacherDetail();
+	})
+	//查询荣誉审核资质信息详情数据接口
+	const GetHonorTeacherDetail = () => {
+		queryHonorTeacherDetail(state.id).then(res=>{
+			if(res.code == 200){
+				const resdata = res.data || null;
+				const honorDataCertList = resdata?.honorDataCertList || [];
+				resdata.honorDataCertList = honorDataCertList.map(item=>({
+					...item,
+					url:item.picUrl
+				}))
+				state.detail = resdata;
+			}
+		})
+	}
+	const GoBack = () => {
+		uni.navigateBack({
+			delta: 1
+		});
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page_body{
+		height: 100%;
+		min-height: auto;
+	}
+	.page_content{
+		height: calc(100% - 96rpx);
+		flex-direction: column;
+		flex-wrap: initial;
+		overflow: auto;
+		.row_item{
+			display: flex;
+			width: 100%;
+			padding: 32rpx 0;
+			border-bottom: 2rpx solid #F3F3F3;
+			.row_label{
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #666666;
+				flex-shrink: 0;
+				margin-right: 10rpx;
+			}
+			.row_value{
+				flex: 1;
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #333333;
+				text-align: right;
+				display: flex;
+				justify-content: flex-end;
+				word-break: break-all;
+				flex-wrap: wrap;
+				&.img_list{
+					gap: 32rpx 24rpx;
+					.img_item{
+						width: 240rpx;
+						height: 140rpx;
+						background-color: #FFFFFF;
+						border-radius: 12rpx;
+						border: 2rpx solid #CED1D8;
+						display: inline-flex;
+						align-items: center;
+						justify-content: center;
+						.pic{
+							width: 240rpx;
+							height: 140rpx;
+						}
+					}
+				}
+				&.document_list{
+					gap: 24rpx;
+					.document_item{
+						width: 100%;
+						padding: 20rpx 0 18rpx 15rpx;
+						display: flex;
+						background-color: #FFFFFF;
+						border-radius: 20rpx;
+						border: 2rpx solid #EBEEF5;
+						.file{
+							width: 80rpx;
+							height: 80rpx;
+							margin-right: 16rpx;
+						}
+						.file_name{
+							flex: 1;
+							display: flex;
+							flex-direction: column;
+							.name{
+								font-weight: 500;
+								font-size: 28rpx;
+								text-align: left;
+								color: #333333;
+							}
+							.file_size_date{
+								display: flex;
+								width: 100%;
+								text-align: left;
+								font-weight: 400;
+								font-size: 24rpx;
+								color: #999999;
+								margin-top: 8rpx;
+								gap: 20rpx;
+								.file_size{
+									white-space: nowrap;
+								}
+								.file_date{
+									white-space: nowrap;
+								}
+							}
+						}
+						.preview_btn{
+							width: 96rpx;
+							box-sizing: border-box;
+							text-align: left;
+							font-weight: 400;
+							font-size: 28rpx;
+							color: #2E64FA;
+							padding:20rpx 0 0 16rpx;
+						}
+					}
+				}
+			}
+		}
+	}
+</style>

+ 5 - 4
pages/teacherHonor/honorTeacher/honorTeacherDetail.vue

@@ -19,10 +19,10 @@
 				@scroll="OnScroll">
 				<view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
 				<view class="page_list">
-					<view class="list_item" v-for="item in state.pageList" :key="item.id" @click="PreviewHonorDetail(item.id)">
+					<view class="list_item" v-for="item in state.pageList" :key="item.id" @click="PreviewHonorDetail(item.id,item.honorName)">
 						<view class="item_honor_type">
 							<view class="honor_type_item">{{item.awardDatetime}}</view>
-							<view class="honor_type_item">{{item.awardDatetime}}</view>
+							<view class="honor_type_item">{{item.honorTypeName}}</view>
 						</view>
 						<view class="honor_content">
 							<image class="honor_pic" :src="item.certPicUrl"></image>
@@ -195,9 +195,9 @@
 			delta: 1
 		});
 	}
-	const PreviewHonorDetail = () => {
+	const PreviewHonorDetail = (id,honorName) => {
 		uni.navigateTo({
-			url: `/pages/teacherHonor/honorTeacher/honorTeacherDetail?teacherId=${teacherId}`
+			url: `/pages/teacherHonor/honorTeacher/honorDetail?id=${id}&honorName=${encodeURIComponent(honorName)}`
 		});
 	}
 </script>
@@ -229,6 +229,7 @@
 					.item_honor_type{
 						display: flex;
 						width: 100%;
+						align-items: center;
 						.honor_type_item{
 							font-weight: 400;
 							font-size: 32rpx;

+ 1 - 1
pages/teacherHonor/index.vue

@@ -7,7 +7,7 @@
 		<!-- 我的荣誉 -->
 		<my-honor v-if="state.tabBarValue=='myHonor'"></my-honor>
 		<!-- 荣誉审核 -->
-		<honor-audit v-if="state.tabBarValue=='myHonor'"></honor-audit>
+		<honor-audit v-if="state.tabBarValue=='honorAudit'"></honor-audit>
 		<!-- 底部tabbar -->
 		<pageTabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></pageTabbar>
 	</view>

+ 158 - 17
pages/teacherHonor/myHonor/index.vue

@@ -1,9 +1,19 @@
 <template>
 	<!-- 头部 -->
-	<nav-bar height="336" title="教师专业发展" :tabList="state.tabList" :tabBtns="state.tabBtns" :onlyTitle="false" :showFilterPicker="false" :showTabs="true" :showTabBtns="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData" @GoBack="GoBack">
+	<nav-bar height="336" backPath="workspace" title="教师专业发展" rightType="button" :tabList="state.tabList" :tabBtns="state.tabBtns" :onlyTitle="false" :showFilterPicker="false" :showTabs="true" :showTabBtns="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData">
+		<template #headRightIcon>
+			<view class="upload_button">
+				<image class="img" src="@/static/image/icon/plusempty.png"></image>
+				<text class="text" @click="UploadEditMyHonor('')">上传荣誉</text>
+			</view>
+		</template>
 		<template #tab_btns_lef>
-			<uni-data-select class="date_select" v-model="state.dateType" :clear="false" placeholder="请选择" :localdata="state.dateTypeList">
-			</uni-data-select>
+			<view class="select_box" @click="ShowDateTimePopup">
+				<view class="select_label">{{state.dateTypeText}}</view>
+				<uni-icons :type="state.showStatus?'up':'down'" class="select_icon"></uni-icons>
+			</view>
+			<!-- <uni-data-select class="date_select" v-model="state.dateType" :clear="false" placeholder="请选择" :localdata="state.dateTypeList">
+			</uni-data-select> -->
 		</template>
 	</nav-bar>
 	<view class="page_content">
@@ -23,17 +33,21 @@
 			@scroll="OnScroll">
 			<view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
 			<view class="page_list">
-				<view class="list_item" v-for="item in state.pageList" :key="item.id" @click="PreviewHonorDetail(item.id)">
+				<view class="list_item" v-for="item in state.pageList" :key="item.id" @click="UploadEditMyHonor(item)">
 					<view class="item_honor_type">
-						<view class="honor_type_item">{{item.awardDatetime}}</view>
-						<view class="honor_type_item">{{item.awardDatetime}}</view>
+						<view class="type_item_left">
+							<view class="honor_type_item">{{item.awardDatetime}}</view>
+							<view class="honor_type_item">{{item.honorTypeName}}</view>
+						</view>
+						<!-- reviewStatus: 0 需审核  1 无审核 honorStatus:0 待审核  1 已完成  2 已驳回 -->
+						<view :class="['del',{'disable':Number(item.reviewStatus) === 0 && Number(item.honorStatus) === 1}]" @click="DeleteMyHonor(item)">删除</view>
 					</view>
 					<view class="honor_content">
 						<image class="honor_pic" :src="item.certPicUrl"></image>
 						<view class="honor_info">
 							<view class="honor_name">{{item.honorName}}</view>
 							<view class="teacher_name">获奖人:{{item.teacherName}}</view>
-							<!-- 0 需审核  1 无审核 -->
+							<!-- reviewStatus: 0 需审核  1 无审核 honorStatus:0 待审核  1 已完成  2 已驳回 -->
 							<view :class="['honor_status',{'waiting':item.honorStatus===0,'finish':item.reviewStatus == 1 || item.honorStatus===1,'reject':item.honorStatus===2}]">
 								<template v-if="item.honorStatus===0">待审核</template>
 								<template v-if="item.reviewStatus == 1 || item.honorStatus===1">已完成</template>
@@ -51,16 +65,31 @@
 			<uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
 		</scroll-view>
 	</view>
+	<!-- 删除确认框 -->
+	<popupDialog ref="popupDelDialogRef" :showFootBorder="true" :content="state.popupDelDialog.content" @DialogConfirm="DialogDelConfirm" />
+	<uni-popup class="my_honor_selector_popup" ref="topPopup" :animation="false" type="top" background-color="#fff" :style="{'top':`${state.selectorPopupTop}rpx`}" @change="ChangeSelectorPopup">
+		<view :class="['selector_item',{'selector_item_active':state.dateType==item.value}]" v-for="item in state.dateTypeList" @click="SelectDateTime(item)">
+			<view class="selector_item_text">{{item.text}}</view>
+			<uni-icons type="checkmarkempty" v-if="state.dateType==item.value" class="selector_item_icon"></uni-icons>
+		</view>
+	</uni-popup>
 </template>
 
 <script setup>
 	import navBar from '@/components/navBar.vue';
-	import {queryHonorTypeList,queryHonorList} from '@/reqApi/teacherHonor.js';
+	import uniDataSelect from '@/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue';
+	import popupDialog from '@/components/popupDialog.vue';
+	import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue';
+	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
+	import {queryHonorTypeList,queryHonorList,deleteHonorData} from '@/reqApi/teacherHonor.js';
 	import {
 		reactive,
 		nextTick,
-		onMounted
+		ref,
+		onMounted,
+		onUnmounted
 	} from 'vue';
+	import { onShow } from '@dcloudio/uni-app';
 	const state = reactive({
 		tabList:[],
 		dateTypeList:[{
@@ -93,6 +122,7 @@
 		pageList:[],//列表数据
 		honorTypeId:'',//荣誉类型
 		dateType:'',//获奖时间 全部-不传值,最近三个月-3,最近 6 个月-6,最近一年-12	
+		dateTypeText:'全部',
 		honorStatus:'',//0-待审核,1-已审核(审核通过),2-已驳回(审核失败)
 		pageSize: 30,
 		pageNum: 1,
@@ -103,11 +133,31 @@
 		isLoading:true,//加载中
 		noMoreData:false,//没有更多数据了
 		loadStatus:'more',//loading状态
+		popupDelDialog:{
+			id:'',
+			content:'是否确认删除?'
+		},
+		selectorPopupTop:'313',//弹框距离顶部的高度
+		showStatus:false,
 	})
-	onMounted((option) => {
+	const topPopup = ref(null);
+	const popupDelDialogRef = ref(null);
+	onShow(()=>{
+		const isLoadMyHonorList = uni.getStorageSync('isLoadMyHonorList');
+		if(isLoadMyHonorList){
+			OnLoadData(true);
+		}
+	})
+	onMounted(() => {
+		const sys = uni.getSystemInfoSync();
+		const statusBarRpx = sys.statusBarHeight * 750 / sys.screenWidth;
+		state.selectorPopupTop = Math.ceil(Math.ceil(statusBarRpx) + 313);
 		GetHonorTypeList();
 		OnLoadData(true);
 	});
+	onUnmounted(()=>{
+		uni.setStorageSync('isLoadHonorOverview', false);
+	})
 	//查询荣誉类型列表
 	const GetHonorTypeList = () => {
 		queryHonorTypeList().then(res=>{
@@ -206,17 +256,61 @@
 			state.scrollTop = 0
 		});
 	}
-	//返回
-	const GoBack = () => {
-		uni.navigateBack({
-			delta: 1
-		});
+	/*
+	*删除
+	*reviewStatus: 0 需审核  1 无审核
+	*honorStatus:0 待审核  1 已完成  2 已驳回
+	*/
+	const DeleteMyHonor = (item) => {
+		if(Number(item.reviewStatus) === 0 && Number(item.honorStatus) === 1) return false;
+		state.popupDelDialog.id = item.id;
+		popupDelDialogRef.value.OpenDialog();
+	}
+	//确定删除
+	const DialogDelConfirm = () => {
+		deleteHonorData(state.popupDelDialog.id).then(res=>{
+			if(res.code == 200){
+				uni.showToast({
+					title: '删除成功!',
+					icon: 'none'
+				});
+				OnLoadData(true);
+			}else{
+				uni.showToast({
+					title: res.msg,
+					icon: 'none'
+				});
+			}
+		})
 	}
-	const PreviewHonorDetail = () => {
+	//上传 修改 查看荣誉
+	const UploadEditMyHonor = (item) => {
+		const id = item?.id || '';
+		let honorStatus = Number(item.honorStatus);//0 待审核  1 已完成  2 已驳回
+		let reviewStatus = Number(item.reviewStatus);//0 需审核  1 无审核
+		let type = '',honorName = '';
+		if(item){
+			type = reviewStatus === 1  || honorStatus === 0 ? 'edit' : 'view';
+			honorName = type == 'view' ? encodeURIComponent(item.honorName) : '';
+		}else{
+			type = 'upload'
+		}
 		uni.navigateTo({
-			url: `/pages/teacherHonor/honorTeacher/honorTeacherDetail?teacherId=${teacherId}`
+			url: `/pages/teacherHonor/myHonor/uploadEditMyHonor?id=${id}&type=${type}&honorName=${honorName}`
 		});
 	}
+	//显示弹框
+	const ShowDateTimePopup = () => {
+		topPopup.value.open();
+	}
+	const SelectDateTime = (item) => {
+		state.dateTypeText = item.text;
+		state.dateType = item.value;
+		topPopup.value.close();
+	}
+	const ChangeSelectorPopup = (e) => {
+		state.showStatus = e.show;
+	}
 </script>
 
 <style lang="scss" scoped>
@@ -231,6 +325,31 @@
 			width: 196rpx;
 		}
 	}
+	.select_box{
+		width: 196rpx;
+		height: 72rpx;
+		padding: 16rpx;
+		flex-shrink: 0;
+		display: flex;
+		align-items: center;
+		box-sizing: border-box;
+		background-color: #FFFFFF;
+		border-radius: 8rpx;
+		border: 2rpx solid #DCDFE6;
+		font-weight: 400;
+		font-size: 28rpx;
+		color: #333333;
+		.select_label{
+			flex:1;
+			white-space: nowrap;
+			overflow: hidden;
+			text-overflow: ellipsis;
+		}
+		.select_icon{
+			font-size: 32rpx;
+			margin-left: 10rpx;
+		}
+	}
 	.page_content{
 		height: calc(100% - 472rpx);
 		.scroll_view_y{
@@ -254,6 +373,22 @@
 				.item_honor_type{
 					display: flex;
 					width: 100%;
+					align-items: center;
+					.type_item_left{
+						display: flex;
+						flex:1;
+						align-items: center;
+						overflow: hidden;
+					}
+					.del{
+						font-weight: 400;
+						font-size: 28rpx;
+						color: #F56C6C;
+						&.disable{
+							color: #bbbbbb;
+							
+						}
+					}
 					.honor_type_item{
 						font-weight: 400;
 						font-size: 32rpx;
@@ -262,6 +397,12 @@
 							font-weight: 500;
 							margin-right: 24rpx;
 						}
+						&:nth-child(2){
+							flex:1;
+							white-space: nowrap;
+							overflow: hidden;
+							text-overflow: ellipsis;
+						}
 					}
 				}
 				.honor_content{

+ 435 - 0
pages/teacherHonor/myHonor/uploadEditMyHonor.vue

@@ -0,0 +1,435 @@
+<template>
+	<view class="page_body">
+		<!-- 头部 -->
+		<nav-bar height="96" :title="state.title" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
+		<view :class="['page_content',{'view':state.type=='view'}]">
+			<uni-forms ref="valiForm" :class="['forms_style',{'view':state.type=='view'}]" :rules="rules" :model="state.formData" border labelWidth="160rpx" label-align="right">
+				<uni-forms-item label="荣誉类型:" required name="honorTypeValue">
+					<template v-if="state.type=='view'">{{state.formData.honorTypeValue}}</template>
+					<uni-data-select v-else v-model="state.formData.honorTypeValue" :clear="false" placeholder="请选择荣誉类型" :disabled="state.type=='upload'?false:true" :localdata="state.honorTypeData" style="width: 400rpx;" @change="ChangeDataSelect"></uni-data-select>
+				</uni-forms-item>
+				<template v-for="item in state.dynamicFormData">
+					<uni-forms-item :label="`${item.dictName}:`" required :name="`input_${item.id}`">
+						<template v-if="state.type=='view'">
+							<template v-if="item.dictType == 1 || item.dictType == 2">{{state.formData[`${item.dictShowType}_${item.id}`]}}</template>
+							<uni-dateformat v-else :date="state.formData[`${item.dictShowType}_${item.id}`]" format="yyyy-MM-dd"></uni-dateformat>
+						</template>
+						<template v-else>
+							<uni-easyinput v-if="item.dictType == 1" v-model="state.formData[`${item.dictShowType}_${item.id}`]" :disabled="item.onlyRead == 1" :maxlength="100" :placeholder="`请输入${item.dictName}`" :style="{width: item.dictName=='获奖人'?'400rpx':''}" />
+							<uni-data-select v-else-if="item.dictType == 2" v-model="state.formData[`${item.dictShowType}_${item.id}`]" :clear="false" :placeholder="`请选择${item.dictName}`" :localdata="item.dictList" style="width: 400rpx;"></uni-data-select>
+							<uni-datetime-picker v-else type="date" :end="todayStr" :clear-icon="false" v-model="state.formData[`${item.dictShowType}_${item.id}`]" placeholder=" " style="width: 400rpx;" />
+						</template>
+					</uni-forms-item>
+				</template>
+				<uni-forms-item label="荣誉证书:" required name="certPicDatas">
+					<uv-upload :maxCount="state.type=='view'?state?.formData?.certPicDatas?.length:50" :deletable="state.type!='view'" :fileList="state.formData.certPicDatas" width="240rpx" height="140rpx" multiple :previewFullImage="true" @AfterRead="AfterRead" @delete="DeletePic">
+						<view class="upload_btn" v-if="state.type!='view'">
+							<uni-icons class="upload_btn_icon" type="plusempty"></uni-icons>
+							<view class="upload_btn_text">上传荣誉证书</view>
+						</view>
+					</uv-upload>
+				</uni-forms-item>
+				<uni-forms-item label="佐证材料:">
+				</uni-forms-item>
+				<uni-forms-item label="驳回原因 :" v-if="state.formData.overruleContent">
+					{{state.formData.overruleContent}}
+				</uni-forms-item>
+				<uni-forms-item label="审核人:" v-if="state.type=='view'">
+					{{state.formData.reviewTeacherName}}
+				</uni-forms-item>
+			</uni-forms>
+		</view>
+		<view class="page_foot" v-if="state.type!='view'"><button class="btn_submit" type="primary" :loading="state.loading" @click="honorTypeConfirm">确认</button></view>
+	</view>
+</template>
+
+<script setup>
+	import navBar from '@/components/navBar.vue';
+	import uniForms from '@/uni_modules/uni-forms/components/uni-forms/uni-forms.vue';
+	import uniFormsItem from '@/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue';
+	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
+	import uniDataSelect from '@/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue';
+	import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue';
+	import uniDateformat from '@/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue';
+	import uvUpload from '@/uni_modules/uv-upload/components/uv-upload/uv-upload.vue';
+	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
+	import {queryHonorTypeDictList,queryHonorDataDetailById,addHonorCertData,updateHonorCertData} from '@/reqApi/teacherHonor.js';
+	import { unioformDateTransform,fileSizeConvert } from '@/common/common.js';
+	import {
+		reactive,
+		ref,
+		onMounted,
+		nextTick
+	} from 'vue';
+	import { onLoad } from '@dcloudio/uni-app';
+	const state = reactive({
+		id:'',//荣誉类型id
+		title:'',//标题
+		type:'',
+		formData: {
+			honorTypeValue: '',//荣誉类型id
+			certPicDatas:[],//荣誉证书
+			docDatas:[],//佐证材料
+			overruleContent:'',//驳回原因
+			reviewTeacherName:''//审核人
+		},
+		honorTypeData:[],//荣誉类型下拉
+		dynamicFormData:[],//动态表单
+		dictDatas:[],//动态表单传参
+		loading:false
+	})
+	const todayStr = ref(new Date().toISOString().split('T')[0]);
+	const rules = ref({
+		honorTypeValue: { rules: [{ required: true, errorMessage: '请选择荣誉类型' }] },
+		certPicDatas: { rules: [{ required: true, errorMessage: '请上传荣誉证书' }] },
+	});
+	onLoad(async(option) => {
+		state.id = option.id;
+		state.type = option.type;
+		await GetHonorTypeDictList(option.type);//查询荣誉类型下拉数据
+		if(option.type == 'upload'){
+			state.title = '上传荣誉';
+		}else{
+			if(option.type == 'edit'){
+				state.title = '修改荣誉';
+			}else {
+				state.title = decodeURIComponent(option.honorName);
+			}
+			GetHonorDataDetailById();
+		}
+		uni.setStorageSync('isLoadMyHonorList', false);//返回列表页是否重新加载我的荣誉列表
+	})
+	//查询荣誉类型下拉数据
+	const GetHonorTypeDictList = async (type) => {
+		await queryHonorTypeDictList().then(res=>{
+			if(res.code == 200){
+				const honorTypeData = res?.data?.honorTypeDictBOS || [];
+				honorTypeData.forEach(item=>{
+					item.value = item.id;
+					item.text = item.honorTypeName;
+					const honorTypeDictData = item?.honorTypeDictBOS || [];
+					honorTypeDictData.forEach(itemList=>{
+						if(itemList.dictType == 2){//单选
+							itemList.dictShowType = 'radio';
+							const dictList = itemList?.dictList || [];
+							dictList.forEach(dic=>{
+								dic.text = dic.dictName;
+								dic.value = dic.id
+							})
+						}else if(itemList.dictType == 1){//输入框
+							itemList.dictShowType = 'input';
+						}else{//日期
+							itemList.dictShowType = 'datetime';
+						}
+					})
+				})
+				state.honorTypeData = honorTypeData;//荣誉类型下拉数据;
+				if(type == 'upload'){//荣誉类型默认值
+					state.formData.honorTypeValue = honorTypeData?.[0].value || '';//荣誉类型默认值
+					const honorTypeDictData = honorTypeData?.[0]?.honorTypeDictBOS || [];
+					SetDynamicFormData(honorTypeDictData);//设置动态表单
+				}
+			}else{
+				state.honorTypeData = [];
+			}
+		})
+	}
+	//设置动态表单的值
+	const SetDynamicFormData = (honorTypeDictData) => {
+		honorTypeDictData.forEach(item=>{
+			if(item.dictType == 2){//单选
+				state.formData[`${item.dictShowType}_${item.id}`] = '';
+				rules.value[`${item.dictShowType}_${item.id}`] = { rules: [{ required: true, errorMessage: `请选择${item.dictName}` }] };
+			}else {//输入框 日期
+				const value = item?.dictList?.[0]?.dictName || '';
+				state.formData[`${item.dictShowType}_${item.id}`] = value;
+				const placeholder = item.dictType == 1 ? '请输入' : '请选择';
+				rules.value[`${item.dictShowType}_${item.id}`] = { rules: [{ required: true, errorMessage: `${placeholder}${item.dictName}` }] };
+			}
+		})
+		state.dynamicFormData = honorTypeDictData;//动态表单
+	}
+	//改变荣誉类型
+	const ChangeDataSelect = (val) => {
+		const selectedHonorType = state.honorTypeData.find(item=>item.id == val);
+		const honorTypeDictData = selectedHonorType?.honorTypeDictBOS || [];
+		SetDynamicFormData(honorTypeDictData)
+	}
+	//查看荣誉详情接口
+	const GetHonorDataDetailById = () => {
+		queryHonorDataDetailById(state.id).then(res=>{
+			if(res.code == 200){
+				const honorTypeId = res?.data?.honorTypeId || '';
+				const honorTypeName = res?.data?.honorTypeName || '';
+				state.formData.honorTypeValue = state.type=='view'?honorTypeName:honorTypeId;//荣誉类型默认值
+				//给动态表单设置值
+				const selectedHonorType = state.honorTypeData.find(item=>item.id == honorTypeId);
+				const honorTypeDictData = selectedHonorType?.honorTypeDictBOS || [];
+				state.dynamicFormData = honorTypeDictData;//动态表单
+				const honorDataList = res?.data?.honorDataList || '';
+				honorDataList.forEach(item=>{
+					if(item.dictType == 2){//单选
+						state.formData[`radio_${item.honorDictLibNameId}`] = state.type!='view'?item.honorDictLibValueId:item.honorDictLibValue;
+					}else {//输入框 日期
+						const type = item.dictType == 1 ? 'input' : 'datetime';
+						state.formData[`${type}_${item.honorDictLibNameId}`] = item.honorDictLibValue;
+					}
+				})
+				const honorDataCertList = res?.data?.honorDataCertList || [];//荣誉证书
+				state.formData.certPicDatas = honorDataCertList.map(item=>({
+					...item,
+					url:item.picUrl
+				}));//荣誉证书
+				state.formData.docDatas = res?.data?.supportDocumentList || [];//佐证材料
+				state.formData.overruleContent = res?.data?.overruleContent || '';//驳回原因
+				state.formData.reviewTeacherName = res?.data?.reviewTeacherName || '';//审核人
+			}
+		})
+	}
+	//提交
+	const honorTypeConfirm = () => {
+		for (const key in rules.value) {
+			if (state.formData?.[key] === "" || state.formData?.[key] === null || state.formData?.[key]&&state.formData?.[key]?.length === 0) {
+				const errorMessage = rules.value[key].rules[0].errorMessage;
+				uni.showToast({
+					title: errorMessage,
+					icon: 'none'
+				})
+				return false
+			}
+		}
+		state.loading = true;
+		const certPicDatas = state.formData.certPicDatas.map(item=>({
+			name:item.picName,
+			url:item.url,
+			fileSize:item?.fileSize || '',
+			time:item?.time || '',
+		}))
+		const docDatas = state.formData.docDatas.map(item=>({
+			name:item.documentName,
+			url:item.documentUrl,
+			fileSize:item.fileSize,
+			time:item.createdAt,
+		}))
+		const dictDatas = [];
+		state.dynamicFormData.forEach(item=>{
+			if(item.dictType == 2){//单选
+				const valueData = state.formData[`${item.dictShowType}_${item.id}`];
+				const dictList = item?.dictList || [];
+				const valueName = dictList.find(dic=>dic.id == valueData);
+				dictDatas.push({
+					keyId: item.id,
+					keyName: item.dictName,
+					valueId: valueData,
+					valueName: valueName?.dictName || ''
+				})
+			}else{
+				let valueData = state.formData[`${item.dictShowType}_${item.id}`];
+				if(state.type == 'upload' && Number(item.dictType) === 0){
+					valueData = valueData + ' 00:00:00'
+				}
+				const dictList = item?.dictList || []; 
+				dictDatas.push({
+					keyId: item.id,
+					keyName: item.dictName,
+					valueId: dictList?.[0]?.id,
+					valueName: valueData
+				})
+			}
+		})
+		let params =  {
+			certPicDatas: certPicDatas,
+			dictDatas: dictDatas,
+			docDatas:docDatas,
+			honorTypeId: state.formData.honorTypeValue
+		}
+		
+		if(state.id){
+			params.id = state.id;
+			updateHonorCertData(params).then(res=>{
+				if(res.code == 200){
+					uni.showToast({
+						title: '修改成功!',
+						icon: 'none'
+					})
+					uni.setStorageSync('isLoadMyHonorList', true);//返回列表页是否重新加载我的荣誉列表
+					GoBack();
+				}
+			}).finally(()=>{
+				state.loading = false;
+			})
+		}else{//新建
+			addHonorCertData(params).then(res=>{
+				if(res.code == 200){
+					uni.showToast({
+						title: '上传成功!',
+						icon: 'none'
+					})
+					uni.setStorageSync('isLoadMyHonorList', true);//返回列表页是否重新加载我的荣誉列表
+					GoBack();
+				}else{
+					
+				}
+			}).finally(()=>{
+				state.loading = false;
+			})
+		}
+	}
+	// 删除图片
+	const DeletePic = (event) => {
+		state.formData.certPicDatas.splice(event.index, 1);
+	}
+	// 新增图片
+	const AfterRead = async(event) => {
+		// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
+		let lists = [].concat(event.file);
+		let fileListLen = state.formData.certPicDatas.length;
+		lists.map((item) => {
+			state.formData.certPicDatas.push({
+				...item,
+				status: 'uploading',
+				message: '上传中'
+			})
+		})
+		for (let i = 0; i < lists.length; i++) {
+			const result = await uploadFilePromise(lists[i].url);
+			if(result.code == 200){
+				let item = state.formData.certPicDatas[fileListLen]
+				state.formData.certPicDatas.splice(fileListLen, 1, Object.assign(item, {
+					status: 'success',
+					message: '',
+					picName: result?.data?.fileName,
+					url:result?.data?.url,
+					fileSize:fileSizeConvert(lists?.[i]?.size || 0),
+					time:unioformDateTransform(new Date())
+				}))
+				fileListLen++
+			}
+			
+		}
+	}
+	const uploadFilePromise = (url) => {
+		return new Promise((resolve, reject) => {
+			const token = uni.getStorageSync('token'); // 假设从本地存储获取token
+			let a = uni.uploadFile({
+				url: '/api/v1/teach/oss/file/oss/upload_filesSele',
+				filePath: url,
+				name: 'file',
+				header:{
+					Authorization:token
+				},
+				success: (res) => {
+					const resdata = JSON.parse(res.data);
+					console.log(res,1323)
+					resolve(resdata)
+				}
+			});
+		})
+	}
+	const GoBack = () => {
+		uni.navigateBack({
+			delta: 1
+		});
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page_body{
+		height: 100%;
+		min-height: auto;
+		overflow: hidden;
+	}
+	.page_content{
+		height: calc(100% - 250rpx);
+		overflow: auto;
+		&.view{
+			height: calc(100% - 96rpx);
+		}
+	}
+	.forms_style{
+		:deep(.uni-forms-item){
+			&.uni-forms-item--border{
+				border-top: 0;
+				border-bottom: 2rpx solid #F3F3F3;
+				.uni-forms-item__label{
+					align-items: flex-start;
+					padding-top: 16rpx;
+				}
+			}
+			.uv-upload__wrap{
+				// flex-direction: row-reverse;
+				gap: 24rpx 32rpx;
+				.uv-upload__wrap__preview{
+					background-color: #FFFFFF;
+					border-radius: 20rpx;
+					border: 2rpx solid #CED1D8;
+					box-sizing: border-box;
+					margin: 0;
+					.uv-upload__deletable{
+						top:10rpx;
+						right: 10rpx;
+						background-color: transparent;
+						height: 32rpx;
+						width: 32rpx;
+						border: 2rpx solid #999999;
+						border-radius: 50%;
+						.uvicon-close{
+							color:#999999 !important;
+							font-size: 11rpx !important;
+						}
+					}
+					.uv-upload__deletable__icon{
+						transform: scale(1);
+						top:50%;
+						right: 50%;
+						transform: translate(50%, -50%);
+					}
+					.uv-upload__success{
+						border-width: 20rpx;
+					}
+				}
+				.upload_btn{
+					width: 240rpx;
+					height: 140rpx;
+					background-color: #FAFBFF;
+					border-radius: 20rpx;
+					border: 2rpx solid #CCCED4;
+					box-sizing: border-box;
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+					justify-content: center;
+					.upload_btn_icon{
+						font-size: 40rpx !important;
+						color: #CCCED4 !important;
+					}
+					.upload_btn_text{
+						margin-top: 16rpx;
+						font-weight: 400;
+						font-size: 24rpx;
+						color: #CCCED4;
+					}
+				}
+			}
+		}
+	}
+	.page_foot{
+		position: fixed;
+		left:0;
+		right: 0;
+		bottom: 0;
+		padding: 20rpx 24rpx;
+		background-color:#FFFFFF;
+		.btn_submit{
+			width: 100%;
+			height: 88rpx;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			background-color: #2E64FA !important;
+			border-radius: 20rpx;
+			color: #FFFFFF;
+			font-size: 28rpx;
+		}
+	}
+</style>

+ 1 - 1
pages/teacherStudy/videoDetails.vue

@@ -51,7 +51,7 @@
 			</view>
 		</view>
 		<!-- 提示框 -->
-		<customPopupDialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCloseBtn="false" @DialogConfirm="DialogConfirm">
+		<customPopupDialog ref="popupDialogRef" title="试题弹出" dialogWidth="702" :isMaskClick="false" :showDialogClose="false" :showCancelBtn="false" @DialogConfirm="DialogConfirm">
 			<view class="dialog_questions_input">
 				<view class="questions_title">{{state?.question?.codeName}}:{{state?.question?.questionsName}}</view>
 				<uni-easyinput type="textarea" v-model="state.questionsAnswer" :style="state.easyinputStyle" placeholder="请输入你的回答" placeholderStyle="font-weight: 400;font-size: 28rpx;color: #999999;"></uni-easyinput>

+ 36 - 0
reqApi/teacherHonor.js

@@ -33,4 +33,40 @@ export function queryHonorTeacherListDetail(data) {//教师荣誉详情
 }
 export function queryHonorList(data) {//查询我的荣誉列表接口
 	return request.post('/api/v1/queryHonorList',data)
+}
+export function queryHonorTypeDictList(data) {//查询荣誉类型下拉数据
+	return request.get('/api/v1/queryHonorTypeDictList',data)
+}
+export function queryHonorDataDetailById(id) {//查看荣誉详情接口
+	return request.get(`/api/v1/queryHonorDataDetailById/${id}`)
+}
+export function updateHonorCertData(data) {//上传荣誉证书接口
+	return request.post('/api/v1/updateHonorCertData',data)
+}
+export function addHonorCertData(data) {//修改荣誉证书接口
+	return request.post('/api/v1/addHonorCertData',data)
+}
+export function queryHonorReviewDataList(data) {//查询荣誉审核列表
+	return request.get('/api/v1/queryHonorReviewDataList',data)
+}
+export function queryHonorReviewDetailList(data) {//查询荣誉审核明细列表
+	return request.get('/api/v1/queryHonorReviewDetailList',data)
+}
+export function queryReviewDetail(id) {//查询荣誉审核资质信息详情数据接口
+	return request.get(`/api/v1/queryReviewDetail/${id}`)
+}
+export function audioHonorData(data) {//审核接口 (驳回/通过)
+	return request.post('/api/v1/audioHonorData',data)
+}
+export function queryHonorTableDataList(data) {//获取荣誉类型 审核详情 的数据接口
+	return request.post('/api/v1/queryHonorTableDataList',data)
+}
+export function queryHonorTeacherDetail(id) {//查询荣誉教师单个荣誉证书的详情数据接口
+	return request.get(`/api/v1/queryHonorTeacherDetail/${id}`)
+}
+export function deleteHonorData(id) {//删除荣誉证书接口
+	return request.get(`/api/v1/deleteHonorData/${id}`)
+}
+export function uploadFilesSele(data) {//文件上传
+	return request.post('/api/v1/teach/oss/file/oss/upload_filesSele',data)
 }

BIN
static/image/icon/plusempty.png


+ 101 - 5
style/common.scss

@@ -307,6 +307,7 @@ uni-page-body {
 .page_body {
 	width: 100%;
 	min-height: 100%;
+	box-sizing: border-box;
 	background-color: #FFFFFF;
 	//修改密码 协议
 	.page_module {
@@ -373,6 +374,11 @@ uni-page-body {
 	.uni-navbar--border{
 		border-bottom-color:#F3F3F3 !important;
 	}
+	&.custom_only_title_back_icon{
+		.uni-navbar__header-btns-left{
+			height: 96rpx;
+		}
+	}
 	.uni-navbar__header-btns-left{
 		position: absolute;
 		top: 0;
@@ -392,7 +398,7 @@ uni-page-body {
 		justify-content: space-between;
 		align-items: center;
 		height: 120rpx;
-		padding: 0 24px 0 96rpx;
+		padding: 0 24rpx 0 96rpx;
 		box-sizing: border-box;
 		box-shadow: 0px 4rpx 16rpx 0px rgba(0,0,0,0.02), inset 0px -2rpx 0px 0px #F3F3F3;
 		&.only_title{
@@ -402,6 +408,12 @@ uni-page-body {
 				text-align: center;
 			}
 		}
+		&.row_search_box{
+			padding: 24rpx 24rpx 0;
+			.search_box{
+				width: 100%;
+			}
+		}
 		// 标题
 		.nav_title{
 			font-weight: 600;
@@ -435,6 +447,25 @@ uni-page-body {
 				height: 72rpx;
 			}
 		}
+		.upload_button{
+			display: flex;
+			align-items: center;
+			height: 72rpx;
+			padding: 0 28rpx;
+			margin-right: 8rpx;
+			background-color: #2E64FA;
+			border-radius: 8rpx;
+			border: 2rpx solid #2E64FA;
+			.img{
+				width: 28rpx;
+				height: 28rpx;
+			}
+			.text{
+				font-weight: 500;
+				font-size: 28rpx;
+				color: #FFFFFF;
+			}
+		}
 	}
 	//条件选择器
 	.filter_picker {
@@ -565,6 +596,19 @@ uni-page-body {
 	width: 100%;
 	display: flex;
 	flex-direction: column;
+	&.view{
+		.uni-forms-item {
+			.uni-forms-item__label {
+				min-height: auto;
+				align-items: flex-start;
+			}
+			//文本
+			.uni-forms-item__content{
+				justify-content: flex-end;
+				text-align: right;
+			}
+		}
+	}
 	.uni-forms-item {
 		&.uni-forms-item--border{
 			padding: 24rpx 0;
@@ -574,7 +618,8 @@ uni-page-body {
 			font-weight: 400;
 			font-size: 28rpx;
 			color: #666666;
-			height: 72rpx;
+			min-height: 72rpx;
+			height: auto;
 			padding: 0;
 		}
 		.uni-forms-item__content {
@@ -589,7 +634,8 @@ uni-page-body {
 					border-radius: 8rpx;
 				}
 				.is-disabled {
-					background-color: #f5f7fa !important;
+					background-color: #F3F3F3 !important;
+					border: 2rpx solid #DCDFE6 !important;
 					color: #333333;
 				}
 				.uni-easyinput__content-input {
@@ -646,6 +692,10 @@ uni-page-body {
 					min-height: 72rpx;
 					box-sizing: border-box;
 					padding: 0 16rpx 0 24rpx;
+					&.uni-select--disabled{
+						background-color: #F3F3F3;
+						border: 2rpx solid #DCDFE6;
+					}
 					.uni-select__input-box {
 						height: 72rpx;
 						.uni-select__input-text {
@@ -696,7 +746,7 @@ uni-page-body {
 			//日期选择框
 			.uni-date {
 				.uni-date-x--border {
-					border: 1px solid #ebeef5;
+					border: 2rpx solid #E9E9E9;
 					border-radius: 8rpx;
 					.uni-date-x {
 						font-size: 28rpx;
@@ -705,6 +755,9 @@ uni-page-body {
 						.uniui-calendar {
 							font-size: 32rpx !important;
 						}
+						.icon-calendar {
+							font-size: 36rpx !important;
+						}
 						.uni-date__x-input {
 							height: 72rpx;
 							line-height: 72rpx;
@@ -771,7 +824,6 @@ uni-page-body {
 			display: inline-flex;
 			align-items: center;
 			.title {
-				margin-left: 16rpx;
 				flex: 1;
 				white-space: nowrap;
 				overflow: hidden;
@@ -841,4 +893,48 @@ uni-page-body {
 	font-size: 24rpx !important;
 	text-align: center !important;
 	white-space: nowrap;
+}
+// 我的荣誉头部下拉框
+.my_honor_selector_popup{
+	top:313rpx;
+	z-index: 999;
+	.maskClass{
+		opacity: 0 !important;
+	}
+	[name="content"]{
+		width: 196rpx;
+		left: 24rpx !important;
+		box-sizing: border-box;
+		border: 1px solid #EBEEF5;
+		border-radius: 6px !important;
+		box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
+		z-index: 3;
+		padding: 4px 0;
+		transition:none !important;
+		transform: none !important;
+	}
+	.uni-popup__wrapper{
+		.selector_item_active{
+		    color: #2E64FA !important;
+		    font-weight: 400;
+			.selector_item_icon{
+				color: #2E64FA !important;
+			}
+		}
+		.selector_item{
+		    justify-content: space-between;
+		    align-items: center;
+			display: flex;
+			cursor: pointer;
+			line-height: 70rpx;
+			font-size: 28rpx;
+			padding: 0px 20rpx;
+			.selector_item_text{
+				flex:1;
+			}
+			.selector_item_icon{
+				font-size: 28rpx !important;
+			}
+		}
+	}
 }

+ 10 - 0
uni_modules/uni-dateformat/changelog.md

@@ -0,0 +1,10 @@
+## 1.0.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-dateformat](https://uniapp.dcloud.io/component/uniui/uni-dateformat)
+## 0.0.5(2021-07-08)
+- 调整 默认时间不再是当前时间,而是显示'-'字符
+## 0.0.4(2021-05-12)
+- 新增 组件示例地址
+## 0.0.3(2021-02-04)
+- 调整为uni_modules目录规范
+- 修复 iOS 平台日期格式化出错的问题

+ 200 - 0
uni_modules/uni-dateformat/components/uni-dateformat/date-format.js

@@ -0,0 +1,200 @@
+// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型
+function pad(str, length = 2) {
+	str += ''
+	while (str.length < length) {
+		str = '0' + str
+	}
+	return str.slice(-length)
+}
+
+const parser = {
+	yyyy: (dateObj) => {
+		return pad(dateObj.year, 4)
+	},
+	yy: (dateObj) => {
+		return pad(dateObj.year)
+	},
+	MM: (dateObj) => {
+		return pad(dateObj.month)
+	},
+	M: (dateObj) => {
+		return dateObj.month
+	},
+	dd: (dateObj) => {
+		return pad(dateObj.day)
+	},
+	d: (dateObj) => {
+		return dateObj.day
+	},
+	hh: (dateObj) => {
+		return pad(dateObj.hour)
+	},
+	h: (dateObj) => {
+		return dateObj.hour
+	},
+	mm: (dateObj) => {
+		return pad(dateObj.minute)
+	},
+	m: (dateObj) => {
+		return dateObj.minute
+	},
+	ss: (dateObj) => {
+		return pad(dateObj.second)
+	},
+	s: (dateObj) => {
+		return dateObj.second
+	},
+	SSS: (dateObj) => {
+		return pad(dateObj.millisecond, 3)
+	},
+	S: (dateObj) => {
+		return dateObj.millisecond
+	},
+}
+
+// 这都n年了iOS依然不认识2020-12-12,需要转换为2020/12/12
+function getDate(time) {
+	if (time instanceof Date) {
+		return time
+	}
+	switch (typeof time) {
+		case 'string':
+			{
+				// 2020-12-12T12:12:12.000Z、2020-12-12T12:12:12.000
+				if (time.indexOf('T') > -1) {
+					return new Date(time)
+				}
+				return new Date(time.replace(/-/g, '/'))
+			}
+		default:
+			return new Date(time)
+	}
+}
+
+export function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') {
+	if (!date && date !== 0) {
+		return ''
+	}
+	date = getDate(date)
+	const dateObj = {
+		year: date.getFullYear(),
+		month: date.getMonth() + 1,
+		day: date.getDate(),
+		hour: date.getHours(),
+		minute: date.getMinutes(),
+		second: date.getSeconds(),
+		millisecond: date.getMilliseconds()
+	}
+	const tokenRegExp = /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|SSS|SS|S/
+	let flag = true
+	let result = format
+	while (flag) {
+		flag = false
+		result = result.replace(tokenRegExp, function(matched) {
+			flag = true
+			return parser[matched](dateObj)
+		})
+	}
+	return result
+}
+
+export function friendlyDate(time, {
+	locale = 'zh',
+	threshold = [60000, 3600000],
+	format = 'yyyy/MM/dd hh:mm:ss'
+}) {
+	if (time === '-') {
+		return time
+	}
+	if (!time && time !== 0) {
+		return ''
+	}
+	const localeText = {
+		zh: {
+			year: '年',
+			month: '月',
+			day: '天',
+			hour: '小时',
+			minute: '分钟',
+			second: '秒',
+			ago: '前',
+			later: '后',
+			justNow: '刚刚',
+			soon: '马上',
+			template: '{num}{unit}{suffix}'
+		},
+		en: {
+			year: 'year',
+			month: 'month',
+			day: 'day',
+			hour: 'hour',
+			minute: 'minute',
+			second: 'second',
+			ago: 'ago',
+			later: 'later',
+			justNow: 'just now',
+			soon: 'soon',
+			template: '{num} {unit} {suffix}'
+		}
+	}
+	const text = localeText[locale] || localeText.zh
+	let date = getDate(time)
+	let ms = date.getTime() - Date.now()
+	let absMs = Math.abs(ms)
+	if (absMs < threshold[0]) {
+		return ms < 0 ? text.justNow : text.soon
+	}
+	if (absMs >= threshold[1]) {
+		return formatDate(date, format)
+	}
+	let num
+	let unit
+	let suffix = text.later
+	if (ms < 0) {
+		suffix = text.ago
+		ms = -ms
+	}
+	const seconds = Math.floor((ms) / 1000)
+	const minutes = Math.floor(seconds / 60)
+	const hours = Math.floor(minutes / 60)
+	const days = Math.floor(hours / 24)
+	const months = Math.floor(days / 30)
+	const years = Math.floor(months / 12)
+	switch (true) {
+		case years > 0:
+			num = years
+			unit = text.year
+			break
+		case months > 0:
+			num = months
+			unit = text.month
+			break
+		case days > 0:
+			num = days
+			unit = text.day
+			break
+		case hours > 0:
+			num = hours
+			unit = text.hour
+			break
+		case minutes > 0:
+			num = minutes
+			unit = text.minute
+			break
+		default:
+			num = seconds
+			unit = text.second
+			break
+	}
+
+	if (locale === 'en') {
+		if (num === 1) {
+			num = 'a'
+		} else {
+			unit += 's'
+		}
+	}
+
+	return text.template.replace(/{\s*num\s*}/g, num + '').replace(/{\s*unit\s*}/g, unit).replace(/{\s*suffix\s*}/g,
+		suffix)
+}

+ 88 - 0
uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue

@@ -0,0 +1,88 @@
+<template>
+	<text>{{dateShow}}</text>
+</template>
+
+<script>
+	import {friendlyDate} from './date-format.js'
+	/**
+	 * Dateformat 日期格式化
+	 * @description 日期格式化组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=3279
+	 * @property {Object|String|Number} date 日期对象/日期字符串/时间戳
+	 * @property {String} locale 格式化使用的语言
+	 * 	@value zh 中文
+	 * 	@value en 英文
+	 * @property {Array} threshold 应用不同类型格式化的阈值
+	 * @property {String} format 输出日期字符串时的格式
+	 */
+	export default {
+		name: 'uniDateformat',
+		props: {
+			date: {
+				type: [Object, String, Number],
+				default () {
+					return '-'
+				}
+			},
+			locale: {
+				type: String,
+				default: 'zh',
+			},
+			threshold: {
+				type: Array,
+				default () {
+					return [0, 0]
+				}
+			},
+			format: {
+				type: String,
+				default: 'yyyy/MM/dd hh:mm:ss'
+			},
+			// refreshRate使用不当可能导致性能问题,谨慎使用
+			refreshRate: {
+				type: [Number, String],
+				default: 0
+			}
+		},
+		data() {
+			return {
+				refreshMark: 0
+			}
+		},
+		computed: {
+			dateShow() {
+				this.refreshMark
+				return friendlyDate(this.date, {
+					locale: this.locale,
+					threshold: this.threshold,
+					format: this.format
+				})
+			}
+		},
+		watch: {
+			refreshRate: {
+				handler() {
+					this.setAutoRefresh()
+				},
+				immediate: true
+			}
+		},
+		methods: {
+			refresh() {
+				this.refreshMark++
+			},
+			setAutoRefresh() {
+				clearInterval(this.refreshInterval)
+				if (this.refreshRate) {
+					this.refreshInterval = setInterval(() => {
+						this.refresh()
+					}, parseInt(this.refreshRate))
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 88 - 0
uni_modules/uni-dateformat/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uni-dateformat",
+  "displayName": "uni-dateformat 日期格式化",
+  "version": "1.0.0",
+  "description": "日期格式化组件,可以将日期格式化为1分钟前、刚刚等形式",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "日期格式化",
+    "时间格式化",
+    "格式化时间",
+    ""
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "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"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 11 - 0
uni_modules/uni-dateformat/readme.md

@@ -0,0 +1,11 @@
+
+
+### DateFormat 日期格式化
+> **组件名:uni-dateformat**
+> 代码块: `uDateformat`
+
+
+日期格式化组件。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-dateformat)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 9 - 0
uni_modules/uv-loading-icon/changelog.md

@@ -0,0 +1,9 @@
+## 1.0.3(2023-08-14)
+1. 新增参数textStyle,自定义文本样式
+## 1.0.2(2023-06-27)
+优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-loading-icon组件

+ 67 - 0
uni_modules/uv-loading-icon/components/uv-loading-icon/props.js

@@ -0,0 +1,67 @@
+export default {
+	props: {
+		// 是否显示组件
+		show: {
+			type: Boolean,
+			default: true
+		},
+		// 颜色
+		color: {
+			type: String,
+			default: '#909193'
+		},
+		// 提示文字颜色
+		textColor: {
+			type: String,
+			default: '#909193'
+		},
+		// 文字和图标是否垂直排列
+		vertical: {
+			type: Boolean,
+			default: false
+		},
+		// 模式选择,circle-圆形,spinner-花朵形,semicircle-半圆形
+		mode: {
+			type: String,
+			default: 'spinner'
+		},
+		// 图标大小,单位默认px
+		size: {
+			type: [String, Number],
+			default: 24
+		},
+		// 文字大小
+		textSize: {
+			type: [String, Number],
+			default: 15
+		},
+		// 文字样式
+		textStyle: {
+			type: Object,
+			default () {
+				return {}
+			}
+		},
+		// 文字内容
+		text: {
+			type: [String, Number],
+			default: ''
+		},
+		// 动画模式 https://www.runoob.com/cssref/css3-pr-animation-timing-function.html
+		timingFunction: {
+			type: String,
+			default: 'linear'
+		},
+		// 动画执行周期时间
+		duration: {
+			type: [String, Number],
+			default: 1200
+		},
+		// mode=circle时的暗边颜色
+		inactiveColor: {
+			type: String,
+			default: ''
+		},
+		...uni.$uv?.props?.loadingIcon
+	}
+}

+ 347 - 0
uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue

@@ -0,0 +1,347 @@
+<template>
+	<view
+		class="uv-loading-icon"
+		:style="[$uv.addStyle(customStyle)]"
+		:class="[vertical && 'uv-loading-icon--vertical']"
+		v-if="show"
+	>
+		<view
+			v-if="!webviewHide"
+			class="uv-loading-icon__spinner"
+			:class="[`uv-loading-icon__spinner--${mode}`]"
+			ref="ani"
+			:style="{
+				color: color,
+				width: $uv.addUnit(size),
+				height: $uv.addUnit(size),
+				borderTopColor: color,
+				borderBottomColor: otherBorderColor,
+				borderLeftColor: otherBorderColor,
+				borderRightColor: otherBorderColor,
+				'animation-duration': `${duration}ms`,
+				'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''
+			}"
+		>
+			<block v-if="mode === 'spinner'">
+				<!-- #ifndef APP-NVUE -->
+				<view
+					v-for="(item, index) in array12"
+					:key="index"
+					class="uv-loading-icon__dot"
+				>
+				</view>
+				<!-- #endif -->
+				<!-- #ifdef APP-NVUE -->
+				<!-- 此组件内部图标部分无法设置宽高,即使通过width和height配置了也无效 -->
+				<loading-indicator
+					v-if="!webviewHide"
+					class="uv-loading-indicator"
+					:animating="true"
+					:style="{
+						color: color,
+						width: $uv.addUnit(size),
+						height: $uv.addUnit(size)
+					}"
+				/>
+				<!-- #endif -->
+			</block>
+		</view>
+		<text
+			v-if="text"
+			class="uv-loading-icon__text"
+			:style="[{
+				fontSize: $uv.addUnit(textSize),
+				color: textColor,
+			},$uv.addStyle(textStyle)]"
+		>{{text}}</text>
+	</view>
+</template>
+
+<script>
+	import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const animation = weex.requireModule('animation');
+	// #endif
+	/**
+	 * loading 加载动画
+	 * @description 警此组件为一个小动画,目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。
+	 * @tutorial https://www.uvui.cn/components/loading.html
+	 * @property {Boolean}			show			是否显示组件  (默认 true)
+	 * @property {String}			color			动画活动区域的颜色,只对 mode = flower 模式有效(默认#909193)
+	 * @property {String}			textColor		提示文本的颜色(默认#909193)
+	 * @property {Boolean}			vertical		文字和图标是否垂直排列 (默认 false )
+	 * @property {String}			mode			模式选择,见官网说明(默认 'circle' )
+	 * @property {String | Number}	size			加载图标的大小,单位px (默认 24 )
+	 * @property {String | Number}	textSize		文字大小(默认 15 )
+	 * @property {String | Number}	text			文字内容 
+	 * @property {Object}	textStyle 文字样式
+	 * @property {String}			timingFunction	动画模式 (默认 'ease-in-out' )
+	 * @property {String | Number}	duration		动画执行周期时间(默认 1200)
+	 * @property {String}			inactiveColor	mode=circle时的暗边颜色 
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * @example <uv-loading mode="circle"></uv-loading>
+	 */
+	export default {
+		name: 'uv-loading-icon',
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				// Array.form可以通过一个伪数组对象创建指定长度的数组
+				// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
+				array12: Array.from({
+					length: 12
+				}),
+				// 这里需要设置默认值为360,否则在安卓nvue上,会延迟一个duration周期后才执行
+				// 在iOS nvue上,则会一开始默认执行两个周期的动画
+				aniAngel: 360, // 动画旋转角度
+				webviewHide: false, // 监听webview的状态,如果隐藏了页面,则停止动画,以免性能消耗
+				loading: false, // 是否运行中,针对nvue使用
+			}
+		},
+		computed: {
+			// 当为circle类型时,给其另外三边设置一个更轻一些的颜色
+			// 之所以需要这么做的原因是,比如父组件传了color为红色,那么需要另外的三个边为浅红色
+			// 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝,导致效果没有那么细腻良好)
+			otherBorderColor() {
+				const lightColor = colorGradient(this.color, '#ffffff', 100)[80]
+				if (this.mode === 'circle') {
+					return this.inactiveColor ? this.inactiveColor : lightColor
+				} else {
+					return 'transparent'
+				}
+			}
+		},
+		watch: {
+			show(n) {
+				// nvue中,show为true,且为非loading状态,就重新执行动画模块
+				// #ifdef APP-NVUE
+				if (n && !this.loading) {
+					setTimeout(() => {
+						this.startAnimate()
+					}, 30)
+				}
+				// #endif
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				setTimeout(() => {
+					// #ifdef APP-NVUE
+					this.show && this.nvueAnimate()
+					// #endif
+					// #ifdef APP-PLUS 
+					this.show && this.addEventListenerToWebview()
+					// #endif
+				}, 20)
+			},
+			// 监听webview的显示与隐藏
+			addEventListenerToWebview() {
+				// webview的堆栈
+				const pages = getCurrentPages()
+				// 当前页面
+				const page = pages[pages.length - 1]
+				// 当前页面的webview实例
+				const currentWebview = page.$getAppWebview()
+				// 监听webview的显示与隐藏,从而停止或者开始动画(为了性能)
+				currentWebview.addEventListener('hide', () => {
+					this.webviewHide = true
+				})
+				currentWebview.addEventListener('show', () => {
+					this.webviewHide = false
+				})
+			},
+			// #ifdef APP-NVUE
+			nvueAnimate() {
+				// nvue下,非spinner类型时才需要旋转,因为nvue的spinner类型,使用了weex的
+				// loading-indicator组件,自带旋转功能
+				this.mode !== 'spinner' && this.startAnimate()
+			},
+			// 执行nvue的animate模块动画
+			startAnimate() {
+				this.loading = true
+				const ani = this.$refs.ani
+				if (!ani) return
+				animation.transition(ani, {
+					// 进行角度旋转
+					styles: {
+						transform: `rotate(${this.aniAngel}deg)`,
+						transformOrigin: 'center center'
+					},
+					duration: this.duration,
+					timingFunction: this.timingFunction,
+					// delay: 10
+				}, () => {
+					// 每次增加360deg,为了让其重新旋转一周
+					this.aniAngel += 360
+					// 动画结束后,继续循环执行动画,需要同时判断webviewHide变量
+					// nvue安卓,页面隐藏后依然会继续执行startAnimate方法
+					this.show && !this.webviewHide ? this.startAnimate() : this.loading = false
+				})
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	$uv-loading-icon-color: #c8c9cc !default;
+	$uv-loading-icon-text-margin-left:4px !default;
+	$uv-loading-icon-text-color:$uv-content-color !default;
+	$uv-loading-icon-text-font-size:14px !default;
+	$uv-loading-icon-text-line-height:20px !default;
+	$uv-loading-width:30px !default;
+	$uv-loading-height:30px !default;
+	$uv-loading-max-width:100% !default;
+	$uv-loading-max-height:100% !default;
+	$uv-loading-semicircle-border-width: 2px !default;
+	$uv-loading-semicircle-border-color:transparent !default;
+	$uv-loading-semicircle-border-top-right-radius: 100px !default;
+	$uv-loading-semicircle-border-top-left-radius: 100px !default;
+	$uv-loading-semicircle-border-bottom-left-radius: 100px !default;
+	$uv-loading-semicircle-border-bottom-right-radiu: 100px !default;
+	$uv-loading-semicircle-border-style: solid !default;
+	$uv-loading-circle-border-top-right-radius: 100px !default;
+	$uv-loading-circle-border-top-left-radius: 100px !default;
+	$uv-loading-circle-border-bottom-left-radius: 100px !default;
+	$uv-loading-circle-border-bottom-right-radiu: 100px !default;
+	$uv-loading-circle-border-width:2px !default;
+	$uv-loading-circle-border-top-color:#e5e5e5 !default;
+	$uv-loading-circle-border-right-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-bottom-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-left-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-style:solid !default;
+	$uv-loading-icon-host-font-size:0px !default;
+	$uv-loading-icon-host-line-height:1 !default;
+	$uv-loading-icon-vertical-margin:6px 0 0 !default;
+	$uv-loading-icon-dot-top:0 !default;
+	$uv-loading-icon-dot-left:0 !default;
+	$uv-loading-icon-dot-width:100% !default;
+	$uv-loading-icon-dot-height:100% !default;
+	$uv-loading-icon-dot-before-width:2px !default;
+	$uv-loading-icon-dot-before-height:25% !default;
+	$uv-loading-icon-dot-before-margin:0 auto !default;
+	$uv-loading-icon-dot-before-background-color:currentColor !default;
+	$uv-loading-icon-dot-before-border-radius:40% !default;
+
+	.uv-loading-icon {
+		/* #ifndef APP-NVUE */
+		// display: inline-flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		color: $uv-loading-icon-color;
+
+		&__text {
+			margin-left: $uv-loading-icon-text-margin-left;
+			color: $uv-loading-icon-text-color;
+			font-size: $uv-loading-icon-text-font-size;
+			line-height: $uv-loading-icon-text-line-height;
+		}
+
+		&__spinner {
+			width: $uv-loading-width;
+			height: $uv-loading-height;
+			position: relative;
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			max-width: $uv-loading-max-width;
+			max-height: $uv-loading-max-height;
+			animation: uv-rotate 1s linear infinite;
+			/* #endif */
+		}
+
+		&__spinner--semicircle {
+			border-width: $uv-loading-semicircle-border-width;
+			border-color: $uv-loading-semicircle-border-color;
+			border-top-right-radius: $uv-loading-semicircle-border-top-right-radius;
+			border-top-left-radius: $uv-loading-semicircle-border-top-left-radius;
+			border-bottom-left-radius: $uv-loading-semicircle-border-bottom-left-radius;
+			border-bottom-right-radius: $uv-loading-semicircle-border-bottom-right-radiu;
+			border-style: $uv-loading-semicircle-border-style;
+		}
+
+		&__spinner--circle {
+			border-top-right-radius: $uv-loading-circle-border-top-right-radius;
+			border-top-left-radius: $uv-loading-circle-border-top-left-radius;
+			border-bottom-left-radius: $uv-loading-circle-border-bottom-left-radius;
+			border-bottom-right-radius: $uv-loading-circle-border-bottom-right-radiu;
+			border-width: $uv-loading-circle-border-width;
+			border-top-color: $uv-loading-circle-border-top-color;
+			border-right-color: $uv-loading-circle-border-right-color;
+			border-bottom-color: $uv-loading-circle-border-bottom-color;
+			border-left-color: $uv-loading-circle-border-left-color;
+			border-style: $uv-loading-circle-border-style;
+		}
+
+		&--vertical {
+			flex-direction: column
+		}
+	}
+
+	/* #ifndef APP-NVUE */
+	:host {
+		font-size: $uv-loading-icon-host-font-size;
+		line-height: $uv-loading-icon-host-line-height;
+	}
+
+	.uv-loading-icon {
+		&__spinner--spinner {
+			animation-timing-function: steps(12)
+		}
+
+		&__text:empty {
+			display: none
+		}
+
+		&--vertical &__text {
+			margin: $uv-loading-icon-vertical-margin;
+			color: $uv-content-color;
+		}
+
+		&__dot {
+			position: absolute;
+			top: $uv-loading-icon-dot-top;
+			left: $uv-loading-icon-dot-left;
+			width: $uv-loading-icon-dot-width;
+			height: $uv-loading-icon-dot-height;
+
+			&:before {
+				display: block;
+				width: $uv-loading-icon-dot-before-width;
+				height: $uv-loading-icon-dot-before-height;
+				margin: $uv-loading-icon-dot-before-margin;
+				background-color: $uv-loading-icon-dot-before-background-color;
+				border-radius: $uv-loading-icon-dot-before-border-radius;
+				content: " "
+			}
+		}
+	}
+
+	@for $i from 1 through 12 {
+		.uv-loading-icon__dot:nth-of-type(#{$i}) {
+			transform: rotate($i * 30deg);
+			opacity: 1 - 0.0625 * ($i - 1);
+		}
+	}
+
+	@keyframes uv-rotate {
+		0% {
+			transform: rotate(0deg)
+		}
+
+		to {
+			transform: rotate(1turn)
+		}
+	}
+
+	/* #endif */
+</style>

+ 87 - 0
uni_modules/uv-loading-icon/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-loading-icon",
+  "displayName": "uv-loading-icon 加载动画 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.3",
+  "description": "此组件为一个小动画,目前用在uv-ui的uv-load-more加载更多等组件,还可以运用在项目中正在加载状态场景。",
+  "keywords": [
+    "uv-loading-icon",
+    "uvui",
+    "uv-ui",
+    "loading",
+    "加载动画"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "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",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 19 - 0
uni_modules/uv-loading-icon/readme.md

@@ -0,0 +1,19 @@
+## LoadingIcon 加载动画
+
+> **组件名:uv-loading-icon**
+
+此组件为一个小动画,目前用在 `uv-ui` 的 `uv-load-more` 加载更多等组件,还可以运用在项目中正在加载状态场景。
+
+# <a href="https://www.uvui.cn/components/loadingIcon.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 9 - 0
uni_modules/uv-overlay/changelog.md

@@ -0,0 +1,9 @@
+## 1.0.3(2023-07-02)
+uv-overlay  由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+## 1.0.2(2023-06-29)
+1. 优化,H5端禁止穿透滚动
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-overlay组件

+ 25 - 0
uni_modules/uv-overlay/components/uv-overlay/props.js

@@ -0,0 +1,25 @@
+export default {
+	props: {
+		// 是否显示遮罩
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 层级z-index
+		zIndex: {
+			type: [String, Number],
+			default: 10070
+		},
+		// 遮罩的过渡时间,单位为ms
+		duration: {
+			type: [String, Number],
+			default: 300
+		},
+		// 不透明度值,当做rgba的第四个参数
+		opacity: {
+			type: [String, Number],
+			default: 0.5
+		},
+		...uni.$uv?.props?.overlay
+	}
+}

+ 85 - 0
uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue

@@ -0,0 +1,85 @@
+<template>
+	<uv-transition
+	  :show="show"
+		mode="fade"
+	  custom-class="uv-overlay"
+	  :duration="duration"
+	  :custom-style="overlayStyle"
+	  @click="clickHandler"
+		@touchmove.stop.prevent="clear"
+	>
+		<slot />
+	</uv-transition>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+
+	/**
+	 * overlay 遮罩
+	 * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
+	 * @tutorial https://www.uvui.cn/components/overlay.html
+	 * @property {Boolean}			show		是否显示遮罩(默认 false )
+	 * @property {String | Number}	zIndex		zIndex 层级(默认 10070 )
+	 * @property {String | Number}	duration	动画时长,单位毫秒(默认 300 )
+	 * @property {String | Number}	opacity		不透明度值,当做rgba的第四个参数 (默认 0.5 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event {Function} click 点击遮罩发送事件
+	 * @example <uv-overlay :show="show" @click="show = false"></uv-overlay>
+	 */
+	export default {
+		name: "uv-overlay",
+		emits: ['click'],
+		mixins: [mpMixin, mixin, props],
+		watch: {
+			show(newVal){
+				// #ifdef H5
+				if(newVal){
+					document.querySelector('body').style.overflow = 'hidden';
+				}else{
+					document.querySelector('body').style.overflow = '';
+				}
+				// #endif
+			}
+		},
+		computed: {
+			overlayStyle() {
+				const style = {
+					position: 'fixed',
+					top: 0,
+					left: 0,
+					right: 0,
+					zIndex: this.zIndex,
+					bottom: 0,
+					'background-color': `rgba(0, 0, 0, ${this.opacity})`
+				}
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			}
+		},
+		methods: {
+			clickHandler() {
+				this.$emit('click')
+			},
+			clear() {}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+/* #ifndef APP-NVUE */
+$uv-overlay-top:0 !default;
+$uv-overlay-left:0 !default;
+$uv-overlay-width:100% !default;
+$uv-overlay-height:100% !default;
+$uv-overlay-background-color:rgba(0, 0, 0, .7) !default;
+.uv-overlay {
+	position: fixed;
+	top:$uv-overlay-top;
+	left:$uv-overlay-left;
+	width: $uv-overlay-width;
+	height:$uv-overlay-height;
+	background-color:$uv-overlay-background-color;
+}
+/* #endif */
+</style>

+ 88 - 0
uni_modules/uv-overlay/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uv-overlay",
+  "displayName": "uv-overlay 遮罩层  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.3",
+  "description": "uv-overlay 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景,uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。",
+  "keywords": [
+    "uv-overlay",
+    "uvui",
+    "uv-ui",
+    "overlay",
+    "遮罩层"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-transition"
+		],
+    "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",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-overlay/readme.md

@@ -0,0 +1,11 @@
+## Overlay 遮罩层
+
+> **组件名:uv-overlay**
+
+创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景,uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。
+
+### <a href="https://www.uvui.cn/components/overlay.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 18 - 0
uni_modules/uv-popup/changelog.md

@@ -0,0 +1,18 @@
+## 1.0.7(2023-11-20)
+修复issues问题:https://gitee.com/climblee/uv-ui/issues/I8HDLO
+## 1.0.6(2023-10-13)
+1. 优化vue,内容有背景色,设置圆角被遮挡的情况
+## 1.0.5(2023-09-10)
+1. 修复H5默认层级过高的问题
+2. 修复全局设置prop无效的问题
+## 1.0.4(2023-08-08)
+1. 修复修改zIndex不生效的BUG
+## 1.0.3(2023-07-02)
+uv-popup  弹出层,代码重构优化,性能翻倍,小程序体验性能更加,避免卡顿。打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/popup.html
+## 1.0.2(2023-06-11)
+1. 修复zIndex层级问题
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-popup组件

+ 45 - 0
uni_modules/uv-popup/components/uv-popup/keypress.js

@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  mounted () {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del']
+    }
+    const listener = ($event) => {
+      if (this.disable) {
+        return
+      }
+      const keyName = Object.keys(keyNames).find(key => {
+        const keyName = $event.key
+        const value = keyNames[key]
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
+      })
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {})
+        }, 0)
+      }
+    }
+    document.addEventListener('keyup', listener)
+    // this.$once('hook:beforeDestroy', () => {
+    //   document.removeEventListener('keyup', listener)
+    // })
+  },
+	render: () => {}
+}
+// #endif

+ 539 - 0
uni_modules/uv-popup/components/uv-popup/uv-popup.vue

@@ -0,0 +1,539 @@
+<template>
+	<view 
+		v-if="showPopup" 
+		class="uv-popup" 
+		:class="[popupClass, isDesktop ? 'fixforpc-z-index' : '']"
+		:style="[{zIndex: zIndex}]"
+	>
+		<view @touchstart="touchstart">
+			<!-- 遮罩层 -->
+			<uv-overlay
+				key="1"
+				v-if="maskShow && overlay"
+				:show="showTrans"
+				:duration="duration"
+				:custom-style="overlayStyle"
+				:opacity="overlayOpacity"
+			  :zIndex="zIndex"
+				@click="onTap"
+			></uv-overlay>
+			<uv-transition 
+				key="2" 
+				:mode="ani" 
+				name="content" 
+				:custom-style="transitionStyle" 
+				:duration="duration"
+				:show="showTrans" 
+				@click="onTap"
+			>
+				<view 
+					class="uv-popup__content" 
+					:style="[contentStyle]" 
+					:class="[popupClass]" 
+					@click="clear"
+				>
+					<uv-status-bar v-if="safeAreaInsetTop"></uv-status-bar>
+					<slot />
+					<uv-safe-bottom v-if="safeAreaInsetBottom"></uv-safe-bottom>
+					<view
+						v-if="closeable"
+						@tap.stop="close"
+						class="uv-popup__content__close"
+						:class="['uv-popup__content__close--' + closeIconPos]"
+						hover-class="uv-popup__content__close--hover"
+						hover-stay-time="150"
+					>
+						<uv-icon
+							name="close"
+							color="#909399"
+							size="18"
+							bold
+						></uv-icon>
+					</view>
+				</view>
+			</uv-transition>
+		</view>
+		<!-- #ifdef H5 -->
+		<keypress v-if="maskShow" @esc="onTap" />
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	import keypress from './keypress.js'
+	// #endif
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	/**
+	* PopUp 弹出层
+	* @description 弹出层组件,为了解决遮罩弹层的问题
+	* @tutorial https://www.uvui.cn/components/popup.html
+	* @property {String} mode = [top|center|bottom|left|right] 弹出方式
+	* 	@value top 顶部弹出
+	* 	@value center 中间弹出
+	* 	@value bottom 底部弹出
+	* 	@value left		左侧弹出
+	* 	@value right  右侧弹出
+	* @property {Number} duration 动画时长,默认300
+	* @property {Boolean} overlay 是否显示遮罩,默认true
+	* @property {Boolean} overlayOpacity 遮罩透明度,默认0.5 
+	* @property {Object} overlayStyle 遮罩自定义样式
+	* @property {Boolean} closeOnClickOverlay = [true|false] 蒙版点击是否关闭弹窗,默认true
+	* @property {Number | String} zIndex 弹出层的层级
+	* @property {Boolean} safeAreaInsetTop 是否留出顶部安全区(状态栏高度),默认false
+	* @property {Boolean} safeAreaInsetBottom 是否为留出底部安全区适配,默认true
+	* @property {Boolean} closeable 是否显示关闭图标,默认false
+	* @property {Boolean} closeIconPos 自定义关闭图标位置,`top-left`-左上角,`top-right`-右上角,`bottom-left`-左下角,`bottom-right`-右下角,默认top-right
+	* @property {String}  bgColor 主窗口背景色
+	* @property {String}  maskBackgroundColor 蒙版颜色
+	* @property {Boolean} customStyle 自定义样式
+	* @event {Function} change 打开关闭弹窗触发,e={show: false}
+	* @event {Function} maskClick 点击遮罩触发
+	*/
+	export default {
+		name: 'uv-popup',
+		components: {
+			// #ifdef H5
+			keypress
+			// #endif
+		},
+		mixins: [mpMixin, mixin],
+		emits: ['change', 'maskClick'],
+		props: {
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+			// message: 消息提示 ; dialog : 对话框
+			mode: {
+				type: String,
+				default: 'center'
+			},
+			// 动画时长,单位ms
+			duration: {
+				type: [String, Number],
+				default: 300
+			},
+			// 层级
+			zIndex: {
+				type: [String, Number],
+				// #ifdef H5
+				default: 997
+				// #endif
+				// #ifndef H5
+				default: 10075
+				// #endif
+			},
+			bgColor: {
+				type: String,
+				default: '#ffffff'
+			},
+			safeArea: {
+				type: Boolean,
+				default: true
+			},
+			// 是否显示遮罩
+			overlay: {
+				type: Boolean,
+				default: true
+			},
+			// 点击遮罩是否关闭弹窗
+			closeOnClickOverlay: {
+				type: Boolean,
+				default: true
+			},
+			// 遮罩的透明度,0-1之间
+			overlayOpacity: {
+				type: [Number, String],
+				default: 0.4
+			},
+			// 自定义遮罩的样式
+			overlayStyle: {
+				type: [Object, String],
+				default: ''
+			},
+			// 是否为iPhoneX留出底部安全距离
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: true
+			},
+			// 是否留出顶部安全距离(状态栏高度)
+			safeAreaInsetTop: {
+				type: Boolean,
+				default: false
+			},
+			// 是否显示关闭图标
+			closeable: {
+				type: Boolean,
+				default: false
+			},
+			// 自定义关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角
+			closeIconPos: {
+				type: String,
+				default: 'top-right'
+			},
+			// mode=center,也即中部弹出时,是否使用缩放模式
+			zoom: {
+				type: Boolean,
+				default: true
+			},
+			round: {
+				type: [Number, String],
+				default: 0
+			},
+			...uni.$uv?.props?.popup
+		},
+		watch: {
+			/**
+			 * 监听type类型
+			 */
+			type: {
+				handler: function(type) {
+					if (!this.config[type]) return
+					this[this.config[type]](true)
+				},
+				immediate: true
+			},
+			isDesktop: {
+				handler: function(newVal) {
+					if (!this.config[newVal]) return
+					this[this.config[this.mode]](true)
+				},
+				immediate: true
+			},
+			// H5 下禁止底部滚动
+			showPopup(show) {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
+				// #endif
+			}
+		},
+		data() {
+			return {
+				ani: [],
+				showPopup: false,
+				showTrans: false,
+				popupWidth: 0,
+				popupHeight: 0,
+				config: {
+					top: 'top',
+					bottom: 'bottom',
+					center: 'center',
+					left: 'left',
+					right: 'right',
+					message: 'top',
+					dialog: 'center',
+					share: 'bottom'
+				},
+				transitionStyle: {
+					position: 'fixed',
+					left: 0,
+					right: 0
+				},
+				maskShow: true,
+				mkclick: true,
+				popupClass: this.isDesktop ? 'fixforpc-top' : 'top',
+				direction: ''
+			}
+		},
+		computed: {
+			isDesktop() {
+				return this.popupWidth >= 500 && this.popupHeight >= 500
+			},
+			bg() {
+				if (this.bgColor === '' || this.bgColor === 'none' || this.$uv.getPx(this.round)>0) {
+					return 'transparent'
+				}
+				return this.bgColor
+			},
+			contentStyle() {
+				const style = {};
+				if (this.bgColor) {
+					style.backgroundColor = this.bg
+				}
+				if(this.round) {
+					const value = this.$uv.addUnit(this.round)
+					const mode = this.direction?this.direction:this.mode
+					style.backgroundColor = this.bgColor
+					if(mode === 'top') {
+						style.borderBottomLeftRadius = value
+						style.borderBottomRightRadius = value
+					} else if(mode === 'bottom') {
+						style.borderTopLeftRadius = value
+						style.borderTopRightRadius = value
+					} else if(mode === 'center') {
+						style.borderRadius = value
+					} 
+				}
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			}
+		},
+		// #ifndef VUE3
+		// TODO vue2
+		destroyed() {
+			this.setH5Visible()
+		},
+		// #endif
+		// #ifdef VUE3
+		// TODO vue3
+		unmounted() {
+			this.setH5Visible()
+		},
+		// #endif
+		created() {
+			// TODO 处理 message 组件生命周期异常的问题
+			this.messageChild = null
+			// TODO 解决头条冒泡的问题
+			this.clearPropagation = false
+		},
+		methods: {
+			setH5Visible() {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = 'visible'
+				// #endif
+			},
+			/**
+			 * 公用方法,不显示遮罩层
+			 */
+			closeMask() {
+				this.maskShow = false
+			},
+			// TODO nvue 取消冒泡
+			clear(e) {
+				// #ifndef APP-NVUE
+				e.stopPropagation()
+				// #endif
+				this.clearPropagation = true
+			},
+
+			open(direction) {
+				// fix by mehaotian 处理快速打开关闭的情况
+				if (this.showPopup) {
+					return
+				}
+				let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
+				if (!(direction && innerType.indexOf(direction) !== -1)) {
+					direction = this.mode
+				}else {
+					this.direction = direction;
+				}
+				if (!this.config[direction]) {
+					return this.$uv.error(`缺少类型:${direction}`);
+				}
+				this[this.config[direction]]()
+				this.$emit('change', {
+					show: true,
+					type: direction
+				})
+			},
+			close(type) {
+				this.showTrans = false
+				this.$emit('change', {
+					show: false,
+					type: this.mode
+				})
+				clearTimeout(this.timer)
+				// // 自定义关闭事件
+				this.timer = setTimeout(() => {
+					this.showPopup = false
+				}, 300)
+			},
+			// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
+			touchstart() {
+				this.clearPropagation = false
+			},
+			onTap() {
+				if (this.clearPropagation) {
+					// fix by mehaotian 兼容 nvue
+					this.clearPropagation = false
+					return
+				}
+				this.$emit('maskClick')
+				if (!this.closeOnClickOverlay) return
+				this.close()
+			},
+			/**
+			 * 顶部弹出样式处理
+			 */
+			top(type) {
+				this.popupClass = this.isDesktop ? 'fixforpc-top' : 'top'
+				this.ani = ['slide-top']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					left: 0,
+					right: 0,
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+				this.$nextTick(() => {
+					if (this.messageChild && this.mode === 'message') {
+						this.messageChild.timerClose()
+					}
+				})
+			},
+			/**
+			 * 底部弹出样式处理
+			 */
+			bottom(type) {
+				this.popupClass = 'bottom'
+				this.ani = ['slide-bottom']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					left: 0,
+					right: 0,
+					bottom: 0,
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			/**
+			 * 中间弹出样式处理
+			 */
+			center(type) {
+				this.popupClass = 'center'
+				this.ani = this.zoom?['zoom-in', 'fade']:['fade'];
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column',
+					/* #endif */
+					bottom: 0,
+					left: 0,
+					right: 0,
+					top: 0,
+					justifyContent: 'center',
+					alignItems: 'center'
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			left(type) {
+				this.popupClass = 'left'
+				this.ani = ['slide-left']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					left: 0,
+					bottom: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			right(type) {
+				this.popupClass = 'right'
+				this.ani = ['slide-right']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					bottom: 0,
+					right: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uv-popup {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+
+		/* #endif */
+		&.top,
+		&.left,
+		&.right {
+			/* #ifdef H5 */
+			top: var(--window-top);
+			/* #endif */
+			/* #ifndef H5 */
+			top: 0;
+			/* #endif */
+		}
+
+		.uv-popup__content {
+			/* #ifndef APP-NVUE */
+			display: block;
+			overflow: hidden;
+			/* #endif */
+			position: relative;
+
+			&.left,
+			&.right {
+				/* #ifdef H5 */
+				padding-top: var(--window-top);
+				/* #endif */
+				/* #ifndef H5 */
+				padding-top: 0;
+				/* #endif */
+				flex: 1;
+			}
+			&__close {
+				position: absolute;
+
+				&--hover {
+					opacity: 0.4;
+				}
+			}
+			
+			&__close--top-left {
+				top: 15px;
+				left: 15px;
+			}
+			
+			&__close--top-right {
+				top: 15px;
+				right: 15px;
+			}
+			
+			&__close--bottom-left {
+				bottom: 15px;
+				left: 15px;
+			}
+			
+			&__close--bottom-right {
+				right: 15px;
+				bottom: 15px;
+			}
+		}
+	}
+
+	.fixforpc-z-index {
+		/* #ifndef APP-NVUE */
+		z-index: 999;
+		/* #endif */
+	}
+
+	.fixforpc-top {
+		top: 0;
+	}
+</style>

+ 92 - 0
uni_modules/uv-popup/package.json

@@ -0,0 +1,92 @@
+{
+  "id": "uv-popup",
+  "displayName": "uv-popup 弹出层 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.7",
+  "description": "uv-popup 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。",
+  "keywords": [
+    "uv-popup",
+    "uvui",
+    "uv-ui",
+    "popup",
+    "弹出层"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-overlay",
+			"uv-transition",
+			"uv-icon",
+			"uv-status-bar",
+			"uv-safe-bottom"
+		],
+    "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",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 21 - 0
uni_modules/uv-popup/readme.md

@@ -0,0 +1,21 @@
+## Popup 弹出层
+
+> **组件名:uv-popup**
+
+弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。
+
+该组件已经放弃原来`uview2.x`的写法,参照了官方`uni-popup`的写法进行重构。在小程序端的性能大大提升,打开和关闭避免延迟,调用方法与之前相比也有所差异,具体请查看文档。
+
+# <a href="https://www.uvui.cn/components/popup.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 7 - 0
uni_modules/uv-status-bar/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-05)
+1. 兼容渐变背景色
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-status-bar组件

+ 8 - 0
uni_modules/uv-status-bar/components/uv-status-bar/props.js

@@ -0,0 +1,8 @@
+export default {
+    props: {
+        bgColor: {
+            type: String,
+            default: 'transparent'
+        }
+    }
+}

+ 54 - 0
uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue

@@ -0,0 +1,54 @@
+<template>
+	<view
+	    :style="[style]"
+	    class="uv-status-bar"
+	>
+		<slot />
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * StatbusBar 状态栏占位
+	 * @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
+	 * @tutorial https://www.uvui.cn/components/statusBar.html
+	 * @property {String}			bgColor			背景色 (默认 'transparent' )
+	 * @property {String | Object}	customStyle		自定义样式 
+	 * @example <uv-status-bar></uv-status-bar>
+	 */
+	export default {
+		name: 'uv-status-bar',
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+			}
+		},
+		computed: {
+			style() {
+				const style = {}
+				// 状态栏高度,由于某些安卓和微信开发工具无法识别css的顶部状态栏变量,所以使用js获取的方式
+				style.height = this.$uv.addUnit(this.$uv.sys().statusBarHeight, 'px')
+				if(this.bgColor){
+					if (this.bgColor.indexOf("gradient") > -1) {// 渐变色
+						style.backgroundImage = this.bgColor;
+					}else{
+						style.background = this.bgColor;
+					}
+				}
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	.uv-status-bar {
+		// nvue会默认100%,如果nvue下,显式写100%的话,会导致宽度不为100%而异常
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		/* #endif */
+	}
+</style>

+ 87 - 0
uni_modules/uv-status-bar/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-status-bar",
+  "displayName": "uv-status-bar 状态栏占位",
+  "version": "1.0.2",
+  "description": "状态栏占位组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。",
+  "keywords": [
+    "uv-status-bar",
+    "uvui",
+    "uv-ui",
+    "status-bar",
+    "状态栏"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "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",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 10 - 0
uni_modules/uv-status-bar/readme.md

@@ -0,0 +1,10 @@
+## StatbusBar 状态栏占位
+
+> **组件名:uv-status-bar**
+
+本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
+

+ 1 - 1
uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue

@@ -16,7 +16,7 @@
 		    class="uv-tabbar__placeholder"
 			v-if="placeholder"
 		    :style="{
-				height: placeholderHeight + 'px',
+				height:'136rpx',
 			}"
 		></view>
 	</view>

+ 17 - 0
uni_modules/uv-upload/changelog.md

@@ -0,0 +1,17 @@
+## 1.0.6(2023-12-20)
+1. 修复动态设置deletable为false不生效的BUG
+## 1.0.5(2023-08-31)
+1. 添加uv-popup依赖
+## 1.0.4(2023-08-18)
+1. 修复图片预览位置错误的BUG
+2. 修复视频预览不生效的BUG
+3. 修复改变上传视频宽高不生效的BUG
+## 1.0.3(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.2(2023-05-24)
+1. 优化fileList,watch中增加deep属性
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-upload 上传

+ 52 - 0
uni_modules/uv-upload/components/uv-preview-video/uv-preview-video.vue

@@ -0,0 +1,52 @@
+<template>
+	<uv-popup ref="popup" @change="change">
+		<view class="video-view" v-if="show">
+			<video class="video" :src="getSec" :autoplay="autoplay"></video>
+		</view>
+	</uv-popup>
+</template>
+<script>
+	export default {
+		props: {
+			src: {
+				type: String,
+				default: ''
+			},
+			autoplay: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				videoSrc: '',
+				show: false
+			}
+		},
+		computed: {
+			getSec() {
+				return this.src || this.videoSrc;
+			}
+		},
+		methods: {
+			open(url) {
+				this.videoSrc = url;
+				this.$refs.popup.open();
+			},
+			close() {
+				this.$refs.popup.close();
+			},
+			change(e) {
+				this.show = e.show;
+			}
+		}
+	}
+</script>
+<style scoped lang="scss">
+	.video-view {
+		width: 750rpx;
+		.video {
+			width: 750rpx;
+		}
+	}
+</style>

+ 22 - 0
uni_modules/uv-upload/components/uv-upload/mixin.js

@@ -0,0 +1,22 @@
+import { error } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
+export default {
+    watch: {
+        // 监听accept的变化,判断是否符合个平台要求
+        // 只有微信小程序才支持选择媒体,文件类型,所以这里做一个判断提示
+        accept: {
+            immediate: true,
+            handler(val) {
+                // #ifndef MP-WEIXIN
+                if (val === 'all' || val === 'media') {
+                    error('只有微信小程序才支持把accept配置为all、media之一')
+                }
+                // #endif
+                // #ifndef H5 || MP-WEIXIN
+                if (val === 'file') {
+                    error('只有微信小程序和H5(HX2.9.9)才支持把accept配置为file')
+                }
+                // #endif
+            }
+        }
+    }
+}

+ 130 - 0
uni_modules/uv-upload/components/uv-upload/props.js

@@ -0,0 +1,130 @@
+export default {
+	props: {
+		// 接受的文件类型, 可选值为all media image file video
+		accept: {
+			type: String,
+			default: 'image'
+		},
+		// 	图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头
+		capture: {
+			type: [String, Array],
+			default: () => ['album', 'camera']
+		},
+		// 当accept为video时生效,是否压缩视频,默认为true
+		compressed: {
+			type: Boolean,
+			default: true
+		},
+		// 当accept为video时生效,可选值为back或front
+		camera: {
+			type: String,
+			default: 'back'
+		},
+		// 当accept为video时生效,拍摄视频最长拍摄时间,单位秒
+		maxDuration: {
+			type: Number,
+			default: 60
+		},
+		// 上传区域的图标,只能内置图标
+		uploadIcon: {
+			type: String,
+			default: 'camera-fill'
+		},
+		// 上传区域的图标的颜色,默认
+		uploadIconColor: {
+			type: String,
+			default: '#D3D4D6'
+		},
+		// 是否开启文件读取前事件
+		useBeforeRead: {
+			type: Boolean,
+			default: false
+		},
+		// 读取后的处理函数
+		afterRead: {
+			type: Function,
+			default: null
+		},
+		// 读取前的处理函数
+		beforeRead: {
+			type: Function,
+			default: null
+		},
+		// 是否开启图片预览功能
+		previewFullImage: {
+			type: Boolean,
+			default: true
+		},
+		// 是否开启视频预览功能
+		previewFullVideo: {
+			type: Boolean,
+			default: true
+		},
+		// 最大上传数量
+		maxCount: {
+			type: [String, Number],
+			default: 52
+		},
+		// 是否禁用
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 预览上传的图片时的裁剪模式,和image组件mode属性一致
+		imageMode: {
+			type: String,
+			default: 'aspectFill'
+		},
+		// 标识符,可以在回调函数的第二项参数中获取
+		name: {
+			type: String,
+			default: ''
+		},
+		// 所选的图片的尺寸, 可选值为original compressed
+		sizeType: {
+			type: Array,
+			default: () => ['original', 'compressed']
+		},
+		// 是否开启图片多选,部分安卓机型不支持
+		multiple: {
+			type: Boolean,
+			default: false
+		},
+		// 是否展示删除按钮
+		deletable: {
+			type: Boolean,
+			default: true
+		},
+		// 文件大小限制,单位为byte
+		maxSize: {
+			type: [String, Number],
+			default: Number.MAX_VALUE
+		},
+		// 显示已上传的文件列表
+		fileList: {
+			type: Array,
+			default: () => []
+		},
+		// 上传区域的提示文字
+		uploadText: {
+			type: String,
+			default: ''
+		},
+		// 内部预览图片区域和选择图片按钮的区域宽度
+		width: {
+			type: [String, Number],
+			default: 80
+		},
+		// 内部预览图片区域和选择图片按钮的区域高度
+		height: {
+			type: [String, Number],
+			default: 80
+		},
+		// 是否在上传完成后展示预览图
+		previewImage: {
+			type: Boolean,
+			default: true
+		},
+		...uni.$uv?.props?.upload
+	}
+}

+ 151 - 0
uni_modules/uv-upload/components/uv-upload/utils.js

@@ -0,0 +1,151 @@
+function pickExclude(obj, keys) {
+	// 某些情况下,type可能会为
+    if (!['[object Object]', '[object File]'].includes(Object.prototype.toString.call(obj))) {
+        return {}
+    }
+    return Object.keys(obj).reduce((prev, key) => {
+        if (!keys.includes(key)) {
+            prev[key] = obj[key]
+        }
+        return prev
+    }, {})
+}
+
+function formatImage(res) {
+    return res.tempFiles.map((item) => ({
+        ...pickExclude(item, ['path']),
+        type: 'image',
+        url: item.path,
+        thumb: item.path,
+		size: item.size,
+		// #ifdef H5
+		name: item.name
+		// #endif
+    }))
+}
+
+function formatVideo(res) {
+    return [
+        {
+            ...pickExclude(res, ['tempFilePath', 'thumbTempFilePath', 'errMsg']),
+            type: 'video',
+            url: res.tempFilePath,
+            thumb: res.thumbTempFilePath,
+			size: res.size,
+			// #ifdef H5
+			name: res.name
+			// #endif
+        }
+    ]
+}
+
+function formatMedia(res) {
+    return res.tempFiles.map((item) => ({
+        ...pickExclude(item, ['fileType', 'thumbTempFilePath', 'tempFilePath']),
+        type: res.type,
+        url: item.tempFilePath,
+        thumb: res.type === 'video' ? item.thumbTempFilePath : item.tempFilePath,
+		size: item.size
+    }))
+}
+
+function formatFile(res) {
+    return res.tempFiles.map((item) => ({ 
+		...pickExclude(item, ['path']), 
+		url: item.path, 
+		size:item.size,
+		// #ifdef H5
+		name: item.name,
+		type: item.type
+		// #endif 
+	}))
+}
+export function chooseFile({
+    accept,
+    multiple,
+    capture,
+    compressed,
+    maxDuration,
+    sizeType,
+    camera,
+    maxCount
+}) {
+    return new Promise((resolve, reject) => {
+        switch (accept) {
+        case 'image':
+            uni.chooseImage({
+                count: multiple ? Math.min(maxCount, 9) : 1,
+                sourceType: capture,
+                sizeType,
+                success: (res) => resolve(formatImage(res)),
+                fail: reject
+            })
+            break
+            // #ifdef MP-WEIXIN
+            // 只有微信小程序才支持chooseMedia接口
+        case 'media':
+            wx.chooseMedia({
+                count: multiple ? Math.min(maxCount, 9) : 1,
+                sourceType: capture,
+                maxDuration,
+                sizeType,
+                camera,
+                success: (res) => resolve(formatMedia(res)),
+                fail: reject
+            })
+            break
+            // #endif
+        case 'video':
+            uni.chooseVideo({
+                sourceType: capture,
+                compressed,
+                maxDuration,
+                camera,
+                success: (res) => resolve(formatVideo(res)),
+                fail: reject
+            })
+            break
+            // #ifdef MP-WEIXIN || H5
+            // 只有微信小程序才支持chooseMessageFile接口
+        case 'file':
+            // #ifdef MP-WEIXIN
+            wx.chooseMessageFile({
+                count: multiple ? maxCount : 1,
+                type: accept,
+                success: (res) => resolve(formatFile(res)),
+                fail: reject
+            })
+            // #endif
+            // #ifdef H5
+            // 需要hx2.9.9以上才支持uni.chooseFile
+            uni.chooseFile({
+                count: multiple ? maxCount : 1,
+                type: accept,
+                success: (res) => resolve(formatFile(res)),
+                fail: reject
+            })
+            // #endif
+            break
+				// #endif
+		default: 
+			// 此为保底选项,在accept不为上面任意一项的时候选取全部文件
+			// #ifdef MP-WEIXIN
+			wx.chooseMessageFile({
+			    count: multiple ? maxCount : 1,
+			    type: 'all',
+			    success: (res) => resolve(formatFile(res)),
+			    fail: reject
+			})
+			// #endif
+			// #ifdef H5
+			// 需要hx2.9.9以上才支持uni.chooseFile
+			uni.chooseFile({
+				count: multiple ? maxCount : 1,
+				type: 'all',
+				success: (res) => resolve(formatFile(res)),
+				fail: reject
+			})
+			// #endif
+        }
+    })
+}

+ 488 - 0
uni_modules/uv-upload/components/uv-upload/uv-upload.vue

@@ -0,0 +1,488 @@
+<template>
+	<view class="uv-upload" :style="[$uv.addStyle(customStyle)]">
+		<view class="uv-upload__wrap">
+			<template v-if="previewImage">
+				<view class="uv-upload__wrap__preview" v-for="(item, index) in lists" :key="index">
+					<image 
+						v-if="item.isImage || (item.type && item.type === 'image')" 
+						:src="item.thumb || item.url" :mode="imageMode" 
+						class="uv-upload__wrap__preview__image" 
+						@tap="onPreviewImage(item,index)" 
+						:style="[{
+							width: $uv.addUnit(width),
+							height: $uv.addUnit(height)
+						}]" 
+						/>
+					<view 
+						v-else 
+						class="uv-upload__wrap__preview__other" 
+						@tap="onPreviewVideo(item,index)" 
+						:style="[{
+							width: $uv.addUnit(width),
+							height: $uv.addUnit(height)
+						}]"
+						>
+						<uv-icon color="#80CBF9" size="26" :name="item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'folder'"></uv-icon>
+						<text class="uv-upload__wrap__preview__other__text">{{item.isVideo || (item.type && item.type === 'video') ? '视频' : '文件'}}</text>
+					</view>
+					<view class="uv-upload__status" v-if="item.status === 'uploading' || item.status === 'failed'">
+						<view class="uv-upload__status__icon">
+							<uv-icon v-if="item.status === 'failed'" name="close-circle" color="#ffffff" size="25" />
+							<uv-loading-icon size="22" mode="circle" v-else />
+						</view>
+						<text v-if="item.message" class="uv-upload__status__message">{{ item.message }}</text>
+					</view>
+					<view class="uv-upload__deletable" v-if="item.status !== 'uploading' && (deletable || item.deletable)" @tap.stop="deleteItem(index)">
+						<view class="uv-upload__deletable__icon">
+							<uv-icon name="close" color="#ffffff" size="10"></uv-icon>
+						</view>
+					</view>
+					<view class="uv-upload__success" v-if="item.status === 'success'">
+						<!-- #ifdef APP-NVUE -->
+						<image :src="successIcon" class="uv-upload__success__icon"></image>
+						<!-- #endif -->
+						<!-- #ifndef APP-NVUE -->
+						<view class="uv-upload__success__icon">
+							<uv-icon name="checkmark" color="#ffffff" size="12"></uv-icon>
+						</view>
+						<!-- #endif -->
+					</view>
+				</view>
+			</template>
+			<template v-if="isInCount">
+				<view @tap="chooseFile">
+					<slot>
+						<view class="uv-upload__button" :hover-class="!disabled ? 'uv-upload__button--hover' : ''" hover-stay-time="150" @tap.stop="chooseFile" :class="[disabled && 'uv-upload__button--disabled']" :style="[{
+								width: $uv.addUnit(width),
+								height: $uv.addUnit(height)
+							}]">
+							<uv-icon :name="uploadIcon" size="26" :color="uploadIconColor"></uv-icon>
+							<text v-if="uploadText" class="uv-upload__button__text">{{ uploadText }}</text>
+						</view>
+					</slot>
+				</view>
+			</template>
+		</view>
+		<uv-preview-video ref="previewVideo"></uv-preview-video>
+	</view>
+</template>
+
+<script>
+	import { func, image, video, array, promise } from '@/uni_modules/uv-ui-tools/libs/function/test.js';
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import { chooseFile } from './utils';
+	import mixin_accept from './mixin.js';
+	import props from './props.js';
+	/**
+	 * upload 上传
+	 * @description 该组件用于上传图片场景
+	 * @tutorial https://www.uvui.cn/components/upload.html
+	 * @property {String}			accept				接受的文件类型, 可选值为all media image file video (默认 'image' )
+	 * @property {String | Array}	capture				图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头(默认 ['album', 'camera'] )
+	 * @property {Boolean}			compressed			当accept为video时生效,是否压缩视频,默认为true(默认 true )
+	 * @property {String}			camera				当accept为video时生效,可选值为back或front(默认 'back' )
+	 * @property {Number}			maxDuration			当accept为video时生效,拍摄视频最长拍摄时间,单位秒(默认 60 )
+	 * @property {String}			uploadIcon			上传区域的图标,只能内置图标(默认 'camera-fill' )
+	 * @property {String}			uploadIconColor		上传区域的图标的字体颜色,只能内置图标(默认 #D3D4D6 )
+	 * @property {Boolean}			useBeforeRead		是否开启文件读取前事件(默认 false )
+	 * @property {Boolean}			previewFullImage	是否开启图片预览功能(默认 true )
+	 * @property {Boolean}			previewFullVideo	是否开启视频预览功能(默认 true )
+	 * @property {String | Number}	maxCount			最大上传数量(默认 52 )
+	 * @property {Boolean}			disabled			是否启用(默认 false )
+	 * @property {String}			imageMode			预览上传的图片时的裁剪模式,和image组件mode属性一致(默认 'aspectFill' )
+	 * @property {String}			name				标识符,可以在回调函数的第二项参数中获取
+	 * @property {Array}			sizeType			所选的图片的尺寸, 可选值为original compressed(默认 ['original', 'compressed'] )
+	 * @property {Boolean}			multiple			是否开启图片多选,部分安卓机型不支持 (默认 false )
+	 * @property {Boolean}			deletable			是否展示删除按钮(默认 true )
+	 * @property {String | Number}	maxSize				文件大小限制,单位为byte (默认 Number.MAX_VALUE )
+	 * @property {Array}			fileList			显示已上传的文件列表
+	 * @property {String}			uploadText			上传区域的提示文字
+	 * @property {String | Number}	width				内部预览图片区域和选择图片按钮的区域宽度(默认 80 )
+	 * @property {String | Number}	height				内部预览图片区域和选择图片按钮的区域高度(默认 80 )
+	 * @property {Object}			customStyle			组件的样式,对象形式
+	 * @event {Function} afterRead		读取后的处理函数
+	 * @event {Function} beforeRead		读取前的处理函数
+	 * @event {Function} oversize		文件超出大小限制
+	 * @event {Function} clickPreview	点击预览时触发
+	 * @event {Function} delete 		删除图片
+	 * @example <uv-upload :action="action" :fileList="fileList" ></uv-upload>
+	 */
+	export default {
+		name: "uv-upload",
+		// #ifdef VUE3
+		emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview'],
+		// #endif
+		mixins: [mpMixin, mixin, mixin_accept, props],
+		data() {
+			return {
+				// #ifdef APP-NVUE
+				successIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAAB65masAAACP0lEQVRYCc3YXygsURwH8K/dpcWyG3LF5u/6/+dKVylSypuUl6uUPMifKMWL8oKEB1EUT1KeUPdR3uTNUsSLxb2udG/cbvInNuvf2rVnazZ/ZndmZ87snjM1Z+Z3zpzfp9+Z5mEAhlvjRtZgCKs+gnPAOcAkkMOR4jEHfItjDvgRxxSQD8cM0BuOCaAvXNCBQrigAsXgggYUiwsK0B9cwIH+4gIKlIILGFAqLiBAOTjFgXJxigJp4BQD0sIpAqSJow6kjSNAFTnRaHJwLenD6Mud52VQAcrBfTd2oyq+HtGaGGWAcnAVcXWoM3bCZrdi+ncPfaAcXE5UKVpdW/vitGPqqAtn98d0gXJwX7Qp6MmegUYVhvmTIezdmHlxJCjpHRTCFerLkRRu4k0aqdajN3sWOo0BK//msHa+xDuPC/oNFMKRhTtM4xjIX0SCNpXL4+7VIaHuyiWEp2L7ahWLf8fejfPdqPmC3mJicORZUp1CQzm+GiphvljGk+PBvWRbxii+xVTj5M6CiZ/tsDufvaXyxEUDxeLIyvu3m0iOyEFWVAkydcVYdyFrE9tQk9iMq6f/GNlvwt3LjQfh60LUrw9/cFyyMJUW/XkLSNMV4Mi6C5ML+ui4x5ClAX9sB9w0wV6wglJwJCv5fOxcr6EstgbGiEw4XcfUry4cWrcEUW8n+ARKxXEJHhw2WG43UKSvwI/TSZgvl7kh0b3XLZaLEy0QmMgLZAVH7J+ALOE+AVnDvQOyiPMAWcW5gSzjCPAV+78S5WE0GrQAAAAASUVORK5CYII=',
+				// #endif
+				lists: [],
+				isInCount: true,
+			}
+		},
+		watch: {
+			// 监听文件列表的变化,重新整理内部数据
+			fileList: {
+				deep: true,
+				immediate: true,
+				handler() {
+					this.formatFileList()
+				}
+			},
+			deletable(newVal) {
+				if(!newVal) {
+					this.lists.map(item=>{
+						item.deletable = this.deletable;
+					})
+				}
+			}
+		},
+		methods: {
+			formatFileList() {
+				const {
+					fileList = [], maxCount
+				} = this;
+				const lists = fileList.map((item) =>
+					Object.assign(Object.assign({}, item), {
+						// 如果item.url为本地选择的blob文件的话,无法判断其为video还是image,此处优先通过accept做判断处理
+						isImage: this.accept === 'image' || image(item.url || item.thumb),
+						isVideo: this.accept === 'video' || video(item.url || item.thumb),
+						deletable: typeof(item.deletable) === 'boolean' ? item.deletable : this.deletable,
+					})
+				);
+				this.lists = lists
+				this.isInCount = lists.length < maxCount
+			},
+			chooseFile() {
+				this.timer && clearTimeout(this.timer);
+				this.timer = setTimeout(() => {
+					const {
+						maxCount,
+						multiple,
+						lists,
+						disabled
+					} = this;
+					if (disabled) return;
+					// 如果用户传入的是字符串,需要格式化成数组
+					let capture;
+					try {
+						capture = array(this.capture) ? this.capture : this.capture.split(',');
+					} catch (e) {
+						capture = [];
+					}
+					chooseFile(
+							Object.assign({
+								accept: this.accept,
+								multiple: this.multiple,
+								capture: capture,
+								compressed: this.compressed,
+								maxDuration: this.maxDuration,
+								sizeType: this.sizeType,
+								camera: this.camera,
+							}, {
+								maxCount: maxCount - lists.length,
+							})
+						)
+						.then((res) => {
+							this.onBeforeRead(multiple ? res : res[0]);
+						})
+						.catch((error) => {
+							this.$emit('error', error);
+						});
+				}, 100)
+			},
+			// 文件读取之前
+			onBeforeRead(file) {
+				const {
+					beforeRead,
+					useBeforeRead,
+				} = this;
+				let res = true
+				// beforeRead是否为一个方法
+				if (func(beforeRead)) {
+					// 如果用户定义了此方法,则去执行此方法,并传入读取的文件回调
+					res = beforeRead(file, this.getDetail());
+				}
+				if (useBeforeRead) {
+					res = new Promise((resolve, reject) => {
+						this.$emit(
+							'beforeRead',
+							Object.assign(Object.assign({
+								file
+							}, this.getDetail()), {
+								callback: (ok) => {
+									ok ? resolve() : reject();
+								},
+							})
+						);
+					});
+				}
+				if (!res) {
+					return;
+				}
+				if (promise(res)) {
+					res.then((data) => this.onAfterRead(data || file));
+				} else {
+					this.onAfterRead(file);
+				}
+			},
+			getDetail(index) {
+				return {
+					name: this.name,
+					index: index == null ? this.fileList.length : index,
+				};
+			},
+			onAfterRead(file) {
+				const {
+					maxSize,
+					afterRead
+				} = this;
+				const oversize = Array.isArray(file) ?
+					file.some((item) => item.size > maxSize) :
+					file.size > maxSize;
+				if (oversize) {
+					this.$emit('oversize', Object.assign({
+						file
+					}, this.getDetail()));
+					return;
+				}
+				if (typeof afterRead === 'function') {
+					afterRead(file, this.getDetail());
+				}
+				this.$emit('afterRead', Object.assign({
+					file
+				}, this.getDetail()));
+			},
+			deleteItem(index) {
+				this.$emit(
+					'delete',
+					Object.assign(Object.assign({}, this.getDetail(index)), {
+						file: this.fileList[index],
+					})
+				);
+			},
+			// 预览图片
+			onPreviewImage(item, index) {
+				const lists = this.$uv.deepClone(this.lists);
+				lists.map((i,j)=>{
+					if(j == index) {
+						i.current = true;
+					}
+				});
+				const filters = lists.filter(i=>i.isImage);
+				const findIndex = filters.findIndex(i=>i.current);
+				this.onClickPreview(item, index);
+				if (!item.isImage || !this.previewFullImage) return
+				uni.previewImage({
+					// 先filter找出为图片的item,再返回filter结果中的图片url
+					urls: this.lists.filter((item) => this.accept === 'image' || image(item.url || item.thumb)).map((item) => item.url || item.thumb),
+					current: findIndex,
+					fail() {
+						this.$uv.toast('预览图片失败')
+					},
+				});
+			},
+			onPreviewVideo(item, index) {
+				this.onClickPreview(item, index);
+				if (!this.previewFullVideo || !item.isVideo) return;
+				this.$refs.previewVideo.open(item.url);
+			},
+			onClickPreview(item, index) {
+				this.$emit(
+					'clickPreview',
+					Object.assign(Object.assign({}, item), this.getDetail(index))
+				);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	$uv-upload-preview-border-radius: 2px !default;
+	$uv-upload-preview-margin: 0 8px 8px 0 !default;
+	$uv-upload-image-width: 80px !default;
+	$uv-upload-image-height: $uv-upload-image-width;
+	$uv-upload-other-bgColor: rgb(242, 242, 242) !default;
+	$uv-upload-other-flex: 1 !default;
+	$uv-upload-text-font-size: 11px !default;
+	$uv-upload-text-color: $uv-tips-color !default;
+	$uv-upload-text-margin-top: 2px !default;
+	$uv-upload-deletable-right: 0 !default;
+	$uv-upload-deletable-top: 0 !default;
+	$uv-upload-deletable-bgColor: rgb(55, 55, 55) !default;
+	$uv-upload-deletable-height: 14px !default;
+	$uv-upload-deletable-width: $uv-upload-deletable-height;
+	$uv-upload-deletable-boder-bottom-left-radius: 100px !default;
+	$uv-upload-deletable-zIndex: 3 !default;
+	$uv-upload-success-bottom: 0 !default;
+	$uv-upload-success-right: 0 !default;
+	$uv-upload-success-border-style: solid !default;
+	$uv-upload-success-border-top-color: transparent !default;
+	$uv-upload-success-border-left-color: transparent !default;
+	$uv-upload-success-border-bottom-color: $uv-success !default;
+	$uv-upload-success-border-right-color: $uv-upload-success-border-bottom-color;
+	$uv-upload-success-border-width: 9px !default;
+	$uv-upload-icon-top: 0px !default;
+	$uv-upload-icon-right: 0px !default;
+	$uv-upload-icon-h5-top: 1px !default;
+	$uv-upload-icon-h5-right: 0 !default;
+	$uv-upload-icon-width: 16px !default;
+	$uv-upload-icon-height: $uv-upload-icon-width;
+	$uv-upload-success-icon-bottom: -10px !default;
+	$uv-upload-success-icon-right: -10px !default;
+	$uv-upload-status-right: 0 !default;
+	$uv-upload-status-left: 0 !default;
+	$uv-upload-status-bottom: 0 !default;
+	$uv-upload-status-top: 0 !default;
+	$uv-upload-status-bgColor: rgba(0, 0, 0, 0.5) !default;
+	$uv-upload-status-icon-Zindex: 1 !default;
+	$uv-upload-message-font-size: 12px !default;
+	$uv-upload-message-color: #FFFFFF !default;
+	$uv-upload-message-margin-top: 5px !default;
+	$uv-upload-button-width: 80px !default;
+	$uv-upload-button-height: $uv-upload-button-width;
+	$uv-upload-button-bgColor: rgb(244, 245, 247) !default;
+	$uv-upload-button-border-radius: 2px !default;
+	$uv-upload-botton-margin: 0 8px 8px 0 !default;
+	$uv-upload-text-font-size: 11px !default;
+	$uv-upload-text-color: $uv-tips-color !default;
+	$uv-upload-text-margin-top: 2px !default;
+	$uv-upload-hover-bgColor: rgb(230, 231, 233) !default;
+	$uv-upload-disabled-opacity: .5 !default;
+	.uv-upload {
+		@include flex(column);
+		flex: 1;
+		&__wrap {
+			@include flex;
+			flex-wrap: wrap;
+			flex: 1;
+			&__preview {
+				border-radius: $uv-upload-preview-border-radius;
+				margin: $uv-upload-preview-margin;
+				position: relative;
+				overflow: hidden;
+				@include flex;
+				&__image {
+					width: $uv-upload-image-width;
+					height: $uv-upload-image-height;
+				}
+				&__other {
+					width: $uv-upload-image-width;
+					height: $uv-upload-image-height;
+					background-color: $uv-upload-other-bgColor;
+					flex: $uv-upload-other-flex;
+					@include flex(column);
+					justify-content: center;
+					align-items: center;
+					&__text {
+						font-size: $uv-upload-text-font-size;
+						color: $uv-upload-text-color;
+						margin-top: $uv-upload-text-margin-top;
+					}
+				}
+			}
+		}
+		&__deletable {
+			position: absolute;
+			top: $uv-upload-deletable-top;
+			right: $uv-upload-deletable-right;
+			background-color: $uv-upload-deletable-bgColor;
+			height: $uv-upload-deletable-height;
+			width: $uv-upload-deletable-width;
+			@include flex;
+			border-bottom-left-radius: $uv-upload-deletable-boder-bottom-left-radius;
+			align-items: center;
+			justify-content: center;
+			z-index: $uv-upload-deletable-zIndex;
+			&__icon {
+				position: absolute;
+				transform: scale(0.7);
+				top: $uv-upload-icon-top;
+				right: $uv-upload-icon-right;
+				/* #ifdef H5 */
+				top: $uv-upload-icon-h5-top;
+				right: $uv-upload-icon-h5-right;
+				/* #endif */
+			}
+		}
+		&__success {
+			position: absolute;
+			bottom: $uv-upload-success-bottom;
+			right: $uv-upload-success-right;
+			@include flex;
+			// 由于weex(nvue)为阿里巴巴的KPI(部门业绩考核)的laji产物,不支持css绘制三角形
+			// 所以在nvue下使用图片,非nvue下使用css实现
+			/* #ifndef APP-NVUE */
+			border-style: $uv-upload-success-border-style;
+			border-top-color: $uv-upload-success-border-top-color;
+			border-left-color: $uv-upload-success-border-left-color;
+			border-bottom-color: $uv-upload-success-border-bottom-color;
+			border-right-color: $uv-upload-success-border-right-color;
+			border-width: $uv-upload-success-border-width;
+			align-items: center;
+			justify-content: center;
+			/* #endif */
+			&__icon {
+				/* #ifndef APP-NVUE */
+				position: absolute;
+				transform: scale(0.7);
+				bottom: $uv-upload-success-icon-bottom;
+				right: $uv-upload-success-icon-right;
+				/* #endif */
+				/* #ifdef APP-NVUE */
+				width: $uv-upload-icon-width;
+				height: $uv-upload-icon-height;
+				/* #endif */
+			}
+		}
+		&__status {
+			position: absolute;
+			top: $uv-upload-status-top;
+			bottom: $uv-upload-status-bottom;
+			left: $uv-upload-status-left;
+			right: $uv-upload-status-right;
+			background-color: $uv-upload-status-bgColor;
+			@include flex(column);
+			align-items: center;
+			justify-content: center;
+			&__icon {
+				position: relative;
+				z-index: $uv-upload-status-icon-Zindex;
+			}
+			&__message {
+				font-size: $uv-upload-message-font-size;
+				color: $uv-upload-message-color;
+				margin-top: $uv-upload-message-margin-top;
+			}
+		}
+		&__button {
+			@include flex(column);
+			align-items: center;
+			justify-content: center;
+			width: $uv-upload-button-width;
+			height: $uv-upload-button-height;
+			background-color: $uv-upload-button-bgColor;
+			border-radius: $uv-upload-button-border-radius;
+			margin: $uv-upload-botton-margin;
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			&__text {
+				font-size: $uv-upload-text-font-size;
+				color: $uv-upload-text-color;
+				margin-top: $uv-upload-text-margin-top;
+			}
+			&--hover {
+				background-color: $uv-upload-hover-bgColor;
+			}
+			&--disabled {
+				opacity: $uv-upload-disabled-opacity;
+			}
+		}
+	}
+</style>

+ 90 - 0
uni_modules/uv-upload/package.json

@@ -0,0 +1,90 @@
+{
+  "id": "uv-upload",
+  "displayName": "uv-upload 上传  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.6",
+  "description": "该组件用于上传图片等文件场景。",
+  "keywords": [
+    "uv-upload",
+    "uvui",
+    "uv-ui",
+    "upload",
+    "上传"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-icon",
+			"uv-loading-icon",
+			"uv-popup"
+		],
+    "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",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-upload/readme.md

@@ -0,0 +1,11 @@
+## Upload 上传
+
+> **组件名:uv-upload**
+
+该组件用于上传图片等文件场景。
+
+### <a href="https://www.uvui.cn/components/upload.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 1 - 0
vite.config.js

@@ -24,6 +24,7 @@ export default defineConfig({
 		port: 8088,
 		proxy: {
 			'/api': {
+				// target: 'http://192.168.1.32:47001/api/',
 				target: 'https://dev3.k12100.net/teaching/api/', // 目标后端服务器地址
 				// target: 'https://www.k12100.com/teaching/api/',
 				changeOrigin: true, // 是否改变源