ZaiZai 1 year ago
parent
commit
3b06142614

+ 15 - 16
httpApi/modules/upload.js

@@ -14,9 +14,19 @@ export const getUploadApi = () => {
 }
 
 export const uploadApi = async (file, form= {}) => {
+    const url = getUploadApi() + 'blade-resource/oss/endpoint/upload-file';
+    return uploadFileApi(url, file, form)
+}
+
+export const uploadApi2 = async (file, form= {}) => {
+    const url = getUploadApi() + 'blade-resource/oss/endpoint/put-file';
+    return uploadFileApi(url, file, form)
+}
+
+export const uploadFileApi = async (url, file, form= {}) => {
     return new Promise((resolve) => {
         uni.uploadFile({
-            url: getUploadApi() + 'blade-resource/oss/endpoint/upload-file',
+            url: url,
             name: 'file',
             formData: form,
             header: getTokenHeader(),
@@ -24,26 +34,15 @@ export const uploadApi = async (file, form= {}) => {
             success:(res) => {
                 const {code, msg, data} = JSON.parse(res?.data)
                 if (code === 200) {
-                    resolve({
-                        error: false,
-                        msg: msg,
-                        data: getObjValue(data)
-                    })
+                    resolve({error: false, msg: msg, data: getObjValue(data)})
                 } else {
-                    resolve({
-                        error: true,
-                        msg: msg,
-                        data: {}
-                    })
+                    resolve({error: true, msg: msg, data: {}})
                 }
             },
             fail:()=> {
-                resolve({
-                    error: true,
-                    msg: '上传失败',
-                    data: {}
-                })
+                resolve({error: true, msg: '上传失败', data: {}})
             }
         });
     })
 }
+

+ 147 - 4
pages/work-order/add.vue

@@ -4,23 +4,64 @@
             <view class="p-3 pt-0" v-if="typeData.length > 0">
                 <uni-segmented-control :current="typeKey" :values="typeData" @clickItem="typeItemChange" />
             </view>
+            <view class="relative px-3">
+                <uv-checkbox-group v-model="typeCheckbox" placement="column">
+                    <uv-checkbox v-for="item in typeDataBiz[typeKey]?.children" :key="item.dictKey"
+                                 :label="item.dictValue" :name="item.dictValue" class="mb-2"/>
+                </uv-checkbox-group>
+            </view>
+            <view class="p-3 pt-1" v-if="isProblemVal">
+                <uv-alert showIcon type="error" description="请先选择问题类型"/>
+            </view>
+        </uni-section>
+        <uni-section title="建议反馈内容" type="line" class="mt-1">
+            <view class="p-3 pt-0">
+                <textarea v-model="opinionContent" class="p-2 text-26 radius w-full h-200" un-border="1 solid gray-3" placeholder="请输入你宝贵的建议,我们将会跟踪解决"/>
+                <uv-alert class="mt-2" showIcon type="error" description="请先填写建议反馈内容" v-if="isContent"/>
+            </view>
+        </uni-section>
+        <uni-section title="上传图片文件" type="line" class="mt-1">
+            <view class="p-3 pt-0">
+                <uv-alert type="warning" description="请上传JPG、PNG格式的图片文件,最多上传3张图片,文件大小不超过30M"/>
+                <!-- 图片文件上传 -->
+                <view class="mt-3">
+                    <hc-row :gutter="10">
+                        <hc-col :span="8" class="h-140" v-for="(item, index) in fileList" :key="index">
+                            <view class="hc-flex h-full b-rounded mr-2" un-border="1 solid gray-2" @click="previewImg(index)">
+                                <hc-image :src="item" class="b-rounded"/>
+                                <view class="hc-tr bg-red-5 text-white text-center w-38 h-38 b-rounded-lb-1 b-rounded-tr-1" @click.stop="delFileClick(index)">
+                                    <text class="relative cuIcon-close top--2 text-24"/>
+                                </view>
+                            </view>
+                        </hc-col>
+                        <hc-col :span="8" class="h-140" v-if="fileList.length < 3">
+                            <view class="hc-flex-center h-full b-rounded" un-border="2 dashed gray-2" @click="addFileClick">
+                                <text class="i-iconoir-plus text-70 text-gray-4"/>
+                            </view>
+                        </hc-col>
+                    </hc-row>
+                </view>
+                <uv-alert class="mt-2" showIcon type="error" description="请先上传图片文件" v-if="isFiles"/>
+            </view>
         </uni-section>
 
         <!--底部操作栏-->
         <HcTabbarBlock :height="70"/>
         <hc-tabbars class="flex items-center">
-            <button hover-class="none" class="cu-btn flex-1 bg-blue-5 text-white">提交工单</button>
+            <button hover-class="none" class="cu-btn flex-1 bg-blue-5 text-white" @click="saveClick">提交工单</button>
         </hc-tabbars>
     </hc-sys>
 </template>
 
 <script setup>
-import {ref} from "vue";
+import {ref,watch} from "vue";
 import mainApi from '~api/other/work-order';
 import {onLoad} from '@dcloudio/uni-app'
 import {errorToast, successToast} from "@/utils/tools";
-import {getArrValue, getObjValue} from "js-fast-way";
+import {getArrValue, getObjValue, isNullES} from "js-fast-way";
 import {useAppStore} from "@/store";
+import {chooseImage} from "@/utils/utils";
+import {uploadApi2} from "@/httpApi/modules/upload";
 
 //初始变量
 const store = useAppStore()
@@ -48,15 +89,117 @@ const queryDictBizList = async () => {
         typeData.value = []
         typeDataBiz.value = []
     }
-    console.log(typeData.value)
 }
 
 //问题类型
 const typeKey = ref(0)
 const typeData = ref([])
 const typeItemChange = ({currentIndex}) => {
+    typeCheckbox.value = []
     typeKey.value = currentIndex
 }
