提交金刚顺丰前端H5
This commit is contained in:
parent
04f40fbf41
commit
9c1d98f36f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/unpackage
|
||||
/node_modules
|
||||
16
.hbuilderx/launch.json
Normal file
16
.hbuilderx/launch.json
Normal file
@ -0,0 +1,16 @@
|
||||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version": "0.0",
|
||||
"configurations": [{
|
||||
"default" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"mp-weixin" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
||||
40
Apis/book.js
Normal file
40
Apis/book.js
Normal file
@ -0,0 +1,40 @@
|
||||
import request from "/utils/request.js";
|
||||
export function ClientSite() {
|
||||
return {
|
||||
// 查看所有门店
|
||||
getSiteDetailsAll: (data) => {
|
||||
return request.request({
|
||||
url: `/ClientSite/GetSiteDetailsAll`,
|
||||
data,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 根据门店ID查路线图
|
||||
GetSiteGuideById: (id) => {
|
||||
return request.request({
|
||||
url:`/ClientSite/GetSiteGuideById?siteId=${id}`,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 获取门店所在的所有城市
|
||||
GetCityAll: () => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetCityAll',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 根据门店所在的城市获取对应的区域
|
||||
GetDistrictByCity: (city) => {
|
||||
return request.request({
|
||||
url: `/ClientSite/GetDistrictByCity?city=${city}`,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
GetSiteStopCarGuideById: (id) => {
|
||||
return request.request({
|
||||
url:`/ClientSite/GetSiteStopCarGuideById?siteId=${id}`,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
55
Apis/clientCustomer.js
Normal file
55
Apis/clientCustomer.js
Normal file
@ -0,0 +1,55 @@
|
||||
import request from '/utils/request.js';
|
||||
|
||||
export function getClientCustomerApi() {
|
||||
return {
|
||||
// 获取中介二维码
|
||||
GetMediatorQrCode: () => {
|
||||
return request.request({
|
||||
url: '/ClientMediator/GetMediatorQrCode',
|
||||
method: 'get'
|
||||
});
|
||||
},
|
||||
// 获取中介信息
|
||||
GetMediatorInfoById: () => {
|
||||
return request.request({
|
||||
url: '/ClientMediator/GetMediatorInfoById',
|
||||
method: 'get'
|
||||
});
|
||||
},
|
||||
// 修改中介信息
|
||||
UpdateMediatorInfo: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientMediator/UpdateMediatorInfo',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
},
|
||||
// 获取中介的邀请记录
|
||||
GetMediatorUpUserList: () => {
|
||||
return request.request({
|
||||
url: '/ClientMediator/GetMediatorUpUserList',
|
||||
method: 'post'
|
||||
});
|
||||
},
|
||||
// 获取当前用户绑定的中介信息
|
||||
GetMediatorByUser: () => {
|
||||
return request.request({
|
||||
url: '/ClientMediator/GetMediatorByUser',
|
||||
method: 'get'
|
||||
});
|
||||
},
|
||||
GetMediatorCompanyAllList: () => {
|
||||
return request.request({
|
||||
url: '/ClientMediator/GetMediatorCompanyAllList',
|
||||
method: 'get'
|
||||
});
|
||||
},
|
||||
ApplyMediator: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientMediator/ApplyMediator',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
37
Apis/coupon.js
Normal file
37
Apis/coupon.js
Normal file
@ -0,0 +1,37 @@
|
||||
import request from "/utils/request.js";
|
||||
export function couponApi() {
|
||||
return {
|
||||
// 获取优惠卷列表
|
||||
GetCouponList: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCoupon/GetCouponList',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 领取优惠卷
|
||||
DrawDownCoupon: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCoupon/DrawDownCoupon?couponCode='+data.couponCode,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 优惠卷弹窗
|
||||
GetNewUserCouponCode: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCoupon/GetNewUserCouponCode',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 美团优惠卷
|
||||
GetMeiTuanCodeByPhone: (data) => {
|
||||
return request.request({
|
||||
url: '/MeiTuan/GetMeiTuanCodeByPhone',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
28
Apis/goodsList.js
Normal file
28
Apis/goodsList.js
Normal file
@ -0,0 +1,28 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useGoodsApi() {
|
||||
return {
|
||||
// 物品清单
|
||||
GetGoodsList: (data) => {
|
||||
return request.request({
|
||||
url: `/ClientOrder/GetGoodsList`,
|
||||
data,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 获取设置过的物品清单
|
||||
GetSubmitGoodsList: (id) => {
|
||||
return request.request({
|
||||
url:`/ClientOrder/GetSubmitGoodsList?orderId=${id}`,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 提交物品清单
|
||||
SubmitGoodsList: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/SubmitGoodsList',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
63
Apis/home.js
Normal file
63
Apis/home.js
Normal file
@ -0,0 +1,63 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useLoginApi() {
|
||||
return {
|
||||
//获取code img
|
||||
getCode: () => {
|
||||
return request.request({
|
||||
url: '/Login/Captcha',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
//登录
|
||||
signIn: (data) => {
|
||||
return request.request({
|
||||
url: '/Login/Login',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
signOut: (data) => {
|
||||
return request.request({
|
||||
url: '/user/signOut',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetUnitTypeAll: () => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetUnitTypeAll',
|
||||
method: 'get',
|
||||
})
|
||||
},
|
||||
GetHeatSites: () => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetHeatSites',
|
||||
method: 'get',
|
||||
})
|
||||
},
|
||||
// 理解預約
|
||||
CreateReservation:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientReservation/CreateReservation',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
},
|
||||
// 获取小程序内容
|
||||
GetPageContent:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientPageContent/GetPageContent',
|
||||
method: 'get',
|
||||
data
|
||||
})
|
||||
},
|
||||
// 获取限时抢购入口
|
||||
GetFlashSaleEntrance:(data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetFlashSaleEntrance',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
55
Apis/invoice.js
Normal file
55
Apis/invoice.js
Normal file
@ -0,0 +1,55 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useInvoiceApi() {
|
||||
return {
|
||||
// 发票申请
|
||||
InvoiceApplyFor: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInvoice/InvoiceApplyFor',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取可开票订单列表
|
||||
GetCanInvoiceList: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/GetCanInvoiceList',
|
||||
method: 'get',
|
||||
data,
|
||||
}
|
||||
)
|
||||
},
|
||||
// 获取申请开票列表
|
||||
GetInvoiceApplyFor: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInvoice/GetInvoiceApplyFor',
|
||||
method: 'get',
|
||||
data,
|
||||
}
|
||||
)
|
||||
},
|
||||
// 取消开票
|
||||
CancelInvoiceApplyFor: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInvoice/CancelInvoiceApplyFor',
|
||||
method: 'get',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 获取申请开票详情
|
||||
GetInvoiceApplyForById: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInvoice/GetInvoiceApplyForById',
|
||||
method: 'get',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 修改发票
|
||||
UpdateInvoiceApplyFor: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInvoice/UpdateInvoiceApplyFor',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
139
Apis/lock.js
Normal file
139
Apis/lock.js
Normal file
@ -0,0 +1,139 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useLockApi() {
|
||||
return {
|
||||
GetDyncPwdByMac: (data) => {
|
||||
return request.request({
|
||||
url: '/LockOperation/GetDyncPwd',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetAccesscontrolQRCodeBySite: (data) => {
|
||||
return request.request({
|
||||
url: '/Accesscontrol/GetAccesscontrolQRCode',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 门禁远程开门
|
||||
RemoteOpenDoor: (data) => {
|
||||
return request.request({
|
||||
url: '/Accesscontrol/RemoteOpenDoor',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
//通通锁远程开锁
|
||||
RemoteOpen: (data) => {
|
||||
return request.request({
|
||||
url: '/LockOperation/RemoteOpen',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取初始化的通通锁列表
|
||||
GetInitLockList: (data) => {
|
||||
return request.request({
|
||||
url: '/Lock/GetLockInfoByOpenId',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取初始化的通通锁信息
|
||||
SaveInitLock: (data) => {
|
||||
return request.request({
|
||||
url: '/Lock/PushLockInfo',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
//授权订单
|
||||
OrderAuthorizeCustomer: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/OrderAuthorizeCustomer',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 更新授权订单
|
||||
UpdateOrderAuthorizeCustomer: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/UpdateOrderAuthorizeCustomer',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 删除授权订单
|
||||
DeleteOrderAuthorize: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/DeleteOrderAuthorize?authorizeId=' + data.authorizeId,
|
||||
method: 'delete',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取授权列表
|
||||
GetOrderAuthorizeList: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/GetOrderAuthorizeList',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取授权的订单
|
||||
GetAuthorizeOrderList: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/GetAuthorizeOrderList',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 设置用户固定密码
|
||||
SetUserFixedPassword: (data) => {
|
||||
return request.request({
|
||||
url: '/LockOperation/SetUserFixedPassword',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 门禁绑卡
|
||||
AddCardNumber: (data) => {
|
||||
return request.request({
|
||||
url: '/Site/AddCardNumber',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
//通通锁绑卡
|
||||
BindCardByWifiSmartLock: (data) => {
|
||||
return request.request({
|
||||
url: '/LockOperation/BindCardByWifiSmartLock',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// zoned 锁绑卡
|
||||
SetRFIDCard: (data) => {
|
||||
return request.request({
|
||||
url: '/LockOperation/SetRFIDCard',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// zoned 绑卡读取卡结果
|
||||
GetBindingCardResult: (data) => {
|
||||
return request.request({
|
||||
url: '/LockOperation/GetBindingCardResult',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 就门禁id 获取门禁信息
|
||||
GetNewLockerId: (data) => {
|
||||
return request.request({
|
||||
url: '/Locker/GetNewLockerId',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
159
Apis/login.js
Normal file
159
Apis/login.js
Normal file
@ -0,0 +1,159 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useLoginApi() {
|
||||
return {
|
||||
//获取code img
|
||||
getCode: () => {
|
||||
return request.request({
|
||||
url: '/Login/Captcha',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
//登录
|
||||
Login: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/Login',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
Register: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/Register',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
EmailVerify: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/EmailVerify',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
ForgotPassword: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/ForgotPassword',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
}
|
||||
});
|
||||
},
|
||||
UpdateUserInfo: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/UpdateUserInfo',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 微信登录
|
||||
AuthorizedLogin: (data) => {
|
||||
return request.request({
|
||||
url:'/ClientCustomer/AuthorizedLogin',
|
||||
method:'post',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 通过用户授权的code去获取手机号码
|
||||
GetPhoneNumber: (data) => {
|
||||
return request.request({
|
||||
url:`/ClientCustomer/GetPhoneNumber`,
|
||||
method:'get',
|
||||
params:{
|
||||
...data,
|
||||
isUpdate:true
|
||||
},
|
||||
})
|
||||
},
|
||||
// 通过用户授权的code去获取手机号码
|
||||
GetPhoneNumberNoUpdate: (data) => {
|
||||
return request.request({
|
||||
url:`/ClientCustomer/GetPhoneNumber?code=${data}`,
|
||||
method:'get',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 获取用户信息
|
||||
GetUserInfo:() =>{
|
||||
return request.request({
|
||||
url:`/ClientCustomer/GetUserInfo`,
|
||||
method:'get',
|
||||
})
|
||||
},
|
||||
// 更新用户信息
|
||||
EditUserInfo: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/EditUserInfo',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 从主题二维码来的
|
||||
GetActivitiesCode: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/GetActivitiesCode',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取openId
|
||||
GetOpenId: (data) => {
|
||||
return request.request({
|
||||
url:'/ClientCustomer/GetOpenId',
|
||||
method:'post',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 获取积分豆
|
||||
GetCustomerPoint: () => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/GetCustomerPoint',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 兑换奖品
|
||||
CustomerExchangeGift: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientPoint/CustomerExchangeGift',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetGiftInfo: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientPoint/GetGiftInfo',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetGiftList: () => {
|
||||
return request.request({
|
||||
url: '/ClientPoint/GetGiftList',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
GetCustomerExchangeGift: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientPoint/GetCustomerExchangeGift',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetCustomerList: (data) => {
|
||||
return request.request({
|
||||
url: '/Customer/GetCustomerList',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
ShunFengLogin: (data) => {
|
||||
return request.request({
|
||||
url: '/ShunFeng/ShunFengLogin',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
172
Apis/order.js
Normal file
172
Apis/order.js
Normal file
@ -0,0 +1,172 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useOrderApi() {
|
||||
return {
|
||||
GetOrderById: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/GetOrderById',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetOrderList: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/GetOrderList',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
AddOrder: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/AddOrder',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
AddOrder2: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/AddOrder2',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
UploaderImage: (data) => {
|
||||
return request.uploadFile({
|
||||
url: '/ClientImages/UploadFileByALiYun',
|
||||
method: 'post',
|
||||
data,
|
||||
headers:{'Content-Type':'multipart/form-data'}
|
||||
});
|
||||
},
|
||||
ApplyForRefundLocker: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/ApplyForRefundLocker',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
CancelApplyForRefundLocker: (data) => {
|
||||
return request.request({
|
||||
url: `/ClientOrder/CancelApplyForRefundLocker?orderId=${data}`,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
SubmitOrderEvaluate: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/SubmitOrderEvaluate',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
//续租订单价格
|
||||
ContinuationOrderPrice:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientOrder/ContinuationOrderPrice',
|
||||
method: 'get',
|
||||
data,
|
||||
})
|
||||
},
|
||||
//续租订单价格
|
||||
ContinuationOrderPricePost:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientOrder/ContinuationOrderPrice',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
},
|
||||
//续租订单
|
||||
ContinuationOrder:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientOrder/ContinuationOrder',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
},
|
||||
//续租订单
|
||||
ContinuationOrderH5:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientOrder/ContinuationOrderH5',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 关闭支付
|
||||
CloseWeChatPayment:(data)=>{
|
||||
return request.request({
|
||||
url: `/ClientOrder/CloseWeChatPayment?out_trade_no=${data.out_trade_no}`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
},
|
||||
//授权
|
||||
OrderAuthorization:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientOrder/OrderAuthorization',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 获取信息
|
||||
GetOrderAuthorizationFace:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientOrder/GetOrderAuthorizationFace',
|
||||
method: 'get',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 申请退押金
|
||||
WeChatMerchantRefund:(data)=>{
|
||||
return request.request({
|
||||
url: '/ClientOrder/WeChatMerchantRefund',
|
||||
method: 'get',
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 获取起租天数
|
||||
GetStartDateRntalByKey: () => {
|
||||
return request.request({
|
||||
url: '/sysconfig/GetStartDateRntalByKey',
|
||||
method: 'get'
|
||||
});
|
||||
},
|
||||
GenerateQuotation : (data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GenerateQuotation',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
},
|
||||
// 获取锁订单时间
|
||||
GetLockOrderTime: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/GetLockOrderTime',
|
||||
method: 'post',
|
||||
params: data,
|
||||
});
|
||||
},
|
||||
// 取消支付
|
||||
OrderCountdownTime: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/OrderCountdownTime',
|
||||
method: 'post',
|
||||
params: data,
|
||||
});
|
||||
},
|
||||
// 继续支付
|
||||
ContinueOrderPay: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientOrder/ContinueOrderPay',
|
||||
method: 'post',
|
||||
params: data,
|
||||
});
|
||||
},
|
||||
GetAppText: (data) => {
|
||||
return request.request({
|
||||
url: '/APP/GetAppText',
|
||||
method: 'get',
|
||||
data
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Apis/recommend.js
Normal file
21
Apis/recommend.js
Normal file
@ -0,0 +1,21 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useRecommend() {
|
||||
return {
|
||||
// 获取推荐列表
|
||||
GetRecommend: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/GetRecommend',
|
||||
data,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 获取推荐人数
|
||||
GetRecommendCount: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCustomer/GetRecommendCount',
|
||||
data,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Apis/site.js
Normal file
70
Apis/site.js
Normal file
@ -0,0 +1,70 @@
|
||||
import request from "/utils/request.js";
|
||||
export function useSiteApi() {
|
||||
return {
|
||||
// 根據門店id 獲取門店
|
||||
GetUnitTypeBySiteId: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetUnitTypeBySiteId',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetLockerBySiteId: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetLockerBySiteIdList',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetLockerById: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetLockerById',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetLockerExpense: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetLockerExpense',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
AlternateReservation: (data) => {
|
||||
return request.request({
|
||||
url:'/ClientUnitType/AlternateReservation',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetReserveIsEnable: (data) => {
|
||||
return request.request({
|
||||
url:'/ClientUnitType/GetReserveIsEnable',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 取消預約
|
||||
CancelReservation: (data) => {
|
||||
return request.request({
|
||||
url:'/ClientUnitType/CancelReservation',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取五羊门店
|
||||
GetMultipleStoreInfo: () => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetMultipleStoreInfo',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
GetLockerAgreementHTMLById: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetLockerAgreementHTMLById',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
67
Apis/validInfo.js
Normal file
67
Apis/validInfo.js
Normal file
@ -0,0 +1,67 @@
|
||||
import request from "/utils/request.js";
|
||||
export function authInfoApi() {
|
||||
return {
|
||||
// 获取认证列表
|
||||
GetCertificateList: (data) => {
|
||||
return request.request({
|
||||
url: '/InfoCertification/GetCertificateList',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 获取是否认证过
|
||||
GetIsCertification: () => {
|
||||
return request.request({
|
||||
url: '/ClientInfoCertification/GetIsCertification',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 获取认证详情
|
||||
GetCertificationInfo: () => {
|
||||
return request.request({
|
||||
url: '/ClientInfoCertification/GetCertificateByUserId',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
// 提交企业认证
|
||||
SubmitEnterpriseCertification: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInfoCertification/SubmitEnterpriseCertification',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 提交个人认证
|
||||
SubmitPersonCertification: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInfoCertification/SubmitIndividualCertification',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 修改验证信息
|
||||
UpdateCertification: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientInfoCertification/UpdateCertification',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 领取优惠卷
|
||||
DrawDownCoupon: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCoupon/DrawDownCoupon?couponCode='+data.couponCode,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
// 优惠卷弹窗
|
||||
GetNewUserCouponCode: (data) => {
|
||||
return request.request({
|
||||
url: '/ClientCoupon/GetNewUserCouponCode',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
241
App.vue
Normal file
241
App.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<script>
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { shunfenLogin,getQueryParam } from "@/utils/common";
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
const getApi = useLoginApi();
|
||||
|
||||
const updateManagerFn = () => {
|
||||
const updateManager = uni.getUpdateManager();
|
||||
|
||||
updateManager.onCheckForUpdate(function (res) {
|
||||
// 请求完新版本信息的回调
|
||||
console.log(res.hasUpdate, "请求完新版本信息的回调");
|
||||
});
|
||||
|
||||
updateManager.onUpdateReady(function (res) {
|
||||
uni.showModal({
|
||||
title: "更新提示",
|
||||
content: "新版本已经准备好,是否重启应用?",
|
||||
showCancel: false,
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
||||
updateManager.applyUpdate();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
updateManager.onUpdateFailed(function (res) {
|
||||
// 新的版本下载失败
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "新版小程序下载失败\n请自行退出程序,手动卸载本程序,再运行",
|
||||
confirmText: "知道了",
|
||||
});
|
||||
});
|
||||
};
|
||||
const listenNetworkChange = () => {
|
||||
uni.onNetworkStatusChange((res) => {
|
||||
if (res.isConnected) {
|
||||
uni.showToast({
|
||||
title: "网络已连接",
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "当前无网络连接",
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
export default {
|
||||
globalData: {
|
||||
statusBarHeight: 0,
|
||||
navbarHeight: 0,
|
||||
},
|
||||
onLaunch: async function () {
|
||||
uni.getNetworkType({
|
||||
success: (res) => {
|
||||
if (res.networkType === 'none') {
|
||||
uni.showToast({
|
||||
title: '当前无网络连接',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 开始监听网络状态变化
|
||||
listenNetworkChange();
|
||||
uni.hideTabBar()
|
||||
const { setTheme, getUserInfo, storeState,logOut } = useMainStore();
|
||||
setTheme();
|
||||
// #ifdef MP-WEIXIN || MP-XHS
|
||||
// 状态栏高度
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
|
||||
const wxMenuBtn = uni.getMenuButtonBoundingClientRect();
|
||||
// 导航栏高度(标题栏高度) = 胶囊高度 + (顶部距离 - 状态栏高度) * 2
|
||||
const barHeight = wxMenuBtn.height + (wxMenuBtn.top - statusBarHeight) * 2;
|
||||
// 总体高度 = 状态栏高度 + 导航栏高度
|
||||
this.globalData.navbarHeight = (barHeight || 40) + statusBarHeight;
|
||||
this.globalData.statusBarHeight = statusBarHeight || 20;
|
||||
// #endif
|
||||
// authCode 存在 就直接登陆
|
||||
const authCode = getQueryParam('authCode');
|
||||
if(authCode){
|
||||
let source = window.sf.isSfApp() ? 2 : 1;
|
||||
getApi.ShunFengLogin({
|
||||
code:authCode,
|
||||
source,
|
||||
})
|
||||
.then(async(res) => {
|
||||
uni.hideLoading();
|
||||
storeState.hasTrytoLogin = true;
|
||||
if (res.code == 200) {
|
||||
uni.setStorageSync("token", res.data.token);
|
||||
// 如果用户没有授权手机号 不登陆
|
||||
await getUserInfo();
|
||||
console.log('用户信息',storeState.userInfo)
|
||||
if(storeState.userInfo.phone){
|
||||
uni.$emit('loginSuccess',{msg:'页面更新'})
|
||||
storeState.token = res.data.token;
|
||||
uni.setStorageSync("token", res.data.token);
|
||||
uni.setStorageSync("openId", res.data.openId);
|
||||
}else{
|
||||
uni.removeStorageSync("token");
|
||||
console.log('用户未授权手机号,启动shunfenLogin')
|
||||
shunfenLogin();
|
||||
}
|
||||
// 更新用户信息
|
||||
|
||||
}else{
|
||||
logOut();
|
||||
}
|
||||
}).catch((err) => {
|
||||
logOut();
|
||||
})
|
||||
}
|
||||
|
||||
// 清掉请求有优惠价的时间记录
|
||||
uni.removeStorageSync('getCouponCodeTime');
|
||||
},
|
||||
onShow: function () {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 检查更新
|
||||
updateManagerFn();
|
||||
// #endif
|
||||
},
|
||||
onHide: function () { },
|
||||
onPageNotFound() {
|
||||
uni.switchTab({
|
||||
url: "/pages/index/index",
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/*每个页面公共css */
|
||||
@import "@/uni_modules/uni-scss/index.scss";
|
||||
@import "@/static/iconfont/iconfont.css";
|
||||
// @import "@/static/style/theme.scss";
|
||||
/* #ifndef APP-NVUE */
|
||||
@import "@/static/customicons.css";
|
||||
|
||||
// 设置整个项目的背景色
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
html{
|
||||
height: unset;
|
||||
min-height: 100vh;
|
||||
}
|
||||
body {
|
||||
background:$backgroundColor;
|
||||
min-height: 100vh;
|
||||
}
|
||||
page {
|
||||
// uni-input,
|
||||
// uni-button,
|
||||
// uni-textarea {
|
||||
// line-height: 2.55555;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
view,
|
||||
text {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.myCustomTabbar {
|
||||
position: fixed;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
padding:20rpx 0;
|
||||
padding-bottom: 40rpx;
|
||||
background-color: #fff;
|
||||
height: 160upx;
|
||||
.uni-tabbar__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.uni-tabbar {
|
||||
height: 200upx;
|
||||
}
|
||||
|
||||
.uni-tabbar__label {
|
||||
color: #000;
|
||||
font-size: 24rpx !important;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.uni-tabbar__item.call-phone-item {
|
||||
position: relative;
|
||||
|
||||
.uni-tabbar__bd {
|
||||
top: -80rpx;
|
||||
width: 160rpx;
|
||||
position: absolute;
|
||||
height: 160rpx;
|
||||
|
||||
.uni-tabbar__icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
|
||||
svg {
|
||||
max-width: 100%;
|
||||
height: 40rpx;
|
||||
width: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
border-radius: 1000rpx;
|
||||
background: linear-gradient(180deg,
|
||||
var(--left-linear),
|
||||
var(--right-linear) 100%);
|
||||
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uni-app--showleftwindow+.uni-tabbar-bottom {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.example-info {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
21
README.md
21
README.md
@ -1,3 +1,20 @@
|
||||
# SFH5
|
||||
# Introduction
|
||||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
|
||||
|
||||
金刚顺丰H5
|
||||
# Getting Started
|
||||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
|
||||
1. Installation process
|
||||
2. Software dependencies
|
||||
3. Latest releases
|
||||
4. API references
|
||||
|
||||
# Build and Test
|
||||
TODO: Describe and show how to build your code and run the tests.
|
||||
|
||||
# Contribute
|
||||
TODO: Explain how other users and developers can contribute to make your code better.
|
||||
|
||||
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
|
||||
- [ASP.NET Core](https://github.com/aspnet/Home)
|
||||
- [Visual Studio Code](https://github.com/Microsoft/vscode)
|
||||
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
|
||||
246
components/AgreementCheck.vue
Normal file
246
components/AgreementCheck.vue
Normal file
@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<view class="agreement-wrapper">
|
||||
<checkbox-group @change="onChange">
|
||||
<label class="agreement-label">
|
||||
<checkbox :checked="checked" style="transform:scale(0.8)" />
|
||||
<text class="agreement-text">
|
||||
{{ $t('agreement.readAndAgree') }}
|
||||
<text class="link" @click.stop="open('service')">
|
||||
{{ $t('agreement.service') }}
|
||||
</text>
|
||||
{{ $t('agreement.and') }}
|
||||
<text class="link" @click.stop="open('privacy')">
|
||||
{{ $t('agreement.privacy') }}
|
||||
</text>
|
||||
</text>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
|
||||
<!-- 协议弹窗 -->
|
||||
<uni-popup ref="popup" type="center">
|
||||
<view class="popup-box">
|
||||
<scroll-view scroll-y class="popup-content">
|
||||
<text class="popup-title">{{ popupTitle }}</text>
|
||||
<text class="popup-text">{{ popupContent }}</text>
|
||||
</scroll-view>
|
||||
<button class="popup-btn" @click="close">
|
||||
我已知晓 / Acknowledge
|
||||
</button>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { projectInfo } from '@/config';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const checked = ref(props.modelValue)
|
||||
const popup = ref(null)
|
||||
const popupType = ref('')
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
val => (checked.value = val)
|
||||
)
|
||||
|
||||
const onChange = e => {
|
||||
checked.value = e.detail.value.length > 0
|
||||
emit('update:modelValue', checked.value)
|
||||
}
|
||||
|
||||
const open = type => {
|
||||
popupType.value = type
|
||||
popup.value.open()
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
popup.value.close()
|
||||
}
|
||||
|
||||
const popupTitle = computed(() =>
|
||||
popupType.value === 'service'
|
||||
? '用户服务协议 / User Service Agreement'
|
||||
: '隐私政策 / Privacy Policy'
|
||||
)
|
||||
|
||||
const popupContent = computed(() => {
|
||||
if (popupType.value === 'service') {
|
||||
return `
|
||||
《用户服务协议》
|
||||
User Service Agreement
|
||||
|
||||
一、协议的确认与接受
|
||||
欢迎您使用由【${projectInfo.name}】运营的仓库租赁小程序。
|
||||
您在注册、登录或使用本小程序服务前,应仔细阅读并充分理解本协议。
|
||||
您勾选同意或实际使用服务的行为,即视为您已阅读、理解并同意本协议全部内容。
|
||||
|
||||
Welcome to the warehouse rental mini program operated by 【Company Name】.
|
||||
By registering, logging in, or using the service, you acknowledge that you have read,
|
||||
understood, and agreed to this Agreement.
|
||||
|
||||
二、服务内容
|
||||
本小程序向用户提供仓库租赁及相关服务,包括但不限于:
|
||||
1. 仓库信息展示与查询
|
||||
2. 仓库租赁订单的创建、管理与履行
|
||||
3. 用户身份认证(个人/企业)
|
||||
4. 合同签署、账单管理及费用结算
|
||||
|
||||
This mini program provides warehouse rental services, including but not limited to
|
||||
warehouse information display, order management, identity verification, and contract execution.
|
||||
|
||||
三、用户身份/企业认证
|
||||
为保障交易安全及符合法律法规要求,用户需根据页面提示提供真实、准确、完整的信息。
|
||||
如信息不真实或不完整,我们有权拒绝或终止相关服务。
|
||||
|
||||
Users are required to provide true, accurate, and complete information for identity verification.
|
||||
We reserve the right to refuse or terminate services if false information is provided.
|
||||
|
||||
四、用户权利与义务
|
||||
用户应妥善保管账户信息,不得转让、出租或出借账户。
|
||||
用户不得利用本服务从事违法或侵害他人合法权益的行为。
|
||||
|
||||
Users shall properly safeguard their account information and shall not engage in illegal activities.
|
||||
|
||||
五、协议变更与终止
|
||||
我们有权依法对本协议进行修订,并在小程序内进行公示。
|
||||
用户继续使用服务即视为接受修订后的协议。
|
||||
|
||||
We reserve the right to amend this Agreement with notice provided within the mini program.
|
||||
|
||||
六、法律适用与争议解决
|
||||
本协议适用中华人民共和国法律。
|
||||
因本协议产生的争议,应提交本公司所在地有管辖权的人民法院解决。
|
||||
|
||||
This Agreement shall be governed by the laws of the People’s Republic of China.
|
||||
`
|
||||
}
|
||||
|
||||
return `
|
||||
《隐私政策》
|
||||
Privacy Policy
|
||||
|
||||
一、个人信息的收集
|
||||
在您使用本小程序过程中,我们可能收集以下信息:
|
||||
- 姓名
|
||||
- 证件类型及证件号码
|
||||
- 证件照片(仅用于实名认证核验)
|
||||
- 手机号码
|
||||
- 紧急联系人信息(如您主动填写)
|
||||
- 企业名称
|
||||
- 营业执照照片(仅用于企业认证核验)
|
||||
- 营业执照号码(仅用于企业认证核验)
|
||||
We may collect your name, ID type and number, ID photos, phone number,
|
||||
emergency contact information (if provided), and order details.
|
||||
|
||||
二、信息的使用目的
|
||||
上述信息仅用于:
|
||||
1. 用户身份认证
|
||||
2. 仓库租赁服务的提供与履行
|
||||
3. 合同签署与合规审查
|
||||
4. 客户服务与风险控制
|
||||
|
||||
The collected information is used solely for identity verification,
|
||||
service fulfillment, contract execution, and customer support.
|
||||
|
||||
三、信息的存储与保护
|
||||
我们将采取加密、权限控制等安全措施保护您的个人信息,
|
||||
防止未经授权的访问、披露或滥用。
|
||||
|
||||
We adopt industry-standard security measures to protect your personal information.
|
||||
|
||||
四、信息的保存期限
|
||||
您的个人信息仅在实现服务目的所必需的期限内保存,
|
||||
超过期限后将依法删除或匿名化处理。
|
||||
|
||||
Personal information is retained only as long as necessary and will be deleted or anonymized thereafter.
|
||||
|
||||
五、信息的共享与披露
|
||||
未经您的明确同意,我们不会向任何第三方共享或披露您的个人信息,
|
||||
法律法规另有规定的除外。
|
||||
|
||||
We do not share or disclose your personal information without your consent,
|
||||
except as required by law.
|
||||
|
||||
六、用户权利
|
||||
您有权依法查询、更正或删除您的个人信息。
|
||||
如需行使相关权利,请通过【客服】与我们联系。
|
||||
|
||||
You have the right to access, correct, or delete your personal information.
|
||||
Please contact us via 【Customer Support Contact】.
|
||||
|
||||
七、未成年人保护
|
||||
本服务主要面向具备完全民事行为能力的用户。
|
||||
|
||||
This service is intended for users with full legal capacity.
|
||||
`
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.agreement-wrapper {
|
||||
margin-top: 24rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.agreement-label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
margin-left: 12rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #f7b500;
|
||||
}
|
||||
|
||||
.en-tip {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.popup-box {
|
||||
width: 680rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
max-height: 720rpx;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.popup-text {
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.popup-btn {
|
||||
margin-top: 20rpx;
|
||||
background-color: #f7b500;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
158
components/MediatorinviteDetail.vue
Normal file
158
components/MediatorinviteDetail.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<view class="invite-wrap">
|
||||
<myPopup v-model="modelValue" mode="bottom" bgColor="none"
|
||||
customStyle="max-height: 70vh; border-radius: 15px 15px 0 0;" :closeOnClickOverlay="true">
|
||||
<view class="inner-wrap">
|
||||
<view class="title">{{ $t("referrerInfo.inviteRecord") }}</view>
|
||||
<view class="close-icon" @click="closeShow">
|
||||
<uv-icon name="close" size="10" :color="themeInfo.activeColor"></uv-icon>
|
||||
</view>
|
||||
<view class="top-nav">
|
||||
<view class="label">{{ $t("referrerInfo.inviteUserName") }}</view>
|
||||
<view class="label">{{ $t("referrerInfo.invitePhone") }}</view>
|
||||
<view class="label">{{ $t("referrerInfo.registrationTime") }}</view>
|
||||
</view>
|
||||
<view class="content-wrap">
|
||||
<view class="list-wrap" v-if="state.list.length">
|
||||
<view class="item-wrap" v-for="(item, index) in state.list" :key="index">
|
||||
<view class="label">{{ item.customerName }}</view>
|
||||
<view class="label">{{ item.phone }}</view>
|
||||
<view class="label">{{ item.createTime }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-wrap" v-else>
|
||||
<view>{{ $t("referrerInfo.inviteEmpty") }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</myPopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import myPopup from './myPopup.vue';
|
||||
import { reactive, watch, computed } from 'vue';
|
||||
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
|
||||
import { getClientCustomerApi } from "@/Apis/clientCustomer.js";
|
||||
const clientCustomerApi = getClientCustomerApi();
|
||||
|
||||
// 接收 v-model
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const state = reactive({
|
||||
list: []
|
||||
});
|
||||
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value);
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => modelValue.value, (value) => {
|
||||
value && getRecommend()
|
||||
});
|
||||
|
||||
const closeShow = () => {
|
||||
modelValue.value = false;
|
||||
}
|
||||
|
||||
const getRecommend = () => {
|
||||
clientCustomerApi.GetMediatorUpUserList().then((res) => {
|
||||
if (res.code === 200) {
|
||||
state.list = res.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invite-wrap {
|
||||
.inner-wrap {
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
padding: 40rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
background: var(--main-color);
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 30rpx;
|
||||
padding: 6rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba($color: #FFFFFF, $alpha: 0.6);
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
display: flex;
|
||||
padding: 16rpx 0;
|
||||
background: var(--right-linear);
|
||||
|
||||
.label {
|
||||
width: 33%;
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrap {
|
||||
padding: 20rpx 0 40rpx;
|
||||
background: #F5F5EF;
|
||||
min-height: 400rpx;
|
||||
max-height: 600rpx;
|
||||
overflow: auto;
|
||||
|
||||
.list-wrap {
|
||||
.item-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
|
||||
.label {
|
||||
width: 33%;
|
||||
font-weight: bold;
|
||||
font-size: 30rpx;
|
||||
text-align: center;
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
&:nth-child(odd) {
|
||||
background: rgba(216, 216, 216, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-wrap {
|
||||
padding: 30rpx 0;
|
||||
margin: 20rpx 30rpx 0;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 16rpx;
|
||||
color: var(--text-color);
|
||||
background: var(--main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
820
components/coupon.vue
Normal file
820
components/coupon.vue
Normal file
@ -0,0 +1,820 @@
|
||||
<template>
|
||||
<view class="coupon" :class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]">
|
||||
<myPopup v-model="modelValue" mode='bottom' bgColor='none' customStyle='height:70vh;border-radius: 18px 18px 0 0;' :closeOnClickOverlay='true'>
|
||||
<view class="couponContent">
|
||||
<view class="header">
|
||||
<view class="line"></view>
|
||||
<view class="title">
|
||||
{{ $t('coupon.coupon') }} {{dataList.length + unusableDataList.length}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="ortherCoupon" v-if="isKingKong && props.siteData?.siteId">
|
||||
<view class="" v-if="storeState.userInfo?.phone">
|
||||
<uv-divider :text="$t('coupon.meituanOrdazhongdianpingCoupon')"></uv-divider>
|
||||
</view>
|
||||
<view v-else>
|
||||
<button @click="bindPhonePopup.open()"> {{ $t('coupon.queryMeituanDazhongdianpingCoupon') }} </button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="couponBox" v-if="isKingKong">
|
||||
<view class="couponUl couponUl2">
|
||||
<view v-if="state.ortherCouponLoading" style="display: flex;justify-content: center;">
|
||||
<uv-loading-icon></uv-loading-icon>
|
||||
</view>
|
||||
<uv-checkbox-group style="width: 100%;" v-model="state.checkboxOrtherCouponValue" @change="ortherCouponChange">
|
||||
<template v-for="(item,index) in state.ortherCoupon" :key="index">
|
||||
<view class="couponLi">
|
||||
<view class="left">
|
||||
<view class="name">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.validityPeriod') }}:{{ item.endTime.substr(0,10) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view style="display: flex;justify-content: center;margin-top: 4px;">
|
||||
<uv-checkbox
|
||||
:key="index"
|
||||
:name="item.number"
|
||||
@click.stop=""
|
||||
></uv-checkbox>
|
||||
<!-- <button class="btn" 0">
|
||||
{{ $t('coupon.apply') }}
|
||||
</button> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</uv-checkbox-group>
|
||||
</view>
|
||||
<uv-divider></uv-divider>
|
||||
</view>
|
||||
<view class="couponBox">
|
||||
<view class="couponUl">
|
||||
<view class="couponLi">
|
||||
<view class="left">
|
||||
<view class="name">
|
||||
{{ $t('coupon.coupon') }}
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.useTips') }}
|
||||
</view>
|
||||
<view class="input">
|
||||
<input v-model="couponCode" :placeholder="$t('coupon.enterCode')"></input>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="desc">
|
||||
{{ $t('coupon.storewide') }}
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.limitedtimeoffer') }}
|
||||
</view>
|
||||
<view>
|
||||
<button class="btn" @click="getCouponCode">
|
||||
{{ $t("coupon.redeemNow") }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="couponUl couponUl2">
|
||||
<uv-checkbox-group style="width: 100%;" v-model="state.checkboxCouponValue" @change="couponChange">
|
||||
<view class="couponLi" v-for="(item,index) in dataList" :key="index" @click="chooseCoupon(item)" :style="{opacity:disabledFunc(item)?0.5:1}">
|
||||
<view class="left">
|
||||
<view class="name">
|
||||
{{ item.couponCode }} {{ couponTitle(item) }}
|
||||
<!-- <text style="letter-spacing: 0;">{{ item.discountLimit }}</text> -->
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.validityPeriod') }}:{{ item.startDate.substr(0,10) }} ~ {{ item.endDate.substr(0,10)}}
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.instructions') }}:{{ couponDesc(item) }} {{ item.siteName.length ?`${item.siteName.toString()}`: $t('coupon.storewide')}} {{ item.renewUsable?$t('coupon.renewable'):$t('coupon.noRenewable') }} {{ item.unitTypeName.length ? `(${item.unitTypeName.toString()})` : `(${$t("coupon.all")})`}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="desc">
|
||||
{{ item.siteName.length ?`${item.siteName.length>1?$t('coupon.multiStoreUse'):item.siteName[0]}`: $t('coupon.storewide') }}
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.limitedtimeoffer') }}
|
||||
</view>
|
||||
<view style="display: flex;justify-content: center;margin-top: 4px;">
|
||||
<uv-checkbox
|
||||
v-if="props.siteData?.lockerId"
|
||||
:key="index"
|
||||
:name="item.couponDispositionId"
|
||||
:disabled="disabledFunc(item)"
|
||||
@click.stop=""
|
||||
></uv-checkbox>
|
||||
<!-- <button class="btn" :disabled="props.disabledFunc(item)" @click="chooseCoupon(item)">
|
||||
{{ $t('coupon.apply') }}
|
||||
</button> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uv-checkbox-group>
|
||||
</view>
|
||||
</view>
|
||||
<view class="unusableCoupons couponUl">
|
||||
<view v-if="unusableDataList.length" @click="state.showUnusableCoupons = !state.showUnusableCoupons" class="showUnusableCoupons">显示不可用优惠卷>></view>
|
||||
<uv-divider v-if="unusableDataList.length && state.showUnusableCoupons" :text="$t('coupon.unusableCoupons')"></uv-divider>
|
||||
<view v-show="state.showUnusableCoupons" class="couponLi" v-for="(item,index) in unusableDataList" :key="index">
|
||||
<view class="left">
|
||||
<view class="name">
|
||||
{{ item.couponCode }} {{ couponTitle(item) }}
|
||||
<!-- <text style="letter-spacing: 0;">{{ item.discountLimit }}</text> -->
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.validityPeriod') }}:{{ item.startDate.substr(0,10) }} ~ {{ item.endDate.substr(0,10)}}
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.instructions') }}:{{ couponDesc(item) }} {{ item.siteName.length ?`${item.siteName.toString()}`: $t('coupon.storewide')}} {{ item.renewUsable?$t('coupon.renewable'):$t('coupon.noRenewable') }} {{ item.unitTypeName.length ? `(${item.unitTypeName.toString()})` : `(${$t("coupon.all")})`}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="desc">
|
||||
{{ item.siteName.length ?`${item.siteName.length>1?$t('coupon.multiStoreUse'):item.siteName[0]}`: $t('coupon.storewide') }}
|
||||
</view>
|
||||
<view class="desc">
|
||||
{{ $t('coupon.limitedtimeoffer') }}
|
||||
</view>
|
||||
<view>
|
||||
<button class="btn" :disabled="true">
|
||||
{{ $t('coupon.apply') }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="confirm" v-if="props.siteData?.lockerId">
|
||||
<!-- @click="submit" -->
|
||||
<button class="next-btn" @click="confirm">
|
||||
{{ $t("common.confirm") }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</myPopup>
|
||||
<uv-popup
|
||||
ref="bindPhonePopup"
|
||||
customStyle="width: 80%; height: 400rpx; padding: 50rpx 0; border-radius: 32rpx; display: flex; flex-direction: column; justify-content: center; align-items: center;"
|
||||
>
|
||||
<text>{{ $t("common.bindPhone") }}</text>
|
||||
<text style="padding: 0 40rpx; margin-top: 20rpx; text-align: center;"> {{ $t("common.bindPhoneUnlock") }}</text>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
style="width: 80%; margin-top: 20px; background: #5BBC6B; color: #FFFFFF; line-height: 80rpx;"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
>
|
||||
{{ $t("common.QuickBind") }}
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</uv-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import myPopup from './myPopup.vue';
|
||||
import { ref,computed,watch } from 'vue';
|
||||
import { couponApi } from '@/Apis/coupon.js';
|
||||
import { useSiteApi } from "@/Apis/site.js";
|
||||
import { AppId } from '@/config/index.js'
|
||||
// 国际化配置
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
import { useOrderApi } from "@/Apis/order.js";
|
||||
const { t } = useI18n();
|
||||
const getOrderApi = useOrderApi();
|
||||
const getApi = couponApi();
|
||||
const getSiteApi = useSiteApi();
|
||||
const popup = ref();
|
||||
const { themeInfo,storeState } = useMainStore();
|
||||
const getLoginApi = useLoginApi();
|
||||
// 接收 v-model
|
||||
const props = defineProps({
|
||||
modelValue:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
disabledFunc:{
|
||||
type:Function,
|
||||
default: ()=> false,
|
||||
},
|
||||
siteData:{
|
||||
type:Object,
|
||||
default:()=>{}
|
||||
},
|
||||
priceData:{
|
||||
type:Object,
|
||||
default:()=>{}
|
||||
},
|
||||
month:{
|
||||
type:Number,
|
||||
default:24
|
||||
},
|
||||
couponItem:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
couponOtherItem:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
isRenew:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
});
|
||||
const isKingKong = (AppId === 'wxb20921dfdd0b94f4' || AppId === 'wx3c4ab696101d77d1') // 是否是金刚 如果是金刚则显示美团和大众点评优惠卷
|
||||
const bindPhonePopup = ref()
|
||||
const couponCode = ref('');
|
||||
const dataList = ref([]);
|
||||
const unusableDataList = ref([]); // 不可用的优惠卷
|
||||
const state = ref({
|
||||
ortherCoupon:[],
|
||||
ortherCouponLoading:false,
|
||||
showUnusableCoupons: false,
|
||||
checkboxCouponValue: [],
|
||||
checkboxOrtherCouponValue: [], // 其他优惠卷
|
||||
canComfirm: false,
|
||||
})
|
||||
|
||||
const isShowOrtherCoupon = (item)=>{
|
||||
return !props.couponOtherItem.map(x=>x.number).includes(item.number)
|
||||
}
|
||||
|
||||
const emit = defineEmits(['close', 'confirm', 'update:modelValue','chooseCoupon']);
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value);
|
||||
}
|
||||
})
|
||||
// watch(() => modelValue.value, (value) => {
|
||||
// state.value.ortherCoupon = []
|
||||
// value && getDataList()
|
||||
// })
|
||||
watch(() => storeState.token, (value) => {
|
||||
state.value.ortherCoupon = []
|
||||
value && getDataList()
|
||||
})
|
||||
const open = ()=>{
|
||||
modelValue.value = true;
|
||||
state.value.checkboxCouponValue = props.couponItem.map(x=>x.couponDispositionId);
|
||||
state.value.checkboxOrtherCouponValue = props.couponOtherItem.map(x=>x.number);
|
||||
getDataList();
|
||||
}
|
||||
const close = ()=>{
|
||||
modelValue.value = false;
|
||||
}
|
||||
// 根据优惠卷类型 返回优惠卷 作用范围 优惠多少钱 打多少折 首页多少钱
|
||||
const couponTitle = (item)=>{
|
||||
let detail = ''
|
||||
switch(item.couponType){
|
||||
case 1:
|
||||
detail = `${t('discountMomey',{discount:item.discountLimit})}`
|
||||
break
|
||||
case 2:
|
||||
detail = `${t('couponDiscount',{percent: 100 - item.discountRange * 100,discount: (item.discountRange * 100) / 10,})}`
|
||||
break
|
||||
case 3:
|
||||
detail = `${t('firstMonthRent',{discount:item.firstMonthAmount})}`
|
||||
break
|
||||
case 4:
|
||||
detail = `${t('couponDiscount',{percent: 100 - item.monthDiscount * 100,discount: (item.monthDiscount * 100) / 10})}`
|
||||
break
|
||||
case 5:
|
||||
detail = `${t('freeMonth',{discount:item.freeMonth})}`
|
||||
break
|
||||
case 6:
|
||||
detail = `${t('BonusMonth',{discount:item.freeMonth})}`
|
||||
break
|
||||
default:
|
||||
detail = `${t('discountMomey',{discount:item.discountLimit})}`
|
||||
break
|
||||
}
|
||||
return `${detail}`
|
||||
}
|
||||
const couponDesc = (item)=>{
|
||||
let detail = ''
|
||||
switch(item.couponType){
|
||||
case 1:
|
||||
detail = `${t('requiredMomey',{momey: item.satisfyAmount})}`
|
||||
break
|
||||
case 2:
|
||||
detail = `${t('requiredMomey',{momey: item.satisfyAmount})}`
|
||||
break
|
||||
case 3:
|
||||
detail = `${t('fullMonths',{count: item.fullMonth})}`
|
||||
break
|
||||
case 4:
|
||||
detail = `${t('fullMonths',{count: item.fullMonth})}`
|
||||
break
|
||||
case 5:
|
||||
detail = `${t('fullMonths',{count: item.fullMonth})}`
|
||||
break
|
||||
case 6:
|
||||
detail = `${t('fullMonths',{count: item.fullMonth})}`
|
||||
break
|
||||
default:
|
||||
detail = `${t('requiredMomey',{momey: item.satisfyAmount})}`
|
||||
break
|
||||
}
|
||||
return `${detail}`
|
||||
}
|
||||
|
||||
|
||||
// 区分可用和不可用优惠卷
|
||||
function splitUnitTypeIdsByCoupon(coupon, siteId, unitTypeId) {
|
||||
const usable = []
|
||||
const unusable = []
|
||||
|
||||
coupon.forEach((item) => {
|
||||
const siteUsable = item.siteIds.length === 0 || item.siteIds.includes(siteId)
|
||||
const unitUsable = item.unitTypeIds.length === 0 || item.unitTypeIds.includes(unitTypeId)
|
||||
if (siteUsable && unitUsable) {
|
||||
usable.push(item)
|
||||
} else {
|
||||
unusable.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
return { usable, unusable }
|
||||
}
|
||||
const getCouponCode = ()=>{
|
||||
if(couponCode.value !== ''){
|
||||
uni.showLoading({
|
||||
mark: true
|
||||
})
|
||||
getApi.DrawDownCoupon({couponCode:couponCode.value}).then(res=>{
|
||||
if(res.code == 200){
|
||||
uni.showToast({
|
||||
title: t('coupon.redemptionuccessful'),
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(()=>{
|
||||
getDataList()
|
||||
},1000)
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}).finally(()=>{
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
}
|
||||
const getDataList = ()=>{
|
||||
uni.showLoading()
|
||||
getApi.GetCouponList({pageIndex:1,pageSize:10000}).then(res=>{
|
||||
uni.hideLoading()
|
||||
if(res.code == 200){
|
||||
dataList.value = res.data.result;
|
||||
res.data.result.forEach(item=>{
|
||||
item.startDate = item.startDate.replace(/-/g,'/')
|
||||
item.endDate = item.endDate.replace(/-/g,'/')
|
||||
})
|
||||
if(props.siteData?.siteId){
|
||||
const { usable, unusable} = splitUnitTypeIdsByCoupon(res.data.result, props.siteData?.siteId, props.siteData?.unitTypeId)
|
||||
dataList.value = usable;
|
||||
unusableDataList.value = unusable;
|
||||
}else{
|
||||
dataList.value = res.data.result;
|
||||
unusableDataList.value = [];
|
||||
}
|
||||
getOtherCoupon()
|
||||
}else{
|
||||
setTimeout(()=>{
|
||||
modelValue.value = false;
|
||||
},500)
|
||||
}
|
||||
}).finally(()=>{
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
const chooseCoupon = (item)=>{
|
||||
if(disabledFunc(item)){
|
||||
uni.showToast({
|
||||
title: t('coupon.currentConditionsNotMet'),
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
if(state.value.checkboxCouponValue.includes(item.couponDispositionId)){
|
||||
state.value.checkboxCouponValue = state.value.checkboxCouponValue.filter(x=>x !== item.couponDispositionId)
|
||||
}else{
|
||||
state.value.checkboxCouponValue.push(item.couponDispositionId)
|
||||
}
|
||||
if(!props.siteData){
|
||||
emit('chooseCoupon',item)
|
||||
modelValue.value = false;
|
||||
}
|
||||
|
||||
}
|
||||
const chooseOtherCoupon = (item)=>{
|
||||
emit('chooseOtherCoupon',item)
|
||||
modelValue.value = false;
|
||||
}
|
||||
// 获取其他(美团)优惠卷
|
||||
const getOtherCoupon = ()=>{
|
||||
const phone = storeState.userInfo?.phone;
|
||||
const siteId = props.siteData?.siteId;
|
||||
if(siteId && phone && isKingKong){
|
||||
state.value.ortherCouponLoading = true;
|
||||
getApi.GetMeiTuanCodeByPhone({phone,siteId}).then(res=>{
|
||||
state.value.ortherCouponLoading = false;
|
||||
if(res.code == 200){
|
||||
state.value.ortherCoupon = res.data;
|
||||
}
|
||||
|
||||
// state.value.ortherCoupon = [
|
||||
// {
|
||||
// number: "NO20240711001",
|
||||
// endTime: "2025-08-31 23:59:59",
|
||||
// platform: "大众点评",
|
||||
// title: "夏日特惠套餐",
|
||||
// price: "88.00",
|
||||
// dealId: "D123456",
|
||||
// dealGroupId: "G987654",
|
||||
// marketPrice: "128.00",
|
||||
// receiptBeginDate: "2025-08-01",
|
||||
// receiptEndDate: "2025-08-31"
|
||||
// },
|
||||
// {
|
||||
// number: "NO20240711002",
|
||||
// endTime: "2025-09-15 23:59:59",
|
||||
// platform: "美团",
|
||||
// title: "双人豪华套餐",
|
||||
// price: "168.00",
|
||||
// dealId: "D654321",
|
||||
// dealGroupId: "G123789",
|
||||
// marketPrice: "218.00",
|
||||
// receiptBeginDate: "2025-09-01",
|
||||
// receiptEndDate: "2025-09-15"
|
||||
// }
|
||||
// ];
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
const getPhoneNumber = (e) => {
|
||||
uni.showLoading();
|
||||
if (e.detail.code) {
|
||||
getLoginApi.GetPhoneNumber({code:e.detail.code}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "获取手机号成功",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
bindPhonePopup.value.close();
|
||||
storeState.userInfo.phone = res.data;
|
||||
getOtherCoupon();
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "获取手机号失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "获取手机号失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
}
|
||||
// 新单
|
||||
const disabledFunc = (item) => {
|
||||
if(!props.siteData?.siteId) return false
|
||||
if (!item) {
|
||||
return true;
|
||||
}
|
||||
// 已经选中的
|
||||
const couponItem = dataList.value.filter(
|
||||
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
|
||||
);
|
||||
// 如果是它已经被选中 就直接返回 false
|
||||
if(state.value.checkboxCouponValue.includes(item.couponDispositionId)) return false;
|
||||
|
||||
// 判断是否存在同类型的优惠卷 不然选
|
||||
const isExistSameType = couponItem.some(
|
||||
(x) => x.couponType === item.couponType
|
||||
);
|
||||
if(isExistSameType) return true;
|
||||
|
||||
// item.siteIds 空的时候 包括全部
|
||||
const isSite = item.siteIds.length
|
||||
? item.siteIds.includes(props.siteData.siteId)
|
||||
: true;
|
||||
|
||||
if(!isSite) return true;
|
||||
|
||||
const isUnit = item.unitTypeIds.length
|
||||
? item.unitTypeIds.includes(props.siteData.unitTypeId)
|
||||
: true;
|
||||
if(!isUnit) return true;
|
||||
|
||||
let isPrice = item.satisfyAmount
|
||||
? props.priceData.discountExpense >= item.satisfyAmount
|
||||
: true;
|
||||
if([3,4,5,6].includes(item.couponType)){
|
||||
isPrice = props.month>=item.fullMonth
|
||||
}
|
||||
if(!isPrice) return true;
|
||||
// 将日期字符串转换为 Date 对象
|
||||
const start = new Date(item.startDate);
|
||||
const end = new Date(item.endDate);
|
||||
// 获取当前日期和时间
|
||||
const now = new Date();
|
||||
// 判断当前日期是否在开始日期和结束日期之间
|
||||
const isDate = now >= start && now <= end;
|
||||
if(!isDate) return true;
|
||||
// 判断是否可以添加
|
||||
const canAdd = canAddValue(couponItem.map(x=>x.couponType),item.couponType)
|
||||
if(!canAdd) return true;
|
||||
|
||||
// 能否用在续仓
|
||||
let canReNew = true;
|
||||
if(props.isRenew){
|
||||
canReNew = item.renewUsable
|
||||
}
|
||||
if(!canReNew) return true;
|
||||
return !(isSite && isUnit && isPrice && isDate&& canAdd && canReNew);
|
||||
};
|
||||
function canAddValue(arr, value) {
|
||||
// 定义可以共存的数字
|
||||
const allowedNumbers = new Set([1, 3, 4]);
|
||||
|
||||
// 如果数组为空,可以添加任意值
|
||||
if (arr.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查数组中是否已经存在非1、3、4的值
|
||||
const hasInvalidNumber = arr.some(num => !allowedNumbers.has(num));
|
||||
if (hasInvalidNumber) {
|
||||
return false; // 如果数组中已经有非1、3、4的值,不能添加任何值
|
||||
}
|
||||
|
||||
// 如果要添加的值是1、3、4,且没有重复,则可以添加
|
||||
if (allowedNumbers.has(value) && !arr.includes(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 其他情况不能添加
|
||||
return false;
|
||||
}
|
||||
|
||||
const ortherCouponChange = (e)=>{
|
||||
const couponItem = dataList.value.filter(
|
||||
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
|
||||
);
|
||||
const couponOtherItem = state.value.ortherCoupon.filter(
|
||||
(x) => state.value.checkboxOrtherCouponValue.includes(x.number)
|
||||
);
|
||||
getLockerExpense(couponItem,couponOtherItem)
|
||||
}
|
||||
|
||||
const couponChange = (e)=>{
|
||||
const couponItem = dataList.value.filter(
|
||||
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
|
||||
);
|
||||
const couponOtherItem = state.value.ortherCoupon.filter(
|
||||
(x) => state.value.checkboxOrtherCouponValue.includes(x.number)
|
||||
);
|
||||
getLockerExpense(couponItem,couponOtherItem)
|
||||
}
|
||||
|
||||
const getLockerExpense = async (couponItem,couponOtherItem) => {
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
});
|
||||
const LockerExpenseApi = props.isRenew ? getOrderApi.ContinuationOrderPricePost : getSiteApi.GetLockerExpense;
|
||||
await LockerExpenseApi({
|
||||
lockerId: props.siteData.lockerId,
|
||||
orderId: props.siteData.orderId,
|
||||
month: props.month,
|
||||
couponIds: couponItem.map((item) => item.couponDispositionId),
|
||||
serialNumber: couponOtherItem.map(x => {
|
||||
return {
|
||||
dealGroupId: x.dealGroupId,
|
||||
number: x.number,
|
||||
marketPrice: x.marketPrice,
|
||||
purchasePrice: x.price
|
||||
}
|
||||
})
|
||||
})
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.code === 200) {
|
||||
state.value.canComfirm = true
|
||||
state.value.feeData = res.data;
|
||||
}else{
|
||||
state.value.canComfirm = false
|
||||
}
|
||||
if(res.code === 1002){
|
||||
uni.showModal({
|
||||
title: t('common.title'),
|
||||
content: t('common.ORDER_AMOUNT_ERROR'),
|
||||
showCancel: false,
|
||||
success: function () {},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const confirm = async ()=>{
|
||||
const couponItem = dataList.value.filter(
|
||||
(x) => state.value.checkboxCouponValue.includes(x.couponDispositionId)
|
||||
);
|
||||
const couponOtherItem = state.value.ortherCoupon.filter(
|
||||
(x) => state.value.checkboxOrtherCouponValue.includes(x.number)
|
||||
);
|
||||
await getLockerExpense(couponItem,couponOtherItem)
|
||||
if(!state.value.canComfirm) return
|
||||
emit('confirm',{couponItem,couponOtherItem})
|
||||
modelValue.value = false;
|
||||
}
|
||||
defineExpose({
|
||||
getOtherCoupon,
|
||||
open,
|
||||
close
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon{
|
||||
.couponContent{
|
||||
.confirm{
|
||||
height: 100rpx;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.next-btn{
|
||||
position: absolute;
|
||||
bottom:30upx;
|
||||
width: 90%;
|
||||
padding: 4px 0;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: var(--text-color);
|
||||
background: var(--active-color);
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
}
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 18upx 18upx 0 0;
|
||||
padding-bottom: 40upx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.header{
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
.line{
|
||||
width: 80rpx;
|
||||
height: 10rpx;
|
||||
background-color:#CBCDCC80;
|
||||
margin: 0 auto;
|
||||
border-radius: 99rpx;
|
||||
}
|
||||
.title{
|
||||
font-size: 36rpx;
|
||||
align-self: self-start;
|
||||
margin-left: 40rpx;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
flex: 1;
|
||||
padding: 0 20upx 100upx 20upx;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
.couponUl{
|
||||
.couponLi{
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 40upx;
|
||||
width: 100%;
|
||||
margin: 20upx 0;
|
||||
border-radius: 18upx;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, var(--left-linear), var(--right-linear));
|
||||
box-shadow: 0px -2px 5px 0px rgba(0, 0, 0, 0.13);
|
||||
font-size: 22upx;
|
||||
&::after{
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: -7px;
|
||||
width: 14px; /* 调整宽度 */
|
||||
height: 100%; /* 调整高度以适应圆形的垂直排列 */
|
||||
background: radial-gradient(#ffffff 0px, #ffffff 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px; /* 保持圆形的大小 */
|
||||
background-repeat: repeat-y; /* 垂直重复 */
|
||||
z-index: 9;
|
||||
}
|
||||
.left{
|
||||
width: 68%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
.name{
|
||||
font-size: 36upx;
|
||||
letter-spacing: 4upx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.desc{
|
||||
margin-top: 10upx;
|
||||
}
|
||||
.input{
|
||||
background-color: var(--bg-popup);
|
||||
text-align: center;
|
||||
width: 220upx;
|
||||
border-radius: 12upx;
|
||||
color: var(--text-color);
|
||||
margin-top: 20upx;
|
||||
}
|
||||
&::before{
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 32upx;
|
||||
width: 32upx;
|
||||
border-radius: 99rpx;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
top: -56upx;
|
||||
right: -18upx;
|
||||
}
|
||||
&::after{
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 32upx;
|
||||
width: 32upx;
|
||||
border-radius: 99rpx;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
bottom: -56upx;
|
||||
right: -18upx;
|
||||
}
|
||||
border-right: 1px solid #fff;
|
||||
}
|
||||
.right{
|
||||
width: 32%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 5%;
|
||||
.btn{
|
||||
border-radius: 99rpx;
|
||||
background-color: #fff;
|
||||
color: var(--stress-color2);
|
||||
font-size: 24upx;
|
||||
line-height: 60upx;
|
||||
margin-top: 30upx;
|
||||
font-weight: bold;
|
||||
&[disabled]{
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.desc{
|
||||
margin-top: 10upx;
|
||||
text-align: center;
|
||||
letter-spacing: 4upx;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
163
components/inviteDetail.vue
Normal file
163
components/inviteDetail.vue
Normal file
@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<view class="invite-wrap">
|
||||
<myPopup v-model="modelValue" mode="bottom" bgColor="none" customStyle="max-height: 70vh; border-radius: 15px 15px 0 0;"
|
||||
:closeOnClickOverlay='true'>
|
||||
<view class="inner-wrap">
|
||||
<view class="title">{{ $t('inviteDetail.title') }}</view>
|
||||
<view class="close-icon" @click="closeShow">
|
||||
<uv-icon name="close" size="10" :color="themeInfo.activeColor"></uv-icon>
|
||||
</view>
|
||||
<view class="top-nav">
|
||||
<view class="label">{{ $t('inviteDetail.Username') }}</view>
|
||||
<view class="label">{{ $t('inviteDetail.Registration Date') }}</view>
|
||||
<view class="label">{{ $t('inviteDetail.Status') }}</view>
|
||||
</view>
|
||||
<view class="content-wrap">
|
||||
<view class="list-wrap" v-if="state.list.length">
|
||||
<view class="item-wrap" v-for="item in state.list">
|
||||
<view class="label">{{ item.name }}</view>
|
||||
<view class="label">{{ item.createTime }}</view>
|
||||
<view class="label">{{ item.status }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-wrap" v-else>
|
||||
<view>{{ $t('inviteDetail.No invitation') }}</view>
|
||||
</view>
|
||||
<button open-type="share" class="share-btn">{{ $t('inviteDetail.Share Invitation') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</myPopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import myPopup from './myPopup.vue';
|
||||
import { reactive, watch, computed } from 'vue';
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
|
||||
import { useRecommend } from "@/Apis/recommend.js";
|
||||
const getApi = useRecommend();
|
||||
// 接收 v-model
|
||||
const props = defineProps({
|
||||
modelValue:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value);
|
||||
}
|
||||
})
|
||||
watch(() => modelValue.value, (value) => {
|
||||
value && getRecommend()
|
||||
})
|
||||
|
||||
const closeShow = () => {
|
||||
modelValue.value = false;
|
||||
}
|
||||
const getRecommend = () => {
|
||||
getApi.GetRecommend().then((res) => {
|
||||
if(res.code === 200){
|
||||
state.list = res.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
list: []
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invite-wrap {
|
||||
.inner-wrap {
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
padding: 40rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
background: var(--main-color);
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 30rpx;
|
||||
padding: 6rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba($color: #FFFFFF, $alpha: 0.6);
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
display: flex;
|
||||
padding: 16rpx 0;
|
||||
background: var(--main-color);
|
||||
|
||||
.label {
|
||||
width: 33.33%;
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrap {
|
||||
padding: 20rpx 0 40rpx;
|
||||
background: #FFFFFF;
|
||||
|
||||
.list-wrap {
|
||||
max-height: 460rpx;
|
||||
overflow: auto;
|
||||
|
||||
.item-wrap {
|
||||
display: flex;
|
||||
padding: 16rpx 0;
|
||||
|
||||
.label {
|
||||
width: 33.33%;
|
||||
font-weight: bold;
|
||||
font-size: 30rpx;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-wrap {
|
||||
padding: 30rpx 0;
|
||||
margin: 20rpx 30rpx 0;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 16rpx;
|
||||
background: var(--main-color);
|
||||
|
||||
.en {
|
||||
margin-top: 20rpx;
|
||||
font-size: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
margin: 40rpx 40rpx 0;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
border-radius: 40rpx;
|
||||
color: var(--text-color);
|
||||
background: var(--main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
124
components/my-dropdown.vue
Normal file
124
components/my-dropdown.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<!--
|
||||
原生 uni-app 下拉组件(不依赖 Element UI)
|
||||
特点:
|
||||
- 点击展开下拉菜单
|
||||
- 选择后关闭
|
||||
- v-model 支持
|
||||
- 简洁轻量
|
||||
|
||||
用法:
|
||||
<uni-native-dropdown v-model="value" :items="['编辑','删除','更多']" />
|
||||
|
||||
<uni-native-dropdown
|
||||
v-model="selected"
|
||||
:items="[
|
||||
{ label: '编辑', value: 'edit' },
|
||||
{ label: '删除', value: 'del' }
|
||||
]
|
||||
placeholder="请选择操作"
|
||||
/>
|
||||
-->
|
||||
<template>
|
||||
<view class="dropdown-container">
|
||||
<!-- 触发区域 -->
|
||||
<view @click="toggle">
|
||||
<slot name="trigger" :selected="selectedLabel">
|
||||
<view class="default-trigger">
|
||||
<text>{{ selectedLabel }}</text>
|
||||
<view class="arrow">▼</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 下拉菜单 -->
|
||||
<view v-if="open" class="dropdown-menu">
|
||||
<view
|
||||
class="dropdown-item"
|
||||
v-for="(item, i) in normalizedItems"
|
||||
:key="i"
|
||||
@click="selectItem(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UniNativeDropdown',
|
||||
props: {
|
||||
modelValue: { type: [String, Number, Object], default: '' },
|
||||
items: { type: Array, default: () => [] },
|
||||
placeholder: { type: String, default: '请选择' }
|
||||
},
|
||||
data() {
|
||||
return { open: false }
|
||||
},
|
||||
computed: {
|
||||
normalizedItems() {
|
||||
// 如果传的是 string 数组,转成 {label, value}
|
||||
return this.items.map(i =>
|
||||
typeof i === 'string' ? { label: i, value: i } : i
|
||||
)
|
||||
},
|
||||
selectedLabel() {
|
||||
const item = this.normalizedItems.find(i => i.value === this.modelValue)
|
||||
return item ? item.label : this.placeholder
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.open = !this.open
|
||||
},
|
||||
selectItem(item) {
|
||||
this.$emit('update:modelValue', item.value)
|
||||
this.$emit('change', item)
|
||||
this.open = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
width: auto;
|
||||
}
|
||||
.dropdown-trigger {
|
||||
border: 1px solid #ccc;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
}
|
||||
.arrow {
|
||||
font-size: 28rpx;
|
||||
margin-left: 10rpx;
|
||||
transform: scaleY(0.8);
|
||||
display: inline-block; /* 重要 */
|
||||
}
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8rpx;
|
||||
margin-top: 6rpx;
|
||||
z-index: 999;
|
||||
}
|
||||
.dropdown-item {
|
||||
padding: 20rpx;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.dropdown-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.dropdown-item:active {
|
||||
background: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
109
components/myCustomtTabBar.vue
Normal file
109
components/myCustomtTabBar.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<uni-tabbar class="myCustomTabbar uni-tabbar" :style="{ flexDirection: direction === 'horizontal' ? 'row' : 'column' }">
|
||||
<div
|
||||
class="uni-tabbar__item"
|
||||
v-for="(item, index) in tabItems"
|
||||
:key="index"
|
||||
@click="handleTabItemTap(item)"
|
||||
:class="{ 'call-phone-item': item.phoneIcon }"
|
||||
>
|
||||
<div class="uni-tabbar__bd">
|
||||
<div class="uni-tabbar__icon">
|
||||
<svg v-if="index === 0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="18" height="18.733154296875" viewBox="0 0 18 18.733154296875"><path d="M18,17.733154C18,18.285454,17.552299,18.733154,17,18.733154L1,18.733154C0.44772005,18.733154,0,18.285454,0,17.733154L0,7.2222252C0,6.9136353,0.14246988,6.6223249,0.38606,6.4328756L8.3860998,0.21064496C8.7472,-0.070214987,9.2528,-0.070214987,9.6139002,0.21064496L17.613899,6.4328756C17.8575,6.6223249,18,6.9136353,18,7.2222252L18,17.733154ZM16,16.733154L16,7.7113056L9,2.266865L2,7.7113056L2,16.733154L16,16.733154Z" :fill="selected === 0 ? '#fb322e' : '#000000'" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>
|
||||
<svg v-if="index === 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="18" height="20" viewBox="0 0 18 20"><path d="M17,20L1,20C0.44772005,20,0,19.552299,0,19L0,1C0,0.44772005,0.44772005,0,1,0L17,0C17.552299,0,18,0.44772005,18,1L18,19C18,19.552299,17.552299,20,17,20ZM16,18L16,2L2,2L2,18L16,18ZM5,5L13,5L13,7L5,7L5,5ZM5,9L13,9L13,11L5,11L5,9ZM5,13L10,13L10,15L5,15L5,13Z" :fill="selected === 1 ? '#fb322e' : '#000000'" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>
|
||||
<svg v-if="index === 2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="21" viewBox="0 0 16 21"><path d="M16,21L14,21L14,19C14,17.3431,12.6569,16,11,16L5,16C3.3431501,16,2,17.3431,2,19L2,21L0,21L0,19C0,16.2386,2.2385802,14,5,14L11,14C13.7614,14,16,16.2386,16,19L16,21ZM8,12C4.6862898,12,2,9.3136997,2,6C2,2.68629,4.6862898,0,8,0C11.3137,0,14,2.68629,14,6C14,9.3136997,11.3137,12,8,12ZM8,10C10.2091,10,12,8.2091398,12,6C12,3.7908602,10.2091,2,8,2C5.7908602,2,4,3.7908602,4,6C4,8.2091398,5.7908602,10,8,10Z" :fill="selected === 2 ? '#fb322e' : '#000000'" fill-opacity="1" style="mix-blend-mode:passthrough"/></svg>
|
||||
<!-- <uv-icon v-if="showIcon" :name="item.icon" custom-prefix="custom-icon" :size="item.size || 20" :color="themeInfo.iconColor"></uv-icon> -->
|
||||
</div>
|
||||
<div class="uni-tabbar__label" :style="{ fontSize: '10px',color: selected === index ? '#fb322e' : '#000000' }">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</uni-tabbar>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { isXiaohongshu } from "@/config/index.js";
|
||||
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js"
|
||||
const { themeInfo } = useMainStore();
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal',
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
selected: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onTabItemTap']);
|
||||
|
||||
const tabItems = computed(() => {
|
||||
if (isXiaohongshu) {
|
||||
return [
|
||||
{ label: t('tabbar.home'), icon: 'home1', index:0, pagePath:'pages/index/index' },
|
||||
{ label: '', icon: 'telephone', index:2, pagePath:'', size: 34, phoneIcon: true },
|
||||
{ label: t('tabbar.book'), icon: 'book', index:1, pagePath:'pages/book/index'},
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ label: t('tabbar.home'), icon: '/static/tabbar/home.svg', index:0, pagePath:'pages/index/index' },
|
||||
// { label: t('tabbar.book'), icon: 'book', index:1, pagePath:'pages/book/index'},
|
||||
// { label: '', icon: 'telephone', index:2, pagePath:'', size: 34, phoneIcon: true },
|
||||
{ label: "订单", icon: '/static/tabbar/order.svg', index:3, pagePath:'pages/unlock/index' },
|
||||
{ label: t('tabbar.personal'), icon: '/static/tabbar/user.svg', index:4, pagePath:'pages/personal/index' },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const handleTabItemTap = (index) => {
|
||||
emit('onTabItemTap', index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.myCustomTabbar {
|
||||
display: flex;
|
||||
}
|
||||
.uni-tabbar{
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
z-index: 998;
|
||||
}
|
||||
.uni-tabbar__item{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
font-size: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.uni-tabbar__bd{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
font-size: 0;
|
||||
text-align: center;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
svg {
|
||||
max-width: 100%;
|
||||
height: 40rpx;
|
||||
width: 40rpx;
|
||||
}
|
||||
</style>
|
||||
210
components/myModal.vue
Normal file
210
components/myModal.vue
Normal file
@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<view class="popup">
|
||||
<uv-popup ref="popup" customStyle="width: 688rpx; height: auto; padding:32rpx;" round="16rpx"
|
||||
:closeOnClickOverlay="false" :safeAreaInsetBottom="false">
|
||||
<view class="modal-container">
|
||||
|
||||
<!-- 标题 -->
|
||||
<view class="modal-title font32 fontb">
|
||||
{{ props.title || $t('common.title') }}
|
||||
</view>
|
||||
|
||||
<!-- 内容 -->
|
||||
<view class="modal-text-wrap" v-if="props.content">
|
||||
<text class="modal-text font28 fontb">
|
||||
{{ props.content }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<view class="modal-button">
|
||||
<uv-button v-if="cancelShow" @click="closeModal" :customStyle="{
|
||||
height: '86rpx',
|
||||
lineHeight: '86rpx',
|
||||
color: '#000',
|
||||
fontSize: '32rpx',
|
||||
}" shape="circle">
|
||||
{{ props.cancelText || $t('common.cancel') }}
|
||||
</uv-button>
|
||||
<uv-button v-if="confirmShow" :customStyle="{
|
||||
height: '86rpx',
|
||||
background: '#FB322E',
|
||||
lineHeight: '86rpx',
|
||||
color: '#fff',
|
||||
fontSize: '32rpx',
|
||||
}" shape="circle" @click="confirm">
|
||||
{{ props.confirmText || $t('common.confirm') }}
|
||||
</uv-button>
|
||||
<!-- <view
|
||||
v-if="cancelShow"
|
||||
class="modal-button-item modal-button-1"
|
||||
@click="closeModal"
|
||||
>
|
||||
{{ props.cancelText || $t('common.cancel') }}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="confirmShow"
|
||||
class="modal-button-item modal-button-2"
|
||||
@click="confirm"
|
||||
>
|
||||
{{ props.confirmText || $t('common.confirm') }}
|
||||
</view> -->
|
||||
|
||||
<slot name="affterBtn"></slot>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</uv-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from "vue";
|
||||
|
||||
const popup = ref()
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
cancelShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
confirmShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
noClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
"close",
|
||||
"confirm",
|
||||
"update:modelValue"
|
||||
])
|
||||
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(value) {
|
||||
emit("update:modelValue", value)
|
||||
|
||||
if (value) {
|
||||
popup.value?.open()
|
||||
} else {
|
||||
popup.value?.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => modelValue.value,
|
||||
(val) => {
|
||||
if (!popup.value) {
|
||||
setTimeout(() => {
|
||||
val ? popup.value?.open() : popup.value?.close()
|
||||
}, 300)
|
||||
} else {
|
||||
val ? popup.value?.open() : popup.value?.close()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
const closeModal = () => {
|
||||
emit("close")
|
||||
|
||||
if (!props.noClose) {
|
||||
modelValue.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
emit("confirm")
|
||||
|
||||
if (!props.noClose) {
|
||||
modelValue.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
|
||||
.modal-title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 36rpx;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* 文本外层 */
|
||||
|
||||
.modal-text-wrap {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
/* 文本 */
|
||||
|
||||
.modal-text {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
// font-size: 34rpx;
|
||||
// font-weight: 600;
|
||||
// line-height: 48rpx;
|
||||
padding: 20rpx 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 按钮容器 */
|
||||
|
||||
.modal-button {
|
||||
margin-top: 20rpx;
|
||||
width: 100%;
|
||||
min-height: 86rpx;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
:deep(.uv-button-wrapper){
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
172
components/myPopup.vue
Normal file
172
components/myPopup.vue
Normal file
@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<uv-popup
|
||||
ref="popup"
|
||||
:customStyle="props.customStyle"
|
||||
:closeOnClickOverlay="props.closeOnClickOverlay"
|
||||
@maskClick="maskClick"
|
||||
:mode="props.mode"
|
||||
:bgColor='props.bgColor'
|
||||
:closeable="props.closeable"
|
||||
@change="handleChange"
|
||||
:safeAreaInsetBottom="false"
|
||||
>
|
||||
<slot></slot>
|
||||
</uv-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref,watch,computed, nextTick } from 'vue';
|
||||
const popup = ref();
|
||||
// 接收 v-model
|
||||
const props = defineProps({
|
||||
modelValue:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
closeOnClickOverlay:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
mode:{
|
||||
type:String,
|
||||
default:'center'
|
||||
},
|
||||
customStyle:{
|
||||
type:String,
|
||||
default:'width: 90%; height: auto; padding:20rpx 0; border-radius: 32rpx;'
|
||||
},
|
||||
bgColor:{
|
||||
type:String,
|
||||
default:'#FFFFFF'
|
||||
},
|
||||
closeable:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
});
|
||||
const handleChange = (e) => {
|
||||
emit('update:modelValue', e.show);
|
||||
}
|
||||
const maskClick = () => {
|
||||
modelValue.value = false;
|
||||
}
|
||||
const emit = defineEmits(['close', 'confirm', 'update:modelValue']);
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value);
|
||||
if(value){
|
||||
popup.value.open()
|
||||
}else{
|
||||
popup.value.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
watch(() => modelValue.value, (val) => {
|
||||
// 组件没出来之前
|
||||
if(!popup.value){
|
||||
nextTick(()=>{
|
||||
val?popup.value?.open():popup.value?.close();
|
||||
})
|
||||
}else{
|
||||
val?popup.value?.open():popup.value?.close();
|
||||
}
|
||||
},{
|
||||
immediate:true
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-container {
|
||||
// height: 700rpx;
|
||||
// background: #000;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.modal-title {
|
||||
width: 486rpx;
|
||||
height: 42rpx;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 36rpx;
|
||||
color: #000000;
|
||||
position: relative;
|
||||
// display: inline-block;
|
||||
z-index: 1;
|
||||
&::before{
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
width: 100%;
|
||||
height: 14rpx;
|
||||
background-color: var(--main-color);
|
||||
border-radius: 100px;
|
||||
display: block;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
.modal-text {
|
||||
width: 486rpx;
|
||||
margin-top: 38rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
line-height: 32rpx;
|
||||
}
|
||||
.upload {
|
||||
margin-top: 30rpx;
|
||||
height: 250rpx;
|
||||
width: 100%;
|
||||
// background: #000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
// overflow-x: scroll;
|
||||
overflow-y: scroll;
|
||||
// white-space: nowrap;
|
||||
flex-wrap:nowrap;
|
||||
// overflow: hidden;
|
||||
}
|
||||
.modal-button {
|
||||
margin-top: 80rpx;
|
||||
width: 527.22rpx;
|
||||
height: 72rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
& > .modal-button-1 {
|
||||
width: 260rpx;
|
||||
height: 72rpx;
|
||||
background: #F4F3F3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
border-radius: 14rpx;
|
||||
&:active {
|
||||
background: #888;
|
||||
}
|
||||
}
|
||||
& > .modal-button-2 {
|
||||
width: 260rpx;
|
||||
height: 72rpx;
|
||||
background: var(--main-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #FBFBFB;
|
||||
border-radius: 14rpx;
|
||||
&:active {
|
||||
background: #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
142
components/myUpload.vue
Normal file
142
components/myUpload.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<uv-upload
|
||||
ref="fileListRef"
|
||||
:fileList="props.modelValue"
|
||||
name="1"
|
||||
:width="props.width"
|
||||
:height="props.height"
|
||||
imageMode="aspectFit"
|
||||
:maxCount="1"
|
||||
:sizeType="['compressed']"
|
||||
:previewFullImage="false"
|
||||
:uploadText="props.uploadText"
|
||||
@afterRead="afterRead"
|
||||
@delete="deletePic"
|
||||
@clickPreview="clickPreview"
|
||||
>
|
||||
<slot></slot>
|
||||
</uv-upload>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref} from "vue";
|
||||
import { useOrderApi } from "@/Apis/order.js";
|
||||
import { baseImageUrl,watermarkURL} from "@/config/index.js";
|
||||
const getApi = useOrderApi();
|
||||
// const fileList = ref([]);
|
||||
const fileListRef = ref();
|
||||
const imageList = ref([]);
|
||||
const props = defineProps({
|
||||
modelValue: Array,
|
||||
previewFullImage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
addWatermark:{
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/* 新增 ↓↓↓ */
|
||||
uploadText: {
|
||||
type: String,
|
||||
default: () => uni.$u?.t?.("unlock.uploadTip") || "Upload"
|
||||
},
|
||||
|
||||
width: {
|
||||
type: String,
|
||||
default: "280rpx"
|
||||
},
|
||||
|
||||
height: {
|
||||
type: String,
|
||||
default: "220rpx"
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const clickPreview = (event) => {
|
||||
if(props.previewFullImage){
|
||||
let url = '';
|
||||
if (event.thumb) {
|
||||
url = event.thumb;
|
||||
} else {
|
||||
const reg = /^(http|https):\/\//;
|
||||
url = reg.test(event.url) ? event.url : baseImageUrl + event.url+(props.addWatermark?watermarkURL:'');
|
||||
}
|
||||
|
||||
uni.previewImage({
|
||||
urls: [url],
|
||||
});
|
||||
}
|
||||
};
|
||||
// 删除图片
|
||||
const deletePic = (event) => {
|
||||
let fileList = [...props.modelValue];
|
||||
fileList.splice(event.index, 1);
|
||||
emit("update:modelValue", fileList);
|
||||
};
|
||||
// 上传请求
|
||||
async function UploaderImage(url) {
|
||||
let url1 = "";
|
||||
try {
|
||||
const res = await getApi.UploaderImage({ filePath: url });
|
||||
// const jsonstr = JSON.parse(res);
|
||||
url1 = res.data+(props.addWatermark?watermarkURL:'');
|
||||
} catch (error) {
|
||||
// 在这里处理错误情况
|
||||
console.error("UploaderImage error:", error);
|
||||
throw error; // 抛出错误,可以让调用者处理
|
||||
}
|
||||
return url1;
|
||||
}
|
||||
// 新增图片方法
|
||||
const afterRead = async (event) => {
|
||||
let fileList = [...props.modelValue];
|
||||
let lists = [].concat(event.file); // 将上传的文件转换为数组
|
||||
let fileListLen = fileList.length;
|
||||
|
||||
// 给每个文件添加上传中状态
|
||||
lists.forEach((item) => {
|
||||
// 去掉默认图片
|
||||
item.thumb = null
|
||||
fileList.push({
|
||||
...item,
|
||||
status: "uploading",
|
||||
message: "上传中",
|
||||
});
|
||||
});
|
||||
emit("update:modelValue", [...fileList]);
|
||||
// 并行上传所有图片
|
||||
const uploadPromises = lists.map(async (item, index) => {
|
||||
try {
|
||||
const result = await UploaderImage(item.url);
|
||||
fileList[fileListLen + index] = {
|
||||
...item,
|
||||
status: "success",
|
||||
message: "",
|
||||
thumb:baseImageUrl+result,
|
||||
url: result,
|
||||
|
||||
};
|
||||
} catch (error) {
|
||||
fileList[fileListLen + index] = {
|
||||
...item,
|
||||
status: "failed",
|
||||
message: "上传失败",
|
||||
url: "",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// 等待所有上传任务完成
|
||||
await Promise.all(uploadPromises);
|
||||
// 所有上传完成后,发出更新事件
|
||||
emit("update:modelValue", [...fileList]);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
::v-deep .uv-upload__deletable {
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
</style>
|
||||
67
components/navBar.vue
Normal file
67
components/navBar.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<view class="back">
|
||||
<view class="left" @click="backTo">
|
||||
<uv-icon class="leftIcon" name="arrow-left" color="#000" blod size="32rpx"></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { navigateBack,navbarHeightAndStatusBarHeight } from '@/utils/common.js';
|
||||
import { useMainStore } from '@/store/index.js';
|
||||
const { themeInfo } = useMainStore();
|
||||
|
||||
const backTo = ()=>{
|
||||
navigateBack()
|
||||
}
|
||||
|
||||
let state = reactive({
|
||||
navHeight: 50,
|
||||
statusBarHeight: 0,
|
||||
});
|
||||
onMounted(() => {
|
||||
// #ifdef MP-WEIXIN || MP-XHS
|
||||
const { tempHeight, navbarHeight, statusBarHeight } = navbarHeightAndStatusBarHeight();
|
||||
state.navHeight = navbarHeight || tempHeight.navbarHeight;
|
||||
state.statusBarHeight = statusBarHeight || tempHeight.statusBarHeight;
|
||||
// #endif
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.back{
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
box-sizing: border-box;
|
||||
height: 90rpx;
|
||||
width: 100vw;
|
||||
padding-left: 40rpx;
|
||||
background-color: #FFF;
|
||||
/* #ifdef APP-PLUS */
|
||||
margin-top: --status-bar-height;
|
||||
/* #endif */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
.leftIcon span {
|
||||
font-weight:bold;
|
||||
}
|
||||
.left{
|
||||
width: 120rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight:bold;
|
||||
::v-deep span {
|
||||
font-weight:bold;
|
||||
}
|
||||
text {
|
||||
font-weight:bold!important;
|
||||
}
|
||||
// transform: scale(1,1.4);
|
||||
// transform-origin: 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
components/noToken.vue
Normal file
29
components/noToken.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<view class="noToken" v-if="!token" @click="goLogin">
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { navigateTo } from '@/utils/navigateTo';
|
||||
const goLogin = ()=>{
|
||||
navigateTo('/pages/login/index')
|
||||
}
|
||||
let token = ref(uni.getStorageSync('token'))
|
||||
onShow(()=>{
|
||||
token.value = uni.getStorageSync('token')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.noToken{
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
336
components/siteDetail.vue
Normal file
336
components/siteDetail.vue
Normal file
@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<view class="site-detail">
|
||||
<!-- 店铺图片 -->
|
||||
<image @click="shopImgClick(siteItem)" :src="baseImageUrl + siteItem.imgUrl" class="shop-image" :class="siteItem.isFiveGoatStores?'shop-imageBig':''"></image>
|
||||
<view class="shop-introduce">
|
||||
<view class="shop-introduce-title">
|
||||
<text>{{ siteItem.name }}</text>
|
||||
<text class="navi-text" @click="handleNavigate">
|
||||
<text v-if="siteItem.distance">{{ siteItem.distance }}KM</text>
|
||||
[ {{ $t("home.navigate") }} ]
|
||||
</text>
|
||||
</view>
|
||||
<view class="shop-region">
|
||||
<view class="left-wrap">
|
||||
<view class="text-wrap">
|
||||
<uv-icon name="landmark" custom-prefix="custom-icon" size="12" color="#616e78"></uv-icon>
|
||||
<text class="text">{{ siteItem.city }}</text>
|
||||
</view>
|
||||
<view class="text-wrap">
|
||||
<uv-icon name="bus" custom-prefix="custom-icon" size="12" color="#616e78"></uv-icon>
|
||||
<text class="text">
|
||||
{{ siteItem.district }}
|
||||
<text style="color: red;font-weight: bold;"
|
||||
@click="handleWayfinding(2)"
|
||||
v-if="siteItem.isStopCarGuide">
|
||||
[ P ]
|
||||
</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="navi-text" @click="handleWayfinding(1)">[ {{ $t("home.wayfinding") }} ]</text>
|
||||
</view>
|
||||
<view class="shop-address">
|
||||
<textEllipsis style="width: 100%;" :address="siteItem.address"></textEllipsis>
|
||||
</view>
|
||||
<button @click="handleBook(siteItem.id)" class="shop-booknow">
|
||||
<!-- #ifdef MP-WEIXIN || H5 || APP-PLUS -->
|
||||
{{ $t("home.book") }}
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-XHS -->
|
||||
{{ $t("home.quote") }}
|
||||
<!-- #endif -->
|
||||
</button>
|
||||
<view @click="handleSite" v-if="siteItem.isFiveGoatStores" class="shop-booknow isFiveGoatStoresBtn">
|
||||
{{ state.isShow ?$t("common.Collapse"):$t("common.Expand") }} 五羊门店 {{ $t("common.OtherStores") }} <view class="isFiveGoatStoresBtn-icon" :class="state.isShow?'rotate':''"><uv-icon name="arrow-down-fill" color="black" size="16"></uv-icon></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="site-detail isFiveGoatStoresSiteDetail" v-if="state.isShow" v-for="item in siteItem.siteList" :key="item.id">
|
||||
<image @click="shopImgClick(item)" :src="baseImageUrl + item.imgUrl" class="shop-image"></image>
|
||||
<view class="shop-introduce">
|
||||
<view class="shop-introduce-title">
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
<button @click="handleBook(item.id)" class="shop-booknow">
|
||||
<!-- #ifdef MP-WEIXIN || H5 || APP-PLUS -->
|
||||
{{ $t("home.book") }}
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-XHS -->
|
||||
{{ $t("home.quote") }}
|
||||
<!-- #endif -->
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="state.isShow" class="closeList" @click="handleSite">
|
||||
{{ $t("common.Collapse") }} 五羊店 <uv-icon name="arrow-up-fill" color="black" size="20"></uv-icon>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { baseImageUrl, isH5, isXiaohongshu, AppId,envVersion } from "@/config/index.js";
|
||||
import { h5GoWx, jumpToSc } from "@/utils/common.js";
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { ref } from "vue";
|
||||
const { storeState } = useMainStore();
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
import textEllipsis from "@/components/textEllipsis.vue";
|
||||
|
||||
const props = defineProps({
|
||||
siteItem: {
|
||||
type: Object,
|
||||
default: () => { },
|
||||
},
|
||||
});
|
||||
const state = ref({
|
||||
isShow:false,
|
||||
})
|
||||
const emit = defineEmits(["showCode"]);
|
||||
const handleSite = (e)=>{
|
||||
state.value.isShow = !state.value.isShow
|
||||
}
|
||||
// 点击路线跳转
|
||||
const handleNavigate = () => {
|
||||
uni.openLocation({
|
||||
latitude: Number(props.siteItem.latitude),
|
||||
longitude: Number(props.siteItem.longitude),
|
||||
name: props.siteItem.name,
|
||||
address: props.siteItem.address,
|
||||
});
|
||||
};
|
||||
|
||||
// 路径指引
|
||||
const handleWayfinding = (type) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/book/navigate?id=${props.siteItem.id}&type=${type}`,
|
||||
});
|
||||
};
|
||||
|
||||
const shopImgClick = (item) => {
|
||||
// 管理员展示门禁二维码操作
|
||||
if (storeState.userInfo?.userType == 2 || storeState.userInfo?.userType == 3) {
|
||||
// if (props.siteItem.accesscontrol == null || props.siteItem.accesscontrol == '') {
|
||||
// uni.showToast({
|
||||
// title: t('site.noAccessId'),
|
||||
// icon: 'none'
|
||||
// });
|
||||
// } else {
|
||||
// // 开门权限只能是用户管理的门店
|
||||
// if (storeState.userInfo?.siteList?.includes('*') || storeState.userInfo?.siteList?.includes(props.siteItem.id)) {
|
||||
// emit("showCode", props.siteItem);
|
||||
// } else {
|
||||
// uni.showToast({
|
||||
// title: t('site.noPermission'),
|
||||
// icon: 'none'
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// 开门权限只能是用户管理的门店
|
||||
if (storeState.userInfo?.siteList?.includes('*') || storeState.userInfo?.siteList?.includes(item.id)) {
|
||||
emit("showCode", item);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: t('site.noPermission'),
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (item.vrUrl) {
|
||||
uni.navigateTo({
|
||||
url: "/pages/webview/web?url=" + encodeURIComponent(item.vrUrl),
|
||||
});
|
||||
} else {
|
||||
handleBook(item.id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBook = (id) => {
|
||||
// 如果是H5
|
||||
if (isH5 && !isXiaohongshu) {
|
||||
h5GoWx()
|
||||
return;
|
||||
}
|
||||
// 根据ID跳到时昌
|
||||
let targetItem = jumpToSc.find((item) => item.id == id);
|
||||
if (targetItem) {
|
||||
uni.navigateToMiniProgram({
|
||||
appId: "wx3002eda9d707a977",
|
||||
path: `/pages/site/index?id=${targetItem.targetId}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
//如果是迷你仓订仓 正式 就跳转金刚迷你仓
|
||||
if(AppId=="wxb20921dfdd0b94f4" && envVersion === 'release'){
|
||||
uni.navigateToMiniProgram({
|
||||
appId: "wx3c4ab696101d77d1",
|
||||
path: `/pages/site/index?id=${id}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/site/index?id=${id}&name=${props.siteItem.name}`,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.site-detail {
|
||||
width: 750rpx;
|
||||
min-height: 258rpx;
|
||||
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(0, 0, 0, 0.14);
|
||||
background-color: #fff;
|
||||
margin-top: 4rpx;
|
||||
padding: 24rpx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&>.shop-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 16rpx;
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
&>.shop-imageBig{
|
||||
height: 250rpx;
|
||||
}
|
||||
|
||||
&>.shop-introduce {
|
||||
width: 460rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
&>.shop-introduce-title {
|
||||
width: 100%;
|
||||
height: 54rpx;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
&>text {
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
&>.shop-introduce-hot {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
margin-left: 10rpx;
|
||||
background-color: #ff0000;
|
||||
text-align: center;
|
||||
font-size: 16rpx;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.navi-text {
|
||||
color: #616e78;
|
||||
opacity: 0.8;
|
||||
font-size: var(--f22);
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
&>.shop-region {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-size: var(--f21);
|
||||
margin: 10rpx 0;
|
||||
|
||||
.left-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.text-wrap {
|
||||
display: flex;
|
||||
|
||||
.text {
|
||||
margin: 0 8rpx;
|
||||
color: #616e78;
|
||||
font-size: var(--f22);
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navi-text {
|
||||
font-size: var(--f22);
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
&>.shop-address {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: var(--f21);
|
||||
}
|
||||
|
||||
&>.shop-booknow {
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
background-color: var(--main-color);
|
||||
color: var(--text-color);
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
&>.isFiveGoatStoresBtn{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--left-linear2);
|
||||
margin-top: 20rpx;
|
||||
text-align: center;
|
||||
border-radius: 8rpx;
|
||||
.isFiveGoatStoresBtn-icon{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.rotate{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isFiveGoatStoresSiteDetail{
|
||||
min-height: 150rpx;
|
||||
margin: 10rpx 0;
|
||||
// align-items: flex-start;
|
||||
.shop-image {
|
||||
width: 200rpx;
|
||||
height: 160rpx;
|
||||
object-fit: contain;
|
||||
}
|
||||
.shop-introduce-title{
|
||||
text {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.closeList{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
</style>
|
||||
110
components/textEllipsis.vue
Normal file
110
components/textEllipsis.vue
Normal file
@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<view class="text-wrap" @longpress="handleLongTap">
|
||||
<uv-icon name="map1" custom-prefix="custom-icon" size="12" color="#616e78"></uv-icon>
|
||||
<view class="inner-text" ref="innerText" :class="{ over: state.over, show: state.showDropdown }" @click.stop.prevent="handleClick">
|
||||
<view class="arrow">
|
||||
<uv-icon :name="state.over ? 'arrow-down' : 'arrow-up'" size="8" color="#616e78"></uv-icon>
|
||||
</view>
|
||||
<view class="text">{{ props.address }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
const props = defineProps(["address"]);
|
||||
const innerText = ref(null);
|
||||
const state = reactive({
|
||||
over: false,
|
||||
showDropdown: false
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef MP-WEIXIN
|
||||
const query = uni.createSelectorQuery().in(instance.proxy);
|
||||
query
|
||||
.select(".inner-text")
|
||||
.boundingClientRect((data) => {
|
||||
let height = data.height;
|
||||
if (height > 18) {
|
||||
state.over = true;
|
||||
state.showDropdown = true;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
let height = innerText.value?.$el?.offsetHeight;
|
||||
|
||||
if (height > 18) {
|
||||
state.over = true;
|
||||
state.showDropdown = true;
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
if (!state.showDropdown) return;
|
||||
state.over = !state.over;
|
||||
}
|
||||
|
||||
const handleLongTap = () => {
|
||||
uni.setClipboardData({
|
||||
data: props.address,
|
||||
showToast: false,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: t("toast.copy"),
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.text-wrap {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.inner-text {
|
||||
position: relative;
|
||||
width: calc(100% - 20rpx);
|
||||
.arrow {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: -20rpx;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-left: 4px;
|
||||
color: #616e78;
|
||||
font-size: var(--f22);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.over {
|
||||
.text {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.show {
|
||||
.arrow {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
212
components/updatePopup.vue
Normal file
212
components/updatePopup.vue
Normal file
@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<uv-popup
|
||||
ref="popup"
|
||||
class="update-popup"
|
||||
:class="[`${themeInfo.theme}-theme`]"
|
||||
custom-style="width: 90%; border-radius: 16rpx;"
|
||||
>
|
||||
<view class="update-wrap">
|
||||
<view class="auth-btn" @click="wxGetUserProfile">{{ $t("login.wxLogin") }}</view>
|
||||
</view>
|
||||
</uv-popup>
|
||||
|
||||
<uv-popup
|
||||
ref="phonePopup"
|
||||
customStyle="width: 80%; height: 400rpx; padding:20rpx 0; border-radius: 32rpx; display: flex; flex-direction: column; justify-content: center; align-items: center;"
|
||||
@change="popupChange"
|
||||
>
|
||||
<text>{{ $t("common.bindPhone") }}</text>
|
||||
<text style="padding: 0 40rpx; margin-top: 20rpx; text-align: center;"> {{ $t("common.bindPhoneAfter") }}</text>
|
||||
<button
|
||||
style="width: 80%; margin-top: 20px; background: #5BBC6B; color: #FFFFFF; line-height: 80rpx;"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
>
|
||||
{{ $t("common.QuickBind") }}
|
||||
</button>
|
||||
</uv-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { projectInfo } from "@/config/index.js";
|
||||
const { themeInfo, getUserInfo, storeState } = useMainStore();
|
||||
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
const getApi = useLoginApi();
|
||||
|
||||
const popup = ref(null);
|
||||
const phonePopup = ref(null);
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const open = () => {
|
||||
popup.value.open();
|
||||
};
|
||||
defineExpose({ open });
|
||||
|
||||
const code = ref("");
|
||||
const encryptedData = ref("");
|
||||
const iv = ref("");
|
||||
const wxLogin = () => {
|
||||
// 微信 登陆授权 code
|
||||
return new Promise(function (reslove, reject) {
|
||||
wx.login({
|
||||
success(res) {
|
||||
code.value = res.code;
|
||||
reslove(res.code);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
console.error("wx.login调用失败:", err);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
const AuthorizedLogin = (data) => {
|
||||
uni.showLoading();
|
||||
getApi.AuthorizedLogin(data).then(async (res) => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
storeState.token = res.data.token;
|
||||
uni.setStorageSync("token", res.data.token);
|
||||
uni.setStorageSync("openId", res.data.openId);
|
||||
uni.removeStorage({
|
||||
key:'Pre_ID'
|
||||
})
|
||||
uni.removeStorage({
|
||||
key:'mediatorId'
|
||||
})
|
||||
const { data: userInfo } = await getUserInfo();
|
||||
if (!userInfo.phone) {
|
||||
phonePopup.value.open();
|
||||
}
|
||||
popup.value.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
const wxGetUserProfile = async () => {
|
||||
uni.showLoading();
|
||||
wx.getUserProfile({
|
||||
desc: "用于完善会员资料", // 获取用户信息的提示语
|
||||
success: async (res) => {
|
||||
encryptedData.value = res.encryptedData;
|
||||
iv.value = res.iv;
|
||||
// 微信 登陆授权 code
|
||||
uni.checkSession({
|
||||
success: (res) => {
|
||||
// 发起网络请求
|
||||
AuthorizedLogin({
|
||||
code: code.value,
|
||||
encryptedData: encryptedData.value,
|
||||
iv: iv.value,
|
||||
openId: uni.getStorageSync("openId"),
|
||||
Pre_ID: uni.getStorageSync("Pre_ID"),
|
||||
mediatorId: uni.getStorageSync('mediatorId')
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
wxLogin().then((res) => {
|
||||
AuthorizedLogin({
|
||||
code: code.value,
|
||||
encryptedData: encryptedData.value,
|
||||
iv: iv.value,
|
||||
openId: uni.getStorageSync("openId"),
|
||||
Pre_ID: uni.getStorageSync("Pre_ID"),
|
||||
mediatorId: uni.getStorageSync('mediatorId')
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
console.error("获取用户信息失败:", err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const getPhoneNumber = async (e) => {
|
||||
uni.showLoading();
|
||||
if (e.detail.code) {
|
||||
getApi.GetPhoneNumber({code:e.detail.code}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
uni.hideLoading();
|
||||
phonePopup.value.close();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "获取手机号失败",
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
if (uni.getAppBaseInfo().hostName === "WeChat") {
|
||||
wxLogin();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.update-wrap {
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
|
||||
.title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 120rpx;
|
||||
background: #f7f7f7;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
position: relative;
|
||||
color: #242e42;
|
||||
z-index: 2;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: -10px;
|
||||
width: calc(100% + 20px);
|
||||
height: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--main-color);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50rpx 40rpx 10rpx;
|
||||
|
||||
.text {
|
||||
color: #292927;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-btn {
|
||||
margin: 0 40rpx 60rpx;
|
||||
padding: 20rpx 0;
|
||||
text-align: center;
|
||||
border-radius: 10rpx;
|
||||
letter-spacing: 2px;
|
||||
background: var(--main-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
71
components/uv-tabsSelf/props.js
Normal file
71
components/uv-tabsSelf/props.js
Normal file
@ -0,0 +1,71 @@
|
||||
export default {
|
||||
props: {
|
||||
// 滑块的移动过渡时间,单位ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
// tabs标签数组
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 滑块颜色
|
||||
lineColor: {
|
||||
type: String,
|
||||
default: '#3c9cff'
|
||||
},
|
||||
// 菜单选择中时的样式
|
||||
activeStyle: {
|
||||
type: [String, Object],
|
||||
default: () => ({
|
||||
color: '#303133'
|
||||
})
|
||||
},
|
||||
// 菜单非选中时的样式
|
||||
inactiveStyle: {
|
||||
type: [String, Object],
|
||||
default: () => ({
|
||||
color: '#606266'
|
||||
})
|
||||
},
|
||||
// 滑块长度
|
||||
lineWidth: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 滑块高度
|
||||
lineHeight: {
|
||||
type: [String, Number],
|
||||
default: 3
|
||||
},
|
||||
// 滑块背景显示大小,当滑块背景设置为图片时使用
|
||||
lineBgSize: {
|
||||
type: String,
|
||||
default: 'cover'
|
||||
},
|
||||
// 菜单item的样式
|
||||
itemStyle: {
|
||||
type: [String, Object],
|
||||
default: () => ({
|
||||
height: '44px'
|
||||
})
|
||||
},
|
||||
// 菜单是否可滚动
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当前选中标签的索引
|
||||
current: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 默认读取的键名
|
||||
keyName: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
...uni.$uv?.props?.tabs
|
||||
}
|
||||
}
|
||||
412
components/uv-tabsSelf/uv-tabs.vue
Normal file
412
components/uv-tabsSelf/uv-tabs.vue
Normal file
@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<view class="uv-tabs" :style="[$uv.addStyle(customStyle)]">
|
||||
<view class="uv-tabs__wrapper">
|
||||
<slot name="left" />
|
||||
<view class="uv-tabs__wrapper__scroll-view-wrapper">
|
||||
<scroll-view
|
||||
:scroll-x="scrollable"
|
||||
:scroll-left="scrollLeft"
|
||||
scroll-with-animation
|
||||
class="uv-tabs__wrapper__scroll-view"
|
||||
:show-scrollbar="false"
|
||||
ref="uv-tabs__wrapper__scroll-view"
|
||||
>
|
||||
<view
|
||||
class="uv-tabs__wrapper__nav"
|
||||
ref="uv-tabs__wrapper__nav"
|
||||
:style="{
|
||||
flex: scrollable ? '' : 1
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="uv-tabs__wrapper__nav__item"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
@tap="clickHandler(item, index)"
|
||||
:ref="`uv-tabs__wrapper__nav__item-${index}`"
|
||||
:style="[{flex: scrollable ? '' : 1},$uv.addStyle(itemStyle)]"
|
||||
:class="[`uv-tabs__wrapper__nav__item-${index}`, item.disabled && 'uv-tabs__wrapper__nav__item--disabled']"
|
||||
>
|
||||
<!-- <text
|
||||
:class="[item.disabled && 'uv-tabs__wrapper__nav__item__text--disabled']"
|
||||
class="uv-tabs__wrapper__nav__item__text"
|
||||
:style="[textStyle(index)]"
|
||||
>{{ item[keyName] }}</text> -->
|
||||
<view class="slfeTabs">
|
||||
<view class="name">{{ item[keyName] }}</view>
|
||||
<view :style="[textStyle(index)]" class="line"></view>
|
||||
<view class="areaRange">{{ item['areaRange'] }}</view>
|
||||
</view>
|
||||
|
||||
<uv-badge
|
||||
:show="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))"
|
||||
:isDot="item.badge && item.badge.isDot || propsBadge.isDot"
|
||||
:value="item.badge && item.badge.value || propsBadge.value"
|
||||
:max="item.badge && item.badge.max || propsBadge.max"
|
||||
:type="item.badge && item.badge.type || propsBadge.type"
|
||||
:showZero="item.badge && item.badge.showZero || propsBadge.showZero"
|
||||
:bgColor="item.badge && item.badge.bgColor || propsBadge.bgColor"
|
||||
:color="item.badge && item.badge.color || propsBadge.color"
|
||||
:shape="item.badge && item.badge.shape || propsBadge.shape"
|
||||
:numberType="item.badge && item.badge.numberType || propsBadge.numberType"
|
||||
:inverted="item.badge && item.badge.inverted || propsBadge.inverted"
|
||||
customStyle="margin-left: 4px;"
|
||||
></uv-badge>
|
||||
</view>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view
|
||||
class="uv-tabs__wrapper__nav__line"
|
||||
ref="uv-tabs__wrapper__nav__line"
|
||||
:style="[{
|
||||
width: $uv.addUnit(lineWidth),
|
||||
height: firstTime?0:$uv.addUnit(lineHeight),
|
||||
background: lineColor,
|
||||
backgroundSize: lineBgSize
|
||||
}]"
|
||||
>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view
|
||||
class="uv-tabs__wrapper__nav__line"
|
||||
ref="uv-tabs__wrapper__nav__line"
|
||||
:style="[{
|
||||
width: $uv.addUnit(lineWidth),
|
||||
transform: `translate(${lineOffsetLeft}px)`,
|
||||
transitionDuration: `${firstTime ? 0 : duration}ms`,
|
||||
height: firstTime?0:$uv.addUnit(lineHeight),
|
||||
background: lineColor,
|
||||
backgroundSize: lineBgSize,
|
||||
}]"
|
||||
>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<slot name="right" />
|
||||
</view>
|
||||
</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 uvBadgeProps from '@/uni_modules/uv-badge/components/uv-badge/props.js'
|
||||
// #ifdef APP-NVUE
|
||||
const animation = uni.requireNativePlugin('animation')
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
import props from './props.js';
|
||||
/**
|
||||
* Tabs 标签
|
||||
* @description tabs标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。 该组件的一个特点是配置为滚动模式时,激活的tab会自动移动到组件的中间位置。
|
||||
* @tutorial https://www.uvui.cn/components/tabs.html
|
||||
* @property {Array} list 标签数组,元素为对象,如[{name: '推荐'}]
|
||||
* @property {String | Number} duration 滑块移动一次所需的时间,单位秒(默认 200 )
|
||||
* @property {String | Object} activeStyle 菜单选择中时的样式(默认{ color: '#303133' })
|
||||
* @property {String | Object} inactiveStyle 菜单非选择中时的样式(默认{ color: '#606266' })
|
||||
* @property {String | Number} lineWidth 滑块长度(默认 20)
|
||||
* @property {String | Number} lineHeight 滑块高度(默认 3)
|
||||
* @property {String} lineColor 滑块颜色(默认:'#3c9cff')
|
||||
* @property {String} lineBgSize 滑块背景显示大小,当滑块背景设置为图片时使用(默认 cover)
|
||||
* @property {String | Number} itemStyle 菜单item的样式(默认 { height: '44px' })
|
||||
* @property {String} scrollable 菜单是否可滚动,选项很少的时候设置为false整个tabs自动居中显示(默认:true)
|
||||
* @property {String | Number} current 当前选中标签的索引(默认 0 )
|
||||
* @property {String} keyName 从list元素对象中读取的键名(默认 'name' )
|
||||
* @property {String | Number} swierWidth swiper的宽度(默认 '750rpx' )
|
||||
* @property {String | Object} customStyle 自定义外部样式
|
||||
*
|
||||
* @event {Function(index)} change 标签改变时触发 index: 点击了第几个tab,索引从0开始
|
||||
* @event {Function(index)} click 点击标签时触发 index: 点击了第几个tab,索引从0开始
|
||||
* @example <uv-tabs :list="list" :is-scroll="false" :current="current" @change="change"></uv-tabs>
|
||||
*/
|
||||
export default {
|
||||
name: 'uv-tabsSelf',
|
||||
emits: ['click','change'],
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
firstTime: true,
|
||||
scrollLeft: 0,
|
||||
scrollViewWidth: 0,
|
||||
lineOffsetLeft: 0,
|
||||
tabsRect: {
|
||||
left: 0
|
||||
},
|
||||
innerCurrent: 0,
|
||||
moving: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current: {
|
||||
immediate: true,
|
||||
handler (newValue, oldValue) {
|
||||
// 内外部值不相等时,才尝试移动滑块
|
||||
if (newValue !== this.innerCurrent) {
|
||||
this.innerCurrent = newValue
|
||||
this.$nextTick(() => {
|
||||
this.resize()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
// list变化时,重新渲染list各项信息
|
||||
list() {
|
||||
this.$nextTick(() => {
|
||||
this.resize()
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
textStyle() {
|
||||
return index => {
|
||||
const style = {}
|
||||
// 取当期是否激活的样式
|
||||
const customeStyle = index == this.innerCurrent ? this.$uv.addStyle(this.activeStyle) : this.$uv
|
||||
.addStyle(
|
||||
this.inactiveStyle)
|
||||
// 如果当前菜单被禁用,则加上对应颜色,需要在此做处理,是因为nvue下,无法在style样式中通过!import覆盖标签的内联样式
|
||||
if (this.list[index].disabled) {
|
||||
style.color = '#c8c9cc'
|
||||
}
|
||||
return this.$uv.deepMerge(customeStyle, style)
|
||||
}
|
||||
},
|
||||
propsBadge() {
|
||||
return uvBadgeProps
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
setLineLeft() {
|
||||
const tabItem = this.list[this.innerCurrent];
|
||||
if (!tabItem) {
|
||||
return;
|
||||
}
|
||||
// 获取滑块该移动的位置
|
||||
let lineOffsetLeft = this.list
|
||||
.slice(0, this.innerCurrent)
|
||||
.reduce((total, curr) => total + curr.rect.width, 0);
|
||||
// 获取下划线的数值px表示法
|
||||
let lineWidth = this.$uv.getPx(this.lineWidth);
|
||||
// 如果传的值未带单位+设置了全局单位,则带上单位计算,这样才没有误差
|
||||
if (this.$uv.test.number(this.lineWidth) && this.$uv.unit) {
|
||||
lineWidth = this.$uv.getPx(`${this.lineWidth}${this.$uv.unit}`);
|
||||
}
|
||||
this.lineOffsetLeft = lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2
|
||||
// #ifdef APP-NVUE
|
||||
// 第一次移动滑块,无需过渡时间
|
||||
this.animation(this.lineOffsetLeft, this.firstTime ? 0 : parseInt(this.duration))
|
||||
// #endif
|
||||
|
||||
// 如果是第一次执行此方法,让滑块在初始化时,瞬间滑动到第一个tab item的中间
|
||||
// 这里需要一个定时器,因为在非nvue下,是直接通过style绑定过渡时间,需要等其过渡完成后,再设置为false(非第一次移动滑块)
|
||||
if (this.firstTime) {
|
||||
setTimeout(() => {
|
||||
this.firstTime = false
|
||||
}, 20);
|
||||
}
|
||||
},
|
||||
// nvue下设置滑块的位置
|
||||
animation(x, duration = 0) {
|
||||
// #ifdef APP-NVUE
|
||||
const ref = this.$refs['uv-tabs__wrapper__nav__line']
|
||||
animation.transition(ref, {
|
||||
styles: {
|
||||
transform: `translateX(${x}px)`
|
||||
},
|
||||
duration
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 点击某一个标签
|
||||
clickHandler(item, index) {
|
||||
// 因为标签可能为disabled状态,所以click是一定会发出的,但是change事件是需要可用的状态才发出
|
||||
this.$emit('click', {
|
||||
...item,
|
||||
index
|
||||
})
|
||||
// 如果disabled状态,返回
|
||||
if (item.disabled) return
|
||||
if(this.innerCurrent != index) {
|
||||
this.$emit('change', {
|
||||
...item,
|
||||
index
|
||||
})
|
||||
}
|
||||
this.innerCurrent = index
|
||||
// #ifndef APP-NVUE
|
||||
this.$nextTick(()=>{
|
||||
this.resize()
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.$nextTick(()=>{
|
||||
// nvue模式下再给点延时,确保万无一失
|
||||
this.$uv.sleep(30).then(res=>{
|
||||
this.resize()
|
||||
});
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
init() {
|
||||
this.$uv.sleep().then(() => {
|
||||
this.resize()
|
||||
})
|
||||
},
|
||||
setScrollLeft() {
|
||||
// 当前活动tab的布局信息,有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
|
||||
const tabRect = this.list[this.innerCurrent]
|
||||
// 累加得到当前item到左边的距离
|
||||
const offsetLeft = this.list
|
||||
.slice(0, this.innerCurrent)
|
||||
.reduce((total, curr) => {
|
||||
return total + curr.rect.width
|
||||
}, 0)
|
||||
// 此处为屏幕宽度
|
||||
const windowWidth = this.$uv.sys().windowWidth
|
||||
// 将活动的tabs-item移动到屏幕正中间,实际上是对scroll-view的移动
|
||||
let scrollLeft = offsetLeft - (this.tabsRect.width - tabRect.rect.width) / 2 - (windowWidth - this.tabsRect
|
||||
.right) / 2 + this.tabsRect.left / 2
|
||||
// 这里做一个限制,限制scrollLeft的最大值为整个scroll-view宽度减去tabs组件的宽度
|
||||
scrollLeft = Math.min(scrollLeft, this.scrollViewWidth - this.tabsRect.width)
|
||||
this.scrollLeft = Math.max(0, scrollLeft)
|
||||
},
|
||||
// 获取所有标签的尺寸
|
||||
resize() {
|
||||
// 如果不存在list,则不处理
|
||||
if(this.list.length === 0) {
|
||||
return
|
||||
}
|
||||
Promise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {
|
||||
this.tabsRect = tabsRect
|
||||
this.scrollViewWidth = 0
|
||||
itemRect.map((item, index) => {
|
||||
// 计算scroll-view的宽度,这里
|
||||
this.scrollViewWidth += item.width
|
||||
// 另外计算每一个item的中心点X轴坐标
|
||||
this.list[index].rect = item
|
||||
})
|
||||
// 获取了tabs的尺寸之后,设置滑块的位置
|
||||
this.setLineLeft()
|
||||
this.setScrollLeft()
|
||||
})
|
||||
},
|
||||
// 获取导航菜单的尺寸
|
||||
getTabsRect() {
|
||||
return new Promise(resolve => {
|
||||
this.queryRect('uv-tabs__wrapper__scroll-view').then(size => resolve(size))
|
||||
})
|
||||
},
|
||||
// 获取所有标签的尺寸
|
||||
getAllItemRect() {
|
||||
return new Promise(resolve => {
|
||||
const promiseAllArr = this.list.map((item, index) => this.queryRect(
|
||||
`uv-tabs__wrapper__nav__item-${index}`, true))
|
||||
Promise.all(promiseAllArr).then(sizes => resolve(sizes))
|
||||
})
|
||||
},
|
||||
// 获取各个标签的尺寸
|
||||
queryRect(el, item) {
|
||||
// #ifndef APP-NVUE
|
||||
// $uvGetRect为uni-ui自带的节点查询简化方法,详见文档介绍:https://www.uvui.cn/js/getRect.html
|
||||
// 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
|
||||
return new Promise(resolve => {
|
||||
this.$uvGetRect(`.${el}`).then(size => {
|
||||
resolve(size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// nvue下,使用dom模块查询元素高度
|
||||
// 返回一个promise,让调用此方法的主体能使用then回调
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => {
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
// #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-tabs {
|
||||
|
||||
.slfeTabs{
|
||||
margin: 16rpx 8rpx;
|
||||
view{
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.line{
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
margin: 10rpx 0;
|
||||
border-radius: 4px;
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
|
||||
&__scroll-view-wrapper {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: auto hidden;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__scroll-view {
|
||||
@include flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__nav {
|
||||
@include flex;
|
||||
position: relative;
|
||||
|
||||
&__item {
|
||||
padding: 0 11px;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--disabled {
|
||||
/* #ifndef APP-NVUE */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 15px;
|
||||
color: $uv-content-color;
|
||||
|
||||
&--disabled {
|
||||
color: $uv-disabled-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__line {
|
||||
height: 3px;
|
||||
background: $uv-primary;
|
||||
width: 30px;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
border-radius: 100px;
|
||||
transition-property: transform;
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
51
components/wxNavbar.vue
Normal file
51
components/wxNavbar.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<!-- #ifdef MP-WEIXIN || MP-XHS -->
|
||||
<view class="wxNavbar" :style="{ height: `${state.navHeight}px` }">
|
||||
<view v-if="state.navHeight" class="title" :style="{ 'margin-top': `${state.statusBarHeight}px` }">{{ props.title }}</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// #ifdef MP-WEIXIN || MP-XHS
|
||||
import { reactive, onMounted } from "vue";
|
||||
import { navbarHeightAndStatusBarHeight } from "@/utils/common.js";
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "EliteSys"
|
||||
}
|
||||
});
|
||||
|
||||
let state = reactive({
|
||||
navHeight: 0,
|
||||
statusBarHeight: 0,
|
||||
});
|
||||
onMounted(() => {
|
||||
const heightData = navbarHeightAndStatusBarHeight();
|
||||
state.navHeight = heightData.navbarHeight;
|
||||
state.statusBarHeight = heightData.statusBarHeight;
|
||||
});
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wxNavbar {
|
||||
z-index: 999999;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-color);
|
||||
background: linear-gradient(to left, var(--left-linear), var(--right-linear));
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
bottom: -2px;
|
||||
background: linear-gradient(to left, var(--left-linear), var(--right-linear));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
61
config/index.js
Normal file
61
config/index.js
Normal file
@ -0,0 +1,61 @@
|
||||
export const isKingKong = true;
|
||||
export const isShichang = !isKingKong; // 是否是时昌小程序环境
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
export const isWeChatMiniProgram = systemInfo.hostName === 'WeChat'; // 是否是微信小程序环境
|
||||
export const isXiaohongshu = systemInfo.hostName === 'xhs'; // 是否是小红书环境
|
||||
const isDev = process.env.NODE_ENV === 'development' // 是否是开发环境
|
||||
export const OFFICIAL_URL = 'https://elitesys.kingkongcang.com/Mini' // 跳转小程序地址
|
||||
// 如果是小程序 就要写全域名, 如果是h5 自动获取域名 兼容测试环境(http://8.134.73.118:10000/api 不用进行跨域操作) 跟正式环境 自动获取域名补充
|
||||
export const devURL = "https://uat.kingkongcang.com/adminApi" // 开发环境地址 'http://118.145.200.78:10000/api http://localhost:5182/api' "https://dev.kingkongcang.com/adminApi"
|
||||
export const testURL = isKingKong?"https://uat.kingkongcang.com/adminApi":"https://www.scstorage.net/adminApi" // 测试环境地址
|
||||
export const prodURL = isKingKong?"https://elitesys.kingkongcang.com/adminApi":"https://www.scstorage.net/adminApi" // 正式环境地址
|
||||
export let IsApp = false
|
||||
// #ifdef APP-PLUS
|
||||
IsApp = true
|
||||
// #endif
|
||||
export let isH5 = false// h5环境下 下单会跳转到小程序
|
||||
export let RElEASE_DATE = '2026/02/26'
|
||||
let accountInfo = {}
|
||||
// #ifdef MP-WEIXIN || MP-XHS
|
||||
accountInfo = uni.getAccountInfoSync();
|
||||
isH5 = false
|
||||
// #endif
|
||||
export const AppId = accountInfo?.miniProgram?.appId
|
||||
export const envVersion = accountInfo?.miniProgram?.envVersion
|
||||
const returnBaseUrl = () => {
|
||||
if (isWeChatMiniProgram || isXiaohongshu || IsApp) {
|
||||
if (envVersion === 'develop') {
|
||||
return devURL;
|
||||
} else if (envVersion === 'trial') {
|
||||
return testURL;
|
||||
} else if (envVersion === 'release') {
|
||||
return prodURL;
|
||||
} else {
|
||||
return prodURL;
|
||||
}
|
||||
// 预留 防止出错
|
||||
return isDev ? testURL: prodURL; // https://elitesys.kingkongcang.com/adminApi // 测试环境 https://uat.kingkongcang.com/adminApi 开发环境 https://dev.kingkongcang.com/adminApi http://localhost:5182/
|
||||
|
||||
} else {
|
||||
return isDev
|
||||
? `${window.location.origin}/api`
|
||||
: `${window.location.origin}/adminApi`;
|
||||
}
|
||||
};
|
||||
export const baseUrl = returnBaseUrl();
|
||||
export const baseImageUrl = isKingKong? 'https://elitesoss.oss-cn-guangzhou.aliyuncs.com/':'https://scstorage.oss-cn-guangzhou.aliyuncs.com/'
|
||||
|
||||
export const currency = isKingKong ? '¥' : '¥';
|
||||
// 小程序默认金刚配色先
|
||||
export const theme = ((isWeChatMiniProgram || isXiaohongshu) && isKingKong) ? 'golden' : 'default'; // 默认主题 - "default" 金刚色主题 - "golden"
|
||||
export const projectInfo = {
|
||||
name: isKingKong ? '金刚迷你仓' : '时昌迷你仓', // 名字:金刚迷你仓、时昌迷你仓
|
||||
miniName: isKingKong ? '金刚迷你仓' : '时昌迷你仓', // 小程序名字:迷你仓订仓、时昌迷你仓
|
||||
phone: isKingKong ? '400-818-1813' : '15323894878',
|
||||
callPhone: isKingKong ? '4008181813' : '15323894878'
|
||||
};
|
||||
export const watermarkURL= '?x-oss-process=image/watermark,text_5Zu-54mH6K6k6K-B5LiT55So,t_80,g_center,rotate_45,color_FF0000,size_100'
|
||||
// 时昌微信二维码
|
||||
export const scWechatImg = baseImageUrl + "d3572937-4a9c-410e-992b-c19ff03e9ada.jpg"
|
||||
|
||||
export const setOrderDays = isKingKong ? 30 : 7; // 金刚小程序默认7天,其他默认30天
|
||||
43
hooks/index.js
Normal file
43
hooks/index.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { ref,onBeforeMount} from "vue";
|
||||
/**
|
||||
* 倒计时
|
||||
* @param {Number} second 倒计时秒数
|
||||
* @return {Number} count 倒计时秒数
|
||||
* @return {Function} countDown 倒计时函数
|
||||
* @example
|
||||
* const { count, countDown } = useCountDown()
|
||||
* countDown(60)
|
||||
* <div>{{ count }}</div>
|
||||
*/
|
||||
|
||||
export function useCountDown() {
|
||||
const count = ref(0)
|
||||
const timer = ref(null);
|
||||
const countDown = (second = 60, ck = () => { }) => {
|
||||
if (count.value === 0 && timer.value === null) {
|
||||
ck();
|
||||
count.value = second;
|
||||
timer.value = setInterval(() => {
|
||||
count.value--
|
||||
if (count.value === 0) {
|
||||
clearInterval(timer.value)
|
||||
timer.value = null
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
const cancelCout=()=>{
|
||||
clearInterval(timer.value)
|
||||
timer.value = null
|
||||
count.value = 0
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
timer.value && clearInterval(timer.value)
|
||||
});
|
||||
|
||||
return {
|
||||
count,
|
||||
countDown,
|
||||
cancelCout
|
||||
};
|
||||
}
|
||||
68
hooks/useCountDown.js
Normal file
68
hooks/useCountDown.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export function useCountDown(startTime, endTime, onFinished) {
|
||||
if (!startTime || !endTime) {
|
||||
throw new Error('startTime and endTime are required')
|
||||
}
|
||||
const remaining = ref(0)
|
||||
const formatted = ref('00:00:00')
|
||||
let timer = null
|
||||
|
||||
const toTimestamp = (t) => dayjs(t).valueOf()
|
||||
|
||||
let startTs = toTimestamp(startTime)
|
||||
let endTs = toTimestamp(endTime)
|
||||
|
||||
const calc = () => {
|
||||
const now = dayjs().valueOf()
|
||||
if (now < startTs) {
|
||||
remaining.value = startTs - now
|
||||
} else if (now >= startTs && now < endTs) {
|
||||
remaining.value = endTs - now
|
||||
} else {
|
||||
remaining.value = 0
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
onFinished && onFinished()
|
||||
}
|
||||
format()
|
||||
}
|
||||
|
||||
const format = () => {
|
||||
let left = remaining.value
|
||||
let totalSeconds = Math.floor(left / 1000)
|
||||
const days = Math.floor(totalSeconds / 86400)
|
||||
totalSeconds %= 86400
|
||||
const h = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
|
||||
const m = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0')
|
||||
const s = String(totalSeconds % 60).padStart(2, '0')
|
||||
formatted.value = days > 0 ? `${days}天 ${h}:${m}:${s}` : `${h}:${m}:${s}`
|
||||
}
|
||||
|
||||
const start = () => {
|
||||
// 如果已有计时器,先清掉
|
||||
if (timer) clearInterval(timer)
|
||||
startTs = toTimestamp(startTime)
|
||||
endTs = toTimestamp(endTime)
|
||||
calc()
|
||||
timer = setInterval(calc, 1000)
|
||||
}
|
||||
|
||||
const reset = (newStart, newEnd) => {
|
||||
startTime = newStart
|
||||
endTime = newEnd
|
||||
start()
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
})
|
||||
|
||||
return {
|
||||
formatted,
|
||||
remaining,
|
||||
start,
|
||||
reset
|
||||
}
|
||||
}
|
||||
54
hooks/useLocation.js
Normal file
54
hooks/useLocation.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { reactive } from "vue";
|
||||
import { useMainStore } from "@/store/index";
|
||||
|
||||
export function useLocation() {
|
||||
const { storeState, setLocation } = useMainStore();
|
||||
|
||||
const locationState = reactive({
|
||||
showGetLocation: false,
|
||||
latitude: 0,
|
||||
longitude: 0
|
||||
});
|
||||
|
||||
const getLocation = () => {
|
||||
return new Promise((resolve) => {
|
||||
// 1️⃣ 优先使用 store 里的定位
|
||||
if (storeState.location?.latitude && storeState.location?.longitude) {
|
||||
locationState.latitude = storeState.location.latitude;
|
||||
locationState.longitude = storeState.location.longitude;
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2️⃣ 本地已经有定位
|
||||
if (locationState.latitude && locationState.longitude) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3️⃣ 没有定位才请求
|
||||
SFUIP.getLocation().then(res => {
|
||||
if (!res.success) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { latitude, longitude } = res.data || {};
|
||||
|
||||
if (latitude && longitude) {
|
||||
locationState.latitude = latitude;
|
||||
locationState.longitude = longitude;
|
||||
locationState.showGetLocation = false;
|
||||
|
||||
setLocation({ latitude, longitude });
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
return { locationState, getLocation };
|
||||
}
|
||||
50
index.html
Normal file
50
index.html
Normal file
@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" /> -->
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<script src="https://ucmp-static.sf-express.com/assets/sdks/microservice-1.0.4.min.js"></script>
|
||||
<script>
|
||||
const sf = new SFUIP.SfMicroservice('prod')
|
||||
window.sf = sf
|
||||
</script>
|
||||
<script>
|
||||
// // 获取当前路径名
|
||||
// const currentPath = window.location.pathname;
|
||||
// var currentDomain = window.location.origin;
|
||||
// // 定义跳转逻辑
|
||||
// function checkAndRedirect() {
|
||||
// const screenWidth =document.documentElement.clientWidth;
|
||||
// if (screenWidth >= 900 && !(currentPath.indexOf('/h5/index.html') ==-1)) {
|
||||
// window.location.href =currentDomain+ '/';
|
||||
// } else if (screenWidth < 900 && (currentPath.indexOf('/h5/index.html') ==-1)) {
|
||||
// window.location.href =currentDomain+ '/h5/index.html';
|
||||
// }
|
||||
// }
|
||||
|
||||
// // // 初始检查
|
||||
// checkAndRedirect();
|
||||
|
||||
// // 监听屏幕宽度变化
|
||||
// window.addEventListener('resize', () => {
|
||||
// checkAndRedirect();
|
||||
// });
|
||||
</script>
|
||||
<title>金刚迷你仓</title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
502
locale/en.json
Normal file
502
locale/en.json
Normal file
@ -0,0 +1,502 @@
|
||||
{
|
||||
"common.confirm": "Confirm",
|
||||
"common.cancel": "Cancel",
|
||||
"common.title": "Tips",
|
||||
"common.noData": "No Data",
|
||||
"common.delete": "Delete",
|
||||
"common.update": "Update",
|
||||
"common.password": "password",
|
||||
"common.close": "Close",
|
||||
"common.Skiptoday": "Skip today",
|
||||
"common.payableTime": "Payable Time",
|
||||
"common.AuthorizationOrder": "Authorization Order",
|
||||
"common.cancelOrder": "Cancel Order",
|
||||
"common.cancelOrderTips": "Are you sure you want to cancel the current order?",
|
||||
"common.unpaidOrderTips": "There are unpaid orders, please pay first, or cancel the order before placing a new one!",
|
||||
"common.cantUselocker": "This locker cannot be rented, please re-select!",
|
||||
"common.userName": "User Name",
|
||||
"common.logout": "Log Out",
|
||||
"common.logoutTip": "Confirm logout?",
|
||||
"common.FirstTimeLoginTips": "First-time login will automatically create an account.",
|
||||
"common.more": "More",
|
||||
"common.SpaceSpecsGuide": "Space Specs Guide",
|
||||
"common.Seelegend": "See legend",
|
||||
"common.Rent": "Rent",
|
||||
"common.OriginalPrice": "Original Price",
|
||||
"common.SalePrice": "Sale Price",
|
||||
"common.SwitchStores": "Switch Stores",
|
||||
"common.SwitchRegion": "Switch Region",
|
||||
"common.Countdown": "Countdown",
|
||||
"common.FlashSale": "Flash Sale",
|
||||
"common.FlashSalePrice": "Flash Sale Price",
|
||||
"common.ClickToCheck": "Click To Check",
|
||||
"common.ClickToZoomIn": "Click To Zoom In",
|
||||
"common.tryZooming": "If access recognition fails, try clicking to zoom in",
|
||||
"common.OnSiteAssessment": "On-Site Assessment",
|
||||
"common.ConsultationQuotation": "Consultation & quotation",
|
||||
"common.Requirement": "Requirement",
|
||||
"common.day": "day(s)",
|
||||
"common.reset": "Reset",
|
||||
"common.notRented": "Not rented",
|
||||
"common.rented": "Rented",
|
||||
"common.locked": "Locked",
|
||||
"locale.auto": "System",
|
||||
"locale.en": "English",
|
||||
"locale.zh-hans": "简体中文",
|
||||
"locale.zh-hant": "繁体中文",
|
||||
"locale.ja": "日语",
|
||||
"index.title": "Hello i18n",
|
||||
"index.home": "Home",
|
||||
"index.component": "Component",
|
||||
"index.api": "API",
|
||||
"index.schema": "Schema",
|
||||
"index.demo": "uni-app globalization",
|
||||
"index.demo-description": "Include uni-framework, manifest.json, pages.json, tabbar, Page, Component, API, Schema",
|
||||
"index.detail": "Detail",
|
||||
"index.language": "Language",
|
||||
"index.language-info": "Settings",
|
||||
"index.system-language": "System language",
|
||||
"index.application-language": "Application language",
|
||||
"index.language-change-confirm": "Applying this setting will restart the app",
|
||||
"api.message": "Message",
|
||||
"schema.name": "Name",
|
||||
"schema.add": "Add",
|
||||
"schema.add-success": "Add success",
|
||||
"tabbar.home": "HOME",
|
||||
"tabbar.book": "BOOK",
|
||||
"tabbar.unlock": "UNLOCK",
|
||||
"tabbar.personal": "PERSONAL",
|
||||
"home.select": "SELECT A STORE",
|
||||
"home.reserve": "RESERVE",
|
||||
"home.book": "BOOK NOW",
|
||||
"home.detail": "DETAILS",
|
||||
"home.appointment": "APPOINTMENT",
|
||||
"home.recommend": "RECOMMENDED",
|
||||
"home.morestore": "MORE STORES",
|
||||
"home.navigate": "NAVIGATE",
|
||||
"home.wayfinding": "ROUTE GUIDANCE",
|
||||
"home.travel": "Travel",
|
||||
"home.collection": "Collection",
|
||||
"home.clothes": "Clothes",
|
||||
"home.appliances": "Appliances",
|
||||
"home.goods": "Goods",
|
||||
"home.relocate": "Relocation",
|
||||
"home.wrapper": "Packing",
|
||||
"home.material": "Materials",
|
||||
"home.document": "Document",
|
||||
"home.device": "Equipment",
|
||||
"home.supplies": "Supplies",
|
||||
"home.ecgoods": "E-Commerce",
|
||||
"home.shops": "RETAIL SHOPS",
|
||||
"home.individuals": "INDIVIDUALS",
|
||||
"home.corporates": "CORPORATES",
|
||||
"home.advantage1": "Guarantee",
|
||||
"home.advantage1Info1": "Chain Management",
|
||||
"home.advantage1Info2": "Extensive media coverage",
|
||||
"home.advantage1Info3": "Best Choice",
|
||||
"home.advantage2": "Convenience",
|
||||
"home.advantage2Info1": "24hrs Self-Access",
|
||||
"home.advantage2Info2": "Smart Lock",
|
||||
"home.advantage2Info3": "Flexible Term",
|
||||
"home.advantage3": "Safety",
|
||||
"home.advantage3Info1": "24hrs CCTV",
|
||||
"home.advantage3Info2": "One Door One Lock",
|
||||
"home.advantage3Info3": "Free Insurance",
|
||||
"home.advantage4": "Cleanliness",
|
||||
"home.advantage4Info1": "Environment Control",
|
||||
"home.advantage4Info2": "Fire Safety System",
|
||||
"home.advantage4Info3": "Regular Sterilization",
|
||||
"home.interior": "Interior",
|
||||
"home.serviceHotline": "Customer Service Hotline",
|
||||
"home.quote": "Quote Now",
|
||||
"book.location": "LOCATION",
|
||||
"book.map": "MAP MODE",
|
||||
"book.list": "LIST MODE",
|
||||
"book.city": "City",
|
||||
"book.area": "Area",
|
||||
"book.get": "Find your nearest store",
|
||||
"book.getSite": "You need to turn on the location Settings to show the nearest store",
|
||||
"book.getCode": "Get QR code",
|
||||
"unlock.door": "DOOR",
|
||||
"unlock.lock": "LOCK",
|
||||
"unlock.renew": "RENEW",
|
||||
"unlock.details": "DETAILS",
|
||||
"unlock.moveout": "MOVE OUT",
|
||||
"unlock.cancelPending": "Pending For Move-out Approval",
|
||||
"unlock.outComplete": "Move-out Completed",
|
||||
"unlock.cancel": "Cancel",
|
||||
"unlock.cCancel": "CANCEL",
|
||||
"unlock.cancelout": "Cancel Move-out Request",
|
||||
"unlock.return": "RETURN",
|
||||
"unlock.moveoutReminder": "MOVE OUT REMINDER",
|
||||
"unlock.moveoutTip": "After you finished the termination procedures, your entrance access will be removed. Please make sure your belongings are taken away. Deposit will be returned in 14 working days.",
|
||||
"unlock.uploadTip": "Please take a picture of an empty storage unit and upload it.",
|
||||
"unlock.moveoutSuccess": "Warehouse return request is in process, please patiently wait for staff review!",
|
||||
"unlock.confirmOut": "MOVE OUT",
|
||||
"unlock.evaluate": "EVALUATE",
|
||||
"unlock.disapproval": "DISAPPROVAL",
|
||||
"unlock.disapprovalRemarks": "Disapproval Remarks",
|
||||
"unlock.overdue": "OVERDUE",
|
||||
"unlock.unPaid": "UNPAID",
|
||||
"unlock.order": "MY ORDER",
|
||||
"unlock.login": "Click To Login",
|
||||
"unlock.nodata": "NO DATA",
|
||||
"unlock.auth": "Auth",
|
||||
"unlock.FixedPassword": "Fixed Password",
|
||||
"unlock.AccessControlCardBinding": "Access Control Card Binding",
|
||||
"unlock.LockCardUnbinding": "Lock Card Binding",
|
||||
"unlock.getAuthOrder": "View authorized orders",
|
||||
"unlock.ResetPassword": "Reset Password",
|
||||
"unlock.remoteOpen": "Remote Access",
|
||||
"unlock.remoteOpenLoading": "Door Opening...",
|
||||
"unlock.remoteOpenSuccess": "Access Granted",
|
||||
"unlock.remoteOpenFail": "Access Denied",
|
||||
"unlock.fillInventory": "Please fill in the inventory list before using the warehouse",
|
||||
"unlock.FaceEnrollment": "Enrollment",
|
||||
"unlock.goToPay": "Go to Pay",
|
||||
"unlock.cancelOrder": "Cancel Order",
|
||||
"unlock.Deposit Refund": "Deposit Refund",
|
||||
"unlock.agreement": "Agreement",
|
||||
"bingCard.start": "Start pairing",
|
||||
"bingCard.fail": "The operation failed",
|
||||
"bingCard.Click": "Click [ Start Pairing ]",
|
||||
"bingCard.Pairing": "Pairing",
|
||||
"bingCard.panel": "Place the door card on the dashboard",
|
||||
"bingCard.single": "You will hear a beep sound when card is paired successfully.",
|
||||
"bingCard.close": "After success, you can close this pop-up window manually.",
|
||||
"detail.store": "Store",
|
||||
"detail.unit": "Unit type",
|
||||
"detail.spec": "Ref Spec",
|
||||
"detail.size": "Ref Size",
|
||||
"detail.cSize": "Ref Volume",
|
||||
"detail.cUnit": "UNIT TYPE",
|
||||
"detail.startDate": "START DATE",
|
||||
"detail.lease": "LEASE TERM",
|
||||
"detail.rentalFee": "Rental fee",
|
||||
"detail.cDeposit": "Deposit(1 month)",
|
||||
"detail.cValueAdded": "VALUE-ADDED",
|
||||
"detail.nodemand": "No Demand",
|
||||
"detail.coupon": "COUPON",
|
||||
"detail.valuation": "ITEM VALUATION",
|
||||
"detail.currency": "(Currency: RMB)",
|
||||
"detail.extraTip": "If your belongings exceed a valuation of $5000, please insure the premium yourself.",
|
||||
"detail.feeDetail": "FEE DETAILS",
|
||||
"detail.deposit": "Deposit",
|
||||
"detail.valueAdded": "Value-added",
|
||||
"detail.discount": "Discount",
|
||||
"detail.total": "Total",
|
||||
"detail.next": "NEXT",
|
||||
"detail.read": "I have read and agreed on ",
|
||||
"detail.agreement": "[User Service Agreement]",
|
||||
"detail.agreeTip": "Please read and agree on [User Service Agreement].",
|
||||
"detail.orderNum": "ORDER NUMBER",
|
||||
"detail.type": "UNIT TYPE",
|
||||
"detail.period": "LEASE PERIOD",
|
||||
"detail.click": "Details: Click on",
|
||||
"detail.sitemap": "SITEMAP",
|
||||
"detail.selected": "SELECTED",
|
||||
"detail.nodata": "No Data",
|
||||
"detail.noselect": "No Select",
|
||||
"detail.RENEWAL": "RENEWAL",
|
||||
"detail.RefundableDeposit": "Refundable Deposit",
|
||||
"detail.to": "TO",
|
||||
"detail.quotation": "Quotation",
|
||||
"detail.generateQuotation": "Generate Quotation",
|
||||
"detail.regenerateQuotation": "Re-Generate Quotation",
|
||||
"detail.viewQuotation": "View Quotation",
|
||||
"detail.quotationFail": "Quotation generation failed",
|
||||
"detail.quotationSuccess": "Please manually save or forward the quotation",
|
||||
"detail.agreeTerm": "Agree to Terms",
|
||||
"detail.scrollRead": "Please read all terms before agreeing",
|
||||
"detail.points": "Points",
|
||||
"detail.PointsRedemption": "Points Redemption",
|
||||
"detail.AvailablePoints": "Available Points",
|
||||
"detail.DeductionAmount": "Deduction Amount",
|
||||
"door.refresh": "Refresh QR code",
|
||||
"door.refreshPwd": "Refresh Password",
|
||||
"door.tip": "Use facial recgonition device to scan QR code.",
|
||||
"door.valid": "Valid for 1 minute.",
|
||||
"door.pwd": "Enter the password to unlock.",
|
||||
"door.Unlock": "Unlock",
|
||||
"door.UnlockSuccessful": "Unlock Successful",
|
||||
"person.order": "Order details",
|
||||
"person.promotion": "Promotion",
|
||||
"person.identify": "Identification",
|
||||
"person.invoice": "Invoice",
|
||||
"person.guide": "User guide",
|
||||
"person.customer": "Customer service",
|
||||
"person.invitation": "Invitation",
|
||||
"person.evaluation": "Evaluation",
|
||||
"person.latestEvents": "Latest Events",
|
||||
"person.lock": "Locks",
|
||||
"person.share": "SHARE AND GAIN RENT-GREE",
|
||||
"person.join": "JOIN NOW",
|
||||
"person.VideoTutorial": "Tutorial",
|
||||
"person.referrerInfo": "Referrer Info",
|
||||
"site.branch": "BRANCH",
|
||||
"site.address": "Address",
|
||||
"site.tip": "*illegal items are strictly prohibited",
|
||||
"site.tip2": "*Charges are based on internal dimensions only",
|
||||
"site.ReferenceVolume": "Ref Volume",
|
||||
"site.WarehouseInternalDimensions": "Int. Dims",
|
||||
"site.full": "SORRY,THIS TYPE IS FULL,PLEASE SELECT ANOTHER TYPE.",
|
||||
"site.appointment": "Appointment",
|
||||
"site.appointmentSuccess": "Appointment successful. Our staff will contact you shortly, please keep your phone accessible!",
|
||||
"site.hadAppointment": "Already Appointment",
|
||||
"site.cancelAppointment": "Whether to cancel the reservation",
|
||||
"site.noAccessId": "The accesscontrol ID is not available",
|
||||
"site.noPermission": "There is no permission for this store",
|
||||
"login.account": "account",
|
||||
"login.password": "password",
|
||||
"login.confirm": "confirm password",
|
||||
"login.code": "code",
|
||||
"login.input": "Please input",
|
||||
"login.login": "Log in",
|
||||
"login.wxLogin": "WeChat Login",
|
||||
"login.register": "Register an account",
|
||||
"login.forget": "forget the password?",
|
||||
"login.send": "SEND",
|
||||
"login.change": "Change",
|
||||
"login.toLogin": "LOGIN AN ACCOUNT",
|
||||
"login.registered": "Registered",
|
||||
"login.different": "The password is different.",
|
||||
"login.phone": "Phone Number",
|
||||
"login.inputPhone": "Please enter your phone number",
|
||||
"login.phoneFormat": "Invalid phone format",
|
||||
"login.inputCode": "Please enter the code",
|
||||
"login.getCode": "Get Code",
|
||||
"login.sending": "Sending...",
|
||||
"login.sendSuccess": "Code sent",
|
||||
"login.UserAgreement": "《User Agreement》",
|
||||
"login.andAgreeTo": "and agree to",
|
||||
"request.tip": "Tips",
|
||||
"request.cancel": "Cancel",
|
||||
"request.confirm": "Confirm",
|
||||
"request.loginContent": "No login, whether to jump to the login page",
|
||||
"request.captchaError": "CAPTCHA error or expired",
|
||||
"request.userCancel": "Request canceled by user",
|
||||
"request.timeout": "Network request timed out",
|
||||
"request.noConnect": "Failed to connect to server",
|
||||
"request.error": "Error",
|
||||
"toast.copy": "Successful replication",
|
||||
"invite.title1": "Invite friends and",
|
||||
"invite.title2": "win the rewards.",
|
||||
"invite.number": "Invitations",
|
||||
"invite.activity": "Mechanism",
|
||||
"invite.branch": "Branch",
|
||||
"invite.details": "Details",
|
||||
"invite.toInvite": "INVITE NOW",
|
||||
"invite.record": "View records",
|
||||
"invite.disclaimer": "* Disclaimer:",
|
||||
"invite.disContent": " the sole decision of Storage Limited shall be final in case of any dispute.",
|
||||
"common.edit": "Edit",
|
||||
"common.paySuccess": "Payment Successful",
|
||||
"common.payFail": "Payment Failed, Please Try Again!",
|
||||
"common.$": "$",
|
||||
"common.notStarted": "Not Started",
|
||||
"common.status": "Status",
|
||||
"common.verifyInfo": " Info Verify",
|
||||
"common.infoUpdate": "Info Update",
|
||||
"common.saveInfo": "Save Info",
|
||||
"common.personalAuth": "Personal Auth",
|
||||
"common.businessAuth": "Business Auth",
|
||||
"common.IdCardFont": "Upload ID Front",
|
||||
"common.IdCardBack": "Upload ID Back",
|
||||
"common.UploadBusinessLicense": "Upload Business License",
|
||||
"common.noOpen": "Not open, contact staff for details!",
|
||||
"common.isGoAuth": "Upload personal information for verification.",
|
||||
"common.Authentication": "Authentication",
|
||||
"common.submit": "Submit",
|
||||
"common.placeInputAll": "Please enter all the information",
|
||||
"common.goodsList": "Goods List",
|
||||
"common.OnlineConsultation": "Consultation",
|
||||
"common.avatar": "Avatar",
|
||||
"common.uploadAvatar": "Upload Avatar",
|
||||
"common.nickname": "Nickname",
|
||||
"common.phone": "Phone",
|
||||
"common.bindPhone": "Bind Phone",
|
||||
"common.bindPhoneAfter": "Bind the mobile phone number before performing this operation",
|
||||
"common.bindPhoneUnlock": "Bind the phone number to get the order information",
|
||||
"common.QuickBind": "Quick Bind",
|
||||
"common.cancelBind": "Cancel Bind",
|
||||
"common.facialData": "Facial Data",
|
||||
"common.auth": "Authorization",
|
||||
"common.requireAvatar": "Please upload the profile picture",
|
||||
"common.requireName": "Please enter the nickname",
|
||||
"common.requirePhone": "Please enter the phone number",
|
||||
"common.note": "Notes",
|
||||
"common.tip": "Tip",
|
||||
"common.cancelApply": "Are you sure to cancel the application?",
|
||||
"common.cancelSuccess": "Cancel successfully",
|
||||
"common.cancelFail": "Cancel failed. Please try again later!",
|
||||
"common.addOrder": "Confirm Order",
|
||||
"common.AuthenticationFailedTips": "Your identity verification has not been approved.You may still place an order for same-day warehouse use (within the rental period).However, starting from the next day, you must submit valid identity information and pass verification to continue using the warehouse.",
|
||||
"common.VacantDay": "Vacant Day",
|
||||
"common.RemainingDay": "Remaining Day",
|
||||
"common.OverdueDay": "Overdue Day",
|
||||
"common.ORDER_AMOUNT_ERROR": "Invalid discount amount. Please select again.",
|
||||
"common.tuangouCouponPrice": "tuangou coupon price",
|
||||
"common.checkAgreementUrl": "Please read and agree to the terms first.",
|
||||
"common.Expand": "Expand",
|
||||
"common.Collapse": "Collapse",
|
||||
"common.OtherStores": "Other Stores",
|
||||
"coupon.coupon": "Coupon",
|
||||
"coupon.meituanOrdazhongdianpingCoupon": "Meituan/Dazhongdianping Coupon",
|
||||
"coupon.queryMeituanDazhongdianpingCoupon": "Click to check Meituan/Dianping coupons",
|
||||
"coupon.useTips": "Instruction: Enter the coupon code to enjoy the discount.",
|
||||
"coupon.enterCode": "Coupon code",
|
||||
"coupon.limitedtimeoffer": "Limited-time offer",
|
||||
"coupon.storewide": "Store-wide",
|
||||
"coupon.redeemNow": "Redeem",
|
||||
"coupon.instructions": "Instructions",
|
||||
"coupon.validityPeriod": "Expiry",
|
||||
"coupon.apply": "Apply",
|
||||
"coupon.all": "All Unit Types",
|
||||
"coupon.multiStoreUse": "Multi-store use",
|
||||
"coupon.renewable": "Usable on reorders",
|
||||
"coupon.noRenewable": "Not usable on reorders",
|
||||
"coupon.redemptionuccessful": "Redemption successful",
|
||||
"coupon.unusableCoupons": "Unusable Coupons",
|
||||
"coupon.currentConditionsNotMet": "Current conditions not met",
|
||||
"request.promoCodeError": "Promo code error",
|
||||
"validation.getPhoneFail": "Failed to obtain the phone number, please enter it manually",
|
||||
"validation.inputName1": "Please input the name",
|
||||
"validation.selectCardType": "Please select the card type",
|
||||
"validation.inputIdCard": "Please input the ID number",
|
||||
"validation.uploadIdCard": "Please upload your ID photo",
|
||||
"validation.inputPhone": "Please input the phone number",
|
||||
"validation.inputInternationalPhone": "Please input the international phone number",
|
||||
"validation.uploadImg": "The picture is uploading, please try again later",
|
||||
"validation.inputName2": "Please input the company name",
|
||||
"validation.inputLicense": "Please input the business license number",
|
||||
"validation.uploadLicense": "Please upload your business ID card",
|
||||
"validation.submitSuccess": "Submit successfully",
|
||||
"validation.uploadSuccess": "Update successfully",
|
||||
"validation.identifyCard": "China ID Card",
|
||||
"validation.passport": "Passport",
|
||||
"validation.permit": "Hong Kong and Macao Permit",
|
||||
"validation.access": "One-click Access",
|
||||
"validation.bind": "Binding",
|
||||
"validation.vailSuccess": "Verification successful.",
|
||||
"validation.agree": "Please read and agree to the User Service Agreement and Privacy Policy.",
|
||||
"agreement.readAndAgree": "I have read and agree to the",
|
||||
"agreement.service": "User Service Agreement",
|
||||
"agreement.and": "and",
|
||||
"agreement.privacy": "Privacy Policy",
|
||||
"agreement.toast": "Please read and agree to the User Service Agreement and Privacy Policy.",
|
||||
"verification.vailFail": "Authentication failed. Please verify that the document type and information are correct, and re-upload a clear image (front and back). Try authenticating again!",
|
||||
"verification.vailSuccess": "Authentication successful.",
|
||||
"invoiceApply.electronicInvoice": "Electronic Invoice",
|
||||
"invoiceApply.paperInvoice": "Paper Invoice",
|
||||
"invoiceApply.invoiceTips": "Your invoice application has been submitted successfully, please wait patiently for the staff to contact!",
|
||||
"invoice.valid": "Invoices",
|
||||
"invoice.pay": "Payment Time",
|
||||
"invoice.site": "Store",
|
||||
"invoice.type": "Unit Type",
|
||||
"invoice.unit": "Unit",
|
||||
"invoice.rent": "Lease Term",
|
||||
"invoice.record": "Application Record",
|
||||
"invoice.allSelect": "Select All",
|
||||
"invoice.nextStep": "NEXT",
|
||||
"invoice.tip": "- Invoices can be issued within one month after payment of the order",
|
||||
"invoice.serial": "Number",
|
||||
"invoice.time": "Application Time",
|
||||
"invoice.status": "Audit Status",
|
||||
"invoice.status0": "Pending approval",
|
||||
"invoice.status1": "Approved, invoicing...",
|
||||
"invoice.status2": "Failed approval",
|
||||
"invoice.status3": "Cancelled",
|
||||
"invoice.status4": "Invoiced",
|
||||
"invoice.selectOrder": "Please select an order",
|
||||
"invoice.validMoney": "The order amount must be greater than 0",
|
||||
"evaluate.customerEvaluation": "CUSTOMER EVALUATION",
|
||||
"evaluate.overallRating": "Overall rating",
|
||||
"evaluate.userExperience": "User experience",
|
||||
"evaluate.Hospitality": "Hospitality",
|
||||
"evaluate.cleanliness": "Cleanliness",
|
||||
"evaluate.convenience": "Convenience",
|
||||
"evaluate.tips": "Please leave your invaluable comment or suggestion here.",
|
||||
"evaluate.anonymous": "ANONYMOUS",
|
||||
"goodsList.note": "Please note",
|
||||
"goodsList.info": "Hello, according to the requirements of relevant departments, the items you store need to be declared independently for their category. This declaration form is for record keeping purposes, please fill it out carefully.",
|
||||
"goodsList.multi": "Multiple choices",
|
||||
"goodsList.tip1": "User Confirmation:",
|
||||
"goodsList.tip2": "1. The stored items were obtained through legal channels;",
|
||||
"goodsList.tip3": "2. Do not store prohibited items;",
|
||||
"goodsList.tip4": "3. If property damage or personal injury is caused by changes in the user's stored items or other reasons, the user shall bear the responsibility.",
|
||||
"goodsList.submit": "Confirm",
|
||||
"houseKey.FriendsName": "Friend's name",
|
||||
"houseKey.AuthorizationDate": "Authorization date",
|
||||
"houseKey.ReceiveNotifications": "Receive notifications",
|
||||
"houseKey.EnableNotifications": "Enable notifications",
|
||||
"houseKey.PhoneNumber": "Phone number",
|
||||
"houseKey.email": "Email",
|
||||
"houseKey.EnterAuthorizedPhoneNumber": "Enter authorized person's phone number",
|
||||
"houseKey.AddAuthorization": "Add authorization",
|
||||
"houseKey.EnterFriendsName": "Enter friend's name",
|
||||
"houseKey.EnterAuthorizationDate": "Enter authorization date",
|
||||
"houseKey.EnterPhoneNumber": "Enter phone number",
|
||||
"houseKey.EnterEmail": "Enter email",
|
||||
"houseKey.CannotAuthorizeYourself": "Cannot authorize yourself",
|
||||
"houseKey.UpdateSuccessful": "Update successful",
|
||||
"houseKey.AddedSuccessfully": "Added successfully",
|
||||
"houseKey.date": "Year-Month-Day",
|
||||
"houseKey.overdue": "The authorization period has expired. Please re-select the authorization period and renew it.",
|
||||
"houseKey.otherPhone": "The phone number of the authorized party",
|
||||
"houseKey.otherEmail": "The email address of the authorized party",
|
||||
"houseKey.getNote": "Open to receive notifications",
|
||||
"unitTypeDetail.oneMonth": "Lease term: one month",
|
||||
"unitTypeDetail.reference": "Reference",
|
||||
"unitTypeDetail.discount": "Discount",
|
||||
"reserve.FULLNAME": "Full Name",
|
||||
"reserve.PHONE": "Phone",
|
||||
"reserve.REGION": "Region",
|
||||
"reserve.TYPE": "Type",
|
||||
"reserve.PHONE NUMBER": "Phone Number",
|
||||
"reserve.Individual & Family": "Individual & Family",
|
||||
"reserve.Business & E-commerce": "Business & E-commerce",
|
||||
"reserve.Retail & Store": "Retail & Store",
|
||||
"reserve.contentTips": "Appointment successful! Please wait patiently for our staff to contact you. Keep your phone available. Thank you!",
|
||||
"inviteDetail.title": "Invitation Records",
|
||||
"inviteDetail.Username": "Username",
|
||||
"inviteDetail.Registration Date": "Registration Date",
|
||||
"inviteDetail.Status": "Status",
|
||||
"inviteDetail.No invitation": "No invitation records found. Please share invitations.",
|
||||
"inviteDetail.SORRY": "SORRY, THERE ARE NO RECORDS. PLEASE SHARE AND INVITE.",
|
||||
"inviteDetail.Share Invitation": "Share Invitation",
|
||||
"referrerInfo.company": "Referrer Company",
|
||||
"referrerInfo.branch": "Referrer Branch",
|
||||
"referrerInfo.commission": "Referral Commission Rate",
|
||||
"referrerInfo.inviteRegister": "Invitation to Register",
|
||||
"referrerInfo.inviteRecord": "Invitation Record",
|
||||
"referrerInfo.inviteUserName": "User Name",
|
||||
"referrerInfo.invitePhone": "Phone Number",
|
||||
"referrerInfo.registrationTime": "Registration Time",
|
||||
"referrerInfo.inviteEmpty": "No invitation record",
|
||||
"referrerInfo.loadQrCode": "Download QR Code",
|
||||
"referrerInfo.loadPoster": "Download Poster",
|
||||
"referrerInfo.forwardInvitation": "Forward Invitation",
|
||||
"pointsMall.title": "Points Mall",
|
||||
"pointsMall.myPoints": "My Points",
|
||||
"pointsMall.pointsUnit": "Points",
|
||||
"pointsMall.exchange": "Redeem",
|
||||
"pointsMall.exchangeFormTitle": "Shipping Information",
|
||||
"pointsMall.submit": "Submit",
|
||||
"pointsMall.receiverName": "Recipient Name",
|
||||
"pointsMall.phone": "Phone Number",
|
||||
"pointsMall.address": "Address",
|
||||
"pointsMall.placeholderName": "Enter recipient name",
|
||||
"pointsMall.placeholderPhone": "Enter phone number",
|
||||
"pointsMall.placeholderAddress": "Enter detailed shipping address",
|
||||
"pointsMall.exchangeConfirmTitle": "Exchange Confirmation",
|
||||
"pointsMall.exchangeConfirmTip": "Are you sure you want to use {points} points to redeem?",
|
||||
"pointsMall.stock": "Stock",
|
||||
"pointsMall.successTitle": "Success",
|
||||
"pointsMall.successTip": "Redemption successful. Please wait for our staff to contact you. Thank you!",
|
||||
"pointsMall.toastOutOfStock": "Out of stock",
|
||||
"pointsMall.toastNotEnoughPoints": "Not enough points",
|
||||
"pointsMall.toastExchanging": "Redeeming...",
|
||||
"pointsMall.toastExchangeFailed": "Redemption failed",
|
||||
"pointsMall.exchangeRecordTitle": "Redemption Records",
|
||||
"pointsMall.noExchangeRecord": "No redemption records"
|
||||
}
|
||||
38
locale/index.js
Normal file
38
locale/index.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
// 导入静态翻译内容
|
||||
import en from './en.json';
|
||||
import zhHans from './zh-Hans.json';
|
||||
import zhHant from './zh-Hant.json';
|
||||
import ja from './ja.json';
|
||||
// 导入动态翻译函数
|
||||
import messagesFunctions from './messagesFunctions.js';
|
||||
|
||||
// 合并静态和动态的翻译内容
|
||||
const mergedMessages = {
|
||||
en: {
|
||||
...en,
|
||||
...messagesFunctions.en,
|
||||
},
|
||||
'zh-Hans': {
|
||||
...zhHans,
|
||||
...messagesFunctions.zhHans,
|
||||
},
|
||||
'zh-Hant': {
|
||||
...zhHant,
|
||||
...messagesFunctions.zhHant,
|
||||
},
|
||||
ja: {
|
||||
...ja,
|
||||
...messagesFunctions.ja,
|
||||
},
|
||||
};
|
||||
|
||||
const language = "zh-Hans";
|
||||
const i18n = createI18n({
|
||||
locale: language,
|
||||
messages: mergedMessages
|
||||
});
|
||||
uni.setStorageSync("eliteSys-language-wx", language);
|
||||
|
||||
export default i18n;
|
||||
23
locale/ja.json
Normal file
23
locale/ja.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"locale.auto": "システム",
|
||||
"locale.en": "英語",
|
||||
"locale.zh-hans": "简体中文",
|
||||
"locale.zh-hant": "繁体中文",
|
||||
"locale.ja": "日语",
|
||||
"index.title": "Hello i18n",
|
||||
"index.home": "ホーム",
|
||||
"index.component": "コンポーネント",
|
||||
"index.api": "API",
|
||||
"index.schema": "Schema",
|
||||
"index.demo": "uni-app globalization",
|
||||
"index.demo-description": "ユニフレームワーク、manifest.json、pages.json、タブバー、ページ、コンポーネント、APIを含める、Schema",
|
||||
"index.detail": "詳細",
|
||||
"index.language": "言語",
|
||||
"index.language-info": "設定",
|
||||
"index.system-language": "システム言語",
|
||||
"index.application-language": "アプリケーション言語",
|
||||
"index.language-change-confirm": "この設定を適用すると、アプリが再起動します",
|
||||
"api.message": "メッセージ",
|
||||
"schema.add": "追加",
|
||||
"schema.add-success": "成功を追加"
|
||||
}
|
||||
49
locale/messagesFunctions.js
Normal file
49
locale/messagesFunctions.js
Normal file
@ -0,0 +1,49 @@
|
||||
const numToChinese = (num) => {
|
||||
if(num<1) num = 1
|
||||
const map = ['零','一','二','三','四','五','六','七','八','九']
|
||||
return map[num] || num.toString()
|
||||
}
|
||||
export default {
|
||||
en: {
|
||||
'person.inviteData': ({ named }) => `Already invited ${named('friends')} friends, Opportunity to receive additional rewards`,
|
||||
"detail.remain": ({ named }) => `Remaining ${named('days')} days`,
|
||||
"detail.discountOff": ({ named }) => `${named('month')} MONTHS ${named('percent')}% OFF`,
|
||||
"month": ({ named }) => `${named('count')} month(s)`,
|
||||
"months": ({ named }) => `${named('count')} month(s)`,
|
||||
"discountMomey": ({ named }) => `${named('discount')} Discount `,
|
||||
"firstMonthRent": ({ named }) => `First Month Rent ${named('discount')}`,
|
||||
"couponDiscount": ({ named }) => `${named('percent')} off `,
|
||||
"freeMonth": ({ named }) => `Free ${named('discount')} months`,
|
||||
"BonusMonth": ({ named }) => `Bonus ${named('discount')} months`,
|
||||
"requiredMomey": ({ named }) => `${named('momey')} required`,
|
||||
"fullMonths": ({ named }) => `available after ${named('count')} full months`,
|
||||
"invoice.order": ({ named }) => `${named('number')} orders have been selected, totaling ${named('money')} yuan`,
|
||||
"giftMonth": ({ named }) => `Gifted ${named('count')} months`,
|
||||
"storeRenovationNotice": ({ named }) => `This store is under renovation. It will be available on ${named('limitDate')}. Orders can be placed in advance.`,
|
||||
"storeCount": ({ named }) => `${named('count')} stores`,
|
||||
"discount": ({ named }) => `${Math.floor(named('discount') * 100)}% off`,
|
||||
"flashSaleDiscount": ({ named }) => `Extra ${Math.floor(named('discount') * 100)}% off`,
|
||||
'order.confirmReferrer': ({ named }) => `This order will be associated with the referrer ${named('referrer')}. Please confirm whether this transaction was completed through their referral.`,
|
||||
},
|
||||
zhHans: {
|
||||
'person.inviteData': ({ named }) => `已邀请 ${named('friends')} 名好友, 有机会获得额外奖励`, //获得 ${named('days')} 天免费租期!
|
||||
"detail.remain": ({ named }) => `租期:剩余${named('days')}天`,
|
||||
"detail.discountOff": ({ named }) => `${named('month')}个月享${named('discount')}折`,
|
||||
"month": ({ named }) => `${named('count')} 月`,
|
||||
"months": ({ named }) => `${named('count')} 月`,
|
||||
"discountMomey": ({ named }) => `优惠 ¥${named('discount')} `,
|
||||
"firstMonthRent": ({ named }) => `首月 ${named('discount')} 元`,
|
||||
"couponDiscount": ({ named }) => ` 打 ${named('discount')} 折 `,
|
||||
"freeMonth": ({ named }) => `免租 ${named('discount')} 个月`,
|
||||
"BonusMonth": ({ named }) => `赠送 ${named('discount')} 个月`,
|
||||
"requiredMomey": ({ named }) => `满¥${named('momey')} 可用`,
|
||||
"fullMonths": ({ named }) => `满 ${named('count')} 个月可用`,
|
||||
"invoice.order": ({ named }) => `已选 ${named('number')} 个订单,共 ${named('money')} 元`,
|
||||
"giftMonth": ({ named }) => `赠送 ${named('count')} 个月`,
|
||||
"storeRenovationNotice": ({ named }) =>`该店铺装修中,将于 ${named('limitDate')} 开放使用,可提前下单`,
|
||||
"storeCount": ({ named }) => `一共 ${named('count')} 店`,
|
||||
"discount": ({ named }) => `${numToChinese(Math.floor(named('discount') * 10))||numToChinese[1]} 折`,
|
||||
"flashSaleDiscount": ({ named }) => `额外${numToChinese(Math.floor(named('discount') * 10))}折`,
|
||||
'order.confirmReferrer': ({ named }) => `此订单将关联至转介人 ${named('referrer')}。请确认是否通过他的推荐完成本次交易?`,
|
||||
},
|
||||
};
|
||||
36
locale/uni-app.ja.json
Normal file
36
locale/uni-app.ja.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"common": {
|
||||
"uni.app.quit": "もう一度押すと、アプリケーションが終了します",
|
||||
"uni.async.error": "サーバーへの接続がタイムアウトしました。画面をクリックして再試行してください",
|
||||
"uni.showActionSheet.cancel": "キャンセル",
|
||||
"uni.showToast.unpaired": "使用するには、showToastとhideToastをペアにする必要があることに注意してください",
|
||||
"uni.showLoading.unpaired": "使用するには、showLoadingとhideLoadingをペアにする必要があることに注意してください",
|
||||
"uni.showModal.cancel": "キャンセル",
|
||||
"uni.showModal.confirm": "OK",
|
||||
"uni.chooseImage.cancel": "キャンセル",
|
||||
"uni.chooseImage.sourceType.album": "アルバムから選択",
|
||||
"uni.chooseImage.sourceType.camera": "カメラ",
|
||||
"uni.chooseVideo.cancel": "キャンセル",
|
||||
"uni.chooseVideo.sourceType.album": "アルバムから選択",
|
||||
"uni.chooseVideo.sourceType.camera": "カメラ",
|
||||
"uni.previewImage.cancel": "キャンセル",
|
||||
"uni.previewImage.button.save": "画像を保存",
|
||||
"uni.previewImage.save.success": "画像をアルバムに正常に保存します",
|
||||
"uni.previewImage.save.fail": "画像をアルバムに保存できませんでした",
|
||||
"uni.setClipboardData.success": "コンテンツがコピーされました",
|
||||
"uni.scanCode.title": "スキャンコード",
|
||||
"uni.scanCode.album": "アルバム",
|
||||
"uni.scanCode.fail": "認識に失敗しました",
|
||||
"uni.scanCode.flash.on": "タッチして点灯",
|
||||
"uni.scanCode.flash.off": "タップして閉じる",
|
||||
"uni.startSoterAuthentication.authContent": "指紋認識...",
|
||||
"uni.picker.done": "完了",
|
||||
"uni.picker.cancel": "キャンセル",
|
||||
"uni.video.danmu": "「弾幕」",
|
||||
"uni.video.volume": "ボリューム",
|
||||
"uni.button.feedback.title": "質問のフィードバック",
|
||||
"uni.button.feedback.send": "送信"
|
||||
},
|
||||
"ios": {},
|
||||
"android": {}
|
||||
}
|
||||
502
locale/zh-Hans.json
Normal file
502
locale/zh-Hans.json
Normal file
@ -0,0 +1,502 @@
|
||||
{
|
||||
"common.confirm": "确认",
|
||||
"common.cancel": "取消",
|
||||
"common.title": "提示",
|
||||
"common.noData": "暂无数据",
|
||||
"common.delete": "删除",
|
||||
"common.update": "更新",
|
||||
"common.password": "密码",
|
||||
"common.close": "关闭",
|
||||
"common.Skiptoday": "暂时跳过",
|
||||
"common.payableTime": "剩余支付时间",
|
||||
"common.AuthorizationOrder": "授权订单",
|
||||
"common.cancelOrder": "取消订单",
|
||||
"common.cancelOrderTips": "确认取消当前订单?",
|
||||
"common.unpaidOrderTips": "存在未支付订单,请先支付,或者取消订单后再下单!",
|
||||
"common.cantUselocker": "此体积仓位不可租用,请重新选择!",
|
||||
"common.checkAgreementUrl": "请先阅读并同意协议",
|
||||
"common.logout": "登出",
|
||||
"common.logoutTip": "确认退出程序?",
|
||||
"common.FirstTimeLoginTips": "「首次登录将自动注册」",
|
||||
"common.SpaceSpecsGuide": "空间规格介绍参考",
|
||||
"common.Seelegend": "查看图例",
|
||||
"common.Rent": "租用",
|
||||
"common.OriginalPrice": "原价格",
|
||||
"common.SalePrice": "活动价",
|
||||
"common.SwitchStores": "切换同地址分店",
|
||||
"common.more": "更多",
|
||||
"common.SwitchRegion": "切换区域",
|
||||
"common.Countdown": "倒计时",
|
||||
"common.FlashSale": "限时抢购",
|
||||
"common.FlashSalePrice": "限时抢购价",
|
||||
"common.ClickToCheck": "点击查看",
|
||||
"common.ClickToZoomIn": "点击放大",
|
||||
"common.tryZooming": "如果门禁识别不成功,可尝试点击放大",
|
||||
"common.OnSiteAssessment": "上门评估",
|
||||
"common.ConsultationQuotation": "咨询报价",
|
||||
"common.Requirement": "需求",
|
||||
"common.day": "天",
|
||||
"common.reset": "重置",
|
||||
"common.notRented": "未租",
|
||||
"common.rented": "已租",
|
||||
"common.locked": "鎖定",
|
||||
"locale.auto": "系统",
|
||||
"locale.en": "English",
|
||||
"locale.zh-hans": "简体中文",
|
||||
"locale.zh-hant": "繁体中文",
|
||||
"locale.ja": "日语",
|
||||
"index.title": "Hello i18n",
|
||||
"index.home": "主页",
|
||||
"index.component": "组件",
|
||||
"index.api": "API",
|
||||
"index.schema": "Schema",
|
||||
"index.demo": "uni-app 国际化演示",
|
||||
"index.demo-description": "包含 uni-framework、manifest.json、pages.json、tabbar、页面、组件、API、Schema",
|
||||
"index.detail": "详情",
|
||||
"index.language": "语言",
|
||||
"index.language-info": "语言信息",
|
||||
"index.system-language": "系统语言",
|
||||
"index.application-language": "应用语言",
|
||||
"index.language-change-confirm": "应用此设置将重启App",
|
||||
"api.message": "提示",
|
||||
"schema.name": "姓名",
|
||||
"schema.add": "新增",
|
||||
"schema.add-success": "新增成功",
|
||||
"tabbar.home": "首页",
|
||||
"tabbar.book": "订仓",
|
||||
"tabbar.unlock": "用仓",
|
||||
"tabbar.personal": "我的",
|
||||
"home.select": "选择分店",
|
||||
"home.reserve": "立即预约",
|
||||
"home.book": "立即订仓",
|
||||
"home.detail": "查看详情",
|
||||
"home.appointment": "立即预约",
|
||||
"home.recommend": "热推门店",
|
||||
"home.morestore": "所有门店",
|
||||
"home.navigate": "导航",
|
||||
"home.wayfinding": "路径指引",
|
||||
"home.travel": "出差寄存",
|
||||
"home.collection": "玩具收藏",
|
||||
"home.clothes": "衣物鞋帽",
|
||||
"home.appliances": "家具家电",
|
||||
"home.goods": "门店货物",
|
||||
"home.relocate": "搬迁装修",
|
||||
"home.wrapper": "包装材料",
|
||||
"home.material": "物资储备",
|
||||
"home.document": "文件档案",
|
||||
"home.device": "办公设备",
|
||||
"home.supplies": "活动物资",
|
||||
"home.ecgoods": "电商货品",
|
||||
"home.shops": "零售·门店",
|
||||
"home.individuals": "个人·家庭",
|
||||
"home.corporates": "企业·电商",
|
||||
"home.advantage1": "连锁经营",
|
||||
"home.advantage1Info1": "实力保证 覆盖广深",
|
||||
"home.advantage1Info2": "南方卫视 多家采访",
|
||||
"home.advantage1Info3": "千万用户 最优选择",
|
||||
"home.advantage2": "使用方便",
|
||||
"home.advantage2Info1": "全天开放 随时存取",
|
||||
"home.advantage2Info2": "手机开仓 手机开锁",
|
||||
"home.advantage2Info3": "一个月起 即租即用",
|
||||
"home.advantage3": "安全保障",
|
||||
"home.advantage3Info1": "实时监控 无死角位",
|
||||
"home.advantage3Info2": "一人一仓 独立储物",
|
||||
"home.advantage3Info3": "免费保险 保驾护航",
|
||||
"home.advantage4": "环境整洁",
|
||||
"home.advantage4Info1": "温度调节 防虫防鼠",
|
||||
"home.advantage4Info2": "配备消防 光洁明亮",
|
||||
"home.advantage4Info3": "定期保洁 专业消毒",
|
||||
"home.interior": "参照",
|
||||
"home.serviceHotline": "客服热线",
|
||||
"home.quote": "立即查价",
|
||||
"book.location": "地区筛选",
|
||||
"book.map": "地图模式",
|
||||
"book.list": "列表模式",
|
||||
"book.city": "城市",
|
||||
"book.area": "区域",
|
||||
"book.get": "查找离你最近的店铺",
|
||||
"book.getSite": "需要打开位置信息设置,来显示最近店铺",
|
||||
"book.getCode": "获取二维码",
|
||||
"unlock.door": "开门",
|
||||
"unlock.lock": "开锁",
|
||||
"unlock.renew": "续仓",
|
||||
"unlock.details": "详情",
|
||||
"unlock.moveout": "退仓",
|
||||
"unlock.cancelPending": "退仓申请中",
|
||||
"unlock.outComplete": "退仓完成",
|
||||
"unlock.cancel": "取消",
|
||||
"unlock.cCancel": "取消",
|
||||
"unlock.cancelout": "取消退仓请求",
|
||||
"unlock.return": "返回",
|
||||
"unlock.moveoutReminder": "退仓提示",
|
||||
"unlock.moveoutTip": "退仓后,您将失去开门权限!请确认已经清空仓内物品,14个工作日内,系统将会自动返还押金。",
|
||||
"unlock.uploadTip": "请上传清空仓库后的照片。",
|
||||
"unlock.moveoutSuccess": "退仓申请中,请等待耐心工作人员审核!",
|
||||
"unlock.confirmOut": "确定退仓",
|
||||
"unlock.evaluate": "评价",
|
||||
"unlock.disapproval": "退仓驳回",
|
||||
"unlock.disapprovalRemarks": "驳回理由",
|
||||
"unlock.overdue": "已逾期",
|
||||
"unlock.unPaid": "未支付",
|
||||
"unlock.order": "我的订单",
|
||||
"unlock.login": "点击登录,查看下单仓库",
|
||||
"unlock.nodata": "暂无数据",
|
||||
"unlock.auth": "授权",
|
||||
"unlock.FixedPassword": "固定密码",
|
||||
"unlock.AccessControlCardBinding": "门禁绑卡",
|
||||
"unlock.LockCardUnbinding": "锁绑卡",
|
||||
"unlock.getAuthOrder": "查看授权订单",
|
||||
"unlock.ResetPassword": "重置密码",
|
||||
"unlock.remoteOpen": "远程开门",
|
||||
"unlock.remoteOpenLoading": "开门中...",
|
||||
"unlock.remoteOpenSuccess": "开门成功",
|
||||
"unlock.remoteOpenFail": "开门失败",
|
||||
"unlock.fillInventory": "用仓前,请先填写物品清单",
|
||||
"unlock.FaceEnrollment": "录入门禁",
|
||||
"unlock.goToPay": "去支付",
|
||||
"unlock.cancelOrder": "取消订单",
|
||||
"unlock.Deposit Refund": "押金退款",
|
||||
"unlock.agreement": "订仓协议",
|
||||
"bingCard.start": "开始配对",
|
||||
"bingCard.fail": "操作失败",
|
||||
"bingCard.Click": "点击[ 开始配对 ]",
|
||||
"bingCard.Pairing": "配对中",
|
||||
"bingCard.panel": "请将ID卡开放在数位面板上",
|
||||
"bingCard.single": "滴一声证明成功",
|
||||
"bingCard.close": "成功后,可手工关闭此弹窗",
|
||||
"detail.store": "门店",
|
||||
"detail.unit": "仓型",
|
||||
"detail.spec": "参考尺寸",
|
||||
"detail.size": "参考体积",
|
||||
"detail.cSize": "参考体积",
|
||||
"detail.cUnit": "已选仓型",
|
||||
"detail.startDate": "启用日期",
|
||||
"detail.lease": "租赁期限",
|
||||
"detail.rentalFee": "租仓费用",
|
||||
"detail.cDeposit": "押金费用(一个月)",
|
||||
"detail.cValueAdded": "额外服务",
|
||||
"detail.nodemand": "无需求",
|
||||
"detail.coupon": "使用优惠",
|
||||
"detail.valuation": "费用详情",
|
||||
"detail.currency": "(单位: 人民币)",
|
||||
"detail.extraTip": "若您的物品估算金额超出¥5000,超出部分请您自行投保。",
|
||||
"detail.feeDetail": "费用详情",
|
||||
"detail.deposit": "押金费用",
|
||||
"detail.valueAdded": "额外费用",
|
||||
"detail.discount": "优惠抵扣",
|
||||
"detail.total": "总共费用",
|
||||
"detail.next": "下一步",
|
||||
"detail.read": "我已经阅读并同意 ",
|
||||
"detail.agreement": "《订仓协议》",
|
||||
"detail.agreeTip": "请阅读并同意《订仓协议》",
|
||||
"detail.orderNum": "订单序号",
|
||||
"detail.type": "仓库",
|
||||
"detail.period": "租赁时期",
|
||||
"detail.click": "详情:点击查看订单",
|
||||
"detail.sitemap": "平面图",
|
||||
"detail.selected": "已选仓位",
|
||||
"detail.nodata": "未选填需求",
|
||||
"detail.noselect": "未选优惠",
|
||||
"detail.RENEWAL": "续仓记录",
|
||||
"detail.RefundableDeposit": "可退押金",
|
||||
"detail.to": "续费至",
|
||||
"detail.quotation": "报价单",
|
||||
"detail.generateQuotation": "生成报价单",
|
||||
"detail.regenerateQuotation": "重新生成报价单",
|
||||
"detail.viewQuotation": "查看报价单",
|
||||
"detail.quotationFail": "生成报价单失败",
|
||||
"detail.quotationSuccess": "请手动保存或转发报价单",
|
||||
"detail.scrollRead": "請在同意前閱讀所有條款",
|
||||
"detail.agreeTerm": "同意條款",
|
||||
"detail.points": "积分",
|
||||
"detail.PointsRedemption": "积分抵扣",
|
||||
"detail.AvailablePoints": "可用积分",
|
||||
"detail.DeductionAmount": "抵扣金额",
|
||||
"door.refresh": "刷新二维码",
|
||||
"door.refreshPwd": "刷新密码",
|
||||
"door.tip": "使用门禁设备扫描二维码",
|
||||
"door.valid": "有效时限为1分钟",
|
||||
"door.pwd": "输入密码即可开锁",
|
||||
"door.Unlock": "开锁",
|
||||
"door.UnlockSuccessful": "开锁成功",
|
||||
"person.order": "订单详情",
|
||||
"person.promotion": "优惠卡包",
|
||||
"person.identify": "信息认证",
|
||||
"person.invoice": "发票申请",
|
||||
"person.guide": "用户指南",
|
||||
"person.customer": "客服咨询",
|
||||
"person.invitation": "邀请详情",
|
||||
"person.evaluation": "用户评价",
|
||||
"person.latestEvents": "最新活动",
|
||||
"person.lock": "锁具管理",
|
||||
"person.share": "分享即送免费租期",
|
||||
"person.join": "立即参加",
|
||||
"person.VideoTutorial": "视频教程",
|
||||
"person.referrerInfo": "推荐人信息",
|
||||
"site.branch": "分店",
|
||||
"site.address": "地址",
|
||||
"site.tip": "*非违规违禁物品均可存放",
|
||||
"site.tip2": "*仅按仓内尺寸收费",
|
||||
"site.ReferenceVolume": "参考体积",
|
||||
"site.WarehouseInternalDimensions": "仓内尺寸",
|
||||
"site.full": "抱歉,此仓型已租满,请选择其他仓型。",
|
||||
"site.appointment": "候补预约",
|
||||
"site.appointmentSuccess": "预约成功。随后工作人员会联系你,请保持手机顺畅!",
|
||||
"site.hadAppointment": "已预约",
|
||||
"site.cancelAppointment": "是否取消预约",
|
||||
"site.noAccessId": "暂无门禁设备accesscontrol ID",
|
||||
"site.noPermission": "暂无该门店的权限",
|
||||
"login.account": "账号",
|
||||
"login.password": "密码",
|
||||
"login.confirm": "确认密码",
|
||||
"login.code": "验证码",
|
||||
"login.input": "请输入",
|
||||
"login.login": "登录",
|
||||
"login.wxLogin": "手机号快捷登录",
|
||||
"login.register": "注册账号",
|
||||
"login.forget": "忘记密码?",
|
||||
"login.send": "发送",
|
||||
"login.change": "更改",
|
||||
"login.toLogin": "登录账号",
|
||||
"login.registered": "注册",
|
||||
"login.different": "密码不一致",
|
||||
"login.phone": "手机号",
|
||||
"login.inputPhone": "请输入手机号",
|
||||
"login.phoneFormat": "手机号格式错误",
|
||||
"login.inputCode": "请输入验证码",
|
||||
"login.getCode": "获取验证码",
|
||||
"login.sending": "发送中...",
|
||||
"login.sendSuccess": "验证码已发送",
|
||||
"login.UserAgreement": "《用户协议》",
|
||||
"login.andAgreeTo": "同意并接受",
|
||||
"request.tip": "提示",
|
||||
"request.cancel": "取消",
|
||||
"request.confirm": "确认",
|
||||
"request.loginContent": "未登录,是否前往登录页面",
|
||||
"request.captchaError": "验证码错误或已过期",
|
||||
"request.userCancel": "用户取消请求",
|
||||
"request.timeout": "网络请求超时",
|
||||
"request.noConnect": "连接服务器失败",
|
||||
"request.error": "错误",
|
||||
"toast.copy": "复制成功",
|
||||
"invite.title1": "邀好友",
|
||||
"invite.title2": "赢优惠券",
|
||||
"invite.number": "邀请人数",
|
||||
"invite.activity": "活动内容",
|
||||
"invite.branch": "适用门店",
|
||||
"invite.details": "详情咨询",
|
||||
"invite.toInvite": "去邀请",
|
||||
"invite.record": "查看邀请记录",
|
||||
"invite.disclaimer": "* 活动解释权归迷你仓所有",
|
||||
"invite.disContent": "",
|
||||
"common.edit": "编辑",
|
||||
"common.paySuccess": "支付成功",
|
||||
"common.payFail": "支付失败,请重新尝试!",
|
||||
"common.$": "¥",
|
||||
"common.notStarted": "未开始",
|
||||
"common.status": "状态",
|
||||
"common.verifyInfo": "信息认证",
|
||||
"common.infoUpdate": "信息修改",
|
||||
"common.saveInfo": "保存信息",
|
||||
"common.personalAuth": "个人认证",
|
||||
"common.businessAuth": "企业认证",
|
||||
"common.IdCardFont": "上传证件照正面",
|
||||
"common.IdCardBack": "上传证件照反面",
|
||||
"common.UploadBusinessLicense": "上传营业执照",
|
||||
"common.noOpen": "暂未开放,详细请联系工作人员!",
|
||||
"common.isGoAuth": "所有用户必须进行身份验证。方可用仓。",
|
||||
"common.Authentication": "身份验证",
|
||||
"common.submit": "提交",
|
||||
"common.placeInputAll": "请填写完信息后提交",
|
||||
"common.goodsList": "物品清单",
|
||||
"common.OnlineConsultation": "在线咨询",
|
||||
"common.avatar": "头像",
|
||||
"common.uploadAvatar": "上传头像",
|
||||
"common.nickname": "昵称",
|
||||
"common.phone": "手机号",
|
||||
"common.facialData": "数据",
|
||||
"common.bindPhone": "绑定手机号",
|
||||
"common.bindPhoneAfter": "请先绑定手机号再进行此操作",
|
||||
"common.bindPhoneUnlock": "请绑定手机号才能获取订单信息",
|
||||
"common.QuickBind": "快速绑定",
|
||||
"common.cancelBind": "暂不授权",
|
||||
"common.auth": "授权",
|
||||
"common.requireAvatar": "请上传头像",
|
||||
"common.requireName": "请输入昵称",
|
||||
"common.requirePhone": "请输入手机号",
|
||||
"common.note": "备注",
|
||||
"common.tip": "提示",
|
||||
"common.cancelApply": "确定取消申请吗?",
|
||||
"common.cancelSuccess": "取消成功",
|
||||
"common.cancelFail": "取消失败,请稍后重试!",
|
||||
"common.addOrder": "直接下单",
|
||||
"common.AuthenticationFailedTips": "您当前身份信息认证未通过,下单当天可用仓(租赁期内),第二天起需要提交正确的身份信息且验证通过,方可继续用仓。",
|
||||
"common.VacantDay": "空闲",
|
||||
"common.RemainingDay": "剩余",
|
||||
"common.OverdueDay": "逾期",
|
||||
"common.ORDER_AMOUNT_ERROR": "订单金额必须大于0.01元,请重新选择!",
|
||||
"common.userName": "姓名",
|
||||
"common.tuangouCouponPrice": "团购优惠劵金额",
|
||||
"common.Expand": "展开",
|
||||
"common.Collapse": "收起",
|
||||
"common.OtherStores": "其他门店",
|
||||
"coupon.coupon": "优惠券",
|
||||
"coupon.meituanOrdazhongdianpingCoupon": "美团/大众点评优惠劵",
|
||||
"coupon.queryMeituanDazhongdianpingCoupon": "点击查询美团/大众点评优惠劵",
|
||||
"coupon.useTips": "使用说明:填入优惠码兑换即可享受优惠。",
|
||||
"coupon.enterCode": "填入优惠码",
|
||||
"coupon.limitedtimeoffer": "限时优惠",
|
||||
"coupon.storewide": "全店通用",
|
||||
"coupon.redeemNow": "立即兑换",
|
||||
"coupon.instructions": "使用说明",
|
||||
"coupon.validityPeriod": "有效期",
|
||||
"coupon.apply": "立即使用",
|
||||
"coupon.all": "全部仓型",
|
||||
"coupon.unusableCoupons": "当前门店不可用优惠券",
|
||||
"coupon.multiStoreUse": "多店可用",
|
||||
"coupon.renewable": "续单可用",
|
||||
"coupon.noRenewable": "续单不可用",
|
||||
"coupon.redemptionuccessful": "兑换成功",
|
||||
"coupon.currentConditionsNotMet": "当前条件不满足",
|
||||
"request.promoCodeError": "优惠码不正确",
|
||||
"validation.getPhoneFail": "获取手机号失败,请手动输入",
|
||||
"validation.inputName1": "请填写用户姓名",
|
||||
"validation.selectCardType": "请选择证件类型",
|
||||
"validation.inputIdCard": "请填写证件号码",
|
||||
"validation.uploadIdCard": "请上传证件照",
|
||||
"validation.inputPhone": "请填写手机号码",
|
||||
"validation.inputInternationalPhone": "请填写境外号码",
|
||||
"validation.inputInternationalPhoneHk": "请填写香港号码",
|
||||
"validation.uploadImg": "图片正在上传中,请稍后重试",
|
||||
"validation.inputName2": "请填写企业名称",
|
||||
"validation.inputLicense": "请填写营业执照号码",
|
||||
"validation.uploadLicense": "请上传企业证照",
|
||||
"validation.submitSuccess": "提交成功",
|
||||
"validation.uploadSuccess": "修改成功",
|
||||
"validation.identifyCard": "内地身份证",
|
||||
"validation.passport": "护照",
|
||||
"validation.permit": "港澳通行证",
|
||||
"validation.access": "一键获取",
|
||||
"validation.bind": "一键绑定",
|
||||
"validation.vailSuccess": "认证成功",
|
||||
"agreement.readAndAgree": "我已阅读并同意",
|
||||
"agreement.service": "《用户服务协议》",
|
||||
"agreement.and": "及",
|
||||
"agreement.privacy": "《隐私政策》",
|
||||
"agreement.toast": "请先阅读并同意《用户服务协议》和《隐私政策》",
|
||||
"verification.vailFail": "认证不通过,请检查证件类型与证件信息是否正确,请重新上传清晰图片(注意正反面)。",
|
||||
"verification.vailSuccess": "认证成功",
|
||||
"invoiceApply.electronicInvoice": "发票申请",
|
||||
"invoiceApply.paperInvoice": "纸质发票",
|
||||
"invoiceApply.invoiceTips": "您的发票申请已经提交成功,请耐心等候工作人员联系!",
|
||||
"invoice.valid": "可开发票",
|
||||
"invoice.pay": "支付时间",
|
||||
"invoice.site": "门店",
|
||||
"invoice.type": "仓型",
|
||||
"invoice.unit": "仓位",
|
||||
"invoice.rent": "租期",
|
||||
"invoice.record": "申请记录",
|
||||
"invoice.allSelect": "全选",
|
||||
"invoice.nextStep": "下一步",
|
||||
"invoice.tip": "- 订单付款后 一个月内可开发票",
|
||||
"invoice.serial": "编号",
|
||||
"invoice.time": "申请时间",
|
||||
"invoice.status": "审核状态",
|
||||
"invoice.status0": "待审核",
|
||||
"invoice.status1": "审核通过,开票中",
|
||||
"invoice.status2": "审核不通过",
|
||||
"invoice.status3": "已取消",
|
||||
"invoice.status4": "已开票",
|
||||
"invoice.selectOrder": "请选择订单",
|
||||
"invoice.validMoney": "订单金额必须大于0元",
|
||||
"evaluate.customerEvaluation": "顾客评价",
|
||||
"evaluate.overallRating": "综合评分",
|
||||
"evaluate.userExperience": "用户体验",
|
||||
"evaluate.Hospitality": "服务态度",
|
||||
"evaluate.cleanliness": "整洁度",
|
||||
"evaluate.convenience": "便利度",
|
||||
"evaluate.tips": "请在这里留下你宝贵的意见或建议。",
|
||||
"evaluate.anonymous": "匿名",
|
||||
"goodsList.note": "请备注",
|
||||
"goodsList.info": "您好,根据有关部门要求,您存放的物品需要进行物品品类自主申报。本申报单作留底备查之用,请认真填写。",
|
||||
"goodsList.multi": "可多选",
|
||||
"goodsList.tip1": "用户确认:",
|
||||
"goodsList.tip2": "1、存放的物品均为合法渠道获取;",
|
||||
"goodsList.tip3": "2、不存放违禁品;",
|
||||
"goodsList.tip4": "3、由于用户存放物品异变等原因造成的财产毁损或人身伤亡的,由该用户承担责任。",
|
||||
"goodsList.submit": "确认并提交",
|
||||
"houseKey.FriendsName": "亲友姓名",
|
||||
"houseKey.AuthorizationDate": "授权期限",
|
||||
"houseKey.ReceiveNotifications": "接收通知",
|
||||
"houseKey.EnableNotifications": "打开接受通知",
|
||||
"houseKey.PhoneNumber": "电话号码",
|
||||
"houseKey.email": "邮箱",
|
||||
"houseKey.EnterAuthorizedPhoneNumber": "填写授权方的手机号",
|
||||
"houseKey.AddAuthorization": "新增授权",
|
||||
"houseKey.EnterFriendsName": "填写亲友姓名",
|
||||
"houseKey.EnterAuthorizationDate": "填写授权期限",
|
||||
"houseKey.EnterPhoneNumber": "填写电话号码",
|
||||
"houseKey.EnterEmail": "填写邮箱",
|
||||
"houseKey.CannotAuthorizeYourself": "不能授权给自己",
|
||||
"houseKey.UpdateSuccessful": "更新成功",
|
||||
"houseKey.AddedSuccessfully": "新增成功",
|
||||
"houseKey.date": "年-月-日",
|
||||
"houseKey.overdue": "授权期限已过期,请重新选择授权期限并更新",
|
||||
"houseKey.otherPhone": "填写授权方的手机号",
|
||||
"houseKey.otherEmail": "填写授权方的邮箱",
|
||||
"houseKey.getNote": "打开接受通知",
|
||||
"unitTypeDetail.oneMonth": "租期:一个月起租",
|
||||
"unitTypeDetail.reference": "参考",
|
||||
"unitTypeDetail.discount": "优惠",
|
||||
"reserve.FULLNAME": "用户姓名",
|
||||
"reserve.PHONE": "手機號碼",
|
||||
"reserve.REGION": "城市地区",
|
||||
"reserve.TYPE": "所属类型",
|
||||
"reserve.PHONE NUMBER": "手机号码",
|
||||
"reserve.Individual & Family": "个人&家庭",
|
||||
"reserve.Business & E-commerce": "企业&电商",
|
||||
"reserve.Retail & Store": "零售&门店",
|
||||
"reserve.contentTips": "预约成功,请耐心等待工作人员联系,保持手机畅通,感谢!",
|
||||
"inviteDetail.title": "邀请记录",
|
||||
"inviteDetail.Username": "用户名称",
|
||||
"inviteDetail.Registration Date": "注册日期",
|
||||
"inviteDetail.Status": "状态",
|
||||
"inviteDetail.No invitation": "抱歉,暂无邀请记录,请分享邀请。",
|
||||
"inviteDetail.SORRY": "抱歉,没有记录。请分享并邀请。",
|
||||
"inviteDetail.Share Invitation": "分享邀请",
|
||||
"referrerInfo.company": "中介公司",
|
||||
"referrerInfo.branch": "中介分行",
|
||||
"referrerInfo.commission": "中介费百分比",
|
||||
"referrerInfo.inviteRegister": "邀请注册",
|
||||
"referrerInfo.inviteRecord": "邀请记录",
|
||||
"referrerInfo.inviteUserName": "用户",
|
||||
"referrerInfo.invitePhone": "手机号",
|
||||
"referrerInfo.registrationTime": "注册时间",
|
||||
"referrerInfo.inviteEmpty": "暂无邀请记录",
|
||||
"referrerInfo.loadQrCode": "下载二维码",
|
||||
"referrerInfo.loadPoster": "下载海报",
|
||||
"referrerInfo.forwardInvitation": "转发邀请",
|
||||
"pointsMall.title": "积分商城",
|
||||
"pointsMall.myPoints": "我的积分",
|
||||
"pointsMall.pointsUnit": "积分",
|
||||
"pointsMall.exchange": "兑换",
|
||||
"pointsMall.exchangeFormTitle": "填写收货信息",
|
||||
"pointsMall.submit": "提交",
|
||||
"pointsMall.receiverName": "姓名",
|
||||
"pointsMall.phone": "手机号",
|
||||
"pointsMall.address": "地址",
|
||||
"pointsMall.placeholderName": "请输入收货人姓名",
|
||||
"pointsMall.placeholderPhone": "请输入手机号码",
|
||||
"pointsMall.placeholderAddress": "请输入详细收货地址",
|
||||
"pointsMall.exchangeConfirmTitle": "兑换确认",
|
||||
"pointsMall.exchangeConfirmTip": "是否确认使用 {points} 积分兑换?",
|
||||
"pointsMall.stock": "库存",
|
||||
"pointsMall.successTitle": "领取成功",
|
||||
"pointsMall.successTip": "兑换成功,请耐心等候工作人员联系!感谢!",
|
||||
"pointsMall.toastOutOfStock": "库存不足",
|
||||
"pointsMall.toastNotEnoughPoints": "积分不足",
|
||||
"pointsMall.toastExchanging": "兑换中...",
|
||||
"pointsMall.toastExchangeFailed": "兑换失败",
|
||||
"pointsMall.exchangeRecordTitle": "兑换记录",
|
||||
"pointsMall.noExchangeRecord": "暂无兑换记录"
|
||||
}
|
||||
24
locale/zh-Hant.json
Normal file
24
locale/zh-Hant.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"locale.auto": "系統",
|
||||
"locale.en": "English",
|
||||
"locale.zh-hans": "简体中文",
|
||||
"locale.zh-hant": "繁體中文",
|
||||
"locale.ja": "日语",
|
||||
"index.title": "Hello i18n",
|
||||
"index.home": "主頁",
|
||||
"index.component": "組件",
|
||||
"index.api": "API",
|
||||
"index.schema": "Schema",
|
||||
"index.demo": "uni-app 國際化演示",
|
||||
"index.demo-description": "包含 uni-framework、manifest.json、pages.json、tabbar、頁面、組件、API、Schema",
|
||||
"index.detail": "詳情",
|
||||
"index.language": "語言",
|
||||
"index.language-info": "語言信息",
|
||||
"index.system-language": "系統語言",
|
||||
"index.application-language": "應用語言",
|
||||
"index.language-change-confirm": "應用此設置將重啟App",
|
||||
"api.message": "提示",
|
||||
"schema.name": "姓名",
|
||||
"schema.add": "新增",
|
||||
"schema.add-success": "新增成功"
|
||||
}
|
||||
20
main.js
Normal file
20
main.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { createSSRApp } from "vue";
|
||||
import App from "./App";
|
||||
import i18n from "./locale/index";
|
||||
import * as Pinia from "pinia";
|
||||
// import VConsole from 'vconsole'
|
||||
// new VConsole()
|
||||
// import '@/uni.scss'
|
||||
// 引入uvUI
|
||||
import uvUI from "@/uni_modules/uv-ui-tools";
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
app.use(uvUI);
|
||||
app.use(Pinia.createPinia());
|
||||
app.use(i18n);
|
||||
return {
|
||||
app,
|
||||
Pinia,
|
||||
};
|
||||
}
|
||||
113
manifest.json
Normal file
113
manifest.json
Normal file
@ -0,0 +1,113 @@
|
||||
{
|
||||
"name" : "金刚迷你仓",
|
||||
"appid" : "__UNI__FB6F2F3",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
"optimization" : {
|
||||
"subPackages" : true
|
||||
},
|
||||
"runmode" : "liberate", // 开启分包优化后,必须配置资源释放模式
|
||||
|
||||
/* 5+App特有相关 */
|
||||
"usingComponents" : true,
|
||||
"nvueCompiler" : "EliteSys",
|
||||
"nvueStyleCompiler" : "EliteSys",
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
"modules" : {},
|
||||
/* 模块配置 */
|
||||
"distribute" : {
|
||||
/* 应用发布信息 */
|
||||
"android" : {
|
||||
/* android打包配置 */
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
"ios" : {},
|
||||
/* ios打包配置 */
|
||||
"sdkConfigs" : {}
|
||||
}
|
||||
},
|
||||
/* SDK配置 */
|
||||
"quickapp" : {},
|
||||
/* 快应用特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "wx3c4ab696101d77d1",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
"usingComponents" : true,
|
||||
"permission" : {
|
||||
"scope.userLocation" : {
|
||||
"desc" : "将获取你的具体位置信息,用于辅助显示最近店铺"
|
||||
}
|
||||
},
|
||||
"plugins" : {
|
||||
"player" : {
|
||||
"version" : "2.0.0",
|
||||
"provider" : "wxa75efa648b60994b"
|
||||
}
|
||||
},
|
||||
"requiredPrivateInfos" : [ "getLocation" ]
|
||||
},
|
||||
"vueVersion" : "3",
|
||||
"h5" : {
|
||||
"router" : {
|
||||
"base" : "./"
|
||||
},
|
||||
"devServer" : {
|
||||
"port" : 8999,
|
||||
"proxy" : {
|
||||
"/api" : {
|
||||
"target" : "http://localhost:5182",
|
||||
"ws" : true,
|
||||
"changeOrigin" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
"optimization" : {
|
||||
"treeShaking" : {
|
||||
"enable" : true
|
||||
}
|
||||
},
|
||||
"sdkConfigs" : {
|
||||
"maps" : {
|
||||
"qqmap" : {
|
||||
"key" : "B5ZBZ-S4SKW-YYQR5-3BVNP-NX4NQ-FYFYF"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"locale" : "en",
|
||||
"fallbackLocale" : "en",
|
||||
"mp-xhs" : {
|
||||
"appid" : "6786436ac669e40001348567",
|
||||
"permission" : {
|
||||
"scope.userLocation" : {
|
||||
"desc" : "将获取你的具体位置信息,用于辅助显示最近店铺"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1443
package-lock.json
generated
Normal file
1443
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "金刚迷你仓",
|
||||
"version": "1.0.0",
|
||||
"description": "金刚迷你仓",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "YOGO",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue3-google-map": "^0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass-loader": "^10.5.2"
|
||||
}
|
||||
}
|
||||
281
pages.json
Normal file
281
pages.json
Normal file
@ -0,0 +1,281 @@
|
||||
{
|
||||
"subPackages": [
|
||||
{
|
||||
"name": "pagesb",
|
||||
"root": "pagesb",
|
||||
"pages": [
|
||||
{
|
||||
"path": "changeUser/index"
|
||||
},
|
||||
{
|
||||
"path": "referrerInfo/index"
|
||||
},
|
||||
{
|
||||
"path": "pointsMall/index"
|
||||
},
|
||||
{
|
||||
"path": "invitation/index"
|
||||
},
|
||||
{
|
||||
"path": "houseKey/index"
|
||||
},
|
||||
{
|
||||
"path": "flashSale/index"
|
||||
},
|
||||
{
|
||||
"path": "latestEvents/index"
|
||||
},
|
||||
{
|
||||
"path": "activityDetail/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "invoice/index"
|
||||
},
|
||||
{
|
||||
"path": "invoiceApplyforRecord/index"
|
||||
},
|
||||
{
|
||||
"path": "invoiceApply/index"
|
||||
},
|
||||
{
|
||||
"path": "videoTutorial/index"
|
||||
},
|
||||
{
|
||||
"path": "reserve/index"
|
||||
},
|
||||
{
|
||||
"path": "validationInfo/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "实名验证"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "userguide/index"
|
||||
},
|
||||
{
|
||||
"path": "unittypeDetail/index"
|
||||
},
|
||||
{
|
||||
"path": "initLock/index"
|
||||
},
|
||||
{
|
||||
"path": "maskUser/index"
|
||||
},
|
||||
{
|
||||
"path": "AControl/index"
|
||||
}
|
||||
],
|
||||
"plugins": {
|
||||
"ttPlugin": {
|
||||
"version": "3.0.6",
|
||||
"provider": "wx43d5971c94455481"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"mp-weixin": {
|
||||
"usingComponents": {
|
||||
"player-component": "plugin://player/video"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/goodsList/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/renewOrder/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": " "
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/webview/web",
|
||||
"style": {
|
||||
"navigationBarTitleText": " ",
|
||||
"navigationStyle": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/book/book",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Book"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/facecode/facecode",
|
||||
"style": {
|
||||
"navigationBarTitleText": "facecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/book/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Book"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/unlock/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/book/mapmode",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Map"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/site/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "门店"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/setOrder/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Order"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Login"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/register/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Register"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/forgotPawd/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "ForgotPAWd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/book/navigate",
|
||||
"style": {
|
||||
"navigationBarTitleText": " ",
|
||||
"navigationStyle": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/orderdetail/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Orderdetail"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/orderdetail/lock",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Lock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/orderdetail/door",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Door"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal/customerAi",
|
||||
"style": {
|
||||
"navigationBarTitleText": " ",
|
||||
"navigationStyle": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/evaluate/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Evaluate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/call/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Call"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/invite/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Home"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": " ",
|
||||
"iconPath": "static/tabbar/index.png",
|
||||
"selectedIconPath": "static/tabbar/index.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/book/index",
|
||||
"text": " ",
|
||||
"iconPath": "static/tabbar/book.png",
|
||||
"selectedIconPath": "static/tabbar/book.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/call/index",
|
||||
"iconPath": "static/tabbar/call.png",
|
||||
"selectedIconPath": "static/tabbar/call.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/unlock/index",
|
||||
"text": " ",
|
||||
"iconPath": "static/tabbar/unlock.png",
|
||||
"selectedIconPath": "static/tabbar/unlock.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/personal/index",
|
||||
"text": " ",
|
||||
"iconPath": "static/tabbar/personal.png",
|
||||
"selectedIconPath": "static/tabbar/personal.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "Elitesys",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8",
|
||||
"navigationStyle": "custom",
|
||||
"app-plus": {
|
||||
"background": "#efeff4"
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
//模式配置,仅开发期间生效
|
||||
"current": 0, //当前激活的模式(list 的索引项)
|
||||
"list": [
|
||||
{
|
||||
"name": "", //模式名称
|
||||
"path": "", //启动页面,必选
|
||||
"query": "" //启动参数,在页面的onLoad函数里面得到
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
53
pages/book/book.vue
Normal file
53
pages/book/book.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<view style="padding: 100px 0;display: flex;justify-content: center;height: 100%;align-items: center;">
|
||||
<uv-button type="success" size="large" @click="goToSetOrder">跳转下单(Go to Order)</uv-button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useLockApi } from "@/Apis/lock.js";
|
||||
const api = useLockApi();
|
||||
const state = ref({
|
||||
lockerId: "",
|
||||
});
|
||||
|
||||
|
||||
onLoad((params) => {
|
||||
if (params?.q) {
|
||||
let urlParams = decodeURIComponent(params.q);
|
||||
state.value.lockerId = urlParams.split("?id=")[1];
|
||||
goToSetOrder()
|
||||
}
|
||||
});
|
||||
const goToSetOrder= async()=>{
|
||||
try {
|
||||
uni.showLoading();
|
||||
const {code,data} = await api.GetNewLockerId({oldLockerId: state.value.lockerId});
|
||||
uni.hideLoading();
|
||||
if (code === 200) {
|
||||
const id = data || state.value.lockerId;
|
||||
uni.navigateTo({
|
||||
url: `/pages/setOrder/index?id=${id}`,
|
||||
});
|
||||
}else {
|
||||
uni.showToast({
|
||||
title: "数据出错",
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
|
||||
}catch (error) {
|
||||
uni.showToast({
|
||||
title: "数据出错",
|
||||
icon: "none",
|
||||
});
|
||||
uni.hideLoading();
|
||||
console.error("Error:", error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
456
pages/book/index.vue
Normal file
456
pages/book/index.vue
Normal file
@ -0,0 +1,456 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]">
|
||||
<view class="header-wrap">
|
||||
<wxNavbar :title="$t('tabbar.book')"></wxNavbar>
|
||||
<!-- 头部的筛选 -->
|
||||
<view class="header">
|
||||
<view class="header-text" @click="open">
|
||||
<uv-icon name="dropdown" custom-prefix="custom-icon" size="16" :color="themeInfo.iconColor"></uv-icon>
|
||||
{{ $t("book.location") }}: {{ popupData.selectCity }}
|
||||
</view>
|
||||
<view class="header-text" @click="tomapMode">
|
||||
{{ $t("book.map") }}
|
||||
<uv-icon name="setting1" custom-prefix="custom-icon" size="16" :color="themeInfo.iconColor"></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- location窗口 -->
|
||||
<uv-overlay :show="popupData.show" @click="open" z-index="99" >
|
||||
<view class="location-popup">
|
||||
<view class="select-area-wrap" :style="{ 'margin-top': `${state.navHeight}px` }" @click.stop.prevent>
|
||||
<view class="city inner-wrap">
|
||||
<view class="top-wrap">
|
||||
<uv-icon name="halfArrow" custom-prefix="custom-icon" size="16" color="#0F2232"></uv-icon>
|
||||
<text class="text">{{ $t("book.city") }}</text>
|
||||
</view>
|
||||
<view class="select-wrap">
|
||||
<view
|
||||
class="select-item"
|
||||
v-for="(item, index) in popupData.cityData"
|
||||
:key="index"
|
||||
:class="{ select: item === popupData.selectCity }"
|
||||
@click="handleCity(item)">
|
||||
<view class="circle"></view>
|
||||
<view class="name">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="border"></view>
|
||||
<view class="area inner-wrap">
|
||||
<view class="top-wrap">
|
||||
<uv-icon name="halfArrow" custom-prefix="custom-icon" size="16" color="#0F2232"></uv-icon>
|
||||
<text class="text">{{ $t("book.area") }}</text>
|
||||
</view>
|
||||
<view class="select-wrap">
|
||||
<view
|
||||
class="select-item"
|
||||
v-for="(item, index) in popupData.areaData"
|
||||
:key="index"
|
||||
:class="{ select: item === popupData.selectDistrict }"
|
||||
@click="handleDistrict(item)">
|
||||
<view class="circle"></view>
|
||||
<view class="name">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uv-overlay>
|
||||
|
||||
<!-- 热推门店详情 -->
|
||||
<view class="get-location-wrap" @click="handleAuthorize" v-if="locationState.showGetLocation">
|
||||
{{ $t("book.get") }}
|
||||
</view>
|
||||
<view :style="{ 'margin-top': `${state.navHeight}px` }" class="shopDetail" v-if="siteData.list?.length">
|
||||
<siteDetail v-for="item in siteData.list" :key="item.id" :siteItem="item" @showCode="handleShowCode"></siteDetail>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<myCustomtTabBar direction="horizontal" :show-icon="true" :selected="2" @onTabItemTap="onTabItemTap" />
|
||||
</view>
|
||||
<!-- 门禁二维码 -->
|
||||
<myPopup v-model="state.showQrcode" bgColor="transparent">
|
||||
<view class="qrcode-wrap">
|
||||
<view class="get-code-btn" v-show="!state.qrcodeUrl">
|
||||
<uv-button @click="GetQRCode">
|
||||
{{ $t("book.getCode") }}
|
||||
</uv-button>
|
||||
</view>
|
||||
<image class="qrcodeImg" :src="state.qrcodeUrl"></image>
|
||||
</view>
|
||||
<view class="btn-wrap">
|
||||
<uv-button @click="RemoteOpenDoor" :loading="state.openDoorLoading" :loadingText="t('unlock.remoteOpenLoading')">{{ state.openDoorText }}</uv-button>
|
||||
</view>
|
||||
</myPopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from "vue";
|
||||
import { onLoad, onShow, onHide, onShareAppMessage} from "@dcloudio/uni-app";
|
||||
import { onTabItemTap, getDistance, navbarHeightAndStatusBarHeight,shareParam,mergeFiveGoatStores } from "@/utils/common.js";
|
||||
import { ClientSite } from "@/Apis/book.js";
|
||||
import wxNavbar from "@/components/wxNavbar.vue";
|
||||
import myCustomtTabBar from "@/components/myCustomtTabBar.vue";
|
||||
import siteDetail from '@/components/siteDetail.vue';
|
||||
import myPopup from '@/components/myPopup.vue';
|
||||
import { useLockApi } from '@/Apis/lock.js';
|
||||
import { AppId } from '@/config/index.js'
|
||||
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo, storeState } = useMainStore();
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
import { useLocation } from "@/hooks/useLocation";
|
||||
const { locationState, openLocationAuthorize, getLocation } = useLocation();
|
||||
const getLockApi = useLockApi();
|
||||
const getApi = ClientSite();
|
||||
const state = reactive({
|
||||
navHeight: 0,
|
||||
showQrcode: false,
|
||||
qrcodeUrl: '',
|
||||
firstLoad: false,
|
||||
openDoorLoading: false,
|
||||
clickItem: {},
|
||||
openDoorText: t('unlock.remoteOpen'),
|
||||
});
|
||||
onShareAppMessage((res) => {
|
||||
if (res.from === "button") {
|
||||
// 来自页面内分享按钮
|
||||
console.log(res.target);
|
||||
}
|
||||
return shareParam;
|
||||
});
|
||||
onLoad(() => {
|
||||
uni.hideTabBar();
|
||||
state.navHeight = Number(navbarHeightAndStatusBarHeight().navbarHeight) + 50;
|
||||
// 获取城市数据
|
||||
getCityData();
|
||||
state.firstLoad = true;
|
||||
// 小红书onshow第一次 不会触发
|
||||
// #ifdef MP-XHS
|
||||
getLocation().finally(() => {
|
||||
getSiteDetail();
|
||||
// #ifndef MP-WEIXIN
|
||||
locationState.showGetLocation = false;
|
||||
// #endif
|
||||
});
|
||||
// #endif
|
||||
});
|
||||
onShow(() => {
|
||||
getLocation().finally(() => {
|
||||
getSiteDetail();
|
||||
// #ifndef MP-WEIXIN
|
||||
locationState.showGetLocation = false;
|
||||
// #endif
|
||||
});
|
||||
});
|
||||
onHide(() => {
|
||||
state.firstLoad = false;
|
||||
});
|
||||
|
||||
/**
|
||||
* 开门功能相关
|
||||
*/
|
||||
const handleShowCode = (item) => {
|
||||
state.clickItem = item;
|
||||
state.showQrcode = true;
|
||||
state.qrcodeUrl = '';
|
||||
}
|
||||
|
||||
// 远程开门
|
||||
const RemoteOpenDoor = () => {
|
||||
if (!state.clickItem?.id || state.openDoorLoading) return;
|
||||
state.openDoorLoading = true;
|
||||
getLockApi.RemoteOpenDoor({ siteId: state.clickItem.id }).then(res => {
|
||||
state.openDoorLoading = false;
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: t('unlock.remoteOpenSuccess'),
|
||||
icon: 'none'
|
||||
});
|
||||
state.openDoorText = t('unlock.remoteOpenSuccess');
|
||||
setTimeout(() => {
|
||||
state.openDoorText = t('unlock.remoteOpen');
|
||||
}, 3000);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: t('unlock.remoteOpenFail'),
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取二维码
|
||||
const GetQRCode = () => {
|
||||
if (!state.clickItem?.id) return;
|
||||
uni.showLoading();
|
||||
getLockApi.GetAccesscontrolQRCodeBySite({ siteId: state.clickItem.id }).then(res => {
|
||||
if (res.code === 200) {
|
||||
state.qrcodeUrl = res.data;
|
||||
}
|
||||
uni.hideLoading();
|
||||
});
|
||||
}
|
||||
|
||||
// 跳转到地图页
|
||||
const tomapMode = ()=>{
|
||||
uni.navigateTo({
|
||||
url: "/pages/book/mapmode"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶部popup 相关功能
|
||||
*/
|
||||
const popupData = reactive({
|
||||
show: false,
|
||||
selectCity: "",
|
||||
selectDistrict: "",
|
||||
cityData: [],
|
||||
areaData: [],
|
||||
});
|
||||
const open = () => {
|
||||
popupData.show = !popupData.show;
|
||||
}
|
||||
const getCityData = () => {
|
||||
getApi.GetCityAll().then(res => {
|
||||
if(res.code === 200) {
|
||||
popupData.cityData = res.data;
|
||||
popupData.cityData.unshift("全部");
|
||||
// 首次直接选择全部
|
||||
popupData.selectCity = popupData.cityData[0];
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleCity = (city) => {
|
||||
popupData.selectCity = city;
|
||||
if (city === "全部") {
|
||||
popupData.areaData = ["全部"];
|
||||
popupData.selectDistrict = "全部";
|
||||
getSiteDetail();
|
||||
} else {
|
||||
getApi.GetDistrictByCity(city).then(res => {
|
||||
if (res.code === 200) {
|
||||
popupData.areaData = res.data;
|
||||
popupData.areaData.unshift("全部");
|
||||
popupData.selectDistrict = popupData.areaData[0];
|
||||
}
|
||||
|
||||
let currentCity = city === "全部" ? "" : city;
|
||||
getSiteDetail(currentCity, "");
|
||||
});
|
||||
}
|
||||
}
|
||||
const handleDistrict = (item) => {
|
||||
popupData.selectDistrict = item;
|
||||
let currentCity = popupData.selectCity === "全部" ? "" : popupData.selectCity;
|
||||
let district = item === "全部" ? "" : item;
|
||||
getSiteDetail(currentCity, district);
|
||||
}
|
||||
let siteData = reactive({
|
||||
list: [],
|
||||
isLoading: false,
|
||||
});
|
||||
// 获取门店信息
|
||||
const getSiteDetail = (city, district,isAll) => {
|
||||
if (siteData.isLoading) return;
|
||||
// #ifdef MP-WEIXIN
|
||||
// if (locationState.showGetLocation) {
|
||||
// uni.showToast({
|
||||
// title: t("book.getSite"),
|
||||
// icon: "none",
|
||||
// duration: 2000
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// #endif
|
||||
uni.showLoading();
|
||||
siteData.isLoading = true;
|
||||
let getCity = city || "";
|
||||
let getDistrict = district || "";
|
||||
if (!city) {
|
||||
getCity = popupData.selectCity == "全部" ? "" : popupData.selectCity;
|
||||
getDistrict = popupData.selectDistrict == "全部" ? "" : popupData.selectDistrict;
|
||||
}
|
||||
if(isAll){
|
||||
getCity = "";
|
||||
getDistrict = "";
|
||||
}
|
||||
getApi.getSiteDetailsAll({
|
||||
city: getCity,
|
||||
district: getDistrict
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
siteData.list = mergeFiveGoatStores(res.data);
|
||||
if (locationState?.latitude && locationState?.longitude) {
|
||||
const { latitude, longitude } = locationState;
|
||||
siteData.list.forEach(item => {
|
||||
const { distance, number } = getDistance(latitude, longitude, item.latitude, item.longitude);
|
||||
item.distance = distance;
|
||||
item.distanceNumber = number;
|
||||
});
|
||||
siteData.list.sort((a, b) => a.distanceNumber - b.distanceNumber);
|
||||
if (state.firstLoad) filterSiteData();
|
||||
}else {
|
||||
// 如果是金刚就不是显示全部
|
||||
if(AppId === 'wxb20921dfdd0b94f4' || AppId === 'wx3c4ab696101d77d1') {
|
||||
if (state.firstLoad) filterSiteData(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
uni.hideLoading();
|
||||
siteData.isLoading = false;
|
||||
}).catch(err => {
|
||||
uni.hideLoading();
|
||||
siteData.isLoading = false;
|
||||
});
|
||||
}
|
||||
const handleAuthorize = () => {
|
||||
openLocationAuthorize().then(res => {
|
||||
state.firstLoad = true;
|
||||
if (res) getSiteDetail('全部','全部',true);
|
||||
});
|
||||
}
|
||||
// 首次只显示距离最近的城市 noLocation 沒有定位的情况下顯示深圳
|
||||
const filterSiteData = (noLocation) => {
|
||||
state.firstLoad = false;
|
||||
if (!siteData.list.length) return;
|
||||
let city = siteData.list[0]['city'];
|
||||
// 如果是金刚的appid,默认显示深圳的门店
|
||||
if(noLocation){
|
||||
if(AppId === 'wxb20921dfdd0b94f4' || AppId === 'wx3c4ab696101d77d1') {
|
||||
city = siteData.list.find(item => item.city.indexOf("深圳") !== -1)?.city;
|
||||
}
|
||||
}
|
||||
siteData.list = siteData.list.filter((item) => item.city.indexOf(city) !== -1);
|
||||
popupData.selectCity = city;
|
||||
getApi.GetDistrictByCity(city).then(res => {
|
||||
if (res.code === 200) {
|
||||
popupData.areaData = res.data;
|
||||
popupData.areaData.unshift("全部");
|
||||
popupData.selectDistrict = popupData.areaData[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/static/style/theme.scss';
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.qrcode-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 18rpx;
|
||||
|
||||
.get-code-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.qrcodeImg {
|
||||
background-color: #FFFFFF;
|
||||
height: 600rpx;
|
||||
width: 600rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrap {
|
||||
width: 600rpx;
|
||||
margin: 0 auto;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.qrcode-wrap,
|
||||
.btn-wrap {
|
||||
::v-deep .uv-button {
|
||||
color: var(--text-color);
|
||||
background-color: var(--btn-color1);
|
||||
border: none;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
text-justify: 20rpx;
|
||||
|
||||
.uv-button__loading-text {
|
||||
font-size: 32rpx!important;
|
||||
font-weight: bold;
|
||||
text-justify: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.header-wrap {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
background: linear-gradient(to bottom, var(--left-linear), var(--right-linear));
|
||||
z-index: 999;
|
||||
|
||||
::v-deep .wxNavbar {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
// 头部的筛选
|
||||
.header {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--whiteOrBlack);
|
||||
|
||||
& > .header-icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 门店列表
|
||||
.shopDetail {
|
||||
width: 100%;
|
||||
margin-bottom: 300rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.get-location-wrap {
|
||||
position: fixed;
|
||||
height: 74rpx;
|
||||
background-color: var(--main-color);
|
||||
border-radius: 45rpx 0rpx 0rpx 45rpx;
|
||||
border: 4rpx solid var(--stress-text);
|
||||
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(0, 0, 0, 0.1);
|
||||
right: -4px;
|
||||
top: 75%;
|
||||
padding: 0 20rpx 0 30rpx;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--stress-text);
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
174
pages/book/map.vue
Normal file
174
pages/book/map.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div class="map" id="map">
|
||||
<!-- #ifdef H5 -->
|
||||
<!-- <GoogleMap
|
||||
ref="googleMap"
|
||||
api-key="AIzaSyC95SewUgAsDlcERNpJGxb845VoFGkAU2c"
|
||||
style="width: 100%; height: 100%"
|
||||
:center="center"
|
||||
:zoom="15"
|
||||
>
|
||||
<CustomMarker v-for="(item,index) in props.markerList" :key='index' :options="{ position: item }">
|
||||
<div class='markerBox' @click="markerClick(item,index)">
|
||||
<div class="text">{{item.name}}</div>
|
||||
<div class="circle"></div>
|
||||
</div>
|
||||
</CustomMarker>
|
||||
</GoogleMap> -->
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- <map style="width: 100%; height: 100%;" @markertap='markertap' :latitude="center.lat" :longitude="center.lng" :markers="WeixinMarkerList">
|
||||
</map> -->
|
||||
<!-- #endif -->
|
||||
<map style="width: 750rpx; height: 100%;" :scale="3" @markertap='markertap' :latitude="center.lat" :include-points="includePoints" :show-location="true" :longitude="center.lng" :markers="WeixinMarkerList">
|
||||
</map>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { wgs84ToGcj02 } from '@/utils/map.js'
|
||||
import {
|
||||
ref, watch,watchEffect
|
||||
} from 'vue';
|
||||
// #ifdef H5
|
||||
import { GoogleMap,CustomMarker } from 'vue3-google-map';
|
||||
// #endif
|
||||
// onLoad(()=>{
|
||||
// uni.hideTabBar()
|
||||
// })
|
||||
const includePoints = ref([]) //缩放视野以包含所有给定的坐标点
|
||||
const emit = defineEmits(["markerClick"])
|
||||
const props = defineProps({
|
||||
markerList:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
locationState:{
|
||||
type:Object,
|
||||
default:()=>{}
|
||||
},
|
||||
})
|
||||
const markertap = (e)=>{
|
||||
const event = JSON.parse(JSON.stringify(WeixinMarkerList.value[e.detail.markerId]||{}))
|
||||
event.index = event.id
|
||||
event.id = event.oId
|
||||
center.value = {lat:event.latitude,lng:event.longitude}
|
||||
currentMarker.value = event.index;
|
||||
emit('markerClick',{event,index:event.index })
|
||||
}
|
||||
const WeixinMarkerList = ref([]);
|
||||
const currentMarker = ref(0); // 当前选择的 marker 的 index
|
||||
// watch(()=>props.markerList,()=>{
|
||||
// WeixinMarkerList.value = props.markerList.map((item,index)=>{
|
||||
// const [lng,lat] = wgs84ToGcj02(item.lng,item.lat)
|
||||
// return {
|
||||
// ...item,
|
||||
// oId:item.id,
|
||||
// id:index,
|
||||
// latitude: lat,
|
||||
// longitude: lng,
|
||||
// iconPath:'/static/book/noSelectMapIcon.png'
|
||||
// }
|
||||
// })
|
||||
// if(WeixinMarkerList.value.length){
|
||||
// // center.value = {lat:WeixinMarkerList.value[0].latitude,lng:WeixinMarkerList.value[0].longitude}
|
||||
// }
|
||||
// includePoints.value = WeixinMarkerList.value.map((item)=>{
|
||||
// return {
|
||||
// latitude:item.latitude,
|
||||
// longitude:item.longitude
|
||||
// }
|
||||
// })
|
||||
// },{ deep: true })
|
||||
|
||||
watch(() => props.markerList, () => {
|
||||
currentMarker.value = 0;
|
||||
});
|
||||
|
||||
watchEffect(()=>{
|
||||
WeixinMarkerList.value = props.markerList.map((item,index)=>{
|
||||
// todo 兼容谷歌未完善
|
||||
// const [lng,lat] = wgs84ToGcj02(item.lng,item.lat)
|
||||
return {
|
||||
...item,
|
||||
oId:item.id,
|
||||
id:index,
|
||||
latitude: item.lat,
|
||||
width:20,
|
||||
height:20,
|
||||
longitude: item.lng,
|
||||
iconPath: currentMarker.value == index ? '/static/book/selectMapIcon.png' : '/static/book/noSelectMapIcon.png'
|
||||
}
|
||||
});
|
||||
includePoints.value = WeixinMarkerList.value.map((item)=>{
|
||||
return {
|
||||
latitude:item.latitude,
|
||||
longitude:item.longitude
|
||||
}
|
||||
})
|
||||
if(props.locationState.latitude && props.locationState.longitude){
|
||||
|
||||
center.value = {lat:props.locationState.latitude,lng:props.locationState.longitude}
|
||||
includePoints.value.push({
|
||||
latitude: center.value.lat,
|
||||
longitude: center.value.lng
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const googleMap = ref();
|
||||
const marker = ref()
|
||||
|
||||
const selectMarker = ref()
|
||||
const defaultCenter = { lat: 22.31615301, lng: 114.16999981 }; //香港的坐标
|
||||
const center = ref(defaultCenter)
|
||||
const markerClick=(event,index)=>{
|
||||
center.value = event
|
||||
emit('markerClick',{event,index})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/static/style/theme.scss';
|
||||
.map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.markerBox{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
text-shadow:
|
||||
-1px -1px 0 #000,
|
||||
1px -1px 0 #000,
|
||||
-1px 1px 0 #000,
|
||||
1px 1px 0 #000;
|
||||
|
||||
|
||||
}
|
||||
.text{
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
.circle{
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
border-radius: 99rpx;
|
||||
border: 8rpx solid #00C8D5;
|
||||
background-color: #005A6B;
|
||||
|
||||
}
|
||||
.circle.click{
|
||||
border-radius: 99rpx;
|
||||
border: 8rpx solid #049EBB;
|
||||
background-color: #00F6D4;
|
||||
}
|
||||
/* #ifdef MP-XHS */
|
||||
xhs-map {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
364
pages/book/mapmode.vue
Normal file
364
pages/book/mapmode.vue
Normal file
@ -0,0 +1,364 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]">
|
||||
<view class="header-wrap">
|
||||
<wxNavbar :title="$t('book.map')"></wxNavbar>
|
||||
<!-- 头部的筛选 -->
|
||||
<view class="header">
|
||||
<view class="header-text" @click="open">
|
||||
<uv-icon name="dropdown" custom-prefix="custom-icon" size="16" :color="themeInfo.iconColor"></uv-icon>
|
||||
{{ $t("book.location") }}: {{ popupData.selectCity }}
|
||||
</view>
|
||||
<view class="header-text" @click="toList">
|
||||
{{ $t("book.list") }}
|
||||
<uv-icon name="setting1" custom-prefix="custom-icon" size="16" :color="themeInfo.iconColor"></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- location窗口 -->
|
||||
<uv-overlay :show="popupData.show" @click="open" z-index="99" >
|
||||
<view class="location-popup">
|
||||
<view class="select-area-wrap" :style="{ 'margin-top': `${state.navHeight}px` }" @click.stop.prevent>
|
||||
<view class="city inner-wrap">
|
||||
<view class="top-wrap">
|
||||
<uv-icon name="halfArrow" custom-prefix="custom-icon" size="16" color="#0F2232"></uv-icon>
|
||||
<text class="text">{{ $t("book.city") }}</text>
|
||||
</view>
|
||||
<view class="select-wrap">
|
||||
<view
|
||||
class="select-item"
|
||||
v-for="(item, index) in popupData.cityData"
|
||||
:key="index"
|
||||
:class="{ select: item === popupData.selectCity }"
|
||||
@click="handleCity(item)">
|
||||
<view class="circle"></view>
|
||||
<view class="name">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="border"></view>
|
||||
<view class="area inner-wrap">
|
||||
<view class="top-wrap">
|
||||
<uv-icon name="halfArrow" custom-prefix="custom-icon" size="16" color="#0F2232"></uv-icon>
|
||||
<text class="text">{{ $t("book.area") }}</text>
|
||||
</view>
|
||||
<view class="select-wrap">
|
||||
<view
|
||||
class="select-item"
|
||||
v-for="(item, index) in popupData.areaData"
|
||||
:key="index"
|
||||
:class="{ select: item === popupData.selectDistrict }"
|
||||
@click="handleDistrict(item)">
|
||||
<view class="circle"></view>
|
||||
<view class="name">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uv-overlay>
|
||||
|
||||
<!-- 地图 -->
|
||||
<view class="mapBpx" :style="{ 'padding-top': `${state.navHeight}px` }" v-show="siteData.markerList">
|
||||
<GoogleMap :markerList="siteData.markerList" :locationState="locationState" @markerClick="markerClick"></GoogleMap>
|
||||
<view class="shopDetail" v-if="state.showMapDetail && siteData.selectSite?.id">
|
||||
<view class="shop-detail">
|
||||
<view class="close">
|
||||
<uv-icon name="close" size="16" color="#969799" @click="closeMapDetail"></uv-icon>
|
||||
</view>
|
||||
<!-- 店铺图片 -->
|
||||
<siteDetail :siteItem="siteData.selectSite"></siteDetail>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="get-location-wrap" @click="handleAuthorize" v-if="locationState.showGetLocation">
|
||||
{{ $t("book.get") }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import wxNavbar from "@/components/wxNavbar.vue";
|
||||
import siteDetail from '@/components/siteDetail.vue';
|
||||
import GoogleMap from "@/pages/book/map.vue";
|
||||
import { ClientSite } from "/Apis/book.js";
|
||||
|
||||
import { getDistance, navbarHeightAndStatusBarHeight } from "@/utils/common.js";
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
import { useLocation } from "@/hooks/useLocation";
|
||||
const { locationState, openLocationAuthorize, getLocation } = useLocation();
|
||||
|
||||
const getApi = ClientSite();
|
||||
|
||||
const state = ref({
|
||||
showMapDetail: true,
|
||||
navHeight: 0,
|
||||
firstLoad: false,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
state.firstLoad = true;
|
||||
getCityData();
|
||||
state.value.navHeight = Number(navbarHeightAndStatusBarHeight().navbarHeight) + 50;
|
||||
|
||||
getLocation().finally(() => {
|
||||
getSiteDetail();
|
||||
// #ifndef MP-WEIXIN
|
||||
locationState.showGetLocation = false;
|
||||
// #endif
|
||||
});
|
||||
});
|
||||
|
||||
const markerClick = (event) => {
|
||||
siteData.selectSite = event.event;
|
||||
// #ifdef MP-WEIXIN
|
||||
if (locationState?.latitude && locationState?.longitude) {
|
||||
let distanceData = getDistance(locationState.latitude, locationState.longitude, siteData.selectSite.latitude, siteData.selectSite.longitude);
|
||||
siteData.selectSite.distance = distanceData.distance;
|
||||
}
|
||||
// #endif
|
||||
state.value.showMapDetail = true;
|
||||
};
|
||||
const toList = () => {
|
||||
uni.switchTab({
|
||||
url: "/pages/book/index"
|
||||
});
|
||||
};
|
||||
const closeMapDetail = () => {
|
||||
state.value.showMapDetail = false;
|
||||
};
|
||||
|
||||
const siteData = reactive({
|
||||
list: [],
|
||||
markerList: [],
|
||||
selectSite: {},
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
const setMarkerList = () => {
|
||||
// 只显示有经纬度的门店
|
||||
let list = siteData.list.filter((item) => Number(item.latitude) && Number(item.longitude));
|
||||
// 获取门店距离,底部显示最近的门店
|
||||
if (locationState?.latitude && locationState?.longitude) {
|
||||
list.forEach(item => {
|
||||
let distanceData = getDistance(locationState.latitude, locationState.longitude, item.latitude, item.longitude);
|
||||
item.distance = distanceData.distance;
|
||||
item.distanceNumber = distanceData.number;
|
||||
});
|
||||
list.sort((a, b) => a.distanceNumber - b.distanceNumber);
|
||||
}
|
||||
siteData.list = list;
|
||||
if (state.firstLoad) {
|
||||
filterSiteData();
|
||||
} else {
|
||||
siteData.markerList = list.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
lat: Number(item.latitude),
|
||||
lng: Number(item.longitude)
|
||||
};
|
||||
});
|
||||
siteData.selectSite = siteData.markerList[0] || {};
|
||||
}
|
||||
};
|
||||
|
||||
// 获取门店信息
|
||||
const getSiteDetail = (city, district) => {
|
||||
if (siteData.isLoading) return;
|
||||
// #ifdef MP-WEIXIN
|
||||
// if (locationState.showGetLocation) {
|
||||
// uni.showToast({
|
||||
// title: t("book.getSite"),
|
||||
// icon: "none",
|
||||
// duration: 2000
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// #endif
|
||||
uni.showLoading();
|
||||
state.isLoading = true;
|
||||
getApi.getSiteDetailsAll({
|
||||
city: city || "",
|
||||
district: district || ""
|
||||
}).then((res) => {
|
||||
if (res.code === 200) {
|
||||
siteData.list = res.data;
|
||||
setMarkerList();
|
||||
}
|
||||
siteData.isLoading = false;
|
||||
uni.hideLoading();
|
||||
});
|
||||
}
|
||||
|
||||
// 首次只显示距离最近的城市
|
||||
const filterSiteData = () => {
|
||||
state.firstLoad = false;
|
||||
if (!siteData.list.length) return;
|
||||
let city = siteData.list[0]['city'];
|
||||
siteData.list = siteData.list.filter((item) => item.city == city);
|
||||
siteData.markerList = siteData.list.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
lat: Number(item.latitude),
|
||||
lng: Number(item.longitude)
|
||||
};
|
||||
});
|
||||
siteData.selectSite = siteData.markerList[0] || {};
|
||||
popupData.selectCity = city;
|
||||
getApi.GetDistrictByCity(city).then(res => {
|
||||
if (res.code === 200) {
|
||||
popupData.areaData = res.data;
|
||||
popupData.areaData.unshift("全部");
|
||||
popupData.selectDistrict = popupData.areaData[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶部popup 相关功能
|
||||
*/
|
||||
const popupData = reactive({
|
||||
show: false,
|
||||
selectCity: "",
|
||||
selectDistrict: "",
|
||||
cityData: [],
|
||||
areaData: [],
|
||||
});
|
||||
const open = () => {
|
||||
popupData.show = !popupData.show;
|
||||
}
|
||||
const getCityData = () => {
|
||||
uni.showLoading();
|
||||
getApi.GetCityAll().then(res => {
|
||||
if(res.code === 200) {
|
||||
popupData.cityData = res.data;
|
||||
popupData.cityData.unshift("全部");
|
||||
// 首次直接选择全部
|
||||
popupData.selectCity = popupData.cityData[0];
|
||||
}
|
||||
uni.hideLoading();
|
||||
})
|
||||
}
|
||||
const handleCity = (city) => {
|
||||
popupData.selectCity = city;
|
||||
if (city === "全部") {
|
||||
popupData.areaData = ["全部"];
|
||||
popupData.selectDistrict = "全部";
|
||||
getSiteDetail();
|
||||
} else {
|
||||
uni.showLoading();
|
||||
getApi.GetDistrictByCity(city).then(res => {
|
||||
if (res.code === 200) {
|
||||
popupData.areaData = res.data;
|
||||
popupData.areaData.unshift("全部");
|
||||
popupData.selectDistrict = popupData.areaData[0];
|
||||
}
|
||||
uni.hideLoading();
|
||||
|
||||
let currentCity = city === "全部" ? "" : city;
|
||||
getSiteDetail(currentCity, "");
|
||||
});
|
||||
}
|
||||
}
|
||||
const handleDistrict = (item) => {
|
||||
popupData.selectDistrict = item;
|
||||
let currentCity = popupData.selectCity === "全部" ? "" : popupData.selectCity;
|
||||
let district = item === "全部" ? "" : item;
|
||||
getSiteDetail(currentCity, district);
|
||||
}
|
||||
const handleAuthorize = () => {
|
||||
openLocationAuthorize().then(res => {
|
||||
if (res) getSiteDetail();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.header-wrap {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
background: linear-gradient(to bottom, var(--left-linear), var(--right-linear));
|
||||
z-index: 999;
|
||||
|
||||
::v-deep .wxNavbar {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 头部的筛选
|
||||
.header {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--whiteOrBlack);
|
||||
|
||||
& > .header-icon {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mapBpx {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
.shopDetail {
|
||||
pointer-events: none; /* 使子元素对鼠标事件透明 */
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
padding: 30rpx 0;
|
||||
background: #FFFFFF;
|
||||
border-radius: 20px 20px 0 0;
|
||||
z-index: 9;
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 20rpx;
|
||||
}
|
||||
|
||||
.shop-detail {
|
||||
pointer-events: all;
|
||||
padding-bottom: 10rpx;
|
||||
flex-wrap: wrap;
|
||||
|
||||
:deep(.site-detail) {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.get-location-wrap {
|
||||
margin-top: 400rpx;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
border-top: 1px solid #DDDDDD;
|
||||
border-bottom: 1px solid #DDDDDD;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
88
pages/book/navigate.vue
Normal file
88
pages/book/navigate.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- <nav-bar></nav-bar> -->
|
||||
<rich-text :nodes="siteGuideData"></rich-text>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad, onShareAppMessage } from '@dcloudio/uni-app'
|
||||
import { ClientSite } from '/Apis/book.js'
|
||||
import { projectInfo } from '@/config'
|
||||
// import NavBar from '../../components/navBar.vue';
|
||||
|
||||
onLoad((option) => {
|
||||
siteId.value = option.id
|
||||
type.value = option.type
|
||||
getData()
|
||||
})
|
||||
|
||||
onShareAppMessage((res) => {
|
||||
return {
|
||||
title: `${projectInfo.miniName}`,
|
||||
path: `/pages/book/navigate?id=${siteId.value}&type=${type.value}`
|
||||
};
|
||||
});
|
||||
|
||||
const getApi = ClientSite()
|
||||
const siteId = ref(null)
|
||||
const type = ref()
|
||||
const siteGuideData = ref([])
|
||||
const getData = ()=>{
|
||||
// tpye 1 是路线指引 2是停车指引
|
||||
if(type.value==2){
|
||||
getStopCar()
|
||||
}else{
|
||||
getSiteGuide()
|
||||
}
|
||||
}
|
||||
function getStopCar() {
|
||||
uni.showLoading()
|
||||
getApi.GetSiteStopCarGuideById(siteId.value).then(res => {
|
||||
uni.hideLoading()
|
||||
if(res.code === 200) {
|
||||
siteGuideData.value = res.data
|
||||
}
|
||||
}).catch(err => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function getSiteGuide() {
|
||||
uni.showLoading()
|
||||
getApi.GetSiteGuideById(siteId.value).then(res => {
|
||||
uni.hideLoading()
|
||||
if(res.code === 200) {
|
||||
siteGuideData.value = res.data
|
||||
}
|
||||
}).catch(err => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/static/style/theme.scss';
|
||||
.container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
// background: linear-gradient(to bottom, #0A83B7, #00AFBD);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
.image {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
margin-left: 40rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
</style>
|
||||
12
pages/call/index.vue
Normal file
12
pages/call/index.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
空白页面站位
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
396
pages/evaluate/index.vue
Normal file
396
pages/evaluate/index.vue
Normal file
@ -0,0 +1,396 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar class="navBar"></navBar>
|
||||
<view class="container-form">
|
||||
<view class="form">
|
||||
<view class="topBox">
|
||||
{{ $t('evaluate.customerEvaluation') }}
|
||||
</view>
|
||||
<view class="midBox">
|
||||
<!-- Overall rating评分 -->
|
||||
<view class="midBox-detail">
|
||||
<view class="circle"></view>
|
||||
<view class="rate">{{ $t("evaluate.overallRating") }}</view>
|
||||
<uv-rate
|
||||
class="uv-rate"
|
||||
:count="5"
|
||||
v-model="rate.overallRating"
|
||||
:activeColor="themeInfo.activeColor"
|
||||
inactiveColor="rgb(13, 32, 49)"
|
||||
size="40rpx"
|
||||
>
|
||||
</uv-rate>
|
||||
</view>
|
||||
<!-- User experience -->
|
||||
<view class="midBox-detail-1">
|
||||
<view class="circle"></view>
|
||||
<view class="rate">{{ $t("evaluate.userExperience") }}</view>
|
||||
<view class="border"></view>
|
||||
</view>
|
||||
<!-- Hospitality评分 -->
|
||||
<view class="midBox-detail-2">
|
||||
<view class="line"></view>
|
||||
<view class="rate">{{ $t("evaluate.Hospitality") }}</view>
|
||||
<uv-rate
|
||||
class="uv-rate"
|
||||
:count="5"
|
||||
v-model="rate.Hospitality"
|
||||
:activeColor="themeInfo.activeColor"
|
||||
inactiveColor="rgb(13, 32, 49)"
|
||||
size="40rpx"
|
||||
>
|
||||
</uv-rate>
|
||||
</view>
|
||||
<!-- Cleanliness评分 -->
|
||||
<view class="midBox-detail-2">
|
||||
<view class="line"></view>
|
||||
<view class="rate">{{ $t("evaluate.cleanliness") }}</view>
|
||||
<uv-rate
|
||||
class="uv-rate"
|
||||
:count="5"
|
||||
v-model="rate.cleanliness"
|
||||
:activeColor="themeInfo.activeColor"
|
||||
inactiveColor="rgb(13, 32, 49)"
|
||||
size="40rpx"
|
||||
>
|
||||
</uv-rate>
|
||||
</view>
|
||||
<!-- Convenience评分 -->
|
||||
<view class="midBox-detail-2">
|
||||
<view class="line"></view>
|
||||
<view class="rate">{{ $t("evaluate.convenience") }}</view>
|
||||
<uv-rate
|
||||
class="uv-rate"
|
||||
:count="5"
|
||||
v-model="rate.convenience"
|
||||
:activeColor="themeInfo.activeColor"
|
||||
inactiveColor="rgb(13, 32, 49)"
|
||||
size="40rpx"
|
||||
>
|
||||
</uv-rate>
|
||||
</view>
|
||||
</view>
|
||||
<view class="textareaBox">
|
||||
<uv-textarea
|
||||
v-model="rate.textarea"
|
||||
height="140.8rpx"
|
||||
:placeholder="$t('evaluate.tips')"
|
||||
customStyle="background: transparent; border-radius: 16rpx; border:none;"
|
||||
textStyle="font-size: 24rpx; color: rgb(15, 34, 50); font-weight: 400; line-height: 35rpx;"
|
||||
|
||||
>
|
||||
</uv-textarea>
|
||||
<view class="textareaBox-bottom">
|
||||
<view class="anonymous">
|
||||
<uv-checkbox-group v-model="rate.checkboxValue">
|
||||
<uv-checkbox activeColor="#A1A1A1"
|
||||
name="true"
|
||||
shape="circle"
|
||||
:label="$t('evaluate.anonymous')"
|
||||
>
|
||||
</uv-checkbox>
|
||||
</uv-checkbox-group>
|
||||
<!-- <text>{{ $t('evaluate.anonymous') }}</text> -->
|
||||
</view>
|
||||
<view class="add-pictures" >
|
||||
<uv-upload
|
||||
:fileList="fileList1"
|
||||
name="6"
|
||||
:maxCount="1"
|
||||
width="102.4rpx"
|
||||
height="91.84rpx"
|
||||
customStyle="width: 102.4rpx;height: 92rpx; border-radius: 12rpx;display: flex; justify-content: center; align-items: center;"
|
||||
@afterRead="afterRead"
|
||||
:previewFullImage="false"
|
||||
@delete="deletePic"
|
||||
>
|
||||
<!-- <view class="add-pictures" @click="addPicture">
|
||||
<uv-image src="../../static/evaluate/image.png" width="42rpx" height="39rpx"></uv-image>
|
||||
<br>
|
||||
add pictures
|
||||
</view> -->
|
||||
<image style="width: 102.4rpx; height: 91.84rpx;" src="../../static/evaluate/addPic.png" ></image>
|
||||
</uv-upload>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<button @click="submit" v-show="ifsubmit" class="submit">
|
||||
{{ $t('common.submit') }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import navBar from '@/components/navBar.vue'
|
||||
import { ref } from 'vue';
|
||||
import { useOrderApi } from '@/Apis/order.js'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
const getApi = useOrderApi()
|
||||
const count = ref(5);
|
||||
const rate = ref({
|
||||
orderId: '',
|
||||
siteId: '',
|
||||
overallRating: '',
|
||||
Hospitality: '',
|
||||
cleanliness: '',
|
||||
convenience: '',
|
||||
imageUrl: '',
|
||||
textarea: "",
|
||||
checkboxValue: [],
|
||||
})
|
||||
onLoad((params) => {
|
||||
rate.value.orderId = params.orderId
|
||||
rate.value.siteId = params.siteId
|
||||
})
|
||||
// const overAll = ref(0);
|
||||
// const Hospitality = ref(0);
|
||||
// const cleanliness = ref(0);
|
||||
// const convenience = ref(0);
|
||||
const ifsubmit = ref(true);
|
||||
|
||||
|
||||
// 上传图片存放
|
||||
const fileList1 = ref([]);
|
||||
// 新增图片方法
|
||||
const afterRead = async (event) => {
|
||||
let lists = [].concat(event.file);
|
||||
let fileListLen = fileList1.value.length;
|
||||
|
||||
lists.forEach(async (item) => {
|
||||
fileList1.value.push({
|
||||
...item,
|
||||
status: 'uploading',
|
||||
message: '上传中'
|
||||
});
|
||||
|
||||
const result = await UploaderImage(item.url);
|
||||
fileList1.value[fileListLen] = {
|
||||
...item,
|
||||
status: 'success',
|
||||
message: '',
|
||||
url: result
|
||||
};
|
||||
rate.value.imageUrl = result
|
||||
|
||||
fileListLen++;
|
||||
});
|
||||
}
|
||||
// 上传请求
|
||||
async function UploaderImage(url) {
|
||||
let url1 = '';
|
||||
try {
|
||||
const res = await getApi.UploaderImage({ filePath: url });
|
||||
// const jsonstr = JSON.parse(res);
|
||||
url1 = res.data;
|
||||
} catch (error) {
|
||||
throw error; //
|
||||
}
|
||||
return url1;
|
||||
}
|
||||
// 删除图片
|
||||
const deletePic = (event) => {
|
||||
fileList1.value.splice(event.index, 1);
|
||||
rate.value.imageUrl=''
|
||||
};
|
||||
// 提交评分
|
||||
async function submit() {
|
||||
uni.showLoading()
|
||||
const res = await getApi.SubmitOrderEvaluate({
|
||||
orderId: String(rate.value.orderId),
|
||||
siteId: String(rate.value.siteId),
|
||||
overallRating: String(rate.value.overallRating),
|
||||
hospitality: String(rate.value.Hospitality),
|
||||
cleanliness: String(rate.value.cleanliness),
|
||||
convenience: String(rate.value.convenience),
|
||||
remark: String(rate.value.textarea),
|
||||
imageUrl: String(rate.value.imageUrl),
|
||||
isAnonymity: rate.value.checkboxValue.length === 1
|
||||
});
|
||||
if(res.code === 200) {
|
||||
uni.hideLoading()
|
||||
ifsubmit.value = false
|
||||
|
||||
uni.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url:'/pages/unlock/index'
|
||||
})
|
||||
},2000)
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/static/style/theme.scss';
|
||||
uni-page-body {
|
||||
height: 100%;
|
||||
background: linear-gradient(0.00deg, rgb(1, 169, 188), rgb(10, 132, 184));
|
||||
overflow: auto;
|
||||
}
|
||||
.navBar {
|
||||
position: relative;
|
||||
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: start;
|
||||
height: 100vh;
|
||||
background: linear-gradient(0.00deg, var(--left-linear), var(--right-linear));
|
||||
}
|
||||
.container-form {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.form {
|
||||
position: relative;
|
||||
width: 690rpx;
|
||||
height: 1000rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
& > .topBox {
|
||||
width: 100%;
|
||||
height: 116rpx;
|
||||
background: #F7F7F7;
|
||||
text-align: center;
|
||||
line-height: 116rpx;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
}
|
||||
& > .midBox {
|
||||
width: 504rpx;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
.midBox-detail {
|
||||
margin-top: 54rpx;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
& > .circle {
|
||||
width: 17rpx;
|
||||
height: 17rpx;
|
||||
background: var(--active-color);
|
||||
border-radius: 100%;
|
||||
}
|
||||
& > .rate {
|
||||
width: 214rpx;
|
||||
margin-left: 25rpx;
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
color: #0F2232;
|
||||
}
|
||||
& > .uv-rate {
|
||||
margin-left: 26rpx;
|
||||
}
|
||||
}
|
||||
.midBox-detail-1 {
|
||||
margin-top: 34rpx;
|
||||
|
||||
@extend .midBox-detail;
|
||||
& > .border {
|
||||
margin-left: 26rpx;
|
||||
background: #979797;
|
||||
width: 233rpx;
|
||||
height: 2rpx;
|
||||
}
|
||||
|
||||
}
|
||||
.midBox-detail-2 {
|
||||
@extend .midBox-detail;
|
||||
margin-top: 21rpx;
|
||||
& > .line {
|
||||
width: 17rpx;
|
||||
height: 2rpx;
|
||||
background: #979797;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
& >.textareaBox {
|
||||
margin-top: 44rpx;
|
||||
width: 630rpx;
|
||||
height: 294rpx;
|
||||
background: #F2F2F2;
|
||||
border-radius: 8rpx;
|
||||
& > .textareaBox-bottom {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
|
||||
& >.anonymous {
|
||||
margin-left: 39rpx;
|
||||
width: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
& > .add-pictures {
|
||||
margin-left: 250rpx;
|
||||
width: 102.4rpx;
|
||||
height: 92rpx;
|
||||
// border: 1px dashed rgb(151, 151, 151);
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 12rpx;
|
||||
font-weight: 500;
|
||||
color: #989898;
|
||||
z-index: 10;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
& > .submit {
|
||||
margin-top: 41rpx;
|
||||
border-radius: 14rpx;
|
||||
background: var(--active-color);
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.13);
|
||||
width: 630rpx;
|
||||
height: 90rpx;
|
||||
color: var(--text-color);
|
||||
font-size: 37rpx;
|
||||
font-weight: 500;
|
||||
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 4px;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--right-linear) 0px, var(--right-linear) 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
62
pages/facecode/facecode.vue
Normal file
62
pages/facecode/facecode.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<view style="padding: 100px 0;display: flex;justify-content: center;">
|
||||
<uv-button @click="goAuth">回到订单页,可对相关订单进行门禁授权</uv-button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { reactive } from "vue";
|
||||
import { useOrderApi } from '@/Apis/order.js'
|
||||
const getApi = useOrderApi()
|
||||
const state = reactive({
|
||||
orderId: "",
|
||||
mac: "",
|
||||
});
|
||||
onLoad(() => {
|
||||
state.mac=options.id;
|
||||
if(options.q){
|
||||
var query=decodeURIComponent(options.q);
|
||||
console.log(query)
|
||||
state.mac=query.split("id=")[1];
|
||||
if(!state.mac){
|
||||
state.mac=query.split("facecode/")[1];
|
||||
}
|
||||
init()
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
const init = () => {
|
||||
uni.showLoading();
|
||||
uni.$on('loginSuccess',function(data){
|
||||
console.log('监听到事件来自 loginSuccess ,携带参数 msg 为:' + data.msg);
|
||||
getOrder()
|
||||
})
|
||||
}
|
||||
const getOrder = () => {
|
||||
uni.showLoading()
|
||||
getApi.GetOrderList().then(res=>{
|
||||
uni.hideLoading();
|
||||
state.value.orderList = []
|
||||
if(res.code === 200){
|
||||
state.orderId = res.data.find(item=>item.accessControl.includes(state.mac) &&item.orderStartStatus == 2 && item.refundLockerStatus === 0)?.orderId
|
||||
if(state.orderId){
|
||||
uni.navigateTo({
|
||||
url:`/pagesb/AControl/index?id=${state.orderId}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
const goAuth = () => {
|
||||
// 回到主页
|
||||
uni.switchTab({
|
||||
url: `/pages/unlock/index`,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
300
pages/forgotPawd/index.vue
Normal file
300
pages/forgotPawd/index.vue
Normal file
@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<view class="forgot-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<view class="container">
|
||||
<view class="logoBox">
|
||||
<image src="/static/logo.png" mode=""></image>
|
||||
<text>EMPOWER YOUR SELF STORAGE</text>
|
||||
</view>
|
||||
<view class="formBox">
|
||||
<view class="form">
|
||||
<uv-form labelPosition="left" :model="state" labelWidth='160rpx' :rules="rules" ref='formRef'>
|
||||
<uv-form-item :label="$t('login.account')" prop="username" borderBottom>
|
||||
<uv-input v-model="state.username" :placeholder="$t('login.account')" border="none">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('login.password')" prop="password" borderBottom >
|
||||
<uv-input v-model="state.password" :placeholder="$t('login.password')" :type='state.passwordVisible?"text":"password"' border="none">
|
||||
<template #suffix>
|
||||
|
||||
<uv-icon @click="state.passwordVisible = !state.passwordVisible" :name="state.passwordVisible?'eye-off-outline':'eye'" size="18" ></uv-icon>
|
||||
</template>
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item labelWidth='300rpx' :label="$t('login.confirm')" prop="passwordTow" borderBottom >
|
||||
<uv-input v-model="state.passwordTow" :placeholder="$t('login.password')" :type='state.passwordVisible?"text":"password"' border="none">
|
||||
<template #suffix>
|
||||
|
||||
<uv-icon @click="state.passwordVisible = !state.passwordVisible" :name="state.passwordVisible?'eye-off-outline':'eye'" size="18" ></uv-icon>
|
||||
</template>
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('login.code')" prop="code" borderBottom >
|
||||
<uv-input v-model="state.code" :placeholder="$t('login.code')" border="none">
|
||||
</uv-input>
|
||||
<template #right>
|
||||
<uv-button size="small" type="primary" @click="send">{{ count ? count+'S' : $t("login.send") }}</uv-button>
|
||||
</template>
|
||||
</uv-form-item>
|
||||
<view>
|
||||
<uv-button shape="circle" block type="primary" @click="onSubmit">{{ $t("login.change") }}</uv-button>
|
||||
<view class="goLogin" @click="goLogin">
|
||||
{{ $t("login.toLogin") }}
|
||||
</view>
|
||||
</view>
|
||||
</uv-form>
|
||||
</view>
|
||||
|
||||
<!-- <van-form class="form" @submit="onSubmit" ref='formRef'>
|
||||
<van-cell-group inset>
|
||||
<van-field v-model="state.username" name="account" label="account" placeholder="account"
|
||||
:rules="[{ required: true, message: 'Please input' }]" />
|
||||
<van-field v-model="state.password" type="password" name="password" label="password"
|
||||
:right-icon="state.passwordVisible ? 'eye-o' : 'eye-off-o'" placeholder="password"
|
||||
:rules="[{ required: true, message: 'Please input' }]" />
|
||||
<van-field
|
||||
v-model="state.passwordTow"
|
||||
type="password"
|
||||
name="passwordTow"
|
||||
label="confirm password"
|
||||
:right-icon="state.passwordVisible ? 'eye-o' : 'eye-off-o'"
|
||||
placeholder="password"
|
||||
:rules="[{ required: true, message: 'Please input' },{ validator: passwordIsSame, message: 'The password is different.' }]"
|
||||
/>
|
||||
<van-field v-model="state.code" name="code" label="code" placeholder="code"
|
||||
:rules="[{ required: true, message: 'Please input' }]">
|
||||
<template #button>
|
||||
<van-button size="small" type="primary" @click="send">{{count? count+'S' : 'SEND'}}</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
<div>
|
||||
<van-button round block type="primary" native-type="submit">
|
||||
change
|
||||
</van-button>
|
||||
<view class="goLogin" @click="goLogin">
|
||||
LOGIN AN ACCOUNT
|
||||
</view>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
</van-form> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
import {
|
||||
onTabItemTap
|
||||
} from '/utils/common.js'
|
||||
import {
|
||||
useLoginApi
|
||||
} from '@/Apis/login.js';
|
||||
import { useCountDown } from "@/hooks/index";
|
||||
import { navigateBack } from '@/utils/common.js'
|
||||
import navBar from '@/components/navBar.vue'
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
const formRef = ref()
|
||||
const { count, countDown,cancelCout } = useCountDown();
|
||||
const getApi = useLoginApi()
|
||||
// 国际化配置
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
const passwordIsSame = ()=> state.value.password === state.value.passwordTow
|
||||
const state = ref({
|
||||
codeImage: '',
|
||||
username: '',
|
||||
uuid: '',
|
||||
code: '',
|
||||
password: '',
|
||||
passwordTow:'',
|
||||
passwordVisible: false,
|
||||
})
|
||||
const rules = {
|
||||
username:[{ required: true, message: t("login.input"), trigger: ['blur', 'change'] }],
|
||||
password:[{ required: true, message: t("login.input"), trigger: ['blur', 'change']}],
|
||||
passwordTow:[{ required: true, message: t("login.input"), trigger: ['blur', 'change']}, { validator: passwordIsSame, message: t("login.different"), trigger: ['blur', 'change']}],
|
||||
code:[{ required: true, message: t("login.input"), trigger: ['blur', 'change']}],
|
||||
}
|
||||
const togglePasswordVisibility = () => {
|
||||
state.value.passwordVisible = !state.value.passwordVisible
|
||||
};
|
||||
const goLogin = ()=>{
|
||||
navigateBack('/pages/login/index')
|
||||
}
|
||||
const send = () => {
|
||||
formRef.value.validateField(['username','password','passwordTow'],vaild=>{
|
||||
if(!vaild.length){
|
||||
countDown(30,()=>{
|
||||
ForgotPassword()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const ForgotPassword = () => {
|
||||
uni.showLoading()
|
||||
getApi.ForgotPassword(`"${state.value.username}"`).then(res => {
|
||||
uni.hideLoading()
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title:res.msg,
|
||||
icon:'none'
|
||||
})
|
||||
}else{
|
||||
cancelCout()
|
||||
}
|
||||
})
|
||||
}
|
||||
const EmailVerify = () => {
|
||||
uni.showLoading()
|
||||
getApi.EmailVerify({
|
||||
emailAddress: state.value.username,
|
||||
verifyCode: state.value.code,
|
||||
}).then(res => {
|
||||
uni.hideLoading()
|
||||
if (res.code === 200) {
|
||||
UpdateUserInfo()
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const UpdateUserInfo = () => {
|
||||
uni.showLoading()
|
||||
getApi.UpdateUserInfo({
|
||||
emailAddress: state.value.username,
|
||||
password: state.value.password,
|
||||
type: 0,
|
||||
}).then(res => {
|
||||
uni.hideLoading()
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title:res.msg,
|
||||
icon:'none'
|
||||
})
|
||||
goLogin()
|
||||
} else {
|
||||
}
|
||||
})
|
||||
}
|
||||
const onSubmit = (values) => {
|
||||
formRef.value.validate().then(()=>{
|
||||
EmailVerify()
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/static/style/theme.scss';
|
||||
.forgot-wrap {
|
||||
background: linear-gradient(0deg, var(--left-linear) 0%, var(--right-linear) 94.004%);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
min-width: 100%;
|
||||
|
||||
.logoBox {
|
||||
margin-top: 6%;
|
||||
padding: 40upx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #FFFFFF;
|
||||
font-weight: 900;
|
||||
font-size: 34rpx;
|
||||
|
||||
image {
|
||||
margin-top: 20rpx;
|
||||
width: 256rpx;
|
||||
height: 63rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.formBox {
|
||||
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.form {
|
||||
background: #FFFFFF;
|
||||
padding: 60rpx 20rpx;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
|
||||
.goLogin {
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
::v-deep .uv-form-item__body{
|
||||
margin-bottom: 25rpx;
|
||||
background-color: #F6F7F9;
|
||||
overflow: visible;
|
||||
padding: 10px 16px;
|
||||
.uv-form-item__body__left{
|
||||
width: auto;
|
||||
min-width: 140rpx;
|
||||
flex-wrap: nowrap;
|
||||
color: #617986;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
// ::v-deep .van-field{
|
||||
// margin-bottom: 25rpx;
|
||||
// background-color: #F6F7F9;
|
||||
// overflow: visible;
|
||||
// .van-field__label{
|
||||
// width: auto;
|
||||
// min-width: 120rpx;
|
||||
// flex-wrap: nowrap;
|
||||
// color: #617986;
|
||||
// font-weight: 600;
|
||||
// white-space: nowrap;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// }
|
||||
// .van-field__error-message{
|
||||
// position: absolute;
|
||||
// top: 40rpx;
|
||||
// }
|
||||
// .van-field__value{
|
||||
// position: relative;
|
||||
// }
|
||||
// .van-field__control{
|
||||
// color: #121819;
|
||||
// font-weight: 600;
|
||||
|
||||
// }
|
||||
// .van-field__button {
|
||||
// .van-button {
|
||||
// height: 50rpx;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
::v-deep .uv-button {
|
||||
border-color: var(--btn-color5);
|
||||
background-color: var(--btn-color5);
|
||||
}
|
||||
}
|
||||
}
|
||||
.code {
|
||||
width: 140rpx;
|
||||
height: 56rpx;
|
||||
}
|
||||
</style>
|
||||
332
pages/goodsList/index.vue
Normal file
332
pages/goodsList/index.vue
Normal file
@ -0,0 +1,332 @@
|
||||
<template>
|
||||
<view class="invoice-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<view class="content">
|
||||
<view class="info">
|
||||
<view class="i-header">
|
||||
<view class="tabbox">
|
||||
<view class="li">
|
||||
{{ $t("common.goodsList") }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips">
|
||||
{{ $t("goodsList.info") }}
|
||||
</view>
|
||||
<view> </view>
|
||||
</view>
|
||||
<view class="infobox">
|
||||
<uv-checkbox-group v-model="state.goodsIds" shape="circle" activeColor="var(--main-color)">
|
||||
<view class="selectbox" v-for="(item, index) in state.dataList" :key="index">
|
||||
<view class="li-title" @click="hadelShowHide(index)">
|
||||
<view class="left"> {{ item.name }}({{ $t("goodsList.multi") }})</view>
|
||||
<view class="right"> {{ item.show?'-':'+' }} </view>
|
||||
</view>
|
||||
<template v-if="item.show&&item.tree.length>0">
|
||||
<view class="li-box" v-for="(item2, index2) in item.tree" :key="index2">
|
||||
<view class="li2-title">
|
||||
<view class="text"> [ {{item2.name}} ] </view>
|
||||
<view class="line"> </view>
|
||||
</view>
|
||||
<view class="radioBox" v-if="item2.tree.length>0">
|
||||
<uv-checkbox
|
||||
:customStyle="{ marginBottom: '8px',marginRight:'8px' }"
|
||||
v-for="(item3, index3) in item2.tree"
|
||||
:key="index"
|
||||
:label="item3.name"
|
||||
:name="item3.id"
|
||||
></uv-checkbox>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</uv-checkbox-group>
|
||||
<view class="selectbox" style="margin-bottom: 20rpx;">
|
||||
<view class="li-title">
|
||||
<view class="left"> 其他({{ $t("goodsList.note") }})</view>
|
||||
<view class="right"> </view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="textareaBox">
|
||||
<uv-textarea
|
||||
v-model="state.goodsListRemark"
|
||||
height="140.8rpx"
|
||||
:placeholder="$t('goodsList.note')"
|
||||
customStyle="background: transparent; border-radius: 16rpx; border:none;"
|
||||
textStyle="font-size: 24rpx; color: rgb(15, 34, 50); font-weight: 400; line-height: 35rpx;"
|
||||
|
||||
>
|
||||
</uv-textarea>
|
||||
</view>
|
||||
<view class="tipsInfo">
|
||||
<view class="p">
|
||||
{{ $t("goodsList.tip1") }}
|
||||
</view>
|
||||
<view class="p">{{ $t("goodsList.tip2") }}</view>
|
||||
<view class="p">{{ $t("goodsList.tip3") }}</view>
|
||||
<view class="p">{{ $t("goodsList.tip4") }}</view>
|
||||
</view>
|
||||
<button class="btn" @click="submit" :disabled="state.goodsListRemark==''&&state.goodsIds.length==0">
|
||||
{{ $t('goodsList.submit') }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import { useGoodsApi } from "@/Apis/goodsList.js";
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { navigateBack } from '@/utils/common.js';
|
||||
const getApi = useGoodsApi();
|
||||
import { reactive } from "vue";
|
||||
const state = reactive({
|
||||
goodsIds: [],
|
||||
orderId: "",
|
||||
dataList: [],
|
||||
goodsListRemark:"",
|
||||
});
|
||||
const hadelShowHide = (index) => {
|
||||
state.dataList[index].show = !state.dataList[index].show;
|
||||
};
|
||||
const getDataList = ()=>{
|
||||
uni.showLoading()
|
||||
getApi.GetGoodsList().then(res=>{
|
||||
if(res.code == 200){
|
||||
getDetails();
|
||||
state.dataList = res.data;
|
||||
state.dataList.forEach(item=>{
|
||||
item.show = true;
|
||||
})
|
||||
|
||||
}
|
||||
}).finally(()=>{
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
|
||||
const submit = ()=>{
|
||||
uni.showLoading()
|
||||
getApi.SubmitGoodsList({
|
||||
goodsIds:state.goodsIds,
|
||||
orderId:state.orderId,
|
||||
goodsListRemark:state.goodsListRemark
|
||||
}).then(res=>{
|
||||
if(res.code == 200){
|
||||
uni.showToast({
|
||||
title:res.msg,
|
||||
icon:"none"
|
||||
})
|
||||
setTimeout(()=>{
|
||||
navigateBack()
|
||||
},500)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title:res.msg,
|
||||
icon:"none"
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const getDetails = ()=>{
|
||||
uni.showLoading()
|
||||
getApi.GetSubmitGoodsList(state.orderId).then(res=>{
|
||||
if(res.code == 200){
|
||||
state.goodsIds = res.data.filter(item=>!item.isRemark).map(item=>item.value);
|
||||
state.goodsListRemark = res.data.find(item=>item.isRemark)?.value
|
||||
}
|
||||
}).finally(()=>{
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
onLoad ((option) => {
|
||||
state.orderId = option.orderId;
|
||||
getDataList();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
|
||||
.invoice-wrap {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
padding-bottom: 20rpx;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--right-linear),
|
||||
var(--left-linear2)
|
||||
);
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
|
||||
.infobox {
|
||||
width: 100%;
|
||||
padding: 20rpx 40rpx;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
.selectbox {
|
||||
width: 100%;
|
||||
|
||||
.li-title{
|
||||
padding: 8rpx 40rpx;
|
||||
color: var(--text-color);
|
||||
border-radius: 999rpx;
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
background: var(--active-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.li2-title{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.text{
|
||||
color: var(--stress-text);
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.line{
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
background: var(--stress-text);
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
.radioBox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.textareaBox {
|
||||
margin-top: 20rpx;
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
background: #F2F2F2;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(
|
||||
var(--left-linear2) 0px,
|
||||
var(--left-linear2) 5px,
|
||||
transparent 5px,
|
||||
transparent
|
||||
);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
.tipsInfo{
|
||||
padding: 40rpx 0rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
.p{
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-bottom: 40rpx;
|
||||
border-radius: 2px;
|
||||
background: var(--active-color);
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
color: var(--text-color);
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.i-header {
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
|
||||
.tips {
|
||||
padding: 0 40rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(
|
||||
var(--right-linear) 0px,
|
||||
var(--right-linear) 5px,
|
||||
transparent 5px,
|
||||
transparent
|
||||
);
|
||||
background-size: 14px 14px;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
padding: 30upx 0;
|
||||
width: 100%;
|
||||
background-color: #f7f7f7;
|
||||
color: #242e42;
|
||||
|
||||
.tabbox {
|
||||
position: relative;
|
||||
padding: 20upx 10%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
.li {
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
font-size: 32upx;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.li.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bottom-line {
|
||||
position: absolute;
|
||||
left: calc(50% - 220rpx);
|
||||
bottom: 0;
|
||||
width: 160rpx;
|
||||
height: 2.5px;
|
||||
border-radius: 20px;
|
||||
background: var(--main-color);
|
||||
transition: left 0.5s ease;
|
||||
|
||||
&.right {
|
||||
left: calc(50% + 60rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1549
pages/index/index.vue
Normal file
1549
pages/index/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
157
pages/invite/index.vue
Normal file
157
pages/invite/index.vue
Normal file
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<view class="invite-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<view class="top-wrap">
|
||||
<view class="text">{{ $t("invite.title1") }} <br> {{ $t("invite.title2") }}</view>
|
||||
<uv-icon class="right-icon" name="present" custom-prefix="custom-icon" size="30" color="#FFFFFF"></uv-icon>
|
||||
</view>
|
||||
<view class="activity-wrap" v-for="item in state.activityList" :key="item.id">
|
||||
<view class="top">
|
||||
<view class="name-wrap">
|
||||
<view class="dot"></view>
|
||||
<view class="name">{{ item.name }}</view>
|
||||
</view>
|
||||
<view class="right">{{ $t("invite.number") }} 0/2</view>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="item">
|
||||
<view class="label">{{ $t("invite.activity") }}: {{ item.mechanism }}</view>
|
||||
<view class="label">{{ $t("invite.branch") }}: {{ item.branch }}</view>
|
||||
<view class="label">{{ $t("invite.details") }}: {{ item.details }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<view class="invite-btn">{{ $t("invite.toInvite") }}</view>
|
||||
<view class="record">{{ $t("invite.record") }}>></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="disclaimer1">{{ $t("invite.disclaimer") }}</view>
|
||||
<view class="disclaimer2">{{ $t("invite.disContent") }}</view>
|
||||
<image class="bottom-bg" src="@/static/personal/bottom-bg.png" mode="widthFix"></image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
import navBar from '@/components/navBar.vue';
|
||||
|
||||
import { reactive } from "vue";
|
||||
const state = reactive({
|
||||
activityList: [
|
||||
{ id: 33, name: "活动名称1", mechanism: "WInvite X friends to follow and register official account to get promotion coupon for storage rental.", branch: "ALL shops", details: 28008800, number: 1 },
|
||||
{ id:2, name: "活动名称2", mechanism: "", branch: "ALL shops", details: 28008800, number: 1 },
|
||||
]
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.invite-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
background: linear-gradient(to bottom, var(--right-linear), var(--left-linear));
|
||||
|
||||
.top-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-top: 40rpx;
|
||||
color: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx 40rpx;
|
||||
background: linear-gradient(to bottom, var(--left-linear), var(--right-linear));
|
||||
|
||||
.text {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
margin-right: 60rpx;
|
||||
}
|
||||
|
||||
.right-icon {
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-wrap {
|
||||
width: 100%;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 40rpx;
|
||||
background-color: #FFFFFF;
|
||||
overflow: hidden;
|
||||
|
||||
.top {
|
||||
padding: 0 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #F6F6F6;
|
||||
height: 80rpx;
|
||||
.name-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 18rpx;
|
||||
background: var(--active-color);
|
||||
}
|
||||
}
|
||||
.right {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20rpx 20rpx 20rpx 50rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 40rpx 20rpx 20rpx 50rpx;
|
||||
.invite-btn {
|
||||
color: var(--text-color);
|
||||
border-radius: 30rpx;
|
||||
padding: 8rpx 30rpx;
|
||||
font-size: 26rpx;
|
||||
background: var(--active-color);
|
||||
}
|
||||
.record {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.disclaimer1,
|
||||
.disclaimer2 {
|
||||
font-size: 28rpx ;
|
||||
align-self: flex-start;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.disclaimer1 {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
.disclaimer2 {
|
||||
margin-left: 20rpx;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
642
pages/login/index.vue
Normal file
642
pages/login/index.vue
Normal file
@ -0,0 +1,642 @@
|
||||
<template>
|
||||
<view class="login-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<view class="container">
|
||||
<view class="logoBox">
|
||||
<image src="/static/logo.png" mode=""></image>
|
||||
<text>EMPOWER YOUR SELF STORAGE</text>
|
||||
</view>
|
||||
<view class="formBox">
|
||||
<view class="form">
|
||||
<uv-form labelPosition="left" :model="state" labelWidth="160rpx" :rules="rules" ref="formRef">
|
||||
<!-- #ifndef MP-WEIXIN-->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<uv-form-item :label="$t('login.account')" prop="username" borderBottom>
|
||||
<uv-input v-model="state.username" :placeholder="$t('login.account')" border="none">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('login.password')" prop="password" borderBottom>
|
||||
<uv-input v-model="state.password" :placeholder="$t('login.password')"
|
||||
:type="state.passwordVisible ? 'text' : 'password'" border="none">
|
||||
<template #suffix>
|
||||
<uv-icon @click="
|
||||
state.passwordVisible =
|
||||
!state.passwordVisible
|
||||
" :name="state.passwordVisible
|
||||
? 'eye-off-outline'
|
||||
: 'eye'
|
||||
" size="18"></uv-icon>
|
||||
</template>
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('login.code')" prop="code" borderBottom>
|
||||
<uv-input v-model="state.code" :placeholder="$t('login.code')" border="none">
|
||||
</uv-input>
|
||||
<template #right>
|
||||
<img class="code" @click="getCodeImage()" :src="'data:image/gif;base64,' + state.codeImage
|
||||
" />
|
||||
</template>
|
||||
</uv-form-item>
|
||||
|
||||
<view class="btn-wrap">
|
||||
<uv-button shape="circle" block type="primary" @click="onSubmit">
|
||||
{{ $t('login.login') }}
|
||||
</uv-button>
|
||||
|
||||
<view class="goLogin" @click="goRegister">
|
||||
{{ $t("login.register") }}
|
||||
</view>
|
||||
<text class="forget-text" @click="goForgotPawd">{{ $t("login.forget") }}</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<uv-form labelPosition="left" :model="state" labelWidth="160rpx" :rules="rulesApp" ref="formRef">
|
||||
<uv-form-item :label="$t('login.phone')" prop="phone" borderBottom>
|
||||
|
||||
<uv-input
|
||||
v-model="state.phone"
|
||||
:placeholder="$t('login.phone')"
|
||||
border="none"
|
||||
style="flex: 1;"
|
||||
>
|
||||
<template v-slot:prefix>
|
||||
<view @click="showAreaCodePicker" style="padding-right: 16rpx; min-width: 100rpx; color: #333;display: flex;align-items: center;">
|
||||
{{ state.areaCode }} <view style="transform: rotate(90deg);"><uv-icon :size="14" name="play-right-fill"></uv-icon></view>
|
||||
</view>
|
||||
</template>
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('login.code')" prop="smsCode" borderBottom>
|
||||
<uv-input v-model="state.smsCode" :placeholder="$t('login.inputCode')" border="none" />
|
||||
<template #right>
|
||||
<uv-button size="small" type="primary" :disabled="countdown > 0" @click="sendSmsCode">
|
||||
{{ countdown > 0 ? `${countdown}s` : $t('login.getCode') }}
|
||||
</uv-button>
|
||||
</template>
|
||||
</uv-form-item>
|
||||
<view class="btn-wrap">
|
||||
<uv-button shape="circle" block type="primary" @click="onAppLogin">
|
||||
{{ $t('login.login') }}
|
||||
</uv-button>
|
||||
<view class="UserAgreementtips" :class="{'shake UserAgreementtips': state.isShaking}" @click="changeCheck(false)">
|
||||
<uv-checkbox-group v-model="state.checked" @change="changeCheck(true)">
|
||||
<uv-checkbox class="myCheckbox" :name="true"> </uv-checkbox>
|
||||
</uv-checkbox-group> {{ $t('common.FirstTimeLoginTips') }},{{ $t('login.andAgreeTo') }}<text @click.stop="goAgreement">{{ $t('login.UserAgreement') }}</text>。
|
||||
</view>
|
||||
</view>
|
||||
</uv-form>
|
||||
<uv-picker
|
||||
ref="AreaCodePickerRef"
|
||||
:columns="[areaCodeList]"
|
||||
:confirmText="$t('common.confirm')"
|
||||
:cancelText="$t('common.cancel')"
|
||||
keyName="label"
|
||||
@confirm="onAreaCodeConfirm"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<uv-button customStyle="margin:100rpx 0" color="#5BBC6B" shape="circle" block type="primary"
|
||||
@click="wxGetUserProfile">
|
||||
{{ $t("login.wxLogin") }}
|
||||
</uv-button>
|
||||
<!-- #endif -->
|
||||
</uv-form>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<uv-popup ref="popup"
|
||||
customStyle="width: 80%; padding:20rpx 0; border-radius: 32rpx; display: flex; flex-direction: column; justify-content: center; align-items: center;"
|
||||
@change="popupChange">
|
||||
<text>{{ $t("common.bindPhone") }}</text>
|
||||
<uv-form labelPosition="left" labelWidth="80" ref="formRef">
|
||||
<uv-form-item :label="$t('common.userName')" prop="username" borderBottom>
|
||||
<uv-input v-model="state.username" class="weui-input" border="none"
|
||||
:placeholder="$t('common.userName')" />
|
||||
</uv-form-item>
|
||||
</uv-form>
|
||||
<text style="padding: 0 40rpx; margin-top: 20rpx; text-align: center;">{{ $t('common.bindPhoneAfter') }}</text>
|
||||
<button style="width: 80%; margin-top: 20px; background: #5BBC6B; color: #FFFFFF; line-height: 80rpx;"
|
||||
open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">{{ $t("common.QuickBind") }}</button>
|
||||
<view style="width: 80%; margin-top: 20px; text-align: center;" @click="isToHome">{{ $t("common.cancelBind") }}</view>
|
||||
</uv-popup>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<myPopup
|
||||
v-model="state.showAgreement"
|
||||
type="center"
|
||||
:mask-closable="false"
|
||||
style="width: 100%; max-height: 80vh;padding:10px;"
|
||||
>
|
||||
<scroll-view scroll-y style="max-height: 60vh; padding: 16rpx; white-space: pre-line;">
|
||||
<view>
|
||||
<view class="AgreementTitle">用户协议 User Agreement</view>
|
||||
<view>
|
||||
<view> 1. 您同意遵守本应用的各项使用规定,不得利用本应用进行违法或侵权行为。 </view>
|
||||
<view> You agree to comply with all the rules of this app and shall not use it for illegal or infringing activities.</view>
|
||||
|
||||
<view>2. 本应用有权根据需要修改或更新本协议内容,修改后的协议将在应用内公告。 </view>
|
||||
<view> The app reserves the right to modify or update this agreement as needed, with changes announced within the app.</view>
|
||||
|
||||
<view>3. 您理解并同意,首次登录即视为同意本协议及隐私政策。 </view>
|
||||
<view> You understand and agree that the first login signifies your acceptance of this User Agreement and Privacy Policy.</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="margin-top: 24rpx;">
|
||||
<view class="AgreementTitle">隐私政策 Privacy Policy</view>
|
||||
<view>1. 我们重视您的隐私保护,严格按照相关法律法规收集和使用您的个人信息。 </view>
|
||||
<view> We value your privacy and collect and use your personal information in accordance with relevant laws and regulations.</view>
|
||||
<view>2. 您的手机号、设备信息、登录状态等信息将用于账户认证和服务优化。 </view>
|
||||
<view>Your phone number, device information, login status, and other data will be used for account authentication and service optimization.</view>
|
||||
<view>3. 您的信息不会未经授权向第三方披露,除非法律法规另有规定。 </view>
|
||||
<view> Your information will not be disclosed to third parties without your authorization, except as required by law.</view>
|
||||
<view>4. 您可以通过应用设置管理您的个人信息权限。 </view>
|
||||
<view>You can manage your personal information permissions through the app settings. </view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view style="text-align: center; margin-top: 24rpx;">
|
||||
<button
|
||||
type="primary"
|
||||
style="width: 80%; margin: 0 auto;"
|
||||
@click="agree"
|
||||
>
|
||||
同意
|
||||
</button>
|
||||
</view>
|
||||
</myPopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onTabItemTap, navigateBack, isToHome } from "/utils/common.js";
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import myPopup from '@/components/myPopup.vue';
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo, storeState, getUserInfo } = useMainStore();
|
||||
// 国际化配置
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
const AreaCodePickerRef = ref();
|
||||
const areaCodeList = ref([
|
||||
{ label: '+86 (大陆)', value: '+86' },
|
||||
{ label: '+852 (香港)', value: '+852' },
|
||||
{ label: '+853 (澳门)', value: '+853' },
|
||||
{ label: '+886 (台湾)', value: '+886' },
|
||||
{ label: '+1 (美国/加拿大)', value: '+1' },
|
||||
{ label: '+44 (英国)', value: '+44' },
|
||||
{ label: '+81 (日本)', value: '+81' },
|
||||
]);
|
||||
const showAreaCodePicker = ()=>{
|
||||
console.log(AreaCodePickerRef,"AreaCodePickerRef")
|
||||
AreaCodePickerRef.value.open();
|
||||
}
|
||||
const closeAreaCodePicker = ()=>{
|
||||
AreaCodePickerRef.value.close();
|
||||
}
|
||||
const ifMiniProgram = ref(false);
|
||||
const getApi = useLoginApi();
|
||||
const formRef = ref();
|
||||
const rules = {
|
||||
phone: [
|
||||
{
|
||||
required: true,
|
||||
message: t("login.inputPhone"),
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
smsCode: [
|
||||
{
|
||||
required: true,
|
||||
message: t("login.inputCode"),
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: t("login.input"),
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: t("login.input"),
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
code: [
|
||||
{
|
||||
required: true,
|
||||
message: t("login.input"),
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
};
|
||||
const goAgreement = ()=>{
|
||||
state.value.showAgreement = true;
|
||||
}
|
||||
const onAreaCodeConfirm = (e) => {
|
||||
state.value.areaCode = e.value[0].value;
|
||||
closeAreaCodePicker();
|
||||
};
|
||||
const countdown = ref(0);
|
||||
let timer = null;
|
||||
const state = ref({
|
||||
areaCode: '+86',
|
||||
phone: '',
|
||||
smsCode: '',
|
||||
codeImage: "",
|
||||
username: "",
|
||||
uuid: "",
|
||||
password: "",
|
||||
passwordVisible: false,
|
||||
code: "",
|
||||
isShaking: false,
|
||||
showAgreement:false, // 是否显示协议
|
||||
checked: [], // 是否同意协议,
|
||||
looked: false, // 是否已阅读协议
|
||||
});
|
||||
|
||||
const changeCheck = (isChange)=>{
|
||||
if(!state.value.looked){
|
||||
if(isChange){
|
||||
state.value.checked = !state.value.checked.length ? [true] : [];
|
||||
}
|
||||
uni.showToast({ title: t("common.checkAgreementUrl"), icon: "none" });
|
||||
return
|
||||
}
|
||||
if(!isChange){
|
||||
state.value.checked = !state.value.checked.length ? [true] : [];
|
||||
}
|
||||
|
||||
}
|
||||
const agree = ()=>{
|
||||
state.value.looked = true;
|
||||
state.value.checked = [true];
|
||||
state.value.showAgreement = false;
|
||||
}
|
||||
|
||||
const sendSmsCode = async () => {
|
||||
if (!state.value.phone.trim()) {
|
||||
uni.showToast({ title: t("login.phoneFormat"), icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: t("login.sending") });
|
||||
try {
|
||||
const res = await getApi.sendSmsCode({ phone: state.value.phone });
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: t("login.sendSuccess"), icon: "success" });
|
||||
countdown.value = 60;
|
||||
timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) clearInterval(timer);
|
||||
}, 1000);
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || "error", icon: "none" });
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
const shake = () => {
|
||||
state.value.isShaking = true;
|
||||
setTimeout(() => {
|
||||
state.value.isShaking = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const onAppLogin = () => {
|
||||
if(!state.value.checked.length){
|
||||
shake();
|
||||
return
|
||||
}
|
||||
// formRef.value
|
||||
// .validate()
|
||||
// .then(() => {
|
||||
// uni.showLoading();
|
||||
// getApi.appLogin({
|
||||
// phone: state.value.phone,
|
||||
// code: state.value.smsCode,
|
||||
// }).then((res) => {
|
||||
// uni.hideLoading();
|
||||
// if (res.code === 200) {
|
||||
// uni.setStorageSync("token", res.data.token);
|
||||
// isToHome();
|
||||
// } else {
|
||||
// uni.showToast({ title: res.msg || "登录失败", icon: "none" });
|
||||
// }
|
||||
// });
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.log(error);
|
||||
// });
|
||||
};
|
||||
|
||||
const goRegister = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/register/index",
|
||||
});
|
||||
};
|
||||
const goForgotPawd = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/forgotPawd/index",
|
||||
});
|
||||
};
|
||||
const togglePasswordVisibility = () => {
|
||||
state.value.passwordVisible = !state.value.passwordVisible;
|
||||
};
|
||||
// 弹窗开启关闭
|
||||
const popupChange = (event) => {
|
||||
// 如果关闭 就跳转
|
||||
if (!event.show) {
|
||||
isToHome()
|
||||
}
|
||||
}
|
||||
const getCodeImage = () => {
|
||||
getApi.getCode().then((res) => {
|
||||
if (res.code === 200) {
|
||||
state.value.uuid = res.data.uuid;
|
||||
state.value.codeImage = res.data.img;
|
||||
}
|
||||
});
|
||||
};
|
||||
// #ifndef MP-WEIXIN
|
||||
getCodeImage();
|
||||
// #endif
|
||||
const onSubmit = (values) => {
|
||||
const data = {
|
||||
emailAddress: state.value.username,
|
||||
password: state.value.password,
|
||||
uuid: state.value.uuid,
|
||||
code: state.value.code,
|
||||
};
|
||||
formRef.value
|
||||
.validate()
|
||||
.then((res) => {
|
||||
uni.showLoading();
|
||||
getApi.Login(data).then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
uni.setStorageSync("token", res.data);
|
||||
isToHome()
|
||||
} else {
|
||||
getCodeImage();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const code = ref("");
|
||||
const encryptedData = ref("");
|
||||
const iv = ref("");
|
||||
const popup = ref()
|
||||
const wxLogin = () => {
|
||||
// 微信 登陆授权 code
|
||||
return new Promise(function (reslove, reject) {
|
||||
wx.login({
|
||||
success(res) {
|
||||
code.value = res.code;
|
||||
reslove(res.code);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
console.error("wx.login调用失败:", err);
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
const AuthorizedLogin = (data) => {
|
||||
uni.showLoading()
|
||||
getApi.AuthorizedLogin(data)
|
||||
.then(async (res) => {
|
||||
uni.hideLoading()
|
||||
if (res.code == 200) {
|
||||
storeState.token = res.data.token;
|
||||
uni.setStorageSync("token", res.data.token);
|
||||
uni.setStorageSync("openId", res.data.openId);
|
||||
uni.removeStorage({key:'Pre_ID'})
|
||||
uni.removeStorage({key:'mediatorId'})
|
||||
const { data: userInfo } = await getUserInfo()
|
||||
if (!userInfo.phone) {
|
||||
popup.value.open()
|
||||
} else {
|
||||
isToHome()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
async function wxGetUserProfile() {
|
||||
uni.showLoading()
|
||||
wx.getUserProfile({
|
||||
desc: "用于完善会员资料", // 获取用户信息的提示语
|
||||
success: async (res) => {
|
||||
const userInfo = res;
|
||||
encryptedData.value = res.encryptedData;
|
||||
iv.value = res.iv;
|
||||
// 微信 登陆授权 code
|
||||
uni.checkSession({
|
||||
success: (res) => {
|
||||
// 发起网络请求
|
||||
AuthorizedLogin({
|
||||
code: code.value,
|
||||
encryptedData: encryptedData.value,
|
||||
iv: iv.value,
|
||||
openId: uni.getStorageSync("openId"),
|
||||
Pre_ID: uni.getStorageSync("Pre_ID"),
|
||||
mediatorId: uni.getStorageSync('mediatorId')
|
||||
})
|
||||
|
||||
},
|
||||
fail: (err) => {
|
||||
wxLogin().then(res=>{
|
||||
AuthorizedLogin({
|
||||
code: code.value,
|
||||
encryptedData: encryptedData.value,
|
||||
iv: iv.value,
|
||||
openId: uni.getStorageSync("openId"),
|
||||
Pre_ID: uni.getStorageSync("Pre_ID"),
|
||||
mediatorId: uni.getStorageSync('mediatorId')
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading()
|
||||
console.error("获取用户信息失败:", err);
|
||||
},
|
||||
});
|
||||
}
|
||||
// 获取服务器返回的token
|
||||
|
||||
async function wxChartLogin() {
|
||||
await wxGetUserProfile();
|
||||
}
|
||||
const phoneNumber = ref('');
|
||||
async function getPhoneNumber(e) {
|
||||
uni.showLoading()
|
||||
phoneNumber.value = e.detail.code;
|
||||
if (e.detail.code) {
|
||||
getApi.GetPhoneNumber({code:e.detail.code,userName:state.value.username.trim()}).then(res => {
|
||||
if (res.code == 200) {
|
||||
uni.hideLoading()
|
||||
isToHome()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '获取手机号失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
}
|
||||
onLoad(() => {
|
||||
if (uni.getSystemInfoSync().hostName === "WeChat") {
|
||||
wxLogin();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- <style>
|
||||
page {
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgb(1, 169, 188) 0%,
|
||||
rgb(10, 132, 184) 94.004%
|
||||
);
|
||||
min-height: 100%;
|
||||
}
|
||||
</style> -->
|
||||
<style scoped lang="scss">
|
||||
@import '@/static/style/theme.scss';
|
||||
// uni-page-body {
|
||||
// background: linear-gradient(
|
||||
// 0deg,
|
||||
// rgb(1, 169, 188) 0%,
|
||||
// rgb(10, 132, 184) 94.004%
|
||||
// );
|
||||
// min-height: 100%;
|
||||
// }
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
20%, 60% { transform: translateX(-10px); }
|
||||
40%, 80% { transform: translateX(10px); }
|
||||
}
|
||||
|
||||
.shake {
|
||||
animation: shake 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.AgreementTitle{
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.UserAgreementtips{
|
||||
margin-top:20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
text{
|
||||
color: var(--active-color);
|
||||
}
|
||||
}
|
||||
.login-wrap {
|
||||
background: linear-gradient(0deg, var(--left-linear) 0%, var(--right-linear) 94.004%);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
min-width: 100%;
|
||||
|
||||
.logoBox {
|
||||
margin-top: 6%;
|
||||
padding: 40upx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #ffffff;
|
||||
font-weight: 900;
|
||||
font-size: 34rpx;
|
||||
|
||||
image {
|
||||
margin-top: 20rpx;
|
||||
width: 256rpx;
|
||||
height: 63rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.formBox {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.form {
|
||||
background: #ffffff;
|
||||
padding: 60rpx 20rpx;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
|
||||
.goLogin {
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.forget-text {
|
||||
color: var(--btn-color2);
|
||||
}
|
||||
|
||||
::v-deep .uv-form-item__body {
|
||||
margin-bottom: 25rpx;
|
||||
background-color: #f6f7f9;
|
||||
overflow: visible;
|
||||
padding: 10px 16px;
|
||||
|
||||
.uv-form-item__body__left {
|
||||
width: auto;
|
||||
min-width: 140rpx;
|
||||
flex-wrap: nowrap;
|
||||
color: #617986;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrap {
|
||||
::v-deep .uv-button {
|
||||
border-color: var(--btn-color5);
|
||||
background-color: var(--btn-color5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
width: 140rpx;
|
||||
height: 56rpx;
|
||||
}
|
||||
</style>
|
||||
224
pages/orderdetail/door.vue
Normal file
224
pages/orderdetail/door.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<view class="box">
|
||||
<view class="top-box">
|
||||
<view class="number">
|
||||
{{ lockDetail.lockerName }}
|
||||
</view>
|
||||
<view class="detail">
|
||||
{{ $t("detail.store") }}: {{ lockDetail.siteName }} <br>
|
||||
{{ $t("detail.unit") }}: {{ lockDetail.unitTypeName }}<br>
|
||||
<view v-if="lockDetail.orderStartStatus == 2 && lockDetail.refundLockerStatus === 0">{{ $t("detail.remain", { days: lockDetail.remainingDays }) }}</view>
|
||||
<view style="color: red;" v-else>
|
||||
{{ $t('common.status') }}:
|
||||
<template v-if="lockDetail.refundLockerStatus === 0">{{ $t("common.notStarted") }}</template>
|
||||
<template v-else-if="lockDetail.refundLockerStatus === 1">{{ $t("unlock.cancelPending") }}</template>
|
||||
<template v-else-if="lockDetail.refundLockerStatus === 2">{{ $t("unlock.outComplete") }}</template>
|
||||
<template v-else-if="lockDetail.refundLockerStatus === 3">{{ $t("unlock.disapproval") }}</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="paw" v-show="ifShow === false">
|
||||
<uv-toast ref="toast"></uv-toast>
|
||||
</view>
|
||||
<img v-show="ifShow" class="paw" width="600rpx" height="600rpx" :src="QRCode" @click="ClickToZoomIn"></img>
|
||||
<view class="tip">
|
||||
{{ $t("door.tip") }}<br>
|
||||
{{ $t("door.valid") }}
|
||||
</view>
|
||||
<button class="refresh" @click="GetQRCode">
|
||||
<uv-icon name="reload" size="24" :color="themeInfo.iconColor"></uv-icon>
|
||||
{{ $t("door.refresh") }}
|
||||
</button>
|
||||
<button class="refresh" @click="ClickToZoomIn">
|
||||
<uv-icon name="search" size="24" :color="themeInfo.iconColor"></uv-icon>
|
||||
{{ $t("common.ClickToZoomIn") }}
|
||||
</button>
|
||||
<view style="text-align: center;color: #FFFFFF;padding: 10rpx 0;">{{ $t("common.tryZooming") }}</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
import navBar from '@/components/navBar.vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useOrderApi } from '@/Apis/order.js'
|
||||
import { useLockApi } from '@/Apis/lock.js'
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
|
||||
const getApi = useOrderApi()
|
||||
const getLockApi = useLockApi()
|
||||
const orderId = ref()
|
||||
const siteId = ref()
|
||||
const lockDetail = ref([])
|
||||
const QRCode = ref([])
|
||||
const ifShow = ref(false)
|
||||
const toast = ref()
|
||||
onLoad((params) => {
|
||||
// uni.showLoading({mask:true})
|
||||
orderId.value = params.id
|
||||
siteId.value = params.siteId
|
||||
GetOrderDetail()
|
||||
})
|
||||
|
||||
const ClickToZoomIn = () => {
|
||||
uni.previewImage({
|
||||
urls: [QRCode.value],
|
||||
longPressActions: {
|
||||
itemList: ['发送给朋友', '保存图片'],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function GetOrderDetail() {
|
||||
uni.showLoading({mask:true})
|
||||
await getApi.GetOrderById({ orderId: orderId.value }).then(res=>{
|
||||
lockDetail.value = res.data
|
||||
GetQRCode()
|
||||
})
|
||||
|
||||
}
|
||||
// 获取二维码
|
||||
|
||||
function GetQRCode() {
|
||||
uni.showLoading({mask:true})
|
||||
getLockApi.GetAccesscontrolQRCodeBySite({siteId: siteId.value, orderId: orderId.value}).then(res=>{
|
||||
if(res.code === 200) {
|
||||
QRCode.value = res.data
|
||||
ifShow.value = true
|
||||
} else {
|
||||
ifShow.value = false
|
||||
}
|
||||
uni.hideLoading();
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/static/style/theme.scss';
|
||||
.container {
|
||||
height: 100vh;
|
||||
background: linear-gradient(0.00deg, var(--left-linear) 0%,var(--right-linear) 94.004%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.box {
|
||||
position: relative;
|
||||
width: 680rpx;
|
||||
border-image-width: 14rpx;
|
||||
border-image-slice: 72;
|
||||
border-image-repeat: repeat;
|
||||
border-image-outset: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 26rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.top-box {
|
||||
width: 710rpx;
|
||||
background: linear-gradient(180.00deg, var(--left-linear), var(--right-linear) 100%);
|
||||
border-radius: 26rpx;
|
||||
margin-top: -2rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40rpx;
|
||||
|
||||
& > .number {
|
||||
padding: 0 40rpx;
|
||||
color: var(--text-color);
|
||||
font-size: 64rpx;
|
||||
font-weight: bold;
|
||||
width: 50%;
|
||||
line-height: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
& > .detail {
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 4px;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--right-linear) 0px, var(--right-linear) 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
}
|
||||
.paw {
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
background: #F2F2F2;
|
||||
margin-top: 74rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.tip {
|
||||
font-size: 27rpx;
|
||||
font-weight: 500;
|
||||
margin-top: 34rpx;
|
||||
text-align: center;
|
||||
color: var(--blackOrWhite);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.refresh {
|
||||
width: 520rpx;
|
||||
position: relative;
|
||||
background: var(--btn-color1);
|
||||
border-radius: 12rpx;
|
||||
padding: 14rpx 0;
|
||||
margin: 50rpx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
|
||||
::v-deep .uv-icon {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
}
|
||||
.container.golden-theme {
|
||||
background: #FFFFFF;
|
||||
|
||||
::v-deep .back .left span,
|
||||
::v-deep .back .left text {
|
||||
color: var(--bg-color) !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
.top-box::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 4px;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(#FFFFFF 0px, #FFFFFF 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
z-index: 9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1569
pages/orderdetail/index.vue
Normal file
1569
pages/orderdetail/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
259
pages/orderdetail/lock.vue
Normal file
259
pages/orderdetail/lock.vue
Normal file
@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<view class="box">
|
||||
<view class="top-box">
|
||||
<view class="number">
|
||||
{{ lockDetail.lockerName }}
|
||||
</view>
|
||||
<view class="detail">
|
||||
{{ $t("detail.store") }}: {{ lockDetail.siteName }} <br>
|
||||
{{ $t("detail.unit") }}: {{ lockDetail.unitTypeName }}<br>
|
||||
<view v-if="lockDetail.orderStartStatus == 2 && lockDetail.refundLockerStatus === 0">{{ $t("detail.remain", { days: lockDetail.remainingDays }) }}</view>
|
||||
<view style="color: red;" v-else>
|
||||
{{ $t('common.status') }}:
|
||||
<template v-if="lockDetail.refundLockerStatus === 0">{{ $t("common.notStarted") }}</template>
|
||||
<template v-else-if="lockDetail.refundLockerStatus === 1">{{ $t("unlock.cancelPending") }}</template>
|
||||
<template v-else-if="lockDetail.refundLockerStatus === 2">{{ $t("unlock.outComplete") }}</template>
|
||||
<template v-else-if="lockDetail.refundLockerStatus === 3">{{ $t("unlock.disapproval") }}</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="!state.isTTlock || (state.tryPWDCount > tryPWDCountMAX)">
|
||||
<view class="paw">
|
||||
{{ lockPwd?.password }}{{ lockPwd?.password?'#':'' }}
|
||||
</view>
|
||||
<view class="tip">
|
||||
{{ $t("door.pwd") }} <br>
|
||||
{{ $t("door.valid") }}
|
||||
</view>
|
||||
</template>
|
||||
<button class="refresh" @click="RemoteOpen" v-if="state.isTTlock">
|
||||
{{ $t("door.Unlock") }}
|
||||
</button>
|
||||
<button class="refresh" @click="GetLockPwd" v-if="!state.isTTlock || (state.tryPWDCount > tryPWDCountMAX)">
|
||||
<uv-icon name="reload" size="24" :color="themeInfo.iconColor"></uv-icon>
|
||||
{{ $t("door.refreshPwd") }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
reactive,
|
||||
ref
|
||||
} from 'vue';
|
||||
import navBar from '@/components/navBar.vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useOrderApi } from '@/Apis/order.js'
|
||||
import { useLockApi } from '@/Apis/lock.js'
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
// 国际化配置
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { themeInfo } = useMainStore();
|
||||
const getApi = useOrderApi()
|
||||
const getLockApi = useLockApi()
|
||||
const orderId = ref()
|
||||
const lockMac = ref()
|
||||
const lockDetail = ref([])
|
||||
const lockPwd = ref([])
|
||||
const tryPWDCountMAX = 3
|
||||
const state = reactive({
|
||||
isTTlock: false, // 是否是通通锁 TT锁可以使用远程开门,
|
||||
tryPWDCount: 0, // 尝试动态次数,
|
||||
})
|
||||
|
||||
onLoad((params) => {
|
||||
orderId.value = params.id
|
||||
lockMac.value = params.lockmac
|
||||
state.tryPWDCount = 0
|
||||
GetOrderDetail()
|
||||
|
||||
})
|
||||
// 获取锁的信息
|
||||
function GetOrderDetail() {
|
||||
uni.showLoading()
|
||||
getApi.GetOrderById({ orderId: orderId.value }).then(res=>{
|
||||
lockDetail.value = res.data
|
||||
if([3, 7].includes(Number(lockDetail.value.lockTypeId))){
|
||||
state.isTTlock = true
|
||||
}else{
|
||||
GetLockPwd()
|
||||
}
|
||||
})
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
// 获取锁的密码
|
||||
function RemoteOpen() {
|
||||
uni.showLoading({mask:true})
|
||||
getLockApi.RemoteOpen({ lockMac: lockMac.value,input: '',siteId:lockDetail.value.siteId }).then(res => {
|
||||
state.tryPWDCount++
|
||||
if(res.data.isSuccess) {
|
||||
uni.showToast({
|
||||
title:t('door.UnlockSuccessful'),
|
||||
duration:3000,
|
||||
icon:'none'
|
||||
})
|
||||
uni.hideLoading()
|
||||
} else {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title:res.data.message,
|
||||
duration:3000,
|
||||
icon:'none'
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
// 获取锁的密码
|
||||
function GetLockPwd() {
|
||||
uni.showLoading({mask:true})
|
||||
getLockApi.GetDyncPwdByMac({ lockMac: lockMac.value,siteId:lockDetail.value.siteId }).then(res => {
|
||||
state.tryPWDCount++
|
||||
if(!res.data.password ) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title:res.data.message,
|
||||
duration:3000,
|
||||
icon:'none'
|
||||
})
|
||||
} else {
|
||||
uni.hideLoading()
|
||||
lockPwd.value = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/static/style/theme.scss';
|
||||
.container {
|
||||
height: 100vh;
|
||||
background: linear-gradient(0.00deg, var(--left-linear) 0%, var(--right-linear) 94.004%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.box {
|
||||
position: relative;
|
||||
width: 680rpx;
|
||||
border-image-width: 14rpx;
|
||||
border-image-slice: 72;
|
||||
border-image-repeat: repeat;
|
||||
border-image-outset: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 26rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.top-box {
|
||||
width: 710rpx;
|
||||
background: linear-gradient(180.00deg, var(--left-linear), var(--right-linear) 100%);
|
||||
border-radius: 26rpx;
|
||||
margin-top: -2rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 40rpx;
|
||||
|
||||
& > .number {
|
||||
padding: 0 40rpx;
|
||||
color: var(--text-color);
|
||||
font-size: 64rpx;
|
||||
font-weight: bold;
|
||||
width: 50%;
|
||||
line-height: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
& > .detail {
|
||||
flex: 1;
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 4px;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--right-linear) 0px, var(--right-linear) 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
}
|
||||
.paw {
|
||||
width: 556.16rpx;
|
||||
height: 128rpx;
|
||||
margin-top: 78rpx;
|
||||
background: var(--active-color);
|
||||
border-radius: 64rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
letter-spacing: 8px;
|
||||
font-size: 65rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.tip {
|
||||
margin-top: 80rpx;
|
||||
font-size: 27rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: var(--blackOrWhite);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.refresh {
|
||||
width: 520rpx;
|
||||
position: relative;
|
||||
background: var(--btn-color1);
|
||||
border-radius: 12rpx;
|
||||
padding: 14rpx 0;
|
||||
margin: 50rpx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
|
||||
::v-deep .uv-icon {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
}
|
||||
.container.golden-theme {
|
||||
background: #FFFFFF;
|
||||
|
||||
::v-deep .back .left span,
|
||||
::v-deep .back .left text {
|
||||
color: var(--bg-color) !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
.top-box::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 4px;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(#FFFFFF 0px, #FFFFFF 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
z-index: 9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
44
pages/personal/customerAi.vue
Normal file
44
pages/personal/customerAi.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<web-view
|
||||
@load="loadDown"
|
||||
@error="onError"
|
||||
style="width: 100%; height: 94vh"
|
||||
:fullscreen="false"
|
||||
:src="webUrl">
|
||||
</web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
let webUrl = ref('');
|
||||
|
||||
onMounted(() => {
|
||||
let openId = uni.getStorageSync("openId");
|
||||
webUrl.value = `https://www.elitesys.hk/EliteBotWeb/#/?wechatId=${openId}`;
|
||||
|
||||
uni.showLoading();
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
const loadDown = () => {
|
||||
uni.hideLoading();
|
||||
};
|
||||
const onError = () => {
|
||||
uni.hideLoading();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #FFFFFF;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
123
pages/personal/index.vue
Normal file
123
pages/personal/index.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`]">
|
||||
<view class="orderInfo sflist card">
|
||||
<ul>
|
||||
<li @click="goOrder">
|
||||
<view class="left"><text class="font28 fontb">订单详情</text></view>
|
||||
<view class="right font36 fontb textGary">
|
||||
<uv-icon color="#000000" blod name="arrow-right" size="24rpx" />
|
||||
</view>
|
||||
</li>
|
||||
<li @click="goVaild">
|
||||
<view class="left"><text class="font28 fontb">信息验证</text></view>
|
||||
<view class="right font36 fontb textGary">
|
||||
<uv-icon color="#000000" blod name="arrow-right" size="24rpx" />
|
||||
</view>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<view class="left"><text class="font28 fontb">发票申请</text></view>
|
||||
<view class="right font36 fontb textGary">
|
||||
<uv-icon color="#000000" blod name="arrow-right" size="32" />
|
||||
</view>
|
||||
</li> -->
|
||||
<li @click="makePhoneCall">
|
||||
<view class="left"><text class="font28 fontb">客服咨询</text></view>
|
||||
<view class="right font36 fontb textGary">
|
||||
<uv-icon color="#000000" blod name="arrow-right" size="24rpx" />
|
||||
</view>
|
||||
</li>
|
||||
</ul>
|
||||
</view>
|
||||
<div class="footer">
|
||||
<myCustomtTabBar direction="horizontal" :show-icon="true" :selected="2" @onTabItemTap="onTabItemTap">
|
||||
</myCustomtTabBar>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
onTabItemTap,
|
||||
makePhoneCall,
|
||||
} from '/utils/common.js'
|
||||
import { navigateTo } from '@/utils/navigateTo';
|
||||
import myCustomtTabBar from '@/components/myCustomtTabBar.vue';
|
||||
import myModal from "@/components/myModal.vue";
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
import { useOrderApi } from '@/Apis/order.js';
|
||||
import { authInfoApi } from "@/Apis/validInfo.js";
|
||||
import { shareParam } from "@/utils/common.js";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js"
|
||||
import { onLoad,onShow,onShareAppMessage} from '@dcloudio/uni-app'
|
||||
import coupon from '@/components/coupon.vue';
|
||||
import inviteDetail from '@/components/inviteDetail.vue';
|
||||
import MediatorinviteDetail from '@/components/MediatorinviteDetail.vue';
|
||||
|
||||
import { useRecommend } from "@/Apis/recommend.js";
|
||||
import { baseImageUrl, projectInfo, isKingKong,envVersion } from '@/config';
|
||||
const { themeInfo, storeState, getUserInfo,logOut } = useMainStore();
|
||||
const getLoginApi = useLoginApi();
|
||||
const getApi = useOrderApi();
|
||||
const couponRef = ref();
|
||||
const getRecommendApi = useRecommend();
|
||||
const getAuthApi = authInfoApi();
|
||||
const { t } = useI18n();
|
||||
uni.hideTabBar();
|
||||
|
||||
const goVaild = () => {
|
||||
navigateTo('/pagesb/validationInfo/index');
|
||||
}
|
||||
const goOrder = () => {
|
||||
navigateTo('/pages/unlock/index');
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '@/static/style/theme.scss';
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 400rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.orderInfo {
|
||||
padding: 0;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #a8aaac69;
|
||||
|
||||
&:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
|
||||
text {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
10
pages/phone/index.vue
Normal file
10
pages/phone/index.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
|
||||
</view>
|
||||
</template>
|
||||
<script setup >
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
305
pages/register/index.vue
Normal file
305
pages/register/index.vue
Normal file
@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<view class="register-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<view class="container">
|
||||
<view class="logoBox">
|
||||
<image src="/static/logo.png" mode=""></image>
|
||||
<text>EMPOWER YOUR SELF STORAGE</text>
|
||||
</view>
|
||||
<view class="formBox">
|
||||
<view class="form">
|
||||
<uv-form labelPosition="left" :model="state" labelWidth='160rpx' :rules="rules" ref="formRef">
|
||||
<uv-form-item :label="$t('login.account')" prop="username" borderBottom>
|
||||
<uv-input v-model="state.username" :placeholder="$t('login.account')" border="none">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('login.password')" prop="password" borderBottom >
|
||||
<uv-input v-model="state.password" :placeholder="$t('login.password')" :type='state.passwordVisible?"text":"password"' border="none">
|
||||
<template #suffix>
|
||||
|
||||
<uv-icon @click="state.passwordVisible = !state.passwordVisible" :name="state.passwordVisible?'eye-off-outline':'eye'" size="18" ></uv-icon>
|
||||
</template>
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item labelWidth='300rpx' :label="$t('login.confirm')" prop="passwordTow" borderBottom >
|
||||
<uv-input v-model="state.passwordTow" :placeholder="$t('login.password')" :type='state.passwordVisible?"text":"password"' border="none">
|
||||
<template #suffix>
|
||||
|
||||
<uv-icon @click="state.passwordVisible = !state.passwordVisible" :name="state.passwordVisible?'eye-off-outline':'eye'" size="18" ></uv-icon>
|
||||
</template>
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('login.code')" prop="code" borderBottom >
|
||||
<uv-input v-model="state.code" :placeholder="$t('login.code')" border="none">
|
||||
</uv-input>
|
||||
<template #right>
|
||||
<uv-button size="small" type="primary" @click="send">{{ count? count+'S' : $t('login.send') }}</uv-button>
|
||||
</template>
|
||||
</uv-form-item>
|
||||
<view>
|
||||
<uv-button shape="circle" block type="primary" @click="onSubmit">{{ $t("login.registered") }}</uv-button>
|
||||
<view class="goLogin" @click="goLogin">
|
||||
{{ $t("login.toLogin") }}
|
||||
</view>
|
||||
</view>
|
||||
</uv-form>
|
||||
</view>
|
||||
<!-- <van-form class="form" @submit="onSubmit" ref='fromRef'>
|
||||
<van-cell-group inset>
|
||||
<van-field v-model="state.username" name="account" label="account" placeholder="account"
|
||||
:rules="[{ required: true, message: 'Please input' }]" />
|
||||
<van-field v-model="state.password" type="password" name="password" label="password"
|
||||
:right-icon="state.passwordVisible ? 'eye-o' : 'eye-off-o'" placeholder="password"
|
||||
:rules="[{ required: true, message: 'Please input' }]" />
|
||||
<van-field v-model="state.passwordTow" type="password" name="passwordTow" label="confirm password"
|
||||
:right-icon="state.passwordVisible ? 'eye-o' : 'eye-off-o'" placeholder="password"
|
||||
:rules="[{ required: true, message: 'Please input' },{ validator: passwordIsSame, message: 'The password is different.' }]" />
|
||||
<van-field v-model="state.code" name="code" label="code" placeholder="code"
|
||||
:rules="[{ required: true, message: 'Please input' }]">
|
||||
<template #button>
|
||||
<van-button size="small" type="primary"
|
||||
@click="send">{{count? count+'S' : 'SEND'}}</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
<div>
|
||||
<van-button round block type="primary" native-type="submit">
|
||||
registered
|
||||
</van-button>
|
||||
<view class="goLogin" @click="goLogin">
|
||||
LOGIN AN ACCOUNT
|
||||
</view>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
</van-form> -->
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
import {
|
||||
onTabItemTap
|
||||
} from '/utils/common.js'
|
||||
import {
|
||||
useLoginApi
|
||||
} from '@/Apis/login.js';
|
||||
import {
|
||||
navigateBack
|
||||
} from '@/utils/common.js'
|
||||
import {
|
||||
useCountDown
|
||||
} from "@/hooks/index";
|
||||
import navBar from '@/components/navBar.vue'
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
// 国际化配置
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
const state = ref({
|
||||
codeImage: '',
|
||||
username: '',
|
||||
uuid: '',
|
||||
code: '',
|
||||
password: '',
|
||||
passwordTow: '',
|
||||
passwordVisible: false,
|
||||
})
|
||||
|
||||
const passwordIsSame = () => state.value.password === state.value.passwordTow
|
||||
const rules = {
|
||||
username:[{ required: true, message: t("login.input"), trigger: ['blur', 'change'] }],
|
||||
password:[{ required: true, message: t("login.input"), trigger: ['blur', 'change']}],
|
||||
passwordTow:[{ required: true, message: t("login.input"), trigger: ['blur', 'change']}, { validator: passwordIsSame, message: t("login.different"), trigger: ['blur', 'change']}],
|
||||
code:[{ required: true, message: t("login.input"), trigger: ['blur', 'change']}],
|
||||
}
|
||||
const getApi = useLoginApi()
|
||||
const {
|
||||
count,
|
||||
countDown,
|
||||
cancelCout
|
||||
} = useCountDown();
|
||||
const formRef = ref()
|
||||
|
||||
|
||||
const togglePasswordVisibility = () => {
|
||||
state.value.passwordVisible = !state.value.passwordVisible
|
||||
};
|
||||
const goLogin = () => {
|
||||
navigateBack('/pages/login/index')
|
||||
}
|
||||
const ForgotPassword = () => {
|
||||
getApi.ForgotPassword(`"${state.value.username}"`).then(res => {
|
||||
console.log(res)
|
||||
if (res.code === 200) {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
const EmailVerify = () => {
|
||||
uni.showLoading()
|
||||
getApi.EmailVerify({
|
||||
emailAddress: state.value.username,
|
||||
verifyCode: state.value.code,
|
||||
}).then(res => {
|
||||
uni.hideLoading()
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title:res.msg,
|
||||
icon:'none'
|
||||
})
|
||||
navigateBack('/pages/login/index')
|
||||
}
|
||||
})
|
||||
}
|
||||
const send = () => {
|
||||
Register()
|
||||
}
|
||||
|
||||
const Register = () => {
|
||||
formRef.value.validateField(['username', 'password', 'passwordTow'],vaild => {
|
||||
if (!vaild.length) {
|
||||
uni.showLoading()
|
||||
getApi.Register({
|
||||
emailAddress: state.value.username,
|
||||
password: state.value.password,
|
||||
type: 0,
|
||||
}).then(res => {
|
||||
uni.hideLoading()
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title:res.msg,
|
||||
icon:'none'
|
||||
})
|
||||
} else {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
const onSubmit = (values) => {
|
||||
formRef.value.validate().then(res=>{
|
||||
EmailVerify()
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/static/style/theme.scss';
|
||||
.register-wrap {
|
||||
background: linear-gradient(0deg, var(--left-linear) 0%, var(--right-linear) 94.004%);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
min-width: 100%;
|
||||
|
||||
.logoBox {
|
||||
margin-top: 6%;
|
||||
padding: 40upx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #FFFFFF;
|
||||
font-weight: 900;
|
||||
font-size: 34rpx;
|
||||
|
||||
image {
|
||||
margin-top: 20rpx;
|
||||
width: 256rpx;
|
||||
height: 63rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.formBox {
|
||||
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.form {
|
||||
background: #FFFFFF;
|
||||
padding: 60rpx 20rpx;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
|
||||
.goLogin {
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
::v-deep .uv-form-item__body{
|
||||
margin-bottom: 25rpx;
|
||||
background-color: #F6F7F9;
|
||||
overflow: visible;
|
||||
padding: 10px 16px;
|
||||
.uv-form-item__body__left{
|
||||
width: auto;
|
||||
min-width: 140rpx;
|
||||
flex-wrap: nowrap;
|
||||
color: #617986;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
// ::v-deep .van-field {
|
||||
// margin-bottom: 25rpx;
|
||||
// background-color: #F6F7F9;
|
||||
// overflow: visible;
|
||||
|
||||
// .van-field__label {
|
||||
// width: auto;
|
||||
// min-width: 120rpx;
|
||||
// flex-wrap: nowrap;
|
||||
// color: #617986;
|
||||
// font-weight: 600;
|
||||
// white-space: nowrap;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// }
|
||||
|
||||
// .van-field__error-message {
|
||||
// position: absolute;
|
||||
// top: 40rpx;
|
||||
// }
|
||||
|
||||
// .van-field__value {
|
||||
// position: relative;
|
||||
// }
|
||||
|
||||
// .van-field__control {
|
||||
// color: #121819;
|
||||
// font-weight: 600;
|
||||
|
||||
// }
|
||||
|
||||
// .van-field__button {
|
||||
// .van-button {
|
||||
// height: 50rpx;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
::v-deep .uv-button {
|
||||
border-color: var(--btn-color5);
|
||||
background-color: var(--btn-color5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
width: 140rpx;
|
||||
height: 56rpx;
|
||||
}
|
||||
</style>
|
||||
1154
pages/renewOrder/index.vue
Normal file
1154
pages/renewOrder/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1770
pages/setOrder/index.vue
Normal file
1770
pages/setOrder/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
350
pages/site/index.vue
Normal file
350
pages/site/index.vue
Normal file
@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="selectSite card" @click="openCityPicker">
|
||||
<view class="left">
|
||||
<image src="/static/home/address.svg" mode="widthFix" class="icon"></image>
|
||||
<text class="address">{{state.city}}{{state.district?'·'+state.district:''}}</text>
|
||||
</view>
|
||||
<view class="right">
|
||||
<text class="more">更多分店</text>
|
||||
<uv-icon name="arrow-right" bold size="24rpx" fontb />
|
||||
</view>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="siteList card" v-for="(item,index) in siteData.list" :key="item.id" @click="toHome(item)">
|
||||
<view class="box">
|
||||
<view class="left">
|
||||
<view class="name font36 fontb">
|
||||
<view class="tuijian">推荐</view> <text>{{ item.name }}</text> <view v-if="item.distance&& index === 0" class="zuijin">距离最近</view>
|
||||
</view>
|
||||
<view class="tagsList">
|
||||
<view class="tag">随存随取</view>
|
||||
<!-- <view class="tag">随存随取</view> -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="distance" @click.stop="handleNavigate(item)">
|
||||
<view class="icon">
|
||||
<image src="/static/site/map.svg"></image>
|
||||
</view>
|
||||
<view class="m" v-if="item.distance">
|
||||
{{ item.distance }} KM
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="box2">
|
||||
<view class="address fontb font28 textGary">{{ item.city }}{{ item.district }}{{ item.address }}</view>
|
||||
<view class="time adress fontb font28 textGary">
|
||||
营业: 00:00-24:00
|
||||
</view>
|
||||
<view class="phone adress fontb font28 textGary">
|
||||
<text @click.stop="makePhoneCall">电话: 400-818-1813</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<uv-picker ref="cityPicker" confirmColor="#FB322E" :columns="[state.cityData]" @confirm="cityConfirm"></uv-picker>
|
||||
<view class="footer">
|
||||
<view @click="toHome">
|
||||
返回主页
|
||||
</view>
|
||||
<view @click="openCityPicker">
|
||||
选择城市
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref,reactive } from "vue";
|
||||
import { onLoad, onShow, onShareAppMessage } from "@dcloudio/uni-app";
|
||||
import { getDistance,mergeFiveGoatStores,makePhoneCall} from "@/utils/common.js";
|
||||
import { useSiteApi } from "@/Apis/site.js";
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
import { ClientSite } from "@/Apis/book.js";
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
// 国际化配置
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useLocation } from "@/hooks/useLocation";
|
||||
const { t } = useI18n();
|
||||
// const getLockApi = useLockApi();
|
||||
const { locationState, getLocation } = useLocation();
|
||||
const { themeInfo, getUserInfo, storeState } = useMainStore();
|
||||
const getApi = ClientSite();
|
||||
const getLoginApi = useLoginApi();
|
||||
const cityPicker = ref(null);
|
||||
const state = ref({
|
||||
city: "全部",
|
||||
district: "",
|
||||
cityData: [],
|
||||
areaData: [],
|
||||
firstLoad: false,
|
||||
});
|
||||
let siteData = reactive({
|
||||
list: [],
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
// 点击路线跳转
|
||||
const handleNavigate = (item) => {
|
||||
SFUIP.openLocation({
|
||||
latitude: Number(item.latitude),
|
||||
longitude: Number(item.longitude),
|
||||
name: item.name,
|
||||
address: item.address,
|
||||
});
|
||||
};
|
||||
|
||||
const toHome = (item) => {
|
||||
uni.$emit('homeSiteId', { id: item.id }); // 触发事件
|
||||
uni.switchTab({
|
||||
url: `/pages/index/index`,
|
||||
});
|
||||
}
|
||||
const cityConfirm = (e) => {
|
||||
console.log(e, "confirm");
|
||||
state.value.city = e.value[0];
|
||||
getSiteDetail();
|
||||
|
||||
}
|
||||
const openCityPicker = () => {
|
||||
cityPicker.value.open();
|
||||
}
|
||||
const closeCityPicker = () => {
|
||||
cityPicker.value.close();
|
||||
}
|
||||
|
||||
onLoad((params) => {
|
||||
state.firstLoad = true;
|
||||
getCityData();
|
||||
});
|
||||
const getCityData = () => {
|
||||
getApi.GetCityAll().then(res => {
|
||||
if(res.code === 200) {
|
||||
state.value.cityData = res.data;
|
||||
state.value.cityData.unshift("全部");
|
||||
// 首次直接选择全部
|
||||
state.value.city = state.value.cityData[0];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 首次只显示距离最近的城市 noLocation 沒有定位的情况下顯示深圳
|
||||
const filterSiteData = (noLocation) => {
|
||||
state.firstLoad = false;
|
||||
if (!siteData.list.length) return;
|
||||
let city = siteData.list[0]['city'];
|
||||
// 如果是金刚的appid,默认显示深圳的门店
|
||||
if(noLocation){
|
||||
city = siteData.list.find(item => item.city.indexOf("广州") !== -1)?.city;
|
||||
}
|
||||
siteData.list = siteData.list.filter((item) => item.city.indexOf(city) !== -1);
|
||||
state.value.city = city;
|
||||
getApi.GetDistrictByCity(city).then(res => {
|
||||
if (res.code === 200) {
|
||||
state.value.areaData = res.data;
|
||||
state.value.areaData.unshift("全部");
|
||||
// state.value.district = state.value.areaData[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取门店信息
|
||||
const getSiteDetail = () => {
|
||||
uni.showLoading();
|
||||
let city = state.value.city === "全部" ? "" : state.value.city;
|
||||
let district = state.value.district === "全部" ? "" : state.value.district;
|
||||
siteData.list = []
|
||||
getApi.getSiteDetailsAll({
|
||||
city,
|
||||
district,
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
siteData.list = mergeFiveGoatStores(res.data);
|
||||
if (locationState?.latitude && locationState?.longitude) {
|
||||
const { latitude, longitude } = locationState;
|
||||
siteData.list.forEach(item => {
|
||||
const { distance, number } = getDistance(latitude, longitude, item.latitude, item.longitude);
|
||||
item.distance = distance;
|
||||
item.distanceNumber = number;
|
||||
});
|
||||
siteData.list.sort((a, b) => a.distanceNumber - b.distanceNumber);
|
||||
if (state.firstLoad) filterSiteData();
|
||||
}else {
|
||||
// 如果是金刚就不是显示全部
|
||||
if(AppId === 'wxb20921dfdd0b94f4' || AppId === 'wx3c4ab696101d77d1') {
|
||||
if (state.firstLoad) filterSiteData(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
siteData.isLoading = false;
|
||||
}).catch(err => {
|
||||
|
||||
siteData.isLoading = false;
|
||||
}).finally(() => {
|
||||
uni.hideLoading();
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
})
|
||||
getLocation().finally(() => {
|
||||
uni.hideLoading();
|
||||
getSiteDetail();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/static/style/theme.scss";
|
||||
.container {
|
||||
height: 100vh;
|
||||
padding-bottom: 100rpx;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
.selectSite {
|
||||
height: 75rpx; // 整体高度
|
||||
display: flex;
|
||||
justify-content: space-between; // 左右分布
|
||||
align-items: center; // 垂直居中
|
||||
background: linear-gradient(90deg, #FFE8C4 0%, #FFFFFF 100%);
|
||||
width: 100%;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.selectSite .left {
|
||||
display: flex;
|
||||
align-items: center; // 左侧垂直居中
|
||||
}
|
||||
|
||||
.selectSite .left .icon {
|
||||
width: 30rpx; // 图片大小,可根据实际调整
|
||||
height: 30rpx;
|
||||
margin-right: 20rpx; // 图片和文字间距
|
||||
}
|
||||
|
||||
.selectSite .left .address {
|
||||
font-size: 28rpx;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.selectSite .right {
|
||||
display: flex;
|
||||
align-items: center; // 右侧垂直居中
|
||||
}
|
||||
|
||||
.selectSite .right .more {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-right: 8rpx; // text 和 icon 间距
|
||||
}
|
||||
|
||||
|
||||
.siteList{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.box2{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.address{
|
||||
margin-bottom: 10rpx;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
.time{
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
}
|
||||
.box{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.left{
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
.name{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
:deep(span){
|
||||
vertical-align: text-top;
|
||||
}
|
||||
.tuijian{
|
||||
color: #FFFFFF;
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color:#334E80;
|
||||
font-size: 24rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.zuijin{
|
||||
color: #000000;
|
||||
padding: 10rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-left: 10rpx;
|
||||
font-size: 16rpx;
|
||||
background-color: #F2F3F8;
|
||||
}
|
||||
}
|
||||
.tagsList{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10rpx;
|
||||
.tag{
|
||||
color: #D2021B;
|
||||
background-color: #FBE8EB;
|
||||
padding: 8rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
margin-right: 10rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.distance{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 20rpx;
|
||||
.icon{
|
||||
text-align: center;
|
||||
}
|
||||
image{
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
padding: 6px;
|
||||
border-radius: 999rpx;
|
||||
background-color: #F2F3F8;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
view{
|
||||
width: 50%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
color: #000;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1472
pages/unlock/index.vue
Normal file
1472
pages/unlock/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
42
pages/webview/web.vue
Normal file
42
pages/webview/web.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- <navBar class="navBar"></navBar> -->
|
||||
<view class="webview-container">
|
||||
<web-view :src="url"></web-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
// import navBar from '@/components/navBar.vue';
|
||||
|
||||
const url = ref('');
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.url) {
|
||||
url.value = decodeURIComponent(options.url);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.navBar {
|
||||
z-index: 10; /* 确保 navBar 在 WebView 之上 */
|
||||
}
|
||||
|
||||
.webview-container {
|
||||
height: 200px;
|
||||
z-index: 1;
|
||||
}
|
||||
uni-web-view.uni-webview--fullscreen{
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
363
pagesb/AControl/index.vue
Normal file
363
pagesb/AControl/index.vue
Normal file
@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<view
|
||||
class="container"
|
||||
:class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]"
|
||||
>
|
||||
<navBar />
|
||||
<view class="content">
|
||||
<view class="info">
|
||||
<view class="i-header">
|
||||
<view class="tabbox">
|
||||
<view
|
||||
class="li"
|
||||
>
|
||||
{{ $t("common.facialData") }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="infobox">
|
||||
<view class="personal">
|
||||
<view class="select">
|
||||
<view class="label"> * 上传{{ state.appText }} Upload Photo </view>
|
||||
<view class="inputBox">
|
||||
<view class="ImgUpload">
|
||||
<view style="position: relative;">
|
||||
<!-- <my-upload v-model="state.formData.idFile1" :show-edit="true">
|
||||
</my-upload> -->
|
||||
<view style="display: flex;justify-content: center;width: 350rpx;align-items: center;">
|
||||
<uv-icon v-if="!state.formData.facePhoto" name="camera" color="#ceced0" size="160" @click="handelCorpImage"></uv-icon>
|
||||
<image v-else :src="state.formData.facePhoto" mode="" @click="handelCorpImage"></image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn">
|
||||
<button class="next-btn" @click="toggleEdit()">
|
||||
{{ $t("common.auth") }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<qf-image-cropper class="qf-image-cropper " :choosable='false' v-show="state.showCrop" ref="qfImageCropperRef" :width="300" :height="400" :checkRange="true" :showAngle="true" @crop="handleCrop"></qf-image-cropper>
|
||||
<!-- 是否验证成功 -->
|
||||
<myModal v-model="state.showAuthModal" @confirm="confirm" content="授权成功" :cancelShow="false"></myModal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import QfImageCropper from '@/pagesb/components/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue';
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { baseImageUrl,RElEASE_DATE} from "@/config/index.js";
|
||||
// import { navigateBack } from '@/utils/common.js';
|
||||
import navBar from "@/components/navBar.vue";
|
||||
// import myUpload from "@/components/myUpload.vue";
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import myModal from "@/components/myModal.vue";
|
||||
const { themeInfo, storeState } = useMainStore();
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useOrderApi } from "@/Apis/order.js";
|
||||
const { t } = useI18n();
|
||||
const app = getApp();
|
||||
const pages = getCurrentPages()
|
||||
const getApi = useOrderApi();
|
||||
const qfImageCropperRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
showAuthModal: false,
|
||||
showCrop:false,
|
||||
contentTips:'', // 验证成功提示
|
||||
formData: {
|
||||
orderId: '',
|
||||
facePhoto:''
|
||||
},
|
||||
appText: "仓位货物"
|
||||
});
|
||||
const handelCorpImage = () => {
|
||||
state.showCrop = true
|
||||
qfImageCropperRef.value.resetData()
|
||||
qfImageCropperRef.value.chooseImage({ sourceType: ['camera'],fail:()=>{
|
||||
state.showCrop = false
|
||||
}}
|
||||
);
|
||||
}
|
||||
const handleCrop = (e) => {
|
||||
state.showCrop = false
|
||||
state.formData.facePhoto = e.tempFilePath
|
||||
// uni.previewImage({
|
||||
// urls: [e.tempFilePath],
|
||||
// current: 0
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
onLoad((event) => {
|
||||
state.formData.orderId = event.id;
|
||||
getInfo()
|
||||
GetAppText()
|
||||
});
|
||||
|
||||
const toggleEdit = () => {
|
||||
verifyPerson()
|
||||
}
|
||||
const getInfo = () => {
|
||||
uni.showLoading();
|
||||
getApi.GetOrderAuthorizationFace({
|
||||
orderId: state.formData.orderId,
|
||||
}).then(res => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
const today = new Date();
|
||||
const target = new Date(2026, 0, 28); // 月份从 0 开始
|
||||
if((today > target)){
|
||||
state.formData.facePhoto = baseImageUrl + res.data
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
const GetAppText = ()=>{
|
||||
getApi.GetAppText({key:'APPTEXT'}).then(res=>{
|
||||
if(res.code == 200){
|
||||
if(state.formData.orderId){
|
||||
state.appText = res.data
|
||||
}
|
||||
}else{
|
||||
state.appText = "仓位货物"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/unlock/index",
|
||||
});
|
||||
};
|
||||
|
||||
// 上传请求
|
||||
async function UploaderImage(url) {
|
||||
let url1 = "";
|
||||
try {
|
||||
const res = await getApi.UploaderImage({ filePath: url });
|
||||
// const jsonstr = JSON.parse(res);
|
||||
url1 = res.data;
|
||||
} catch (error) {
|
||||
// 在这里处理错误情况
|
||||
console.error("UploaderImage error:", error);
|
||||
throw error; // 抛出错误,可以让调用者处理
|
||||
}
|
||||
return url1;
|
||||
}
|
||||
// 提交个人认证信息
|
||||
const verifyPerson = async () => {
|
||||
let facePhoto = state.formData.facePhoto
|
||||
if(!facePhoto){
|
||||
uni.showToast({
|
||||
title: "请上传图片,再授权!",
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
if(state.formData.facePhoto.indexOf(baseImageUrl) ===-1){
|
||||
try{
|
||||
uni.showLoading();
|
||||
facePhoto = await UploaderImage(state.formData.facePhoto)
|
||||
facePhoto = baseImageUrl + facePhoto
|
||||
}catch(e){
|
||||
//TODO handle the exception
|
||||
uni.showToast({
|
||||
title: "上传失败,请稍后重新尝试",
|
||||
icon: 'none'
|
||||
})
|
||||
uni.hideLoading()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
uni.showLoading();
|
||||
getApi.OrderAuthorization({
|
||||
orderId: state.formData.orderId,
|
||||
facePhoto: facePhoto.replace(baseImageUrl,""),
|
||||
|
||||
}).then(res => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
state.showAuthModal = true;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding: 0 20upx;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 80rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--right-linear),
|
||||
var(--left-linear2)
|
||||
);
|
||||
background-attachment: fixed;
|
||||
.qf-image-cropper {
|
||||
z-index: 10;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
.infobox {
|
||||
width: 100%;
|
||||
padding: 20upx 30upx;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--left-linear2) 0px, var(--left-linear2) 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding:0 20rpx;
|
||||
button {
|
||||
font-size: 28rpx;
|
||||
padding: 12rpx 0;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: var(--text-color);
|
||||
background: var(--active-color);
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.13);
|
||||
}
|
||||
}
|
||||
.select {
|
||||
border-bottom: 1px dashed #d8d8d857;
|
||||
margin-bottom: 16rpx;
|
||||
.label {
|
||||
color: #bec2ce;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.info {
|
||||
color: #242e42;
|
||||
font-size: 22rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-weight: bold;
|
||||
padding: 0 18rpx;
|
||||
opacity: 0.7;
|
||||
&.Total {
|
||||
margin-top: 10rpx;
|
||||
color: #242e42;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.garyBox {
|
||||
background-color: rgba(216, 216, 216, 0.3);
|
||||
border-radius: 8rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
color: rgb(36, 46, 66);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 10rpx 4rpx;
|
||||
}
|
||||
.inputBox {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10rpx;
|
||||
margin-left: 5px;
|
||||
|
||||
.ImgUpload {
|
||||
display: flex;
|
||||
margin: 10upx 0;
|
||||
}
|
||||
.value {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
color: #242e42;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.arrow {
|
||||
width: auto;
|
||||
font-size: 24rpx;
|
||||
.codeBtn {
|
||||
font-size: 24rpx;
|
||||
background-color: #d1cbcb2d;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.i-header {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--right-linear) 0px, var(--right-linear) 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
z-index: 9;
|
||||
}
|
||||
padding: 30upx 0;
|
||||
width: 100%;
|
||||
background-color: #f7f7f7;
|
||||
color: #242e42;
|
||||
.tabbox {
|
||||
position: relative;
|
||||
padding: 20upx 10%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.li {
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
font-size: 32upx;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
}
|
||||
.li.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
.bottom-line {
|
||||
position: absolute;
|
||||
left: calc(50% - 220rpx);
|
||||
bottom: 0;
|
||||
width: 160rpx;
|
||||
height: 2.5px;
|
||||
border-radius: 20px;
|
||||
background: var(--main-color);
|
||||
transition: left 0.5s ease;
|
||||
|
||||
&.right {
|
||||
left: calc(50% + 60rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
17
pagesb/Apis/flashSale.js
Normal file
17
pagesb/Apis/flashSale.js
Normal file
@ -0,0 +1,17 @@
|
||||
import request from "/utils/request.js";
|
||||
export default {
|
||||
GetFlashSaleDistrict:(data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetFlashSaleDistrict',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
GetFlashSaleInfo:(data) => {
|
||||
return request.request({
|
||||
url: '/ClientSite/GetFlashSaleInfo',
|
||||
method: 'get',
|
||||
data,
|
||||
});
|
||||
},
|
||||
}
|
||||
173
pagesb/activityDetail/index.vue
Normal file
173
pagesb/activityDetail/index.vue
Normal file
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar class="navBar"></navBar>
|
||||
<view class="wrapbox">
|
||||
<view class="container-form">
|
||||
<view class="cHeader">
|
||||
<view class="left"> 人品大爆发 </view>
|
||||
<view class="right"> 已邀请人数 {{state.count}}/50 </view>
|
||||
</view>
|
||||
<view class="content">
|
||||
<text class="detail">
|
||||
{{ `活动内容:邀请50位小伙伴关注[${projectInfo.name}]公众号,即可获得租仓优惠券!` }}
|
||||
</text>
|
||||
<view class="info">
|
||||
<text> 使用门店:全部门店 </text>
|
||||
<text @click="makePhoneCall()">
|
||||
{{ `详情咨询:${projectInfo.phone}` }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<view class="left">
|
||||
<button open-type="share" class="btn">去邀请</button>
|
||||
<button class="btn" @click="makePhoneCall()">兑换</button>
|
||||
</view>
|
||||
<view class="right" @click="state.inviteShow = true"> 查看邀请记录>> </view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips">
|
||||
{{ `*活动解释权归${projectInfo.name}所有` }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 邀请详情 -->
|
||||
<inviteDetail v-model="state.inviteShow"></inviteDetail>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import { makePhoneCall } from "/utils/common.js";
|
||||
import { ref } from "vue";
|
||||
import { useRecommend } from "@/Apis/recommend.js";
|
||||
import { onLoad, onShareAppMessage,onShow } from "@dcloudio/uni-app";
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { baseImageUrl, projectInfo } from "@/config/index.js";
|
||||
import inviteDetail from '@/components/inviteDetail.vue';
|
||||
const { themeInfo } = useMainStore();
|
||||
const getApi = useRecommend();
|
||||
const state = ref({
|
||||
inviteShow: false,
|
||||
count: 0
|
||||
});
|
||||
onLoad((params) => {});
|
||||
onShow(() => {
|
||||
getApi.GetRecommendCount().then((res) => {
|
||||
console.log(res);
|
||||
if(res.code === 200){
|
||||
state.count = res.data;
|
||||
}
|
||||
});
|
||||
});
|
||||
onShareAppMessage((res) => {
|
||||
if (res.from === "button") {
|
||||
let userInfo = uni.getStorageSync("userInfo");
|
||||
// 来自页面内分享按钮
|
||||
return {
|
||||
title: `您的好友(${
|
||||
userInfo?.customerName || "微信好友"
|
||||
})邀请您一起体验${projectInfo.name},改变储物方式!`,
|
||||
path: "pages/index/index?Pre_ID=" + userInfo?.customerNo, // 路径,传递参数到指定页面。,
|
||||
imageUrl: baseImageUrl + "2c5f7950-e842-47e5-a135-86d04f22bab8.png",
|
||||
};
|
||||
}
|
||||
return shareParam;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
uni-page-body {
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgb(1, 169, 188), rgb(10, 132, 184));
|
||||
overflow: auto;
|
||||
}
|
||||
.navBar {
|
||||
position: relative;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
height: 100vh;
|
||||
|
||||
background: linear-gradient(0deg, var(--left-linear), var(--right-linear));
|
||||
.wrapbox {
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
}
|
||||
.container-form {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
border-radius: 18rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
font-size: 24rpx;
|
||||
.cHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
padding-bottom: 20rpx;
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content {
|
||||
padding: 0 10rpx;
|
||||
padding-top: 20rpx;
|
||||
.detail {
|
||||
margin: 40rpx 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 10rpx 0;
|
||||
padding-top: 40rpx;
|
||||
font-weight: bold;
|
||||
.left {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 22rpx;
|
||||
.btn {
|
||||
width: 160rpx;
|
||||
height: 44rpx;
|
||||
line-height: 44rpx;
|
||||
padding: 0;
|
||||
font-size: 22rpx;
|
||||
margin-right: 10rpx;
|
||||
border-radius: 999px;
|
||||
border: 1rpx solid transparent;
|
||||
&:nth-child(2) {
|
||||
border-color: #000000;
|
||||
}
|
||||
&:nth-child(1) {
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
margin-top: 30rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
339
pagesb/changeUser/index.vue
Normal file
339
pagesb/changeUser/index.vue
Normal file
@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]">
|
||||
<nav-bar></nav-bar>
|
||||
<view class="content">
|
||||
<view class="openId-box">
|
||||
<view class="row">
|
||||
<text class="label">openId</text>
|
||||
<uni-easyinput v-model="openId" placeholder="请输入 openId" :inputBorder="true" />
|
||||
</view>
|
||||
<view class="btn-row">
|
||||
<uv-button @click="saveOpenId">写入 openId</uv-button>
|
||||
<uv-button type="warning" @click="clearOpenId">清空 openId</uv-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
import { useLoginApi as homeApi } from "/Apis/home.js";
|
||||
import { ClientSite } from "@/Apis/book.js";
|
||||
|
||||
const { themeInfo } = useMainStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const getLoginApi = useLoginApi();
|
||||
const getHomeApi = homeApi();
|
||||
const getSiteApi = ClientSite();
|
||||
|
||||
const regionPickerRef = ref();
|
||||
const typeSelectRef = ref();
|
||||
const openId = ref("");
|
||||
const state = reactive({
|
||||
contentTips: t("reserve.contentTips"), // 验证成功提示
|
||||
formData: {
|
||||
tag: 2,
|
||||
userName: "",
|
||||
phone: "",
|
||||
type: "",
|
||||
region: "", // 区域
|
||||
},
|
||||
showTypeSelectModal: false,
|
||||
columns: [],
|
||||
});
|
||||
|
||||
const localOpenId = uni.getStorageSync("openId");
|
||||
openId.value = localOpenId || "";
|
||||
const saveOpenId = () => {
|
||||
uni.setStorageSync("openId", openId.value || "");
|
||||
if (openId.value) {
|
||||
uni.showLoading({ title: "登录中..." });
|
||||
getLoginApi.AuthorizedLogin({
|
||||
openId: openId.value,
|
||||
})
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
uni.setStorageSync("token", res.data.token);
|
||||
uni.setStorageSync("openId", res.data.openId);
|
||||
uni.showToast({ title: "登录成功", icon: "none" });
|
||||
} else {
|
||||
uni.showToast({ title: res.msg, icon: "none" });
|
||||
}
|
||||
}).catch((err) => {
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: "请输入 openId", icon: "none" });
|
||||
}
|
||||
|
||||
};
|
||||
const clearOpenId = () => {
|
||||
openId.value = "";
|
||||
uni.setStorageSync("openId", "");
|
||||
uni.showToast({ title: "已清空 openId", icon: "none" });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
|
||||
.typeSelectBox {
|
||||
button {
|
||||
font-size: 32rpx;
|
||||
padding: 12rpx 0;
|
||||
font-size: 32rpx;
|
||||
padding: 12rpx 0;
|
||||
color: var(--text-color);
|
||||
background: var(--active-color);
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.13);
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding: 0 20upx;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 80rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(to bottom,
|
||||
var(--right-linear),
|
||||
var(--left-linear2));
|
||||
background-attachment: fixed;
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
|
||||
.infobox {
|
||||
width: 100%;
|
||||
padding: 10upx 6% 60upx;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--left-linear2) 0px,
|
||||
var(--left-linear2) 5px,
|
||||
transparent 5px,
|
||||
transparent);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 60rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
button {
|
||||
font-size: 28rpx;
|
||||
padding: 12rpx 0;
|
||||
color: var(--text-color);
|
||||
background: var(--active-color);
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.13);
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
border-bottom: 1px solid #bec2ce;
|
||||
margin: 30rpx 0;
|
||||
|
||||
.label {
|
||||
color: #bec2ce;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #242e42;
|
||||
font-size: 22rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-weight: bold;
|
||||
padding: 0 18rpx;
|
||||
opacity: 0.7;
|
||||
|
||||
&.Total {
|
||||
margin-top: 10rpx;
|
||||
color: #242e42;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.garyBox {
|
||||
background-color: rgba(216, 216, 216, 0.3);
|
||||
border-radius: 8rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
color: rgb(36, 46, 66);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 10rpx 4rpx;
|
||||
}
|
||||
|
||||
.inputBox {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10rpx;
|
||||
margin-left: 5px;
|
||||
|
||||
.ImgUpload {
|
||||
display: flex;
|
||||
margin: 10upx 0;
|
||||
|
||||
::v-deep .uv-upload {
|
||||
margin-right: 40rpx;
|
||||
border-radius: 18rpx;
|
||||
border: 2px solid #9797974f;
|
||||
|
||||
.uv-upload__wrap__preview {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.upLoadText {
|
||||
font-size: 28upx;
|
||||
text-align: center;
|
||||
padding: 10upx 0;
|
||||
background: var(--active-color);
|
||||
border-radius: 0 0 18upx 18upx;
|
||||
margin: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
color: #242e42;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
width: auto;
|
||||
font-size: 24rpx;
|
||||
|
||||
.codeBtn {
|
||||
font-size: 24rpx;
|
||||
background-color: #d1cbcb2d;
|
||||
}
|
||||
}
|
||||
|
||||
image {
|
||||
width: 20rpx;
|
||||
height: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.openId-box {
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
margin-top: 20rpx;
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
grid-template-columns: 140rpx 1fr;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
|
||||
::v-deep .uv-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.i-header {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--right-linear) 0px,
|
||||
var(--right-linear) 5px,
|
||||
transparent 5px,
|
||||
transparent);
|
||||
background-size: 14px 14px;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
padding: 40upx 0;
|
||||
width: 100%;
|
||||
background-color: #f7f7f7;
|
||||
color: #242e42;
|
||||
|
||||
.tabbox {
|
||||
position: relative;
|
||||
padding-top: 10upx;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
.li {
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
font-size: 34upx;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.li.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bottom-line {
|
||||
position: absolute;
|
||||
left: calc(50% - 220rpx);
|
||||
bottom: 0;
|
||||
width: 160rpx;
|
||||
height: 2.5px;
|
||||
border-radius: 20px;
|
||||
background: var(--main-color);
|
||||
transition: left 0.5s ease;
|
||||
|
||||
&.right {
|
||||
left: calc(50% + 60rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
pagesb/components/my-uv-collapse/changelog.md
Normal file
5
pagesb/components/my-uv-collapse/changelog.md
Normal file
@ -0,0 +1,5 @@
|
||||
## 1.0.1(2023-05-16)
|
||||
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
|
||||
2. 优化部分功能
|
||||
## 1.0.0(2023-05-10)
|
||||
uv-collapse 折叠面板
|
||||
@ -0,0 +1,60 @@
|
||||
export default {
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题右侧内容
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题下方的描述信息
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用折叠面板
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否展示右侧箭头并开启点击反馈
|
||||
isLink: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否开启点击反馈
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示内边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 标题的对齐方式
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 唯一标识符
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 标题左侧图片,可为绝对路径的图片或内置图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 面板展开收起的过渡时间,单位ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
...uni.$uv?.props?.collapseItem
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<view class="uv-collapse-item">
|
||||
<view
|
||||
class="uv-collapse-item__content"
|
||||
:animation="animationData"
|
||||
ref="animation"
|
||||
>
|
||||
<view
|
||||
class="uv-collapse-item__content__text content-class"
|
||||
:id="elId"
|
||||
:ref="elId"
|
||||
><slot /></view>
|
||||
</view>
|
||||
<uv-cell
|
||||
:title="title"
|
||||
:value="value"
|
||||
:label="label"
|
||||
:icon="icon"
|
||||
:isLink="isLink"
|
||||
:clickable="clickable"
|
||||
:border="parentData.border && showBorder"
|
||||
@click="clickHandler"
|
||||
cellStyle="background-color: var(--right-linear);padding:10px 20px;"
|
||||
:arrowDirection="expanded ? 'up' : 'down'"
|
||||
:disabled="disabled"
|
||||
rightIconStyle="color: #000;font-size:20rpx;"
|
||||
>
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<!-- 微信小程序不支持,因为微信中不支持 <slot name="title" slot="title" />的写法 -->
|
||||
<template slot="title">
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
<template slot="icon">
|
||||
<slot name="icon"></slot>
|
||||
</template>
|
||||
<template slot="value">
|
||||
<slot name="value"></slot>
|
||||
</template>
|
||||
<template slot="right-icon">
|
||||
<slot name="right-icon"></slot>
|
||||
</template>
|
||||
<!-- #endif -->
|
||||
</uv-cell>
|
||||
|
||||
<uv-line v-if="parentData.border"></uv-line>
|
||||
</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';
|
||||
// #ifdef APP-NVUE
|
||||
const animation = uni.requireNativePlugin('animation')
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
/**
|
||||
* collapseItem 折叠面板Item
|
||||
* @description 通过折叠面板收纳内容区域(搭配uv-collapse使用)
|
||||
* @tutorial https://www.uvui.cn/components/collapse.html
|
||||
* @property {String} title 标题
|
||||
* @property {String} value 标题右侧内容
|
||||
* @property {String} label 标题下方的描述信息
|
||||
* @property {Boolean} disbled 是否禁用折叠面板 ( 默认 false )
|
||||
* @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 ( 默认 true )
|
||||
* @property {Boolean} clickable 是否开启点击反馈 ( 默认 true )
|
||||
* @property {Boolean} border 是否显示内边框 ( 默认 true )
|
||||
* @property {String} align 标题的对齐方式 ( 默认 'left' )
|
||||
* @property {String | Number} name 唯一标识符
|
||||
* @property {String} icon 标题左侧图片,可为绝对路径的图片或内置图标
|
||||
* @event {Function} change 某个item被打开或者收起时触发
|
||||
* @example <uv-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</uv-collapse-item>
|
||||
*/
|
||||
export default {
|
||||
name: "uv-collapse-item",
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
elId: '',
|
||||
// uni.createAnimation的导出数据
|
||||
animationData: {},
|
||||
// 是否展开状态
|
||||
expanded: false,
|
||||
// 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时
|
||||
showBorder: false,
|
||||
// 是否动画中,如果是则不允许继续触发点击
|
||||
animating: false,
|
||||
// 父组件uv-collapse的参数
|
||||
parentData: {
|
||||
accordion: false,
|
||||
border: false
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
expanded(n) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
// 这里根据expanded的值来进行一定的延时,是为了cell的下划线更好的显示效果
|
||||
this.timer = setTimeout(() => {
|
||||
this.showBorder = n
|
||||
}, n ? 10 : 290)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.elId = this.$uv.guid();
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 异步获取内容,或者动态修改了内容时,需要重新初始化
|
||||
init() {
|
||||
// 初始化数据
|
||||
this.updateParentData()
|
||||
if (!this.parent) {
|
||||
return this.$uv.error('uv-collapse-item必须要搭配uv-collapse组件使用')
|
||||
}
|
||||
const {
|
||||
value,
|
||||
accordion,
|
||||
children = []
|
||||
} = this.parent
|
||||
|
||||
if (accordion) {
|
||||
if (this.$uv.test.array(value)) {
|
||||
return this.$uv.error('手风琴模式下,uv-collapse组件的value参数不能为数组')
|
||||
}
|
||||
this.expanded = this.name == value
|
||||
} else {
|
||||
if (!this.$uv.test.array(value) && value !== null) {
|
||||
return this.$uv.error('非手风琴模式下,uv-collapse组件的value参数必须为数组')
|
||||
}
|
||||
this.expanded = (value || []).some(item => item == this.name)
|
||||
}
|
||||
// 设置组件的展开或收起状态
|
||||
this.$nextTick(function() {
|
||||
this.setContentAnimate()
|
||||
})
|
||||
},
|
||||
updateParentData() {
|
||||
// 此方法在mixin中
|
||||
this.getParentData('uv-collapse')
|
||||
},
|
||||
async setContentAnimate() {
|
||||
// 每次面板打开或者收起时,都查询元素尺寸
|
||||
// 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度
|
||||
const rect = await this.queryRect()
|
||||
const height = this.expanded ? rect.height : 0
|
||||
this.animating = true
|
||||
// #ifdef APP-NVUE
|
||||
const ref = this.$refs['animation'].ref
|
||||
animation.transition(ref, {
|
||||
styles: {
|
||||
height: height + 'px'
|
||||
},
|
||||
duration: this.duration,
|
||||
// 必须设置为true,否则会到面板收起或展开时,页面其他元素不会随之调整它们的布局
|
||||
needLayout: true,
|
||||
timingFunction: 'ease-in-out',
|
||||
}, () => {
|
||||
this.animating = false
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
const animation = uni.createAnimation({
|
||||
timingFunction: 'ease-in-out',
|
||||
});
|
||||
animation
|
||||
.height(height)
|
||||
.step({
|
||||
duration: this.duration,
|
||||
})
|
||||
.step()
|
||||
// 导出动画数据给面板的animationData值
|
||||
this.animationData = animation.export()
|
||||
// 标识动画结束
|
||||
this.$uv.sleep(this.duration).then(() => {
|
||||
this.animating = false
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 点击collapsehead头部
|
||||
clickHandler() {
|
||||
if (this.disabled && this.animating) return
|
||||
// 设置本组件为相反的状态
|
||||
this.parent && this.parent.onChange(this)
|
||||
},
|
||||
// 查询内容高度
|
||||
queryRect() {
|
||||
// #ifndef APP-NVUE
|
||||
// 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
|
||||
return new Promise(resolve => {
|
||||
this.$uvGetRect(`#${this.elId}`).then(size => {
|
||||
resolve(size)
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// nvue下,使用dom模块查询元素高度
|
||||
// 返回一个promise,让调用此方法的主体能使用then回调
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(this.$refs[this.elId], res => {
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
// #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-collapse-item {
|
||||
|
||||
&__content {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
|
||||
&__text {
|
||||
padding: 12px 15px;
|
||||
color: $uv-content-color;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,20 @@
|
||||
export default {
|
||||
props: {
|
||||
// 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
|
||||
value: {
|
||||
type: [String, Number, Array, null],
|
||||
default: null
|
||||
},
|
||||
// 是否手风琴模式
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示外边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
...uni.$uv?.props?.collapse
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<view class="uv-collapse">
|
||||
<uv-line v-if="border"></uv-line>
|
||||
<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';
|
||||
/**
|
||||
* collapse 折叠面板
|
||||
* @description 通过折叠面板收纳内容区域
|
||||
* @tutorial https://www.uvui.cn/components/collapse.html
|
||||
* @property {String | Number | Array} value 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
|
||||
* @property {Boolean} accordion 是否手风琴模式( 默认 false )
|
||||
* @property {Boolean} border 是否显示外边框 ( 默认 true )
|
||||
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
|
||||
* @example <uv-collapse></uv-collapse>
|
||||
*/
|
||||
export default {
|
||||
name: "uv-collapse",
|
||||
mixins: [mpMixin, mixin, props],
|
||||
watch: {
|
||||
needInit() {
|
||||
this.init()
|
||||
},
|
||||
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(uv-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
computed: {
|
||||
needInit() {
|
||||
// 通过computed,同时监听accordion和value值的变化
|
||||
// 再通过watch去执行init()方法,进行再一次的初始化
|
||||
return [this.accordion, this.value]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 重新初始化一次内部的所有子元素
|
||||
init() {
|
||||
this.children.map(child => {
|
||||
child.init()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* collapse-item被点击时触发,由collapse统一处理各子组件的状态
|
||||
* @param {Object} target 被操作的面板的实例
|
||||
*/
|
||||
onChange(target) {
|
||||
let changeArr = []
|
||||
this.children.map((child, index) => {
|
||||
// 如果是手风琴模式,将其他的折叠面板收起来
|
||||
if (this.accordion) {
|
||||
child.expanded = child === target ? !target.expanded : false
|
||||
child.setContentAnimate()
|
||||
} else {
|
||||
if(child === target) {
|
||||
child.expanded = !child.expanded
|
||||
child.setContentAnimate()
|
||||
}
|
||||
}
|
||||
// 拼接change事件中,数组元素的状态
|
||||
changeArr.push({
|
||||
// 如果没有定义name属性,则默认返回组件的index索引
|
||||
name: child.name || index,
|
||||
status: child.expanded ? 'open' : 'close'
|
||||
})
|
||||
})
|
||||
|
||||
this.$emit('change', changeArr)
|
||||
this.$emit(target.expanded ? 'open' : 'close', target.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
89
pagesb/components/my-uv-collapse/package.json
Normal file
89
pagesb/components/my-uv-collapse/package.json
Normal file
@ -0,0 +1,89 @@
|
||||
{
|
||||
"id": "uv-collapse",
|
||||
"displayName": "uv-collapse 折叠面板 全面兼容小程序、nvue、vue2、vue3等多端",
|
||||
"version": "1.0.1",
|
||||
"description": "折叠面板组件,通过折叠面板收纳内容区域,点击可展开收起,多功能参数可配置。",
|
||||
"keywords": [
|
||||
"uv-collapse",
|
||||
"uvui",
|
||||
"uv-ui",
|
||||
"collapse",
|
||||
"折叠面板"
|
||||
],
|
||||
"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-line",
|
||||
"uv-cell"
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
pagesb/components/qf-image-cropper/changelog.md
Normal file
67
pagesb/components/qf-image-cropper/changelog.md
Normal file
@ -0,0 +1,67 @@
|
||||
## 2.2.4(2024-06-21)
|
||||
* 新增 reverseRotatable 属性,是否支持逆向翻转
|
||||
* 修复 `2.1.7` 版本导致旋转后图片没有自动适配裁剪框的问题
|
||||
## 2.2.3(2024-06-21)
|
||||
* 新增 gpu 属性,是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题
|
||||
* 修复 组件使用 `v-if` 并设置 `src` 属性时可能会出现图片渲染位置存在偏差的问题
|
||||
|
||||
## 2.2.2(2024-06-21)
|
||||
* 优化 组件实例 chooseImage 方法支持传参
|
||||
* 修复 组件使用 `v-if` 时组件无非正常渲染的问题
|
||||
|
||||
## 2.2.1(2024-06-15)
|
||||
* 修复 H5平台不支持手势拖动图片的问题
|
||||
|
||||
## 2.2.0(2024-05-31)
|
||||
* 修复 APP平台 `vue2` 项目因 `2.1.9` 版本修复 `vue3` 项目bug而引发的问题
|
||||
|
||||
## 2.1.9(2024-05-29)
|
||||
* 修复 APP平台 `vue3` 项目因 uniapp `renderjs` 中未支持条件编译,导致运行了H5平台代码报错的问题
|
||||
|
||||
## 2.1.8(2024-05-29)
|
||||
* 新增 zIndex 属性,调整组件层级
|
||||
* 新增 组件内容插槽
|
||||
* 优化 微信小程序平台动态修改元素style时的多余内容
|
||||
|
||||
## 2.1.7(2024-05-28)
|
||||
* 新增 checkRange 属性,当 checkRange=false 时允许图片位置超出裁剪边界
|
||||
* 新增 minScale 属性,图片最小缩放倍数,当 minScale<0 时可使图片宽高不再受裁剪区域宽高限制
|
||||
* 新增 backgroundColor 属性,生成图片背景色,如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块
|
||||
* 优化 动态修改图片宽高但没有传入src时,尺寸适应问题
|
||||
* 修复 APP平台通过 `this.$ownerInstance` 获取组件实例时机过早,其值为 `undefined` 导致报错界面没有正常渲染的问题
|
||||
|
||||
## 2.1.6(2023-04-16)
|
||||
* 修复 组件使用 v-show 指令会导致选择图片后初始位置严重偏位的问题
|
||||
|
||||
## 2.1.5(2023-04-15)
|
||||
* 新增 兼容APP平台
|
||||
|
||||
## 2.1.4(2023-03-13)
|
||||
* 新增 fileType 属性,用于指定生成文件的类型,只支持 'jpg' 或 'png',默认为 'png'
|
||||
* 新增 delay 属性,微信小程序平台使用 `Canvas 2D` 绘制时控制图片从绘制到生成所需时间
|
||||
* 优化 当生成图片的尺寸宽/高超过 Canvas 2D 最大限制(1365*1365)则将画布尺寸缩放在限制范围内绘制完成后输出目标尺寸
|
||||
* 优化 旋转图标指示方向与实际旋转方向不符
|
||||
|
||||
## 2.1.3(2023-02-06)
|
||||
* 优化 vue3支持
|
||||
|
||||
## 2.1.2(2023-02-03)
|
||||
* 新增 navigation 属性,H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差
|
||||
* 修复 H5平台部分设备(已知iPhone11以下机型)拍照的图片缩放时会闪动的问题
|
||||
|
||||
## 2.1.1(2022-12-06)
|
||||
* 修复 横屏适配问题
|
||||
|
||||
## 2.1.0(2022-12-06)
|
||||
* 新增 兼容H5平台,使用 renderjs 响应手势事件
|
||||
|
||||
## 2.0.0(2022-12-05)
|
||||
* 重构 插件,使用 WXS 响应手势事件
|
||||
* 新增 图片翻转
|
||||
* 新增 拉伸裁剪框放大图片
|
||||
* 新增 监听PC鼠标滚轮触发缩放
|
||||
* 新增 圆形、圆角矩形的图片裁剪
|
||||
* 优化 图片缩放,移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||
* 优化 裁剪框样式
|
||||
* 优化 图片位置拖动 支持边界回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||
* 优化 生成图片使用新版 Canvas 2D 接口
|
||||
@ -0,0 +1,738 @@
|
||||
/**
|
||||
* 图片编辑器-手势监听
|
||||
* 1. 支持编译到app-vue(uni-app 2.5.5及以上版本)、H5上
|
||||
*/
|
||||
/** 图片偏移量 */
|
||||
var offset = { x: 0, y: 0 };
|
||||
/** 图片缩放比例 */
|
||||
var scale = 1;
|
||||
/** 图片最小缩放比例 */
|
||||
var minScale = 1;
|
||||
/** 图片旋转角度 */
|
||||
var rotate = 0;
|
||||
/** 触摸点 */
|
||||
var touches = [];
|
||||
/** 图片布局信息 */
|
||||
var img = {};
|
||||
/** 系统信息 */
|
||||
var sys = {};
|
||||
/** 裁剪区域布局信息 */
|
||||
var area = {};
|
||||
/** 触摸行为类型 */
|
||||
var touchType = '';
|
||||
/** 操作角的位置 */
|
||||
var activeAngle = 0;
|
||||
/** 裁剪区域布局信息偏移量 */
|
||||
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
/** 元素ID */
|
||||
var elIds = {
|
||||
'imageStyles': 'crop-image',
|
||||
'maskStylesList': 'crop-mask-block',
|
||||
'borderStyles': 'crop-border',
|
||||
'circleBoxStyles': 'crop-circle-box',
|
||||
'circleStyles': 'crop-circle',
|
||||
'gridStylesList': 'crop-grid',
|
||||
'angleStylesList': 'crop-angle',
|
||||
}
|
||||
/** 记录上次初始化时间戳,排除APP重复更新 */
|
||||
var timestamp = 0;
|
||||
/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */
|
||||
// #ifdef H5
|
||||
var platform = 'H5';
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
var platform = 'APP';
|
||||
// #endif
|
||||
/**
|
||||
* 样式对象转字符串
|
||||
* @param {Object} style 样式对象
|
||||
*/
|
||||
function styleToString(style) {
|
||||
if(typeof style === 'string') return style;
|
||||
var str = '';
|
||||
for (let k in style) {
|
||||
str += k + ':' + style[k] + ';';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Object} instance 页面实例对象
|
||||
* @param {Object} key 要修改样式的key
|
||||
* @param {Object|Array} style 样式
|
||||
*/
|
||||
function setStyle(instance, key, style) {
|
||||
// console.log('setStyle', instance, key, JSON.stringify(style))
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP') {
|
||||
if(Object.prototype.toString.call(style) === '[object Array]') {
|
||||
for (var i = 0, len = style.length; i < len; i++) {
|
||||
var el = window.document.getElementById(elIds[key] + '-' + (i + 1));
|
||||
el && (el.style = styleToString(style[i]));
|
||||
}
|
||||
} else {
|
||||
var el = window.document.getElementById(elIds[key]);
|
||||
el && (el.style = styleToString(style));
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5') instance[key] = style;
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 触发页面实例指定方法
|
||||
* @param {Object} instance 页面实例对象
|
||||
* @param {Object} name 方法名称
|
||||
* @param {Object} obj 传递参数
|
||||
*/
|
||||
function callMethod(instance, name, obj) {
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP') instance.callMethod(name, obj);
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5') instance[name](obj);
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 计算两点间距
|
||||
* @param {Object} touches 触摸点信息
|
||||
*/
|
||||
function getDistanceByTouches(touches) {
|
||||
// 根据勾股定理求两点间距离
|
||||
var a = touches[1].pageX - touches[0].pageX;
|
||||
var b = touches[1].pageY - touches[0].pageY;
|
||||
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||
// 求两点间的中点坐标
|
||||
// 1. a、b可能为负值
|
||||
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||
var x = touches[1].pageX - a / 2;
|
||||
var y = touches[1].pageY - b / 2;
|
||||
return { c, x, y };
|
||||
};
|
||||
|
||||
/**
|
||||
* 修正取值
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @param {Object} c
|
||||
* @param {Object} reverse 是否反向
|
||||
*/
|
||||
function correctValue(a, b, c, reverse) {
|
||||
return reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
*/
|
||||
function checkRange(e) {
|
||||
var r = rotate / 90 % 2;
|
||||
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||
var o = (img.height - img.width) / 2; // 宽高差值一半
|
||||
return {
|
||||
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, img.height < area.height),
|
||||
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, img.width < area.width)
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 变更图片布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeImageRect(e) {
|
||||
// console.log('changeImageRect', e)
|
||||
offset.x += e.x || 0;
|
||||
offset.y += e.y || 0;
|
||||
if(e.check && area.checkRange) { // 检查边界
|
||||
var point = checkRange(offset);
|
||||
if(offset.x !== point.x || offset.y !== point.y) {
|
||||
offset = point;
|
||||
}
|
||||
}
|
||||
|
||||
// 因频繁修改 width/height 会造成大量的内存消耗,改为scale
|
||||
// e.instance.imageStyles = {
|
||||
// width: img.width + 'px',
|
||||
// height: img.height + 'px',
|
||||
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)'
|
||||
// };
|
||||
var ox = (img.width - img.oldWidth) / 2;
|
||||
var oy = (img.height - img.oldHeight) / 2;
|
||||
// e.instance.imageStyles = {
|
||||
// width: img.oldWidth + 'px',
|
||||
// height: img.oldHeight + 'px',
|
||||
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
// };
|
||||
setStyle(e.instance, 'imageStyles', {
|
||||
width: img.oldWidth + 'px',
|
||||
height: img.oldHeight + 'px',
|
||||
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
});
|
||||
callMethod(e.instance, 'dataChange', {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: offset.x,
|
||||
y: offset.y,
|
||||
rotate: rotate
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 变更裁剪区域布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeAreaRect(e) {
|
||||
// console.log('changeAreaRect', e)
|
||||
// 变更蒙版样式
|
||||
setStyle(e.instance, 'maskStylesList', [
|
||||
{
|
||||
left: 0,
|
||||
width: (area.left + areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.right + areaOffset.right) + 'px',
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
height: (area.top + areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
}
|
||||
]);
|
||||
// 变更边框样式
|
||||
if(area.showBorder) {
|
||||
setStyle(e.instance, 'borderStyles', {
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
});
|
||||
}
|
||||
|
||||
// 变更参考线样式
|
||||
if(area.showGrid) {
|
||||
setStyle(e.instance, 'gridStylesList', [
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// 变更四个伸缩角样式
|
||||
if(area.showAngle) {
|
||||
setStyle(e.instance, 'angleStylesList', [
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// 变更圆角样式
|
||||
if(area.radius > 0) {
|
||||
var radius = area.radius;
|
||||
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||
radius = (area.width / 2);
|
||||
} else { // 圆角矩形
|
||||
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||
}
|
||||
}
|
||||
setStyle(e.instance, 'circleBoxStyles', {
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
});
|
||||
setStyle(e.instance, 'circleStyles', {
|
||||
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||
'border-radius': radius + 'px'
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 缩放图片
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function scaleImage(e) {
|
||||
// console.log('scaleImage', e)
|
||||
var last = scale;
|
||||
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||
if(last !== scale) {
|
||||
img.width = img.oldWidth * scale;
|
||||
img.height = img.oldHeight * scale;
|
||||
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||
e.x = (e.x - offset.x) * (1 - scale / last);
|
||||
e.y = (e.y - offset.y) * (1 - scale / last);
|
||||
changeImageRect(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* 获取触摸点在哪个角
|
||||
* @param {number} x 触摸点x轴坐标
|
||||
* @param {number} y 触摸点y轴坐标
|
||||
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||
*/
|
||||
function getToucheAngle(x, y) {
|
||||
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||
var oy = sys.navigation ? 0 : sys.windowTop;
|
||||
if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 1; // 左上角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 2; // 右上角
|
||||
}
|
||||
} else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 3; // 左下角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 4; // 右下角
|
||||
}
|
||||
}
|
||||
return 0; // 无触摸到角
|
||||
};
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
function resetData() {
|
||||
offset = { x: 0, y: 0 };
|
||||
scale = 1;
|
||||
minScale = img.minScale;
|
||||
rotate = 0;
|
||||
};
|
||||
function getTouchs(touches) {
|
||||
var result = [];
|
||||
var len = touches ? touches.length : 0
|
||||
for (var i = 0; i < len; i++) {
|
||||
result[i] = {
|
||||
pageX: touches[i].pageX,
|
||||
// h5无标题栏时,窗口顶部距离仍为标题栏高度,且触摸点y轴坐标还是有标题栏的值,即减去标题栏高度的值
|
||||
pageY: touches[i].pageY + sys.windowTop
|
||||
};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
var mouseEvent = false;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imageStyles: {},
|
||||
maskStylesList: [{}, {}, {}, {}],
|
||||
borderStyles: {},
|
||||
gridStylesList: [{}, {}, {}, {}],
|
||||
angleStylesList: [{}, {}, {}, {}],
|
||||
circleBoxStyles: {},
|
||||
circleStyles: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听 PC 端鼠标滚轮
|
||||
// #ifdef H5
|
||||
platform === 'H5' && window.addEventListener('mousewheel', async (e) => {
|
||||
var touchs = getTouchs([e])
|
||||
img.src && scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
check: true,
|
||||
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||
scale: e.deltaY > 0 ? -0.05 : 0.05,
|
||||
x: touchs[0].pageX,
|
||||
y: touchs[0].pageY
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
// #ifdef H5
|
||||
mounted() {
|
||||
platform === 'H5' && this.initH5Events();
|
||||
},
|
||||
// #endif
|
||||
setPlatform(p) {
|
||||
platform = p;
|
||||
},
|
||||
methods: {
|
||||
// #ifdef H5
|
||||
getTouchEvent(e) {
|
||||
e.touches = [
|
||||
{ pageX: e.pageX, pageY: e.pageY }
|
||||
];
|
||||
return e;
|
||||
},
|
||||
initH5Events() {
|
||||
const preview = document.getElementById('pic-preview');
|
||||
preview?.addEventListener('mousedown', (e, ev) => {
|
||||
mouseEvent = true;
|
||||
this.touchstart(this.getTouchEvent(e));
|
||||
});
|
||||
preview?.addEventListener('mousemove', (e) => {
|
||||
if (!mouseEvent) return;
|
||||
this.touchmove(this.getTouchEvent(e));
|
||||
});
|
||||
preview?.addEventListener('mouseup', (e) => {
|
||||
mouseEvent = false;
|
||||
this.touchend(this.getTouchEvent(e))
|
||||
});
|
||||
preview?.addEventListener('mouseleave', (e) => {
|
||||
mouseEvent = false;
|
||||
this.touchend(this.getTouchEvent(e))
|
||||
});
|
||||
},
|
||||
// #endif
|
||||
async getInstance() {
|
||||
// #ifdef APP-PLUS
|
||||
if(platform === 'APP')
|
||||
return this.$ownerInstance
|
||||
? Promise.resolve(this.$ownerInstance)
|
||||
: new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
resolve(await this.getInstance());
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if(platform === 'H5')
|
||||
return Promise.resolve(this);
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
* 初始化:观察数据变更
|
||||
* @param {Object} newVal 新数据
|
||||
* @param {Object} oldVal 旧数据
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
initObserver: async function(newVal, oldVal, o, i) {
|
||||
// console.log('initObserver', newVal, oldVal, o, i)
|
||||
if(newVal && (!img.src || timestamp !== newVal.timestamp)) {
|
||||
timestamp = newVal.timestamp;
|
||||
img = newVal.img;
|
||||
sys = newVal.sys;
|
||||
area = newVal.area;
|
||||
minScale = img.minScale;
|
||||
resetData();
|
||||
const instance = await this.getInstance()
|
||||
img.src && changeImageRect({
|
||||
instance,
|
||||
x: (sys.windowWidth - img.width) / 2,
|
||||
y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2
|
||||
});
|
||||
changeAreaRect({
|
||||
instance
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 鼠标滚轮滚动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
mousewheel: function(e, o) {
|
||||
// h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel
|
||||
},
|
||||
/**
|
||||
* 触摸开始
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchstart: function(e, o) {
|
||||
if(!img.src) return;
|
||||
touches = getTouchs(e.touches);
|
||||
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||
if(touches.length === 1 && activeAngle !== 0) {
|
||||
touchType = 'stretch'; // 伸缩裁剪区域
|
||||
} else {
|
||||
touchType = '';
|
||||
}
|
||||
// console.log('touchstart', e, activeAngle)
|
||||
},
|
||||
/**
|
||||
* 触摸移动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchmove: async function(e, o) {
|
||||
if(!img.src) return;
|
||||
// console.log('touchmove', e, o)
|
||||
e.touches = getTouchs(e.touches);
|
||||
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||
var point = e.touches[0];
|
||||
var start = touches[0];
|
||||
var x = point.pageX - start.pageX;
|
||||
var y = point.pageY - start.pageY;
|
||||
if(x !== 0 || y !== 0) {
|
||||
var maxX = area.width * (1 - area.minScale);
|
||||
var maxY = area.height * (1 - area.minScale);
|
||||
// console.log(x, y, maxX, maxY, offset, area)
|
||||
touches[0] = point;
|
||||
switch(activeAngle) {
|
||||
case 1: // 左上角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.top;
|
||||
// console.log(x, y, offset.left > area.left)
|
||||
// console.log(maxX, maxY)
|
||||
if(x >= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, offset.x - area.left)
|
||||
: false;
|
||||
if(x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 2: // 右上角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.top;
|
||||
if(x <= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 3: // 左下角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.bottom;
|
||||
if(x >= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, offset.x - area.left)
|
||||
: false;
|
||||
if(x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
case 4: // 右下角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.bottom;
|
||||
if(x <= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// console.log(x, y, JSON.stringify(areaOffset))
|
||||
changeAreaRect({
|
||||
instance: await this.getInstance(),
|
||||
});
|
||||
// this.draw();
|
||||
}
|
||||
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||
var start = getDistanceByTouches(touches);
|
||||
var end = getDistanceByTouches(e.touches);
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
check: !area.bounce,
|
||||
scale: (end.c - start.c) / 100,
|
||||
x: end.x,
|
||||
y: end.y
|
||||
});
|
||||
touchType = 'scale';
|
||||
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||
touchType = 'move';
|
||||
} else {
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: !area.bounce,
|
||||
x: e.touches[0].pageX - touches[0].pageX,
|
||||
y: e.touches[0].pageY - touches[0].pageY
|
||||
});
|
||||
touchType = 'move';
|
||||
}
|
||||
touches = e.touches;
|
||||
},
|
||||
/**
|
||||
* 触摸结束
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchend: async function(e, o) {
|
||||
if(!img.src) return;
|
||||
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||
// 裁剪区域宽度被缩放到多少
|
||||
var left = areaOffset.left;
|
||||
var right = areaOffset.right;
|
||||
var top = areaOffset.top;
|
||||
var bottom = areaOffset.bottom;
|
||||
var w = area.width + right - left;
|
||||
var h = area.height + bottom - top;
|
||||
// 图像放大倍数
|
||||
var p = scale * (area.width / w) - scale;
|
||||
// 复原裁剪区域
|
||||
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
changeAreaRect({
|
||||
instance: await this.getInstance(),
|
||||
});
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
scale: p,
|
||||
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||
});
|
||||
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: true
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
rotateImage: async function(r) {
|
||||
rotate = (rotate + (r || 90)) % 360;
|
||||
|
||||
if(img.minScale >= 1) {
|
||||
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||
minScale = 1;
|
||||
if(img.width < area.height) {
|
||||
minScale = area.height / img.oldWidth;
|
||||
} else if(img.height < area.width) {
|
||||
minScale = (area.width / img.oldHeight)
|
||||
}
|
||||
if(minScale !== 1) {
|
||||
scaleImage({
|
||||
instance: await this.getInstance(),
|
||||
scale: minScale - scale,
|
||||
x: sys.windowWidth / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||
changeImageRect({
|
||||
instance: await this.getInstance(),
|
||||
check: true,
|
||||
x: -ox - oy,
|
||||
y: -oy + ox
|
||||
});
|
||||
},
|
||||
rotateImage90: function() {
|
||||
this.rotateImage(90)
|
||||
},
|
||||
rotateImage270: function() {
|
||||
this.rotateImage(270)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,790 @@
|
||||
<template>
|
||||
<view class="image-cropper" :style="{ zIndex }" @wheel="cropper.mousewheel">
|
||||
<canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{
|
||||
width: `${canvansWidth}px`,
|
||||
height: `${canvansHeight}px`,
|
||||
}"></canvas>
|
||||
<canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{
|
||||
width: `${canvansWidth}px`,
|
||||
height: `${canvansHeight}px`,
|
||||
}"></canvas>
|
||||
<view id="pic-preview" class="pic-preview" :change:init="cropper.initObserver" :init="initData"
|
||||
@touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend">
|
||||
<image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp>
|
||||
</image>
|
||||
<view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block"
|
||||
:style="cropper.maskStylesList[index]"></view>
|
||||
<view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view>
|
||||
<view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles">
|
||||
<view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view>
|
||||
</view>
|
||||
<block v-if="showGrid">
|
||||
<view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid"
|
||||
:style="cropper.gridStylesList[index]"></view>
|
||||
</block>
|
||||
<block v-if="showAngle">
|
||||
<view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle"
|
||||
:style="cropper.angleStylesList[index]">
|
||||
<view :style="[
|
||||
{
|
||||
width: `${angleSize}px`,
|
||||
height: `${angleSize}px`,
|
||||
},
|
||||
]"></view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<slot />
|
||||
<view class="fixed-bottom safe-area-inset-bottom" :style="{ zIndex: initData.area.zIndex + 99 }">
|
||||
<view v-if="(rotatable || reverseRotatable) && !!imgSrc" class="action-bar">
|
||||
<view v-if="reverseRotatable" class="rotate-icon" @click="cropper.rotateImage270"></view>
|
||||
<view v-if="rotatable" class="rotate-icon is-reverse" @click="cropper.rotateImage90"></view>
|
||||
</view>
|
||||
<view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view>
|
||||
<block v-else-if="!!imgSrc">
|
||||
<view class="rechoose" @click="chooseImage">重选</view>
|
||||
<button class="button" size="mini" @click="cropClick">确定</button>
|
||||
</block>
|
||||
<view v-else class="choose-btn" @click="chooseImage">选择图片</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- #ifdef APP-VUE -->
|
||||
<script module="cropper" lang="renderjs">
|
||||
import cropper from './qf-image-cropper.render.js';
|
||||
// vue3 app renderjs中条件编译无效
|
||||
cropper.setPlatform('APP');
|
||||
export default {
|
||||
mixins: [ cropper ]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<script module="cropper" lang="renderjs">
|
||||
import cropper from './qf-image-cropper.render.js';
|
||||
export default {
|
||||
mixins: [ cropper ]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ -->
|
||||
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script>
|
||||
<!-- #endif -->
|
||||
<script>
|
||||
/** 裁剪区域最大宽高所占屏幕宽度百分比 */
|
||||
const AREA_SIZE = 75;
|
||||
/** 图片默认宽高 */
|
||||
const IMG_SIZE = 300;
|
||||
|
||||
export default {
|
||||
name: "qf-image-cropper",
|
||||
// #ifdef MP-WEIXIN
|
||||
options: {
|
||||
// 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响
|
||||
styleIsolation: "isolated",
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
/** 图片资源地址 */
|
||||
src: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
/** 裁剪宽度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||
width: {
|
||||
type: Number,
|
||||
default: IMG_SIZE,
|
||||
},
|
||||
/** 裁剪高度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: IMG_SIZE,
|
||||
},
|
||||
/** 是否绘制裁剪区域边框 */
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 是否绘制裁剪区域网格参考线 */
|
||||
showGrid: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 是否展示四个支持伸缩的角 */
|
||||
showAngle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 裁剪区域最小缩放倍数 */
|
||||
areaScale: {
|
||||
type: Number,
|
||||
default: 0.3,
|
||||
},
|
||||
/** 图片最小缩放倍数 */
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
/** 图片最大缩放倍数 */
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
|
||||
checkRange: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
},
|
||||
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
|
||||
bounce: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 是否支持翻转 */
|
||||
rotatable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 是否支持逆向翻转 */
|
||||
reverseRotatable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** 是否支持从本地选择素材 */
|
||||
choosable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
|
||||
gpu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** 四个角尺寸,单位px */
|
||||
angleSize: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
/** 四个角边框宽度,单位px */
|
||||
angleBorderWidth: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
},
|
||||
/** 裁剪图片圆角半径,单位px */
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
|
||||
fileType: {
|
||||
type: String,
|
||||
default: "png",
|
||||
},
|
||||
/**
|
||||
* 图片从绘制到生成所需时间,单位ms
|
||||
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
|
||||
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间
|
||||
*/
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 1000,
|
||||
},
|
||||
// #ifdef H5
|
||||
/**
|
||||
* 页面是否是原生标题栏
|
||||
* H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。
|
||||
* 注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的
|
||||
*/
|
||||
navigation: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
emits: ["crop"],
|
||||
data() {
|
||||
return {
|
||||
// 用不同 id 使 v-for key 不重复
|
||||
maskList: [
|
||||
{ id: "crop-mask-block-1" },
|
||||
{ id: "crop-mask-block-2" },
|
||||
{ id: "crop-mask-block-3" },
|
||||
{ id: "crop-mask-block-4" },
|
||||
],
|
||||
gridList: [
|
||||
{ id: "crop-grid-1" },
|
||||
{ id: "crop-grid-2" },
|
||||
{ id: "crop-grid-3" },
|
||||
{ id: "crop-grid-4" },
|
||||
],
|
||||
angleList: [
|
||||
{ id: "crop-angle-1" },
|
||||
{ id: "crop-angle-2" },
|
||||
{ id: "crop-angle-3" },
|
||||
{ id: "crop-angle-4" },
|
||||
],
|
||||
/** 本地缓存的图片路径 */
|
||||
imgSrc: "",
|
||||
/** 图片的裁剪宽度 */
|
||||
imgWidth: IMG_SIZE,
|
||||
/** 图片的裁剪高度 */
|
||||
imgHeight: IMG_SIZE,
|
||||
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
|
||||
widthPercent: AREA_SIZE,
|
||||
/** 裁剪区域最大高度所占屏幕宽度百分比 */
|
||||
heightPercent: AREA_SIZE,
|
||||
/** 裁剪区域布局信息 */
|
||||
area: {},
|
||||
/** 未被缩放过的图片宽 */
|
||||
oldWidth: 0,
|
||||
/** 未被缩放过的图片高 */
|
||||
oldHeight: 0,
|
||||
/** 系统信息 */
|
||||
sys: uni.getSystemInfoSync(),
|
||||
scaleWidth: 0,
|
||||
scaleHeight: 0,
|
||||
rotate: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
use2d: false,
|
||||
canvansWidth: 0,
|
||||
canvansHeight: 0,
|
||||
// imageStyles: {},
|
||||
// maskStylesList: [{}, {}, {}, {}],
|
||||
// borderStyles: {},
|
||||
// gridStylesList: [{}, {}, {}, {}],
|
||||
// angleStylesList: [{}, {}, {}, {}],
|
||||
// circleBoxStyles: {},
|
||||
// circleStyles: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
initData() {
|
||||
// console.log('initData')
|
||||
return {
|
||||
timestamp: new Date().getTime(),
|
||||
area: {
|
||||
...this.area,
|
||||
bounce: this.bounce,
|
||||
showBorder: this.showBorder,
|
||||
showGrid: this.showGrid,
|
||||
showAngle: this.showAngle,
|
||||
angleSize: this.angleSize,
|
||||
angleBorderWidth: this.angleBorderWidth,
|
||||
minScale: this.areaScale,
|
||||
widthPercent: this.widthPercent,
|
||||
heightPercent: this.heightPercent,
|
||||
radius: this.radius,
|
||||
checkRange: this.checkRange,
|
||||
zIndex: +this.zIndex || 0,
|
||||
},
|
||||
sys: this.sys,
|
||||
img: {
|
||||
minScale: this.minScale,
|
||||
maxScale: this.maxScale,
|
||||
src: this.imgSrc,
|
||||
width: this.oldWidth,
|
||||
height: this.oldHeight,
|
||||
oldWidth: this.oldWidth,
|
||||
oldHeight: this.oldHeight,
|
||||
gpu: this.gpu,
|
||||
},
|
||||
};
|
||||
},
|
||||
imgProps() {
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
src: this.src,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
imgProps: {
|
||||
handler(val, oldVal) {
|
||||
// 自定义裁剪尺,示例如下:
|
||||
this.imgWidth = Number(val.width) || IMG_SIZE;
|
||||
this.imgHeight = Number(val.height) || IMG_SIZE;
|
||||
let use2d = true;
|
||||
// #ifndef MP-WEIXIN
|
||||
use2d = false;
|
||||
// #endif
|
||||
// if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) {
|
||||
// use2d = false;
|
||||
// }
|
||||
let canvansWidth = this.imgWidth;
|
||||
let canvansHeight = this.imgHeight;
|
||||
let size = Math.max(canvansWidth, canvansHeight);
|
||||
let scalc = 1;
|
||||
if (size > 1365) {
|
||||
scalc = 1365 / size;
|
||||
}
|
||||
this.canvansWidth = canvansWidth * scalc;
|
||||
this.canvansHeight = canvansHeight * scalc;
|
||||
this.use2d = use2d;
|
||||
this.initArea();
|
||||
const src = val.src || this.imgSrc;
|
||||
src && this.initImage(src, oldVal === undefined);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 提供给wxs调用,用来接收图片变更数据 */
|
||||
dataChange(e) {
|
||||
// console.log('dataChange', e)
|
||||
this.scaleWidth = e.width;
|
||||
this.scaleHeight = e.height;
|
||||
this.rotate = e.rotate;
|
||||
this.offsetX = e.x;
|
||||
this.offsetY = e.y;
|
||||
},
|
||||
/** 初始化裁剪区域布局信息 */
|
||||
initArea() {
|
||||
// 底部操作栏高度 = 底部底部操作栏内容高度 + 设备底部安全区域高度
|
||||
this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom;
|
||||
// #ifndef H5
|
||||
this.sys.windowTop = 0;
|
||||
this.sys.navigation = true;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
// h5平台的窗口高度是包含标题栏的
|
||||
this.sys.windowTop = this.sys.windowTop || 44;
|
||||
this.sys.navigation = this.navigation;
|
||||
// #endif
|
||||
let wp = this.widthPercent;
|
||||
let hp = this.heightPercent;
|
||||
if (this.imgWidth > this.imgHeight) {
|
||||
hp = (hp * this.imgHeight) / this.imgWidth;
|
||||
} else if (this.imgWidth < this.imgHeight) {
|
||||
wp = (wp * this.imgWidth) / this.imgHeight;
|
||||
}
|
||||
const size =
|
||||
this.sys.windowWidth > this.sys.windowHeight
|
||||
? this.sys.windowHeight
|
||||
: this.sys.windowWidth;
|
||||
const width = (size * wp) / 100;
|
||||
const height = (size * hp) / 100;
|
||||
const left = (this.sys.windowWidth - width) / 2;
|
||||
const right = left + width;
|
||||
const top =
|
||||
(this.sys.windowHeight +
|
||||
this.sys.windowTop -
|
||||
this.sys.offsetBottom -
|
||||
height) /
|
||||
2;
|
||||
const bottom =
|
||||
this.sys.windowHeight +
|
||||
this.sys.windowTop -
|
||||
this.sys.offsetBottom -
|
||||
top;
|
||||
this.area = { width, height, left, right, top, bottom };
|
||||
this.scaleWidth = width;
|
||||
this.scaleHeight = height;
|
||||
},
|
||||
/** 从本地选取图片 */
|
||||
chooseImage(options) {
|
||||
// #ifdef MP-WEIXIN || MP-JD
|
||||
if (uni.chooseMedia) {
|
||||
uni.chooseMedia({
|
||||
...options,
|
||||
count: 1,
|
||||
mediaType: ["image"],
|
||||
success: (res) => {
|
||||
this.resetData();
|
||||
this.initImage(res.tempFiles[0].tempFilePath);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
uni.chooseImage({
|
||||
...options,
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
this.resetData();
|
||||
this.initImage(res.tempFiles[0].path);
|
||||
},
|
||||
});
|
||||
},
|
||||
/** 重置数据 */
|
||||
resetData() {
|
||||
this.imgSrc = "";
|
||||
this.rotate = 0;
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
this.initArea();
|
||||
},
|
||||
/**
|
||||
* 初始化图片信息
|
||||
* @param {String} url 图片链接
|
||||
*/
|
||||
initImage(url, isFirst) {
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success: async (res) => {
|
||||
if (isFirst && this.src === url)
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
this.imgSrc = res.path;
|
||||
let scale = res.width / res.height;
|
||||
let areaScale = this.area.width / this.area.height;
|
||||
if (scale > 1) {
|
||||
// 横向图片
|
||||
if (scale >= areaScale) {
|
||||
// 图片宽不小于目标宽,则高固定,宽自适应
|
||||
this.scaleWidth =
|
||||
(this.scaleHeight / res.height) *
|
||||
this.scaleWidth *
|
||||
(res.width / this.scaleWidth);
|
||||
} else {
|
||||
// 否则宽固定、高自适应
|
||||
this.scaleHeight = (res.height * this.scaleWidth) / res.width;
|
||||
}
|
||||
} else {
|
||||
// 纵向图片
|
||||
if (scale <= areaScale) {
|
||||
// 图片高不小于目标高,宽固定,高自适应
|
||||
this.scaleHeight =
|
||||
((this.scaleWidth / res.width) * this.scaleHeight) /
|
||||
(this.scaleHeight / res.height);
|
||||
} else {
|
||||
// 否则高固定,宽自适应
|
||||
this.scaleWidth = (res.width * this.scaleHeight) / res.height;
|
||||
}
|
||||
}
|
||||
// 记录原始宽高,为缩放比列做限制
|
||||
this.oldWidth = this.scaleWidth;
|
||||
this.oldHeight = this.scaleHeight;
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error(err);
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 剪切图片圆角
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {Number} radius 圆角半径
|
||||
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||
* @param {Function} drawImage 执行剪切时所调用的绘图方法,入参为是否执行了剪切
|
||||
*/
|
||||
drawClipImage(ctx, radius, scale, drawImage) {
|
||||
if (radius > 0) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
const w = this.canvansWidth;
|
||||
const h = this.canvansHeight;
|
||||
if (w === h && radius >= w / 2) {
|
||||
// 圆形
|
||||
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
|
||||
} else {
|
||||
// 圆角矩形
|
||||
if (w !== h) {
|
||||
// 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(w / 2, h / 2, radius);
|
||||
// radius = Math.min(Math.max(w, h) / 2, radius);
|
||||
}
|
||||
ctx.moveTo(radius, 0);
|
||||
ctx.arcTo(w, 0, w, h, radius);
|
||||
ctx.arcTo(w, h, 0, h, radius);
|
||||
ctx.arcTo(0, h, 0, 0, radius);
|
||||
ctx.arcTo(0, 0, w, 0, radius);
|
||||
ctx.closePath();
|
||||
}
|
||||
ctx.clip();
|
||||
drawImage && drawImage(true);
|
||||
ctx.restore();
|
||||
} else {
|
||||
drawImage && drawImage(false);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 旋转图片
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {Number} rotate 旋转角度
|
||||
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||
*/
|
||||
drawRotateImage(ctx, rotate, scale) {
|
||||
if (rotate !== 0) {
|
||||
// 1. 以图片中心点为旋转中心点
|
||||
const x = (this.scaleWidth * scale) / 2;
|
||||
const y = (this.scaleHeight * scale) / 2;
|
||||
ctx.translate(x, y);
|
||||
// 2. 旋转画布
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
// 3. 旋转完画布后恢复设置旋转中心时所做的偏移
|
||||
ctx.translate(-x, -y);
|
||||
}
|
||||
},
|
||||
drawImage(ctx, image, callback) {
|
||||
// 生成图片的实际尺寸与截取区域比
|
||||
const scale = this.canvansWidth / this.area.width;
|
||||
if (this.backgroundColor) {
|
||||
if (ctx.setFillStyle) ctx.setFillStyle(this.backgroundColor);
|
||||
else ctx.fillStyle = this.backgroundColor;
|
||||
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
}
|
||||
this.drawClipImage(ctx, this.radius, scale, () => {
|
||||
this.drawRotateImage(ctx, this.rotate, scale);
|
||||
const r = this.rotate / 90;
|
||||
ctx.drawImage(
|
||||
image,
|
||||
[
|
||||
this.offsetX - this.area.left,
|
||||
this.offsetY - this.area.top,
|
||||
-(this.offsetX - this.area.left),
|
||||
-(this.offsetY - this.area.top),
|
||||
][r] * scale,
|
||||
[
|
||||
this.offsetY - this.area.top,
|
||||
-(this.offsetX - this.area.left),
|
||||
-(this.offsetY - this.area.top),
|
||||
this.offsetX - this.area.left,
|
||||
][r] * scale,
|
||||
this.scaleWidth * scale,
|
||||
this.scaleHeight * scale
|
||||
);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 绘图
|
||||
* @param {Object} canvas
|
||||
* @param {Object} ctx canvas 的绘图上下文对象
|
||||
* @param {String} src 图片路径
|
||||
* @param {Function} callback 开始绘制时回调
|
||||
*/
|
||||
draw2DImage(canvas, ctx, src, callback) {
|
||||
// console.log('draw2DImage', canvas, ctx, src, callback)
|
||||
if (canvas) {
|
||||
const image = canvas.createImage();
|
||||
image.onload = () => {
|
||||
this.drawImage(ctx, image);
|
||||
// 如果觉得`生成时间过长`或`出现生成图片空白`可尝试调整延迟时间
|
||||
callback && setTimeout(callback, this.delay);
|
||||
};
|
||||
image.onerror = (err) => {
|
||||
console.error(err);
|
||||
uni.hideLoading();
|
||||
};
|
||||
image.src = src;
|
||||
} else {
|
||||
this.drawImage(ctx, src);
|
||||
setTimeout(() => {
|
||||
ctx.draw(false, callback);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 画布转图片到本地缓存
|
||||
* @param {Object} canvas
|
||||
* @param {String} canvasId
|
||||
*/
|
||||
canvasToTempFilePath(canvas, canvasId) {
|
||||
// console.log('canvasToTempFilePath', canvas, canvasId)
|
||||
uni.canvasToTempFilePath(
|
||||
{
|
||||
canvas,
|
||||
canvasId,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.canvansWidth,
|
||||
height: this.canvansHeight,
|
||||
destWidth: this.imgWidth, // 必要,保证生成图片宽度不受设备分辨率影响
|
||||
destHeight: this.imgHeight, // 必要,保证生成图片高度不受设备分辨率影响
|
||||
fileType: this.fileType, // 目标文件的类型,默认png
|
||||
success: (res) => {
|
||||
// 生成的图片临时文件路径
|
||||
this.handleImage(res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: "裁剪失败,生成图片异常!", icon: "none" });
|
||||
},
|
||||
},
|
||||
this
|
||||
);
|
||||
},
|
||||
/** 确认裁剪 */
|
||||
cropClick() {
|
||||
uni.showLoading({ title: "裁剪中...", mask: true });
|
||||
if (!this.use2d) {
|
||||
const ctx = uni.createCanvasContext("imgCanvas", this);
|
||||
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
this.draw2DImage(null, ctx, this.imgSrc, () => {
|
||||
this.canvasToTempFilePath(null, "imgCanvas");
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query
|
||||
.select("#imgCanvas")
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
const canvas = res[0].node;
|
||||
|
||||
const dpr = uni.getSystemInfoSync().pixelRatio;
|
||||
canvas.width = res[0].width * dpr;
|
||||
canvas.height = res[0].height * dpr;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||
|
||||
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
|
||||
this.canvasToTempFilePath(canvas);
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
handleImage(tempFilePath) {
|
||||
// 在H5平台下,tempFilePath 为 base64
|
||||
// console.log(tempFilePath)
|
||||
uni.hideLoading();
|
||||
this.$emit("crop", { tempFilePath });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-cropper {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #000;
|
||||
|
||||
.img-canvas {
|
||||
position: absolute !important;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.pic-preview {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.crop-mask-block {
|
||||
background-color: rgba(51, 51, 51, 0.8);
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.crop-circle-box {
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
.crop-circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.crop-image {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
display: block !important;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.crop-border {
|
||||
position: fixed;
|
||||
border: 1px solid #fff;
|
||||
box-sizing: border-box;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.crop-grid {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
border-style: dashed;
|
||||
border-color: #fff;
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.crop-angle {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
border-style: solid;
|
||||
border-color: #fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-bottom {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: $uni-bg-color-grey;
|
||||
|
||||
.action-bar {
|
||||
position: absolute;
|
||||
top: -90rpx;
|
||||
left: 10rpx;
|
||||
display: flex;
|
||||
|
||||
.rotate-icon {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=");
|
||||
background-size: 60% 60%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
|
||||
&.is-reverse {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rechoose {
|
||||
color: $uni-color-primary;
|
||||
padding: 0 $uni-spacing-row-lg;
|
||||
line-height: 100rpx;
|
||||
}
|
||||
|
||||
.choose-btn {
|
||||
color: $uni-color-primary;
|
||||
text-align: center;
|
||||
line-height: 100rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: auto $uni-spacing-row-lg auto auto;
|
||||
background-color: $uni-color-primary;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.safe-area-inset-bottom {
|
||||
padding-bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
|
||||
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>=11.2
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,604 @@
|
||||
/**
|
||||
* 图片编辑器-手势监听
|
||||
* 1. wxs 暂不支持 es6 语法
|
||||
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上(uni-app 2.2.5及以上版本)
|
||||
*/
|
||||
/** 图片偏移量 */
|
||||
var offset = { x: 0, y: 0 };
|
||||
/** 图片缩放比例 */
|
||||
var scale = 1;
|
||||
/** 图片最小缩放比例 */
|
||||
var minScale = 1;
|
||||
/** 图片旋转角度 */
|
||||
var rotate = 0;
|
||||
/** 触摸点 */
|
||||
var touches = [];
|
||||
/** 图片布局信息 */
|
||||
var img = {};
|
||||
/** 系统信息 */
|
||||
var sys = {};
|
||||
/** 裁剪区域布局信息 */
|
||||
var area = {};
|
||||
/** 触摸行为类型 */
|
||||
var touchType = '';
|
||||
/** 操作角的位置 */
|
||||
var activeAngle = 0;
|
||||
/** 裁剪区域布局信息偏移量 */
|
||||
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
/**
|
||||
* 计算两点间距
|
||||
* @param {Object} touches 触摸点信息
|
||||
*/
|
||||
function getDistanceByTouches(touches) {
|
||||
// 根据勾股定理求两点间距离
|
||||
var a = touches[1].pageX - touches[0].pageX;
|
||||
var b = touches[1].pageY - touches[0].pageY;
|
||||
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||
// 求两点间的中点坐标
|
||||
// 1. a、b可能为负值
|
||||
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||
var x = touches[1].pageX - a / 2;
|
||||
var y = touches[1].pageY - b / 2;
|
||||
return { c, x, y };
|
||||
};
|
||||
/**
|
||||
* 修正取值
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @param {Object} c
|
||||
* @param {Object} reverse 是否反向
|
||||
*/
|
||||
function correctValue(a, b, c, reverse) {
|
||||
return reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||
* @param {Object} e 点坐标
|
||||
*/
|
||||
function checkRange(e) {
|
||||
var r = rotate / 90 % 2;
|
||||
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||
var o = (img.height - img.width) / 2; // 宽高差值一半
|
||||
return {
|
||||
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, img.height < area.height),
|
||||
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, img.width < area.width)
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 变更图片布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeImageRect(e) {
|
||||
offset.x += e.x || 0;
|
||||
offset.y += e.y || 0;
|
||||
var image = e.instance.selectComponent('.crop-image');
|
||||
if(e.check && area.checkRange) { // 检查边界
|
||||
var point = checkRange(offset);
|
||||
if(offset.x !== point.x || offset.y !== point.y) {
|
||||
offset = point;
|
||||
}
|
||||
}
|
||||
// image.setStyle({
|
||||
// width: img.width + 'px',
|
||||
// height: img.height + 'px',
|
||||
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
|
||||
// });
|
||||
var ox = (img.width - img.oldWidth) / 2;
|
||||
var oy = (img.height - img.oldHeight) / 2;
|
||||
image.setStyle({
|
||||
width: img.oldWidth + 'px',
|
||||
height: img.oldHeight + 'px',
|
||||
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||
});
|
||||
|
||||
e.instance.callMethod('dataChange', {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
x: offset.x,
|
||||
y: offset.y,
|
||||
rotate: rotate
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 变更裁剪区域布局信息
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function changeAreaRect(e) {
|
||||
// 变更蒙版样式
|
||||
var masks = e.instance.selectAllComponents('.crop-mask-block');
|
||||
var maskStyles = [
|
||||
{
|
||||
left: 0,
|
||||
width: (area.left + areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.right + areaOffset.right) + 'px',
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: 0,
|
||||
height: (area.top + areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
},
|
||||
{
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||
bottom: 0,
|
||||
'z-index': area.zIndex + 2
|
||||
}
|
||||
];
|
||||
var len = masks.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
masks[i].setStyle(maskStyles[i]);
|
||||
}
|
||||
|
||||
// 变更边框样式
|
||||
if(area.showBorder) {
|
||||
var border = e.instance.selectComponent('.crop-border');
|
||||
border.setStyle({
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
});
|
||||
}
|
||||
|
||||
// 变更参考线样式
|
||||
if(area.showGrid) {
|
||||
var grids = e.instance.selectAllComponents('.crop-grid');
|
||||
var gridStyles = [
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '1px 0 0 0',
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
right: (area.right + areaOffset.right) + 'px',
|
||||
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 1px 0 0',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
];
|
||||
var len = grids.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
grids[i].setStyle(gridStyles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 变更四个伸缩角样式
|
||||
if(area.showAngle) {
|
||||
var angles = e.instance.selectAllComponents('.crop-angle');
|
||||
var angleStyles = [
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
},
|
||||
{
|
||||
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||
'z-index': area.zIndex + 3
|
||||
}
|
||||
];
|
||||
var len = angles.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
angles[i].setStyle(angleStyles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 变更圆角样式
|
||||
if(area.radius > 0) {
|
||||
var circleBox = e.instance.selectComponent('.crop-circle-box');
|
||||
var circle = e.instance.selectComponent('.crop-circle');
|
||||
var radius = area.radius;
|
||||
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||
radius = (area.width / 2);
|
||||
} else { // 圆角矩形
|
||||
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||
}
|
||||
}
|
||||
circleBox.setStyle({
|
||||
left: (area.left + areaOffset.left) + 'px',
|
||||
top: (area.top + areaOffset.top) + 'px',
|
||||
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||
'z-index': area.zIndex + 2
|
||||
});
|
||||
circle.setStyle({
|
||||
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||
'border-radius': radius + 'px'
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 缩放图片
|
||||
* @param {Object} e 布局信息
|
||||
*/
|
||||
function scaleImage(e) {
|
||||
var last = scale;
|
||||
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||
if(last !== scale) {
|
||||
img.width = img.oldWidth * scale;
|
||||
img.height = img.oldHeight * scale;
|
||||
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||
e.x = (e.x - offset.x) * (1 - scale / last);
|
||||
e.y = (e.y - offset.y) * (1 - scale / last);
|
||||
changeImageRect(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* 获取触摸点在哪个角
|
||||
* @param {number} x 触摸点x轴坐标
|
||||
* @param {number} y 触摸点y轴坐标
|
||||
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||
*/
|
||||
function getToucheAngle(x, y) {
|
||||
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 1; // 左上角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 2; // 右上角
|
||||
}
|
||||
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
|
||||
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||
return 3; // 左下角
|
||||
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||
return 4; // 右下角
|
||||
}
|
||||
}
|
||||
return 0; // 无触摸到角
|
||||
};
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
function resetData() {
|
||||
offset = { x: 0, y: 0 };
|
||||
scale = 1;
|
||||
minScale = img.minScale;
|
||||
rotate = 0;
|
||||
};
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
function rotateImage(e, o, r) {
|
||||
rotate = (rotate + r) % 360;
|
||||
|
||||
if(img.minScale >= 1) {
|
||||
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||
minScale = 1;
|
||||
if(img.width < area.height) {
|
||||
minScale = area.height / img.oldWidth;
|
||||
} else if(img.height < area.width) {
|
||||
minScale = (area.width / img.oldHeight)
|
||||
}
|
||||
if(minScale !== 1) {
|
||||
scaleImage({
|
||||
instance: o,
|
||||
scale: minScale - scale,
|
||||
x: sys.windowWidth / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: true,
|
||||
x: -ox - oy,
|
||||
y: -oy + ox
|
||||
});
|
||||
};
|
||||
module.exports = {
|
||||
/**
|
||||
* 初始化:观察数据变更
|
||||
* @param {Object} newVal 新数据
|
||||
* @param {Object} oldVal 旧数据
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
initObserver: function(newVal, oldVal, o, i) {
|
||||
if(newVal) {
|
||||
img = newVal.img;
|
||||
sys = newVal.sys;
|
||||
area = newVal.area;
|
||||
minScale = img.minScale;
|
||||
resetData();
|
||||
img.src && changeImageRect({
|
||||
instance: o,
|
||||
x: (sys.windowWidth - img.width) / 2,
|
||||
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
|
||||
});
|
||||
changeAreaRect({
|
||||
instance: o
|
||||
});
|
||||
// console.log('initRect', JSON.stringify(newVal))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 鼠标滚轮滚动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
mousewheel: function(e, o) {
|
||||
if(!img.src) return;
|
||||
scaleImage({
|
||||
instance: o,
|
||||
check: true,
|
||||
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 触摸开始
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchstart: function(e, o) {
|
||||
if(!img.src) return;
|
||||
touches = e.touches;
|
||||
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||
if(touches.length === 1 && activeAngle !== 0) {
|
||||
touchType = 'stretch'; // 伸缩裁剪区域
|
||||
} else {
|
||||
touchType = '';
|
||||
}
|
||||
// console.log('touchstart', JSON.stringify(e), activeAngle)
|
||||
},
|
||||
/**
|
||||
* 触摸移动
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchmove: function(e, o) {
|
||||
if(!img.src) return;
|
||||
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
|
||||
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||
var point = e.touches[0];
|
||||
var start = touches[0];
|
||||
var x = point.pageX - start.pageX;
|
||||
var y = point.pageY - start.pageY;
|
||||
if(x !== 0 || y !== 0) {
|
||||
var maxX = area.width * (1 - area.minScale);
|
||||
var maxY = area.height * (1 - area.minScale);
|
||||
// console.log(x, y, maxX, maxY, offset, area)
|
||||
touches[0] = point;
|
||||
switch(activeAngle) {
|
||||
case 1: // 左上角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.top;
|
||||
if(x >= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, offset.x - area.left)
|
||||
: false;
|
||||
if(x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 2: // 右上角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.top;
|
||||
if(x <= 0 && y >= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||
? Math.min(offset.y - area.top, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(y > maxY) y = maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.top = y;
|
||||
}
|
||||
break;
|
||||
case 3: // 左下角
|
||||
x += areaOffset.left;
|
||||
y += areaOffset.bottom;
|
||||
if(x >= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x >= area.left) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, offset.x - area.left)
|
||||
: false;
|
||||
if(x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(x > maxX) x = maxX;
|
||||
y = -x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = -y * area.width / area.height;
|
||||
}
|
||||
areaOffset.left = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
case 4: // 右下角
|
||||
x += areaOffset.right;
|
||||
y += areaOffset.bottom;
|
||||
if(x <= 0 && y <= 0) { // 有效滑动
|
||||
var max = minScale < 1 && area.checkRange && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y + img.height <= area.bottom))
|
||||
? Math.min(area.bottom - offset.y - img.height, area.right - offset.x - img.width)
|
||||
: false;
|
||||
if(-x > -y) { // 以x轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxX = max;
|
||||
if(-x > maxX) x = -maxX;
|
||||
y = x * area.height / area.width;
|
||||
} else { // 以y轴滑动距离为缩放基准
|
||||
if(typeof max === 'number') maxY = max;
|
||||
if(-y > maxY) y = -maxY;
|
||||
x = y * area.width / area.height;
|
||||
}
|
||||
areaOffset.right = x;
|
||||
areaOffset.bottom = y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// console.log(x, y, JSON.stringify(areaOffset))
|
||||
changeAreaRect({
|
||||
instance: o,
|
||||
});
|
||||
// this.draw();
|
||||
}
|
||||
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||
var start = getDistanceByTouches(touches);
|
||||
var end = getDistanceByTouches(e.touches);
|
||||
scaleImage({
|
||||
instance: o,
|
||||
check: !area.bounce,
|
||||
scale: (end.c - start.c) / 100,
|
||||
x: end.x,
|
||||
y: end.y
|
||||
});
|
||||
touchType = 'scale';
|
||||
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||
touchType = 'move';
|
||||
} else {
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: !area.bounce,
|
||||
x: e.touches[0].pageX - touches[0].pageX,
|
||||
y: e.touches[0].pageY - touches[0].pageY
|
||||
});
|
||||
touchType = 'move';
|
||||
}
|
||||
touches = e.touches;
|
||||
},
|
||||
/**
|
||||
* 触摸结束
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
touchend: function(e, o) {
|
||||
if(!img.src) return;
|
||||
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||
// 裁剪区域宽度被缩放到多少
|
||||
var left = areaOffset.left;
|
||||
var right = areaOffset.right;
|
||||
var top = areaOffset.top;
|
||||
var bottom = areaOffset.bottom;
|
||||
var w = area.width + right - left;
|
||||
var h = area.height + bottom - top;
|
||||
// 图像放大倍数
|
||||
var p = scale * (area.width / w) - scale;
|
||||
// 复原裁剪区域
|
||||
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
changeAreaRect({
|
||||
instance: o,
|
||||
});
|
||||
scaleImage({
|
||||
instance: o,
|
||||
scale: p,
|
||||
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||
});
|
||||
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||
changeImageRect({
|
||||
instance: o,
|
||||
check: true
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 顺时针翻转图片90°
|
||||
* @param {Object} e 事件对象
|
||||
* @param {Object} o 组件实例对象
|
||||
*/
|
||||
rotateImage: function(e, o) {
|
||||
rotateImage(e, o, 90);
|
||||
},
|
||||
rotateImage90: function(e, o) {
|
||||
rotateImage(e, o, 90)
|
||||
},
|
||||
rotateImage270: function(e, o) {
|
||||
rotateImage(e, o, 270)
|
||||
},
|
||||
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
|
||||
imageStyles: '',
|
||||
maskStylesList: ['', '', '', ''],
|
||||
borderStyles: '',
|
||||
gridStylesList: ['', '', '', ''],
|
||||
angleStylesList: ['', '', '', ''],
|
||||
circleBoxStyles: '',
|
||||
circleStyles: '',
|
||||
}
|
||||
81
pagesb/components/qf-image-cropper/package.json
Normal file
81
pagesb/components/qf-image-cropper/package.json
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"id": "qf-image-cropper",
|
||||
"displayName": "图片裁剪插件",
|
||||
"version": "2.2.4",
|
||||
"description": "图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。",
|
||||
"keywords": [
|
||||
"qf-image-cropper",
|
||||
"图片裁剪",
|
||||
"图片编辑",
|
||||
"头像裁剪",
|
||||
"小程序"
|
||||
],
|
||||
"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": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "n",
|
||||
"百度": "n",
|
||||
"字节跳动": "n",
|
||||
"QQ": "u",
|
||||
"钉钉": "n",
|
||||
"快手": "n",
|
||||
"飞书": "n",
|
||||
"京东": "n"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "n",
|
||||
"联盟": "n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
pagesb/components/qf-image-cropper/readme.md
Normal file
95
pagesb/components/qf-image-cropper/readme.md
Normal file
@ -0,0 +1,95 @@
|
||||
# qf-image-cropper
|
||||
## 图片裁剪插件
|
||||
uniapp微信小程序图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。
|
||||
|
||||
### 平台支持:
|
||||
1. 支持微信小程序:移动端、PC端、开发者工具
|
||||
2. 支持H5平台(2.1.0版本起)
|
||||
3. 支持APP平台(2.1.5版本起):Android、IOS
|
||||
4. 其他平台暂未测试兼容性未知
|
||||
|
||||
### 支持功能:
|
||||
1. 自定义裁剪尺寸
|
||||
2. 定点等比例缩放:移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||
3. 自由拖动:支持限制滑出边界,也支持回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||
4. 图片翻转:在裁剪尺寸非 1:1 的情况下,翻转时宽高无法铺满裁剪区域时,图片会自动放大到合适尺寸
|
||||
5. 裁剪生成新图片
|
||||
6. 本地选择图片
|
||||
7. 可定制样式:可自由选择是否渲染裁剪边框、可伸缩裁剪顶角、参考线
|
||||
8. 裁剪圆角图片:圆形、圆角矩形
|
||||
|
||||
### 属性说明
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
|:---|:---|:---|:---|
|
||||
| src | String | | 图片资源地址 |
|
||||
| width | Number | 300 | 裁剪宽度 |
|
||||
| height | Number | 300 | 裁剪高度 |
|
||||
| showBorder | Boolean | true | 是否绘制裁剪区域边框 |
|
||||
| showGrid | Boolean | true | 是否绘制裁剪区域网格参考线 |
|
||||
| showAngle | Boolean | true | 是否展示四个支持伸缩的角 |
|
||||
| areaScale | Number | 0.3 | 裁剪区域最小缩放倍数 |
|
||||
| minScale | Number | 1 | 图片最小缩放倍数 |
|
||||
| maxScale | Number | 5 | 图片最大缩放倍数 |
|
||||
| checkRange | Boolean | true | 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 |
|
||||
| backgroundColor | String | | 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性则生成图片存在一定的透明块 |
|
||||
| bounce | Boolean | true | 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 |
|
||||
| rotatable | Boolean | true | 是否支持翻转 |
|
||||
| reverseRotatable | Boolean | false | 是否支持逆向翻转 |
|
||||
| choosable | Boolean | true | 是否支持从本地选择素材 |
|
||||
| gpu | Boolean | false | 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 |
|
||||
| angleSize | Number | 20 | 四个角尺寸,单位px |
|
||||
| angleBorderWidth | Number | 2 | 四个角边框宽度,单位px |
|
||||
| zIndex | Number/String | | 调整组件层级 |
|
||||
| radius | Number | | 裁剪图片圆角半径,单位px |
|
||||
| fileType | String | png | 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' |
|
||||
| delay | Number | 1000 | 图片从绘制到生成所需时间,单位ms<br>微信小程序平台使用 `Canvas 2D` 绘制时有效<br>如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 |
|
||||
| navigation | Boolean | true | 页面是否是原生标题栏:<br>H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 `"navigationStyle": "custom"` 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。<br>注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的 |
|
||||
| @crop | EventHandle | | 剪裁完成后触发,event = { tempFilePath }。在H5平台下,tempFilePath 为 base64 |
|
||||
|
||||
### 基本用法
|
||||
```
|
||||
<template>
|
||||
<div>
|
||||
<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop"></qf-image-cropper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QfImageCropper from '@/components/qf-image-cropper/qf-image-cropper.vue';
|
||||
export default {
|
||||
components: {
|
||||
QfImageCropper
|
||||
},
|
||||
methods: {
|
||||
handleCrop(e) {
|
||||
uni.previewImage({
|
||||
urls: [e.tempFilePath],
|
||||
current: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
通过ref组件实例可在进入页面后直接打开相册选择图片
|
||||
```
|
||||
mounted() {
|
||||
this.$refs.qfImageCropper.chooseImage({ sourceType: ['album'] });
|
||||
}
|
||||
```
|
||||
### 使用说明
|
||||
1.建议在`pages.json`中将引用插件的页面添加一下配置禁止下拉刷新和禁止页面滑动,防止出现性能或页面抖动等问题。
|
||||
```
|
||||
{
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
```
|
||||
2.建议使用本插件不要设置过大宽高的目标图片尺寸,建议1365x1365以内,否则可能会导致如下问题:
|
||||
```
|
||||
1.界面卡顿,内存占用过高
|
||||
2.生成图片失真(模糊)
|
||||
3.确定裁剪后一直显示 `裁剪中...`,该问题是由 `uni.canvasToTempFilePath` 无法回调导致,不同平台不同设备限制可能有所不同。
|
||||
```
|
||||
3.如裁剪后的图片存在偏移的问题,请检查是否受自己项目中父组件或全局样式影响。
|
||||
4.src属性设置网络图片时,图片资源必须是能触发 `getImageInfo` API 的 success 回调才可用于插件裁剪。因此小程序平台获取网络图片信息需先配置download域名白名单才能生效。
|
||||
451
pagesb/flashSale/index.vue
Normal file
451
pagesb/flashSale/index.vue
Normal file
@ -0,0 +1,451 @@
|
||||
<template>
|
||||
<view class="invoice-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<!-- 彩带背景 -->
|
||||
<view class="seckill-bg">
|
||||
<!-- 彩带 -->
|
||||
<view class="confetti"></view>
|
||||
<view class="confetti"></view>
|
||||
<view class="confetti"></view>
|
||||
<view class="confetti"></view>
|
||||
<view class="confetti"></view>
|
||||
<view class="confetti"></view>
|
||||
|
||||
<!-- 光斑 -->
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
|
||||
<!-- 烟雾 -->
|
||||
<view class="smoke"></view>
|
||||
<view class="smoke"></view>
|
||||
</view>
|
||||
|
||||
<view class="content">
|
||||
<view class="title" style="transform: scaleY(1.5);height: 100rpx;display: flex;justify-content: center;color: #ffffff;font-size: 68rpx;font-weight: bolder;font-style: italic;letter-spacing: 4rpx;margin-bottom: 40rpx;">
|
||||
{{ $t('common.FlashSale') }}
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="cityBox">
|
||||
<view>{{$t('common.SwitchRegion')}}</view>
|
||||
<view>
|
||||
<myDropdown v-model="state.city" :items="state.cityList" @change="changeCity"></myDropdown>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="siteListBox">
|
||||
<view class="top">
|
||||
<view>
|
||||
{{$t('common.Countdown')}}
|
||||
</view>
|
||||
<view>
|
||||
{{ countDown?.formatted?.value }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="siteList">
|
||||
<view class="ul" v-for="(item,index) in state.dataList" :key="index" @click="goOrder(item)">
|
||||
<view class="left">
|
||||
<view class="tags">
|
||||
{{ $t("discount",{discount:item.discount}) }}
|
||||
</view>
|
||||
<view class="name">
|
||||
{{item.siteName}}
|
||||
</view>
|
||||
<view class="type">
|
||||
<text>
|
||||
{{item.unitTypeName}}
|
||||
</text>
|
||||
<text>
|
||||
[ {{ item.lockerName }} ]
|
||||
</text>
|
||||
</view>
|
||||
<view class="volume">
|
||||
<text>
|
||||
{{$t('site.ReferenceVolume')}}:{{item.volume}}m³
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="priceBox">
|
||||
<view class="price1">
|
||||
{{currency}} {{item.flashSalePrice}}
|
||||
</view>
|
||||
<view class="price2">
|
||||
{{currency}} {{item.price}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="icon">
|
||||
<uv-icon name="arrow-right" size="14" color="#000000"></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="ul" v-if="state.dataList.length==0">
|
||||
<view style="text-align: center;width: 100%;font-size: 32rpx;padding: 10rpx;">
|
||||
{{ $t('common.noData') }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import flashSaleApi from "@/pagesb/Apis/flashSale.js";
|
||||
import { onLoad,onShow } from '@dcloudio/uni-app';
|
||||
import { getDistance} from '@/utils/common.js';
|
||||
import myDropdown from "@/components/my-dropdown.vue";
|
||||
import { useCountDown } from "@/hooks/useCountDown.js";
|
||||
import { reactive, ref } from "vue";
|
||||
import { currency } from '@/config/index.js'
|
||||
import { useLocation } from "@/hooks/useLocation";
|
||||
const { themeInfo } = useMainStore();
|
||||
const { locationState,getLocation } = useLocation();
|
||||
const state = reactive({
|
||||
goodsIds: [],
|
||||
orderId: "",
|
||||
dataList: [],
|
||||
goodsListRemark: "",
|
||||
cityList: [],
|
||||
startTime:null,
|
||||
endTime:null,
|
||||
allDataList: [],
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
getDataList()
|
||||
})
|
||||
|
||||
const changeCity = () => {
|
||||
applyCityFilter()
|
||||
// 切换城市后,如需重置倒计时显示(如果 startTime/endTime 是全局一样,其实不用)
|
||||
if (state.dataList.length > 0 && state.startTime && state.endTime) {
|
||||
initCountDown()
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 根据当前 city 筛选展示列表
|
||||
const applyCityFilter = () => {
|
||||
if (!state.city) {
|
||||
state.dataList = []
|
||||
return
|
||||
}
|
||||
state.dataList = state.allDataList.filter(x => x.district === state.city)
|
||||
}
|
||||
|
||||
// ✅ 从全量数据生成下拉 district 列表
|
||||
const buildCityListFromAllData = () => {
|
||||
const districts = Array.from(
|
||||
new Set((state.allDataList || []).map(x => x.district).filter(Boolean))
|
||||
)
|
||||
state.cityList = districts.map(d => ({ label: d, value: d }))
|
||||
|
||||
// 如果当前 city 不存在,就默认第一个
|
||||
if (!state.city || !districts.includes(state.city)) {
|
||||
state.city = districts[0] || ""
|
||||
}
|
||||
}
|
||||
|
||||
const goOrder = (item) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/setOrder/index?id=${item.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const getCityList = () => {
|
||||
uni.showLoading()
|
||||
// let data = {}
|
||||
// if (locationState?.latitude && locationState?.longitude) {
|
||||
// data = {
|
||||
// latitude: locationState.latitude,
|
||||
// longitude: locationState.longitude
|
||||
// }
|
||||
// }
|
||||
flashSaleApi.GetFlashSaleDistrict().then(res => {
|
||||
state.cityList = []
|
||||
|
||||
if (res.code == 200) {
|
||||
state.cityList = res.data.map(item =>({label:item,value:item}));
|
||||
if(state.cityList.length>0){
|
||||
state.city = state.cityList[0].value
|
||||
getDataList()
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
const getDataList = () => {
|
||||
uni.showLoading()
|
||||
state.dataList = []
|
||||
flashSaleApi.GetFlashSaleInfo().then(res => {
|
||||
if (res.code == 200) {
|
||||
state.startTime = res.data.flashSaleStartTime
|
||||
state.endTime = res.data.flashSaleEndTime
|
||||
|
||||
const lockerData = res.data.lockerData;
|
||||
// 按照距离排序
|
||||
if (locationState?.latitude && locationState?.longitude) {
|
||||
const { latitude, longitude } = locationState;
|
||||
lockerData.forEach(item => {
|
||||
const { distance, number } = getDistance(latitude, longitude, item.latitude, item.longitude);
|
||||
item.distance = distance;
|
||||
item.distanceNumber = number;
|
||||
});
|
||||
lockerData.sort((a, b) => a.distanceNumber - b.distanceNumber);
|
||||
}
|
||||
state.dataList= lockerData
|
||||
// ✅ 全量源数据
|
||||
state.allDataList = lockerData || []
|
||||
|
||||
// ✅ 用 district 生成 cityList
|
||||
buildCityListFromAllData()
|
||||
|
||||
// ✅ 按当前 city 过滤展示
|
||||
applyCityFilter()
|
||||
if(state.dataList.length>0 && (state.startTime && state.endTime)){
|
||||
initCountDown()
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
let countDown = null
|
||||
const initCountDown = () => {
|
||||
if (!countDown) {
|
||||
countDown = useCountDown(
|
||||
state.startTime,
|
||||
state.endTime,
|
||||
() => {
|
||||
getDataList()
|
||||
uni.showToast({
|
||||
title: '倒计时结束',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// 重置倒计时,不会产生重复计时器
|
||||
countDown.reset(state.startTime, state.endTime)
|
||||
}
|
||||
countDown.start()
|
||||
}
|
||||
onLoad((option) => {
|
||||
state.orderId = option.orderId;
|
||||
getLocation().finally(() => {
|
||||
getDataList();
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
|
||||
.invoice-wrap {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
padding-bottom: 20rpx;
|
||||
background: linear-gradient(to bottom,
|
||||
var(--right-linear),
|
||||
var(--left-linear2));
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
.info {
|
||||
.cityBox {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 16rpx;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.siteListBox {
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 200rpx;
|
||||
|
||||
.top {
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
background-color: #12324D;
|
||||
color: #ffffff;
|
||||
font-size: 42rpx;
|
||||
font-weight: bold;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.siteList {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 0 0 16rpx 16rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
.ul {
|
||||
padding: 20rpx;
|
||||
border-bottom: 1px dashed var(--main-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
&:last-child{
|
||||
border: none;
|
||||
}
|
||||
.left {
|
||||
.tags{
|
||||
text-align: center;
|
||||
padding: 6rpx 10rpx;
|
||||
width: fit-content;
|
||||
color: #FFFFFF;
|
||||
background-color: rgb(252, 89, 116);
|
||||
font-size: 22rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.name {
|
||||
margin-top: 4rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.type {
|
||||
margin-top: 4rpx;
|
||||
text {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.volume {
|
||||
margin-top: 4rpx;
|
||||
text {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.priceBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-style: italic;
|
||||
.price1 {
|
||||
font-size: 52rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.price2 {
|
||||
font-size: 52rpx;
|
||||
font-weight: bold;
|
||||
color: #a8a8a8;
|
||||
position: relative;
|
||||
&:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 120%;
|
||||
height: 2px;
|
||||
background-color: var(--btn-color3);
|
||||
// transform: rotate(9deg);
|
||||
position: absolute;
|
||||
top: 49%;
|
||||
left: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.seckill-bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
// background-color: #ff4b2b; /* 固定纯色背景 */
|
||||
}
|
||||
|
||||
/* 彩带,限制在顶部 300px 内 */
|
||||
.confetti {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
top: 0; /* 起点在顶部 */
|
||||
animation: fall 4s linear infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.confetti:nth-child(1) { left: 5%; animation-delay: 0s; animation-duration: 4s; background: #ffeb3b; }
|
||||
.confetti:nth-child(2) { left: 18%; animation-delay: 0.5s; animation-duration: 3.5s; background: #00e5ff; }
|
||||
.confetti:nth-child(3) { left: 32%; animation-delay: 1s; animation-duration: 4.2s; background: #ff4081; }
|
||||
.confetti:nth-child(4) { left: 46%; animation-delay: 1.2s; animation-duration: 3.8s; background: #76ff03; }
|
||||
.confetti:nth-child(5) { left: 60%; animation-delay: 1.5s; animation-duration: 4.5s; background: #ff6e40; }
|
||||
.confetti:nth-child(6) { left: 75%; animation-delay: 0.3s; animation-duration: 4.1s; background: #e040fb; }
|
||||
.confetti:nth-child(7) { left: 88%; animation-delay: 0.7s; animation-duration: 3.7s; background: #ffea00; }
|
||||
|
||||
@keyframes fall {
|
||||
0% { transform: translate(0,0) rotate(0deg) scale(0.8); opacity: 1; }
|
||||
50% { transform: translate(20px,150px) rotate(180deg) scale(1.2); opacity: 0.9; }
|
||||
100% { transform: translate(-20px,300px) rotate(360deg) scale(0.8); opacity: 0; }
|
||||
}
|
||||
|
||||
/* 光斑 */
|
||||
.dot {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: rgba(255,255,255,0.7);
|
||||
border-radius: 50%;
|
||||
animation: blink 3s infinite;
|
||||
}
|
||||
|
||||
.dot:nth-child(7) { top: 10%; left: 20%; animation-delay: 0s;}
|
||||
.dot:nth-child(8) { top: 16%; left: 60%; animation-delay: 1s;}
|
||||
.dot:nth-child(9) { top: 13%; left: 80%; animation-delay: 2s;}
|
||||
|
||||
@keyframes blink {
|
||||
0%,100% {opacity: 0.2;}
|
||||
50% {opacity: 1;}
|
||||
}
|
||||
|
||||
/* 烟雾 */
|
||||
.smoke {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.08), transparent 70%);
|
||||
top: 70%;
|
||||
animation: smokeDrift 15s linear infinite;
|
||||
filter: blur(20px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.smoke:nth-child(10) { left: 10%; animation-duration: 18s; width: 250px; height: 250px; }
|
||||
.smoke:nth-child(11) { left: 60%; animation-duration: 20s; width: 300px; height: 300px; }
|
||||
|
||||
@keyframes smokeDrift {
|
||||
0% { transform: translateX(0) translateY(0) scale(0.5);}
|
||||
50% { transform: translateX(100px) translateY(-50vh) scale(1);}
|
||||
100% { transform: translateX(200px) translateY(-100vh) scale(1.5);}
|
||||
}
|
||||
</style>
|
||||
340
pagesb/houseKey/index.vue
Normal file
340
pagesb/houseKey/index.vue
Normal file
@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar class="navBar"></navBar>
|
||||
<view class="wrapbox">
|
||||
<view class="container-form" v-for="(item,index) in state.dataList" :key="item.authorizeId || index">
|
||||
<view class="cHeader">
|
||||
</view>
|
||||
<view class="content">
|
||||
<uv-form labelWidth="auto">
|
||||
<uv-form-item :label="$t('houseKey.FriendsName')" prop="formData.name">
|
||||
<uv-input shape="circle" border="none" placeholder="* * *" v-model="item.userName">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('houseKey.AuthorizationDate')" prop="formData.date" @click="showCalendar(item)">
|
||||
<uv-input shape="circle" border="none" :placeholder="$t('houseKey.date')" readonly v-model="item.endDate" >
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<view class="past-date" v-if="getIsPast(item)">{{ $t('houseKey.overdue') }}</view>
|
||||
<uv-form-item :label="$t('houseKey.ReceiveNotifications')" prop="formData.isNotice">
|
||||
<uv-input shape="circle" disabled disabledColor="#ffffff" :placeholder="$t('houseKey.getNote')" border="none">
|
||||
</uv-input>
|
||||
<template v-slot:right>
|
||||
<uv-switch v-model="item.isNotice" size="20"></uv-switch>
|
||||
</template>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('houseKey.PhoneNumber')" prop="formData.phone">
|
||||
<uv-input shape="circle" border="none" :placeholder="$t('houseKey.otherPhone')" v-model="item.phone">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('houseKey.email')" prop="formData.email">
|
||||
<uv-input shape="circle" border="none" :placeholder="$t('houseKey.otherEmail')" v-model="item.email">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
</uv-form>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<view class="left">
|
||||
<button @click="add(item)" class="btn">{{ $t('common.update') }}</button>
|
||||
<button class="btn" @click="deleteItem(item)">{{ $t('common.delete') }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="container-form">
|
||||
<view class="cHeader">
|
||||
</view>
|
||||
<view class="content">
|
||||
<uv-form labelWidth="auto">
|
||||
<uv-form-item :label="$t('houseKey.FriendsName')" prop="formData.name">
|
||||
<uv-input shape="circle" border="none" placeholder="* * *" v-model="state.formData.userName">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('houseKey.AuthorizationDate')" prop="formData.date" @click="showCalendar()">
|
||||
<uv-input shape="circle" border="none" :placeholder="$t('houseKey.date')" v-model="state.formData.endDate" readonly >
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('houseKey.ReceiveNotifications')" prop="formData.isNotice">
|
||||
<uv-input shape="circle" disabled disabledColor="#ffffff" :placeholder="$t('houseKey.getNote')" border="none">
|
||||
</uv-input>
|
||||
<template v-slot:right>
|
||||
<uv-switch v-model="state.formData.isNotice" size="20"></uv-switch>
|
||||
</template>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('houseKey.PhoneNumber')" prop="formData.phone">
|
||||
<uv-input shape="circle" border="none" :placeholder="$t('houseKey.otherPhone')" v-model="state.formData.phone">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
<uv-form-item :label="$t('houseKey.email')" prop="formData.email">
|
||||
<uv-input shape="circle" border="none" :placeholder="$t('houseKey.otherEmail')" v-model="state.formData.email">
|
||||
</uv-input>
|
||||
</uv-form-item>
|
||||
</uv-form>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<view class="left">
|
||||
<button @click="add(null)" class="btn">{{ $t('houseKey.AddAuthorization') }}</button>
|
||||
<!-- <button class="btn" @click="makePhoneCall">兑换</button> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<uv-calendars ref="calendar" @confirm="confirm"></uv-calendars>
|
||||
</template>
|
||||
<script setup>
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import { useLockApi } from '@/Apis/lock.js';
|
||||
import { ref } from "vue";
|
||||
import { onLoad,onShow } from "@dcloudio/uni-app";
|
||||
import dayjs from "dayjs";
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
// 国际化配置
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
const { themeInfo,storeState } = useMainStore();
|
||||
const getApi = useLockApi();
|
||||
const calendar = ref();
|
||||
const state = ref({
|
||||
inviteShow: false,
|
||||
orderId: null,
|
||||
dataList: [],
|
||||
item:null,
|
||||
formData:{
|
||||
userName:'',
|
||||
endDate:'',
|
||||
phone:'',
|
||||
email:'',
|
||||
isNotice:false
|
||||
}
|
||||
});
|
||||
const showCalendar = (item)=>{
|
||||
state.value.item = item || null;
|
||||
calendar.value.open();
|
||||
}
|
||||
const confirm = (e) => {
|
||||
if(state.value.item){
|
||||
state.value.item.endDate = dayjs(e.fulldate).format("YYYY/MM/DD");
|
||||
}else{
|
||||
state.value.formData.endDate = dayjs(e.fulldate).format("YYYY/MM/DD")
|
||||
}
|
||||
};
|
||||
const getIsPast = (item) => {
|
||||
return dayjs(item.authorDate).isBefore(dayjs().format("YYYY/MM/DD"));
|
||||
};
|
||||
onLoad((params) => {
|
||||
state.value.orderId = params.Order_ID;
|
||||
if(!state.value.orderId){
|
||||
uni.showToast({
|
||||
title: "参数错误(Params error)",
|
||||
icon: "none",
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}else{
|
||||
getDataList();
|
||||
}
|
||||
});
|
||||
onShow(() => {});
|
||||
const getDataList = () => {
|
||||
getApi.GetOrderAuthorizeList({orderId:state.value.orderId}).then(res => {
|
||||
if (res.code == 200) {
|
||||
state.value.dataList = res.data;
|
||||
state.value.dataList.forEach(item => item.authorDate = item.endDate);
|
||||
}
|
||||
})
|
||||
}
|
||||
const deleteItem = (item) => {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确定删除吗(Delete It)?",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
getApi.DeleteOrderAuthorize({authorizeId:item.authorizeId}).then(res => {
|
||||
if (res.code == 200) {
|
||||
uni.showToast({
|
||||
title: "删除成功(Delete Successful)",
|
||||
icon: "none",
|
||||
});
|
||||
getDataList();
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
const add = (item) => {
|
||||
let formData = state.value.formData;
|
||||
if(item){
|
||||
formData = item;
|
||||
}
|
||||
formData.orderId = state.value.orderId;
|
||||
if(!formData.userName){
|
||||
uni.showToast({
|
||||
title: t('houseKey.EnterFriendsName'),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if(!formData.endDate){
|
||||
uni.showToast({
|
||||
title: t('houseKey.EnterAuthorizationDate'),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if(!formData.phone){
|
||||
//#ifdef MP-WEIXIN
|
||||
uni.showToast({
|
||||
title: t('houseKey.EnterPhoneNumber'),
|
||||
icon: "none",
|
||||
});
|
||||
//#endif
|
||||
//#ifdef H5
|
||||
uni.showToast({
|
||||
title: t('houseKey.EnterEmail'),
|
||||
icon: "none",
|
||||
});
|
||||
//#endif
|
||||
return;
|
||||
}
|
||||
if(formData.phone == storeState.userInfo.phone){
|
||||
uni.showToast({
|
||||
title: t('houseKey.CannotAuthorizeYourself'),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if(formData.authorizeId){
|
||||
formData.id = formData.authorizeId;
|
||||
getApi.UpdateOrderAuthorizeCustomer(formData).then(res => {
|
||||
if (res.code == 200) {
|
||||
uni.showToast({
|
||||
title: t('houseKey.UpdateSuccessful'),
|
||||
icon: "none",
|
||||
});
|
||||
getDataList();
|
||||
}
|
||||
})
|
||||
}else{
|
||||
getApi.OrderAuthorizeCustomer(formData).then(res => {
|
||||
if (res.code == 200) {
|
||||
state.value.formData = {
|
||||
userName:'',
|
||||
endDate:'',
|
||||
phone:'',
|
||||
email:'',
|
||||
isNotice:false
|
||||
}
|
||||
uni.showToast({
|
||||
title: t('houseKey.AddedSuccessfully'),
|
||||
icon: "none",
|
||||
});
|
||||
getDataList();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
// onShareAppMessage((res) => {
|
||||
// if (res.from === "button") {
|
||||
// }
|
||||
// return shareParam;
|
||||
// });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
uni-page-body {
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgb(1, 169, 188), rgb(10, 132, 184));
|
||||
overflow: auto;
|
||||
}
|
||||
.navBar {
|
||||
position: relative;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
background: linear-gradient(0deg, var(--left-linear), var(--right-linear));
|
||||
.wrapbox {
|
||||
padding: 20rpx;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
.container-form {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #242a37;
|
||||
border-radius: 18rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
font-size: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
.content {
|
||||
padding: 0 20rpx;
|
||||
padding-top: 20rpx;
|
||||
width: 100%;
|
||||
:deep(.uv-form-item){
|
||||
background-color: #FFF;
|
||||
border-radius: 99px;
|
||||
padding:0 8px;
|
||||
margin-bottom: 20rpx;
|
||||
.uv-form-item__body{
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
}
|
||||
.past-date {
|
||||
margin: 5px 0 8px;
|
||||
color: #ff1d0c;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 10rpx 0;
|
||||
padding-top: 20rpx;
|
||||
font-weight: bold;
|
||||
.left {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 22rpx;
|
||||
padding: 0 20rpx;
|
||||
width: 100%;
|
||||
.btn {
|
||||
width: 49%;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
padding: 0;
|
||||
font-size: 32rpx;
|
||||
border-radius: 999px;
|
||||
border: 1rpx solid transparent;
|
||||
&:nth-child(2) {
|
||||
background-color: #ff1d0c;
|
||||
color: #FFF;
|
||||
}
|
||||
&:nth-child(1) {
|
||||
background-color: var(--main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
728
pagesb/initLock/index.vue
Normal file
728
pagesb/initLock/index.vue
Normal file
@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<view class="init-lock-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<NavBar></NavBar>
|
||||
|
||||
<view class="search" @click="showScanPopup()">
|
||||
<uv-icon name="search" size="20" color="#c0bdc0"></uv-icon>
|
||||
</view>
|
||||
<view class="lock-info" v-if="state.lockInfo.lockData">
|
||||
<view class="name">{{ state.lockInfo.deviceName || state.lockInfo.MAC }}</view>
|
||||
<view class="lock-wrap">
|
||||
<uv-loading-icon mode="spinner" size="40" :color="themeInfo.activeColor" v-if="state.initLoading"></uv-loading-icon>
|
||||
<uv-icon name="lock-fill" size="70" :color="themeInfo.activeColor" v-else></uv-icon>
|
||||
</view>
|
||||
<view class="tip">该智能锁已初始化成功</view>
|
||||
<view class="setting-wrap">
|
||||
<view class="wifi" @click="openNetworkPopup">
|
||||
<uv-icon name="setting" size="20" :color="themeInfo.activeColor"></uv-icon>
|
||||
<view class="text">WiFi</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-lock" v-else>
|
||||
<view class="empty" @click="showScanPopup(true)">
|
||||
<uv-icon name="plus" size="36" color="#c0bdc0"></uv-icon>
|
||||
</view>
|
||||
<view class="text">添加锁时,手机必须在锁旁边</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索智能锁弹框 -->
|
||||
<uv-popup ref="scanPopup" class="scan-popup" mode="bottom" closeable :close-on-click-overlay="false">
|
||||
<view class="scan-wrap">
|
||||
<template v-if="state.initLockList?.length">
|
||||
<view class="name">成功初始化的智能锁列表</view>
|
||||
<view class="item-wrap" @click="selectLock(item)">
|
||||
<div class="item active" v-for="item in state.initLockList" :key="item.lockId">
|
||||
<text>{{ item.deviceName || item.MAC }}</text>
|
||||
<uv-icon v-if="item.lockId == state.lockInfo.lockId" name="checkmark-circle" size="14" :color="themeInfo.activeColor"></uv-icon>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
<view class="btn-wrap">
|
||||
<view class="btn" @click="startScan">开始扫描</view>
|
||||
<view class="btn">停止扫描</view>
|
||||
</view>
|
||||
<view class="loading-wrap" v-if="state.searchLoading">
|
||||
<uv-loading-icon mode="spinner" vertical text="扫描附近的智能锁设备中"></uv-loading-icon>
|
||||
</view>
|
||||
<template v-else-if="state.searchList?.length">
|
||||
<view class="name">扫描的智能锁列表</view>
|
||||
<view class="item-wrap">
|
||||
<div class="item" v-for="item in state.searchList" :key="item.deviceId" @click="handleSetName(item)" :class="{ 'active': item.isSettingMode }">
|
||||
<text>{{ item.deviceName || item.MAC }}</text>
|
||||
<uv-icon v-if="item.isSettingMode" name="plus-circle" size="14" :color="themeInfo.activeColor"></uv-icon>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
<view class="empty-wrap" v-else>
|
||||
<uv-icon name="empty-data" size="26" color="#b3b4b5"></uv-icon>
|
||||
<text class="text">智能锁为空~</text>
|
||||
</view>
|
||||
</view>
|
||||
</uv-popup>
|
||||
|
||||
<!-- 修改名称 -->
|
||||
<uv-modal
|
||||
ref="nameModal"
|
||||
title="修改名称"
|
||||
showCancelButton
|
||||
:closeOnClickOverlay="false"
|
||||
confirmText="确认初始化"
|
||||
:confirmColor="themeInfo.activeColor"
|
||||
@confirm="handleInitLock">
|
||||
<uv-input v-model="state.lockAlias" placeholder="请输入名称" border="surround"></uv-input>
|
||||
</uv-modal>
|
||||
|
||||
<!-- 配置网络 -->
|
||||
<uv-popup ref="networkPopup" mode="bottom" :close-on-click-overlay="false">
|
||||
<view class="network-wrap">
|
||||
<view class="nav-wrap">
|
||||
<uv-icon name="arrow-leftward" :color="themeInfo.iconColor" @click="closeNetwork"></uv-icon>
|
||||
<view class="name">配置网络</view>
|
||||
</view>
|
||||
<view class="tip">不支持 5G WiFi 网络,请选择 2.4G WiFi 网络进行配置</view>
|
||||
<view class="input-wrap" @click="openWifiPopup">
|
||||
<view class="label">WiFi 名称</view>
|
||||
<view class="name">{{ wifiData.selectWifi.SSID }}</view>
|
||||
<uv-icon name="arrow-right"></uv-icon>
|
||||
</view>
|
||||
<view class="input-wrap">
|
||||
<view class="label">WiFi 密码</view>
|
||||
<uv-input v-model="wifiData.password" placeholder="请输入WiFi密码" border="none"></uv-input>
|
||||
</view>
|
||||
<view class="btn" @click="handleConfigWifi">确定</view>
|
||||
</view>
|
||||
</uv-popup>
|
||||
|
||||
<uv-popup ref="wifiPopup" mode="bottom" :close-on-click-overlay="false">
|
||||
<view class="wifi-wrap">
|
||||
<view class="nav-wrap">
|
||||
<text style="z-index: 9;" @click="closeWifiPopup">取消</text>
|
||||
<view class="name">选择网络</view>
|
||||
<text style="z-index: 9;" @click="handleSearchWifi">搜索</text>
|
||||
</view>
|
||||
<view class="loading-wrap" v-if="wifiData.isLoading">
|
||||
<uv-loading-icon mode="spinner" vertical text="正在扫描智能锁附近可用wifi列表"></uv-loading-icon>
|
||||
</view>
|
||||
<template v-if="wifiData.list?.length">
|
||||
<view class="wifi-item" v-for="(item, index) in wifiData.list" :key="index" @click="selectWifi(item)">
|
||||
{{ item.SSID }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</uv-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import CryptoJs from "crypto-js";
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
import { useLockApi } from "/Apis/lock.js";
|
||||
import { ttLockRequest, ttLockRequest2 } from "./lockInitApi.js";
|
||||
|
||||
import NavBar from "@/components/navBar.vue";
|
||||
|
||||
const lockApi = useLockApi();
|
||||
|
||||
const scanPopup = ref();
|
||||
const nameModal = ref();
|
||||
const wifiPopup = ref();
|
||||
const networkPopup = ref();
|
||||
|
||||
const state = reactive({
|
||||
searchLoading: false, // 正在搜索智能锁设备中
|
||||
initLoading: false, // 正在初始化智能锁中
|
||||
lockInfo: {}, // 当前的智能锁
|
||||
initInfo: {}, // 选择初始化的智能锁
|
||||
initLockList: [],
|
||||
searchList: [],
|
||||
ttToken: "",
|
||||
lockAlias: ""
|
||||
});
|
||||
|
||||
const getLockList = async () => {
|
||||
const openId = uni.getStorageSync("openId");
|
||||
const { code, data } = await lockApi.GetInitLockList({ openId });
|
||||
if (code == 200) {
|
||||
state.initLockList = data;
|
||||
if (state.initLockList?.length) {
|
||||
state.lockInfo = state.initLockList[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectLock = (item) => {
|
||||
state.lockInfo = item;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLockList();
|
||||
});
|
||||
|
||||
const showScanPopup = (check) => {
|
||||
if (check && state.lockInfo.lockData) return;
|
||||
scanPopup.value.open();
|
||||
startScan();
|
||||
}
|
||||
|
||||
const checkBlueTooth = () => {
|
||||
return new Promise(resolve => {
|
||||
uni.openBluetoothAdapter({
|
||||
success() {
|
||||
resolve(true);
|
||||
},
|
||||
fail(err) {
|
||||
console.log(err);
|
||||
uni.showToast({
|
||||
title: `蓝牙初始化失败:${err.errCode === 10001 ? "请打开蓝牙" : err.errMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 开始扫描附近的智能锁设备
|
||||
const startScan = async () => {
|
||||
const openBlue = await checkBlueTooth();
|
||||
if (!openBlue || state.searchLoading) return;
|
||||
state.searchLoading = true;
|
||||
|
||||
requirePlugin("ttPlugin", ({ startScanBleDevice }) => {
|
||||
// 开启蓝牙设备扫描
|
||||
startScanBleDevice((lockDevice, lockList) => {
|
||||
// TODO 成功扫描到设备
|
||||
state.searchList = lockList;
|
||||
state.searchLoading = false;
|
||||
stopScan();
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
uni.showToast({
|
||||
title: `蓝牙扫描智能锁设备失败:${err.errorMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
state.searchLoading = false;
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭蓝牙设备扫描
|
||||
const stopScan = () => {
|
||||
requirePlugin("ttPlugin", ({ stopScanBleDevice }) => {
|
||||
stopScanBleDevice().then(res => {
|
||||
state.searchLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 获取通通登录后的用户 token
|
||||
const getLoginToken = () => {
|
||||
return new Promise(resolve => {
|
||||
if (state.ttToken) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
ttLockRequest("/oauth2/token", "POST", {
|
||||
"client_id": "7946f0d923934a61baefb3303de4d132",
|
||||
"client_secret": "56d9721abbc3d22a58452c24131a5554",
|
||||
"grant_type": "password",
|
||||
"redirect_uri": "http://www.sciener.cn",
|
||||
"username": "+8617727694079",
|
||||
"password": CryptoJs.MD5(String("lqx123456")).toString()
|
||||
}).then(res => {
|
||||
state.ttToken = res.access_token;
|
||||
if (state.ttToken) {
|
||||
uni.setStorageSync("tt_access_token", state.ttToken);
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const handleSetName = (deviceFromScan) => {
|
||||
if (!deviceFromScan.isSettingMode) {
|
||||
uni.showToast({
|
||||
title: `智能锁${deviceFromScan.deviceName || deviceFromScan.MAC}已被初始化,当前不可添加`,
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
state.initInfo = deviceFromScan;
|
||||
nameModal.value.open();
|
||||
}
|
||||
|
||||
// 初始化智能锁
|
||||
const handleInitLock = () => {
|
||||
nameModal.value.close();
|
||||
let deviceFromScan = state.initInfo;
|
||||
if (state.initLoading) return;
|
||||
state.initLoading = true;
|
||||
uni.showLoading({
|
||||
title: '正在初始化智能锁'
|
||||
});
|
||||
requirePlugin("ttPlugin", ({ getLockVersion, initLock }) => {
|
||||
// 更新智能锁版本信息
|
||||
getLockVersion({ deviceFromScan }).then(res => {
|
||||
if (res.errorCode == 0) {
|
||||
initLock({ deviceFromScan }).then(async result => {
|
||||
uni.hideLoading();
|
||||
state.initLoading = false;
|
||||
if (result.errorCode == 0) {
|
||||
console.log('----智能锁数据----', result);
|
||||
const res = await getLoginToken();
|
||||
if (!res) {
|
||||
uni.showToast({
|
||||
title: "获取tt用户token失败,正在重置智能锁",
|
||||
icon: 'none'
|
||||
});
|
||||
handleResetLock(result.lockData);
|
||||
return;
|
||||
}
|
||||
ttLockRequest2("/v3/lock/initialize", "POST", {
|
||||
"lockData": result.lockData,
|
||||
"lockAlias": state.lockAlias
|
||||
}).then(res => {
|
||||
console.log('------tt用户同步数据------', res);
|
||||
if (res.lockId) {
|
||||
uni.showToast({
|
||||
title: "智能锁已成功添加",
|
||||
icon: 'none'
|
||||
});
|
||||
state.lockInfo = {
|
||||
lockId: String(res.lockId),
|
||||
lockData: result.lockData,
|
||||
deviceId: state.initInfo.deviceId,
|
||||
deviceName: state.lockAlias || state.initInfo.deviceName,
|
||||
}
|
||||
saveLockData();
|
||||
openNetworkPopup();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "智能锁数据上传失败, 正在重置智能锁",
|
||||
icon: 'none'
|
||||
});
|
||||
handleResetLock(result.lockData);
|
||||
}
|
||||
});
|
||||
scanPopup.value.close();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `初始化智能锁失败:${result.errorMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
state.initLoading = false;
|
||||
uni.showToast({
|
||||
title: `更新智能锁版本信息失败:${res.errorMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 锁的数据绑定到小程序账户
|
||||
const saveLockData = () => {
|
||||
if (!state.lockInfo.lockData) return;
|
||||
const openId = uni.getStorageSync("openId");
|
||||
lockApi.SaveInitLock({
|
||||
openId,
|
||||
...state.lockInfo
|
||||
});
|
||||
}
|
||||
|
||||
// 重置智能锁
|
||||
const handleResetLock = (lockData) => {
|
||||
requirePlugin("ttPlugin", ({ resetLock }) => {
|
||||
resetLock({ lockData }).then(res => {
|
||||
if (res.errorCode == 0) {
|
||||
uni.showToast({
|
||||
title: '智能锁重置成功',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `智能锁重置失败,请长按重置键进行设备重置:${res.errorMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const wifiData = reactive({
|
||||
password: "",
|
||||
isLoading: false,
|
||||
list: [],
|
||||
selectWifi: {},
|
||||
});
|
||||
|
||||
// 打开网络配置
|
||||
const openNetworkPopup = () => {
|
||||
wifiData.password = "";
|
||||
networkPopup.value.open();
|
||||
}
|
||||
|
||||
// 关闭网络配置
|
||||
const closeNetwork = () => {
|
||||
networkPopup.value.close();
|
||||
}
|
||||
|
||||
const openWifiPopup = () => {
|
||||
wifiPopup.value.open();
|
||||
if (!wifiData.list?.length) handleSearchWifi();
|
||||
}
|
||||
|
||||
const closeWifiPopup = () => {
|
||||
wifiPopup.value.close();
|
||||
}
|
||||
|
||||
const handleSearchWifi = () => {
|
||||
if (wifiData.isLoading) return;
|
||||
wifiData.isLoading = true;
|
||||
wifiData.list = [];
|
||||
requirePlugin("ttPlugin", ({ scanWifi }) => {
|
||||
scanWifi({ lockData: state.lockInfo.lockData }).then(res => {
|
||||
wifiData.isLoading = false;
|
||||
if (res.errorCode == 0) {
|
||||
wifiData.list = res.data.wifiList;
|
||||
uni.showToast({
|
||||
title: "扫描智能锁附近可用wifi列表成功",
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `扫描智能锁附近可用wifi列表失败:${res.errorMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const selectWifi = (item) => {
|
||||
wifiData.selectWifi = item;
|
||||
closeWifiPopup();
|
||||
}
|
||||
|
||||
// 获取锁对应绑定的 wifi 信息
|
||||
// const getLockWifi = () => {
|
||||
// ttLockRequest2("/v3/wifiLock/detail", "POST", {
|
||||
// "lockId": state.lockInfo.lockId,
|
||||
// }).then(res => {
|
||||
// wifiData.wifiInfo = res;
|
||||
// });
|
||||
// }
|
||||
|
||||
// 配置锁的 wifi
|
||||
const handleConfigWifi = () => {
|
||||
if (!wifiData.password) {
|
||||
uni.showToast({
|
||||
title: "请输入wifi密码",
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: "正在配置wifi信息" });
|
||||
requirePlugin("ttPlugin", ({ configWifi, configServer }) => {
|
||||
configWifi({
|
||||
config: {
|
||||
SSID: wifiData.selectWifi.SSID,
|
||||
password: wifiData.password
|
||||
},
|
||||
lockData: state.lockInfo.lockData
|
||||
}).then(res => {
|
||||
if (res.errorCode == 0) {
|
||||
// 配置服务器信息
|
||||
configServer({
|
||||
config: {
|
||||
server: "cnwifilock.ttlock.com",
|
||||
port: 4999,
|
||||
},
|
||||
lockData: state.lockInfo.lockData
|
||||
}).then(async res => {
|
||||
if (res.errorCode == 0) {
|
||||
const res = await getLoginToken();
|
||||
if (!res) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "获取tt用户token失败",
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 服务器配置成功
|
||||
ttLockRequest2("/v3/wifiLock/updateNetwork", "POST", {
|
||||
lockId: state.lockInfo.lockId, // 智能锁ID
|
||||
networkName: wifiData.selectWifi.SSID, // 连接的网络名称
|
||||
rssi: wifiData.selectWifi.rssi, // Wifi信号强度
|
||||
useStaticIp: false, // 是否使用静态IP
|
||||
}).then(result => {
|
||||
console.log('---wifi上传到服务器的配置---', result);
|
||||
uni.hideLoading();
|
||||
if (result.errcode === 0) {
|
||||
// getLockWifi();
|
||||
uni.showToast({
|
||||
title: "Wifi参数已上传服务器",
|
||||
icon: 'none'
|
||||
});
|
||||
closeNetwork();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "Wifi参数上传服务器失败",
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
uni.hideLoading();
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `配置服务器信息失败:${res.errorMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: `配置wifi信息失败:${res.errorMsg}`,
|
||||
icon: 'none'
|
||||
});
|
||||
uni.hideLoading();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.init-lock-wrap {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background: #FFFFFF;
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.lock-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
margin-bottom: 80rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.lock-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 6px 10px rgba($color: #000000, $alpha: 0.2);
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.setting-wrap {
|
||||
width: 100%;
|
||||
margin-top: 60rpx;
|
||||
padding-top: 60rpx;
|
||||
border-top: 1px solid #f3f3ed;
|
||||
|
||||
.wifi {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 25%;
|
||||
|
||||
.text {
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-lock {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 160rpx;
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 6px 10px rgba($color: #000000, $alpha: 0.2);
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: 140rpx;
|
||||
color: #656265;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.scan-wrap {
|
||||
padding: 60rpx 20rpx 0;
|
||||
height: calc(100vh - 400rpx);
|
||||
|
||||
.btn-wrap {
|
||||
display: flex;
|
||||
margin: 30rpx 0;
|
||||
|
||||
.btn {
|
||||
padding: 20rpx;
|
||||
margin-right: 30rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: var(--text-color);
|
||||
background: var(--active-color);
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 20rpx 0;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-wrap {
|
||||
.item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 40px;
|
||||
padding: 0 32rpx;
|
||||
color: #333333;
|
||||
background: #F2F4F8;
|
||||
border-bottom: 1px solid #E8E8E8;
|
||||
|
||||
&.active {
|
||||
background: #F9FBD8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 200rpx;
|
||||
|
||||
.text {
|
||||
margin-top: 10rpx;
|
||||
text-align: center;
|
||||
color: #b3b4b5;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
margin-top: 200rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.network-wrap {
|
||||
height: calc(100vh - 400rpx);
|
||||
background: #F6F4F7;
|
||||
|
||||
.nav-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx;
|
||||
background: var(--main-color);
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
padding: 20rpx 0 20rpx 20rpx;
|
||||
font-size: 28rpx;
|
||||
background: #F2F6Fc;
|
||||
}
|
||||
|
||||
.input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 20rpx;
|
||||
font-size: 28rpx;
|
||||
background: #FFFFFF;
|
||||
border-bottom: 1px solid #F2F6Fc;
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
:deep(input) {
|
||||
text-align: right !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 40rpx 80rpx 0;
|
||||
padding: 20rpx 0;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
background: var(--main-color);
|
||||
}
|
||||
}
|
||||
|
||||
.wifi-wrap {
|
||||
height: 600rpx;
|
||||
overflow: auto;
|
||||
background: #FFFFFF;
|
||||
|
||||
.nav-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1px solid #F2F6Fc;
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
margin-top: 120rpx;
|
||||
}
|
||||
|
||||
.wifi-item {
|
||||
padding: 30rpx 0 30rpx 40rpx;
|
||||
border-bottom: 1px solid #F2F6Fc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
158
pagesb/initLock/lockInitApi.js
Normal file
158
pagesb/initLock/lockInitApi.js
Normal file
@ -0,0 +1,158 @@
|
||||
const getResponseData = (response) => {
|
||||
switch (response.type) {
|
||||
case 0:
|
||||
return response["data"]; // 操作成功
|
||||
default: {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: response.errorMsg,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const ttLockRequest = (url, method, params) => {
|
||||
return new Promise(resolve => {
|
||||
uni.request({
|
||||
url: `https://cnapi.ttlock.com${url}`,
|
||||
header: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: params,
|
||||
method: method,
|
||||
success: (response) => {
|
||||
let result = null;
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
if (!!response.data && typeof response.data["errcode"] === "undefined") {
|
||||
result = getResponseData({
|
||||
data: response.data,
|
||||
errorCode: -2,
|
||||
errorMsg: "操作成功",
|
||||
type: 0,
|
||||
});
|
||||
} else {
|
||||
let errMsg = response.data["errcode"] === 0 ? "操作成功" : response.data["errmsg"];
|
||||
result = getResponseData({
|
||||
data: response.data,
|
||||
errorCode: -2,
|
||||
errorMsg: errMsg,
|
||||
type: 0,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = getResponseData({
|
||||
errorCode: -2,
|
||||
errorMsg: `服务器请求失败,状态码:${response.statusCode}`,
|
||||
type: 2,
|
||||
});
|
||||
break;
|
||||
}
|
||||
resolve(result);
|
||||
},
|
||||
fail: (err) => {
|
||||
let result = getResponseData({
|
||||
errorCode: -1,
|
||||
errorMsg: "服务器请求失败,请检查服务器域名是否已被列入白名单",
|
||||
type: 3,
|
||||
});
|
||||
resolve(result);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const ttLockRequest2 = (url, method, params) => {
|
||||
return new Promise(resolve => {
|
||||
uni.request({
|
||||
url: `https://api.ttlock.com${url}`,
|
||||
header: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
data: _makeParams(params),
|
||||
method: method,
|
||||
dataType: "json",
|
||||
success: (response) => {
|
||||
let result = null;
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
if (!!response.data && typeof response.data["errcode"] === "undefined") {
|
||||
result = getResponseData({
|
||||
data: response.data,
|
||||
errorCode: -2,
|
||||
errorMsg: "操作成功",
|
||||
type: 0,
|
||||
});
|
||||
} else {
|
||||
let errMsg = response.data["errcode"] === 0 ? "操作成功" : response.data["errmsg"];
|
||||
result = getResponseData({
|
||||
data: response.data,
|
||||
errorCode: -2,
|
||||
errorMsg: errMsg,
|
||||
type: 0,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = getResponseData({
|
||||
errorCode: -2,
|
||||
errorMsg: `服务器请求失败,状态码:${response.statusCode}`,
|
||||
type: 2,
|
||||
});
|
||||
break;
|
||||
}
|
||||
resolve(result);
|
||||
},
|
||||
fail: (err) => {
|
||||
let result = getResponseData({
|
||||
errorCode: -1,
|
||||
errorMsg: "服务器请求失败,请检查服务器域名是否已被列入白名单",
|
||||
type: 3,
|
||||
});
|
||||
resolve(result);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function _generateParams(params) {
|
||||
if (!params) return {};
|
||||
for (let key of Object.keys(params)) {
|
||||
if (params[key] === null) {
|
||||
params[key] = undefined;
|
||||
continue;
|
||||
}
|
||||
const type = typeof params[key];
|
||||
switch (type) {
|
||||
case "function":
|
||||
{
|
||||
params[key] = undefined;
|
||||
}
|
||||
break;
|
||||
case "object":
|
||||
{
|
||||
params[key] = JSON.stringify(params[key]);
|
||||
}
|
||||
break;
|
||||
case "number":
|
||||
case "string":
|
||||
case "boolean":
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return JSON.parse(JSON.stringify(params));
|
||||
}
|
||||
|
||||
function _makeParams(params) {
|
||||
return JSON.parse(
|
||||
JSON.stringify({
|
||||
..._generateParams(params),
|
||||
clientId: "7946f0d923934a61baefb3303de4d132",
|
||||
accessToken: uni.getStorageSync("tt_access_token"),
|
||||
date: Date.now(),
|
||||
})
|
||||
);
|
||||
}
|
||||
394
pagesb/invitation/index.vue
Normal file
394
pagesb/invitation/index.vue
Normal file
@ -0,0 +1,394 @@
|
||||
<template>
|
||||
<view class="invitation-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
<!-- <canvas type="2d" id="posterCanvas" :style="{ width: `${state.canvasWidth}px`, height: `${state.canvasHeight}px` }"></canvas> -->
|
||||
<view class="QrCode" :style="{ height: `${state.canvasHeight}px` }">
|
||||
<image class="qrcodeImg" :src="state.qrCodeBase64"></image>
|
||||
</view>
|
||||
<view class="bottom-wrap">
|
||||
<view class="btn-wrap" @click="saveImageToAlbum(state.qrCodeBase64)">
|
||||
<view class="icon-wrap">
|
||||
<uv-icon name="download" size="30" color="#1B493E"></uv-icon>
|
||||
</view>
|
||||
<view class="text">{{ $t("referrerInfo.loadQrCode") }}</view>
|
||||
</view>
|
||||
<!-- <view class="btn-wrap" @click="saveImageToAlbum(state.posterFilePath)">
|
||||
<view class="icon-wrap">
|
||||
<uv-icon name="photo" size="30" color="#1B493E"></uv-icon>
|
||||
</view>
|
||||
<view class="text">{{ $t("referrerInfo.loadPoster") }}</view>
|
||||
</view> -->
|
||||
<view class="btn-wrap">
|
||||
<view class="icon-wrap">
|
||||
<uv-icon name="weixin-fill" size="30" color="#1B493E"></uv-icon>
|
||||
</view>
|
||||
<button open-type="share" class="share-btn"></button>
|
||||
<view class="text">{{ $t("referrerInfo.forwardInvitation") }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<canvas type="2d" id="qrCodeCanvas" class="qrCode-hide" style="width: 300px; height: 300px;"></canvas>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick } from 'vue';
|
||||
import { onLoad, onShareAppMessage } from '@dcloudio/uni-app';
|
||||
|
||||
import { navbarHeightAndStatusBarHeight } from '@/utils/common.js';
|
||||
|
||||
import navBar from '@/components/navBar.vue';
|
||||
|
||||
// 主题色配置
|
||||
import { useMainStore } from '@/store/index.js';
|
||||
const { themeInfo, storeState } = useMainStore();
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
import { getClientCustomerApi } from '@/Apis/clientCustomer.js';
|
||||
const clientCustomerApi = getClientCustomerApi();
|
||||
|
||||
const state = reactive({
|
||||
qrCodeBase64: '',
|
||||
qrCodeUrl: '',
|
||||
posterFilePath: '',
|
||||
qrCodeFilePath: '',
|
||||
canvasWidth: 750,
|
||||
canvasHeight: 800
|
||||
});
|
||||
|
||||
const getQrCodeBase64 = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
clientCustomerApi.GetMediatorQrCode().then((res) => {
|
||||
if (res.code == 200) {
|
||||
state.qrCodeBase64 = res.data;
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
const getPosterImage = async () => {
|
||||
uni.showLoading({ title: t('toast.generating'), mask: true });
|
||||
|
||||
try {
|
||||
const canvas = await drawCanvas();
|
||||
state.posterFilePath = await canvasToTempFile(canvas);
|
||||
} catch (error) {
|
||||
console.error('生成失败:', error);
|
||||
uni.showToast({ title: t('toast.generateFail'), icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化Canvas
|
||||
const initCanvas = (canvasId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
.select(`#${canvasId}`)
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
if (res && res[0]) {
|
||||
const canvas = res[0].node;
|
||||
canvas.width = res[0].width;
|
||||
canvas.height = res[0].height;
|
||||
resolve(canvas);
|
||||
} else {
|
||||
reject(new Error('Canvas初始化失败'));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const drawCanvas = () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
await nextTick();
|
||||
const canvas = await initCanvas('posterCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const dpr = uni.getWindowInfo().pixelRatio || 1;
|
||||
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
const displayWidth = width * dpr;;
|
||||
const displayHeight = height * dpr;
|
||||
|
||||
canvas.width = displayWidth;
|
||||
canvas.height = displayHeight;
|
||||
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
let text = '';
|
||||
let textMetrics = 0;
|
||||
let imgHeight = 0;
|
||||
|
||||
ctx.fillStyle = '#1B493E';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.font = 'bold 36px PingFang SC';
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
text = '青春公寓 · 学生专享';
|
||||
textMetrics = ctx.measureText(text);
|
||||
x = (width - textMetrics.width) / 2;
|
||||
y = 70;
|
||||
ctx.fillText(text, x, y);
|
||||
|
||||
ctx.font = '14px PingFang SC';
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
text = '智能门禁 | 近校选址 | 极速网络';
|
||||
textMetrics = ctx.measureText(text);
|
||||
x = (width - textMetrics.width) / 2;
|
||||
y += 46;
|
||||
ctx.fillText(text, x, y);
|
||||
|
||||
const drawImage = (ctx) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = canvas.createImage();
|
||||
img.src = 'https://elitesysapartment.oss-cn-hongkong.aliyuncs.com/2025/0814/posterRoomImg.jpg';
|
||||
img.onload = () => {
|
||||
x = 20;
|
||||
y += 50;
|
||||
imgHeight = height - y - 150;
|
||||
ctx.drawImage(img, x, y, width - x * 2, imgHeight);
|
||||
resolve(true);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const drawPriceCards = (ctx) => {
|
||||
const cardData = [
|
||||
{ title: '单人房', price: '$ 17,625/月起' },
|
||||
{ title: '双人房', price: '$ 8,378.95/月起' },
|
||||
{ title: '三人房', price: '$ 5375/月起' }
|
||||
];
|
||||
|
||||
let startY = y;
|
||||
let startX = x + (width - x * 2) / 2;
|
||||
const cardWidth = (width - x * 2) / 2;
|
||||
const cardGap = 20;
|
||||
const cardHeight = (imgHeight - cardGap * 2) / 3;
|
||||
|
||||
cardData.forEach((item, index) => {
|
||||
// 绘制卡片背景
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.2)";
|
||||
ctx.fillRect(startX, startY, cardWidth, cardHeight, 10);
|
||||
ctx.fill();
|
||||
|
||||
// 绘制标题
|
||||
ctx.font = "bold 18px PingFang SC";
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.fillText(item.title, startX + 15, startY + 40);
|
||||
|
||||
// 绘制价格
|
||||
ctx.font = "16px PingFang SC";
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.fillText(item.price, startX + 15, startY + 70);
|
||||
|
||||
startY += cardHeight + cardGap;
|
||||
});
|
||||
}
|
||||
|
||||
const drawBottom = (ctx) => {
|
||||
return new Promise((resolve) => {
|
||||
y = y + imgHeight + 40;
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.fillRect(0, y, width, height);
|
||||
|
||||
ctx.font = "bold 18px PingFang SC";
|
||||
ctx.fillStyle = "#1B493E";
|
||||
ctx.fillText("ONE PACE", 40, height - 60);
|
||||
ctx.fillText("一步居", 40, height - 30);
|
||||
|
||||
const imgWidth = 80;
|
||||
const imgY = (height - y - imgWidth) / 2;
|
||||
const imgX = (width - 40 - imgWidth);
|
||||
|
||||
const qrCodeImage = canvas.createImage();
|
||||
qrCodeImage.src = state.qrCodeBase64;
|
||||
qrCodeImage.onload = () => {
|
||||
ctx.drawImage(qrCodeImage, imgX, y + imgY, imgWidth, imgWidth);
|
||||
resolve(true);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
await drawImage(ctx);
|
||||
// drawPriceCards(ctx);
|
||||
await drawBottom(ctx);
|
||||
|
||||
resolve(canvas);
|
||||
} catch {
|
||||
reject(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Canvas转临时文件
|
||||
const canvasToTempFile = (canvas) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.canvasToTempFilePath({
|
||||
canvas: canvas,
|
||||
success: res => resolve(res.tempFilePath),
|
||||
fail: (error => {
|
||||
console.log(error);
|
||||
reject(false);
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 保存到相册
|
||||
const saveImageToAlbum = (base64Path) => {
|
||||
// if (!filePath) return;
|
||||
// uni.saveImageToPhotosAlbum({
|
||||
// filePath,
|
||||
// success: () => {
|
||||
// uni.showToast({ title: 'Success', icon: 'none' });
|
||||
// },
|
||||
// fail: (err) => {
|
||||
// console.log(err, '保存图片到相册失败');
|
||||
// }
|
||||
// });
|
||||
const fs = uni.getFileSystemManager();
|
||||
const filePath = `${wx.env.USER_DATA_PATH}/temp.png`;
|
||||
|
||||
fs.writeFile({
|
||||
filePath,
|
||||
data: base64Path.replace(/^data:image\/\w+;base64,/, ''),
|
||||
encoding: 'base64',
|
||||
success() {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath,
|
||||
success() {
|
||||
uni.showToast({ title: '保存成功' });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getQrCodeImage = async () => {
|
||||
try {
|
||||
await nextTick();
|
||||
const canvas = await initCanvas('qrCodeCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const dpr = uni.getWindowInfo().pixelRatio || 1;
|
||||
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
const displayWidth = width * dpr;
|
||||
const displayHeight = height * dpr;
|
||||
|
||||
canvas.width = displayWidth;
|
||||
canvas.height = displayHeight;
|
||||
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
const qrCodeImage = canvas.createImage();
|
||||
qrCodeImage.src = state.qrCodeBase64;
|
||||
qrCodeImage.onload = async () => {
|
||||
ctx.drawImage(qrCodeImage, 0, 0, width, height);
|
||||
state.qrCodeFilePath = await canvasToTempFile(canvas);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
// imageUrl: 'https://elitesysapartment.oss-cn-hongkong.aliyuncs.com/miniProgram/2025/0520/banner1.jpg',
|
||||
title: '使用该链接注册登录,绑定专属中介',
|
||||
path: `/pages/index/index?mediatorId=${storeState.userInfo.id}`
|
||||
};
|
||||
});
|
||||
|
||||
onLoad(async () => {
|
||||
// 设置 canvas 的宽高
|
||||
const systemInfo = uni.getWindowInfo();
|
||||
state.canvasWidth = systemInfo.screenWidth;
|
||||
state.canvasHeight = systemInfo.screenHeight - (navbarHeightAndStatusBarHeight()?.navbarHeight || 80) - 100;
|
||||
|
||||
await getQrCodeBase64();
|
||||
// getPosterImage();
|
||||
// getQrCodeImage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/static/style/theme.scss';
|
||||
|
||||
.invitation-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: #F5F5EF;
|
||||
overflow: hidden;
|
||||
|
||||
.QrCode{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.qrcodeImg{
|
||||
width: 500rpx;
|
||||
height: 500rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.qrCode-hide {
|
||||
position: fixed;
|
||||
left: 7500rpx;
|
||||
}
|
||||
|
||||
.bottom-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
border-top: 1px solid #EEEEEE;
|
||||
background: #FFFFFF;
|
||||
// background: rgba(0, 0, 0, 0.3);
|
||||
|
||||
.btn-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.icon-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: #666666;
|
||||
font-size: 28rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
291
pagesb/invoice/index.vue
Normal file
291
pagesb/invoice/index.vue
Normal file
@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<view class="invoice-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
|
||||
<view class="wrapBox">
|
||||
<view class="top">
|
||||
<view class="arrow">
|
||||
<image src="/static/setOrder/selectArrow.png" mode=""></image>
|
||||
</view>
|
||||
{{ $t("invoice.valid") }}
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="orderBox">
|
||||
<uv-checkbox-group v-model="state.checkList">
|
||||
<!-- @click="goInvoiceApply(item)" -->
|
||||
<view
|
||||
class="orderLi"
|
||||
v-for="(item, index) in state.list"
|
||||
:key="index"
|
||||
>
|
||||
<view class="leftCheckBox">
|
||||
<uv-checkbox activeColor="black" :name="item" shape="circle">
|
||||
</uv-checkbox>
|
||||
</view>
|
||||
<view class="left">
|
||||
<view>{{ $t("invoice.pay") }}: {{ item.paymentTime }}</view>
|
||||
<view>{{ $t("invoice.site") }}: {{ item.siteName }}</view>
|
||||
<view>{{ $t("invoice.type") }}: {{ item.unitTypeName }}</view>
|
||||
<view
|
||||
>{{ $t("invoice.rent") }}:
|
||||
{{ item.termOfLease?.match(/\d{4}-\d{2}-\d{2}/g)[0] }} - {{
|
||||
item.termOfLease?.match(/\d{4}-\d{2}-\d{2}/g)[1]
|
||||
}}</view
|
||||
>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view>{{ item.lockerName }}</view>
|
||||
<view>{{ currency }}{{ item.money }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</uv-checkbox-group>
|
||||
</view>
|
||||
<button class="contact" @click="goInvoiceApplyforRecord">
|
||||
<uv-icon
|
||||
name="file"
|
||||
custom-prefix="custom-icon"
|
||||
size="16"
|
||||
:color="themeInfo.theme === 'default' ? '#045459' : '#0F2232'"
|
||||
></uv-icon>
|
||||
<text style="margin-left: 8px">{{ $t("invoice.record") }}</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="bottom">
|
||||
<!-- <text
|
||||
>已选 {{ state.checkList.length }} 个订单,共
|
||||
{{ checkRowMoney }} 元</text
|
||||
> -->
|
||||
<text>{{ $t("invoice.order", { number: state.checkList.length, money: checkRowMoney }) }}</text>
|
||||
<text>{{ $t("invoice.tip") }}</text>
|
||||
</view>
|
||||
<view class="goApply">
|
||||
<view class="checlBox">
|
||||
<uv-checkbox-group @change="selectAll">
|
||||
<uv-checkbox
|
||||
activeColor="black"
|
||||
name="all"
|
||||
shape="circle"
|
||||
:label="$t('invoice.allSelect')"
|
||||
>
|
||||
</uv-checkbox>
|
||||
</uv-checkbox-group>
|
||||
</view>
|
||||
<view class="next">
|
||||
<button size="large" @click="goInvoiceApply">{{ $t("invoice.nextStep") }}</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 主题色配置
|
||||
import { reactive, computed } from "vue";
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import { useInvoiceApi } from "@/Apis/invoice";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { currency } from "@/config/index.js";
|
||||
const { t } = useI18n();
|
||||
|
||||
const getApi = useInvoiceApi();
|
||||
const state = reactive({
|
||||
list: [],
|
||||
checkList: [],
|
||||
});
|
||||
const selectAll = (e) => {
|
||||
if (e.length) {
|
||||
state.checkList = state.list;
|
||||
} else {
|
||||
state.checkList = [];
|
||||
}
|
||||
};
|
||||
const checkRowMoney = computed(() => {
|
||||
return state.checkList.reduce((pre, cur) => {
|
||||
return pre + Number(cur.money);
|
||||
}, 0).toFixed(2);
|
||||
});
|
||||
const goInvoiceApply = (row) => {
|
||||
if(state.checkList.length == 0){
|
||||
uni.showToast({
|
||||
title: t("invoice.selectOrder"),
|
||||
icon: "none",
|
||||
});
|
||||
return
|
||||
}
|
||||
if (checkRowMoney.value <= 0) {
|
||||
uni.showToast({
|
||||
title: t("invoice.validMoney"),
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const ids = state.checkList.map((item) => item.paymentId);
|
||||
uni.navigateTo({
|
||||
url: `/pagesb/invoiceApply/index?paymentId=${ids}&num=${checkRowMoney.value}`,
|
||||
});
|
||||
// 跳转后 清空选择
|
||||
state.checkList = [];
|
||||
};
|
||||
const getDataList = () => {
|
||||
uni.showLoading();
|
||||
getApi.GetCanInvoiceList().then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
state.list = res.data
|
||||
} else {
|
||||
state.list = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
const goInvoiceApplyforRecord = ()=>{
|
||||
uni.navigateTo({
|
||||
url: "/pagesb/invoiceApplyforRecord/index",
|
||||
});
|
||||
}
|
||||
onShow(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/static/style/theme.scss';
|
||||
.invoice-wrap {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--right-linear),
|
||||
var(--left-linear)
|
||||
);
|
||||
.wrapBox {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
.top {
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.arrow {
|
||||
width: 20rpx;
|
||||
display: flex;
|
||||
margin-right: 6rpx;
|
||||
image {
|
||||
width: 20rpx;
|
||||
height: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
padding-bottom: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
.contact {
|
||||
position: fixed;
|
||||
bottom: 300rpx;
|
||||
right: 0;
|
||||
height: 74rpx;
|
||||
background-color: var(--main-color);
|
||||
border-radius: 45rpx 0rpx 0rpx 45rpx;
|
||||
border: 4rpx solid var(--stress-text);
|
||||
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 0 20rpx 0 30rpx;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--stress-text);
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.orderBox {
|
||||
padding: 20rpx;
|
||||
.orderLi {
|
||||
width: 100%;
|
||||
margin-bottom: 30rpx;
|
||||
border-radius: 18rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 24rpx;
|
||||
background-color: var(--main-color);
|
||||
display: flex;
|
||||
.leftCheckBox {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.left {
|
||||
flex: 1;
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 150rpx;
|
||||
:nth-child(1) {
|
||||
border-radius: 99rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
background-color: black;
|
||||
color: var(--main-color);
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
:nth-child(2) {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bottom {
|
||||
height: 100rpx;
|
||||
font-size: 24rpx;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: var(--stress-color);
|
||||
|
||||
text:first-of-type {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
text:last-of-type {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.goApply {
|
||||
height: 130rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10upx 40upx;
|
||||
align-items: center;
|
||||
.next {
|
||||
button {
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 99rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
582
pagesb/invoiceApply/index.vue
Normal file
582
pagesb/invoiceApply/index.vue
Normal file
@ -0,0 +1,582 @@
|
||||
<template>
|
||||
<view
|
||||
class="container"
|
||||
:class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]"
|
||||
>
|
||||
<navBar />
|
||||
<view class="content">
|
||||
<view class="info">
|
||||
<view class="i-header">
|
||||
<view class="tabbox">
|
||||
<view
|
||||
class="li"
|
||||
@click="changeValidType(1)"
|
||||
:class="{ active: state.vaildType == 1 }"
|
||||
>
|
||||
{{ $t("invoiceApply.electronicInvoice") }}
|
||||
</view>
|
||||
<!-- <view
|
||||
class="li"
|
||||
@click="changeValidType(2)"
|
||||
:class="{ active: state.vaildType == 2 }"
|
||||
>
|
||||
{{ $t("invoiceApply.paperInvoice") }}
|
||||
</view> -->
|
||||
<view class="bottom-line" :class="{ right: state.vaildType == 2 }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="infobox">
|
||||
<view class="personal">
|
||||
<view class="select">
|
||||
<view class="label"> * 发票类型 INVOICE TYPE </view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<uv-radio-group size="12" v-model="state.formData.applicationType" @change="changeApplicationType" placement="row">
|
||||
<uv-radio :name="2" label="全电普票"></uv-radio>
|
||||
<uv-radio :name="1" label="全电专票"></uv-radio>
|
||||
</uv-radio-group>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> * 抬头类型 HEADER TYPE </view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<uv-radio-group size="12" v-model="state.formData.titleType" placement="row">
|
||||
<uv-radio :disabled="state.formData.applicationType==1" :name="1" label="个人/事业单位"></uv-radio>
|
||||
<uv-radio :name="2" label="公司/企业单位"></uv-radio>
|
||||
</uv-radio-group>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> * 发票抬头 INVOICE TITLE </view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input placeholder="发票抬头" type="text" v-model="state.formData.title" />
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view @click="chooseInvoiceTitle" style="display: flex;align-items: center;font-size: 26rpx;">
|
||||
抬头簿
|
||||
<uv-icon name="file-text-fill" color="#000" size="26"></uv-icon>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> {{ state.formData.titleType == 2 ? '*' : '-' }} 纳税人识别号 IDENTIFICATION NUMBER </view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.identificationNo" />
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select" @click="state.expand = !state.expand">
|
||||
<view class="label"> - 更多信息 REMARKS </view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<view class="label">{{ state.expand?'点击收起信息':'展开可填写购买方信息、备注等。' }} </view>
|
||||
</view>
|
||||
<view class="arrow" :class="{ arrowClose: !state.expand }" >
|
||||
<image src="/static/setOrder/selectArrow.png" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="state.expand">
|
||||
<!-- <view class="select">
|
||||
<view class="label"> - 银行账号 ACCOUNT NUMBER</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.bank" />
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="select">
|
||||
<view class="label"> - 银行账号 ACCOUNT NUMBER</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.bankAccount" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> - 开户银行 ACCOUNT OPENING</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.bank" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> - 公司地址 ADDRESS</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.address" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> - 公司电话 PHONE NUMBER</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.telephone" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view class="receiving" style="padding: 10rpx 0;">
|
||||
* 收票邮箱 RECEIVING INFORMATION
|
||||
</view>
|
||||
<view class="select">
|
||||
<view class="label"> * 收票邮箱 EMAIL</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.email" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="select">
|
||||
<view class="label"> * 收票人姓名 NAME</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.recipient" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> * 收票人电话 PHONE</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.recipientPhone" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> * 收票人地址 ADDRESS</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" v-model="state.formData.recipientAddress" />
|
||||
</view>
|
||||
</view>
|
||||
</view>-->
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> - 备注信息 REMARKS</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
<input type="text" placeholder="请填写需在发票备注栏中显示的内容" v-model="state.formData.remarks" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="select">
|
||||
<view class="label"> - 发票金额 AMOUNT</view>
|
||||
<view class="inputBox">
|
||||
<view class="value">
|
||||
{{ currency }} {{ state.formData.amount }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="btn">
|
||||
<button class="next-btn" @click="submit()">
|
||||
{{ $t("common.submit") }}
|
||||
</button>
|
||||
</view>
|
||||
<view class="tips" style="padding: 10rpx 0;">
|
||||
根据相关政策规定,消费环节汇总各种形式的券、积分等金额不支持开票。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 是否验证成功 -->
|
||||
<myModal v-model="state.showAuthModal" :content="state.contentTips" @confirm="confirm" :cancelShow="false"></myModal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { navigateBack } from '@/utils/common.js';
|
||||
import navBar from "@/components/navBar.vue";
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import myModal from "@/components/myModal.vue";
|
||||
const { themeInfo, storeState } = useMainStore();
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useLoginApi } from "@/Apis/login.js";
|
||||
import { useInvoiceApi } from "@/Apis/invoice.js";
|
||||
import { currency } from "@/config/index.js";
|
||||
const { t } = useI18n();
|
||||
const pages = getCurrentPages()
|
||||
const getLoginApi = useLoginApi();
|
||||
const getInvoiceApi = useInvoiceApi();
|
||||
|
||||
const canGetCode = ref();
|
||||
const state = reactive({
|
||||
expand: false,
|
||||
tips: "获取验证码",
|
||||
hasVeriPerson: false, // 是否验证过个人
|
||||
hasVeriEnterprise: false, // 是否验证过企业
|
||||
editPerson: true, // 是否编辑个人信息
|
||||
editEnterprise: true, // 是否编辑企业信息
|
||||
vaildType: 1, // 1 电子发票 2 纸质发票
|
||||
showAuthModal: false,
|
||||
contentTips:'发票申请成功,请耐心等待工作人员审核。', // 验证成功提示
|
||||
formData: {
|
||||
id:'',
|
||||
paymentId:'',
|
||||
applicationType: 2,//发票类型 1 增值税 2 普通发票
|
||||
titleType: 1,//抬头类型 1 个人 2 企业
|
||||
title: '',// 抬头
|
||||
identificationNo:'',//纳税人识别号
|
||||
email: '', //邮箱
|
||||
bankAccount: '', // 银行账号
|
||||
bank: '', // 开户银行名称
|
||||
address: '', // 开户银行地址
|
||||
telephone: '', // 公司电话
|
||||
remarks: '', // 备注
|
||||
amount: 0,
|
||||
recipient: "",
|
||||
recipientPhone: "",
|
||||
recipientAddress: ""
|
||||
},
|
||||
|
||||
});
|
||||
const changeValidType = (val) => {
|
||||
state.vaildType = val;
|
||||
};
|
||||
const changeApplicationType = (val) => {
|
||||
if(state.formData.applicationType == 1){
|
||||
state.formData.titleType = 2
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((e) => {
|
||||
console.log(e.id);
|
||||
// e.id 有值为编辑
|
||||
if(e.id){
|
||||
state.formData.id = e.id;
|
||||
getDeteil(e.id)
|
||||
}
|
||||
state.formData.paymentId = e.paymentId?.split(',');
|
||||
state.formData.amount = e.num;
|
||||
});
|
||||
const getDeteil = () => {
|
||||
getInvoiceApi.GetInvoiceApplyForById({id:state.formData.id}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
state.formData = res.data
|
||||
}
|
||||
});
|
||||
}
|
||||
const chooseInvoiceTitle = (val) => {
|
||||
uni.chooseInvoiceTitle({
|
||||
success(res) {
|
||||
state.formData.titleType = res.type == 0 ? 2 : 1;
|
||||
state.formData.title = res.title;
|
||||
state.formData.identificationNo = res.taxNumber;
|
||||
state.formData.address = res.companyAddress;
|
||||
state.formData.telephone = res.telephone;
|
||||
state.formData.bank = res.bankName;
|
||||
state.formData.bankAccount = res.bankAccount;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getPhoneNumber = (e) => {
|
||||
if (!state.editPerson) return;
|
||||
// if (state.hasPhone) {
|
||||
// state.formData.phone = storeState.userInfo.phone;
|
||||
// } else {
|
||||
// }
|
||||
uni.showLoading();
|
||||
if (e.code) {
|
||||
getLoginApi.GetPhoneNumber({code:e.code}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
uni.hideLoading();
|
||||
state.formData.phone = res.data;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: t('validation.getPhoneFail'),
|
||||
icon: "none",
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
navigateBack()
|
||||
// uni.redirectTo({
|
||||
// url: '/pagesb/invoiceApplyforRecord/index'
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
const submit = () => {
|
||||
// todo 提交
|
||||
verifyPerson();
|
||||
}
|
||||
|
||||
// 提交
|
||||
const verifyPerson = () => {
|
||||
const params = {
|
||||
...state.formData,
|
||||
}
|
||||
delete params.amount;
|
||||
if (!state.formData.title) {
|
||||
return uni.showToast({
|
||||
title: "请填写抬头",
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
if(state.formData.titleType == 2){
|
||||
if (!state.formData.identificationNo) return uni.showToast({
|
||||
title: "请填写纳税人识别号",
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
// if(state.formData.applicationType == 1){
|
||||
// if(!state.formData.bankAccount||!state.formData.bank||!state.formData.address||!state.formData.telephone) {
|
||||
// uni.showToast({
|
||||
// title: "请填写完整,开户银行与公司信息",
|
||||
// icon: 'none'
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
if (!state.formData.email) return uni.showToast({
|
||||
title: "请填写邮箱",
|
||||
icon: 'none'
|
||||
});
|
||||
uni.showLoading({mask:true});
|
||||
if(state.formData.id){
|
||||
getInvoiceApi.UpdateInvoiceApplyFor(params).then(res => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
state.showAuthModal = true;
|
||||
}
|
||||
});
|
||||
}else{
|
||||
getInvoiceApi.InvoiceApplyFor(params).then(res => {
|
||||
uni.showLoading({mask:true})
|
||||
if (res.code == 200) {
|
||||
state.showAuthModal = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding: 0 20upx;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 80rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--right-linear),
|
||||
var(--left-linear2)
|
||||
);
|
||||
background-attachment: fixed;
|
||||
.content {
|
||||
width: 100%;
|
||||
.infobox {
|
||||
width: 100%;
|
||||
padding: 20upx 30upx;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
z-index: 9;
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--left-linear2) 0px, var(--left-linear2) 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
margin-top: 60rpx;
|
||||
margin-bottom: 20rpx;
|
||||
button {
|
||||
font-size: 40rpx;
|
||||
color: var(--text-color);
|
||||
background: var(--active-color);
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.13);
|
||||
}
|
||||
}
|
||||
.receiving{
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.tips{
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.select {
|
||||
border-bottom: 1px dashed #d8d8d857;
|
||||
margin-bottom: 16rpx;
|
||||
.label {
|
||||
color: #bec2ce;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.info {
|
||||
color: #242e42;
|
||||
font-size: 22rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-weight: bold;
|
||||
padding: 0 18rpx;
|
||||
opacity: 0.7;
|
||||
&.Total {
|
||||
margin-top: 10rpx;
|
||||
color: #242e42;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.garyBox {
|
||||
background-color: rgba(216, 216, 216, 0.3);
|
||||
border-radius: 8rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
color: rgb(36, 46, 66);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 10rpx 4rpx;
|
||||
}
|
||||
.inputBox {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10rpx;
|
||||
margin-left: 5px;
|
||||
:deep(.uv-radio){
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.ImgUpload {
|
||||
display: flex;
|
||||
margin: 10upx 0;
|
||||
::v-deep .uv-upload {
|
||||
margin-right: 40rpx;
|
||||
border-radius: 18rpx;
|
||||
border:2px solid #9797974F;
|
||||
.uv-upload__wrap__preview{
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.upLoadText{
|
||||
font-size: 28upx;
|
||||
text-align: center;
|
||||
padding: 10upx 0;
|
||||
background: var(--active-color);
|
||||
border-radius: 0 0 18upx 18upx;
|
||||
margin: -1px;
|
||||
}
|
||||
}
|
||||
.value {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
color: #242e42;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.arrowClose{
|
||||
// 中心点翻转
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.arrow {
|
||||
width: auto;
|
||||
font-size: 24rpx;
|
||||
.codeBtn {
|
||||
font-size: 24rpx;
|
||||
background-color: #d1cbcb2d;
|
||||
}
|
||||
}
|
||||
image {
|
||||
width: 20rpx;
|
||||
height: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.i-header {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
background: radial-gradient(var(--right-linear) 0px, var(--right-linear) 5px, transparent 5px, transparent);
|
||||
background-size: 14px 14px;
|
||||
z-index: 9;
|
||||
}
|
||||
padding: 30upx 0;
|
||||
width: 100%;
|
||||
background-color: #f7f7f7;
|
||||
color: #242e42;
|
||||
.tabbox {
|
||||
position: relative;
|
||||
padding: 20upx 10%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.li {
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
font-size: 32upx;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
}
|
||||
.li.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
.bottom-line {
|
||||
position: absolute;
|
||||
// left: calc(50% - 220rpx);
|
||||
bottom: 0;
|
||||
width: 160rpx;
|
||||
height: 2.5px;
|
||||
border-radius: 20px;
|
||||
background: var(--main-color);
|
||||
transition: left 0.5s ease;
|
||||
|
||||
&.right {
|
||||
left: calc(50% + 60rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
271
pagesb/invoiceApplyforRecord/index.vue
Normal file
271
pagesb/invoiceApplyforRecord/index.vue
Normal file
@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<view class="invoice-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar></navBar>
|
||||
|
||||
<view class="wrapBox">
|
||||
<view class="content">
|
||||
<view class="orderBox">
|
||||
<!-- @click="goInvoiceApply(item)" -->
|
||||
<view
|
||||
class="orderLi"
|
||||
v-for="(item, index) in state.list"
|
||||
:key="index"
|
||||
>
|
||||
<view class="left">
|
||||
<view>{{ $t("invoice.serial") }}: <text>{{ item.id }}</text> </view>
|
||||
<view>{{ $t("invoice.time") }}: {{ item.applicationTime }}</view>
|
||||
<view>{{ $t("invoice.status") }}: {{ auditOption[item.audit] }}</view>
|
||||
<view>{{ $t("invoice.unit") }}: {{ item.lockerNames?.length ? item.lockerNames.join("、") : "无"}}</view>
|
||||
<view v-if="item.audit == 2">
|
||||
{{ $t("common.note") }}: {{ item.rejectRemarks}}</view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<!-- <view>{{ item.lockerNames }}</view> -->
|
||||
<view>{{ currency }}{{ item.money }}</view>
|
||||
</view>
|
||||
<view class="btn" v-if="item.audit == 0 || item.audit == 2">
|
||||
<button @click="editApply(item.id)">{{ $t("common.edit") }}</button>
|
||||
<button @click="cancelApply(item.id)">{{ $t("common.cancel") }}</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="state.list.length == 0" class="noData">{{ $t('common.noData') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 主题色配置
|
||||
import { reactive, computed } from "vue";
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { currency } from "@/config/index.js";
|
||||
import navBar from "@/components/navBar.vue";
|
||||
import { useInvoiceApi } from "@/Apis/invoice";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
const { themeInfo } = useMainStore();
|
||||
|
||||
const getApi = useInvoiceApi();
|
||||
const state = reactive({
|
||||
list: [],
|
||||
});
|
||||
const auditOption = computed(() => {
|
||||
return {
|
||||
0: t("invoice.status0"),
|
||||
1: t("invoice.status1"),
|
||||
2: t("invoice.status2"),
|
||||
3: t("invoice.status3"),
|
||||
4: t("invoice.status4"),
|
||||
}
|
||||
});
|
||||
const editApply = (id)=>{
|
||||
uni.navigateTo({
|
||||
url: '/pagesb/invoiceApply/index?id='+id
|
||||
})
|
||||
}
|
||||
const cancelApply = (id)=>{
|
||||
uni.showModal({
|
||||
title: t("common.tip"),
|
||||
content: t("common.cancelApply"),
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
getApi.CancelInvoiceApplyFor({id}).then((res) => {
|
||||
if(res.code == 200){
|
||||
uni.showToast({
|
||||
title: t("common.cancelSuccess"),
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
getDataList();
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: t("common.cancelFail"),
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
} else if (res.cancel) {
|
||||
console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
const getDataList = () => {
|
||||
uni.showLoading();
|
||||
getApi.GetInvoiceApplyFor().then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
state.list = res.data;
|
||||
} else {
|
||||
state.list = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
onShow(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/static/style/theme.scss';
|
||||
|
||||
.invoice-wrap {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--right-linear),
|
||||
var(--left-linear)
|
||||
);
|
||||
.noData{
|
||||
text-align: center;
|
||||
}
|
||||
.wrapBox {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
.top {
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.arrow {
|
||||
width: 20rpx;
|
||||
display: flex;
|
||||
margin-right: 6rpx;
|
||||
image {
|
||||
width: 20rpx;
|
||||
height: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
.contact {
|
||||
position: absolute;
|
||||
bottom: 60rpx;
|
||||
right: 0;
|
||||
height: 74rpx;
|
||||
background-color: var(--main-color);
|
||||
border-radius: 45rpx 0rpx 0rpx 45rpx;
|
||||
border: 4rpx solid var(--stress-text);
|
||||
box-shadow: 0rpx 4rpx 10rpx 0rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 0 20rpx 0 30rpx;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--stress-text);
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
.orderBox {
|
||||
padding: 20rpx;
|
||||
.orderLi {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 30rpx;
|
||||
border-radius: 18rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 24rpx;
|
||||
background-color: var(--main-color);
|
||||
display: flex;
|
||||
.leftCheckBox {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.left {
|
||||
flex: 1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.btn{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: 20rpx;
|
||||
padding-top: 10rpx;
|
||||
border-top:1px solid #292222;
|
||||
button {
|
||||
min-width: 124rpx;
|
||||
font-size: 22rpx;
|
||||
color: var(--btn-color1);
|
||||
background-color: var(--text-color);
|
||||
border: none;
|
||||
height: 56rpx;
|
||||
border-radius: 28rpx;
|
||||
line-height: 56rpx;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.39px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 150rpx;
|
||||
// :nth-child(1) {
|
||||
// border-radius: 99rpx;
|
||||
// padding: 0 20rpx;
|
||||
// height: 40rpx;
|
||||
// line-height: 40rpx;
|
||||
// background-color: black;
|
||||
// color: var(--main-color);
|
||||
// margin-bottom: 10rpx;
|
||||
// }
|
||||
:nth-child(1) {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bottom {
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
font-size: 24rpx;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: var(--stress-color);
|
||||
}
|
||||
.goApply {
|
||||
height: 130rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10upx 40upx;
|
||||
align-items: center;
|
||||
.next {
|
||||
button {
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 99rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
189
pagesb/latestEvents/index.vue
Normal file
189
pagesb/latestEvents/index.vue
Normal file
@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<view class="container" :class="[`${themeInfo.theme}-theme`]">
|
||||
<navBar class="navBar"></navBar>
|
||||
<view class="wrapbox">
|
||||
<view class="box" v-for="(item,index) in state.dataList" :key="index">
|
||||
<view class="imageBox">
|
||||
<image class="topImage" :src="baseImageUrl + item.imgUrl" mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="title">
|
||||
<view class="title1">{{ item.title }}</view>
|
||||
<view class="title2">{{ item.viceTitle }}</view>
|
||||
</view>
|
||||
<view class="content">
|
||||
<my-uv-collapse :ref="(e)=>setRefs(e,index)" :border="false" :clickable="false">
|
||||
<my-uv-collapse-item :clickable="false" value="详情" name="Docs guide">
|
||||
<view class="htmlbox">
|
||||
<rich-text :nodes="item.content"></rich-text>
|
||||
</view>
|
||||
</my-uv-collapse-item>
|
||||
</my-uv-collapse>
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="NoDataTips" v-if="state.dataList.length === 0">
|
||||
<text>抱歉,暂无活动,敬请关注。</text>
|
||||
<text class="en">SORRY,THERE ARE NO EVENTS,STAY TUNED PLEASE.</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 邀请详情 -->
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import navBar from "@/components/navBar.vue";
|
||||
// import { makePhoneCall } from "/utils/common.js";
|
||||
import { ref,nextTick } from "vue";
|
||||
import { useLoginApi } from "/Apis/home.js";
|
||||
import { onLoad, onShareAppMessage,onShow,} from "@dcloudio/uni-app";
|
||||
import myUvCollapse from "@/pagesb/components/my-uv-collapse/components/uv-collapse/uv-collapse.vue";
|
||||
import myUvCollapseItem from "@/pagesb/components/my-uv-collapse/components/uv-collapse-item/uv-collapse-item.vue";
|
||||
// 主题色配置
|
||||
import { useMainStore } from "@/store/index.js";
|
||||
import { baseImageUrl } from "@/config/index.js";
|
||||
const { themeInfo } = useMainStore();
|
||||
const getApi = useLoginApi();
|
||||
const state = ref({
|
||||
dataList: [],
|
||||
});
|
||||
const collapseRefs = ref([]);
|
||||
const setRefs = (ref,index) => {
|
||||
collapseRefs.value[index] = ref;
|
||||
};
|
||||
onLoad((params) => {
|
||||
getData();
|
||||
});
|
||||
onShow(() => {
|
||||
|
||||
// $nextTick(() => {
|
||||
// // 再次调用 init 重新初始化内部高度
|
||||
// collapseRefs?.value.init();
|
||||
// uni.hideLoading();
|
||||
// })
|
||||
|
||||
|
||||
});
|
||||
const getData = async () => {
|
||||
// 轮播图
|
||||
const params = {
|
||||
platformType: 1, // 小程序
|
||||
forPage: 4, // 首頁
|
||||
contentType: 5, // 1轮播图 9视频 5活动
|
||||
};
|
||||
uni.showLoading();
|
||||
getApi.GetPageContent(params).then((res) => {
|
||||
uni.hideLoading();
|
||||
if(res.code === 200){
|
||||
state.value.dataList = res.data;
|
||||
nextTick(() => {
|
||||
collapseRefs.value.forEach((item) => {
|
||||
item.init();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
onShareAppMessage((res) => {
|
||||
if (res.from === "button") {
|
||||
}
|
||||
return shareParam;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/static/style/theme.scss";
|
||||
uni-page-body {
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgb(1, 169, 188), rgb(10, 132, 184));
|
||||
overflow: auto;
|
||||
}
|
||||
.navBar {
|
||||
position: relative;
|
||||
}
|
||||
.NoDataTips{
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 40rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: var(--right-linear);
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(0deg, var(--left-linear), var(--right-linear));
|
||||
.wrapbox {
|
||||
width: 100%;
|
||||
padding: 20rpx 20rpx;
|
||||
min-height: 100vh;
|
||||
background-color: #FFF;
|
||||
.box{
|
||||
background-color: var(--right-linear);
|
||||
border-radius: 20rpx;
|
||||
margin: 20rpx 0;
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
.topImage{
|
||||
width: 100%;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
}
|
||||
.title{
|
||||
padding: 20px;
|
||||
padding-bottom: 0;
|
||||
padding-top: 10px;
|
||||
view{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&::before{
|
||||
display: block;
|
||||
content: ' ';
|
||||
width: 18rpx;
|
||||
height: 18rpx;
|
||||
background-color: black;
|
||||
margin-right: 20rpx;
|
||||
border-radius: 999px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content{
|
||||
::v-deep(.uv-collapse){
|
||||
.uv-cell__value{
|
||||
font-size: 20rpx;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
.htmlbox{
|
||||
width: 100%;
|
||||
background-color: rgb(250, 252, 243);
|
||||
margin-right: 20rpx;
|
||||
padding: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
rich-text{
|
||||
font-size: 22rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 30rpx;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user