From f4fd489475500b0d41dde019963307d217321d50 Mon Sep 17 00:00:00 2001
From: xuxueyang <xuxy@fengyuntec.com>
Date: 星期日, 20 十月 2024 20:30:49 +0800
Subject: [PATCH] update 定位

---
 pages/user/address/address.vue |  525 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 454 insertions(+), 71 deletions(-)

diff --git a/pages/user/address/address.vue b/pages/user/address/address.vue
index 75053a7..82a5fdd 100644
--- a/pages/user/address/address.vue
+++ b/pages/user/address/address.vue
@@ -1,19 +1,53 @@
 <template>
-	<!-- 收获地址列表 -->
+	<!-- 收货地址列表 -->
 	<view class="p15 container-address">
+		<view class="search-container m-t-12 flex">
+			<view class="flex1 input" @click="selectWxAddress('input')">
+				<u-input placeholder="小区名/店铺名/写字楼/街道名等" disabled v-model="search_flow">
+					<template slot="suffix">
+						<uni-icons color="#20613D" type="search" size="24"></uni-icons>
+					</template>
+				</u-input>
+			</view>
+		</view>
+		<view class="m-t-12 ">
+			<view class="desc-gray">当前位置</view>
+			<view class="flex current-address">
+				<view class="flex1">
+					{{ addressDesc || (address&&address.id?address.address: '选择地址')  }}
+
+				</view>
+				<view class="flex" @click="changeCurrentAddress">
+					<view>
+						<image style="width: 30rpx;height: 30rpx;margin-top: 6rpx;margin-right: 10rpx;" class="m-t-2"
+							src="https://hmy-flower.oss-cn-shanghai.aliyuncs.com/a2/a248385d4fe748ca81ee058ea5bf8c47icon-address.jpg">
+						</image>
+					</view>
+					<view>重新定位</view>
+				</view>
+			</view>
+		</view>
+		<view class="m-t-12 ">
+			<view class="desc-gray">我的收获地址</view>
+
+		</view>
 		<no-data v-if="!list||list.length==0" style="width: 100%;"></no-data>
 
 		<!-- 循环 -->
-		<view v-for="(item,index) of list" :key="index" class="location-each">
+		<view v-for="(item,index) of list" :key="index" class="location-each m-t-12">
 			<view class="flex">
 				<view class="container-info">
 					<view class="flex">
-						<view class="name">{{item.name || '-'}}</view>
-						<view class="tel">{{item.tel||'-'}}</view>
+						<view class="name">{{ item.name || '-' }}</view>
+						<view class="tel">{{ item.tel || '-' }}</view>
 					</view>
 					<view class="address">
-						{{item.address}}
-
+						{{ item['province'] || '' }}{{ item['city'] && ('/' + item['city']) || '' }}{{
+						  item['region'] && ('/' + item['region']) || ''
+						}}
+					</view>
+					<view class="address">
+						{{ item.address }}
 					</view>
 				</view>
 				<!-- <view class="h-line"></view> -->
@@ -28,21 +62,13 @@
 			</view>
 			<view class="v-line"></view>
 			<view class="flex">
-				<view class="flex" @click="changeDefaultAddress(item,index)">
-					<!-- <label class="radio flex" > -->
-					<!-- 		<radio value="r1" :checked="item.defaulted" disabled
-							style="transform:scale(0.6)" v-if="item.defaulted" />
-						<radio value="r1" 
-							style="transform:scale(0.6)" :checked="item.defaulted" v-if="!item.defaulted"  @change="changeDefaultAddress(item,index)"/>	
-							 -->
-
-					<!-- </label> -->
-					<view class="radio m-t-2 m-r-10" :class="[item.defaulted?'select':'']">
+				<!--        -->
+				<view class="flex">
+					<view class="radio m-t-2 m-r-10" :class="[item.isDefault?'select':'']"
+						@click="changeDefaultAddress(item,index)">
 
 					</view>
-					<span class="desc">{{item.defaulted?'默认地址:':'当前地址:'}}{{item.schoolAreaStr||''}}
-						{{item.blockStr||''}}
-						{{item.room||''}}</span>
+					<span class="desc" style="word-break: break-all;">{{ item.isDefault ? '默认地址:' : '当前地址:' }}</span>
 					<!-- 当前地址/默认地址勾选 -->
 				</view>
 				<view class="desc del t-red" @click="deleteAddress(item)">