+
+//问题类型的checkbox
+const typeCheckbox = ref([])
+const isProblemVal = ref(false)
+watch(typeCheckbox, (val) => {
+    if (val.length > 0) {
+        isProblemVal.value = false
+    }
+})
+
+//意见内容
+const opinionContent = ref('')
+const isContent = ref(false)
+watch(opinionContent, (val) => {
+    if (!isNullES(val)) {
+        isContent.value = false
+    }
+})
+
+//上传文件的
+const fileList = ref([])
+const isFiles = ref(false)
+watch(() => fileList.value, (val) => {
+    if (val.length > 0) {
+        isFiles.value = false
+    }
+}, {deep: true})
+
+//选择文件上传
+const addFileClick = async () => {
+    const tempFiles = await chooseImage(3 - fileList.value.length)
+    if (tempFiles.length > 0) {
+        uni.showLoading({title: '上传文件中...', mask: true});
+    }
+    for (let i = 0; i < tempFiles.length; i++) {
+        const {error, msg, data} = await uploadApi2(tempFiles[i].path)
+        if (!error) {
+            const { link } = getObjValue(data)
+            fileList.value.push(link)
+        } else {
+            errorToast(msg)
+        }
+    }
+    uni.hideLoading();
+}
+
+//删除文件
+const delFileClick = (index) => {
+    fileList.value.splice(index, 1)
+}
+
+//预览图片
+const previewImg = (index) => {
+    uni.previewImage({
+        current: index,
+        urls: fileList.value,
+    });
+}
+
+//提交反馈
+const saveClick = async () => {
+    const type_data = typeData.value, type_key = typeKey.value, type_check = typeCheckbox.value
+    let isForm = true
+    //问题类型
+    if (type_check.length <= 0) {
+        isProblemVal.value = true
+        isForm = false
+    }
+    //内容
+    const content = opinionContent.value
+    if (isNullES(content)) {
+        isContent.value = true
+        isForm = false
+    }
+    //文件
+    const files = fileList.value
+    if (files.length <= 0) {
+        isFiles.value = true
+        isForm = false
+    }
+    if (!isForm) return
+    //处理数据
+    uni.showLoading({title: '提交工单中...', mask: true});
+    const problemType = type_data[type_key] + '-' + type_check.join('-')
+    //发起请求
+    const {error, code} = await mainApi.saveUserOpinion({
+        projectId: projectId.value,
+        contractId: contractId.value,
+        problemType: problemType,
+        opinionContent: content,
+        returnFiles: files
+    })
+    uni.hideLoading();
+    if (!error && code === 200) {
+        successToast('提交成功')
+        setTimeout(() => {
+            uni.navigateBack()
+        }, 1500)
+    } else {
+        errorToast('提交失败,请稍后重试')
+    }
+}
 </script>
 
 <style lang="scss" scoped>

