初始化项目模板
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# .env.development
|
||||
NODE_ENV=development
|
||||
VITE_APP_RUN_ENV=development
|
||||
VITE_APP_BASE_URL="http://127.0.0.1:8000"
|
||||
@@ -0,0 +1,4 @@
|
||||
# .env.production
|
||||
NODE_ENV=production
|
||||
VITE_APP_RUN_ENV=production
|
||||
VITE_APP_BASE_URL="http://127.0.0.1:8000"
|
||||
@@ -0,0 +1,3 @@
|
||||
# Numerous always-ignore extensions
|
||||
unpackage
|
||||
node_modules
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
// 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" : [
|
||||
{
|
||||
"app-plus" : {
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"default" : {
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"mp-weixin" : {
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
},
|
||||
{
|
||||
"openVueDevtools" : false,
|
||||
"type" : "uni-app:miniProgram"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uni_modules
|
||||
@@ -0,0 +1,42 @@
|
||||
// 配置几个常用的就可以
|
||||
module.exports = {
|
||||
// 一行最多多少个字符
|
||||
printWidth: 160,
|
||||
// 指定每个缩进级别的空格数
|
||||
tabWidth: 2,
|
||||
// 使用制表符而不是空格缩进行
|
||||
useTabs: false,
|
||||
// 在语句末尾打印分号
|
||||
semi: true,
|
||||
// 使用单引号而不是双引号
|
||||
singleQuote: true,
|
||||
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
|
||||
quoteProps: 'as-needed',
|
||||
// 在JSX中使用单引号而不是双引号
|
||||
jsxSingleQuote: false,
|
||||
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
|
||||
trailingComma: 'es5',
|
||||
// 在对象文字中的括号之间打印空格
|
||||
bracketSpacing: true,
|
||||
// jsx 标签的反尖括号需要换行
|
||||
jsxBracketSameLine: false,
|
||||
// 在jsx中把'>' 是否单独放一行
|
||||
bracketSameLine: false,
|
||||
// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
|
||||
arrowParens: 'always',
|
||||
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
// 指定要使用的解析器,不需要写文件开头的 @prettier
|
||||
requirePragma: false,
|
||||
// 不需要自动在文件开头插入 @prettier
|
||||
insertPragma: false,
|
||||
// 使用默认的折行标准 always\never\preserve
|
||||
proseWrap: 'preserve',
|
||||
// 指定HTML文件的全局空格敏感度 css\strict\ignore
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
// Vue文件脚本和样式标签缩进
|
||||
vueIndentScriptAndStyle: false,
|
||||
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
|
||||
endOfLine: 'lf',
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
<script setup>
|
||||
import {
|
||||
ref
|
||||
} from 'vue';
|
||||
import {
|
||||
onShow,
|
||||
onLaunch,
|
||||
onHide
|
||||
} from '@dcloudio/uni-app';
|
||||
import {
|
||||
useI18n
|
||||
} from 'vue-i18n';
|
||||
import i18n from '@/lang';
|
||||
import pinia from '@/store/store.js';
|
||||
import {
|
||||
useUserStore
|
||||
} from '@/store/user';
|
||||
import {
|
||||
isEmpty,
|
||||
platformMap
|
||||
} from '@/common/common.js';
|
||||
import {
|
||||
getConfigInfo
|
||||
} from '@/common/subscribePayment.js'
|
||||
const {
|
||||
t,
|
||||
locale
|
||||
} = useI18n();
|
||||
|
||||
// 初始化platform
|
||||
function initApplication() {
|
||||
const user = useUserStore(pinia);
|
||||
// platform
|
||||
let uniPlatform = uni.getSystemInfoSync().uniPlatform;
|
||||
let hasPlatform = platformMap.filter((h) => h.map.match(uniPlatform))[0];
|
||||
if (hasPlatform) {
|
||||
// 非app,mp-weixin,mp-alipay,web都会进入这里
|
||||
user.platform = hasPlatform.key;
|
||||
} else {
|
||||
// app
|
||||
let port = uni.getSystemInfoSync().platform;
|
||||
let platform = platformMap.filter((h) => h.map.match(port))[0];
|
||||
user.platform = platform.key;
|
||||
}
|
||||
// 获取本机语种
|
||||
let appLanguage = uni.getSystemInfoSync().appLanguage;
|
||||
let languageMap = ['zh-CN', 'zh-TW'];
|
||||
if (isEmpty(user.lang)) {
|
||||
if (languageMap.indexOf(appLanguage) === -1) {
|
||||
user.lang = 'zh-CN';
|
||||
} else {
|
||||
user.lang = appLanguage;
|
||||
}
|
||||
}
|
||||
user.lang = user.lang.replace(/\"/g, '');
|
||||
locale.value = user.lang;
|
||||
i18n.global.locale.value = user.lang;
|
||||
}
|
||||
|
||||
// 检查小程序更新
|
||||
function checkUpDateMini() {
|
||||
if (uni.canIUse('getUpdateManager')) {
|
||||
const updateManager = uni.getUpdateManager();
|
||||
updateManager.onCheckForUpdate(function(res) {
|
||||
if (res.hasUpdate) {
|
||||
updateManager.onUpdateReady(function(ready) {
|
||||
uni.showModal({
|
||||
title: t('modal.upDate'),
|
||||
content: t('modal.finishUpdate'),
|
||||
showCancel: false,
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
updateManager.applyUpdate();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
updateManager.onUpdateFailed(function(err) {
|
||||
uni.showModal({
|
||||
title: t('modal.hasUpdate'),
|
||||
content: t('modal.hasntUsed'),
|
||||
showCancel: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: t('modal.title'),
|
||||
content: t('modal.upgradeCarrier'),
|
||||
showCancel: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onLaunch(() => {
|
||||
initApplication();
|
||||
// getConfigInfo();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
// #ifdef APP-PLUS
|
||||
uni.onNetworkStatusChange(function(res) {
|
||||
if (res.isConnected) {
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
}, 0);
|
||||
} else {
|
||||
uni.showModal({
|
||||
cancelText: t('modal.reStart'),
|
||||
confirmText: t('modal.reLoad'),
|
||||
content: t('modal.offNet'),
|
||||
title: t('modal.title'),
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
} else {
|
||||
plus.runtime.restart();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
checkUpDateMini();
|
||||
// #endif
|
||||
});
|
||||
onHide(() => {});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/utilities';
|
||||
@import 'tailwindcss/components';
|
||||
|
||||
html,
|
||||
head,
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: SourceHanSansCN, SourceHanSansCN;
|
||||
}
|
||||
|
||||
page {
|
||||
background: #fbfbfd;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0px 5rpx 10rpx 0px rgba(207, 207, 207, 0.5);
|
||||
}
|
||||
|
||||
.border-bottom {
|
||||
@apply border-0 border-b border-solid border-[#F2F2F2];
|
||||
}
|
||||
|
||||
.border-bottom-theme {
|
||||
@apply border-0 border-b border-solid border-theme;
|
||||
}
|
||||
|
||||
.border-round-theme {
|
||||
@apply border border-solid border-theme;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
@apply bg-theme text-white h-[100rpx] text-[30rpx];
|
||||
}
|
||||
|
||||
:root {
|
||||
--theme-color: #274c40;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { get, post, put, dele } from '@/utils/request';
|
||||
|
||||
/**
|
||||
* @desc 初始化支付
|
||||
* groupId 订单组id
|
||||
*/
|
||||
export const apiInitPay = (groupId) => {
|
||||
return get('/sale/tripcommon/order/pay-init',{groupId});
|
||||
};
|
||||
/**
|
||||
* 查询客服
|
||||
* @returns
|
||||
*/
|
||||
export const apiQueryCustomer = () => {
|
||||
return get('/sale/tripcommon/index/kf');
|
||||
};
|
||||
/**
|
||||
* 手机区号-全球
|
||||
* @returns
|
||||
*/
|
||||
export const apiWholeAreaCode = () => {
|
||||
return get('/sale/attr/tel-area-code');
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { get, post, put, dele } from '@/utils/request';
|
||||
|
||||
/**
|
||||
* @desc 上传图片
|
||||
*/
|
||||
export const uploadImage = (params) => {
|
||||
return post('/front/upload/image?useType=comment', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 修改手机号获取验证码初始化
|
||||
*/
|
||||
export const editMobileInit = (params) => {
|
||||
return get('/front/user/user/edit-mobile-init', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 发送验证码
|
||||
*/
|
||||
export const sendCode = (params) => {
|
||||
return get('/sale/login/send-mb-verify-code', params);
|
||||
};
|
||||
/**
|
||||
* @desc 发送验证码初始化
|
||||
*/
|
||||
export const loginLRInit = (params) => {
|
||||
return get('/sale/login/send-mb-verify-code-init', params);
|
||||
};
|
||||
/**
|
||||
* @desc 登录
|
||||
*/
|
||||
export const login = (params) => {
|
||||
return get('/sale/login/login', params);
|
||||
};
|
||||
/**
|
||||
* @desc 注册
|
||||
*/
|
||||
export const register = (params) => {
|
||||
return get('/front/user/login/register', params);
|
||||
};
|
||||
/**
|
||||
* @desc 忘记密码
|
||||
*/
|
||||
export const forgetPassword = (params) => {
|
||||
return get('/front/user/login/forget-password', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 会员首页
|
||||
*/
|
||||
export const userIndex = (params) => {
|
||||
return get('/front/user/user/index', params);
|
||||
};
|
||||
/**
|
||||
* @desc 获取会员数据
|
||||
*/
|
||||
export const userInfo = (params) => {
|
||||
return get('/sale/user/profile', params);
|
||||
};
|
||||
/**
|
||||
* @desc 更改会员手机号
|
||||
*/
|
||||
export const updateMobile = (params) => {
|
||||
return get('/sale/user/update-mobile', params);
|
||||
};
|
||||
/**
|
||||
* @desc 更改会员资料
|
||||
*/
|
||||
export const updateProfile = (params) => {
|
||||
return get('/sale/user/update-profile', params);
|
||||
};
|
||||
/**
|
||||
* @desc 注销账户
|
||||
*/
|
||||
export const voidAccount = (params) => {
|
||||
return get('/front/user/user/void-account', params);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 获取隐私政策
|
||||
*/
|
||||
export const policyServer = (params) => {
|
||||
return get('/sale/login/get-policy', params);
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
import {
|
||||
isEmpty
|
||||
} from '@/common/common.js'
|
||||
import {
|
||||
useUserStore
|
||||
} from '@/store/user.js'
|
||||
import { request } from '@/utils/request.js'
|
||||
|
||||
/*
|
||||
@desc 多端一键登录授权
|
||||
@desc wx:自2023年8月26日起,手机号快速验证组件将需要付费使用。标准单价为:每次组件调用成功,收费0.03元。
|
||||
zfb:用户同意授权后,scope为长期有效,需用户自行手动解除授权。
|
||||
@params e 为getPhoneNumber原生按钮的回调值
|
||||
*/
|
||||
export function uniLogin(e) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
return
|
||||
}
|
||||
// 获取当前操作端
|
||||
uni.getProvider({
|
||||
service: 'oauth',
|
||||
success: (res) => {
|
||||
if (res.provider.includes('weixin') || res.provider.includes('alipay')) {
|
||||
// 登录
|
||||
uni.login({
|
||||
provider: res.provider[0],
|
||||
success: (loginRes) => {
|
||||
if (loginRes.code) {
|
||||
// 请求后端接口解密code,拿到真实电话号码,并缓存,然后关闭弹窗,渲染页面数据
|
||||
var params = {
|
||||
codeType: 'mpMobile',
|
||||
codeValue: JSON.stringify({
|
||||
...e.detail,
|
||||
...{
|
||||
loginData: loginRes,
|
||||
},
|
||||
}),
|
||||
}
|
||||
request({
|
||||
method: 'POST',
|
||||
url: '/sale/login/login',
|
||||
data: params,
|
||||
}).then((resp) => {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
mask: true,
|
||||
title: '登录成功',
|
||||
})
|
||||
if (resp.success) {
|
||||
const userStore = useUserStore()
|
||||
userStore.userToken = resp.data.token;
|
||||
userStore.isLogin = resp.data.isLogin;
|
||||
userStore.setProfile({
|
||||
...resp.data.userData,
|
||||
});
|
||||
resolve(true)
|
||||
} else {
|
||||
// 失败的操作
|
||||
console.log('request-fail', resp)
|
||||
uni.showToast({
|
||||
title: '授权登录失败',
|
||||
icon: 'none',
|
||||
})
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log({ err })
|
||||
uni.showToast({
|
||||
title: '授权失败',
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @desc 获取当前小程序端的登录授权码code
|
||||
*/
|
||||
export function getLoginCode() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
uni.getProvider({
|
||||
service: 'oauth',
|
||||
success: function(res) {
|
||||
if (~res.provider.indexOf('weixin') || ~res.provider.indexOf('alipay')) {
|
||||
// 登录
|
||||
uni.login({
|
||||
provider: res.provider[0],
|
||||
success: function(loginRes) {
|
||||
resolve(loginRes);
|
||||
},
|
||||
fail(err) {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
swan.getLoginCode({
|
||||
success(loginRes) {
|
||||
resolve(loginRes);
|
||||
},
|
||||
fail(err) {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
// #ifdef APP-PLUS || H5
|
||||
resolve();
|
||||
// #endif
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 退出登录,清除token,手机号缓存
|
||||
*/
|
||||
export function loginOut(status) {
|
||||
const userStore = useUserStore();
|
||||
userStore.clearProfile();
|
||||
userStore.userToken = '';
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
title: 'loading...'
|
||||
})
|
||||
setTimeout(() => {
|
||||
if (status) {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
uni.hideLoading()
|
||||
}, 800)
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 检查是否存在token等用户信息
|
||||
* @returns false 不存在token 、true存在token
|
||||
*/
|
||||
export function checkLoginStatus() {
|
||||
const userStore = useUserStore();
|
||||
const token = userStore.userToken;
|
||||
if (isEmpty(token)) {
|
||||
loginOut(false)
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import { apiInitPay } from '../api/common';
|
||||
import { useUserStore } from '../store/user';
|
||||
import { baseUrl } from '../utils/request';
|
||||
import { getConfig } from '@/pages/index/api';
|
||||
import i18n from '../lang';
|
||||
|
||||
/**
|
||||
* @desc 获取全局配置参数
|
||||
*/
|
||||
export const getConfigInfo = () => {
|
||||
getConfig().then((res) => {
|
||||
if (res.success) {
|
||||
const bottomNavList = res.data?.bottomNavList;
|
||||
// if (bottomNavList.length > 0) {
|
||||
// bottomNavList.map((item) => {
|
||||
// uni.setTabBarItem({
|
||||
// ...item,
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 退款协议
|
||||
* @params companyCode 退款协议需要conpanyCode
|
||||
* @params isRefund false非退款协议 true退款协议
|
||||
* @return html富文本
|
||||
*/
|
||||
export function serviceApi(companyCode, isRefund) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let id;
|
||||
if (isRefund) {
|
||||
id = companyCode === 'turbojet' ? 34 : 35;
|
||||
} else {
|
||||
id = 1;
|
||||
}
|
||||
request
|
||||
.post(api.infoMessage, {
|
||||
id: id,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
resolve(res.data.content);
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: res.message,
|
||||
});
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
/**
|
||||
* @desc APP调用第三方程序 集成微信,支付寶跳轉
|
||||
*/
|
||||
export function launchApp() {
|
||||
if (
|
||||
plus.runtime.isApplicationExist({
|
||||
pname: 'com.tencent.mm',
|
||||
action: 'weixin://',
|
||||
}) &&
|
||||
paymentKey.value.includes('wx')
|
||||
) {
|
||||
// 跳转应用
|
||||
plus.runtime.launchApplication(
|
||||
{
|
||||
pname: 'com.tencent.mm',
|
||||
action: 'weixin://',
|
||||
},
|
||||
function (e) {
|
||||
console.log('e', e);
|
||||
}
|
||||
);
|
||||
// 检查支付通道
|
||||
plus.payment.getChannels((res) => {
|
||||
console.log('getChannels', res);
|
||||
});
|
||||
// 后台获取支付参数
|
||||
request
|
||||
.post(api.pay, {
|
||||
groupId: groupId.value,
|
||||
payAgentId: payAgentId.value,
|
||||
})
|
||||
.then((res) => {
|
||||
// 拉起微信支付
|
||||
plus.payment.request(
|
||||
'wxpay',
|
||||
res.data,
|
||||
function (resp) {
|
||||
console.log('resp', resp);
|
||||
},
|
||||
function (err) {
|
||||
console.log('err', err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
// 判断支付宝
|
||||
if (
|
||||
plus.runtime.isApplicationExist({
|
||||
pname: 'com.eg.android.AlipayGphone',
|
||||
action: 'alipay://',
|
||||
}) &&
|
||||
paymentKey.value.includes('zfb')
|
||||
) {
|
||||
plus.runtime.launchApplication(
|
||||
{
|
||||
pname: 'com.eg.android.AlipayGphone',
|
||||
action: 'alipay://',
|
||||
},
|
||||
function (e) {
|
||||
console.log('launchApplication', e.message);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 处理支付
|
||||
* @params groupId 订单组id
|
||||
* @return 支付参数
|
||||
*/
|
||||
export const handlePay = (groupId) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const res = await apiInitPay(groupId);
|
||||
if (res.success) {
|
||||
console.log(res.data);
|
||||
const payInfo = res.data;
|
||||
// 半屏支付
|
||||
if (payInfo.payType === 'mpSkip') {
|
||||
uni.openEmbeddedMiniProgram({
|
||||
...payInfo.mpSkipParam,
|
||||
success: (res) => {
|
||||
console.log('success', res);
|
||||
resolve(res.data);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('fail', err);
|
||||
resolve(err.message);
|
||||
},
|
||||
});
|
||||
} else if (payInfo.payType === 'direct') {
|
||||
handleDirectPay(groupId, () => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve(res.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export function handleDirectPay(orderNo, callback) {
|
||||
const userStore = useUserStore();
|
||||
let __userToken = userStore.userToken;
|
||||
let __lang = userStore.lang;
|
||||
let __platform = userStore.platform;
|
||||
let orderNum = typeof orderNo === 'object' ? orderNo.join(',') : orderNo;
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
title: 'loading...',
|
||||
});
|
||||
uni.request({
|
||||
url: baseUrl + '/sale/pay/pay',
|
||||
method: 'POST',
|
||||
data: {
|
||||
groupId: orderNum,
|
||||
__token: __userToken,
|
||||
__platform: __platform,
|
||||
__language: __lang,
|
||||
channelId: 33,
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data.code === 0) {
|
||||
let requestParams = {};
|
||||
// #ifdef MP-BAIDU
|
||||
requestParams = {
|
||||
orderInfo: {
|
||||
...res.data.data,
|
||||
...res.data.data.thirdParams,
|
||||
},
|
||||
};
|
||||
// #endif
|
||||
console.log(res.data.data);
|
||||
// #ifndef MP-BAIDU
|
||||
requestParams = {
|
||||
orderInfo: res.data.data.thirdParams ? res.data.data.thirdParams.frontData.tradeNo : '',
|
||||
timeStamp: res.data.data.timeStamp,
|
||||
nonceStr: res.data.data.nonceStr,
|
||||
package: res.data.data.package,
|
||||
signType: res.data.data.signType,
|
||||
paySign: res.data.data.paySign,
|
||||
};
|
||||
// #endif
|
||||
uni.requestPayment({
|
||||
...requestParams,
|
||||
success(res) {
|
||||
// #ifdef MP-ALIPAY
|
||||
if (res.resultCode === '6001') {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: i18n.global.t('cancelPay'),
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
if (res.resultCode === '9000') {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: i18n.global.t('paySuccess'),
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: i18n.global.t('paySuccess'),
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
fail(res) {
|
||||
console.log('fail-res', res);
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: i18n.global.t('payFailed'),
|
||||
mask: true,
|
||||
});
|
||||
},
|
||||
complete() {
|
||||
callback();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 创建失败
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: res.data.message,
|
||||
mask: true,
|
||||
});
|
||||
callback();
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('fail-err', err);
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: err.message,
|
||||
});
|
||||
setTimeout(() => {
|
||||
callback();
|
||||
}, 1500);
|
||||
},
|
||||
complete() {
|
||||
uni.hideLoading();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<!-- 区域号码选择 -->
|
||||
<up-popup :show="isShow" round="20" closeable @close="close">
|
||||
<view class="container flex flex-col px-[20px]">
|
||||
<view class="title flex items-center justify-center mx-[15px]">
|
||||
<text class="txt text-[16px] py-[15px] font-bold">{{ t('areaCode') }}</text>
|
||||
</view>
|
||||
<view class="search-box my-[10px]">
|
||||
<up-search v-model="keyword" :showAction="false" :placeholder="t('areaCodePlace')" @change="handleKeywordChange"></up-search>
|
||||
</view>
|
||||
<!-- 主列表 -->
|
||||
<view class="index-list relative overflow-hidden">
|
||||
<scroll-view scroll-y :scroll-into-view="scrollIntoId" class="main-list h-[70vh] overflow-hidden" @scroll="handleScroll">
|
||||
<template v-if="isEmpty(keyword)">
|
||||
<view v-for="(group, index) in list" :key="index" :id="group.groupKey" class="group-container relative">
|
||||
<!-- 吸顶标题 -->
|
||||
<view class="group-title text-[14px] text-main py-[5px] sticky top-0 bg-white z-[999] sticky-title">
|
||||
{{ group.name }}
|
||||
</view>
|
||||
<view v-for="(item, idx) in group.data" :key="idx" class="list-item text-main text-[14px] py-[10px]" @click="handleSelect(item)">
|
||||
{{ `${item.name} (+${item.code})` }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view v-else v-for="(item, idx) in filterList" :key="idx" class="list-item text-main text-[14px] py-[10px]" @click="handleSelect(item)">
|
||||
{{ `${item.name} (+${item.code})` }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右侧索引 -->
|
||||
<view class="index-bar absolute top-0 right-0 py-[10px] gap-[10px] z-[9999]">
|
||||
<view
|
||||
v-for="(group, index) in list"
|
||||
:key="index"
|
||||
class="index-item text-center text-[12px] "
|
||||
:class="currentIntoId === group.groupKey ? 'bg-[#e61e30] rounded-full text-white' : 'text-[#e61e30]'"
|
||||
@click="handleIndexclick(group.groupKey)"
|
||||
>
|
||||
{{ group.groupKey }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { onLoad, onReady } from '@dcloudio/uni-app';
|
||||
import { useSystemInfo } from '@/hooks/useSystemInfo.js';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { isEmpty } from '@/common/common.js';
|
||||
import { apiWholeAreaCode } from '@/api/common.js';
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const { computedDomHeight } = useSystemInfo();
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 获取区域码数据
|
||||
const getAreaCodeList = async () => {
|
||||
apiWholeAreaCode({ type: '' }).then((res) => {
|
||||
if (res.success) {
|
||||
list.value = res.data.codeGroups;
|
||||
nextTick(() => {
|
||||
calculateGroupPositions();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isShow = ref(false);
|
||||
watch(
|
||||
() => props.show,
|
||||
(newVal) => {
|
||||
isShow.value = newVal;
|
||||
if (newVal) getAreaCodeList();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
const emits = defineEmits(['close', 'select']);
|
||||
const close = () => emits('close');
|
||||
|
||||
const keyword = ref('');
|
||||
const filterList = ref([]);
|
||||
const handleKeywordChange = (e) => {
|
||||
if (!e) return (filterList.value = []);
|
||||
const result = [];
|
||||
for (let group of list.value) {
|
||||
for (let item of group.data) {
|
||||
const target = item.search?.replace(/\s+/g, '');
|
||||
if (target.includes(e)) {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
filterList.value = uniqueByCode(result, 'code');
|
||||
};
|
||||
|
||||
// 数组对象去重
|
||||
const uniqueByCode = (arr, field) => {
|
||||
const map = new Map(); // 用于存储已经出现过的 field
|
||||
return arr.filter((item) => {
|
||||
if (!map.has(item[field])) {
|
||||
map.set(item[field], true); // 将 field 添加到 Map 中
|
||||
return true; // 保留当前对象
|
||||
}
|
||||
return false; // 过滤掉重复对象
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelect = (e) => {
|
||||
emits('select', e);
|
||||
};
|
||||
|
||||
// 数据
|
||||
const list = ref([]);
|
||||
|
||||
const scrollIntoId = ref(''); // 当前滚动到的锚点ID
|
||||
const currentIntoId = ref(''); // 当前的索引
|
||||
const groupPositions = ref([]); // 存储每个组的顶部位置
|
||||
|
||||
// 点击索引跳转
|
||||
const handleIndexclick = (title) => {
|
||||
keyword.value = '';
|
||||
scrollIntoId.value = title;
|
||||
currentIntoId.value = title;
|
||||
};
|
||||
|
||||
// 处理滚动事件
|
||||
const handleScroll = (event) => {
|
||||
const scrollTop = event.detail.scrollTop;
|
||||
// 根据滚动位置判断当前吸顶的标题
|
||||
for (let i = groupPositions.value.length - 1; i >= 0; i--) {
|
||||
if (scrollTop > groupPositions.value[i].top) {
|
||||
currentIntoId.value = groupPositions.value[i].title;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 计算每个组的顶部位置
|
||||
const calculateGroupPositions = async () => {
|
||||
let heightList = [];
|
||||
for (let i = 0; i < list.value.length; i++) {
|
||||
const group = list.value[i];
|
||||
const data = await computedDomHeight(`#${group.groupKey}`);
|
||||
if (data) {
|
||||
heightList[i] = data.height || 0;
|
||||
groupPositions.value.push({
|
||||
title: group.groupKey,
|
||||
top: i > 0 ? heightList.slice(0, i).reduce((pre, cur) => cur + pre, 0) : 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t, locale } = useI18n();
|
||||
const emit = defineEmits(['confirm', 'close']);
|
||||
const props = defineProps({
|
||||
title: {
|
||||
// 選擇器標題
|
||||
type: String,
|
||||
default: () => {
|
||||
return '';
|
||||
},
|
||||
},
|
||||
show: {
|
||||
// 展示狀態
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
// 數組列表
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
keyName: {
|
||||
type: String,
|
||||
default: () => {
|
||||
return 'name';
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const showPicker = ref(false); // 是否显示弹窗
|
||||
const columnList = ref([]); // 列表数据
|
||||
const onConfirm = (e) => {
|
||||
emit('confirm', e.value);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
emit('close');
|
||||
};
|
||||
watch(
|
||||
() => props.show,
|
||||
(newVal) => {
|
||||
showPicker.value = newVal;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.columns,
|
||||
(newVal) => {
|
||||
// console.log(newVal);
|
||||
columnList.value.push(
|
||||
newVal.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
};
|
||||
})
|
||||
);
|
||||
// console.log(columnList.value);
|
||||
},
|
||||
{
|
||||
// immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<!-- <up-popup :show="showPicker" :round="10" mode="bottom" :closeOnClickOverlay="true" @close="onCancel"> -->
|
||||
<up-picker
|
||||
:show="showPicker"
|
||||
:title="props.title"
|
||||
:confirmText="t('confirm')"
|
||||
:cancelText="t('cancel')"
|
||||
confirmColor="#274C40"
|
||||
:columns="columnList"
|
||||
:keyName="keyName"
|
||||
:closeOnClickOverlay="true"
|
||||
@confirm="onConfirm"
|
||||
@cancel="onCancel"
|
||||
@close="onCancel"
|
||||
></up-picker>
|
||||
<!-- </up-popup> -->
|
||||
</template>
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<view class="relative">
|
||||
<!-- 输入框触发 -->
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
:value="selectedDate ? selectedDate.format('YYYY-MM-DD') : '选择日期'"
|
||||
@click="showCalendar = !showCalendar"
|
||||
class="w-full px-4 py-2 border-2 border-[#9C032D] rounded-lg cursor-pointer bg-white"
|
||||
/>
|
||||
|
||||
<!-- 日历容器 -->
|
||||
<view
|
||||
v-if="showCalendar"
|
||||
class="fixed bottom-0 left-1/2 transform -translate-x-1/2 mb-4 w-[320px] bg-[#f8d7da] border-2 border-[#9C032D] rounded-lg p-4 z-50 shadow-xl"
|
||||
>
|
||||
<!-- 头部 -->
|
||||
<view class="flex justify-between items-center mb-4">
|
||||
<button @click="changeMonth(-1)" class="text-[#9C032D] hover:text-[#80022a] transition-colors">◀</button>
|
||||
<view class="font-bold text-[#9C032D]">
|
||||
{{ currentDate.format('YYYY年MM月') }}
|
||||
</view>
|
||||
<button @click="changeMonth(1)" class="text-[#9C032D] hover:text-[#80022a] transition-colors">▶</button>
|
||||
</view>
|
||||
|
||||
<!-- 星期 -->
|
||||
<view class="grid grid-cols-7 gap-1 mb-2">
|
||||
<view v-for="day in ['日', '一', '二', '三', '四', '五', '六']" :key="day" class="text-center text-sm text-[#9C032D] font-medium">
|
||||
{{ day }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日期格子 -->
|
||||
<view class="grid grid-cols-7 gap-1">
|
||||
<view
|
||||
v-for="(date, index) in calendarDays"
|
||||
:key="index"
|
||||
@click="selectDate(date)"
|
||||
:class="[
|
||||
'text-center p-1 rounded cursor-pointer transition-colors',
|
||||
date.month() !== currentDate.month() ? 'text-gray-400' : 'text-[#9C032D]',
|
||||
date.isSame(selectedDate, 'day') ? 'bg-[#9C032D] text-white' : 'hover:bg-[#f5b5ba]',
|
||||
]"
|
||||
>
|
||||
{{ date.date() }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 确认按钮 -->
|
||||
<button @click="confirmDate" class="w-full mt-4 py-2 bg-[#9C032D] text-white rounded-lg hover:bg-[#80022a] transition-colors">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: String, // 接收外部v-model
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'confirm']);
|
||||
|
||||
const showCalendar = ref(false);
|
||||
const currentDate = ref(dayjs());
|
||||
const selectedDate = ref(props.modelValue ? dayjs(props.modelValue) : null);
|
||||
|
||||
// 生成日历日期数组
|
||||
const calendarDays = computed(() => {
|
||||
const start = currentDate.value.startOf('month').startOf('week');
|
||||
const end = currentDate.value.endOf('month').endOf('week');
|
||||
const days = [];
|
||||
let day = start;
|
||||
|
||||
while (day <= end) {
|
||||
days.push(day);
|
||||
day = day.add(1, 'day');
|
||||
}
|
||||
return days;
|
||||
});
|
||||
|
||||
// 切换月份
|
||||
const changeMonth = (delta) => {
|
||||
currentDate.value = currentDate.value.add(delta, 'month');
|
||||
};
|
||||
|
||||
// 选择日期
|
||||
const selectDate = (date) => {
|
||||
selectedDate.value = date;
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const confirmDate = () => {
|
||||
showCalendar.value = false;
|
||||
emit('update:modelValue', selectedDate.value.format('YYYY-MM-DD'));
|
||||
emit('confirm', selectedDate.value.format('YYYY-MM-DD'));
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,214 @@
|
||||
<script setup>
|
||||
/**
|
||||
* @desc 通用布局组件
|
||||
*/
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
// import LoginPopup from './login-popup.vue';
|
||||
import { getSystemSafeArea, getSafeTopHeight, getSystemInfo, isEmpty } from '@/common/common.js';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { useSystemInfo } from '@/hooks/useSystemInfo.js';
|
||||
const { navHeight } = useSystemInfo();
|
||||
const props = defineProps({
|
||||
showBgImage: {
|
||||
// 是否展示导航栏
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
showNav: {
|
||||
// 是否展示导航栏
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
navTitle: {
|
||||
//标题
|
||||
type: String,
|
||||
default: () => {
|
||||
return '';
|
||||
},
|
||||
},
|
||||
// navPlaceholder: {
|
||||
// //是否生成一个等高元素,防止塌陷
|
||||
// type: Boolean,
|
||||
// default: () => {
|
||||
// return false;
|
||||
// },
|
||||
// },
|
||||
navShowReturn: {
|
||||
//是否显示返回图标
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
navShowNavBar: {
|
||||
//是否显示更多图标
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
navColor: {
|
||||
//标题背景颜色
|
||||
type: String,
|
||||
default: () => {
|
||||
return '#fff';
|
||||
},
|
||||
},
|
||||
bgColor: {
|
||||
//标题字体、icon颜色
|
||||
type: String,
|
||||
default: () => {
|
||||
return '#fbfbfd';
|
||||
},
|
||||
},
|
||||
titleColor: {
|
||||
//标题字体、icon颜色
|
||||
type: String,
|
||||
default: () => {
|
||||
return '#1D1D1F';
|
||||
},
|
||||
},
|
||||
titleText: {
|
||||
// 顶部标题文字
|
||||
type: String,
|
||||
default: () => {
|
||||
return '';
|
||||
},
|
||||
},
|
||||
// showLogin: {
|
||||
// // 唤起登录
|
||||
// type: Boolean,
|
||||
// default: () => {
|
||||
// return false;
|
||||
// },
|
||||
// },
|
||||
});
|
||||
const emit = defineEmits(['getContentHeight', 'loginStatus']);
|
||||
const safeAreaInsetTop = ref(false); //是否开启顶部安全区适配
|
||||
const viewHeight = ref(0); //可用高度
|
||||
const pageHeight = ref(0); //页面高度
|
||||
const pageWidth = ref(getSystemInfo().windowWidth);
|
||||
const hasTabbar = ref(false);
|
||||
const user = useUserStore();
|
||||
const navPlaceholder = ref(false);
|
||||
// #ifndef MP-WEIXIN
|
||||
navPlaceholder.value = false;
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
navPlaceholder.value = true;
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* @desc 返回上一页
|
||||
*/
|
||||
const returnBack = () => {
|
||||
uni.navigateBack(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 返回内容部分的高度
|
||||
*/
|
||||
const contentHeight = () => {
|
||||
emit('getContentHeight', {
|
||||
viewHeight: viewHeight.value,
|
||||
pageHeight: pageHeight.value,
|
||||
});
|
||||
};
|
||||
|
||||
const showSideNav = ref(false);
|
||||
// 关闭/打开侧边弹出层
|
||||
const operaSideNav = () => {
|
||||
showSideNav.value = !showSideNav.value;
|
||||
};
|
||||
|
||||
// 关闭登录弹窗
|
||||
// const onCloseLogin = () => {
|
||||
// const status = isEmpty(user.userToken); // true 为空,false为已登录
|
||||
// emit('loginStatus', status);
|
||||
// };
|
||||
|
||||
onMounted(() => {
|
||||
// #ifndef MP-ALIPAY
|
||||
safeAreaInsetTop.value = true;
|
||||
// #endif
|
||||
uni.showTabBar({
|
||||
success() {
|
||||
hasTabbar.value = true;
|
||||
},
|
||||
fail() {
|
||||
hasTabbar.value = false;
|
||||
},
|
||||
complete() {
|
||||
pageHeight.value = getSystemSafeArea();
|
||||
if (props.showNav) {
|
||||
viewHeight.value = pageHeight.value - getSafeTopHeight();
|
||||
} else {
|
||||
viewHeight.value = pageHeight.value;
|
||||
}
|
||||
contentHeight();
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="absolute left-[50%] right-0 top-0 translate-x-[-50%]" :style="{ height: `${pageHeight}px`, width: `${pageWidth}px` }">
|
||||
<up-navbar
|
||||
:title="props?.navTitle"
|
||||
:safeAreaInsetTop="safeAreaInsetTop"
|
||||
:placeholder="navPlaceholder"
|
||||
:bgColor="props.navColor"
|
||||
:titleStyle="{ color: props.titleColor }"
|
||||
:height="navPlaceholder ? '44px' : `${getSafeTopHeight()}px`"
|
||||
class="!flex-1"
|
||||
v-if="props.showNav"
|
||||
>
|
||||
<template #left>
|
||||
<view class="flex flex-row items-center gap-[10px]">
|
||||
<up-icon name="list-dot" :color="props.titleColor" size="26" @click="operaSideNav" v-if="props.navShowNavBar"></up-icon>
|
||||
<!-- #ifndef MP-BAIDU -->
|
||||
<up-icon name="arrow-left" :color="props.titleColor" size="24" @click="returnBack" v-if="props.navShowReturn"></up-icon>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
</up-navbar>
|
||||
<view
|
||||
class="relative left-0 right-0 top-0"
|
||||
:style="{
|
||||
height: `${viewHeight}px`,
|
||||
background: props.bgColor,
|
||||
top: navPlaceholder ? 0 : `${getSafeTopHeight()}px`,
|
||||
}"
|
||||
>
|
||||
<view v-if="showBgImage" class="w-full relative">
|
||||
<view
|
||||
class="w-full h-[calc(200px+var(--status-bar-height))] bg-cover bg-center bg-no-repeat"
|
||||
style="background-image: url('/static/test/top2.png')"
|
||||
></view>
|
||||
<text class="w-full text-[#fff] font-bold text-[20px] absolute left-0 right-0 bottom-[40px] text-center px-[40px]">
|
||||
{{ props.titleText }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="absolute left-0 right-0 w-full overflow-y-auto"
|
||||
:style="{
|
||||
top: showBgImage ? 'calc(200px + var(--status-bar-height))' : props.showNav ? 0 : `${navHeight}px`,
|
||||
height: showBgImage ? `calc(100% - 200px - var(--status-bar-height))` : '100%',
|
||||
}"
|
||||
>
|
||||
<slot name="content" :row="{ viewHeight, pageHeight }"> </slot>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 登录 -->
|
||||
<!-- <LoginPopup :show="props.showLogin" @close="onCloseLogin"></LoginPopup> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<up-popup :show="showPicker" :closeable="true" round="10" mode="bottom" @close="onClose">
|
||||
<view class="login">
|
||||
<view class="logo mb-[10rpx] mt-[50rpx] flex justify-center text-center">
|
||||
<image class="h-[140rpx] rounded-full" src="/static/common/logo.png" mode="heightFix"></image>
|
||||
</view>
|
||||
<Login @close="onClose"></Login>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import Login from './login.vue';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
// 展示狀態
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const onClose = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const showPicker = ref(false);
|
||||
watch(
|
||||
() => props.show,
|
||||
(newVal) => {
|
||||
showPicker.value = newVal;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,855 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { areaCodeOptions, checkTelPhone, checkEmail, createV, showLoadingToast, closeToast, showToast, isEmpty } from '@/common/common.js';
|
||||
import { getLoginCode } from '@/common/login.js';
|
||||
import { login, register, sendCode, policyServer, loginLRInit } from '@/api/user.js';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import CommonPicker from './common-picker.vue';
|
||||
const { t, locale } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const isLogin = ref(true); // 是否登录,默认登录,为false表示注册
|
||||
import { uniLogin } from '@/common/login';
|
||||
const isMobile = ref(true); //是否手机号登录
|
||||
const isPassword = ref(false); //是否密码登录
|
||||
const isForgetPassword = ref(false); //是否忘记密码
|
||||
// const isDisable = ref(true); //是否登录注册按钮是否禁用,即是否通过校验
|
||||
const mobile = ref(''); // 手机号
|
||||
const areaCode = ref(''); // 地区码
|
||||
const code = ref(''); // 验证码
|
||||
const password = ref(''); // 密码
|
||||
const passwordConfirm = ref(''); // 确认密码
|
||||
const email = ref(''); // 邮箱
|
||||
const emit = defineEmits(['close']);
|
||||
const wxJsCode = ref({}); //wx授权码code
|
||||
const codeType = ref(''); // 区分code信息类型:=mpLogin(小程序中手机号+验证码登录);=mpMobile(小程序中授权获取手机号登录)
|
||||
|
||||
//同意条款相关
|
||||
const checked = ref(false);
|
||||
const onChange = (e) => {
|
||||
checked.value = !checked.value;
|
||||
};
|
||||
|
||||
//地区码弹框相关
|
||||
const columns = ref([]);
|
||||
const showCommonPicker = ref(false);
|
||||
const onConfirmCommonPicker = (data) => {
|
||||
areaCode.value = data[0].value;
|
||||
showCommonPicker.value = false;
|
||||
};
|
||||
const onCloseCommonPicker = () => {
|
||||
showCommonPicker.value = false;
|
||||
};
|
||||
|
||||
//导航手机号点击功能
|
||||
const onMobile = () => {
|
||||
isMobile.value = true;
|
||||
password.value = '';
|
||||
passwordConfirm.value = '';
|
||||
// isDisable.value = true;
|
||||
// console.log(isMobile.value);
|
||||
};
|
||||
//导航邮箱点击功能
|
||||
const onEmail = () => {
|
||||
isMobile.value = false;
|
||||
isPassword.value = true;
|
||||
password.value = '';
|
||||
passwordConfirm.value = '';
|
||||
// isDisable.value = true;
|
||||
};
|
||||
//去登录
|
||||
const toLogin = () => {
|
||||
isLogin.value = true;
|
||||
isMobile.value = true;
|
||||
isPassword.value = true;
|
||||
mobile.value = '';
|
||||
areaCode.value = '852';
|
||||
code.value = '';
|
||||
password.value = '';
|
||||
passwordConfirm.value = '';
|
||||
email.value = '';
|
||||
clearInterval(timerId2.value);
|
||||
};
|
||||
//去注册
|
||||
const toRegister = () => {
|
||||
isLogin.value = false;
|
||||
isMobile.value = true;
|
||||
isPassword.value = true;
|
||||
mobile.value = '';
|
||||
areaCode.value = '852';
|
||||
code.value = '';
|
||||
password.value = '';
|
||||
passwordConfirm.value = '';
|
||||
email.value = '';
|
||||
clearInterval(timerId1.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 登录
|
||||
*/
|
||||
const onLogin = () => {
|
||||
if (!checked.value) {
|
||||
uni.showToast({
|
||||
title: t('policyTips'),
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
//登录参数
|
||||
let params = {};
|
||||
|
||||
//小程序免验证登录,codeType.value = mpMobile时,表示本次登录为免验证登录,noVerify值为1
|
||||
if (codeType.value == 'mpMobile') {
|
||||
params.codeValue = JSON.stringify(wxJsCode.value); //微信端需要传递登录授权code
|
||||
params.noVerify = 1; // 免验证登录必传, codeType.value = mpMobile 时,为1表示本次登录为免验证登录
|
||||
} else {
|
||||
//手机号+验证码登录
|
||||
params.codeType = userStore.platform == 'h5' ? '' : codeType.value; // 小程序有效,仅在短信验证码登录时有值 mpLogin
|
||||
params.loginType = userStore.platform == 'h5' ? 'mobile' : ''; // h5环境下短信验证码登录必须要
|
||||
params.areaCode = isMobile.value ? areaCode.value : ''; // 区号
|
||||
params.mobile = isMobile.value ? mobile.value : ''; // 手机号
|
||||
params.verifyCode = code.value; // 验证码
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
params.codeValue = JSON.stringify(wxJsCode.value); //微信端需要传递登录授权code
|
||||
// #endif
|
||||
|
||||
//登录前的验证
|
||||
if (!LoginCheck()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(params);
|
||||
|
||||
try {
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
});
|
||||
login(params).then((res) => {
|
||||
console.log(res);
|
||||
uni.hideLoading();
|
||||
if (res.success) {
|
||||
userStore.userToken = res.data.token;
|
||||
userStore.isLogin = res.data.isLogin;
|
||||
userStore.setProfile({
|
||||
...res.data.userData,
|
||||
});
|
||||
emit('close');
|
||||
mobile.value = '';
|
||||
password.value = '';
|
||||
code.value = '';
|
||||
email.value = '';
|
||||
} else {
|
||||
//重新获取code
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
wxGetLoginCode();
|
||||
// #endif
|
||||
uni.showToast({
|
||||
title: res.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
//重新获取code
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
wxGetLoginCode();
|
||||
// #endif
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
//登录校验
|
||||
const LoginCheck = (val) => {
|
||||
if (isMobile.value) {
|
||||
//手机号登录
|
||||
if (!checkTelPhone(areaCode.value, mobile.value)) {
|
||||
uni.showToast({
|
||||
title: t('personal.mobileTips'),
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (isPassword.value) {
|
||||
//密码登录
|
||||
if (password.value == '' || password.value == null) {
|
||||
uni.showToast({
|
||||
title: '请输入密码',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
//验证码登录
|
||||
if (code.value == '' || code.value == null) {
|
||||
uni.showToast({
|
||||
title: t('personal.validatePlaceholder'),
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//邮箱登录
|
||||
if (!checkEmail(email.value)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的电子邮箱',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (password.value == '' || password.value == null) {
|
||||
uni.showToast({
|
||||
title: '请输入密码',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
//注册
|
||||
const onRegister = () => {
|
||||
if (!checked.value) {
|
||||
uni.showToast({
|
||||
title: t('policyTips'),
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let params = {
|
||||
regType: isMobile.value ? '1' : '2', //1手機號 2郵箱
|
||||
areaCode: isMobile.value ? areaCode.value : '',
|
||||
mobile: isMobile.value ? mobile.value : '',
|
||||
password: isPassword.value ? password.value : '',
|
||||
passwordConfirm: isPassword.value ? passwordConfirm.value : '',
|
||||
code: isMobile.value ? code.value : '',
|
||||
email: !isMobile.value ? email.value : '',
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
wxJsCode: wxJsCode.value, //微信端需要传递登录授权code
|
||||
// #endif
|
||||
};
|
||||
console.log(params);
|
||||
if (!RegisterCheck()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
});
|
||||
//注册api
|
||||
register(params).then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.success) {
|
||||
userStore.userToken = res.data.token;
|
||||
userStore.setProfile({
|
||||
...res.data.user,
|
||||
});
|
||||
emit('close');
|
||||
|
||||
mobile.value = '';
|
||||
password.value = '';
|
||||
passwordConfirm.value = '';
|
||||
code.value = '';
|
||||
email.value = '';
|
||||
} else {
|
||||
//重新获取code
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
wxGetLoginCode();
|
||||
// #endif
|
||||
uni.showToast({
|
||||
title: res.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
//重新获取code
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
wxGetLoginCode();
|
||||
// #endif
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
//注册校验
|
||||
const RegisterCheck = (val) => {
|
||||
if (isMobile.value) {
|
||||
//手机号注册
|
||||
if (!checkTelPhone(areaCode.value, mobile.value)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的地区号和手机号',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (code.value == '' || code.value == null) {
|
||||
uni.showToast({
|
||||
title: '请输入验证码',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
//邮箱注册
|
||||
if (!checkEmail(email.value)) {
|
||||
uni.showToast({
|
||||
title: '请输入正确的电子邮箱',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (password.value == '' || password.value == null) {
|
||||
uni.showToast({
|
||||
title: '请输入密码',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
if (password.value?.length < 6 || password.value?.length > 20) {
|
||||
uni.showToast({
|
||||
title: '密碼長度應為6-20位',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (passwordConfirm.value == '' || passwordConfirm.value == null) {
|
||||
uni.showToast({
|
||||
title: '請再次輸入密碼',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
if (password.value != passwordConfirm.value) {
|
||||
uni.showToast({
|
||||
title: '兩次密碼不一樣',
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const dataV = ref(''); //加密数据
|
||||
const isCanWxLogin = ref(false); // 允许国内公司快速获取手机号登录
|
||||
/**
|
||||
* 初始化,获取data.v
|
||||
*/
|
||||
const initMobile = async () => {
|
||||
try {
|
||||
await loginLRInit().then((res) => {
|
||||
if (res.success) {
|
||||
areaCode.value = res.data.areaCodeList[0].value;
|
||||
isCanWxLogin.value = res.data.isAllowMpMobile; // 允许国内公司快速获取手机号登录
|
||||
res.data.areaCodeList.forEach((item, index) => {
|
||||
columns.value[index] = {
|
||||
value: item.value,
|
||||
name: item.label,
|
||||
};
|
||||
});
|
||||
dataV.value = res.data.v;
|
||||
console.log(dataV.value);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const timerId1 = ref(null); // 定时器1(登录)
|
||||
const timerId2 = ref(null); // 定时器2(注册)
|
||||
const countDown1 = ref(60); // 倒计时1(登录)
|
||||
const allowSendCode1 = ref(true); // 允许发送验证码1(登录)
|
||||
const countDown2 = ref(60); // 倒计时2(注册)
|
||||
const allowSendCode2 = ref(true); // 允许发送验证码2(注册)
|
||||
//获取验证码
|
||||
const getValifyCode = () => {
|
||||
//手机号注册
|
||||
if (!checkTelPhone(areaCode.value, mobile.value)) {
|
||||
uni.showToast({
|
||||
title: t('personal.mobileTips'),
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
let account = areaCode.value + ',' + mobile.value;
|
||||
//加密data.v
|
||||
let postV;
|
||||
if (isLogin.value) {
|
||||
postV = createV(dataV.value, account);
|
||||
} else {
|
||||
postV = createV(dataV.value, account);
|
||||
}
|
||||
|
||||
let params = {
|
||||
// account: isMobile.value ? account : '', //1手機號 2郵箱
|
||||
// type: isMobile.value ? '1' : '2', //類型:1 手機號 2 郵箱
|
||||
// purpose: !isLogin.value ? '1' : !isForgetPassword.value ? '2' : '3', //目的:1註冊 2登錄 3忘記密碼
|
||||
areaCode: areaCode.value,
|
||||
mobile: mobile.value,
|
||||
v: postV, //加密数据
|
||||
};
|
||||
try {
|
||||
// console.log(params);
|
||||
uni.showLoading({
|
||||
mask: true,
|
||||
});
|
||||
sendCode(params).then((res) => {
|
||||
uni.hideLoading();
|
||||
if (res.success) {
|
||||
uni.showToast({
|
||||
title: t('personal.resendSuccess'),
|
||||
icon: 'none',
|
||||
});
|
||||
|
||||
// #ifdef MP
|
||||
codeType.value = 'mpLogin';
|
||||
// #endif
|
||||
|
||||
if (isLogin.value) {
|
||||
allowSendCode1.value = false;
|
||||
timerId1.value = setInterval(() => {
|
||||
countDown1.value--;
|
||||
if (countDown1.value == 0) {
|
||||
countDown1.value = 60;
|
||||
allowSendCode1.value = true;
|
||||
clearInterval(timerId1.value);
|
||||
// console.log(timerId1.value);
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
allowSendCode2.value = false;
|
||||
timerId2.value = setInterval(() => {
|
||||
countDown2.value--;
|
||||
if (countDown2.value == 0) {
|
||||
countDown2.value = 60;
|
||||
allowSendCode2.value = true;
|
||||
clearInterval(timerId2.value);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
};
|
||||
//忘记密码
|
||||
const toForgetPassword = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/forgetPassword',
|
||||
});
|
||||
};
|
||||
|
||||
// 协议弹框相关
|
||||
const noticeData = ref({}); //协议数据
|
||||
const showAgreementPicker = ref(false);
|
||||
const onShowAgreementPicker = () => {
|
||||
showAgreementPicker.value = true;
|
||||
};
|
||||
const onCancelAgreementPicker = () => {
|
||||
showAgreementPicker.value = false;
|
||||
};
|
||||
const getPolicyServer = async () => {
|
||||
try {
|
||||
await policyServer({
|
||||
type: '',
|
||||
}).then((res) => {
|
||||
console.log(res);
|
||||
if (res.success) {
|
||||
noticeData.value = res.data?.content;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @desc 获取微信登录授权码
|
||||
*/
|
||||
const wxGetLoginCode = async () => {
|
||||
await getLoginCode().then((res) => {
|
||||
wxJsCode.value = res;
|
||||
console.log(wxJsCode.value, '新获取的code');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 免验证登录
|
||||
*/
|
||||
const quickLogin = () => {
|
||||
getLoginCode().then((res) => {
|
||||
// #ifdef MP
|
||||
codeType.value = 'mpMobile';
|
||||
// #endif
|
||||
wxJsCode.value = res;
|
||||
onLogin();
|
||||
});
|
||||
};
|
||||
|
||||
// 获取用户手机号码
|
||||
const onGetphonenumber = (ev) => {
|
||||
uni.$u.debounce(async () => {
|
||||
if (ev.detail.errMsg.includes('fail')) return;
|
||||
if (!checked.value) {
|
||||
uni.showToast({
|
||||
title: t('policyTips'),
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: t('loading'),
|
||||
mask: true,
|
||||
});
|
||||
const flag = await uniLogin(ev);
|
||||
if (!flag) {
|
||||
uni.showToast({
|
||||
title: '登录失败',
|
||||
icon: 'none',
|
||||
});
|
||||
} else {
|
||||
emit('close');
|
||||
console.log({
|
||||
flag,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log({
|
||||
err,
|
||||
});
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleChecked = () => {
|
||||
if (!checked.value) {
|
||||
uni.showToast({
|
||||
title: t('policyTips'),
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await initMobile();
|
||||
// await getPolicyServer();
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
await wxGetLoginCode();
|
||||
// #endif
|
||||
});
|
||||
onUnmounted(() => {
|
||||
clearInterval(timerId1.value);
|
||||
clearInterval(timerId2.value);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<!-- <view class="login-type flex text-[34rpx] text-theme">
|
||||
<view class="mobile-type flex-1 font-medium border-bottom" @tap="onMobile" :class="{ border-bottom-theme: isMobile }">{{ isLogin ? '手機號碼登入' : '手機號碼註冊' }}</view>
|
||||
<view class="email-type flex-1 font-medium border-bottom" @tap="onEmail" :class="{ border-bottom-theme: !isMobile }">{{ isLogin ? '電郵登入' : '電郵註冊' }}</view>
|
||||
</view> -->
|
||||
<!-- 微信快捷登录 -->
|
||||
<view v-if="isLogin && isCanWxLogin">
|
||||
<view class="login-box">
|
||||
<button
|
||||
v-if="!checked"
|
||||
class="up-reset-button bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
|
||||
@click="handleChecked"
|
||||
>
|
||||
{{ t('phoneQuickLogin') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="up-reset-button bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="onGetphonenumber"
|
||||
>
|
||||
{{ t('phoneQuickLogin') }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 登录 -->
|
||||
<view v-else-if="isLogin">
|
||||
<view class="mobile" v-if="isMobile">
|
||||
<view class="label text-[26rpx]">{{ t('personal.mobile') }}</view>
|
||||
<view class="mobile-text flex items-center border-round-theme">
|
||||
<view class="areaCode flex items-center">
|
||||
<view class="text-[30rpx]" @tap="showCommonPicker = true">+{{ areaCode }}</view>
|
||||
<view class="down-icon">
|
||||
<up-icon name="arrow-down-fill" size="8" color></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="email" v-else>
|
||||
<view class="label text-[26rpx]">{{ t('personal.email') }}</view>
|
||||
<view class="email-text flex items-center border-round-theme">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="password" v-if="isPassword">
|
||||
<view class="label text-[26rpx]">{{ t('personal.password') }}</view>
|
||||
<view class="password-text flex items-center border-round-theme">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.passwordPlaceholder')" v-model="password" type="password" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.passwordPlaceholder')" v-model="password" type="password" border="none" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="code" v-else>
|
||||
<view class="label text-[26rpx]">{{ t('personal.validateCode') }}</view>
|
||||
<view class="code-text flex items-center border-round-theme">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none" />
|
||||
<!-- #endif -->
|
||||
<view v-if="allowSendCode1" class="whitespace-nowrap text-[28rpx] font-medium text-theme" @tap="getValifyCode">{{
|
||||
t('personal.getValidateCode')
|
||||
}}</view>
|
||||
<view v-else class="whitespace-nowrap text-[28rpx] font-medium text-[rgb(131,131,131)]"> {{ countDown1 }}s{{ t('personal.resend') }} </view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 免验证登录 -->
|
||||
<!-- #ifdef MP -->
|
||||
<view class="flex flex-row items-center justify-center flex-1" @click="quickLogin">
|
||||
<text class="text-[15px]">{{ t('quickLogin') }}</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- <view class="text-right text-[26rpx] mx-[30rpx] text-gray-600" @tap="toForgetPassword">忘記密碼?</view> -->
|
||||
<view class="bg-theme rounded-full mx-[30rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white" @tap="onLogin">{{
|
||||
t('login')
|
||||
}}</view>
|
||||
|
||||
<!-- <view class="flex mx-[30rpx] items-center justify-between text-[28rpx]">
|
||||
<view>沒有賬號?點挈 <text class="text-theme" @tap="toRegister">註冊</text></view>
|
||||
<view v-if="isMobile" class="text-theme" @tap="isPassword = !isPassword">{{ !isPassword ? '密碼登入' : '验证码登入' }}</view>
|
||||
</view> -->
|
||||
</view>
|
||||
<!-- 注册 -->
|
||||
<view v-else>
|
||||
<view class="mobile" v-if="isMobile">
|
||||
<view class="label text-[26rpx]">{{ t('personal.mobile') }}</view>
|
||||
<view class="mobile-text flex items-center border-round-theme">
|
||||
<view class="areaCode flex items-center">
|
||||
<view class="text-[30rpx]" @tap="showCommonPicker = true">+{{ areaCode }}</view>
|
||||
<view class="down-icon">
|
||||
<up-icon name="arrow-down-fill" size="8" color></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.mobilePlaceholder')" v-model="mobile" type="number" border="none" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="email" v-else>
|
||||
<view class="label text-[26rpx]">{{ t('personal.email') }}</view>
|
||||
<view class="email-text flex items-center border-round-theme">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.emailPlaceholder')" v-model="email" border="none" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="code" v-if="isMobile">
|
||||
<view class="label text-[26rpx]">{{ t('personal.validateCode') }}</view>
|
||||
<view class="code-text flex items-center border-round-theme">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" placeholder="t('personal.validatePlaceholder')" v-model="code" type="number" border="none" />
|
||||
<!-- #endif -->
|
||||
<view v-if="allowSendCode2" class="whitespace-nowrap text-[28rpx] font-medium text-theme" @tap="getValifyCode">{{
|
||||
t('personal.getValidateCode')
|
||||
}}</view>
|
||||
<view v-else class="whitespace-nowrap text-[28rpx] font-medium text-[rgb(131,131,131)]">
|
||||
{{ `${countDown2}s ${t('personal.resend')}` }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="password">
|
||||
<view class="label text-[26rpx]">{{ t('personal.password') }}</view>
|
||||
<view class="password-text flex items-center border-round-theme">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.setPassword')" v-model="password" type="password" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.setPassword')" v-model="password" type="password" border="none" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="password">
|
||||
<view class="label text-[26rpx]">{{ t('personal.confirmPassword') }}</view>
|
||||
<view class="password-text flex items-center border-round-theme">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<up-input :placeholder="t('personal.passwordAgain')" v-model="passwordConfirm" type="password" border="none"></up-input>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<input class="flex-1" :placeholder="t('personal.passwordAgain')" v-model="passwordConfirm" type="password" border="none" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<!-- :class="{ disable: isDisable }" -->
|
||||
<view
|
||||
class="bg-theme rounded-full mx-[30rpx] mt-[15rpx] my-[40rpx] flex h-[92rpx] items-center justify-center text-[34rpx] font-medium text-white"
|
||||
@tap="onRegister"
|
||||
>{{ t('regLogin') }}</view
|
||||
>
|
||||
<view class="flex mx-[30rpx] mb-[40rpx] items-center justify-between text-[28rpx]">
|
||||
<view
|
||||
>{{ t('hasAccount') }} <text class="text-theme" @tap="toLogin">{{ t('login') }}</text></view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-center my-[40rpx] text-[28rpx]">
|
||||
<checkbox-group @change="onChange">
|
||||
<checkbox class="checkbox" :checked="checked" color="#00A88E"> </checkbox>
|
||||
{{ t('policyNote') }} <text class="text-theme font-medium" @tap="onShowAgreementPicker">{{ t('policy') }}</text>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
<!-- 地区号 -->
|
||||
<CommonPicker
|
||||
:title="t('personal.selectAreaCode')"
|
||||
:show="showCommonPicker"
|
||||
:columns="columns"
|
||||
@confirm="onConfirmCommonPicker"
|
||||
@close="onCloseCommonPicker"
|
||||
></CommonPicker>
|
||||
<!-- 用户协议弹框 -->
|
||||
<up-popup :show="showAgreementPicker" :closeable="true" round="10" mode="bottom" @close="onCancelAgreementPicker">
|
||||
<view class="h-[70vh] flex flex-col px-[30rpx] pb-[30rpx] text-[28rpx]">
|
||||
<view class="py-[30rpx] text-center text-[36rpx]">
|
||||
{{ t('policy') }}
|
||||
</view>
|
||||
<view class="flex-1 overflow-y-scroll">
|
||||
<up-parse :content="noticeData"></up-parse>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.border-bottom-theme .login-type {
|
||||
height: 88rpx;
|
||||
margin: 30rpx;
|
||||
|
||||
.mobile-type,
|
||||
.email-type {
|
||||
text-align: center;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile,
|
||||
.email,
|
||||
.password,
|
||||
.code {
|
||||
margin: 30rpx;
|
||||
|
||||
.label {
|
||||
font-size: 26rpx;
|
||||
padding-left: 40rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.mobile-text,
|
||||
.email-text,
|
||||
.password-text,
|
||||
.code-text {
|
||||
// margin: 0 15px;
|
||||
height: 100rpx;
|
||||
padding: 0 40rpx;
|
||||
border-radius: 100rpx;
|
||||
|
||||
.van-field {
|
||||
background-color: #ffffff !important;
|
||||
padding: 0 10rpx !important;
|
||||
}
|
||||
|
||||
.areaCode {
|
||||
margin-right: 17rpx;
|
||||
|
||||
.down-icon {
|
||||
width: 40rpx;
|
||||
padding-left: 10rpx;
|
||||
}
|
||||
|
||||
border-right: 2rpx solid #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disable {
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,135 @@
|
||||
<script setup>
|
||||
import {
|
||||
getPredictTime
|
||||
} from '@/pages/booking/api.js';
|
||||
import {
|
||||
onMounted,
|
||||
ref,
|
||||
reactive,
|
||||
watch
|
||||
} from 'vue';
|
||||
import {
|
||||
useI18n
|
||||
} from 'vue-i18n';
|
||||
import {
|
||||
getNowDate
|
||||
} from '@/common/common';
|
||||
import {
|
||||
isEmpty,
|
||||
showToast
|
||||
} from '../common/common';
|
||||
const {
|
||||
t,
|
||||
locale
|
||||
} = useI18n();
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
date: {
|
||||
type: String,
|
||||
default: getNowDate(),
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'confirm']);
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
const pckList = ref([]); // 套餐总数组
|
||||
const title = ref('');
|
||||
|
||||
/**
|
||||
* @desc 選擇套餐
|
||||
* @param res 選中的套餐
|
||||
*/
|
||||
const choiceValue = ref('');
|
||||
const choiceData = ref({});
|
||||
const choice = (res) => {
|
||||
if (!res.time.status) {
|
||||
return showToast(res.time.tips);
|
||||
}
|
||||
choiceData.value = {
|
||||
pckId: res.item.pckId,
|
||||
pckName: res.item.pckName,
|
||||
...res.time,
|
||||
};
|
||||
choiceValue.value = res.time.value;
|
||||
};
|
||||
|
||||
const confirm = () => {
|
||||
if (isEmpty(choiceData.value)) {
|
||||
return showToast(t('booking.useTimePlaceholder'));
|
||||
}
|
||||
emit('confirm', choiceData.value);
|
||||
close();
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 查询用餐时间
|
||||
*/
|
||||
const init = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getPredictTime({
|
||||
date: props.date,
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
pckList.value = res.data?.pckList;
|
||||
title.value = res.data?.title;
|
||||
resolve()
|
||||
} else {
|
||||
showToast(res.message)
|
||||
reject()
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const timeFun = reactive({
|
||||
init
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
timeFun,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<up-popup :show="props.show" mode="bottom" round="20" :closeable="true" :safeAreaInsetBottom="true" @close="close">
|
||||
<view class="flex flex-col gap-[20px] p-[20px] h-[70vh] relative">
|
||||
<view class="flex h-[44px] items-center justify-center text-[16px] text-[#1D1D1F]">{{ title }}</view>
|
||||
<view class="flex flex-col overflow-auto time-height gap-[20px]">
|
||||
<view class="flex flex-col gap-[20px]" v-for="item in pckList" :key="item.pckId">
|
||||
<view class="flex flex-col gap-[10px]">
|
||||
<view class="text-main text-[14px] font-bold">{{ item.pckName }}</view>
|
||||
<view class="gap-[20px] grid grid-cols-2">
|
||||
<view class="flex items-center justify-center h-[35px] rounded-[5px] text-[14px]"
|
||||
v-for="time in item.useTimeData" :key="time.value" :class="{
|
||||
'!bg-theme text-white': choiceValue === time.value,
|
||||
'bg-[#999]/[.5] text-white': !time.status,
|
||||
'bg-white text-main border-round-theme': time.status,
|
||||
}" @click="choice({ item, time })">
|
||||
{{ time.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="my-[20px] flex absolute bottom-0 left-[20px] right-[20px]">
|
||||
<up-button class="h-[40px] flex-1" :text="t('confirm')" shape="circle" color="#B5966E"
|
||||
@click="confirm" />
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.time-height {
|
||||
height: calc(100% - 40px - 44px - 40px); // 按钮高度+title高度+内边距上下共40px
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,128 @@
|
||||
export default {
|
||||
theme: {
|
||||
colors: {
|
||||
/**
|
||||
* 项目主题颜色,背景色系、字体色系、边框色系均有用到
|
||||
* 用法: 背景色系(bg-theme)
|
||||
* 字体色系(text-theme)
|
||||
* 边框色系(border-theme)
|
||||
* 页面:首页/pages/index/index、产品列表页/pages/product/index、客服页/pages/customer/index、个人中心/pages/mine/index
|
||||
*/
|
||||
theme: '#00A88E',
|
||||
/**
|
||||
* 用法: 浅背景主题色(bg-theme-opacity)
|
||||
* 页面: 产品列表页/pages/product/index
|
||||
*/
|
||||
'theme-opacity': '#ECF8EA',
|
||||
tab: '#303030', //导航栏字体颜色
|
||||
main: '#333333', //黑色字体颜色
|
||||
white: '#fff', //白色字体
|
||||
/**
|
||||
* 用法: 备注类字体颜色(text-describe)
|
||||
* 页面: 产品列表页/pages/product/index
|
||||
*/
|
||||
describe: '#666666',
|
||||
/**
|
||||
* 用法: 价格字体颜色(text-price)
|
||||
* 页面: 订单详情页/pages/order/order-detail、订单列表页/pages/order/index
|
||||
*/
|
||||
price: '#D4493C',
|
||||
/**
|
||||
* 用法: 价格后缀字体颜色(text-price-after)
|
||||
* 页面: 产品详情页/pages/product/detail
|
||||
*/
|
||||
'price-after': '#7F8186',
|
||||
/**
|
||||
* 用法: 倒计时字体颜色(text-countdown)
|
||||
* 页面: 订单详情页/pages/order/order-detail、订单列表页/pages/order/index
|
||||
*/
|
||||
countdown: '#DB1D0C',
|
||||
/**
|
||||
* 用法: 未支付字体颜色(text-order-unpay)
|
||||
* 页面: 订单详情页/pages/order/order-detail
|
||||
*/
|
||||
'order-unpay': '#DB1D0C',
|
||||
/**
|
||||
* 用法: 未支付字体颜色(text-order-usable)
|
||||
* 页面: 订单详情页/pages/order/order-detail
|
||||
*/
|
||||
'order-usable': '#274C40',
|
||||
/**
|
||||
* 用法: 已完成字体颜色(text-order-finish)
|
||||
* 页面: 订单详情页/pages/order/order-detail
|
||||
*/
|
||||
'order-finish': '#666666',
|
||||
/**
|
||||
* 主要边框颜色(用的最多)
|
||||
* 用法:(border-bg-theme)
|
||||
* 页面:产品列表页/pages/product/index
|
||||
*/
|
||||
'bd-main': '#D1B043',
|
||||
/**
|
||||
* 产品详情页-tab栏背景色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
'product-detail-tabs-bg': '#f4f6f5',
|
||||
/**
|
||||
* 产品详情页-tab栏字体颜色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
'product-detail-tabs-text': '#252527',
|
||||
/**
|
||||
* 产品详情页-套餐背景色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
productPckBg: '#f9f9f9',
|
||||
/**
|
||||
* 評價tab背景色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
commentTabBg: '#eaedec',
|
||||
/**
|
||||
* 評價tab背景色-選中
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
commentTabBgActive: '#fff',
|
||||
/**
|
||||
* 評價tab字體顏色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
commentTabText: '#333333',
|
||||
/**
|
||||
* 評價tab字體顏色-選中
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
commentTabTextActive: '#274C40',
|
||||
/**
|
||||
* 預訂彈窗日期字體顏色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
bookingPopupDateText: '#222',
|
||||
/**
|
||||
* 預訂彈窗日期字體顏色-選中
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
bookingPopupDateTextActive: '#222',
|
||||
/**
|
||||
* 預訂彈窗日期邊框顏色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
bookingPopupDateBorderColor: 'rgba(39, 76, 64, 0.40)',
|
||||
/**
|
||||
* 預訂彈窗日期邊框顏色-選中
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
bookingPopupDateBorderColorActive: '#274C40',
|
||||
/**
|
||||
* 預訂彈窗日期背景顏色
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
bookingPopupDateBgColor: '#fff',
|
||||
/**
|
||||
* 預訂彈窗日期背景顏色-選中
|
||||
* 页面:产品详情页/pages/product/detail
|
||||
*/
|
||||
bookingPopupDateBgColorActive: '#eaedec',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { onLoad, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
|
||||
|
||||
export function useSharePage(getParams) {
|
||||
const info = uni.getSystemInfoSync();
|
||||
// 页面中仍需要导入onShareAppMessage
|
||||
onShareAppMessage((e) => {
|
||||
if (info.uniPlatform !== 'web' && info.uniPlatform !== 'app') {
|
||||
uni.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menu: ['shareAppMessage', 'shareTimeline'],
|
||||
});
|
||||
}
|
||||
const params = getParams || {}; // 每次分享时获取最新的参数
|
||||
const buttonParams = e.target?.dataset || {};
|
||||
return {
|
||||
title: '',
|
||||
...params,
|
||||
...buttonParams,
|
||||
};
|
||||
});
|
||||
// 页面中仍需要导入onShareTimeline
|
||||
onShareTimeline(() => {
|
||||
const params = getParams || {}; // 每次分享时获取最新的参数
|
||||
return {
|
||||
title: '',
|
||||
...params,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
getCurrentInstance
|
||||
} from 'vue'
|
||||
|
||||
export function useSystemInfo() {
|
||||
const {
|
||||
proxy
|
||||
} = getCurrentInstance()
|
||||
const systemInfo = ref({})
|
||||
const validHeight = ref(0)
|
||||
const navHeight = ref(0)
|
||||
|
||||
// 获取页面可用高度
|
||||
const getValidHeight = (info) => {
|
||||
const windowHeight = info.windowHeight
|
||||
// 在非tabbar页,ios端预留底部安全高度
|
||||
const safeBottom = info.osName === 'ios' && info.safeAreaInsets.bottom && info.uniPlatform !== 'web' && info
|
||||
.uniPlatform !== 'app' ? 20 : 0
|
||||
|
||||
return windowHeight - safeBottom
|
||||
}
|
||||
|
||||
|
||||
// 获取导航栏高度
|
||||
const getNavHeight = (info) => {
|
||||
let navHeight = 0
|
||||
if (info.uniPlatform === 'mp-weixin') {
|
||||
const custom = uni.getMenuButtonBoundingClientRect();
|
||||
navHeight = custom.height + (custom.top - info.statusBarHeight) * 2;
|
||||
} else if (info.uniPlatform === 'mp-alipay') {
|
||||
navHeight = info.titleBarHeight
|
||||
} else if (info.uniPlatform === 'mp-baidu') {
|
||||
navHeight = info.navigationBarHeight
|
||||
}
|
||||
return navHeight
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
systemInfo.value = uni.getSystemInfoSync()
|
||||
validHeight.value = getValidHeight(systemInfo.value)
|
||||
navHeight.value = getNavHeight(systemInfo.value)
|
||||
})
|
||||
|
||||
// 计算dom高度(需要在mounted生命周期后调用)
|
||||
const computedDomHeight = (selectors, selectType = 'select') => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let context = uni.createSelectorQuery().in(proxy)
|
||||
if (systemInfo.value.uniPlatform === 'mp-alipay') {
|
||||
context = uni.createSelectorQuery();
|
||||
}
|
||||
|
||||
let domHeight = 0;
|
||||
let domWidth = 0;
|
||||
let queriesCount = 0;
|
||||
let selectorQueries = [];
|
||||
|
||||
// 如果selectors是字符串,则将其转换为单元素数组
|
||||
if (typeof selectors === 'string') {
|
||||
selectors = [selectors];
|
||||
}
|
||||
if (!Array.isArray(selectors)) {
|
||||
console.error('selectors 必须是字符串或字符串数组')
|
||||
reject(false)
|
||||
return
|
||||
}
|
||||
|
||||
// 长度为0
|
||||
if (!selectors.length) {
|
||||
resolve(0)
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到执行上下文
|
||||
for (let selectorStr of selectors) {
|
||||
context[selectType](selectorStr).boundingClientRect();
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
context.exec(result => {
|
||||
// 累加高度
|
||||
result.forEach((cur) => {
|
||||
if (cur) {
|
||||
domHeight += cur.height;
|
||||
domWidth += cur.width;
|
||||
}
|
||||
});
|
||||
resolve({
|
||||
height: domHeight,
|
||||
width: domWidth,
|
||||
rect: result
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
systemInfo,
|
||||
validHeight,
|
||||
navHeight,
|
||||
computedDomHeight
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 判断是否为移动端
|
||||
* @returns {Boolean} true为移动端
|
||||
*/
|
||||
export function useUsingMobile() {
|
||||
const isUsingMobile = /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent);
|
||||
return isUsingMobile;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<!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>
|
||||
<link rel="icon" href="./static/favicon.ico" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/animate.css@3.5.2/animate.min.css" />
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
createI18n
|
||||
} from 'vue-i18n'
|
||||
|
||||
import zhCn from './zh-cn.js'
|
||||
import zhTw from './zh-tw.js'
|
||||
|
||||
|
||||
const messages = {
|
||||
'zh-CN': zhCn,
|
||||
'zh-TW': zhTw,
|
||||
}
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false, // 使用CompotitionAPI必须添加这条.
|
||||
globalInjection: true,
|
||||
locale: 'zh-TW',
|
||||
messages
|
||||
})
|
||||
|
||||
export default i18n
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
appName: '跳跳糖',
|
||||
|
||||
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
appName: 'sweekar',
|
||||
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import App from './App'
|
||||
import {
|
||||
nextTick
|
||||
} from "vue"
|
||||
// 全局注册layout组件
|
||||
import Layout from '@/components/layout.vue'
|
||||
// 引入pinia
|
||||
import pinia from "@/store/store.js"
|
||||
// 引入uvie-plus
|
||||
import uviewPlus from 'uview-plus'
|
||||
// 引入封装好的request
|
||||
import {
|
||||
request
|
||||
} from '@/utils/request.js';
|
||||
// 引入vue-i18n多语言翻译
|
||||
import i18n from '@/lang/index.js'
|
||||
// 引入animate.css 具体查看 https://www.dowebok.com/demo/2014/98/
|
||||
import 'animate.css';
|
||||
/* 非app断网提示 */
|
||||
// #ifndef APP-PLUS
|
||||
uni.onNetworkStatusChange((res) => {
|
||||
if (res.networkType === null || res.networkType === "unknown" || (!res.isConnected)) {
|
||||
uni.showModal({
|
||||
cancelText: i18n.global.t('modal.reStart'),
|
||||
confirmText: i18n.global.t('modal.reLoad'),
|
||||
content: i18n.global.t('modal.offNet'),
|
||||
title: i18n.global.t('modal.title'),
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
uni.navigateBack(0)
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// Vue2
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import './uni.promisify.adaptor'
|
||||
Vue.config.productionTip = false
|
||||
App.mpType = 'app'
|
||||
const app = new Vue({
|
||||
...App
|
||||
})
|
||||
app.$mount()
|
||||
// #endif
|
||||
|
||||
|
||||
// Vue3
|
||||
// #ifdef VUE3
|
||||
import {
|
||||
createSSRApp
|
||||
} from 'vue'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
app.component('Layout', Layout).use(uviewPlus).use(i18n).use(pinia)
|
||||
// 必须使用 nextTick,不然会有加载顺序问题,导致绑定失败
|
||||
nextTick(() => {
|
||||
// 代替 Vue.prototype.$xxx 绑定
|
||||
app.config.globalProperties.$request = request; // 自定义添加request
|
||||
// 全局异常拦截
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
console.log(err, instance, info);
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '❌ ' + err.message,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
})
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"name" : "sweekar-app",
|
||||
"appid" : "__UNI__AB3EDCE",
|
||||
"description" : "",
|
||||
"versionName" : "1.1.3",
|
||||
"versionCode" : 113,
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"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\"/>"
|
||||
],
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
|
||||
"minSdkVersion" : 21,
|
||||
"targetSdkVersion" : 34
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {
|
||||
"idfa" : false
|
||||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {}
|
||||
},
|
||||
"nvueLaunchMode" : ""
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"minified" : true
|
||||
},
|
||||
"usingComponents" : true,
|
||||
"lazyCodeLoading" : "requiredComponents"
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true,
|
||||
"appid" : "2021004158656274"
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "3",
|
||||
"fallbackLocale" : "zh-Hant",
|
||||
"locale" : "zh-Hant",
|
||||
"h5" : {
|
||||
"router" : {
|
||||
"mode" : "hash",
|
||||
"base" : "./"
|
||||
}
|
||||
},
|
||||
"proxy" : {
|
||||
"/sale" : {
|
||||
"target" : "http://110.v2-app.devcn.dabapiao.com",
|
||||
"changeOrigin" : true,
|
||||
"secure" : false,
|
||||
"pathRewrite" : {
|
||||
"^/sale" : "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"animate.css": "^4.1.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"pinyin-pro": "^3.26.0",
|
||||
"uview-plus": "^3.4.20",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.3.0-beta.25",
|
||||
"zipson": "^0.2.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.65.1",
|
||||
"sass-loader": "^10.4.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"weapp-tailwindcss": "^3.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "weapp-tw patch"
|
||||
},
|
||||
"name": "quick-mp-front",
|
||||
"version": "1.0.0",
|
||||
"main": "main.js",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": ""
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"easycom": {
|
||||
// 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175
|
||||
"custom": {
|
||||
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
|
||||
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
|
||||
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
// #ifdef MP-ALIPAY
|
||||
"enablePullDownRefresh": true
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"path": "pages/product/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "跳跳糖",
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"backgroundColor": "#F2F3F9"
|
||||
},
|
||||
"uniIdRouter": {},
|
||||
"tabBar": {
|
||||
"color": "#999999",
|
||||
"selectedColor": "#AB002D",
|
||||
"custom": true,
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "/static/tabbar/home.png",
|
||||
"selectedIconPath": "/static/tabbar/active-home.png"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"pagePath": "pages/order/index",
|
||||
"iconPath": "/static/tabbar/order.png",
|
||||
"selectedIconPath": "/static/tabbar/active-order.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mine/index",
|
||||
"iconPath": "/static/tabbar/mine.png",
|
||||
"selectedIconPath": "/static/tabbar/active-mine.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { get, post, put, dele } from '@/utils/request';
|
||||
/**
|
||||
* @desc 全局参数
|
||||
*/
|
||||
export const getConfig = (params) => {
|
||||
return get('/sale/tripcommon/index/config', params);
|
||||
};
|
||||
/**
|
||||
* @desc 获取产品列表
|
||||
*/
|
||||
export const getProducts = (params) => {
|
||||
return get('/api/products/', params)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, getCurrentInstance, nextTick } from 'vue';
|
||||
import { onLoad, onShow, onReady, onHide, onPullDownRefresh } from '@dcloudio/uni-app';
|
||||
|
||||
import { getSystemSafeArea, getSystemInfo, isEmpty, showToast, showLoadingToast, closeToast } from '@/common/common.js';
|
||||
import LoginPopup from '@/components/login-popup.vue';
|
||||
import { getProducts } from './api.js';
|
||||
import { useUserStore } from '@/store/user.js';
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const props = defineProps(['status']);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const tabList = ref([]); //tab列表
|
||||
const tabsIndex = ref(0); //当前选中的tab索引
|
||||
// const modelTypeList = ref([]);
|
||||
const tabsLeft = ref(0); //下划线移动的距离
|
||||
const finished = ref(false); //数据是否全部加载完成
|
||||
const windowWidth = ref(0); //tab元素的宽度
|
||||
const orderRows = ref([]); //订单列表
|
||||
const showLogin = ref(false); //显示登录
|
||||
const triggered = ref(false); //是否下拉刷新状态,true为是
|
||||
const loading = ref(false); // 添加loading状态
|
||||
const productData = ref();
|
||||
const scrollHeight = ref(0); //scroll-view滚动高度
|
||||
|
||||
const pageView = ref({});
|
||||
/**
|
||||
* @desc 获取可视内容高度
|
||||
*/
|
||||
const getContentHeight = (e) => {
|
||||
pageView.value = e;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @desc 跳转至详情页面
|
||||
*/
|
||||
const toDetailPage = (productId) => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/product/detail',
|
||||
});
|
||||
};
|
||||
//获取首页数据
|
||||
const getProductList = async()=>{
|
||||
const res = await getProducts();
|
||||
|
||||
productData.value=res.data;
|
||||
console.log("商品数据",productData.value);
|
||||
}
|
||||
|
||||
|
||||
onLoad(()=>{
|
||||
// getProductList();
|
||||
|
||||
});
|
||||
|
||||
// 模拟数据
|
||||
|
||||
|
||||
/**
|
||||
* @desc 处理预订事件
|
||||
*/
|
||||
const handleBook = (productId) => {
|
||||
// 这里可以添加预订逻辑,比如跳转到详情页
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/detail?id=${productId}`,
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-[140rpx]">hello</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.u-count-down__text) {
|
||||
color: #ab002d !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.navi-title-center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.line {
|
||||
transition: 0.3s all;
|
||||
}
|
||||
|
||||
.search {
|
||||
:deep(.u-search) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { get, post, put, dele } from '@/utils/request';
|
||||
/**
|
||||
* @desc 获取产品列表
|
||||
*/
|
||||
export const getProducts = (params) => {
|
||||
return get('/api/products/', params)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { get, post, put, dele } from '@/utils/request';
|
||||
/**
|
||||
* @desc 获取产品列表
|
||||
*/
|
||||
export const getProducts = (params) => {
|
||||
return get('/api/products/', params)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, getCurrentInstance, nextTick } from 'vue';
|
||||
import { onLoad, onShow, onReady, onHide, onPullDownRefresh } from '@dcloudio/uni-app';
|
||||
|
||||
import { getSystemSafeArea, getSystemInfo, isEmpty, showToast, showLoadingToast, closeToast } from '@/common/common.js';
|
||||
import LoginPopup from '@/components/login-popup.vue';
|
||||
import { getProducts } from './api.js';
|
||||
import { useUserStore } from '@/store/user.js';
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const props = defineProps(['status']);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const tabList = ref([]); //tab列表
|
||||
const tabsIndex = ref(0); //当前选中的tab索引
|
||||
// const modelTypeList = ref([]);
|
||||
const tabsLeft = ref(0); //下划线移动的距离
|
||||
const finished = ref(false); //数据是否全部加载完成
|
||||
const windowWidth = ref(0); //tab元素的宽度
|
||||
const orderRows = ref([]); //订单列表
|
||||
const showLogin = ref(false); //显示登录
|
||||
const triggered = ref(false); //是否下拉刷新状态,true为是
|
||||
const loading = ref(false); // 添加loading状态
|
||||
const productData = ref();
|
||||
const scrollHeight = ref(0); //scroll-view滚动高度
|
||||
|
||||
const pageView = ref({});
|
||||
/**
|
||||
* @desc 获取可视内容高度
|
||||
*/
|
||||
const getContentHeight = (e) => {
|
||||
pageView.value = e;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @desc 跳转至详情页面
|
||||
*/
|
||||
const toDetailPage = (productId) => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/product/detail',
|
||||
});
|
||||
};
|
||||
//获取首页数据
|
||||
const getProductList = async()=>{
|
||||
const res = await getProducts();
|
||||
|
||||
productData.value=res.data;
|
||||
console.log("商品数据",productData.value);
|
||||
}
|
||||
|
||||
|
||||
onLoad(()=>{
|
||||
// getProductList();
|
||||
|
||||
});
|
||||
|
||||
// 模拟数据
|
||||
|
||||
|
||||
/**
|
||||
* @desc 处理预订事件
|
||||
*/
|
||||
const handleBook = (productId) => {
|
||||
// 这里可以添加预订逻辑,比如跳转到详情页
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/detail?id=${productId}`,
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-[140rpx]">hello</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.u-count-down__text) {
|
||||
color: #ab002d !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.navi-title-center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.line {
|
||||
transition: 0.3s all;
|
||||
}
|
||||
|
||||
.search {
|
||||
:deep(.u-search) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { get, post, put, dele } from '@/utils/request';
|
||||
/**
|
||||
* @desc 获取产品列表
|
||||
*/
|
||||
export const getProducts = (params) => {
|
||||
return get('/api/products/', params)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, getCurrentInstance, nextTick } from 'vue';
|
||||
import { onLoad, onShow, onReady, onHide, onPullDownRefresh } from '@dcloudio/uni-app';
|
||||
|
||||
import { getSystemSafeArea, getSystemInfo, isEmpty, showToast, showLoadingToast, closeToast } from '@/common/common.js';
|
||||
import LoginPopup from '@/components/login-popup.vue';
|
||||
import { getProducts } from './api.js';
|
||||
import { useUserStore } from '@/store/user.js';
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const props = defineProps(['status']);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const tabList = ref([]); //tab列表
|
||||
const tabsIndex = ref(0); //当前选中的tab索引
|
||||
// const modelTypeList = ref([]);
|
||||
const tabsLeft = ref(0); //下划线移动的距离
|
||||
const finished = ref(false); //数据是否全部加载完成
|
||||
const windowWidth = ref(0); //tab元素的宽度
|
||||
const orderRows = ref([]); //订单列表
|
||||
const showLogin = ref(false); //显示登录
|
||||
const triggered = ref(false); //是否下拉刷新状态,true为是
|
||||
const loading = ref(false); // 添加loading状态
|
||||
const productData = ref();
|
||||
const scrollHeight = ref(0); //scroll-view滚动高度
|
||||
|
||||
const pageView = ref({});
|
||||
/**
|
||||
* @desc 获取可视内容高度
|
||||
*/
|
||||
const getContentHeight = (e) => {
|
||||
pageView.value = e;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @desc 跳转至详情页面
|
||||
*/
|
||||
const toDetailPage = (productId) => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/product/detail',
|
||||
});
|
||||
};
|
||||
//获取首页数据
|
||||
const getProductList = async()=>{
|
||||
const res = await getProducts();
|
||||
|
||||
productData.value=res.data;
|
||||
console.log("商品数据",productData.value);
|
||||
}
|
||||
|
||||
|
||||
onLoad(()=>{
|
||||
// getProductList();
|
||||
|
||||
});
|
||||
|
||||
// 模拟数据
|
||||
|
||||
|
||||
/**
|
||||
* @desc 处理预订事件
|
||||
*/
|
||||
const handleBook = (productId) => {
|
||||
// 这里可以添加预订逻辑,比如跳转到详情页
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/detail?id=${productId}`,
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="overflow-hidden" :style="{ height: `${pageView.pageHeight}px` }">
|
||||
<Layout :navShowNavBar="false" bgColor="#F7F7F7" @getContentHeight="getContentHeight">
|
||||
<template #content>
|
||||
<view class="w-full flex flex-col gap-[15px] p-[15px]">
|
||||
<view class="flex p-[10px] gap-[15px] border border-round-theme rounded-[10px]" v-for="(product,index) in productData" :key="index">
|
||||
<image :src="product.image" class="w-[80px] h-[80px] rounded-[10px]"></image>
|
||||
<view class="flex-1 flex flex-col gap-[4px]">
|
||||
<text class="text-[15px] text-[#333333] font-bold">{{product.name}}</text>
|
||||
<text class="text-[12px] text-[#1D9EF8] font-Medium">{{product.description}}</text>
|
||||
|
||||
<view class="flex gap-[10px]">
|
||||
<text class="text-price">售价:{{product.price}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
</Layout>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.u-count-down__text) {
|
||||
color: #ab002d !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.navi-title-center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.line {
|
||||
transition: 0.3s all;
|
||||
}
|
||||
|
||||
.search {
|
||||
:deep(.u-search) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"appid": "wx64689b2e9eecaff7",
|
||||
"projectname": "hktourbus",
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "3.8.0",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"condition": {},
|
||||
"setting": {
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"enhance": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmRelationList": [],
|
||||
"compileHotReLoad": true,
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
}
|
||||
},
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 2
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 974 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1013 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 789 B |
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
ref,
|
||||
reactive
|
||||
} from 'vue';
|
||||
import {
|
||||
defineStore
|
||||
} from 'pinia'
|
||||
import {
|
||||
parse,
|
||||
stringify
|
||||
} from 'zipson'
|
||||
|
||||
|
||||
export const useCommon = defineStore(
|
||||
'common',
|
||||
() => {
|
||||
const test = 'test'
|
||||
return {
|
||||
test
|
||||
}
|
||||
|
||||
}, {
|
||||
persist: {
|
||||
paths: ['commonState'],
|
||||
storage: {
|
||||
getItem(key) {
|
||||
return uni.getStorageSync(key)
|
||||
},
|
||||
setItem(key, value) {
|
||||
uni.setStorageSync(key, value)
|
||||
},
|
||||
},
|
||||
serializer: {
|
||||
deserialize: parse,
|
||||
serialize: stringify,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
页面内使用pinia
|
||||
|
||||
1、引用你定义的store实例
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
2、实例化useUserStore
|
||||
const user = useUserStore()
|
||||
|
||||
3、引入 storeToRefs 用于 给实例的属性创建refs,即响应式
|
||||
import { storeToRefs } from 'pinia'
|
||||
例如:const {count} = storeToRefs(user)
|
||||
count.value 此时为响应式
|
||||
|
||||
4、修改state 方式共两种 如下
|
||||
user.count++
|
||||
4-1、
|
||||
// 带自动补全 ✨
|
||||
user.$patch({ count: counter.count + 1 })
|
||||
$patch()可批量修改state的属性值
|
||||
例如:
|
||||
批量修改
|
||||
const pathState = () => {
|
||||
store.$patch({
|
||||
user: { name: 'ks', age: 20 },
|
||||
});
|
||||
};
|
||||
|
||||
其他操作state的方法如下:
|
||||
|
||||
替换State
|
||||
const replaceState = () => {
|
||||
store.$state.user = { age: 20, name: 'eee', sex: 'women' };
|
||||
};
|
||||
重置state的值
|
||||
const reset = () => {
|
||||
store.$reset();
|
||||
};
|
||||
|
||||
4-2、
|
||||
// 或使用 action 代替
|
||||
user.increment()
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,7 @@
|
||||
import {
|
||||
createPinia
|
||||
} from 'pinia';
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
export default pinia;
|
||||
@@ -0,0 +1,112 @@
|
||||
// 引用pinia商店
|
||||
import { ref, reactive } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { parse, stringify } from 'zipson';
|
||||
export const useUserStore = defineStore(
|
||||
'user',
|
||||
() => {
|
||||
const userInfo = ref({});
|
||||
const platform = ref('');
|
||||
const userToken = ref('');
|
||||
const isLogin = ref(false);
|
||||
const currency = ref('HKD');
|
||||
const kfTelCN = ref('');
|
||||
const kfTelHK = ref('');
|
||||
const wxLink = ref('');
|
||||
const lang = ref('');
|
||||
const setProfile = (val) => {
|
||||
userInfo.value = {
|
||||
...val,
|
||||
};
|
||||
console.log(userInfo.value);
|
||||
};
|
||||
//清理用户信息,退出时使用
|
||||
const clearProfile = () => {
|
||||
userInfo.value = {
|
||||
areaCode: '',
|
||||
avatar: '',
|
||||
birthday: null,
|
||||
email: null,
|
||||
gender: null,
|
||||
mobile: '',
|
||||
nickName: '',
|
||||
trueName: null,
|
||||
userId: null,
|
||||
};
|
||||
};
|
||||
return {
|
||||
isLogin,
|
||||
userInfo,
|
||||
userToken,
|
||||
platform,
|
||||
currency,
|
||||
kfTelCN,
|
||||
kfTelHK,
|
||||
wxLink,
|
||||
lang,
|
||||
setProfile,
|
||||
clearProfile,
|
||||
};
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
storage: {
|
||||
getItem(key) {
|
||||
return uni.getStorageSync(key);
|
||||
},
|
||||
setItem(key, value) {
|
||||
uni.setStorageSync(key, value);
|
||||
},
|
||||
},
|
||||
serializer: {
|
||||
deserialize: parse,
|
||||
serialize: stringify,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
页面内使用pinia
|
||||
|
||||
1、引用你定义的store实例
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
2、实例化useUserStore
|
||||
const user = useUserStore()
|
||||
|
||||
3、引入 storeToRefs 用于 给实例的属性创建refs,即响应式
|
||||
import { storeToRefs } from 'pinia'
|
||||
例如:const {count} = storeToRefs(user)
|
||||
count.value 此时为响应式
|
||||
|
||||
4、修改state 方式共两种 如下
|
||||
user.count++
|
||||
4-1、
|
||||
// 带自动补全 ✨
|
||||
user.$patch({ count: counter.count + 1 })
|
||||
$patch()可批量修改state的属性值
|
||||
例如:
|
||||
批量修改
|
||||
const pathState = () => {
|
||||
store.$patch({
|
||||
user: { name: 'ks', age: 20 },
|
||||
});
|
||||
};
|
||||
|
||||
其他操作state的方法如下:
|
||||
|
||||
替换State
|
||||
const replaceState = () => {
|
||||
store.$state.user = { age: 20, name: 'eee', sex: 'women' };
|
||||
};
|
||||
重置state的值
|
||||
const reset = () => {
|
||||
store.$reset();
|
||||
};
|
||||
|
||||
4-2、
|
||||
// 或使用 action 代替
|
||||
user.increment()
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,27 @@
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
const resolve = (p) => {
|
||||
return path.resolve(__dirname, p);
|
||||
};
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
// 增加前辍避免样式冲突
|
||||
// prefix: 'zhs-',
|
||||
// 注意此处,一定要 `path.resolve` 一下, 传入绝对路径
|
||||
// 你要有其他目录,比如 components,也必须在这里,添加一下
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'custom-color': '#9C032D',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
content: ['./pages/**/*.{html,js,ts,jsx,tsx,vue}', './components/**/*.{html,js,ts,jsx,tsx,vue}'].map(resolve),
|
||||
corePlugins: {
|
||||
// 跨多端可以 h5 开启,小程序关闭
|
||||
preflight: false,
|
||||
},
|
||||
...config,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
|
||||
/* 颜色变量 */
|
||||
/* uview-plus */
|
||||
@import 'uview-plus/theme.scss';
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-font-size-lg:16;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:20px;
|
||||
$uni-img-size-base:26px;
|
||||
$uni-img-size-lg:40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
$uni-border-radius-base: 3px;
|
||||
$uni-border-radius-lg: 6px;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 5px;
|
||||
$uni-spacing-row-base: 10px;
|
||||
$uni-spacing-row-lg: 15px;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 4px;
|
||||
$uni-spacing-col-base: 8px;
|
||||
$uni-spacing-col-lg: 12px;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
||||
@@ -0,0 +1,30 @@
|
||||
## 1.4.12(2024-09-21)
|
||||
- 修复 calendar在选择日期范围后重新选择日期需要点两次的Bug
|
||||
## 1.4.11(2024-01-10)
|
||||
- 修复 回到今天时,月份显示不一致问题
|
||||
## 1.4.10(2023-04-10)
|
||||
- 修复 某些情况 monthSwitch 未触发的Bug
|
||||
## 1.4.9(2023-02-02)
|
||||
- 修复 某些情况切换月份错误的Bug
|
||||
## 1.4.8(2023-01-30)
|
||||
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
|
||||
## 1.4.7(2022-09-16)
|
||||
- 优化 支持使用 uni-scss 控制主题色
|
||||
## 1.4.6(2022-09-08)
|
||||
- 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件的Bug
|
||||
## 1.4.5(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.4(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.3(2021-09-22)
|
||||
- 修复 startDate、 endDate 属性失效的Bug
|
||||
## 1.4.2(2021-08-24)
|
||||
- 新增 支持国际化
|
||||
## 1.4.1(2021-08-05)
|
||||
- 修复 弹出层被 tabbar 遮盖的Bug
|
||||
## 1.4.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.3.16(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.3.15(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -0,0 +1,544 @@
|
||||
/**
|
||||
* @1900-2100区间内的公历、农历互转
|
||||
* @charset UTF-8
|
||||
* @github https://github.com/jjonline/calendar.js
|
||||
* @Author Jea杨(JJonline@JJonline.Cn)
|
||||
* @Time 2014-7-21
|
||||
* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
|
||||
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
|
||||
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
|
||||
* @Version 1.0.3
|
||||
* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
|
||||
* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
|
||||
*/
|
||||
/* eslint-disable */
|
||||
var calendar = {
|
||||
|
||||
/**
|
||||
* 农历1900-2100的润大小信息表
|
||||
* @Array Of Property
|
||||
* @return Hex
|
||||
*/
|
||||
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
|
||||
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
|
||||
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
|
||||
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
|
||||
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
|
||||
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
|
||||
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
|
||||
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
|
||||
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
|
||||
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
|
||||
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
|
||||
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
|
||||
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
|
||||
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
|
||||
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
|
||||
/** Add By JJonline@JJonline.Cn**/
|
||||
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
|
||||
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
|
||||
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
|
||||
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
|
||||
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
|
||||
0x0d520], // 2100
|
||||
|
||||
/**
|
||||
* 公历每个月份的天数普通表
|
||||
* @Array Of Property
|
||||
* @return Number
|
||||
*/
|
||||
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||
|
||||
/**
|
||||
* 天干地支之天干速查表
|
||||
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
|
||||
|
||||
/**
|
||||
* 天干地支之地支速查表
|
||||
* @Array Of Property
|
||||
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
|
||||
|
||||
/**
|
||||
* 天干地支之地支速查表<=>生肖
|
||||
* @Array Of Property
|
||||
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
|
||||
|
||||
/**
|
||||
* 24节气速查表
|
||||
* @Array Of Property
|
||||
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
|
||||
* @return Cn string
|
||||
*/
|
||||
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
|
||||
|
||||
/**
|
||||
* 1900-2100各年的24节气日期速查表
|
||||
* @Array Of Property
|
||||
* @return 0x string For splice
|
||||
*/
|
||||
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
|
||||
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
|
||||
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
|
||||
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
|
||||
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
|
||||
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
|
||||
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
|
||||
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
|
||||
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
|
||||
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
|
||||
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
|
||||
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
|
||||
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
|
||||
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
|
||||
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
|
||||
|
||||
/**
|
||||
* 数字转中文速查表
|
||||
* @Array Of Property
|
||||
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
|
||||
|
||||
/**
|
||||
* 日期转农历称呼速查表
|
||||
* @Array Of Property
|
||||
* @trans ['初','十','廿','卅']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
|
||||
|
||||
/**
|
||||
* 月份转农历称呼速查表
|
||||
* @Array Of Property
|
||||
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
|
||||
|
||||
/**
|
||||
* 返回农历y年一整年的总天数
|
||||
* @param lunar Year
|
||||
* @return Number
|
||||
* @eg:var count = calendar.lYearDays(1987) ;//count=387
|
||||
*/
|
||||
lYearDays: function (y) {
|
||||
var i; var sum = 348
|
||||
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
|
||||
return (sum + this.leapDays(y))
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0-12)
|
||||
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
|
||||
*/
|
||||
leapMonth: function (y) { // 闰字编码 \u95f0
|
||||
return (this.lunarInfo[y - 1900] & 0xf)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年闰月的天数 若该年没有闰月则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0、29、30)
|
||||
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
|
||||
*/
|
||||
leapDays: function (y) {
|
||||
if (this.leapMonth(y)) {
|
||||
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
|
||||
}
|
||||
return (0)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
|
||||
* @param lunar Year
|
||||
* @return Number (-1、29、30)
|
||||
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
|
||||
*/
|
||||
monthDays: function (y, m) {
|
||||
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
|
||||
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回公历(!)y年m月的天数
|
||||
* @param solar Year
|
||||
* @return Number (-1、28、29、30、31)
|
||||
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
|
||||
*/
|
||||
solarDays: function (y, m) {
|
||||
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||
var ms = m - 1
|
||||
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
|
||||
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
|
||||
} else {
|
||||
return (this.solarMonth[ms])
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 农历年份转换为干支纪年
|
||||
* @param lYear 农历年的年份数
|
||||
* @return Cn string
|
||||
*/
|
||||
toGanZhiYear: function (lYear) {
|
||||
var ganKey = (lYear - 3) % 10
|
||||
var zhiKey = (lYear - 3) % 12
|
||||
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
|
||||
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
|
||||
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
|
||||
},
|
||||
|
||||
/**
|
||||
* 公历月、日判断所属星座
|
||||
* @param cMonth [description]
|
||||
* @param cDay [description]
|
||||
* @return Cn string
|
||||
*/
|
||||
toAstro: function (cMonth, cDay) {
|
||||
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
|
||||
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
|
||||
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入offset偏移量返回干支
|
||||
* @param offset 相对甲子的偏移量
|
||||
* @return Cn string
|
||||
*/
|
||||
toGanZhi: function (offset) {
|
||||
return this.Gan[offset % 10] + this.Zhi[offset % 12]
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入公历(!)y年获得该年第n个节气的公历日期
|
||||
* @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
|
||||
* @return day Number
|
||||
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
|
||||
*/
|
||||
getTerm: function (y, n) {
|
||||
if (y < 1900 || y > 2100) { return -1 }
|
||||
if (n < 1 || n > 24) { return -1 }
|
||||
var _table = this.sTermInfo[y - 1900]
|
||||
var _info = [
|
||||
parseInt('0x' + _table.substr(0, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(5, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(10, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(15, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(20, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(25, 5)).toString()
|
||||
]
|
||||
var _calday = [
|
||||
_info[0].substr(0, 1),
|
||||
_info[0].substr(1, 2),
|
||||
_info[0].substr(3, 1),
|
||||
_info[0].substr(4, 2),
|
||||
|
||||
_info[1].substr(0, 1),
|
||||
_info[1].substr(1, 2),
|
||||
_info[1].substr(3, 1),
|
||||
_info[1].substr(4, 2),
|
||||
|
||||
_info[2].substr(0, 1),
|
||||
_info[2].substr(1, 2),
|
||||
_info[2].substr(3, 1),
|
||||
_info[2].substr(4, 2),
|
||||
|
||||
_info[3].substr(0, 1),
|
||||
_info[3].substr(1, 2),
|
||||
_info[3].substr(3, 1),
|
||||
_info[3].substr(4, 2),
|
||||
|
||||
_info[4].substr(0, 1),
|
||||
_info[4].substr(1, 2),
|
||||
_info[4].substr(3, 1),
|
||||
_info[4].substr(4, 2),
|
||||
|
||||
_info[5].substr(0, 1),
|
||||
_info[5].substr(1, 2),
|
||||
_info[5].substr(3, 1),
|
||||
_info[5].substr(4, 2)
|
||||
]
|
||||
return parseInt(_calday[n - 1])
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历数字月份返回汉语通俗表示法
|
||||
* @param lunar month
|
||||
* @return Cn string
|
||||
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
|
||||
*/
|
||||
toChinaMonth: function (m) { // 月 => \u6708
|
||||
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||
var s = this.nStr3[m - 1]
|
||||
s += '\u6708'// 加上月字
|
||||
return s
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历日期数字返回汉字表示法
|
||||
* @param lunar day
|
||||
* @return Cn string
|
||||
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
|
||||
*/
|
||||
toChinaDay: function (d) { // 日 => \u65e5
|
||||
var s
|
||||
switch (d) {
|
||||
case 10:
|
||||
s = '\u521d\u5341'; break
|
||||
case 20:
|
||||
s = '\u4e8c\u5341'; break
|
||||
case 30:
|
||||
s = '\u4e09\u5341'; break
|
||||
default :
|
||||
s = this.nStr2[Math.floor(d / 10)]
|
||||
s += this.nStr1[d % 10]
|
||||
}
|
||||
return (s)
|
||||
},
|
||||
|
||||
/**
|
||||
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
|
||||
* @param y year
|
||||
* @return Cn string
|
||||
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
|
||||
*/
|
||||
getAnimal: function (y) {
|
||||
return this.Animals[(y - 4) % 12]
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
|
||||
* @param y solar year
|
||||
* @param m solar month
|
||||
* @param d solar day
|
||||
* @return JSON object
|
||||
* @eg:console.log(calendar.solar2lunar(1987,11,01));
|
||||
*/
|
||||
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
|
||||
// 年份限定、上限
|
||||
if (y < 1900 || y > 2100) {
|
||||
return -1// undefined转换为数字变为NaN
|
||||
}
|
||||
// 公历传参最下限
|
||||
if (y == 1900 && m == 1 && d < 31) {
|
||||
return -1
|
||||
}
|
||||
// 未传参 获得当天
|
||||
if (!y) {
|
||||
var objDate = new Date()
|
||||
} else {
|
||||
var objDate = new Date(y, parseInt(m) - 1, d)
|
||||
}
|
||||
var i; var leap = 0; var temp = 0
|
||||
// 修正ymd参数
|
||||
var y = objDate.getFullYear()
|
||||
var m = objDate.getMonth() + 1
|
||||
var d = objDate.getDate()
|
||||
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
|
||||
for (i = 1900; i < 2101 && offset > 0; i++) {
|
||||
temp = this.lYearDays(i)
|
||||
offset -= temp
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += temp; i--
|
||||
}
|
||||
|
||||
// 是否今天
|
||||
var isTodayObj = new Date()
|
||||
var isToday = false
|
||||
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
|
||||
isToday = true
|
||||
}
|
||||
// 星期几
|
||||
var nWeek = objDate.getDay()
|
||||
var cWeek = this.nStr1[nWeek]
|
||||
// 数字表示周几顺应天朝周一开始的惯例
|
||||
if (nWeek == 0) {
|
||||
nWeek = 7
|
||||
}
|
||||
// 农历年
|
||||
var year = i
|
||||
var leap = this.leapMonth(i) // 闰哪个月
|
||||
var isLeap = false
|
||||
|
||||
// 效验闰月
|
||||
for (i = 1; i < 13 && offset > 0; i++) {
|
||||
// 闰月
|
||||
if (leap > 0 && i == (leap + 1) && isLeap == false) {
|
||||
--i
|
||||
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
|
||||
} else {
|
||||
temp = this.monthDays(year, i)// 计算农历普通月天数
|
||||
}
|
||||
// 解除闰月
|
||||
if (isLeap == true && i == (leap + 1)) { isLeap = false }
|
||||
offset -= temp
|
||||
}
|
||||
// 闰月导致数组下标重叠取反
|
||||
if (offset == 0 && leap > 0 && i == leap + 1) {
|
||||
if (isLeap) {
|
||||
isLeap = false
|
||||
} else {
|
||||
isLeap = true; --i
|
||||
}
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += temp; --i
|
||||
}
|
||||
// 农历月
|
||||
var month = i
|
||||
// 农历日
|
||||
var day = offset + 1
|
||||
// 天干地支处理
|
||||
var sm = m - 1
|
||||
var gzY = this.toGanZhiYear(year)
|
||||
|
||||
// 当月的两个节气
|
||||
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
|
||||
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
|
||||
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
|
||||
|
||||
// 依据12节气修正干支月
|
||||
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
|
||||
if (d >= firstNode) {
|
||||
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
|
||||
}
|
||||
|
||||
// 传入的日期的节气与否
|
||||
var isTerm = false
|
||||
var Term = null
|
||||
if (firstNode == d) {
|
||||
isTerm = true
|
||||
Term = this.solarTerm[m * 2 - 2]
|
||||
}
|
||||
if (secondNode == d) {
|
||||
isTerm = true
|
||||
Term = this.solarTerm[m * 2 - 1]
|
||||
}
|
||||
// 日柱 当月一日与 1900/1/1 相差天数
|
||||
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
|
||||
var gzD = this.toGanZhi(dayCyclical + d - 1)
|
||||
// 该日期所属的星座
|
||||
var astro = this.toAstro(m, d)
|
||||
|
||||
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
|
||||
* @param y lunar year
|
||||
* @param m lunar month
|
||||
* @param d lunar day
|
||||
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
|
||||
* @return JSON object
|
||||
* @eg:console.log(calendar.lunar2solar(1987,9,10));
|
||||
*/
|
||||
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
|
||||
var isLeapMonth = !!isLeapMonth
|
||||
var leapOffset = 0
|
||||
var leapMonth = this.leapMonth(y)
|
||||
var leapDay = this.leapDays(y)
|
||||
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
|
||||
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
|
||||
var day = this.monthDays(y, m)
|
||||
var _day = day
|
||||
// bugFix 2016-9-25
|
||||
// if month is leap, _day use leapDays method
|
||||
if (isLeapMonth) {
|
||||
_day = this.leapDays(y, m)
|
||||
}
|
||||
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
|
||||
|
||||
// 计算农历的时间差
|
||||
var offset = 0
|
||||
for (var i = 1900; i < y; i++) {
|
||||
offset += this.lYearDays(i)
|
||||
}
|
||||
var leap = 0; var isAdd = false
|
||||
for (var i = 1; i < m; i++) {
|
||||
leap = this.leapMonth(y)
|
||||
if (!isAdd) { // 处理闰月
|
||||
if (leap <= i && leap > 0) {
|
||||
offset += this.leapDays(y); isAdd = true
|
||||
}
|
||||
}
|
||||
offset += this.monthDays(y, i)
|
||||
}
|
||||
// 转换闰月农历 需补充该年闰月的前一个月的时差
|
||||
if (isLeapMonth) { offset += day }
|
||||
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
|
||||
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
|
||||
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
|
||||
var cY = calObj.getUTCFullYear()
|
||||
var cM = calObj.getUTCMonth() + 1
|
||||
var cD = calObj.getUTCDate()
|
||||
|
||||
return this.solar2lunar(cY, cM, cD)
|
||||
}
|
||||
}
|
||||
|
||||
export default calendar
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"uni-calender.ok": "ok",
|
||||
"uni-calender.cancel": "cancel",
|
||||
"uni-calender.today": "today",
|
||||
"uni-calender.MON": "MON",
|
||||
"uni-calender.TUE": "TUE",
|
||||
"uni-calender.WED": "WED",
|
||||
"uni-calender.THU": "THU",
|
||||
"uni-calender.FRI": "FRI",
|
||||
"uni-calender.SAT": "SAT",
|
||||
"uni-calender.SUN": "SUN"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import en from './en.json'
|
||||
import zhHans from './zh-Hans.json'
|
||||
import zhHant from './zh-Hant.json'
|
||||
export default {
|
||||
en,
|
||||
'zh-Hans': zhHans,
|
||||
'zh-Hant': zhHant
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"uni-calender.ok": "确定",
|
||||
"uni-calender.cancel": "取消",
|
||||
"uni-calender.today": "今日",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"uni-calender.ok": "確定",
|
||||
"uni-calender.cancel": "取消",
|
||||
"uni-calender.today": "今日",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<view class="uni-calendar-item__weeks-box"
|
||||
:class="{
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}"
|
||||
@click="choiceDate(weeks)">
|
||||
<view class="uni-calendar-item__weeks-box-item">
|
||||
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle">{{ weeks.price }}</text>
|
||||
<text class="uni-calendar-item__weeks-box-text" :class="{
|
||||
'uni-calendar-item--isDay-text': weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.date}}</text>
|
||||
<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}">{{todayText}}</text>
|
||||
<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
|
||||
<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--extra':weeks.extraInfo.info,
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.extraInfo.info}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const { t } = initVueI18n(i18nMessages)
|
||||
|
||||
export default {
|
||||
emits:['change'],
|
||||
props: {
|
||||
weeks: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
calendar: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
todayText() {
|
||||
return t("uni-calender.today")
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
choiceDate(weeks) {
|
||||
this.$emit('change', weeks)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$uni-font-size-base:14px;
|
||||
$uni-text-color:#333;
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-color-error: #e43d33;
|
||||
$uni-opacity-disabled: 0.3;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
$uni-primary: #00A88E !default;
|
||||
.uni-calendar-item__weeks-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-text {
|
||||
font-size: $uni-font-size-base;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-lunar-text {
|
||||
font-size: $uni-font-size-sm;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex:1;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-circle {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
// width: 8px;
|
||||
// height: 8px;
|
||||
// border-radius: 8px;
|
||||
// background-color: $uni-color-error;
|
||||
|
||||
}
|
||||
|
||||
.uni-calendar-item--disable {
|
||||
background-color: rgba(249, 249, 249, $uni-opacity-disabled);
|
||||
color: $uni-text-color-disable;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay {
|
||||
background-color: $uni-primary;
|
||||
border-radius: 10px;
|
||||
opacity: 0.8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar-item--extra {
|
||||
color: $uni-color-error;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--checked {
|
||||
background-color: $uni-primary;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--multiple {
|
||||
background-color: $uni-primary;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.uni-calendar-item--before-checked {
|
||||
background-color: #c4ebe5;
|
||||
color: #fff;
|
||||
}
|
||||
.uni-calendar-item--after-checked {
|
||||
background-color: #c4ebe5;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,585 @@
|
||||
<template>
|
||||
<view class="uni-calendar">
|
||||
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"
|
||||
@click="clean"></view>
|
||||
<view v-if="insert || show" class="uni-calendar__content"
|
||||
:class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
|
||||
<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
|
||||
<view class="uni-calendar__header-btn-box" @click="close">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__header-btn-box" @click="confirm">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__box">
|
||||
<view v-if="showMonth" class="uni-calendar__box-bg">
|
||||
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__header">
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="pre">
|
||||
<view class="uni-calendar__header-btn uni-calendar--left"></view>
|
||||
</view>
|
||||
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
|
||||
<text
|
||||
class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
|
||||
</picker>
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="next">
|
||||
<view class="uni-calendar__header-btn uni-calendar--right"></view>
|
||||
</view>
|
||||
<text class="uni-calendar__backtoday" @click="backToday">{{todayText}}</text>
|
||||
|
||||
</view>
|
||||
<view class="uni-calendar__weeks border-b-1 border-solid border-theme">
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
|
||||
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
|
||||
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar"
|
||||
:selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Calendar from './util.js';
|
||||
import CalendarItem from './uni-calendar-item.vue'
|
||||
|
||||
import {
|
||||
initVueI18n
|
||||
} from '@dcloudio/uni-i18n'
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const {
|
||||
t
|
||||
} = initVueI18n(i18nMessages)
|
||||
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
|
||||
* @property {String} date 自定义当前时间,默认为今天
|
||||
* @property {Boolean} lunar 显示农历
|
||||
* @property {String} startDate 日期选择范围-开始日期
|
||||
* @property {String} endDate 日期选择范围-结束日期
|
||||
* @property {Boolean} range 范围选择
|
||||
* @property {Boolean} insert = [true|false] 插入模式,默认为false
|
||||
* @value true 弹窗模式
|
||||
* @value false 插入模式
|
||||
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
|
||||
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
|
||||
* @property {Boolean} showMonth 是否选择月份为背景
|
||||
* @event {Function} change 日期改变,`insert :ture` 时生效
|
||||
* @event {Function} confirm 确认选择`insert :false` 时生效
|
||||
* @event {Function} monthSwitch 切换月份时触发
|
||||
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
|
||||
*/
|
||||
export default {
|
||||
components: {
|
||||
CalendarItem
|
||||
},
|
||||
emits: ['close', 'confirm', 'change', 'monthSwitch'],
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
startDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
endDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
insert: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
clearDate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
weeks: [],
|
||||
calendar: {},
|
||||
nowDate: '',
|
||||
aniMaskShow: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* for i18n
|
||||
*/
|
||||
|
||||
okText() {
|
||||
return t("uni-calender.ok")
|
||||
},
|
||||
cancelText() {
|
||||
return t("uni-calender.cancel")
|
||||
},
|
||||
todayText() {
|
||||
return t("uni-calender.today")
|
||||
},
|
||||
monText() {
|
||||
return t("uni-calender.MON")
|
||||
},
|
||||
TUEText() {
|
||||
return t("uni-calender.TUE")
|
||||
},
|
||||
WEDText() {
|
||||
return t("uni-calender.WED")
|
||||
},
|
||||
THUText() {
|
||||
return t("uni-calender.THU")
|
||||
},
|
||||
FRIText() {
|
||||
return t("uni-calender.FRI")
|
||||
},
|
||||
SATText() {
|
||||
return t("uni-calender.SAT")
|
||||
},
|
||||
SUNText() {
|
||||
return t("uni-calender.SUN")
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
date(newVal) {
|
||||
// this.cale.setDate(newVal)
|
||||
this.init(newVal)
|
||||
},
|
||||
startDate(val) {
|
||||
this.cale.resetSatrtDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
endDate(val) {
|
||||
this.cale.resetEndDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
selected(newVal) {
|
||||
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
|
||||
this.weeks = this.cale.weeks
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cale = new Calendar({
|
||||
selected: this.selected,
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate,
|
||||
range: this.range,
|
||||
})
|
||||
this.init(this.date)
|
||||
},
|
||||
methods: {
|
||||
// 取消穿透
|
||||
clean() {},
|
||||
bindDateChange(e) {
|
||||
const value = e.detail.value + '-1'
|
||||
this.setDate(value)
|
||||
|
||||
const {
|
||||
year,
|
||||
month
|
||||
} = this.cale.getDate(value)
|
||||
this.$emit('monthSwitch', {
|
||||
year,
|
||||
month
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 初始化日期显示
|
||||
* @param {Object} date
|
||||
*/
|
||||
init(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.calendar = this.cale.getInfo(date)
|
||||
},
|
||||
/**
|
||||
* 打开日历弹窗
|
||||
*/
|
||||
open() {
|
||||
// 弹窗模式并且清理数据
|
||||
if (this.clearDate && !this.insert) {
|
||||
this.cale.cleanMultipleStatus()
|
||||
// this.cale.setDate(this.date)
|
||||
this.init(this.date)
|
||||
}
|
||||
this.show = true
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.aniMaskShow = true
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 关闭日历弹窗
|
||||
*/
|
||||
close() {
|
||||
this.aniMaskShow = false
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
this.$emit('close')
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 确认按钮
|
||||
*/
|
||||
confirm() {
|
||||
this.setEmit('confirm')
|
||||
this.close()
|
||||
},
|
||||
/**
|
||||
* 变化触发
|
||||
*/
|
||||
change() {
|
||||
if (!this.insert) return
|
||||
this.setEmit('change')
|
||||
},
|
||||
/**
|
||||
* 选择月份触发
|
||||
*/
|
||||
monthSwitch() {
|
||||
let {
|
||||
year,
|
||||
month
|
||||
} = this.nowDate
|
||||
this.$emit('monthSwitch', {
|
||||
year,
|
||||
month: Number(month)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 派发事件
|
||||
* @param {Object} name
|
||||
*/
|
||||
setEmit(name) {
|
||||
let {
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fullDate,
|
||||
lunar,
|
||||
extraInfo
|
||||
} = this.calendar
|
||||
this.$emit(name, {
|
||||
range: this.cale.multipleStatus,
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fulldate: fullDate,
|
||||
lunar,
|
||||
extraInfo: extraInfo || {}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 选择天触发
|
||||
* @param {Object} weeks
|
||||
*/
|
||||
choiceDate(weeks) {
|
||||
if (weeks.disable) return
|
||||
this.calendar = weeks
|
||||
// 设置多选
|
||||
this.cale.setMultiple(this.calendar.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 回到今天
|
||||
*/
|
||||
backToday() {
|
||||
const nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`
|
||||
const date = this.cale.getDate(new Date())
|
||||
const todayYearMonth = `${date.year}-${date.month}`
|
||||
|
||||
this.init(date.fullDate)
|
||||
|
||||
if (nowYearMonth !== todayYearMonth) {
|
||||
this.monthSwitch()
|
||||
}
|
||||
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 上个月
|
||||
*/
|
||||
pre() {
|
||||
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
|
||||
this.setDate(preDate)
|
||||
this.monthSwitch()
|
||||
|
||||
},
|
||||
/**
|
||||
* 下个月
|
||||
*/
|
||||
next() {
|
||||
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
|
||||
this.setDate(nextDate)
|
||||
this.monthSwitch()
|
||||
},
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.cale.getInfo(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$uni-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
|
||||
$uni-border-color: #00A88E;
|
||||
$uni-text-color: #333;
|
||||
$uni-bg-color-hover: #f1f1f1;
|
||||
$uni-font-size-base: 14px;
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-color-subtitle: #555555;
|
||||
$uni-text-color-grey: #999;
|
||||
|
||||
.uni-calendar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.uni-calendar__mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $uni-bg-color-mask;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
opacity: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--mask-show {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.uni-calendar--fixed {
|
||||
position: fixed;
|
||||
/* #ifdef APP-NVUE */
|
||||
bottom: 0;
|
||||
/* #endif */
|
||||
left: 0;
|
||||
right: 0;
|
||||
transition-property: transform;
|
||||
transition-duration: 0.3s;
|
||||
transform: translateY(460px);
|
||||
/* #ifndef APP-NVUE */
|
||||
bottom: calc(var(--window-bottom));
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--ani-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.uni-calendar__content {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar__header {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
border-bottom-color: $uni-border-color;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-top {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
border-top-color: $uni-border-color;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-width {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__backtoday {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 25rpx;
|
||||
padding: 0 5px;
|
||||
padding-left: 10px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 12px;
|
||||
border-top-left-radius: 25px;
|
||||
border-bottom-left-radius: 25px;
|
||||
color: $uni-text-color;
|
||||
background-color: $uni-bg-color-hover;
|
||||
}
|
||||
|
||||
.uni-calendar__header-text {
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
font-size: $uni-font-size-base;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-left-color: $uni-text-color-placeholder;
|
||||
border-left-style: solid;
|
||||
border-left-width: 2px;
|
||||
border-top-color: $uni-color-subtitle;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
.uni-calendar--left {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.uni-calendar--right {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
|
||||
.uni-calendar__weeks {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-item {
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
border-bottom-color: #00A88E;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-calendar__box {
|
||||
position: relative;
|
||||
border: 2rpx solid #00A88E;
|
||||
border-radius: 10rpx;
|
||||
padding: 10px;
|
||||
background:rgb(0 255 220 / 5%);
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg-text {
|
||||
font-size: 200px;
|
||||
font-weight: bold;
|
||||
color: $uni-text-color-grey;
|
||||
opacity: 0.1;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: 1;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,360 @@
|
||||
import CALENDAR from './calendar.js'
|
||||
|
||||
class Calendar {
|
||||
constructor({
|
||||
date,
|
||||
selected,
|
||||
startDate,
|
||||
endDate,
|
||||
range
|
||||
} = {}) {
|
||||
// 当前日期
|
||||
this.date = this.getDate(new Date()) // 当前初入日期
|
||||
// 打点信息
|
||||
this.selected = selected || [];
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
this.range = range
|
||||
// 多选状态
|
||||
this.cleanMultipleStatus()
|
||||
// 每周日期
|
||||
this.weeks = {}
|
||||
// this._getWeek(this.date.fullDate)
|
||||
}
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.selectDate = this.getDate(date)
|
||||
this._getWeek(this.selectDate.fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理多选状态
|
||||
*/
|
||||
cleanMultipleStatus() {
|
||||
this.multipleStatus = {
|
||||
before: '',
|
||||
after: '',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置开始日期
|
||||
*/
|
||||
resetSatrtDate(startDate) {
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置结束日期
|
||||
*/
|
||||
resetEndDate(endDate) {
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任意时间
|
||||
*/
|
||||
getDate(date, AddDayCount = 0, str = 'day') {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
if (typeof date !== 'object') {
|
||||
date = date.replace(/-/g, '/')
|
||||
}
|
||||
const dd = new Date(date)
|
||||
switch (str) {
|
||||
case 'day':
|
||||
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
case 'month':
|
||||
if (dd.getDate() === 31 && AddDayCount>0) {
|
||||
dd.setDate(dd.getDate() + AddDayCount)
|
||||
} else {
|
||||
const preMonth = dd.getMonth()
|
||||
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
|
||||
const nextMonth = dd.getMonth()
|
||||
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
|
||||
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
|
||||
}
|
||||
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
|
||||
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'year':
|
||||
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
}
|
||||
const y = dd.getFullYear()
|
||||
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
|
||||
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
|
||||
return {
|
||||
fullDate: y + '-' + m + '-' + d,
|
||||
year: y,
|
||||
month: m,
|
||||
date: d,
|
||||
day: dd.getDay()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取上月剩余天数
|
||||
*/
|
||||
_getLastMonthDays(firstDay, full) {
|
||||
let dateArr = []
|
||||
for (let i = firstDay; i > 0; i--) {
|
||||
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
|
||||
dateArr.push({
|
||||
date: beforeDate,
|
||||
month: full.month - 1,
|
||||
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取本月天数
|
||||
*/
|
||||
_currentMonthDys(dateData, full) {
|
||||
let dateArr = []
|
||||
let fullDate = this.date.fullDate
|
||||
for (let i = 1; i <= dateData; i++) {
|
||||
let nowDate = full.year + '-' + (full.month < 10 ?
|
||||
full.month : full.month) + '-' + (i < 10 ?
|
||||
'0' + i : i)
|
||||
// 是否今天
|
||||
let isDay = fullDate === nowDate
|
||||
// 获取打点信息
|
||||
let info = this.selected && this.selected.find((item) => {
|
||||
if (this.dateEqual(nowDate, item.date)) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
|
||||
// 日期禁用
|
||||
let disableBefore = true
|
||||
let disableAfter = true
|
||||
if (this.startDate) {
|
||||
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
|
||||
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
|
||||
disableBefore = this.dateCompare(this.startDate, nowDate)
|
||||
}
|
||||
|
||||
if (this.endDate) {
|
||||
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
|
||||
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
|
||||
disableAfter = this.dateCompare(nowDate, this.endDate)
|
||||
}
|
||||
let multiples = this.multipleStatus.data
|
||||
let checked = false
|
||||
let multiplesStatus = -1
|
||||
if (this.range) {
|
||||
if (multiples) {
|
||||
multiplesStatus = multiples.findIndex((item) => {
|
||||
return this.dateEqual(item, nowDate)
|
||||
})
|
||||
}
|
||||
if (multiplesStatus !== -1) {
|
||||
checked = true
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
fullDate: nowDate,
|
||||
year: full.year,
|
||||
date: i,
|
||||
multiple: this.range ? checked : false,
|
||||
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
|
||||
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
|
||||
month: full.month,
|
||||
lunar: this.getlunar(full.year, full.month, i),
|
||||
disable: !(disableBefore && disableAfter),
|
||||
isDay
|
||||
}
|
||||
if (info) {
|
||||
data.extraInfo = info
|
||||
}
|
||||
|
||||
dateArr.push(data)
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取下月天数
|
||||
*/
|
||||
_getNextMonthDays(surplus, full) {
|
||||
let dateArr = []
|
||||
for (let i = 1; i < surplus + 1; i++) {
|
||||
dateArr.push({
|
||||
date: i,
|
||||
month: Number(full.month) + 1,
|
||||
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期详情
|
||||
* @param {Object} date
|
||||
*/
|
||||
getInfo(date) {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
|
||||
return dateInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间大小
|
||||
*/
|
||||
dateCompare(startDate, endDate) {
|
||||
// 计算截止时间
|
||||
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
|
||||
if (startDate <= endDate) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间是否相等
|
||||
*/
|
||||
dateEqual(before, after) {
|
||||
// 计算截止时间
|
||||
before = new Date(before.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
after = new Date(after.replace('-', '/').replace('-', '/'))
|
||||
if (before.getTime() - after.getTime() === 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取日期范围内所有日期
|
||||
* @param {Object} begin
|
||||
* @param {Object} end
|
||||
*/
|
||||
geDateAll(begin, end) {
|
||||
var arr = []
|
||||
var ab = begin.split('-')
|
||||
var ae = end.split('-')
|
||||
var db = new Date()
|
||||
db.setFullYear(ab[0], ab[1] - 1, ab[2])
|
||||
var de = new Date()
|
||||
de.setFullYear(ae[0], ae[1] - 1, ae[2])
|
||||
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
|
||||
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
|
||||
for (var k = unixDb; k <= unixDe;) {
|
||||
k = k + 24 * 60 * 60 * 1000
|
||||
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
/**
|
||||
* 计算阴历日期显示
|
||||
*/
|
||||
getlunar(year, month, date) {
|
||||
return CALENDAR.solar2lunar(year, month, date)
|
||||
}
|
||||
/**
|
||||
* 设置打点
|
||||
*/
|
||||
setSelectInfo(data, value) {
|
||||
this.selected = value
|
||||
this._getWeek(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多选状态
|
||||
*/
|
||||
setMultiple(fullDate) {
|
||||
let {
|
||||
before,
|
||||
after
|
||||
} = this.multipleStatus
|
||||
|
||||
if (!this.range) return
|
||||
if (before && after) {
|
||||
this.multipleStatus.before = fullDate
|
||||
this.multipleStatus.after = ''
|
||||
this.multipleStatus.data = []
|
||||
} else {
|
||||
if (!before) {
|
||||
this.multipleStatus.before = fullDate
|
||||
} else {
|
||||
this.multipleStatus.after = fullDate
|
||||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
|
||||
} else {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._getWeek(fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每周数据
|
||||
* @param {Object} dateData
|
||||
*/
|
||||
_getWeek(dateData) {
|
||||
const {
|
||||
year,
|
||||
month
|
||||
} = this.getDate(dateData)
|
||||
let firstDay = new Date(year, month - 1, 1).getDay()
|
||||
let currentDay = new Date(year, month, 0).getDate()
|
||||
let dates = {
|
||||
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
|
||||
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
|
||||
nextMonthDays: [], // 下个月开始几天
|
||||
weeks: []
|
||||
}
|
||||
let canlender = []
|
||||
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
|
||||
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
|
||||
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
|
||||
let weeks = {}
|
||||
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
|
||||
for (let i = 0; i < canlender.length; i++) {
|
||||
if (i % 7 === 0) {
|
||||
weeks[parseInt(i / 7)] = new Array(7)
|
||||
}
|
||||
weeks[parseInt(i / 7)][i % 7] = canlender[i]
|
||||
}
|
||||
this.canlender = canlender
|
||||
this.weeks = weeks
|
||||
}
|
||||
|
||||
//静态方法
|
||||
// static init(date) {
|
||||
// if (!this.instance) {
|
||||
// this.instance = new Calendar(date);
|
||||
// }
|
||||
// return this.instance;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
export default Calendar
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"id": "uni-calendar",
|
||||
"displayName": "uni-calendar 日历",
|
||||
"version": "1.4.12",
|
||||
"description": "日历组件",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"日历",
|
||||
"",
|
||||
"打卡",
|
||||
"日历选择"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
|
||||
|
||||
## Calendar 日历
|
||||
> **组件名:uni-calendar**
|
||||
> 代码块: `uCalendar`
|
||||
|
||||
|
||||
日历组件
|
||||
|
||||
> **注意事项**
|
||||
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
|
||||
> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
|
||||
> - 仅支持自定义组件模式
|
||||
> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
|
||||
> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
|
||||
> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
|
||||
|
||||
|
||||
### 安装方式
|
||||
|
||||
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
|
||||
|
||||
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
|
||||
|
||||
### 基本用法
|
||||
|
||||
在 ``template`` 中使用组件
|
||||
|
||||
```html
|
||||
<view>
|
||||
<uni-calendar
|
||||
:insert="true"
|
||||
:lunar="true"
|
||||
:start-date="'2019-3-2'"
|
||||
:end-date="'2019-5-20'"
|
||||
@change="change"
|
||||
/>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 通过方法打开日历
|
||||
|
||||
需要设置 `insert` 为 `false`
|
||||
|
||||
```html
|
||||
<view>
|
||||
<uni-calendar
|
||||
ref="calendar"
|
||||
:insert="false"
|
||||
@confirm="confirm"
|
||||
/>
|
||||
<button @click="open">打开日历</button>
|
||||
</view>
|
||||
```
|
||||
|
||||
```javascript
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
open(){
|
||||
this.$refs.calendar.open();
|
||||
},
|
||||
confirm(e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Calendar Props
|
||||
|
||||
| 属性名 | 类型 | 默认值| 说明 |
|
||||
| - | - | - | - |
|
||||
| date | String |- | 自定义当前时间,默认为今天 |
|
||||
| lunar | Boolean | false | 显示农历 |
|
||||
| startDate | String |- | 日期选择范围-开始日期 |
|
||||
| endDate | String |- | 日期选择范围-结束日期 |
|
||||
| range | Boolean | false | 范围选择 |
|
||||
| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 |
|
||||
|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
|
||||
| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|
||||
|showMonth | Boolean | true | 是否显示月份为背景 |
|
||||
|
||||
### Calendar Events
|
||||
|
||||
| 事件名 | 说明 |返回值|
|
||||
| - | - | - |
|
||||
| open | 弹出日历组件,`insert :false` 时生效|- |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 组件示例
|
||||
|
||||
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)
|
||||
@@ -0,0 +1,135 @@
|
||||
import { useUserStore } from '@/store/user';
|
||||
|
||||
export const baseUrl = import.meta.env.VITE_APP_BASE_URL;
|
||||
const appVersion = uni.getSystemInfoSync().appVersion;
|
||||
// 添加拦截器
|
||||
// const httpInterceptor = {
|
||||
// // 拦截前触发
|
||||
// invoke(options) {
|
||||
// // 1. 非 http 开头需拼接地址
|
||||
// if (!options.url.startsWith('http')) {
|
||||
// options.url = baseUrl + options.url;
|
||||
// }
|
||||
// // 2. 请求超时, 默认 10s
|
||||
// options.timeout = 10000;
|
||||
// options.header = {
|
||||
// ...options.header,
|
||||
// 'Content-Type': 'application/x-www-form-urlencoded',
|
||||
// };
|
||||
// const userStore = useUserStore();
|
||||
// options.data = {
|
||||
// ...options.data,
|
||||
// __token: userStore.userToken,
|
||||
// __platform: userStore.platform,
|
||||
// };
|
||||
// },
|
||||
// };
|
||||
// uni.addInterceptor('request', httpInterceptor);
|
||||
// // #ifndef H5
|
||||
// uni.addInterceptor('uploadFile', httpInterceptor);
|
||||
// // #endif
|
||||
|
||||
// 对uni原生的request做的一个封装
|
||||
export const request = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const userStore = useUserStore();
|
||||
// 请求头
|
||||
options.header = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
// version: appVersion,
|
||||
};
|
||||
var params = {
|
||||
__token: userStore.userToken,
|
||||
__platform: userStore.platform,
|
||||
__language: userStore.lang,
|
||||
__channelId: 33,
|
||||
};
|
||||
var data = options.data;
|
||||
data = {
|
||||
...data,
|
||||
...params,
|
||||
};
|
||||
// uni原生request
|
||||
uni
|
||||
.request({
|
||||
url: baseUrl + options.url || '',
|
||||
method: options.method || 'GET',
|
||||
data: data || {},
|
||||
header: options.header || {},
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.statusCode === 401) {
|
||||
// usetToken无效,移除本地缓存
|
||||
userStore.userToken = '';
|
||||
userStore.clearProfile();
|
||||
userStore.isLogin = false;
|
||||
resolve({
|
||||
...res.data,
|
||||
statusCode: res.statusCode,
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
...res.data,
|
||||
statusCode: res.statusCode,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.statusCode === 401) {
|
||||
// usetToken无效,移除本地缓存
|
||||
userStore.userToken = '';
|
||||
userStore.clearProfile();
|
||||
reject({
|
||||
...error.data,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
duration: 2500,
|
||||
icon: 'none',
|
||||
mask: true,
|
||||
title: 'Server exception, please try again later',
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
reject(error.data);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 定义get请求
|
||||
export const get = (url, data = {}, options = {}) => {
|
||||
options.url = url;
|
||||
options.method = 'GET';
|
||||
options.data = data;
|
||||
|
||||
return request(options);
|
||||
};
|
||||
// 定义post请求
|
||||
export const post = (url, data = {}, options = {}) => {
|
||||
options.url = url;
|
||||
options.method = 'POST';
|
||||
options.data = data;
|
||||
|
||||
return request(options);
|
||||
};
|
||||
|
||||
// 定义put请求
|
||||
export const put = (url, data = {}, options = {}) => {
|
||||
options.url = url;
|
||||
options.method = 'PUT';
|
||||
options.data = data;
|
||||
|
||||
return request(options);
|
||||
};
|
||||
|
||||
// 定义delete请求
|
||||
export const dele = (url, data = {}, options = {}) => {
|
||||
options.url = url;
|
||||
options.method = 'DELETE';
|
||||
options.data = data;
|
||||
|
||||
return request(options);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import path from 'path';
|
||||
import uni from '@dcloudio/vite-plugin-uni';
|
||||
|
||||
// start 引入tailwindcss增加的配置0
|
||||
import { UnifiedViteWeappTailwindcssPlugin as uvwt } from 'weapp-tailwindcss/vite';
|
||||
// 注意: 打包成 h5 和 app 都不需要开启插件配置
|
||||
const isH5 = process.env.UNI_PLATFORM === 'h5';
|
||||
const isApp = process.env.UNI_PLATFORM === 'app';
|
||||
const WeappTailwindcssDisabled = isH5 || isApp;
|
||||
|
||||
const resolve = (p) => {
|
||||
return path.resolve(__dirname, p);
|
||||
};
|
||||
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
// 根据当前工作目录中的 `mode` 加载 .env 文件
|
||||
// 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
return {
|
||||
// vite 配置
|
||||
base: './',
|
||||
define: {},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/pages',
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/sale': {
|
||||
target: process.env.VITE_APP_BASE_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/sale/, '/'),
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
uni(),
|
||||
// 引入tailwindcss增加的配置plugins
|
||||
uvwt({
|
||||
rem2rpx: true,
|
||||
disabled: WeappTailwindcssDisabled,
|
||||
// 由于 hbuilderx 会改变 process.cwd 所以这里必须传入当前目录的绝对路径
|
||||
tailwindcssBasedir: __dirname,
|
||||
}),
|
||||
],
|
||||
// 引入tailwindcss增加的配置postcss
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
require('tailwindcss')({
|
||||
// 注意此处,手动传入你 `tailwind.config.js` 的绝对路径
|
||||
config: resolve('./tailwind.config.js'),
|
||||
}),
|
||||
require('autoprefixer'),
|
||||
],
|
||||
},
|
||||
},
|
||||
ssr: {
|
||||
format: 'cjs', //esm,cjs即将在vite5移除
|
||||
},
|
||||
};
|
||||
});
|
||||