更新大理市民卡app
diff --git a/components/uni-rate/uni-rate.vue b/components/uni-rate/uni-rate.vue
new file mode 100644
index 0000000..5e18a58
--- /dev/null
+++ b/components/uni-rate/uni-rate.vue
@@ -0,0 +1,295 @@
+<template>
+	<view>
+		<view
+		    ref="uni-rate"
+		    class="uni-rate"
+		>
+			<view
+			    class="uni-rate__icon"
+			    :style="{ 'margin-right': margin + 'px' }"
+			    v-for="(star, index) in stars"
+			    :key="index"
+			    @touchstart.stop="touchstart"
+			    @touchmove.stop="touchmove"
+			>
+				<uni-icons
+				    :color="color"
+				    :size="size"
+				    :type="isFill ? 'star-filled' : 'star'"
+				/>
+				<!-- #ifdef APP-NVUE -->
+				<view
+				    :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}"
+				    class="uni-rate__icon-on"
+				>
+					<uni-icons
+					    style="text-align: left;"
+					    :color="disabled?'#ccc':activeColor"
+					    :size="size"
+					    type="star-filled"
+					/>
+				</view>
+				<!-- #endif -->
+				<!-- #ifndef APP-NVUE -->
+				<view
+				    :style="{ width: star.activeWitch}"
+				    class="uni-rate__icon-on"
+				>
+					<uni-icons
+					    :color="disabled?disabledColor:activeColor"
+					    :size="size"
+					    type="star-filled"
+					/>
+				</view>
+				<!-- #endif -->
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const dom = uni.requireNativePlugin('dom');
+	// #endif
+	import uniIcons from "../uni-icons/uni-icons.vue";
+	/**
+	 * Rate 评分
+	 * @description 评分组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=33
+	 * @property {Boolean} 	isFill = [true|false] 		星星的类型,是否为实心类型, 默认为实心
+	 * @property {String} 	color 						未选中状态的星星颜色,默认为 "#ececec"
+	 * @property {String} 	activeColor 				选中状态的星星颜色,默认为 "#ffca3e"
+	 * @property {String} 	disabledColor 				禁用状态的星星颜色,默认为 "#c0c0c0"
+	 * @property {Number} 	size 						星星的大小
+	 * @property {Number} 	value/v-model 				当前评分
+	 * @property {Number} 	max 						最大评分评分数量,目前一分一颗星
+	 * @property {Number} 	margin 						星星的间距,单位 px
+	 * @property {Boolean} 	disabled = [true|false] 	是否为禁用状态,默认为 false
+	 * @property {Boolean} 	readonly = [true|false] 	是否为只读状态,默认为 false
+	 * @property {Boolean} 	allowHalf = [true|false] 	是否实现半星,默认为 false
+	 * @property {Boolean} 	touchable = [true|false] 	是否支持滑动手势,默认为 true
+	 * @event {Function} change 						uniRate 的 value 改变时触发事件,e={value:Number}
+	 */
+
+	export default {
+		components: {
+			uniIcons
+		},
+		name: "UniRate",
+		props: {
+			isFill: {
+				// 星星的类型,是否镂空
+				type: [Boolean, String],
+				default: true
+			},
+			color: {
+				// 星星未选中的颜色
+				type: String,
+				default: "#ececec"
+			},
+			activeColor: {
+				// 星星选中状态颜色
+				type: String,
+				default: "#ffca3e"
+			},
+			disabledColor: {
+				// 星星禁用状态颜色
+				type: String,
+				default: "#c0c0c0"
+			},
+			size: {
+				// 星星的大小
+				type: [Number, String],
+				default: 24
+			},
+			value: {
+				// 当前评分
+				type: [Number, String],
+				default: 1
+			},
+			max: {
+				// 最大评分
+				type: [Number, String],
+				default: 5
+			},
+			margin: {
+				// 星星的间距
+				type: [Number, String],
+				default: 0
+			},
+			disabled: {
+				// 是否可点击
+				type: [Boolean, String],
+				default: false
+			},
+			readonly: {
+				// 是否只读
+				type: [Boolean, String],
+				default: false
+			},
+			allowHalf: {
+				// 是否显示半星
+				type: [Boolean, String],
+				default: false
+			},
+			touchable: {
+				// 是否支持滑动手势
+				type: [Boolean, String],
+				default: true
+			}
+		},
+		data() {
+			return {
+				valueSync: ""
+			};
+		},
+		watch: {
+			value(newVal) {
+				this.valueSync = Number(newVal);
+			}
+		},
+		computed: {
+			stars() {
+				const value = this.valueSync ? this.valueSync : 0;
+				const starList = [];
+				const floorValue = Math.floor(value);
+				const ceilValue = Math.ceil(value);
+				for (let i = 0; i < this.max; i++) {
+					if (floorValue > i) {
+						starList.push({
+							activeWitch: "100%"
+						});
+					} else if (ceilValue - 1 === i) {
+						starList.push({
+							activeWitch: (value - floorValue) * 100 + "%"
+						});
+					} else {
+						starList.push({
+							activeWitch: "0"
+						});
+					}
+				}
+				return starList;
+			}
+		},
+		created() {
+			this.valueSync = Number(this.value);
+			this._rateBoxLeft = 0
+			this._oldValue = null
+		},
+		mounted() {
+				setTimeout(() => {
+					this._getSize()
+				}, 100)
+		},
+		methods: {
+			touchstart(e) {
+				if (this.readonly || this.disabled) return
+				const {
+					clientX,
+					screenX
+				} = e.changedTouches[0]
+				// TODO 做一下兼容,只有 Nvue 下才有 screenX,其他平台式 clientX
+				this._getRateCount(clientX || screenX)
+			},
+			touchmove(e) {
+				if (this.readonly || this.disabled || !this.touchable) return
+				const {
+					clientX,
+					screenX
+				} = e.changedTouches[0]
+				this._getRateCount(clientX || screenX)
+			},
+			/**
+			 * 获取星星个数
+			 */
+			_getRateCount(clientX) {
+				const rateMoveRange = clientX - this._rateBoxLeft
+				const index = parseInt(rateMoveRange / (this.size + this.margin))
+				const range = parseInt(rateMoveRange - ((this.size + this.margin) * index))
+				let value = 0
+
+				if (this.allowHalf) {
+					if (range > (this.size / 2)) {
+						value = index + 1
+					} else {
+						value = index + 0.5
+					}
+				} else {
+					value = index + 1
+				}
+
+				value = Math.max(0.5, Math.min(value, this.max))
+				if (this.valueSync !== value) {
+					this.valueSync = value
+					this._onChange()
+				}
+				// const rateCount = parseInt(rateMoveRange / (this.size / 2)) + 1
+			},
+
+			/**
+			 * 触发动态修改
+			 */
+			_onChange() {
+
+				this.$emit("input", this.valueSync);
+				this.$emit("change", {
+					value: this.valueSync
+				});
+			},
+			/**
+			 * 获取星星距离屏幕左侧距离
+			 */
+			_getSize() {
+				// #ifndef APP-NVUE
+				uni.createSelectorQuery()
+					.in(this)
+					.select('.uni-rate')
+					.boundingClientRect()
+					.exec(ret => {
+						if (ret) {
+							this._rateBoxLeft = ret[0].left
+						}
+					})
+				// #endif
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['uni-rate'], (ret) => {
+					const size = ret.size
+					if (size) {
+						this._rateBoxLeft = size.left
+					}
+				})
+				// #endif
+			}
+		}
+	};
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+	.uni-rate {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		line-height: 1;
+		font-size: 0;
+		flex-direction: row;
+	}
+
+	.uni-rate__icon {
+		position: relative;
+		line-height: 1;
+		font-size: 0;
+	}
+
+	.uni-rate__icon-on {
+		overflow: hidden;
+		position: absolute;
+		top: 0;
+		left: 0;
+		line-height: 1;
+		text-align: left;
+	}
+</style>