+ 7 - 0
uni_modules/uv-alert/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次实践的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-alert 警告提示组件

+ 45 - 0
uni_modules/uv-alert/components/uv-alert/props.js

@@ -0,0 +1,45 @@
+export default {
+	props: {
+		// 显示文字
+		title: {
+			type: String,
+			default: ''
+		},
+		// 主题,success/warning/info/error
+		type: {
+			type: String,
+			default: 'warning'
+		},
+		// 辅助性文字
+		description: {
+			type: String,
+			default: ''
+		},
+		// 是否可关闭
+		closable: {
+			type: Boolean,
+			default: false
+		},
+		// 是否显示图标
+		showIcon: {
+			type: Boolean,
+			default: false
+		},
+		// 浅或深色调,light-浅色,dark-深色
+		effect: {
+			type: String,
+			default: 'light'
+		},
+		// 文字是否居中
+		center: {
+			type: Boolean,
+			default: false
+		},
+		// 字体大小
+		fontSize: {
+			type: [String, Number],
+			default: 14
+		},
+		...uni.$uv?.props?.alert
+	}
+}

+ 246 - 0
uni_modules/uv-alert/components/uv-alert/uv-alert.vue

@@ -0,0 +1,246 @@
+<template>
+	<uv-transition
+	    mode="fade"
+	    :show="show"
+	>
+		<view
+		    class="uv-alert"
+		    :class="[`uv-alert--${type}--${effect}`]"
+		    @tap.stop="clickHandler"
+		    :style="[$uv.addStyle(customStyle)]"
+		>
+			<view
+			    class="uv-alert__icon"
+			    v-if="showIcon"
+			>
+				<uv-icon
+				    :name="iconName"
+				    size="18"
+				    :color="iconColor"
+				></uv-icon>
+			</view>
+			<view
+			    class="uv-alert__content"
+			    :style="[{
+					paddingRight: closable ? '20px' : 0
+				}]"
+			>
+				<text
+				    class="uv-alert__content__title"
+				    v-if="title"
+					:style="[{
+						fontSize: $uv.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]"
+				>{{ title }}</text>
+				<text
+				    class="uv-alert__content__desc"
+					v-if="description"
+					:style="[{
+						fontSize: $uv.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]"
+				>{{ description }}</text>
+			</view>
+			<view
+			    class="uv-alert__close"
+			    v-if="closable"
+			    @tap.stop="closeHandler"
+			>
+				<uv-icon
+				    name="close"
+				    :color="iconColor"
+				    size="15"
+				></uv-icon>
+			</view>
+		</view>
+	</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';
+	/**
+	 * Alert  警告提示
+	 * @description 警告提示,展现需要关注的信息。
+	 * @tutorial https://www.uvui.cn/components/alertTips.html
+	 * 
+	 * @property {String}			title       显示的文字 
+	 * @property {String}			type        使用预设的颜色  (默认 'warning' )
+	 * @property {String}			description 辅助性文字,颜色比title浅一点,字号也小一点,可选  
+	 * @property {Boolean}			closable    关闭按钮(默认为叉号icon图标)  (默认 false )
+	 * @property {Boolean}			showIcon    是否显示左边的辅助图标   ( 默认 false )
+	 * @property {String}			effect      多图时,图片缩放裁剪的模式  (默认 'light' )
+	 * @property {Boolean}			center		文字是否居中  (默认 false )
+	 * @property {String | Number}	fontSize    字体大小  (默认 14 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event    {Function}        click       点击组件时触发
+	 * @example  <uv-alert :title="title"  type = "warning" :closable="closable" :description = "description"></uv-alert>
+	 */
+	export default {
+		name: 'uv-alert',
+		mixins: [mpMixin, mixin, props],
+		emits: ['click'],
+		data() {
+			return {
+				show: true
+			}
+		},
+		computed: {
+			iconColor() {
+				return this.effect === 'light' ? this.type : '#fff'
+			},
+			// 不同主题对应不同的图标
+			iconName() {
+				switch (this.type) {
+					case 'success':
+						return 'checkmark-circle-fill';
+						break;
+					case 'error':
+						return 'close-circle-fill';
+						break;
+					case 'warning':
+						return 'error-circle-fill';
+						break;
+					case 'info':
+						return 'info-circle-fill';
+						break;
+					case 'primary':
+						return 'more-circle-fill';
+						break;
+					default: 
+						return 'error-circle-fill';
+				}
+			}
+		},
+		methods: {
+			// 点击内容
+			clickHandler() {
+				this.$emit('click')
+			},
+			// 点击关闭按钮
+			closeHandler() {
+				this.show = false
+			}
+		}
+	}
+</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-alert {
+		position: relative;
+		background-color: $uv-primary;
+		padding: 8px 10px;
+		@include flex(row);
+		align-items: center;
+		border-top-left-radius: 4px;
+		border-top-right-radius: 4px;
+		border-bottom-left-radius: 4px;
+		border-bottom-right-radius: 4px;
+
+		&--primary--dark {
+			background-color: $uv-primary;
+		}
+
+		&--primary--light {
+			background-color: #ecf5ff;
+		}
+
+		&--error--dark {
+			background-color: $uv-error;
+		}
+
+		&--error--light {
+			background-color: #FEF0F0;
+		}
+
+		&--success--dark {
+			background-color: $uv-success;
+		}
+
+		&--success--light {
+			background-color: #f5fff0;
+		}
+
+		&--warning--dark {
+			background-color: $uv-warning;
+		}
+
+		&--warning--light {
+			background-color: #FDF6EC;
+		}
+
+		&--info--dark {
+			background-color: $uv-info;
+		}
+
+		&--info--light {
+			background-color: #f4f4f5;
+		}
+
+		&__icon {
+			margin-right: 5px;
+		}
+
+		&__content {
+			@include flex(column);
+			flex: 1;
+
+			&__title {
+				color: $uv-main-color;
+				font-size: 14px;
+				font-weight: bold;
+				color: #fff;
+				margin-bottom: 2px;
+			}
+
+			&__desc {
+				color: $uv-main-color;
+				font-size: 14px;
+				flex-wrap: wrap;
+				color: #fff;
+			}
+		}
+
+		&__title--dark,
+		&__desc--dark {
+			color: #FFFFFF;
+		}
+
+		&__text--primary--light,
+		&__text--primary--light {
+			color: $uv-primary;
+		}
+
+		&__text--success--light,
+		&__text--success--light {
+			color: $uv-success;
+		}
+
+		&__text--warning--light,
+		&__text--warning--light {
+			color: $uv-warning;
+		}
+
+		&__text--error--light,
+		&__text--error--light {
+			color: $uv-error;
+		}
+
+		&__text--info--light,
+		&__text--info--light {
+			color: $uv-info;
+		}
+
+		&__close {
+			position: absolute;
+			top: 11px;
+			right: 10px;
+		}
+	}
+</style>

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