@@ -57,20 +83,14 @@
 		<view style="height: 92rpx;width: 690rpx;">
 			&nbsp;
 		</view>
-		<view class="button-green button-fixed-bottom" style="width: 690rpx;line-height: 92rpx;height: 92rpx;" @click=" ()=>{
-				delete this.submitForm.id
-				this.submitForm['name'] = ''
-				this.submitForm['tel'] = ''
-				this.submitForm['tel'] = ''
-				this.submitForm['area'] = ''
-				
-				openAddressForm()
-			}">添加收货地址</view>
+		<view class="button-green button-fixed-bottom" v-if="currentInfo.id"
+			style="width: 690rpx;line-height: 92rpx;height: 92rpx;" @click="addAddress">添加收货地址
+		</view>
 
 		<uni-popup ref="popup_form" type="top" :mask-click="false">
 			<view class="popup-address-form">
 				<view class="close-parent">
-					{{submitForm.id?'新增':'编辑'}}收货地址
+					{{ submitForm.id ? '新增' : '编辑' }}收货地址
 					<uni-icons class="close" type="closeempty" @click="closeAddressForm"></uni-icons>
 				</view>
 				<!--收货人、手机号、校区、详细地址 -->
@@ -85,20 +105,44 @@
 					</view>
 					<view class="form-item">
 						<view class="form-item-label require">
-							请选择收获地址
+							手机号
 						</view>
 						<view class="form-item-value">
-							<input v-model="submitForm.room" placeholder="请选择收获地址" disabled class="form-input"></input>
-							<!-- todo 点击定位 -->
+							<input v-model="submitForm.tel" placeholder="请输入收货人手机号" class="form-input"
+								@blur="validatePhoneNumber()"></input>
 						</view>
 					</view>
 					<view class="form-item">
 						<view class="form-item-label require">
+							收货地址
+						</view>
+						<view class="m-l-a m-r-0 flex " :class="[!dto['province']?'desc-gray':'']">
+							<uni-data-picker :area="true" @change="(e)=>{PickArea(submitForm,e)}" placeholder=""
+								:localdata="regionDataPlus">
+								{{ submitForm['province'] || '请选择' }}{{ submitForm['city'] && ('/' + submitForm['city']) || '' }}{{
+                  submitForm['region'] && ('/' + submitForm['region']) || ''
+                }}
+							</uni-data-picker>
+							<u-icon class="m-l-a" name="arrow-right"></u-icon>
+						</view>
+
+
+					</view>
+					<view class="form-item" style="height: unset;min-height: 36rpx;">
+						<view class="form-item-label require">
 							详细地址
 						</view>
-						<view class="form-item-value">
-							<input v-model="submitForm.address" placeholder="请输入详细地址" class="form-input"></input>
+						<view class="form-item-value flex flex1">
+							<input v-model="submitForm.address" placeholder="请输入详细地址" class="form-input flex1"></input>
+							<!-- <u-textarea v-model="submitForm.address" autoHeight placeholder="请输入详细地址" class="form-input flex1" >
+							
+							</u-textarea>
 
+							<view class="m-l-15 m-r-0 flex m-t-8" style="min-width: 120rpx;color: #7CC662;text-align: right;"
+								@click="selectWxAddress('form')">
+								<uni-icons color="#7CC662" type="location" size="18"></uni-icons>
+								定位
+							</view> -->
 						</view>
 					</view>
 					<view class="form-item">
@@ -106,8 +150,8 @@
 							是否默认
 						</view>
 						<view class="form-item-value">
-							<radio value="r1" :checked="submitForm.defaulted"
-								@click="submitForm.defaulted=!submitForm.defaulted"
+							<radio value="r1" :checked="submitForm.isDefault"
+								@click="submitForm.isDefault=!submitForm.isDefault"
 								style="transform:scale(0.6);margin-top: -8rpx;" />
 						</view>
 					</view>
@@ -121,6 +165,15 @@
 </template>
 
 <script>