@@ -0,0 +1,88 @@
+{
+	"id": "uv-alert",
+	"displayName": "uv-alert 警告提示 全面兼容小程序、nvue、vue2、vue3等多端",
+	"version": "1.0.2",
+	"description": "uv-alert 警告提示,展现需要关注的信息。灵活配置,功能齐全,兼容全端",
+	"keywords": [
+        "alert",
+        "uvui",
+        "uv-ui",
+        "警告提示"
+    ],
+	"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",
+			"uv-icon"
+		],
+		"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"
+				}
+			}
+		}
+	}
+}

+ 15 - 0
uni_modules/uv-alert/readme.md

@@ -0,0 +1,15 @@
+## Alert 警告提示
+
+> **组件名:uv-alert**
+
+警告提示,展现需要关注的信息。
+
+当某个页面需要向用户显示警告的信息时。
+
+非浮层的静态展现形式,始终展现,不会自动消失,用户可以点击关闭。
+
+### <a href="https://www.uvui.cn/components/alert.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>

+ 15 - 0
uni_modules/uv-transition/changelog.md

@@ -0,0 +1,15 @@
+## 1.0.6(2023-07-24)
+1. 优化  nvue模式下增加cellChild参数,是否在list中cell节点下,nvue中cell下建议设置成true
+## 1.0.5(2023-07-02)
+修改VUE3模式下可能存在的BUG
+## 1.0.4(2023-07-02)
+uv-transition  动画组件,代码重构优化,性能更加友好,增加自定义动画功能。详情参考文档:https://www.uvui.cn/components/transition.html
+## 1.0.3(2023-06-12)
+1. 恢复this.$nextTick的使用,经过测试百度等平台无问题
+## 1.0.2(2023-05-23)
+1. 百度小程序等平台不支持this.$nextick,修改成延时
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增动画组件

+ 131 - 0
uni_modules/uv-transition/components/uv-transition/createAnimation.js

@@ -0,0 +1,131 @@
+// const defaultOption = {
+// 	duration: 300,
+// 	timingFunction: 'linear',
+// 	delay: 0,
+// 	transformOrigin: '50% 50% 0'
+// }
+// #ifdef APP-NVUE
+const nvueAnimation = uni.requireNativePlugin('animation')
+// #endif
+class MPAnimation {
+	constructor(options, _this) {
+		this.options = options
+		// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
+		this.animation = uni.createAnimation({
+			...options
+		})
+		this.currentStepAnimates = {}
+		this.next = 0
+		this.$ = _this
+
+	}
+
+	_nvuePushAnimates(type, args) {
+		let aniObj = this.currentStepAnimates[this.next]
+		let styles = {}
+		if (!aniObj) {
+			styles = {
+				styles: {},
+				config: {}
+			}
+		} else {
+			styles = aniObj
+		}
+		if (animateTypes1.includes(type)) {
+			if (!styles.styles.transform) {
+				styles.styles.transform = ''
+			}
+			let unit = ''
+			if(type === 'rotate'){
+				unit = 'deg'
+			}
+			styles.styles.transform += `${type}(${args+unit}) `
+		} else {
+			styles.styles[type] = `${args}`
+		}
+		this.currentStepAnimates[this.next] = styles
+	}
+	_animateRun(styles = {}, config = {}) {
+		let ref = this.$.$refs['ani'].ref
+		if (!ref) return
+		return new Promise((resolve, reject) => {
+			nvueAnimation.transition(ref, {
+				styles,
+				...config
+			}, res => {
+				resolve()
+			})
+		})
+	}
+
+	_nvueNextAnimate(animates, step = 0, fn) {
+		let obj = animates[step]
+		if (obj) {
+			let {
+				styles,
+				config
+			} = obj
+			this._animateRun(styles, config).then(() => {
+				step += 1
+				this._nvueNextAnimate(animates, step, fn)
+			})
+		} else {
+			this.currentStepAnimates = {}
+			typeof fn === 'function' && fn()
+			this.isEnd = true
+		}
+	}
+
+	step(config = {}) {
+		// #ifndef APP-NVUE
+		this.animation.step(config)
+		// #endif
+		// #ifdef APP-NVUE
+		this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
+		this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
+		this.next++
+		// #endif
+		return this
+	}
+
+	run(fn) {
+		// #ifndef APP-NVUE
+		this.$.animationData = this.animation.export()
+		this.$.timer = setTimeout(() => {
+			typeof fn === 'function' && fn()
+		}, this.$.durationTime)
+		// #endif
+		// #ifdef APP-NVUE
+		this.isEnd = false
+		let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
+		if(!ref) return
+		this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
+		this.next = 0
+		// #endif
+	}
+}
+
+
+const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
+	'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
+	'translateZ'
+]
+const animateTypes2 = ['opacity', 'backgroundColor']
+const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
+animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
+	MPAnimation.prototype[type] = function(...args) {
+		// #ifndef APP-NVUE
+		this.animation[type](...args)
+		// #endif
+		// #ifdef APP-NVUE
+		this._nvuePushAnimates(type, args)
+		// #endif
+		return this
+	}
+})
+
+export function createAnimation(option, _this) {
+	if(!_this) return
+	clearTimeout(_this.timer)
+	return new MPAnimation(option, _this)
+}