+	// #ifdef PUB_CUSTOMER
+	// import qqmapwx from '@/plugins/qqmap-wx-jssdk.min.js';
+	// const lockey = 'GSBBZ-CJA3U-NNDVH-GE65N-6FIF6-ZGBCU'; //使用在腾讯位置服务申请的key
+	// const chooseLocation = requirePlugin('chooseLocation');	
+	// #endif
+
+	import {
+		mapState
+	} from 'vuex'
 	export default {
 		async onPullDownRefresh() {
 			this.page.current = 0
@@ -130,8 +183,56 @@
 		async onLoad(options) {
 			// this.list = [{}, {}]
 			this.source = options.source || ''
-			this.listApi = '/api/address/customer/list'
-			await this.getList()
+			this.listApi = '/api/address/list'
+			if (!this.currentInfo.id) {
+
+			} else {
+				await this.getList()
+
+			}
+
+		},
+		onShow() {
+			// const location = chooseLocation.getLocation(); // 如果点击确认选点按钮,则返回选点结果对象,否则返回null
+			// // console.log('onshow location', location, this.tmp_picker_lock_index, this.formcodes["LOC_ONE"])
+			// // address: "江苏省苏州市吴中区太湖东路280号"
+			// // city: "苏州市"
+			// // district: "吴中区"
+			// // latitude: 31.262438
+			// // longitude: 120.633985
+			// // name: "太湖东路280号小区"
+			// // province: "江苏省"
+			// if (location && location.address) {
+			// 	this.submitForm.address = location.address
+			// 	this.submitForm.latitude = location.latitude
+			// 	this.submitForm.longitude = location.longitude
+			// 	if (!this.submitForm.province && location.province) {
+			// 		this.submitForm.province = location.province
+			// 		if (!this.submitForm.city && location.city) {
+			// 			this.submitForm.city = location.city
+			// 		}
+			// 		if (!this.submitForm.region && location.district) {
+			// 			this.submitForm.region = location.district
+			// 		}
+			// 		if (!this.submitForm.region && location.city) {
+			// 			this.submitForm.region = location.city
+			// 		}
+			// 	}
+			// 	this.$forceUpdate()
+			// }
+		},
+		computed: {
+			...mapState({
+				address: state => {
+					return state.defaultaddress || {}
+				},
+				addressDesc: state => {
+					return state.addressDesc || ''
+				},
+				// search_flow() {
+				// 	return this.addressDesc || ''
+				// }
+			}),
 
 		},
 		data() {
@@ -141,34 +242,285 @@
 					name: '',
 					tel: '',
 					address: '',
-					schoolArea: '',
-					block: '',
-					room: '',
-					defaulted: false
+					isDefault: false,
+					province: '',
+					city: '',
+					region: '',
+					latitude: 0,
+					longitude: 0,
 				},
+				regionDataPlus: [],
+				list: [],
+				search_flow: '',
 			};
 		},
 		methods: {
+			selectWxAddress(source) {
+				let that = this
+
+				{
+					//表单选择了地址
+					wx.chooseLocation({
+						success: location => {
+							console.log('success loc', location,source)
+							// address: "江苏省苏州市吴中区太湖东路288号"
+							// errMsg: "chooseLocation:ok"
+							// latitude: 31.26249
+							// longitude: 120.63212
+							// name: "苏州市吴中区人民政府"
+							if (source === 'form') {
+								// (location.address || '') +
+								that.submitForm.address = (location.name || location.address || '')
+								that.submitForm.latitude = location.latitude || 0
+								that.submitForm.longitude = location.longitude || 0
+							} else if (source === 'input') {
+								// (location.address || '') +
+								// that.search_flow =   (location.name || '')
+								that.$store.commit('setAddressDesc', (location.name || location.address || ''))
+								that.$forceUpdate()
+							}
+
+							// if (!that.submitForm.province && location.provinceName) {
+							// 	that.submitForm.province = location.provinceName
+							// 	if (!that.submitForm.city && location.cityName) {
+							// 		that.submitForm.city = location.cityName
+							// 	}
+							// 	if (!that.submitForm.region && location.countyName) {
+							// 		that.submitForm.region = location.countyName
+							// 	}
+							// 	if (!that.submitForm.region && location.cityName) {
+							// 		that.submitForm.region = location.cityName
+							// 	}
+							// }
+							// success loc 
+							// {errMsg: "chooseAddress:ok", userName: "张三", nationalCode: "510000", telNumber: "020-81167888", postalCode: "510000", …}
+							// cityName: "广州市"
+							// countyName: "海珠区"
+							// detailInfo: "新港中路397号"
+							// errMsg: "chooseAddress:ok"
+							// nationalCode: "510000"
+							// postalCode: "510000"
+							// provinceName: "广东省"
+							// telNumber: "020-81167888"
+							// userName: "张三"
+						},
+						fail: e => {
+							console.log('fail loc', e)
+							// that.$message.showToast('定位失败')
+						}
+					})
+					// uni.getLocation({
+					// 	type: 'wgs84',
+					// 	success(res) {
+					// 		//得到经纬度
+					// 		console.log(res);
+					// 		const referer = '花满芫'; //调用插件的app的名称
+					// 		const location = JSON.stringify({
+					// 			latitude: res.latitude,
+					// 			longitude: res.longitude
+					// 		});
+					// 		const category = '生活服务,娱乐休闲';
+					// 		// &category=${category}
+
+					// 		wx.navigateTo({
+					// 			url: `plugin://chooseLocation/index?key=${lockey}&referer=${referer}&location=${location}`
+					// 		});
+
+					// 	}
+					// })
+				}
+			},
+			validatePhoneNumber() {
+
+				// 手机号码正则表达式,可以根据需要调整
+				const phoneRegex = /^[1][3-9][0-9]{9}$/;
+				if (!phoneRegex.test(this.submitForm['tel'])) {
+					this.$message.showToast('请填写正确手机号码')
+				}
+
+			},
+
+			addAddress() {
+				delete this.submitForm.id
+				this.submitForm['name'] = ''
+				this.submitForm['tel'] = ''
+				this.submitForm['address'] = ''
+				this.submitForm['province'] = ''
+				this.submitForm['city'] = ''
+				this.submitForm['region'] = ''
+				this.submitForm['isDefault'] = false
+				this.openAddressForm()
+			},
+			changeCurrentAddress() {
+				// if(!this.addressDesc){
+
+				// }
+				let that = this
+				this.$message.showLoading()
+				uni.getLocation({
+					type: 'gcj02',
+					geocode: true,
+					success: async (res) => {
+						//  this.location = {
+						// latitude: res.latitude,
+						// longitude: res.longitude,
+						// speed: res.speed,
+						// accuracy: res.accuracy,
+						// address: res.address,
+						//  };
+						const {
+							code,data
+						} = that.$http.request('get', '/api/pub/customer/home/address/parse', {
+							data: {},
+							params: {
+								// https://apis.map.qq.com/ws/geocoder/v1/?location=39.984154,116.307490&key=[你的key]&get_poi=1
+								location:`${res.latitude},${res.longitude}`
+							}
+						})
+						console.log('定位成功:', data);
+						if(data){
+							var address = data.address || ''
+							
+							that.$store.commit('setAddressDesc', (address || ''))
+						}
+						
+
+					},
+					fail: (err) => {
+						that.error = err;
+						console.error('定位失败:', err);
+					},
+					complete() {
+						that.$message.hideLoading()
+					}
+				});
+			},
+			PickArea(item, e) {
+				console.log('PickArea', item, e)
+				if (e.detail.value) {
+					this.submitForm.province = ''
+					this.submitForm.city = ''
+					this.submitForm.region = ''
+					if (e.detail.value.length == 2) {
+						if (!!e.detail.value[0])
+							this.submitForm.province = e.detail.value[0].value
+						if (!!e.detail.value[0])
+							this.submitForm.city = e.detail.value[0].value
+						if (!!e.detail.value[1])
+							this.submitForm.region = e.detail.value[1].value
+					} else if (e.detail.value.length <= 3) {
+						if (!!e.detail.value[0])
+							this.submitForm.province = e.detail.value[0].value
+						if (!!e.detail.value[1])
+							this.submitForm.city = e.detail.value[1].value
+						if (!!e.detail.value[2])
+							this.submitForm.region = e.detail.value[2].value
+					} else {
+						//说明有重复的
+						var plusnum = e.detail.value.length - 3
+						if (!!e.detail.value[plusnum + 0])
+							this.submitForm.province = e.detail.value[plusnum + 0].value
+						if (!!e.detail.value[plusnum + 1])
+							this.submitForm.city = e.detail.value[plusnum + 1].value
+						if (!!e.detail.value[plusnum + 2])
+							this.submitForm.region = e.detail.value[plusnum + 2].value
+					}
+
+					this.$forceUpdate()
+					console.log('submit forn', this.submitForm)
+				}
+			},
+			async init_area() {
+				if (this.regionDataPlus && this.regionDataPlus.length > 0) {
+
+				} else {
+					var a = this.$storage.getItem('cache_area')
+					if (a) {
+						this.regionDataPlus = JSON.parse(a) || []
+					} else {
+						const res = await this.$http.request('get', '/api/pub/china/area/json')
+						// console.log('area', JSON.parse(res.data))
+						this.regionDataPlus = res.data && JSON.parse(res.data.replaceAll('code', 'value').replaceAll(
+							'name',
+							'text')) || []
+						this.$storage.setItem('cache_area', JSON.stringify(this.regionDataPlus))
+					}
+				}
+
+			},
+			chooseLocation() {
+				//前往接口挑选位置
+				const qqmapsdk = new qqmapwx({
+					// 使用你在腾讯地图应用生成的key
+					key: lockey
+				});
+
+				uni.getLocation({
+					type: 'wgs84',
+					success(res) {
+						//得到经纬度
+						console.log(res);
+						qqmapsdk.reverseGeocoder({
+							location: {
+								latitude: res.latitude,
+								longitude: res.longitude
+							},
+							//成功后的回调
+							success: (r) => {
+								// console.log('地址信息', r.result.address_component);
+								// result: {location: {lat: 31.26249, lng: 120.63212}, address: "江苏省苏州市吴中区太湖东路288号",…}
+								// ad_info: {nation_code: "156", adcode: "320506", phone_area_code: "0512", city_code: "156320500",…}
+								// address: "江苏省苏州市吴中区太湖东路288号"
+								// address_component: {nation: "中国", province: "江苏省", city: "苏州市", district: "吴中区", street: "太湖东路",…}
+								// address_reference: {,…}
+								// formatted_addresses: {recommend: "长桥苏州市吴中区人民政府(太湖东路北)", rough: "长桥苏州市吴中区人民政府(太湖东路北)"}
+								// location: {lat: 31.26249, lng: 120.63212}
+								// status: 0
+								tmp.formcodes['LOC_ONE'][tmp.tmp_picker_lock_index].longitude = r
+									.result.location.lng
+								tmp.formcodes['LOC_ONE'][tmp.tmp_picker_lock_index].latitude = r
+									.result.location.lat
+								tmp.formcodes['LOC_ONE'][tmp.tmp_picker_lock_index].loc_desc = r
+									.result.address
+								tmp.formcodes['LOC_ONE'][tmp.tmp_picker_lock_index]._show = false
+								tmp.$forceUpdate()
+								tmp.tmp_picker_lock_index = -1
+
+							},
+							fail: function(res) {
+								console.log(res);
+								tmp.tmp_picker_lock_index = -1
+							},
+						});
+					}
+				});
+			},
 			async changeDefaultAddress(item, index) {
-				if (item.defaulted) {
+				if (item.isDefault) {
 					return
 				}
 				this.$nextTick(() => {
-					// item.defaulted = false
-					// this.$set(item,'defaulted',false)
+
 					this.$message.confirm('确定设置此地址为默认地址吗').then(async res => {
 						this.$message.showLoading()
 						var {
 							code
-						} = await this.$http.request('post', '/api/address/customer/default/' +
-							item.id, {})
+						} = await this.$http.request('post', '/api/address/set/default/' + item
+							.id, {
+								data: {
+									id: item.id
+								},
+								params: {
+									id: item.id
+								}
+							})
 						this.$message.hideLoading()
-						if (code == 0) {
+						if (code === 0) {
 							for (var dto of this.list) {
-								dto.defaulted = false
+								dto.isDefault = false
 							}
 							this.$message.showToast('设置成功')
-							item.defaulted = true
+							item.isDefault = true
 							this.$store.commit('setDefaultAddress', {
 								...item
 							})
@@ -176,7 +528,7 @@
 						}
 					}, err => {
 						this.$nextTick(() => {
-							this.list[index].defaulted = false
+							this.list[index].isDefault = false
 							this.$forceUpdate()
 							console.log('err', err, this.list[index])
 						})
@@ -186,7 +538,7 @@
 				})
 			},
 			async selectAddress(item) {
-				await this.$message.confirm('确定选择此地址作为收获地址吗')
+				await this.$message.confirm('确定选择此地址作为收货地址吗')
 				//携带,然后返回
 				this.$store.commit('setDefaultAddress', {
 					...item
@@ -198,10 +550,14 @@
 				this.$message.showLoading()
 				var {
 					code
-				} = await this.$http.request('get', '/api/address/customer/delete/' + item.id, {})
+				} = await this.$http.request('get', '/api/address/delete?id=' + item.id, {})
 				this.$message.hideLoading()
-				if (code == 0) {
+				if (code === 0) {
 					this.$message.showToast('删除成功')
+					if (this.address.id === item.id) {
+						//要清空地址了
+						this.$store.commit('setDefaultAddress', {})
+					}
 					setTimeout(() => {
 						this.page.current = 1
 						this.getList()
@@ -209,11 +565,12 @@
 				}
 			},
 			async openAddressForm() {
+				this.init_area()
 				if (this.submitForm.id) {
 					this.$message.showLoading()
 					var {
 						data
-					} = await this.$http.request('get', '/api/address/customer/get/' + this.submitForm.id, {})
+					} = await this.$http.request('get', '/api/address/list/detail?id=' + this.submitForm.id, {})
 
 
 					if (data) {
@@ -221,23 +578,20 @@
 							...this.submitForm,
 							...data
 						}
-						this.blockIndex = -1
-						this.schoolIndex = -1
 					}
 					this.$message.hideLoading()
 				} else {
 					this.submitForm = {
-						name: this.currentInfo && this.currentInfo.nickName || '',
-						tel: this.currentInfo && this.currentInfo.tel || '',
+						// name: this.currentInfo && this.currentInfo.nickName || '',
+						// tel: this.currentInfo && this.currentInfo.tel || '',
+						name: '',
+						tel: '',
 						address: '',
-						schoolArea: '',
-						block: '',
-						room: '',
-						defaulted: false
+						isDefault: false,
+						province: '',
+						city: '',
+						region: '',
 					}
-					this.schoolIndex = -1
-					this.blockIndex = -1
-					this.blocks = []
 				}
 
 				this.$refs.popup_form.open()
@@ -252,22 +606,43 @@
 					this.$message.showToast('字段未填写完整')
 					return
 				}
+				if (!this.submitForm['province'] || !this.submitForm['province'] || !this.submitForm['province']) {
+					this.$message.showToast('省市区未选择完整')
+					return
+				}
+
+				// 手机号码正则表达式,可以根据需要调整
+				const phoneRegex = /^[1][3-9][0-9]{9}$/;
+				if (!phoneRegex.test(this.submitForm['tel'])) {
+					this.$message.showToast('请填写正确手机号码')
+					return
+				}
+
+
+
 				this.$message.showLoading()
 				const {
 					code
-				} = await this.$http.request('post', '/api/address/customer/' + (this.submitForm.id ? 'edit' :
-					'new'), {
+				} = await this.$http.request('post', '/api/address/' + (this.submitForm.id ? 'update' :
+					'add'), {
 					data: this.submitForm
 				})
 				this.$message.hideLoading()
-				if (code == 0) {
+				if (code === 0) {
 					this.$refs.popup_form.close()
 					this.$message.showToast(this.submitForm.id ? '修改成功' : '新增成功')
 
+					let tmp = this
 					setTimeout(() => {
-						this.page.current = 1
-						this.getList()
+						tmp.page.current = 1
+						tmp.refreshList()
 					}, 300)
+					//如果当前选择的地址是这个,需要更新一下数据
+					if (this.address.id && this.submitForm.id === this.address.id) {
+						this.$store.commit('setDefaultAddress', {
+							...this.submitForm
+						})
+					}
 				}
 			},
 
@@ -277,6 +652,14 @@
 
 <style lang="scss" scoped>
 	.container-address {
+		.current-address {
+			padding: 20rpx;
+			background-color: #FFFFFF;
+			border-radius: 16rpx;
+			margin-top: 16rpx;
+			margin-bottom: 20rpx;
+		}
+
 		.popup-address-form {
 			background: #FFFFFF;
 			border-radius: 16rpx;

--
Gitblit v1.9.3