+ 31 - 0
uni_modules/uv-transition/components/uv-transition/props.js

@@ -0,0 +1,31 @@
+export default {
+	props: {
+		// 是否展示组件
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 使用的动画模式
+		mode: {
+			type: [Array, String, null],
+			default() {
+				return 'fade'
+			}
+		},
+		// 动画的执行时间,单位ms
+		duration: {
+			type: [String, Number],
+			default: 300
+		},
+		// 使用的动画过渡函数
+		timingFunction: {
+			type: String,
+			default: 'ease-out'
+		},
+		customClass: {
+			type: String,
+			default: ''
+		},
+		...uni.$uv?.props?.transition
+	}
+}

+ 315 - 0
uni_modules/uv-transition/components/uv-transition/uv-transition.vue

@@ -0,0 +1,315 @@
+<template>
+  <!-- #ifndef APP-NVUE -->
+  <view 
+		v-if="isShow" 
+		ref="ani" 
+		:animation="animationData" 
+		:class="customClass" 
+		:style="transformStyles" 
+		@click="onClick">
+		<slot></slot>
+	</view>
+  <!-- #endif -->
+  <!-- #ifdef APP-NVUE -->
+  <view 
+		v-if="isShow" 
+		ref="ani" 
+		:animation="animationData" 
+		:class="customClass" 
+		:style="transformStyles" 
+		@click="onClick">
+		<slot></slot>
+	</view>
+  <!-- #endif -->
+</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 { createAnimation } from './createAnimation'
+	/**
+	* transition  动画组件
+	* @description
+	* @tutorial
+	* @property {Boolean}	show	控制组件显示或关闭 (默认 false )
+	* @property {Array | String	}	mode	内置过渡动画类型 (默认 'fade' )
+	* @value fade 渐隐渐出过渡
+	* @value slide-top 由上至下过渡
+	* @value slide-bottom 由下至上过渡
+	* @value slide-left 由左至右过渡
+	* @value slide-right 由右至左过渡
+	* @value zoom-in 由小到大过渡
+	* @value zoom-out 由大到小过渡
+	* @property {String | Number}	duration	动画的执行时间,单位ms (默认 300 )
+	* @property {String} timingFunction	使用的动画过渡函数 (默认 'ease-out' )
+	* @property {Object} customStyle	自定义样式
+	* @property {String} customClass	自定义类名
+	* @event {Function} click 点击组件触发	
+	* @event {Function} change	过渡动画结束时触发	
+	* @example 
+	*/
+	export default {
+		name: 'uv-transition',
+		mixins: [mpMixin,mixin],
+		emits:['click','change'],
+		props: {
+			// 是否展示组件
+			show: {
+				type: Boolean,
+				default: false
+			},
+			// 使用的动画模式
+			mode: {
+				type: [Array, String, null],
+				default() {
+					return 'fade'
+				}
+			},
+			// 动画的执行时间,单位ms
+			duration: {
+				type: [String, Number],
+				default: 300
+			},
+			// 使用的动画过渡函数
+			timingFunction: {
+				type: String,
+				default: 'ease-out'
+			},
+			customClass: {
+				type: String,
+				default: ''
+			},
+			// nvue模式下 是否直接显示,在uv-list等cell下面使用就需要设置
+			cellChild: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data(){
+			return {
+				isShow: false,
+				transform: '',
+				opacity: 1,
+				animationData: {},
+				durationTime: 300,
+				config: {}
+			}
+		},
+		watch: {
+			show: {
+				handler(newVal) {
+					if (newVal) {
+						this.open();
+					} else {
+						// 避免上来就执行 close,导致动画错乱
+						if (this.isShow) {
+							this.close();
+						}
+					}
+				},
+				immediate: true
+			}
+		},
+		computed: {
+			// 初始化动画条件
+			transformStyles() {
+				const style = {
+					transform: this.transform,
+					opacity: this.opacity,
+					...this.$uv.addStyle(this.customStyle),
+					'transition-duration': `${this.duration / 1000}s`
+				};
+				return this.$uv.addStyle(style,'string');
+			}
+		},
+		created() {
+			// 动画默认配置
+			this.config = {
+				duration: this.duration,
+				timingFunction: this.timingFunction,
+				transformOrigin: '50% 50%',
+				delay: 0
+			};
+			this.durationTime = this.duration;
+		},
+		methods: {
+			/**
+			 *  ref 触发 初始化动画
+			 */
+			init(obj = {}) {
+				if (obj.duration) {
+					this.durationTime = obj.duration;
+				}
+				this.animation = createAnimation(Object.assign(this.config, obj),this);
+			},
+			/**
+			 * 点击组件触发回调
+			 */
+			onClick() {
+				this.$emit('click', {
+					detail: this.isShow
+				})
+			},
+			/**
+			 * ref 触发 动画分组
+			 * @param {Object} obj
+			 */
+			step(obj, config = {}) {
+				if (!this.animation) return;
+				for (let i in obj) {
+					try {
+						if(typeof obj[i] === 'object'){
+							this.animation[i](...obj[i]);
+						}else{
+							this.animation[i](obj[i]);
+						}
+					} catch (e) {
+						console.error(`方法 ${i} 不存在`);
+					}
+				}
+				this.animation.step(config);
+				return this;
+			},
+			/**
+			 *  ref 触发 执行动画
+			 */
+			run(fn) {
+				if (!this.animation) return;
+				this.animation.run(fn);
+			},
+			// 开始过度动画
+			open() {
+				clearTimeout(this.timer);
+				this.transform = '';
+				this.isShow = true;
+				let { opacity, transform } = this.styleInit(false);
+				if (typeof opacity !== 'undefined') {
+					this.opacity = opacity;
+				}
+				this.transform = transform;
+				// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
+				this.$nextTick(() => {
+					// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
+					this.timer = setTimeout(() => {
+						this.animation = createAnimation(this.config, this);
+						this.tranfromInit(false).step();
+						// #ifdef APP-NVUE
+						if(this.cellChild) {
+							this.opacity = 1;
+						} else{
+							this.animation.run();
+						}
+						// #endif
+						// #ifndef APP-NVUE
+						this.animation.run();
+						// #endif
+						// #ifdef VUE3
+						// #ifdef H5
+						this.opacity = 1;
+						// #endif
+						// #endif
+						this.$emit('change', {
+							detail: this.isShow
+						})
+					}, 20);
+				})
+			},
+			// 关闭过渡动画
+			close(type) {
+				if (!this.animation) return;
+				this.tranfromInit(true)
+					.step()
+					.run(() => {
+						this.isShow = false;
+						this.animationData = null;
+						this.animation = null;
+						let { opacity, transform } = this.styleInit(false);
+						this.opacity = opacity || 1;
+						this.transform = transform;
+						this.$emit('change', {
+							detail: this.isShow
+						});
+					})
+			},
+			// 处理动画开始前的默认样式
+			styleInit(type) {
+				let styles = {
+					transform: ''
+				};
+				let buildStyle = (type, mode) => {
+					if (mode === 'fade') {
+						styles.opacity = this.animationType(type)[mode];
+					} else {
+						styles.transform += this.animationType(type)[mode] + ' ';
+					}
+				}
+				if (typeof this.mode === 'string') {
+					buildStyle(type, this.mode);
+				} else {
+					this.mode.forEach(mode => {
+						buildStyle(type, mode)
+					})
+				}
+				return styles
+			},
+			// 处理内置组合动画
+			tranfromInit(type) {
+				let buildTranfrom = (type, mode) => {
+					let aniNum = null;
+					if (mode === 'fade') {
+						aniNum = type ? 0 : 1;
+					} else {
+						aniNum = type ? '-100%' : '0';
+						if (mode === 'zoom-in') {
+							aniNum = type ? 0.8 : 1
+						}
+						if (mode === 'zoom-out') {
+							aniNum = type ? 1.2 : 1
+						}
+						if (mode === 'slide-right') {
+							aniNum = type ? '100%' : '0'
+						}
+						if (mode === 'slide-bottom') {
+							aniNum = type ? '100%' : '0'
+						}
+					}
+					this.animation[this.animationMode()[mode]](aniNum)
+				}
+				if (typeof this.mode === 'string') {
+					buildTranfrom(type, this.mode)
+				} else {
+					this.mode.forEach(mode => {
+						buildTranfrom(type, mode)
+					})
+				}
+				return this.animation;
+			},
+			animationType(type) {
+				return {
+					fade: type ? 1 : 0,
+					'slide-top': `translateY(${type ? '0' : '-100%'})`,
+					'slide-right': `translateX(${type ? '0' : '100%'})`,
+					'slide-bottom': `translateY(${type ? '0' : '100%'})`,
+					'slide-left': `translateX(${type ? '0' : '-100%'})`,
+					'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
+					'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
+				}
+			},
+			// 内置动画类型与实际动画对应字典
+			animationMode() {
+				return {
+					fade: 'opacity',
+					'slide-top': 'translateY',
+					'slide-right': 'translateX',
+					'slide-bottom': 'translateY',
+					'slide-left': 'translateX',
+					'zoom-in': 'scale',
+					'zoom-out': 'scale'
+				}
+			},
+			// 驼峰转中横线
+			toLine(name) {
+				return name.replace(/([A-Z])/g, '-$1').toLowerCase()
+			}
+		}
+	}
+</script>

+ 87 - 0
uni_modules/uv-transition/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-transition",
+  "displayName": "uv-transition 动画 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.6",
+  "description": "transition 该组件用于组件的动画过渡效果。",
+  "keywords": [
+    "uv-transition",
+    "uvui",
+    "uv-ui",
+    "transition",
+    "动画"
+],
+  "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"
+				}
+			}
+		}
+  }
+}

+ 15 - 0
uni_modules/uv-transition/readme.md

@@ -0,0 +1,15 @@
+## Transition 动画
+
+> **组件名:uv-transition**
+
+该组件用于组件的动画过渡效果,支持自定义动画,开箱即用。
+
+# <a href="https://www.uvui.cn/components/transition.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 2 - 0
uni_modules/uv-ui-tools/changelog.md

@@ -1,3 +1,5 @@
+## 1.1.11(2023-09-04)
+1. 1.1.11版本
 ## 1.1.10(2023-08-31)
 1. 修复customStyle和customClass存在冲突的问题
 ## 1.1.9(2023-08-27)

+ 2 - 2
uni_modules/uv-ui-tools/libs/config/config.js

@@ -1,5 +1,5 @@
-// 此版本发布于2023-08-30
-const version = '1.1.10'
+// 此版本发布于2023-09-02
+const version = '1.1.11'
 
 // 开发环境才提示,生产环境不会提示
 if (process.env.NODE_ENV === 'development') {

+ 1 - 1
uni_modules/uv-ui-tools/package.json

@@ -1,7 +1,7 @@
 {
   "id": "uv-ui-tools",
   "displayName": "uv-ui-tools 工具集 全面兼容vue3+2、app、h5、小程序等多端",
-  "version": "1.1.10",
+  "version": "1.1.11",
   "description": "uv-ui-tools,集成工具库,强大的Http请求封装,清晰的文档说明,开箱即用。方便使用,可以全局使用",
   "keywords": [
     "uv-ui-tools,uv-ui组件库,工具集,uvui,uView2.x"