fix:重构前的保存
This commit is contained in:
parent
012c7c8c17
commit
feeaa6a561
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -48,5 +48,5 @@
|
||||
"src/uni_modules/uni-search-bar/components/uni-search-bar/i18n",
|
||||
"src/uni_modules/z-paging/components/z-paging/i18n"
|
||||
],
|
||||
"cSpell.words": ["xuexiaole"]
|
||||
"cSpell.words": ["iconfont", "pinia", "VITE", "xuexiaole"]
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"unplugin-vue-define-options": "^1.4.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"lodash": "4.17.21",
|
||||
"vue-qrcode-reader": "^5.5.7",
|
||||
"weixin-js-sdk": "^1.6.5"
|
||||
},
|
||||
@ -93,6 +94,7 @@
|
||||
"prettier": "^3.0.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "4.1.4",
|
||||
"@types/lodash": "4.17.16",
|
||||
"vue-eslint-parser": "^9.3.2",
|
||||
"vue-tsc": "^1.0.24"
|
||||
},
|
||||
|
||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@ -62,6 +62,9 @@ importers:
|
||||
lint-staged:
|
||||
specifier: ^15.0.1
|
||||
version: 15.0.1
|
||||
lodash:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
pinia:
|
||||
specifier: ^2.0.36
|
||||
version: 2.0.36(typescript@4.9.5)(vue@3.3.4)
|
||||
@ -114,6 +117,9 @@ importers:
|
||||
'@dcloudio/vite-plugin-uni':
|
||||
specifier: 3.0.0-3081220230817001
|
||||
version: 3.0.0-3081220230817001(postcss@8.4.38)(ts-node@10.9.1(@types/node@20.5.1)(typescript@4.9.5))(vite@4.1.4(@types/node@20.5.1)(sass@1.72.0)(terser@5.22.0))(vue@3.3.4)
|
||||
'@types/lodash':
|
||||
specifier: 4.17.16
|
||||
version: 4.17.16
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^6.8.0
|
||||
version: 6.8.0(@typescript-eslint/parser@6.8.0(eslint@8.51.0)(typescript@4.9.5))(eslint@8.51.0)(typescript@4.9.5)
|
||||
@ -1629,6 +1635,9 @@ packages:
|
||||
'@types/json5@0.0.29':
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
|
||||
'@types/lodash@4.17.16':
|
||||
resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==}
|
||||
|
||||
'@types/minimist@1.2.3':
|
||||
resolution: {integrity: sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==}
|
||||
|
||||
@ -7009,6 +7018,8 @@ snapshots:
|
||||
|
||||
'@types/json5@0.0.29': {}
|
||||
|
||||
'@types/lodash@4.17.16': {}
|
||||
|
||||
'@types/minimist@1.2.3': {}
|
||||
|
||||
'@types/node@20.5.1': {}
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
import $req from './request';
|
||||
import $req from './request'
|
||||
|
||||
// 获取用户信息
|
||||
export const getUserInfo = () =>
|
||||
$req({
|
||||
url: '/sysUser/selectUser',
|
||||
});
|
||||
})
|
||||
|
||||
// 获取客服信息
|
||||
export const getCsInfo = (id: string) =>
|
||||
$req({
|
||||
url: `/customerService/getInfoByUserId/${id}`,
|
||||
});
|
||||
})
|
||||
|
||||
// 更新用户信息
|
||||
export const updateUserInfo = (data: anyObj) =>
|
||||
export const updateUserInfo = (data: any) =>
|
||||
$req({
|
||||
url: '/sysUser/editPerfectMessage',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
})
|
||||
|
||||
// 获取协议
|
||||
export const getAgreeInfo = (id: string, isChat = false) =>
|
||||
@ -27,30 +27,30 @@ export const getAgreeInfo = (id: string, isChat = false) =>
|
||||
headers: {
|
||||
Authorization: '',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
// 获取字典
|
||||
export const getDict = () =>
|
||||
$req({
|
||||
url: '/sysDictType/tree',
|
||||
});
|
||||
})
|
||||
|
||||
// 设备通知注册
|
||||
export const bindRegId = (data: anyObj) =>
|
||||
export const bindRegId = (data: any) =>
|
||||
$req({
|
||||
url: '/sysUser/dealRegistrationId',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
})
|
||||
|
||||
// 获取未读消息数量
|
||||
export const getUnreadNum = () => $req({ url: '/customerService/getUnreadNum' });
|
||||
export const getUnreadNum = () => $req({ url: '/customerService/getUnreadNum' })
|
||||
|
||||
// 支付创建订单
|
||||
export const createInspectorPrepayOrder = (data: anyObj) => {
|
||||
export const createInspectorPrepayOrder = (data: any) => {
|
||||
return $req({
|
||||
method: 'post',
|
||||
url: '/orderManagement/createInspectorPrepayOrder',
|
||||
data,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import $req from './request';
|
||||
import $req from './request'
|
||||
/**
|
||||
* 手机号授权
|
||||
* @param data 请求参数
|
||||
@ -8,18 +8,18 @@ export const getBindPhoneTypeApi = data => {
|
||||
method: 'post',
|
||||
url: '/xcx/login/getBindRelationList',
|
||||
data,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 手机号获取用户身份
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getAdminTypeByPhoneApi = (params: anyObj) =>
|
||||
export const getAdminTypeByPhoneApi = (params: any) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/sysUser/getAdminTypeByPhone',
|
||||
params,
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
@ -29,7 +29,7 @@ export const sendCodeMessageApi = data =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: `/sms/sendMessage?phoneNumbers=${data}`,
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
@ -43,7 +43,7 @@ export const smsLoginApi = data =>
|
||||
...data,
|
||||
clientType: 'MOBILE',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
@ -53,17 +53,17 @@ export const getUserInfoApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/sysUser/selectUser',
|
||||
});
|
||||
})
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const updateUserInfoApi = (data: anyObj) =>
|
||||
export const updateUserInfoApi = (data: any) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/sysUser/updateInfo',
|
||||
data,
|
||||
});
|
||||
})
|
||||
/**
|
||||
* 授权用户绑定
|
||||
* @param data 请求参数
|
||||
@ -73,7 +73,7 @@ export const bindAuthInfoApi = data =>
|
||||
method: 'post',
|
||||
url: '/wechatPublic/bindAuthInfo',
|
||||
data,
|
||||
});
|
||||
})
|
||||
/**
|
||||
* 获取静默授权链接
|
||||
* @param data 请求参数
|
||||
@ -82,10 +82,10 @@ export const getAuthUrlApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/wechatPublic/getAuthUrl',
|
||||
});
|
||||
})
|
||||
|
||||
// 退出登录
|
||||
export const logout = () =>
|
||||
$req({
|
||||
url: '/logout',
|
||||
});
|
||||
})
|
||||
|
||||
@ -1,60 +1,60 @@
|
||||
import $req from '../request';
|
||||
import $req from '../request'
|
||||
/**
|
||||
* 学情报告-学科目录
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const subjectApi = (gradeId: string) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/subject/list/${gradeId}`,
|
||||
});
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/subject/list/${gradeId}`,
|
||||
})
|
||||
/**
|
||||
* 学情报告-学习时长统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const studyTimeStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/duration',
|
||||
params,
|
||||
});
|
||||
export const studyTimeStatApi = (params: any) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/duration',
|
||||
params,
|
||||
})
|
||||
/**
|
||||
* 学情报告-视频学习统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const videoStudyStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/video',
|
||||
params,
|
||||
});
|
||||
export const videoStudyStatApi = (params: any) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/video',
|
||||
params,
|
||||
})
|
||||
/**
|
||||
* 学情报告-知识图谱统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const knowledgeStudyStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/knowledge',
|
||||
params,
|
||||
});
|
||||
export const knowledgeStudyStatApi = (params: any) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/knowledge',
|
||||
params,
|
||||
})
|
||||
/**
|
||||
* 学情报告-错题本统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const studyErrorStatApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/error',
|
||||
params,
|
||||
});
|
||||
export const studyErrorStatApi = (params: any) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/userSubjectReport/study/error',
|
||||
params,
|
||||
})
|
||||
/**
|
||||
* 学情报告-英语语感统计
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const englishLanguageStatApi = (data: anyobj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/userSentenceLearn/queryStatListByTimeType',
|
||||
data,
|
||||
});
|
||||
export const englishLanguageStatApi = (data: any) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/userSentenceLearn/queryStatListByTimeType',
|
||||
data,
|
||||
})
|
||||
|
||||
@ -1,30 +1,27 @@
|
||||
import $req from '../request';
|
||||
import $req from '../request'
|
||||
import { request } from '../request/request'
|
||||
/**
|
||||
* 获取邀请配置
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getJsapiSignatureApi = (params: anyobj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/wechatPublic/createJsapiSignature',
|
||||
params,
|
||||
});
|
||||
export const getJsapiSignatureApi = async (params: any) =>
|
||||
await request.get<any>('/wechatPublic/createJsapiSignature', params)
|
||||
/**
|
||||
* 邀请绑定
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const parentBindInviteApi = (data: anyobj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBindInvite',
|
||||
data,
|
||||
});
|
||||
export const parentBindInviteApi = (data: any) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBindInvite',
|
||||
data,
|
||||
})
|
||||
/**
|
||||
* 跳转邀请绑定页面后获取对应数据
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getInviteInfoApi = id =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/parentBindInvite/${id}`,
|
||||
});
|
||||
$req({
|
||||
method: 'get',
|
||||
url: `/parentBindInvite/${id}`,
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@ import { request } from '../request/request'
|
||||
* 家长绑定的孩子
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getParentBindChildApi = (params: anyObj) =>
|
||||
export const getParentBindChildApi = (params: any) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBindChild/list',
|
||||
@ -15,7 +15,7 @@ export const getParentBindChildApi = (params: anyObj) =>
|
||||
* 家长绑定的设备
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const getParentBindDeviceApi = async (params: anyObj) =>
|
||||
export const getParentBindDeviceApi = async (params: any) =>
|
||||
await request.get<any>('/parentBindDevice/list', params)
|
||||
/**
|
||||
* 我的孩子-绑定的家长
|
||||
@ -30,7 +30,7 @@ export const parentBindChildApi = (id: string) =>
|
||||
* 我的孩子-解除绑定家长
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const childUnBoundParentApi = (data: anyObj) =>
|
||||
export const childUnBoundParentApi = (data: any) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBindChild/unBound',
|
||||
@ -40,7 +40,7 @@ export const childUnBoundParentApi = (data: anyObj) =>
|
||||
* 我的设备-解除绑定
|
||||
* @param params 请求参数
|
||||
*/
|
||||
export const parentUnBoundDeviceApi = (data: anyObj) =>
|
||||
export const parentUnBoundDeviceApi = (data: any) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/parentBindDevice/unBound',
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
import $req from '../request';
|
||||
import $req from '../request'
|
||||
|
||||
/**
|
||||
* 家长端-电子保修卡信息
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getWarrantyCardMsgApi = () =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/getBindDeviceList',
|
||||
});
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/parentBind/getBindDeviceList',
|
||||
})
|
||||
/**
|
||||
* 家长端-申请售后
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const applyServiceApi = (data: anyObj) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/deviceWarrantyRecord',
|
||||
data,
|
||||
});
|
||||
export const applyServiceApi = (data: any) =>
|
||||
$req({
|
||||
method: 'post',
|
||||
url: '/deviceWarrantyRecord',
|
||||
data,
|
||||
})
|
||||
/**
|
||||
* 家长端-查询售后历史
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export const getServiceHistoryApi = (params: anyObj) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/deviceWarrantyRecord/list',
|
||||
params,
|
||||
});
|
||||
export const getServiceHistoryApi = (params: any) =>
|
||||
$req({
|
||||
method: 'get',
|
||||
url: '/deviceWarrantyRecord/list',
|
||||
params,
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { user } from '@/store'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { getCache } from '@/utils'
|
||||
import router from '@/router/router'
|
||||
import db from '@/utils/db'
|
||||
|
||||
const CONFIG = {
|
||||
@ -50,7 +51,7 @@ const request = async (config: Record<string, any>): Promise<any> => {
|
||||
Authorization: mergerToken ? `Bearer ${mergerToken}` : '',
|
||||
...config.headers,
|
||||
},
|
||||
complete(res: anyObj) {
|
||||
complete(res: any) {
|
||||
if (res.statusCode === 200) {
|
||||
if (res.data.code === 200) {
|
||||
return resolve(res.data)
|
||||
@ -68,9 +69,7 @@ const request = async (config: Record<string, any>): Promise<any> => {
|
||||
res.href = (config.baseURL || CONFIG.baseURL) + config.url
|
||||
if (res.statusCode === 401) {
|
||||
clear()
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index',
|
||||
})
|
||||
router.reLaunch('/pages/login/index')
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
|
||||
@ -2,7 +2,7 @@ import { type RequestOptions, type ResponseData, type ErrorResponse } from './ty
|
||||
import { HTTP_STATUS, ERROR_MSG } from './config'
|
||||
import router from '@/router/router'
|
||||
import db from '@/utils/db'
|
||||
import toast from '@/utils/toast'
|
||||
import toast from '@/utils/hud'
|
||||
|
||||
// 请求拦截器
|
||||
export const requestInterceptor = async (options: RequestOptions): Promise<RequestOptions> => {
|
||||
|
||||
@ -13,6 +13,8 @@
|
||||
</uni-nav-bar>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import router from '@/router/router'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
@ -26,32 +28,19 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const emits = defineEmits(['back']);
|
||||
const emits = defineEmits(['back'])
|
||||
function onLeftClick() {
|
||||
emits('back');
|
||||
emits('back')
|
||||
if (props.custom) {
|
||||
console.log('自定义返回路径');
|
||||
return;
|
||||
return
|
||||
}
|
||||
console.log('默认组件返回路径');
|
||||
const canNavBack = getCurrentPages();
|
||||
const canNavBack = getCurrentPages()
|
||||
if (canNavBack && canNavBack.length > 1) {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
// if (canNavBack[canNavBack.length - 1].route === canNavBack[canNavBack.length - 2].route) {
|
||||
// uni.navigateTo({
|
||||
// url: `/${canNavBack[canNavBack.length - 3].route}`,
|
||||
// });
|
||||
// } else {
|
||||
// uni.navigateBack({
|
||||
// delta: 1,
|
||||
// });
|
||||
// }
|
||||
router.navigateBack()
|
||||
} else {
|
||||
history.back();
|
||||
history.back()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -8,7 +8,11 @@
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<wd-popup v-model="showCalenderPopup" position="bottom" custom-style="border-radius: 30rpx 30rpx 0 0;">
|
||||
<wd-popup
|
||||
v-model="showCalenderPopup"
|
||||
position="bottom"
|
||||
custom-style="border-radius: 30rpx 30rpx 0 0;"
|
||||
>
|
||||
<student-calender v-model:value="currentDate"></student-calender>
|
||||
<view class="calender-popup-footer">
|
||||
<wd-button :round="false" class="cancel" @click="closeCalender">取消</wd-button>
|
||||
@ -18,22 +22,22 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang=ts>
|
||||
import {ref, watch} from "vue";
|
||||
import StudentCalender from "@/components/student-calendar/index"
|
||||
import dayjs from "dayjs";
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import StudentCalender from '@/components/student-calendar/index.vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: [String, dayjs],
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:value', 'change'])
|
||||
|
||||
const showCalenderPopup = ref(false)
|
||||
|
||||
const currentDate = ref("")
|
||||
const currentDate = ref('')
|
||||
|
||||
const openCalender = () => {
|
||||
showCalenderPopup.value = true
|
||||
@ -43,17 +47,21 @@ const closeCalender = () => {
|
||||
showCalenderPopup.value = false
|
||||
}
|
||||
|
||||
watch(() => props.value, value => {
|
||||
if (value) {
|
||||
currentDate.value = typeof value === "string" ? value : value.format('YYYY-MM-DD')
|
||||
} else {
|
||||
currentDate.value = dayjs().format('YYYY-MM-DD')
|
||||
}
|
||||
}, {immediate: true, deep: true})
|
||||
watch(
|
||||
() => props.value,
|
||||
value => {
|
||||
if (value) {
|
||||
currentDate.value = typeof value === 'string' ? value : value.format('YYYY-MM-DD')
|
||||
} else {
|
||||
currentDate.value = dayjs().format('YYYY-MM-DD')
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
const confirm = () => {
|
||||
emit('update:value', dayjs(currentDate.value))
|
||||
emit("change", dayjs(currentDate.value))
|
||||
emit('change', dayjs(currentDate.value))
|
||||
closeCalender()
|
||||
}
|
||||
</script>
|
||||
@ -84,8 +92,8 @@ const confirm = () => {
|
||||
width: 230rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #EFEFFF;
|
||||
color: #615DFF;
|
||||
background-color: #efefff;
|
||||
color: #615dff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
@ -93,7 +101,7 @@ const confirm = () => {
|
||||
width: 440rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #615DFF;
|
||||
background-color: #615dff;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
|
||||
@ -1,46 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import router from '@/router/router'
|
||||
|
||||
// import { handleBack } from '../../hooks';
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
}>();
|
||||
title: string
|
||||
}>()
|
||||
|
||||
function handleBack() {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
router.navigateBack()
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<wd-navbar
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
safeAreaInsetTop
|
||||
fixed
|
||||
placeholder
|
||||
left-arrow
|
||||
:z-index="0"
|
||||
@click-left="handleBack"
|
||||
>
|
||||
<template #left>
|
||||
<image
|
||||
src="https://box-1313840333.cos.ap-guangzhou.myqcloud.com/subpackage/back.svg"
|
||||
class="back_icon"
|
||||
></image>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<wd-navbar
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
safeAreaInsetTop
|
||||
fixed
|
||||
placeholder
|
||||
left-arrow
|
||||
:z-index="0"
|
||||
@click-left="handleBack"
|
||||
>
|
||||
<template #left>
|
||||
<image
|
||||
src="https://box-1313840333.cos.ap-guangzhou.myqcloud.com/subpackage/back.svg"
|
||||
class="back_icon"
|
||||
></image>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.back_icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
:deep(.wd-navbar) {
|
||||
.wd-navbar {
|
||||
&__left {
|
||||
padding-left: $space;
|
||||
width: 48rpx;
|
||||
}
|
||||
&__title {
|
||||
text-align: center;
|
||||
}
|
||||
.wd-navbar {
|
||||
&__left {
|
||||
padding-left: $space;
|
||||
width: 48rpx;
|
||||
}
|
||||
&__title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -22,11 +22,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { bindApplyStore } from '@/store'
|
||||
import router from '@/router/router'
|
||||
|
||||
import { getCache } from '@/utils'
|
||||
import db from '@/utils/db'
|
||||
|
||||
const props = withDefaults(
|
||||
@ -45,7 +44,7 @@ const notice = ref(false)
|
||||
function switchTab(item, index) {
|
||||
currentIndex.value = index
|
||||
let url = item.pagePath
|
||||
uni.redirectTo({ url: url })
|
||||
router.redirectTo({ path: url })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@ -1,44 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name: string;
|
||||
size?: string | number;
|
||||
color?: string;
|
||||
img?: boolean;
|
||||
}>(),
|
||||
{ img: false, size: '40rpx' },
|
||||
);
|
||||
defineProps<{
|
||||
name: string
|
||||
size?: string | number
|
||||
color?: string
|
||||
img?: boolean
|
||||
}>(),
|
||||
{ img: false, size: '40rpx' },
|
||||
)
|
||||
|
||||
const emit = defineEmits(['click']);
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
defineOptions({
|
||||
options: {
|
||||
virtualHost: true,
|
||||
},
|
||||
});
|
||||
options: {
|
||||
virtualHost: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<nut-icon
|
||||
v-if="props.img"
|
||||
:name="`https://box-1313840333.cos.ap-guangzhou.myqcloud.com/mobole-terminal/icon/${props.name}.svg`"
|
||||
:width="props.size"
|
||||
:height="props.size"
|
||||
@click="(e: anyObj) => emit('click', e)"
|
||||
></nut-icon>
|
||||
<nut-icon
|
||||
v-else
|
||||
font-class-name="iconfont"
|
||||
class-prefix="icon"
|
||||
:name="props.name"
|
||||
:custom-color="props.color"
|
||||
:size="props.size"
|
||||
:width="props.size"
|
||||
:height="props.size"
|
||||
@click="(e: anyObj) => emit('click', e)"
|
||||
></nut-icon>
|
||||
<nut-icon
|
||||
v-if="props.img"
|
||||
:name="`https://box-1313840333.cos.ap-guangzhou.myqcloud.com/mobole-terminal/icon/${props.name}.svg`"
|
||||
:width="props.size"
|
||||
:height="props.size"
|
||||
@click="(e: any) => emit('click', e)"
|
||||
></nut-icon>
|
||||
<nut-icon
|
||||
v-else
|
||||
font-class-name="iconfont"
|
||||
class-prefix="icon"
|
||||
:name="props.name"
|
||||
:custom-color="props.color"
|
||||
:size="props.size"
|
||||
:width="props.size"
|
||||
:height="props.size"
|
||||
@click="(e: any) => emit('click', e)"
|
||||
></nut-icon>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.iconfont {
|
||||
color: $uni-text-color;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
</style>
|
||||
|
||||
12
src/components/mjui/mj-empty/mj-empty.vue
Normal file
12
src/components/mjui/mj-empty/mj-empty.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'mj-empty' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tui-no-data imgUrl="/static/nodata.png">暂无数据</tui-no-data>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mj-empty {
|
||||
}
|
||||
</style>
|
||||
80
src/components/mjui/mj-list-cell/mj-list-cell.vue
Normal file
80
src/components/mjui/mj-list-cell/mj-list-cell.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'mj-list-cell' })
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
icon?: string
|
||||
iconWidth?: any
|
||||
title?: string
|
||||
height?: any
|
||||
arrow?: boolean
|
||||
}>(),
|
||||
{
|
||||
iconWidth: 40,
|
||||
height: 60,
|
||||
arrow: true,
|
||||
}
|
||||
)
|
||||
const { icon, iconWidth, title, arrow, height } = props
|
||||
const emit = defineEmits(['click'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tui-list-cell lineLeft="0" v-bind="$attrs" :arrow="arrow" @click="emit('click')">
|
||||
<view
|
||||
class="cell-box"
|
||||
:style="{
|
||||
height: height + 'rpx',
|
||||
paddingRight: arrow ? 40 + 'rpx' : 0,
|
||||
}"
|
||||
>
|
||||
<view class="left">
|
||||
<view class="mj-icon" v-if="icon">
|
||||
<image
|
||||
:style="{
|
||||
width: iconWidth + 'rpx',
|
||||
}"
|
||||
:src="icon"
|
||||
class="cell-logo"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
</view>
|
||||
<text class="cell-title">{{ title }}</text>
|
||||
</view>
|
||||
<view class="right">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</tui-list-cell>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cell-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cell-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cell-logo {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
}
|
||||
|
||||
.mj-icon {
|
||||
width: 50rpx;
|
||||
margin-right: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
28
src/components/mjui/mj-list/mj-list.vue
Normal file
28
src/components/mjui/mj-list/mj-list.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'mj-list' })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
round?: boolean
|
||||
margin?: string
|
||||
}>(),
|
||||
{
|
||||
round: false,
|
||||
margin: '0',
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<!-- 支付宝小程序中,必须要再套一层。class在自定义组件上几乎是失效的 -->
|
||||
<view
|
||||
class="mj-list"
|
||||
:class="{
|
||||
round: props.round,
|
||||
}"
|
||||
:style="{ margin: props.margin }"
|
||||
>
|
||||
<tui-list-view v-bind="$attrs"><slot /></tui-list-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
188
src/components/mjui/mj-page/mj-page.vue
Normal file
188
src/components/mjui/mj-page/mj-page.vue
Normal file
@ -0,0 +1,188 @@
|
||||
<script setup lang="ts">
|
||||
import dict from '@/utils/dict'
|
||||
import page from '@/utils/page'
|
||||
import {
|
||||
dialog_dict_key,
|
||||
type DialogOptions,
|
||||
hide_dict_key,
|
||||
loading_dict_key,
|
||||
toast_bg_colors,
|
||||
toast_dict_key,
|
||||
} from '@/utils/hud'
|
||||
import { onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
const props = defineProps<{
|
||||
background?: any
|
||||
padding?: any
|
||||
}>()
|
||||
|
||||
defineOptions({ name: 'MjPage' })
|
||||
|
||||
// 页面路径
|
||||
let pagePath = ''
|
||||
|
||||
const bgColor = ref(toast_bg_colors.info)
|
||||
// toast的引用
|
||||
const toastRef = ref(null as any)
|
||||
// 显示toast
|
||||
function toast(type: string, msg: string, duration: number) {
|
||||
bgColor.value = toast_bg_colors[type]
|
||||
toastRef?.value.showTips({
|
||||
msg,
|
||||
duration,
|
||||
})
|
||||
}
|
||||
|
||||
// loading
|
||||
const loadingRef = ref({
|
||||
show: false,
|
||||
text: undefined,
|
||||
})
|
||||
// 显示loading
|
||||
function loading(text: any) {
|
||||
loadingRef.value.show = true
|
||||
loadingRef.value.text = text
|
||||
}
|
||||
// 隐藏loading
|
||||
function hide() {
|
||||
loadingRef.value.show = false
|
||||
}
|
||||
|
||||
// dialog的引用
|
||||
const dialog_default_title = '提示'
|
||||
const dialog_default_buttons = [
|
||||
{
|
||||
text: '取消',
|
||||
color: '#999',
|
||||
},
|
||||
{
|
||||
text: '确定',
|
||||
color: '#333',
|
||||
},
|
||||
]
|
||||
|
||||
const dialogRef = ref({
|
||||
show: false,
|
||||
title: dialog_default_title,
|
||||
buttons: dialog_default_buttons,
|
||||
close: undefined as any,
|
||||
click: undefined as any,
|
||||
content: '',
|
||||
maskClosable: false,
|
||||
})
|
||||
|
||||
// 显示dialog
|
||||
function dialog(options: DialogOptions) {
|
||||
if (!options.title) {
|
||||
options.title = dialog_default_title
|
||||
}
|
||||
if (!options.buttons) {
|
||||
options.buttons = cloneDeep(dialog_default_buttons)
|
||||
// 取消按钮
|
||||
if (options.hideCancel) {
|
||||
// 删除取消按钮
|
||||
options.buttons.shift()
|
||||
options.cancel = undefined
|
||||
} else {
|
||||
if (options.cancelText) {
|
||||
options.buttons[0].text = options.cancelText
|
||||
}
|
||||
if (options.cancelColor) {
|
||||
options.buttons[0].color = options.cancelColor
|
||||
}
|
||||
}
|
||||
|
||||
// 确定按钮
|
||||
const okIdx = options.buttons.length - 1
|
||||
if (options.okText) {
|
||||
options.buttons[okIdx].text = options.okText
|
||||
}
|
||||
if (options.okColor) {
|
||||
options.buttons[okIdx].color = options.okColor
|
||||
}
|
||||
} else {
|
||||
// 自定义按钮
|
||||
options.cancel = undefined
|
||||
options.ok = undefined
|
||||
}
|
||||
|
||||
// 点击事件处理
|
||||
const clickFn = options.click
|
||||
const okFn = options.ok
|
||||
const cancelFn = options.cancel
|
||||
options.click = (e: any) => {
|
||||
const hide = () => {
|
||||
dialogRef.value.show = false
|
||||
}
|
||||
if (clickFn) {
|
||||
clickFn(hide, e)
|
||||
}
|
||||
// 点击了取消按钮
|
||||
if (cancelFn && e.index === 0) {
|
||||
cancelFn()
|
||||
hide()
|
||||
}
|
||||
// 点击了确定按钮
|
||||
const okIdx = (options.buttons?.length || 1) - 1
|
||||
if (okFn && e.index === okIdx) {
|
||||
okFn(hide)
|
||||
}
|
||||
}
|
||||
dialogRef.value = options as any
|
||||
dialogRef.value.show = true
|
||||
}
|
||||
|
||||
function init() {
|
||||
pagePath = page.getPagePath()
|
||||
dict.setFn(toast_dict_key, toast)
|
||||
dict.setFn(loading_dict_key, loading)
|
||||
dict.setFn(hide_dict_key, hide)
|
||||
dict.setFn(dialog_dict_key, dialog)
|
||||
}
|
||||
|
||||
function deinit() {
|
||||
dict.popPageFns(pagePath)
|
||||
}
|
||||
|
||||
onMounted(init)
|
||||
onActivated(init)
|
||||
onUnmounted(deinit)
|
||||
onDeactivated(deinit)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="global-wrapper"
|
||||
:style="{
|
||||
background: props.background,
|
||||
}"
|
||||
>
|
||||
<view class="global-page">
|
||||
<view
|
||||
class="global-content"
|
||||
:style="{
|
||||
padding: props.padding,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
<tui-tips ref="toastRef" :backgroundColor="bgColor" position="center"></tui-tips>
|
||||
<tui-loading
|
||||
v-if="loadingRef.show"
|
||||
maskColor="rgba(0, 0, 0, 0)"
|
||||
:text="loadingRef.text"
|
||||
is-mask
|
||||
></tui-loading>
|
||||
<tui-dialog
|
||||
:buttons="dialogRef.buttons"
|
||||
:show="dialogRef.show"
|
||||
:title="dialogRef.title"
|
||||
:maskClosable="dialogRef.maskClosable"
|
||||
@close="dialogRef.close"
|
||||
@click="dialogRef.click"
|
||||
>
|
||||
<template v-slot:content>{{ dialogRef.content }}</template>
|
||||
</tui-dialog>
|
||||
</view>
|
||||
</template>
|
||||
37
src/components/mjui/mj-password/mj-password.vue
Normal file
37
src/components/mjui/mj-password/mj-password.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
defineOptions({ name: 'mj-password' })
|
||||
|
||||
// 对于双向绑定的属性,小程序端不能依靠v-bind="$attrs"来实现双向绑定
|
||||
const props = defineProps<{
|
||||
modelValue?: any
|
||||
}>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(val) {
|
||||
emit('update:modelValue', val)
|
||||
},
|
||||
})
|
||||
|
||||
const password = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tui-input v-model="modelValue" placeholder="请输入密码" :password="password" v-bind="$attrs">
|
||||
<template v-slot:right>
|
||||
<tui-icon
|
||||
:name="password ? 'unseen' : 'seen'"
|
||||
color="#B2B2B2"
|
||||
margin="0 0 0
|
||||
20rpx"
|
||||
@click="password = !password"
|
||||
/>
|
||||
</template>
|
||||
</tui-input>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
41
src/components/mjui/mj-tabbar/mj-tabbar.ts
Normal file
41
src/components/mjui/mj-tabbar/mj-tabbar.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import router from '@/router/router'
|
||||
import page from '@/utils/page'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export const useTabbarStore = defineStore('tabbar', () => {
|
||||
const list = [
|
||||
{
|
||||
pagePath: '/pages/home/home',
|
||||
text: '首页',
|
||||
iconPath: '/static/footerBar/home-icon-link.png',
|
||||
selectedIconPath: '/static/footerBar/home-icon-active.png',
|
||||
},
|
||||
{
|
||||
pagePath: '/pages/bill/bill',
|
||||
text: '学费账单',
|
||||
iconPath: '/static/footerBar/tuitionBill-icon-link.png',
|
||||
selectedIconPath: '/static/footerBar/tuitionBill-icon-active.png',
|
||||
},
|
||||
{
|
||||
pagePath: '/pages/mine/mine',
|
||||
text: '我的',
|
||||
iconPath: '/static/footerBar/main-icon-link.png',
|
||||
selectedIconPath: '/static/footerBar/main-icon-active.png',
|
||||
},
|
||||
]
|
||||
const currentUrl = page.getPagePath()
|
||||
const current = ref(list.findIndex(item => item.pagePath === currentUrl))
|
||||
|
||||
function switchTab(e: any) {
|
||||
if (current.value === e.index) return
|
||||
current.value = e.index
|
||||
router.switchTab(list[e.index].pagePath)
|
||||
}
|
||||
|
||||
return {
|
||||
list,
|
||||
current,
|
||||
switchTab,
|
||||
}
|
||||
})
|
||||
23
src/components/mjui/mj-tabbar/mj-tabbar.vue
Normal file
23
src/components/mjui/mj-tabbar/mj-tabbar.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { useTabbarStore } from './mj-tabbar'
|
||||
|
||||
const { list, current, switchTab } = useTabbarStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tui-tabbar
|
||||
:current="current"
|
||||
:unlined="true"
|
||||
:tabBar="list"
|
||||
color="#777"
|
||||
selectedColor="#4575f5"
|
||||
@click="switchTab"
|
||||
></tui-tabbar>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
/* 修复bug */
|
||||
.tui-unlined::before {
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
6
src/components/mjui/mjui.scss
Normal file
6
src/components/mjui/mjui.scss
Normal file
@ -0,0 +1,6 @@
|
||||
.mj-list.round {
|
||||
.tui-list-content {
|
||||
border-radius: 20rpx !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
219
src/components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal file
219
src/components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal file
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<view @touchmove.stop.prevent>
|
||||
<view class="tui-actionsheet" :class="{'tui-actionsheet-show':show,'tui-actionsheet-radius':radius}"
|
||||
:style="{zIndex:getZIndex}">
|
||||
<view class="tui-actionsheet-tips" :style="{fontSize:size+'rpx',color:color}" v-if="tips">
|
||||
{{tips}}
|
||||
</view>
|
||||
<view :class="[isCancel?'tui-operate-box':'']">
|
||||
<block v-for="(item,index) in itemList" :key="index">
|
||||
<view class="tui-actionsheet-btn tui-actionsheet-divider"
|
||||
:class="{'tui-btn-last':!isCancel && index==itemList.length-1}"
|
||||
hover-class="tui-actionsheet-hover" :hover-stay-time="150" :data-index="index"
|
||||
:style="{color:item.color || textColor,fontSize:(item.size || textSize)+'rpx'}"
|
||||
@tap="handleClickItem">{{item[textField]}}</view>
|
||||
</block>
|
||||
</view>
|
||||
<view class="tui-actionsheet-btn tui-actionsheet-cancel" hover-class="tui-actionsheet-hover"
|
||||
:hover-stay-time="150" :style="{fontSize:textSize+'rpx',color:cancelColor}" v-if="isCancel"
|
||||
@tap="handleClickCancel">取消</view>
|
||||
</view>
|
||||
<view class="tui-actionsheet-mask" :class="{'tui-mask-show':show}" :style="{background:maskColor,zIndex:zIndex}"
|
||||
@tap="handleClickMask"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiActionsheet",
|
||||
emits: ['click', 'cancel'],
|
||||
props: {
|
||||
//显示操作菜单
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//菜单按钮数组,自定义文本颜色,红色参考色:#e53a37
|
||||
itemList: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [{
|
||||
text: "确定",
|
||||
color: "#2B2B2B",
|
||||
size: 34
|
||||
}]
|
||||
}
|
||||
},
|
||||
textField: {
|
||||
type: String,
|
||||
default: "text"
|
||||
},
|
||||
textColor: {
|
||||
type: String,
|
||||
default: '#2B2B2B'
|
||||
},
|
||||
textSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
//点击遮罩 是否可关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//v2.1.0
|
||||
maskColor: {
|
||||
type: String,
|
||||
default: "rgba(0, 0, 0, 0.6)"
|
||||
},
|
||||
//提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
//提示文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: "#808080"
|
||||
},
|
||||
//提示文字大小 rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
//是否需要圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否需要取消按钮
|
||||
isCancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: '#1a1a1a'
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 998
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getZIndex() {
|
||||
return Number(this.zIndex) + 2
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickMask() {
|
||||
if (!this.maskClosable) return;
|
||||
this.handleClickCancel();
|
||||
},
|
||||
handleClickItem(e) {
|
||||
if (!this.show) return;
|
||||
const index = Number(e.currentTarget.dataset.index);
|
||||
this.$emit('click', {
|
||||
index: index,
|
||||
...this.itemList[index]
|
||||
});
|
||||
},
|
||||
handleClickCancel() {
|
||||
this.$emit('cancel');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-actionsheet {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
visibility: hidden;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.25s ease-in-out;
|
||||
background-color: #F7F7F7;
|
||||
min-height: 100rpx;
|
||||
}
|
||||
|
||||
.tui-actionsheet-radius {
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-actionsheet-show {
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-actionsheet-tips {
|
||||
width: 100%;
|
||||
padding: 40rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-operate-box {
|
||||
padding-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.tui-actionsheet-btn {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-btn-last {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tui-actionsheet-divider::before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
border-top: 1rpx solid #E7E7E7;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.tui-actionsheet-cancel {
|
||||
color: #1a1a1a;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tui-actionsheet-hover {
|
||||
background-color: #f7f7f9;
|
||||
}
|
||||
|
||||
.tui-actionsheet-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
140
src/components/thorui/tui-alert/tui-alert.vue
Normal file
140
src/components/thorui/tui-alert/tui-alert.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="tui-alert-class tui-alert-box" :class="[show?'tui-alert-show':'']">
|
||||
<view class="tui-alert-content" :style="{fontSize:size+'rpx',color:color}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="tui-alert-btn" :style="{color:getColor}" hover-class="tui-alert-btn-hover"
|
||||
:hover-stay-time="150" @tap.stop="handleClick">{{btnText}}</view>
|
||||
</view>
|
||||
<view class="tui-alert-mask" :class="[show?'tui-alert-mask-show':'']" @tap.stop="handleClickCancel"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiAlert",
|
||||
emits: ['click', 'cancel'],
|
||||
props: {
|
||||
//控制显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//提示信息字体大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
//提示信息字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: "#333"
|
||||
},
|
||||
//按钮字体颜色
|
||||
btnColor: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
btnText: {
|
||||
type: String,
|
||||
default: "确定"
|
||||
},
|
||||
//点击遮罩 是否可关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getColor() {
|
||||
return this.btnColor || (uni && uni.$tui && uni.$tui.color.danger) || '#EB0909';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(e) {
|
||||
if (!this.show) return;
|
||||
this.$emit('click', {});
|
||||
},
|
||||
handleClickCancel() {
|
||||
if (!this.maskClosable) return;
|
||||
this.$emit('cancel');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-alert-box {
|
||||
position: fixed;
|
||||
width: 560rpx;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
background-color: #fff;
|
||||
transition: all 0.3s ease-in-out;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
opacity: 0;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.tui-alert-show {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-alert-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-alert-mask-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-alert-content {
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
padding: 98rpx 48rpx 92rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tui-alert-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
font-size: 32rpx;
|
||||
line-height: 32rpx;
|
||||
}
|
||||
|
||||
.tui-alert-btn-hover {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.tui-alert-btn::before {
|
||||
width: 100%;
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: 1rpx solid #E0E0E0;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
205
src/components/thorui/tui-alerts/tui-alerts.vue
Normal file
205
src/components/thorui/tui-alerts/tui-alerts.vue
Normal file
@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<view class="tui-alert__wrap"
|
||||
:style="{backgroundColor:backgroundColor?backgroundColor:getColor(type),borderRadius:radius,paddingTop:padding[0] || 0,paddingRight:padding[1]||0,paddingBottom:padding[2] || padding[0]||0,paddingLeft:padding[3] || padding[1]||0}">
|
||||
<view class="tui-alert__shrink" @tap.stop="leftClick">
|
||||
<slot name="left"></slot>
|
||||
<icon :type="type" :size="iconSize" :color="iconColor" v-if="!isLeft"></icon>
|
||||
</view>
|
||||
<view class="tui-alert__content" :class="{'tui-text__p-left':!isLeft,'tui-text__p-right':closable}"
|
||||
@tap.stop="onClick">
|
||||
<text class="tui-alert__text" :style="{fontSize:size,color:color}" v-if="title">{{title}}</text>
|
||||
<text class="tui-alert__text tui-desc__padding" :class="{'tui-alert__single':single}"
|
||||
:style="{fontSize:descSize,color:descColor}" v-if="desc">{{desc}}</text>
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<view class="tui-alert__shrink">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
<icon @tap.stop="close" type="cancel" :size="closeSize" :color="closeColor" v-if="closable"
|
||||
:class="{'tui-alert__icon-close':desc}">
|
||||
</icon>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-alerts",
|
||||
emits: ['leftClick', 'click', 'close'],
|
||||
props: {
|
||||
//info, success, warn, waiting,clear
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info'
|
||||
},
|
||||
//背景色,如果设置type对应颜色失效
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//nvue不支持简写,['20rpx','30rpx','20rpx','30rpx']=>[上,右,下,左]
|
||||
padding: {
|
||||
type: Array,
|
||||
default () {
|
||||
return ['20rpx', '30rpx']
|
||||
}
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: '6rpx'
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//icon字体大小,px
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
closeColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//关闭icon字体大小,px
|
||||
closeSize: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
//是否自定义左侧内容
|
||||
isLeft: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isRight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '14px'
|
||||
},
|
||||
desc: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
descColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
descSize: {
|
||||
type: String,
|
||||
default: '12px'
|
||||
},
|
||||
//描述文字单行展示,超出隐藏
|
||||
single: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getColor(type) {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
const color = (global && global.primary) || '#5677fc'
|
||||
const colors = {
|
||||
'success': (global && global.success) || '#07c160',
|
||||
'warn': (global && global.warning) || '#ff7900',
|
||||
'clear': (global && global.danger) || '#EB0909'
|
||||
}
|
||||
return colors[type] ? colors[type] : color;
|
||||
},
|
||||
leftClick() {
|
||||
this.$emit('leftClick', {})
|
||||
},
|
||||
onClick() {
|
||||
this.$emit('click', {})
|
||||
},
|
||||
close() {
|
||||
this.$emit('close', {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-alert__wrap {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-alert__shrink {
|
||||
/* #ifndef APP-NVUE */
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-alert__content {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-alert__text {
|
||||
/* #ifndef APP-NVUE */
|
||||
word-break: break-all;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-desc__padding {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.tui-text__p-left {
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
.tui-text__p-right {
|
||||
padding-right: 60rpx;
|
||||
}
|
||||
|
||||
.tui-alert__single {
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tui-alert__icon-close {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 16rpx;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
151
src/components/thorui/tui-amount-inwords/tui-amount-inwords.vue
Normal file
151
src/components/thorui/tui-amount-inwords/tui-amount-inwords.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<text v-if="show && money" class="tui-amount-inwords"
|
||||
:style="{fontSize:size+'rpx',color:color,fontWeight:fontWeight,padding:padding}">{{money}}</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-amount-inwords",
|
||||
emits: ['change'],
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 32
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
fontWeight: {
|
||||
type: [Number, String],
|
||||
default: 400
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
money: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue, oldValue) {
|
||||
this.money = this.getAmountInWords(newValue)
|
||||
this.$emit('change', {
|
||||
moeny: this.money
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.money = this.getAmountInWords(this.value)
|
||||
this.$emit('change', {
|
||||
moeny: this.money
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
trimAll: function(value) {
|
||||
return value.toString().replace(/\s+/g, "").replace('¥',"")
|
||||
},
|
||||
getAmountInWords(money) {
|
||||
money = this.trimAll(money)
|
||||
//汉字的数字
|
||||
let cnNums = new Array('零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖');
|
||||
//基本单位
|
||||
let cnIntRadice = new Array('', '拾', '佰', '仟');
|
||||
//对应整数部分扩展单位
|
||||
let cnIntUnits = new Array('', '万', '亿', '兆');
|
||||
//对应小数部分单位
|
||||
let cnDecUnits = new Array('角', '分', '毫', '厘');
|
||||
//整数金额时后面跟的字符
|
||||
let cnInteger = '整';
|
||||
//整型完以后的单位
|
||||
let cnIntLast = '元';
|
||||
//最大处理的数字
|
||||
let maxNum = 999999999999999.9999;
|
||||
//金额整数部分
|
||||
let integerNum;
|
||||
//金额小数部分
|
||||
let decimalNum;
|
||||
//输出的中文金额字符串
|
||||
let chineseStr = '';
|
||||
//分离金额后用的数组,预定义
|
||||
let parts;
|
||||
if (money == '') {
|
||||
return '';
|
||||
}
|
||||
money = parseFloat(money);
|
||||
if (money >= maxNum) {
|
||||
//超出最大处理数字
|
||||
return '';
|
||||
}
|
||||
if (money == 0) {
|
||||
chineseStr = cnNums[0] + cnIntLast + cnInteger;
|
||||
return chineseStr;
|
||||
}
|
||||
//转换为字符串
|
||||
money = money.toString();
|
||||
if (money.indexOf('.') == -1) {
|
||||
integerNum = money;
|
||||
decimalNum = '';
|
||||
} else {
|
||||
parts = money.split('.');
|
||||
integerNum = parts[0];
|
||||
decimalNum = parts[1].substr(0, 4);
|
||||
}
|
||||
//获取整型部分转换
|
||||
if (parseInt(integerNum, 10) > 0) {
|
||||
var zeroCount = 0;
|
||||
var IntLen = integerNum.length;
|
||||
for (var i = 0; i < IntLen; i++) {
|
||||
var n = integerNum.substr(i, 1);
|
||||
var p = IntLen - i - 1;
|
||||
var q = p / 4;
|
||||
var m = p % 4;
|
||||
if (n == '0') {
|
||||
zeroCount++;
|
||||
} else {
|
||||
if (zeroCount > 0) {
|
||||
chineseStr += cnNums[0];
|
||||
}
|
||||
//归零
|
||||
zeroCount = 0;
|
||||
chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
|
||||
}
|
||||
if (m == 0 && zeroCount < 4) {
|
||||
chineseStr += cnIntUnits[q];
|
||||
}
|
||||
}
|
||||
chineseStr += cnIntLast;
|
||||
}
|
||||
//小数部分
|
||||
if (decimalNum != '') {
|
||||
let decLen = decimalNum.length;
|
||||
for (let i = 0; i < decLen; i++) {
|
||||
let n = decimalNum.substr(i, 1);
|
||||
if (n != '0') {
|
||||
chineseStr += cnNums[Number(n)] + cnDecUnits[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chineseStr == '') {
|
||||
chineseStr += cnNums[0] + cnIntLast + cnInteger;
|
||||
} else if (decimalNum == '') {
|
||||
chineseStr += cnInteger;
|
||||
}
|
||||
return chineseStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
129
src/components/thorui/tui-badge/tui-badge.vue
Normal file
129
src/components/thorui/tui-badge/tui-badge.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view :class="[dot ? 'tui-badge-dot' : 'tui-badge', !dot ? 'tui-badge-scale' : '']"
|
||||
:style="{ top: top, right: right, position: absolute ? 'absolute' : 'static', transform: getStyle, margin: margin,background:getBackground,color:getColor }"
|
||||
@tap="handleClick">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiBadge',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//primary,warning,green,danger,white,black,gray,white_red
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
//是否是圆点
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//是否绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: '-8rpx'
|
||||
},
|
||||
right: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//缩放比例
|
||||
scaleRatio: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//水平方向移动距离
|
||||
translateX: {
|
||||
type: String,
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getStyle() {
|
||||
return `scale(${this.scaleRatio}) translateX(${this.translateX})`;
|
||||
},
|
||||
getBackground() {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
let color = {
|
||||
'primary': (global && global.primary) || '#5677fc',
|
||||
'green': (global && global.success) || '#07c160',
|
||||
'warning': (global && global.warning) || '#ff7900',
|
||||
'danger': (global && global.danger) || '#EB0909',
|
||||
'white': '#fff',
|
||||
'black': '#000',
|
||||
'gray': '#ededed',
|
||||
'red': (global && global.pink) || '#f74d54',
|
||||
'pink': (global && global.pink) || '#f74d54',
|
||||
'white_red': '#fff',
|
||||
'white_primary': '#fff',
|
||||
'white_green': '#fff',
|
||||
'white_warning': '#fff',
|
||||
'white_pink': '#fff'
|
||||
} [this.type]
|
||||
return color
|
||||
},
|
||||
getColor() {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
let color = {
|
||||
'primary': '#fff',
|
||||
'green': '#fff',
|
||||
'warning': '#fff',
|
||||
'danger': '#fff',
|
||||
'white': '#333',
|
||||
'black': '#fff',
|
||||
'gray': '#999',
|
||||
'red': '#fff',
|
||||
'pink': (global && global.pink) || '#f74d54',
|
||||
'white_red': (global && global.danger) || '#EB0909',
|
||||
'white_primary': (global && global.primary) || '#5677fc',
|
||||
'white_green': (global && global.success) || '#07c160',
|
||||
'white_warning': (global && global.warning) || '#ff7900',
|
||||
'white_pink': (global && global.pink) || '#f74d54',
|
||||
} [this.type]
|
||||
return color
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('click', {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-badge-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.tui-badge {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
height: 36rpx;
|
||||
min-width: 36rpx;
|
||||
padding: 0 10rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-badge-scale {
|
||||
transform-origin: center center;
|
||||
}
|
||||
</style>
|
||||
72
src/components/thorui/tui-banner-arc/tui-banner-arc.vue
Normal file
72
src/components/thorui/tui-banner-arc/tui-banner-arc.vue
Normal file
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<view class="tui-banner-arc" :style="{height:height+'rpx'}">
|
||||
<view class="tui-banner--box"
|
||||
:style="{background:background,height:height+'rpx',width:width+'%',paddingLeft:(width-100)/2+'%',paddingRight:(width-100)/2+'%',left:'-'+(width-100)/2+'%'}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-banner-arc",
|
||||
// #ifdef MP-WEIXIN
|
||||
options: {
|
||||
virtualHost: true
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 400
|
||||
},
|
||||
percent: {
|
||||
type: [Number, String],
|
||||
default: 120
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.width = this.getPercent(this.percent)
|
||||
},
|
||||
watch: {
|
||||
percent(val) {
|
||||
this.width = this.getPercent(val)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
width: 120
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getPercent(val) {
|
||||
//最低值120,尽量传偶数值
|
||||
val = Number(val || 0)
|
||||
return val < 120 ? 120 : val
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-banner-arc {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-banner--box {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
border-radius: 0 0 50% 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<view @touchmove.stop.prevent="stop">
|
||||
<view class="tui-bottom-navigation" :style="{ backgroundColor: isDarkMode ? '#202020' : backgroundColor }" :class="{ 'tui-navigation-fixed': isFixed, 'tui-remove-splitLine': unlined }">
|
||||
<view
|
||||
class="tui-navigation-item"
|
||||
:class="{ 'tui-item-after_height': splitLineScale, 'tui-last-item': index == itemList.length - 1 }"
|
||||
v-for="(item, index) in itemList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="tui-item-inner" @tap="menuClick(index, item.parameter, item.type)">
|
||||
<image
|
||||
:src="getIcon(current,index, item)"
|
||||
class="tui-navigation-img"
|
||||
v-if="item.iconPath || (current == index && item.selectedIconPath && item.type == 1)"
|
||||
></image>
|
||||
<text
|
||||
class="tui-navigation-text"
|
||||
:style="{
|
||||
color: isDarkMode ? '#fff' : current == index && item.type == 1 ? getSelectColor : item.color || color,
|
||||
fontWeight: current == index && bold && item.type == 1 ? 'bold' : 'normal',
|
||||
fontSize: fontSize
|
||||
}"
|
||||
>
|
||||
{{ item.text }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="tui-navigation-popup"
|
||||
:class="{ 'tui-navigation-popup_show': showMenuIndex == index }"
|
||||
:style="{ backgroundColor: isDarkMode ? '#4c4c4c' : subMenuBgColor, left: item.popupLeft || '50%' }"
|
||||
v-if="item.itemList"
|
||||
>
|
||||
<view
|
||||
class="tui-popup-cell"
|
||||
:class="{ 'tui-first-cell': subIndex === 0, 'tui-last-cell': subIndex === item.itemList.length - 1 }"
|
||||
:hover-class="subMenuHover ? (isDarkMode ? 'tui-item-dark_hover' : 'tui-item-hover') : ''"
|
||||
:hover-stay-time="150"
|
||||
v-for="(subItem, subIndex) in item.itemList || []"
|
||||
:key="subIndex"
|
||||
@tap="subMenuClick(index, item.type, subIndex, subItem.parameter || '')"
|
||||
>
|
||||
<text class="tui-ellipsis" :style="{ color: isDarkMode ? '#fff' : subMenuColor, fontSize: subMenufontSize, lineHeight: subMenufontSize }">
|
||||
{{ subItem.text }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="tui-popup-triangle" :style="{ borderTopColor: isDarkMode ? '#4c4c4c' : subMenuBgColor }"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-navigation-mask" :class="{ 'tui-navigation-mask_show': showMenuIndex != -1 }" @tap="handleClose"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiBottomNavigation',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//当前索引
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* {
|
||||
text: 'ThorUI',
|
||||
iconPath: '/static/images/common/icon_menu_gray.png',
|
||||
selectedIconPath: '/static/images/common/icon_menu_gray.png',
|
||||
color: '#666',
|
||||
//1-选中切换,2-跳转、请求、其他操作,3-菜单
|
||||
type: 3,
|
||||
//自定义参数,类型自定义
|
||||
parameter: null,
|
||||
//子菜单left值,不传默认50%,当菜单贴近左右两边可用此参数调整
|
||||
popupLeft: '',
|
||||
itemList: [
|
||||
{
|
||||
//不建议超过6个字,请自行控制
|
||||
text: '自定义参',
|
||||
//自定义参数,类型自定义
|
||||
parameter: null
|
||||
},
|
||||
{
|
||||
text: '自定义参数',
|
||||
//自定义参数,类型自定义
|
||||
parameter: null
|
||||
}
|
||||
]
|
||||
}
|
||||
*
|
||||
* */
|
||||
itemList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#666'
|
||||
},
|
||||
//选中颜色
|
||||
selectedColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fontSize: {
|
||||
type: String,
|
||||
default: '28rpx'
|
||||
},
|
||||
//选中后字体是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//导航条背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#F8F8F8'
|
||||
},
|
||||
//item分割线高度是否缩小
|
||||
splitLineScale: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//二级菜单字体颜色
|
||||
subMenuColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//二级菜单字体大小
|
||||
subMenufontSize: {
|
||||
type: String,
|
||||
default: '28rpx'
|
||||
},
|
||||
//二级菜单背景色 深色:#4c4c4c
|
||||
subMenuBgColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//二级菜单是否有点击效果
|
||||
subMenuHover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否固定在底部
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//去除导航栏顶部的线条
|
||||
unlined: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否暗黑模式 (true:所有设置颜色失效)
|
||||
isDarkMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMenuIndex: -1 //显示的菜单index
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
getSelectColor(){
|
||||
return this.selectedColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getIcon: function(current, index, item) {
|
||||
let url = item.iconPath;
|
||||
if (item.type == 1) {
|
||||
url = current == index ? item.selectedIconPath || item.iconPath : item.iconPath;
|
||||
}
|
||||
return url;
|
||||
},
|
||||
stop() {
|
||||
return false;
|
||||
},
|
||||
handleClose() {
|
||||
this.showMenuIndex = -1;
|
||||
},
|
||||
menuClick(index, parameter, type) {
|
||||
//type:1-选中切换,2-跳转、请求、其他操作,3-菜单
|
||||
if (type == 3) {
|
||||
this.showMenuIndex = this.showMenuIndex == index ? -1 : index;
|
||||
} else {
|
||||
this.showMenuIndex = -1;
|
||||
this.$emit('click', {
|
||||
menu: 'main', //main,sub 主菜单,子菜单
|
||||
type: type,
|
||||
index: index,
|
||||
parameter: parameter || ''
|
||||
});
|
||||
}
|
||||
},
|
||||
subMenuClick(index, type, subIndex, parameter) {
|
||||
this.showMenuIndex = -1;
|
||||
this.$emit('click', {
|
||||
menu: 'sub', //main,sub 主菜单,子菜单
|
||||
type: type,
|
||||
index: index,
|
||||
subIndex: subIndex,
|
||||
parameter: parameter || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-bottom-navigation {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.tui-navigation-fixed {
|
||||
position: fixed !important;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.tui-bottom-navigation::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
border-top: 1px solid #bfbfbf;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
.tui-remove-splitLine::before {
|
||||
border-top: 0 !important;
|
||||
}
|
||||
|
||||
.tui-navigation-item {
|
||||
flex: 1;
|
||||
height: 100rpx;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-item-inner {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-navigation-item::after {
|
||||
height: 100%;
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-right: 1px solid #bfbfbf;
|
||||
transform: scaleX(0.5) translateZ(0);
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tui-item-after_height::after {
|
||||
height: 40% !important;
|
||||
top: 30% !important;
|
||||
}
|
||||
|
||||
.tui-last-item::after {
|
||||
border-right: 0 !important;
|
||||
}
|
||||
|
||||
.tui-navigation-img {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.tui-navigation-popup {
|
||||
max-width: 160%;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
border-radius: 8rpx;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.12s ease-in-out;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.tui-navigation-popup_show {
|
||||
transform: translate3d(-50%, -124rpx, 0);
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-popup-triangle {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 9rpx solid transparent;
|
||||
border-right: 9rpx solid transparent;
|
||||
border-top: 18rpx solid;
|
||||
left: 50%;
|
||||
bottom: -18rpx;
|
||||
-webkit-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
z-index: 997;
|
||||
}
|
||||
|
||||
.tui-popup-cell {
|
||||
width: 100%;
|
||||
padding: 32rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tui-popup-cell::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1rpx solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
bottom: 0;
|
||||
right: 24rpx;
|
||||
left: 24rpx;
|
||||
}
|
||||
|
||||
.tui-item-hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.tui-item-dark_hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.tui-first-cell {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-top-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
.tui-last-cell {
|
||||
border-bottom-left-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
.tui-last-cell::after {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tui-navigation-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 995;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.tui-navigation-mask_show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
119
src/components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal file
119
src/components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view @touchmove.stop.prevent>
|
||||
<view class="tui-popup-class tui-bottom-popup"
|
||||
:class="{ 'tui-popup-show': show, 'tui-popup-radius': radius,'tui-bp__safearea':isSafeArea }"
|
||||
:style="{ background: backgroundColor, height: height ? height + 'rpx' : 'auto', zIndex: zIndex,transform:`translate3d(0, ${show?translateY:'100%'}, 0)`}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="tui-popup-mask" :class="[show ? 'tui-mask-show' : '']" :style="{ zIndex: maskZIndex }" v-if="mask"
|
||||
@tap="handleClose"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiBottomPopup',
|
||||
emits: ['close'],
|
||||
props: {
|
||||
//是否需要mask
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//控制显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//高度 rpx
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//设置圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 997
|
||||
},
|
||||
maskZIndex: {
|
||||
type: [Number, String],
|
||||
default: 996
|
||||
},
|
||||
//弹层显示时,垂直方向移动的距离
|
||||
translateY: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//是否需要判断底部安全区域(主要针对iphonex以上机型)
|
||||
isSafeArea: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
this.$emit('close', {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-bottom-popup {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
min-height: 20rpx;
|
||||
}
|
||||
|
||||
.tui-bp__safearea {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tui-popup-radius {
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.tui-popup-show {
|
||||
opacity: 1;
|
||||
/* transform: translate3d(0, 0, 0); */
|
||||
}
|
||||
|
||||
.tui-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
204
src/components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal file
204
src/components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal file
@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<view :class="{ 'tui-flex-end': flexEnd }">
|
||||
<view class="tui-popup-list" :class="{ 'tui-popup-show': show,'tui-z_index':show && position!='relative' }" :style="{ width: width, backgroundColor: backgroundColor, borderRadius: radius, color: color, position: position, left: left, right: right, bottom: bottom, top: top,transform:`translate(${translateX},${translateY})` }">
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `transparent transparent ${backgroundColor} transparent`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'top'"></view>
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `${backgroundColor} transparent transparent transparent`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'bottom'"></view>
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `transparent ${backgroundColor} transparent transparent`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'left'"></view>
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `transparent transparent transparent ${backgroundColor}`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'right'"></view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view @touchmove.stop.prevent="stop" class="tui-popup-mask" :class="{ 'tui-popup-show': show }" :style="{ backgroundColor: maskBgColor }"
|
||||
v-if="mask" @tap="handleClose"></view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiBubblePopup',
|
||||
emits: ['close'],
|
||||
props: {
|
||||
//宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '300rpx'
|
||||
},
|
||||
//popup圆角
|
||||
radius: {
|
||||
type: String,
|
||||
default: '8rpx'
|
||||
},
|
||||
//popup 定位 left right top bottom值
|
||||
left: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
right: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
bottom: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
translateX:{
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
translateY:{
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#4c4c4c'
|
||||
},
|
||||
//字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//三角border-width
|
||||
borderWidth: {
|
||||
type: String,
|
||||
default: '12rpx'
|
||||
},
|
||||
//三角形方向 top left right bottom
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
//定位 left right top bottom值
|
||||
triangleLeft: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
triangleRight: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
triangleTop: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
triangleBottom: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
//定位 relative absolute fixed
|
||||
position: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
//flex-end
|
||||
flexEnd: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否需要mask
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.4)'
|
||||
},
|
||||
//控制显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
this.$emit('close', {});
|
||||
},
|
||||
stop() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-popup-list {
|
||||
z-index: 1;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-flex-end {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tui-triangle {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
z-index: 997;
|
||||
}
|
||||
|
||||
.tui-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 995;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-popup-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-z_index {
|
||||
z-index: 996;
|
||||
}
|
||||
</style>
|
||||
405
src/components/thorui/tui-button/tui-button.vue
Normal file
405
src/components/thorui/tui-button/tui-button.vue
Normal file
@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<view class="tui-button__wrap"
|
||||
:class="[(width==='100%' || !width || width===true) && (!btnSize || btnSize===true)?'tui-btn__flex-1':'',getShapeClass(shape, plain),!disabled?'tui-button__hover':'']"
|
||||
:style="{width: getWidth, height: getHeight, margin: margin}">
|
||||
<button class="tui-btn" :class="[
|
||||
plain ? 'tui-' + type + '-outline' : 'tui-btn-' + (type || 'primary'),
|
||||
getDisabledClass(disabled, type, plain),
|
||||
getShapeClass(shape, plain),
|
||||
bold ? 'tui-text-bold' : '',
|
||||
link ? 'tui-btn__link' : ''
|
||||
]" :style="{ width: getWidth, height: getHeight, lineHeight: getHeight, fontSize: getSize + 'rpx',background:getBgColor(type,plain),color:getColor(type,plain),boxShadow:shadow?getShadow(type,plain):'none' }"
|
||||
:loading="loading" :form-type="formType" :open-type="openType" :app-parameter="appParameter"
|
||||
@getuserinfo="bindgetuserinfo" @getphonenumber="bindgetphonenumber" @contact="bindcontact"
|
||||
@error="binderror" @chooseavatar="bindchooseavatar" @launchapp="bindlaunchapp" :disabled="disabled"
|
||||
@tap="handleClick">
|
||||
<slot></slot>
|
||||
</button>
|
||||
<view class="tui-button__border" :class="[getShapeClass(shape, plain),getDisabledClass(disabled, type, plain)]"
|
||||
:style="{borderColor:getBgColor(type)}" v-if="!link && plain"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiButton',
|
||||
emits: ['click', 'getuserinfo', 'contact', 'getphonenumber', 'error','chooseavatar','launchapp'],
|
||||
// #ifdef MP-WEIXIN
|
||||
behaviors: ['wx://form-field-button'],
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
behaviors: ['swan://form-field'],
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
behaviors: ['qq://form-field'],
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
behaviors: ['uni://form-field'],
|
||||
// #endif
|
||||
props: {
|
||||
//样式类型 primary, white, danger, warning, green,blue, gray,black,brown,gray-primary,gray-danger,gray-warning,gray-green
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
//是否加阴影 移除
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 宽度 rpx或 %
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
//高度 rpx
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//medium 184*40 / small 120 40/ mini 58*32
|
||||
btnSize: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//字体大小 rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//形状 circle(圆角), square(默认方形),rightAngle(平角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//link样式,去掉边框,结合plain一起使用
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//禁用后背景是否为灰色 (非空心button生效)
|
||||
disabledGray: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//是否需要阻止重复点击【默认200ms】
|
||||
preventClick: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getWidth() {
|
||||
//medium 184*40 / small 120 40/ mini 58*32
|
||||
let width = this.width;
|
||||
if (this.btnSize && this.btnSize !== true) {
|
||||
width = {
|
||||
'medium': '368rpx',
|
||||
'small': '240rpx',
|
||||
'mini': '116rpx'
|
||||
} [this.btnSize] || this.width
|
||||
}
|
||||
return width
|
||||
},
|
||||
getHeight() {
|
||||
//medium 184*40 / small 120 40/ mini 58*32
|
||||
let height = this.height || (uni && uni.$tui && uni.$tui.tuiButton.height) || '96rpx';
|
||||
if (this.btnSize && this.btnSize !== true) {
|
||||
height = {
|
||||
'medium': '80rpx',
|
||||
'small': '80rpx',
|
||||
'mini': '64rpx'
|
||||
} [this.btnSize] || '96rpx'
|
||||
}
|
||||
return height
|
||||
},
|
||||
getSize() {
|
||||
return this.size || (uni && uni.$tui && uni.$tui.tuiButton.size) || 32;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
time: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
hexToRGB(hex) {
|
||||
if (hex.length === 4) {
|
||||
let text = hex.substring(1, 4);
|
||||
hex = '#' + text + text;
|
||||
}
|
||||
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : {};
|
||||
},
|
||||
getColorByType(type, isText, plain) {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
let color = ''
|
||||
const colors = {
|
||||
'primary': (global && global.primary) || '#5677fc',
|
||||
'white': '#fff',
|
||||
'danger': (global && global.danger) || '#EB0909',
|
||||
'warning': (global && global.warning) || '#ff7900',
|
||||
'green': (global && global.success) || '#07c160',
|
||||
'blue': (global && global.blue) || '#007aff',
|
||||
'gray': '#bfbfbf',
|
||||
'black': '#333333',
|
||||
'brown': '#ac9157',
|
||||
'gray-primary': '#f2f2f2',
|
||||
'gray-danger': '#f2f2f2',
|
||||
'gray-warning': '#f2f2f2',
|
||||
'gray-green': '#f2f2f2'
|
||||
}
|
||||
if (isText) {
|
||||
if (type && ~type.indexOf('gray-')) {
|
||||
const tp = type.replace('gray-', '')
|
||||
color = colors[tp]
|
||||
} else if (type === 'white') {
|
||||
color = '#333'
|
||||
} else {
|
||||
if (plain) {
|
||||
color = colors[type]
|
||||
} else {
|
||||
color = '#fff'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
color = colors[type] || colors.primary
|
||||
}
|
||||
return color;
|
||||
},
|
||||
getShadow(type, plain) {
|
||||
const color = this.getColorByType(type)
|
||||
if (plain || !color) return 'none';
|
||||
const rgb = this.hexToRGB(color)
|
||||
return `0 10rpx 14rpx 0 rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.2)`
|
||||
},
|
||||
getBgColor(type, plain) {
|
||||
return plain ? 'transparent' : this.getColorByType(type)
|
||||
},
|
||||
getColor(type, plain) {
|
||||
return this.getColorByType(type, true, plain)
|
||||
},
|
||||
handleClick() {
|
||||
if (this.disabled) return;
|
||||
if (this.preventClick) {
|
||||
if (new Date().getTime() - this.time <= 200) return;
|
||||
this.time = new Date().getTime();
|
||||
setTimeout(() => {
|
||||
this.time = 0;
|
||||
}, 200);
|
||||
}
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
});
|
||||
},
|
||||
bindgetuserinfo({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
this.$emit('getuserinfo', detail);
|
||||
},
|
||||
bindcontact({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
this.$emit('contact', detail);
|
||||
},
|
||||
bindgetphonenumber({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
this.$emit('getphonenumber', detail);
|
||||
},
|
||||
binderror({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
this.$emit('error', detail);
|
||||
},
|
||||
bindchooseavatar({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
this.$emit('chooseavatar', detail);
|
||||
},
|
||||
bindlaunchapp({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
this.$emit('launchapp', detail);
|
||||
},
|
||||
getDisabledClass: function(disabled, type, plain) {
|
||||
let className = '';
|
||||
if (disabled && type != 'white' && type.indexOf('-') == -1) {
|
||||
let classVal = this.disabledGray ? 'tui-gray-disabled' : 'tui-dark-disabled';
|
||||
className = plain ? 'tui-dark-disabled-outline' : classVal;
|
||||
}
|
||||
return className;
|
||||
},
|
||||
getShapeClass: function(shape, plain) {
|
||||
let className = '';
|
||||
if (shape == 'circle') {
|
||||
className = plain ? 'tui-outline-fillet' : 'tui-fillet';
|
||||
} else if (shape == 'rightAngle') {
|
||||
className = plain ? 'tui-outline-rightAngle' : 'tui-rightAngle';
|
||||
}
|
||||
return className;
|
||||
},
|
||||
getHoverClass: function(disabled, type, plain) {
|
||||
let className = '';
|
||||
if (!disabled) {
|
||||
className = plain ? 'tui-outline-hover' : 'tui-' + (type || 'primary') + '-hover';
|
||||
}
|
||||
return className;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-button__wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-button__hover:active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 6rpx;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* button start*/
|
||||
|
||||
.tui-btn {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border: 0 !important;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6rpx;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-btn::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.tui-btn__flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tui-button__border {
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform-origin: 0 0;
|
||||
transform: scale(0.5, 0.5) translateZ(0);
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 12rpx;
|
||||
border: 1px solid transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tui-dark-disabled {
|
||||
opacity: 0.6 !important;
|
||||
color: #fafbfc !important;
|
||||
}
|
||||
|
||||
.tui-dark-disabled-outline {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.tui-gray-disabled {
|
||||
background: #f3f3f3 !important;
|
||||
color: #919191 !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*圆角 */
|
||||
|
||||
.tui-fillet {
|
||||
border-radius: 220rpx !important;
|
||||
}
|
||||
|
||||
.tui-fillet::after {
|
||||
border-radius: 220rpx !important;
|
||||
}
|
||||
|
||||
.tui-outline-fillet {
|
||||
border-radius: 220rpx !important;
|
||||
}
|
||||
|
||||
.tui-outline-fillet::after {
|
||||
border-radius: 220rpx !important;
|
||||
}
|
||||
|
||||
/*平角*/
|
||||
.tui-rightAngle {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.tui-rightAngle::after {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.tui-outline-rightAngle {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.tui-outline-rightAngle::after {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.tui-btn__link::after {
|
||||
border: 0 !important;
|
||||
}
|
||||
</style>
|
||||
562
src/components/thorui/tui-calendar/tui-calendar.js
Normal file
562
src/components/thorui/tui-calendar/tui-calendar.js
Normal file
@ -0,0 +1,562 @@
|
||||
/**
|
||||
* @1900-2100区间内的公历、农历互转
|
||||
* @公历转农历:solar2lunar(1987,11,01);
|
||||
* @农历转公历:lunar2solar(1987,09,10);
|
||||
*/
|
||||
let 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, 0x055c0, 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
|
||||
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:let count = calendar.lYearDays(1987) ;//count=387
|
||||
*/
|
||||
lYearDays: function(y) {
|
||||
let i, sum = 348;
|
||||
for (i = 0x8000; i > 0x8; i >>= 1) {
|
||||
sum += (calendar.lunarInfo[y - 1900] & i) ? 1 : 0;
|
||||
}
|
||||
return (sum + calendar.leapDays(y));
|
||||
},
|
||||
/**
|
||||
* 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0-12)
|
||||
* @eg:let leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
|
||||
*/
|
||||
leapMonth: function(y) { //闰字编码 \u95f0
|
||||
return (calendar.lunarInfo[y - 1900] & 0xf);
|
||||
},
|
||||
/**
|
||||
* 返回农历y年闰月的天数 若该年没有闰月则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0、29、30)
|
||||
* @eg:let leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
|
||||
*/
|
||||
leapDays: function(y) {
|
||||
if (calendar.leapMonth(y)) {
|
||||
return ((calendar.lunarInfo[y - 1900] & 0x10000) ? 30 : 29);
|
||||
}
|
||||
return (0);
|
||||
},
|
||||
/**
|
||||
* 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
|
||||
* @param lunar Year
|
||||
* @return Number (-1、29、30)
|
||||
* @eg:let MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
|
||||
*/
|
||||
monthDays: function(y, m) {
|
||||
if (m > 12 || m < 1) {
|
||||
return -1
|
||||
} //月份参数从1至12,参数错误返回-1
|
||||
return ((calendar.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29);
|
||||
},
|
||||
/**
|
||||
* 返回公历(!)y年m月的天数
|
||||
* @param solar Year
|
||||
* @return Number (-1、28、29、30、31)
|
||||
* @eg:let solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
|
||||
*/
|
||||
solarDays: function(y, m) {
|
||||
if (m > 12 || m < 1) {
|
||||
return -1
|
||||
} //若参数错误 返回-1
|
||||
let ms = m - 1;
|
||||
if (ms == 1) { //2月份的闰平规律测算后确认返回28或29
|
||||
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28);
|
||||
} else {
|
||||
return (calendar.solarMonth[ms]);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 农历年份转换为干支纪年
|
||||
* @param lYear 农历年的年份数
|
||||
* @return Cn string
|
||||
*/
|
||||
toGanZhiYear: function(lYear) {
|
||||
let ganKey = (lYear - 3) % 10;
|
||||
let zhiKey = (lYear - 3) % 12;
|
||||
if (ganKey == 0) ganKey = 10; //如果余数为0则为最后一个天干
|
||||
if (zhiKey == 0) zhiKey = 12; //如果余数为0则为最后一个地支
|
||||
return calendar.Gan[ganKey - 1] + calendar.Zhi[zhiKey - 1];
|
||||
},
|
||||
/**
|
||||
* 公历月、日判断所属星座
|
||||
* @param cMonth [description]
|
||||
* @param cDay [description]
|
||||
* @return Cn string
|
||||
*/
|
||||
toAstro: function(cMonth, cDay) {
|
||||
let 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";
|
||||
let 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 calendar.Gan[offset % 10] + calendar.Zhi[offset % 12];
|
||||
},
|
||||
/**
|
||||
* 传入公历(!)y年获得该年第n个节气的公历日期
|
||||
* @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
|
||||
* @return day Number
|
||||
* @eg:let _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;
|
||||
}
|
||||
let _table = calendar.sTermInfo[y - 1900];
|
||||
let _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()
|
||||
];
|
||||
let _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:let cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
|
||||
*/
|
||||
toChinaMonth: function(m) { // 月 => \u6708
|
||||
if (m > 12 || m < 1) {
|
||||
return -1
|
||||
} //若参数错误 返回-1
|
||||
let s = calendar.nStr3[m - 1];
|
||||
s += "\u6708"; //加上月字
|
||||
return s;
|
||||
},
|
||||
/**
|
||||
* 传入农历日期数字返回汉字表示法
|
||||
* @param lunar day
|
||||
* @return Cn string
|
||||
* @eg:let cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
|
||||
*/
|
||||
toChinaDay: function(d) { //日 => \u65e5
|
||||
let s;
|
||||
switch (d) {
|
||||
case 10:
|
||||
s = '\u521d\u5341';
|
||||
break;
|
||||
case 20:
|
||||
s = '\u4e8c\u5341';
|
||||
break;
|
||||
break;
|
||||
case 30:
|
||||
s = '\u4e09\u5341';
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
s = calendar.nStr2[Math.floor(d / 10)];
|
||||
s += calendar.nStr1[d % 10];
|
||||
}
|
||||
return (s);
|
||||
},
|
||||
/**
|
||||
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
|
||||
* @param y year
|
||||
* @return Cn string
|
||||
* @eg:let animal = calendar.getAnimal(1987) ;//animal='兔'
|
||||
*/
|
||||
getAnimal: function(y) {
|
||||
return calendar.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;
|
||||
} //年份限定、上限
|
||||
if (y == 1900 && m == 1 && d < 31) {
|
||||
return -1;
|
||||
} //下限
|
||||
let objDate;
|
||||
if (!y) { //未传参 获得当天
|
||||
objDate = new Date();
|
||||
} else {
|
||||
objDate = new Date(y, parseInt(m) - 1, d)
|
||||
}
|
||||
let i, leap = 0,
|
||||
temp = 0;
|
||||
//修正ymd参数
|
||||
y = objDate.getFullYear();
|
||||
m = objDate.getMonth() + 1;
|
||||
d = objDate.getDate();
|
||||
let offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) /
|
||||
86400000;
|
||||
for (i = 1900; i < 2101 && offset > 0; i++) {
|
||||
temp = calendar.lYearDays(i);
|
||||
offset -= temp;
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += temp;
|
||||
i--;
|
||||
}
|
||||
//是否今天
|
||||
let isTodayObj = new Date(),
|
||||
isToday = false;
|
||||
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
|
||||
isToday = true;
|
||||
}
|
||||
//星期几
|
||||
let nWeek = objDate.getDay(),
|
||||
cWeek = calendar.nStr1[nWeek];
|
||||
if (nWeek == 0) {
|
||||
nWeek = 7;
|
||||
} //数字表示周几顺应天朝周一开始的惯例
|
||||
//农历年
|
||||
let year = i;
|
||||
leap = calendar.leapMonth(i); //闰哪个月
|
||||
let isLeap = false;
|
||||
//效验闰月
|
||||
for (i = 1; i < 13 && offset > 0; i++) {
|
||||
//闰月
|
||||
if (leap > 0 && i == (leap + 1) && isLeap == false) {
|
||||
--i;
|
||||
isLeap = true;
|
||||
temp = calendar.leapDays(year); //计算农历闰月天数
|
||||
} else {
|
||||
temp = calendar.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;
|
||||
}
|
||||
//农历月
|
||||
let month = i;
|
||||
//农历日
|
||||
let day = offset + 1;
|
||||
//天干地支处理
|
||||
let sm = m - 1;
|
||||
let gzY = calendar.toGanZhiYear(year);
|
||||
//月柱 1900年1月小寒以前为 丙子月(60进制12)
|
||||
let firstNode = calendar.getTerm(year, (m * 2 - 1)); //返回当月「节」为几日开始
|
||||
let secondNode = calendar.getTerm(year, (m * 2)); //返回当月「节」为几日开始
|
||||
//依据12节气修正干支月
|
||||
let gzM = calendar.toGanZhi((y - 1900) * 12 + m + 11);
|
||||
if (d >= firstNode) {
|
||||
gzM = calendar.toGanZhi((y - 1900) * 12 + m + 12);
|
||||
}
|
||||
//传入的日期的节气与否
|
||||
let isTerm = false;
|
||||
let Term = null;
|
||||
if (firstNode == d) {
|
||||
isTerm = true;
|
||||
Term = calendar.solarTerm[m * 2 - 2];
|
||||
}
|
||||
if (secondNode == d) {
|
||||
isTerm = true;
|
||||
Term = calendar.solarTerm[m * 2 - 1];
|
||||
}
|
||||
//日柱 当月一日与 1900/1/1 相差天数
|
||||
let dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10;
|
||||
let gzD = calendar.toGanZhi(dayCyclical + d - 1);
|
||||
//该日期所属的星座
|
||||
let astro = calendar.toAstro(m, d);
|
||||
return {
|
||||
'lYear': year,
|
||||
'lMonth': month,
|
||||
'lDay': day,
|
||||
'Animal': calendar.getAnimal(year),
|
||||
'IMonthCn': (isLeap ? "\u95f0" : '') + calendar.toChinaMonth(month),
|
||||
'IDayCn': calendar.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
|
||||
isLeapMonth = !!isLeapMonth;
|
||||
let leapOffset = 0;
|
||||
let leapMonth = calendar.leapMonth(y);
|
||||
let leapDay = calendar.leapDays(y);
|
||||
if (isLeapMonth && (leapMonth != m)) {
|
||||
return -1;
|
||||
} //传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
|
||||
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) {
|
||||
return -1;
|
||||
} //超出了最大极限值
|
||||
let day = calendar.monthDays(y, m);
|
||||
let _day = day;
|
||||
//bugFix 2016-9-25
|
||||
//if month is leap, _day use leapDays method
|
||||
if (isLeapMonth) {
|
||||
_day = calendar.leapDays(y, m);
|
||||
}
|
||||
if (y < 1900 || y > 2100 || d > _day) {
|
||||
return -1;
|
||||
} //参数合法性效验
|
||||
//计算农历的时间差
|
||||
let offset = 0;
|
||||
for (let i = 1900; i < y; i++) {
|
||||
offset += calendar.lYearDays(i);
|
||||
}
|
||||
let leap = 0,
|
||||
isAdd = false;
|
||||
for (let i = 1; i < m; i++) {
|
||||
leap = calendar.leapMonth(y);
|
||||
if (!isAdd) { //处理闰月
|
||||
if (leap <= i && leap > 0) {
|
||||
offset += calendar.leapDays(y);
|
||||
isAdd = true;
|
||||
}
|
||||
}
|
||||
offset += calendar.monthDays(y, i);
|
||||
}
|
||||
//转换闰月农历 需补充该年闰月的前一个月的时差
|
||||
if (isLeapMonth) {
|
||||
offset += day;
|
||||
}
|
||||
//1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
|
||||
let stmap = Date.UTC(1900, 1, 30, 0, 0, 0);
|
||||
let calObj = new Date((offset + d - 31) * 86400000 + stmap);
|
||||
let cY = calObj.getUTCFullYear();
|
||||
let cM = calObj.getUTCMonth() + 1;
|
||||
let cD = calObj.getUTCDate();
|
||||
return calendar.solar2lunar(cY, cM, cD);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
solar2lunar: calendar.solar2lunar,
|
||||
lunar2solar: calendar.lunar2solar
|
||||
};
|
||||
964
src/components/thorui/tui-calendar/tui-calendar.vue
Normal file
964
src/components/thorui/tui-calendar/tui-calendar.vue
Normal file
@ -0,0 +1,964 @@
|
||||
<template>
|
||||
<view @touchmove.stop.prevent="stop" v-if="isFixed">
|
||||
<view class="tui-bottom-popup" :class="{'tui-popup-show': isShow}">
|
||||
<view class="tui-calendar-header" :class="{ 'tui-calendar-radius': radius }">
|
||||
<view>{{title}}</view>
|
||||
<view class="tui-iconfont tui-font-close" hover-class="tui-opacity" :hover-stay-time="150" @tap="hide">
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tui-date-box">
|
||||
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: yearArrowColor }"
|
||||
hover-class="tui-opacity" :hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(0)"></view>
|
||||
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: monthArrowColor }"
|
||||
hover-class="tui-opacity" :hover-stay-time="150" @tap="changeMonth(0)"></view>
|
||||
<view class="tui-date_time">{{ showTitle }}</view>
|
||||
<view class="tui-iconfont tui-font-arrowright" :style="{ color: monthArrowColor }"
|
||||
hover-class="tui-opacity" :hover-stay-time="150" @tap="changeMonth(1)"></view>
|
||||
<view class="tui-iconfont tui-font-arrowright" :style="{ color: yearArrowColor }"
|
||||
hover-class="tui-opacity" :hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(1)"></view>
|
||||
</view>
|
||||
<view class="tui-date-header">
|
||||
<view class="tui-date">日</view>
|
||||
<view class="tui-date">一</view>
|
||||
<view class="tui-date">二</view>
|
||||
<view class="tui-date">三</view>
|
||||
<view class="tui-date">四</view>
|
||||
<view class="tui-date">五</view>
|
||||
<view class="tui-date">六</view>
|
||||
</view>
|
||||
<view class="tui-date-content" :class="{ 'tui-flex-start': isFixed && fixedHeight }"
|
||||
:style="{ height: isFixed && fixedHeight ? dateHeight * 6 + 'px' : 'auto' }">
|
||||
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||
<view class="tui-date"></view>
|
||||
</block>
|
||||
<view class="tui-date" :class="{
|
||||
'tui-date-pd_0': isFixed && fixedHeight,
|
||||
'tui-opacity': openDisAbled(year, month, index + 1),
|
||||
'tui-start-date': (type == 2 && startDate == `${year}-${month}-${index + 1}`) || type == 1,
|
||||
'tui-end-date': (type == 2 && endDate == `${year}-${month}-${index + 1}`) || type == 1
|
||||
}" :style="{ backgroundColor: isFixed ? getColor(index, 1) : 'transparent', height: isFixed && fixedHeight ? dateHeight + 'px' : 'auto' }"
|
||||
v-for="(item, index) in daysArr" :key="index" @tap="dateClick(index)">
|
||||
<view class="tui-date-text"
|
||||
:style="{ color: isFixed ? getColor(index, 2) : getStatusData(3, index), backgroundColor: getStatusData(2, index) }">
|
||||
<view v-if="isFixed || !getStatusData(4, index)">{{ index + 1 }}</view>
|
||||
<view v-if="!getStatusData(4, index)" class="tui-custom-desc"
|
||||
:class="{ 'tui-lunar-unshow': !lunar && isFixed }">
|
||||
{{ getDescText(index, startDate, endDate) }}
|
||||
</view>
|
||||
<text class="tui-iconfont tui-font-check" v-if="getStatusData(4, index)"></text>
|
||||
</view>
|
||||
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||
v-if="!lunar && type == 2 && startDate == `${year}-${month}-${index + 1}` && startDate != endDate">
|
||||
{{ startText }}
|
||||
</view>
|
||||
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||
v-if="!lunar && type == 2 && endDate == `${year}-${month}-${index + 1}`">{{ endText }}</view>
|
||||
</view>
|
||||
<view class="tui-bg-month">{{ month }}</view>
|
||||
</view>
|
||||
|
||||
<view class="tui-calendar-op">
|
||||
<view class="tui-calendar-result">
|
||||
<text>{{ type == 1 ? activeDate : startDate }}</text>
|
||||
<text v-if="endDate">至{{ endDate }}</text>
|
||||
</view>
|
||||
<view class="tui-calendar-btn_box">
|
||||
<view :style="{background:getBtnBgColor}" @tap.stop="btnFix(false)" class="tui-btn__confirm"
|
||||
:class="{'tui-btn__disabled':disabled,'tui-btn__confirm-hover':!disabled}">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tui-popup-mask" :class="[isShow ? 'tui-mask-show' : '']" @tap="hide"></view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="tui-date-box">
|
||||
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: yearArrowColor }" hover-class="tui-opacity"
|
||||
:hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(0)"></view>
|
||||
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: monthArrowColor }" hover-class="tui-opacity"
|
||||
:hover-stay-time="150" @tap="changeMonth(0)"></view>
|
||||
<view class="tui-date_time">{{ showTitle }}</view>
|
||||
<view class="tui-iconfont tui-font-arrowright" :style="{ color: monthArrowColor }" hover-class="tui-opacity"
|
||||
:hover-stay-time="150" @tap="changeMonth(1)"></view>
|
||||
<view class="tui-iconfont tui-font-arrowright" :style="{ color: yearArrowColor }" hover-class="tui-opacity"
|
||||
:hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(1)"></view>
|
||||
</view>
|
||||
<view class="tui-date-header">
|
||||
<view class="tui-date">日</view>
|
||||
<view class="tui-date">一</view>
|
||||
<view class="tui-date">二</view>
|
||||
<view class="tui-date">三</view>
|
||||
<view class="tui-date">四</view>
|
||||
<view class="tui-date">五</view>
|
||||
<view class="tui-date">六</view>
|
||||
</view>
|
||||
<view class="tui-date-content" :style="{ height: isFixed && fixedHeight ? dateHeight * 6 + 'px' : 'auto' }">
|
||||
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||
<view class="tui-date"></view>
|
||||
</block>
|
||||
<view class="tui-date" :class="{
|
||||
'tui-date-pd_0': isFixed && fixedHeight,
|
||||
'tui-opacity': openDisAbled(year, month, index + 1),
|
||||
'tui-start-date': (type == 2 && startDate == `${year}-${month}-${index + 1}`) || type == 1,
|
||||
'tui-end-date': (type == 2 && endDate == `${year}-${month}-${index + 1}`) || type == 1
|
||||
}" :style="{ backgroundColor: isFixed ? getColor(index, 1) : 'transparent', height: isFixed && fixedHeight ? dateHeight + 'px' : 'auto' }"
|
||||
v-for="(item, index) in daysArr" :key="index" @tap="dateClick(index)">
|
||||
<view class="tui-date-text"
|
||||
:style="{ color: isFixed ? getColor(index, 2) : getStatusData(3, index), backgroundColor: getStatusData(2, index) }">
|
||||
<view v-if="isFixed || !getStatusData(4, index)">{{ index + 1 }}</view>
|
||||
<view v-if="!getStatusData(4, index)" class="tui-custom-desc"
|
||||
:class="{ 'tui-lunar-unshow': !lunar && isFixed }">
|
||||
{{ getDescText(index, startDate, endDate) }}
|
||||
</view>
|
||||
<text class="tui-iconfont tui-font-check" v-if="getStatusData(4, index)"></text>
|
||||
</view>
|
||||
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||
v-if="!lunar && type == 2 && startDate == `${year}-${month}-${index + 1}` && startDate != endDate">
|
||||
{{ startText }}
|
||||
</view>
|
||||
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||
v-if="!lunar && type == 2 && endDate == `${year}-${month}-${index + 1}`">{{ endText }}</view>
|
||||
</view>
|
||||
<view class="tui-bg-month">{{ month }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import calendar from './tui-calendar.js';
|
||||
export default {
|
||||
name: 'tuiCalendar',
|
||||
emits: ['hide', 'change'],
|
||||
props: {
|
||||
//1-切换月份和年份 2-切换月份
|
||||
arrowType: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//1-单个日期选择 2-开始日期+结束日期选择
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//可切换最大年份
|
||||
maxYear: {
|
||||
type: Number,
|
||||
default: 2030
|
||||
},
|
||||
//可切换最小年份
|
||||
minYear: {
|
||||
type: Number,
|
||||
default: 1920
|
||||
},
|
||||
//最小可选日期(不在范围内日期禁用不可选)
|
||||
minDate: {
|
||||
type: String,
|
||||
default: '1920-01-01'
|
||||
},
|
||||
/**
|
||||
* 最大可选日期
|
||||
* 默认最大值为今天,之后的日期不可选
|
||||
* 2030-12-31
|
||||
* */
|
||||
maxDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '日期选择'
|
||||
},
|
||||
//显示圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//状态 数据顺序与当月天数一致,index=>day
|
||||
/**
|
||||
* [{
|
||||
* text:"", 描述:2字以内
|
||||
* value:"",状态值
|
||||
* bgColor:"",背景色
|
||||
* color:"" 文字颜色,
|
||||
* check:false //是否显示对勾
|
||||
*
|
||||
}]
|
||||
*
|
||||
* **/
|
||||
status: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//月份切换箭头颜色
|
||||
monthArrowColor: {
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
//年份切换箭头颜色
|
||||
yearArrowColor: {
|
||||
type: String,
|
||||
default: '#bcbcbc'
|
||||
},
|
||||
//默认日期字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//选中|起始结束日期背景色
|
||||
activeBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//选中|起始结束日期字体颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//范围内日期背景色
|
||||
rangeBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(86,119,252,0.1)'
|
||||
},
|
||||
//范围内日期字体颜色
|
||||
rangeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//type=2时生效,起始日期自定义文案
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始'
|
||||
},
|
||||
//type=2时生效,结束日期自定义文案
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束'
|
||||
},
|
||||
//按钮背景色 V2.3.0+
|
||||
btnBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//固定在底部
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//固定日历容器高度,isFixed=true时生效
|
||||
fixedHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//当前选中日期带选中效果
|
||||
isActiveCurrent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//切换年月是否触发事件 type=1时生效
|
||||
isChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否显示农历
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//初始化起始选中日期 格式: 2020-06-06 或 2020/06/06 【type=1 or 2】
|
||||
initStartDate: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
//初始化结束日期 格式: 2020-06-06 或 2020/06/06【type=2】
|
||||
initEndDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
weekday: 1, // 星期几,值为1-7
|
||||
weekdayArr: [],
|
||||
days: 0, //当前月有多少天
|
||||
daysArr: [],
|
||||
showTitle: '',
|
||||
year: 2020,
|
||||
month: 0,
|
||||
day: 0,
|
||||
startYear: 0,
|
||||
startMonth: 0,
|
||||
startDay: 0,
|
||||
endYear: 0,
|
||||
endMonth: 0,
|
||||
endDay: 0,
|
||||
today: '',
|
||||
activeDate: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
isStart: true,
|
||||
min: null,
|
||||
max: null,
|
||||
dateHeight: 20
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataChange() {
|
||||
return `${this.type}-${this.minDate}-${this.maxDate}-${this.initStartDate}-${this.initEndDate}`;
|
||||
},
|
||||
disabled() {
|
||||
return this.type == 2 && (!this.startDate || !this.endDate)
|
||||
},
|
||||
getActiveBgColor(){
|
||||
return this.activeBgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getBtnBgColor() {
|
||||
return this.btnBgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getRangeColor(){
|
||||
return this.rangeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dataChange(val) {
|
||||
this.init();
|
||||
},
|
||||
fixedHeight(val) {
|
||||
if (val) {
|
||||
this.initDateHeight();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
getColor(index, type) {
|
||||
let color = type == 1 ? '' : this.color;
|
||||
let day = index + 1;
|
||||
let date = `${this.year}-${this.month}-${day}`;
|
||||
let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
|
||||
let start = this.startDate.replace(/\-/g, '/');
|
||||
let end = this.endDate.replace(/\-/g, '/');
|
||||
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
|
||||
color = type == 1 ? this.getActiveBgColor : this.activeColor;
|
||||
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
|
||||
color = type == 1 ? this.rangeBgColor : this.getRangeColor;
|
||||
}
|
||||
return color;
|
||||
},
|
||||
//获取状态数据
|
||||
getStatusData(type, index) {
|
||||
//1-描述text,2-bgColor背景色,3-color文字颜色 4-check 是否显示对勾
|
||||
let val = ['', 'transparent', '#333', ''][type - 1];
|
||||
if (!this.isFixed && this.status && this.status.length > 0) {
|
||||
let item = this.status[index];
|
||||
if (item) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
val = item.text;
|
||||
break;
|
||||
case 2:
|
||||
val = item.bgColor;
|
||||
break;
|
||||
case 3:
|
||||
val = item.color;
|
||||
break;
|
||||
case 4:
|
||||
val = item.check;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return val;
|
||||
},
|
||||
getDescText(index, startDate, endDate) {
|
||||
let text = this.lunar ? this.getLunar(this.year, this.month, index + 1) : '';
|
||||
if (this.isFixed && this.type == 2) {
|
||||
//此判断不能与上面条件一起判断
|
||||
if (this.lunar) {
|
||||
let date = `${this.year}-${this.month}-${index + 1}`;
|
||||
if (startDate == date && startDate != endDate) {
|
||||
text = this.startText;
|
||||
} else if (endDate == date) {
|
||||
text = this.endText;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let status = this.getStatusData(1, index);
|
||||
if (status) text = status;
|
||||
}
|
||||
return text;
|
||||
},
|
||||
getLunar(year, month, day) {
|
||||
let obj = calendar.solar2lunar(year, month, day);
|
||||
return obj.IDayCn;
|
||||
},
|
||||
initDateHeight() {
|
||||
if (this.fixedHeight && this.isFixed) {
|
||||
this.dateHeight = uni.getSystemInfoSync().windowWidth / 7;
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.initDateHeight();
|
||||
let now = new Date();
|
||||
this.year = now.getFullYear();
|
||||
this.month = now.getMonth() + 1;
|
||||
this.day = now.getDate();
|
||||
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
||||
this.activeDate = this.today;
|
||||
this.min = this.initDate(this.minDate);
|
||||
this.max = this.initDate(this.maxDate || this.today);
|
||||
if (this.openDisAbled(this.year, this.month, this.day)) {
|
||||
this.year = this.max.year;
|
||||
this.month = this.max.month;
|
||||
this.day = this.max.day;
|
||||
this.activeDate = `${this.max.year}-${this.max.month}-${this.max.day}`;
|
||||
this.max = this.initDate(this.maxDate || this.minDate);
|
||||
}
|
||||
this.startDate = '';
|
||||
this.startYear = 0;
|
||||
this.startMonth = 0;
|
||||
this.startDay = 0;
|
||||
if (this.initStartDate) {
|
||||
let start = new Date(this.initStartDate.replace(/\-/g, '/'));
|
||||
if (this.type == 1) {
|
||||
this.year = start.getFullYear();
|
||||
this.month = start.getMonth() + 1;
|
||||
this.day = start.getDate();
|
||||
this.activeDate = `${start.getFullYear()}-${start.getMonth() + 1}-${start.getDate()}`;
|
||||
} else {
|
||||
this.startDate = `${start.getFullYear()}-${start.getMonth() + 1}-${start.getDate()}`;
|
||||
this.startYear = start.getFullYear();
|
||||
this.startMonth = start.getMonth() + 1;
|
||||
this.startDay = start.getDate();
|
||||
this.activeDate = '';
|
||||
}
|
||||
|
||||
}
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = '';
|
||||
if (this.initEndDate && this.type == 2) {
|
||||
let end = new Date(this.initEndDate.replace(/\-/g, '/'));
|
||||
this.endDate = `${end.getFullYear()}-${end.getMonth() + 1}-${end.getDate()}`;
|
||||
this.endYear = end.getFullYear();
|
||||
this.endMonth = end.getMonth() + 1;
|
||||
this.endDay = end.getDate();
|
||||
this.activeDate = '';
|
||||
this.year = end.getFullYear();
|
||||
this.month = end.getMonth() + 1;
|
||||
this.day = end.getDate();
|
||||
}
|
||||
this.isStart = true;
|
||||
this.changeData();
|
||||
},
|
||||
//日期处理
|
||||
initDate(date) {
|
||||
let fdate = date.split('-');
|
||||
return {
|
||||
year: Number(fdate[0] || 1920),
|
||||
month: Number(fdate[1] || 1),
|
||||
day: Number(fdate[2] || 1)
|
||||
};
|
||||
},
|
||||
openDisAbled: function(year, month, day) {
|
||||
let bool = true;
|
||||
let date = `${year}/${month}/${day}`;
|
||||
// let today = this.today.replace(/\-/g, '/');
|
||||
let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
|
||||
let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
|
||||
let timestamp = new Date(date).getTime();
|
||||
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
|
||||
bool = false;
|
||||
}
|
||||
return bool;
|
||||
},
|
||||
generateArray: function(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
formatNum: function(num) {
|
||||
return num < 10 ? '0' + num : num + '';
|
||||
},
|
||||
stop() {
|
||||
return false;
|
||||
},
|
||||
//一个月有多少天
|
||||
getMonthDay(year, month) {
|
||||
let days = new Date(year, month, 0).getDate();
|
||||
return days;
|
||||
},
|
||||
getWeekday(year, month) {
|
||||
let date = new Date(`${year}/${month}/01 00:00:00`);
|
||||
return date.getDay();
|
||||
},
|
||||
checkRange(year) {
|
||||
let overstep = false;
|
||||
if (year < this.minYear || year > this.maxYear) {
|
||||
uni.showToast({
|
||||
title: '日期超出范围啦~',
|
||||
icon: 'none'
|
||||
});
|
||||
overstep = true;
|
||||
}
|
||||
return overstep;
|
||||
},
|
||||
changeMonth(isAdd) {
|
||||
if (isAdd) {
|
||||
let month = this.month + 1;
|
||||
let year = month > 12 ? this.year + 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month > 12 ? 1 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
} else {
|
||||
let month = this.month - 1;
|
||||
let year = month < 1 ? this.year - 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month < 1 ? 12 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
}
|
||||
},
|
||||
changeYear(isAdd) {
|
||||
let year = isAdd ? this.year + 1 : this.year - 1;
|
||||
if (!this.checkRange(year)) {
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
},
|
||||
changeData() {
|
||||
this.days = this.getMonthDay(this.year, this.month);
|
||||
this.daysArr = this.generateArray(1, this.days);
|
||||
this.weekday = this.getWeekday(this.year, this.month);
|
||||
this.weekdayArr = this.generateArray(1, this.weekday);
|
||||
this.showTitle = `${this.year}年${this.month}月`;
|
||||
if (this.isChange && this.type == 1) {
|
||||
this.btnFix(true);
|
||||
}
|
||||
},
|
||||
dateClick: function(day) {
|
||||
day += 1;
|
||||
if (!this.openDisAbled(this.year, this.month, day)) {
|
||||
this.day = day;
|
||||
let date = `${this.year}-${this.month}-${day}`;
|
||||
if (this.type == 1) {
|
||||
this.activeDate = date;
|
||||
} else {
|
||||
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(
|
||||
/\-/g, '/')).getTime();
|
||||
if (this.isStart || compare) {
|
||||
this.startDate = date;
|
||||
this.startYear = this.year;
|
||||
this.startMonth = this.month;
|
||||
this.startDay = this.day;
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = '';
|
||||
this.activeDate = '';
|
||||
this.isStart = false;
|
||||
} else {
|
||||
this.endDate = date;
|
||||
this.endYear = this.year;
|
||||
this.endMonth = this.month;
|
||||
this.endDay = this.day;
|
||||
this.isStart = true;
|
||||
}
|
||||
}
|
||||
if (!this.isFixed) {
|
||||
this.btnFix();
|
||||
}
|
||||
}
|
||||
},
|
||||
show() {
|
||||
this.isShow = true;
|
||||
},
|
||||
hide() {
|
||||
this.isShow = false;
|
||||
this.$emit('hide', {})
|
||||
},
|
||||
getWeekText(date) {
|
||||
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
|
||||
let week = date.getDay();
|
||||
return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
|
||||
},
|
||||
btnFix(show) {
|
||||
if (!show) {
|
||||
if(this.disabled) return;
|
||||
this.hide();
|
||||
}
|
||||
if (this.type == 1) {
|
||||
let arr = this.activeDate.split('-');
|
||||
let year = this.isChange ? this.year : Number(arr[0]);
|
||||
let month = this.isChange ? this.month : Number(arr[1]);
|
||||
let day = this.isChange ? this.day : Number(arr[2]);
|
||||
//当前月有多少天
|
||||
let days = this.getMonthDay(year, month);
|
||||
let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
|
||||
let weekText = this.getWeekText(result);
|
||||
let isToday = false;
|
||||
if (`${year}-${month}-${day}` == this.today) {
|
||||
//今天
|
||||
isToday = true;
|
||||
}
|
||||
let lunar = calendar.solar2lunar(year, month, day);
|
||||
this.$emit('change', {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
days: days,
|
||||
result: result,
|
||||
week: weekText,
|
||||
isToday: isToday,
|
||||
switch: show, //是否是切换年月操作
|
||||
lunar: lunar
|
||||
});
|
||||
} else {
|
||||
if (!this.startDate || !this.endDate) return;
|
||||
let startMonth = this.formatNum(this.startMonth);
|
||||
let startDay = this.formatNum(this.startDay);
|
||||
let startDate = `${this.startYear}-${startMonth}-${startDay}`;
|
||||
let startWeek = this.getWeekText(startDate);
|
||||
let startLunar = calendar.solar2lunar(this.startYear, startMonth, startDay);
|
||||
|
||||
let endMonth = this.formatNum(this.endMonth);
|
||||
let endDay = this.formatNum(this.endDay);
|
||||
let endDate = `${this.endYear}-${endMonth}-${endDay}`;
|
||||
let endWeek = this.getWeekText(endDate);
|
||||
let endLunar = calendar.solar2lunar(this.endYear, endMonth, endDay);
|
||||
this.$emit('change', {
|
||||
startYear: this.startYear,
|
||||
startMonth: this.startMonth,
|
||||
startDay: this.startDay,
|
||||
startDate: startDate,
|
||||
startWeek: startWeek,
|
||||
startLunar: startLunar,
|
||||
endYear: this.endYear,
|
||||
endMonth: this.endMonth,
|
||||
endDay: this.endDay,
|
||||
endDate: endDate,
|
||||
endWeek: endWeek,
|
||||
endLunar: endLunar
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: 'tuiDateFont';
|
||||
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAVgAA0AAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAFRAAAABoAAAAci0/w50dERUYAAAUkAAAAHgAAAB4AKQANT1MvMgAAAaAAAABDAAAAVjxuSNNjbWFwAAAB+AAAAEoAAAFS5iPQt2dhc3AAAAUcAAAACAAAAAj//wADZ2x5ZgAAAlQAAAFHAAABvPf29TBoZWFkAAABMAAAADAAAAA2GMsN3WhoZWEAAAFgAAAAHQAAACQHjAOFaG10eAAAAeQAAAATAAAAFgzQAPJsb2NhAAACRAAAABAAAAAQAOoBSG1heHAAAAGAAAAAHgAAACABEwA3bmFtZQAAA5wAAAFJAAACiCnmEVVwb3N0AAAE6AAAADQAAABLUwjqHHjaY2BkYGAAYp5Gj5/x/DZfGbhZGEDg1tUn7+F00P/LzOuY9YFcDgYmkCgAa0gNlHjaY2BkYGBu+N/AEMPCAALM6xgYGVABCwBT4AMaAAAAeNpjYGRgYGBn0GZgYgABEMkFhAwM/8F8BgANaAFLAAB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ49ZG7438AQw9zA0AAUZgTJAQDrcAy8AHjaY2GAABYIDgLCBQx1AAcEAc8AeNpjYGBgZoBgGQZGBhDwAfIYwXwWBgMgzQGETAwMzxifcTx7+P8/kMUAYUkxS/6VVIXqAgNGNgY4lxGoB6QPBTAyDHsAADDkDYkAAAAAAAAAAAAAADQAagC2AN542m2QsU7DMBCG/Tt1bNPUiUnkSgiVtqKpxJAgVLVbeAa6MaK+B4JXgJWBjY21UtW5gpkdMTFX7dzApaJLhXU6n8+n//ttxtn458N79XJWZ8eMxS00C4wy9A1EP8PQncAlIQzS4WgsVtPpSmwzV3OFRqLetH5TSQMK939X61ptPZ2p2EAttNMLBRMrtschQblDeS34aY50cIkCzg/B2Y5C+VpyQxhFkRgu515O8jvU5mmPM2O0wJ5Z27vhX+yMsV437WvCdTM+GI40MgwKfuGammC0uURqeqFMfe9cxaJclkt5GMaB1hIR1VobOgpEiKq+sLZcIrJWhO3/Jw7qWlYj1Jf21FaCtmd5bevrlk28O/7A4spXTl4KTh9MTlqQ8PESBRstReic+sRj0Dni9fIqmNS/pXNWCvWOeYBmx5S9Bsn9Ah+5WtAAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAO2MGiTIxMjMyMLIys7GmJeRmlmWZQ2pQ5OSORLaU0Mz2/FACDfwlbAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABgABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9K2rT97DaABNlwiuAAA=) format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tui-iconfont {
|
||||
font-family: 'tuiDateFont' !important;
|
||||
font-size: 36rpx;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.tui-font-close:before {
|
||||
content: '\e608';
|
||||
}
|
||||
|
||||
.tui-font-check:before {
|
||||
content: '\e6e1';
|
||||
}
|
||||
|
||||
.tui-font-arrowright:before {
|
||||
content: '\e600';
|
||||
}
|
||||
|
||||
.tui-font-arrowleft:before {
|
||||
content: '\e601';
|
||||
}
|
||||
|
||||
.tui-date-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 0 30rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.tui-calendar-radius {
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-date_time {
|
||||
padding: 0 16rpx;
|
||||
color: #333;
|
||||
font-size: 32rpx;
|
||||
line-height: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tui-font-arrowleft {
|
||||
margin-right: 32rpx;
|
||||
}
|
||||
|
||||
.tui-font-arrowright {
|
||||
margin-left: 32rpx;
|
||||
}
|
||||
|
||||
.tui-date-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #555;
|
||||
box-shadow: 0 15rpx 20rpx -15rpx #efefef;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-date-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 12rpx 0;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-flex-start {
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.tui-bg-month {
|
||||
position: absolute;
|
||||
font-size: 260rpx;
|
||||
line-height: 260rpx;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #f5f5f7;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tui-date {
|
||||
width: 14.2857%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12rpx 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-date-pd_0 {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.tui-start-date {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
}
|
||||
|
||||
.tui-end-date {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
.tui-date-text {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-size: 32rpx;
|
||||
line-height: 32rpx;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.tui-btn-calendar {
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tui-opacity {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tui-bottom-popup {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
visibility: hidden;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
min-height: 20rpx;
|
||||
}
|
||||
|
||||
.tui-popup-show {
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 9996;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-calendar-header {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background-color: #fff;
|
||||
color: #555;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-font-close {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tui-btn-calendar {
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tui-font-check {
|
||||
color: #fff;
|
||||
font-size: 54rpx;
|
||||
line-height: 54rpx;
|
||||
}
|
||||
|
||||
.tui-custom-desc {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tui-lunar-unshow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 8rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-date-desc {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
text-align: center;
|
||||
bottom: 8rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-calendar-op {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
padding: 0 42rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tui-calendar-result {
|
||||
height: 48rpx;
|
||||
transform: scale(0.9);
|
||||
transform-origin: center 100%;
|
||||
}
|
||||
|
||||
.tui-calendar-btn_box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tui-btn__confirm {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
border-radius: 72rpx;
|
||||
font-size: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-btn__confirm-hover:active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
background: rgba(0, 0, 0, .2);
|
||||
border-radius: 72rpx;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-btn__disabled {
|
||||
opacity: .5;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
234
src/components/thorui/tui-card/tui-card.vue
Normal file
234
src/components/thorui/tui-card/tui-card.vue
Normal file
@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<view class="tui-card-class tui-card" :class="[full?'tui-card-full':'',border?'tui-card-border':'']"
|
||||
@tap="handleClick" @longpress="longTap">
|
||||
<slot>
|
||||
<view class="tui-card-header" :class="{'tui-header-line':header.line}"
|
||||
:style="{background:header.bgcolor || '#fff'}">
|
||||
<view class="tui-header-left">
|
||||
<image :src="imageUrl || image.url" class="tui-header-thumb"
|
||||
:class="{'tui-thumb-circle':image.circle}" mode="widthFix" v-if="imageUrl || image.url"
|
||||
:style="{height:(image.height || 60)+'rpx',width:(image.width || 60)+'rpx'}"></image>
|
||||
<text class="tui-header-title"
|
||||
:style="{fontSize:(title.size || 30)+'rpx',color:(title.color || '#7A7A7A')}"
|
||||
v-if="titleText || title.text">{{titleText || title.text}}</text>
|
||||
</view>
|
||||
<view class="tui-header-right" :style="{fontSize:(tag.size || 24)+'rpx',color:(tag.color || '#b2b2b2')}"
|
||||
v-if="tagText || tag.text">
|
||||
{{tagText || tag.text}}
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
<view class="tui-card-body">
|
||||
<slot name="body"></slot>
|
||||
</view>
|
||||
<view class="tui-card-footer">
|
||||
<slot name="footer"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiCard",
|
||||
emits: ['click', 'longclick'],
|
||||
props: {
|
||||
//是否铺满
|
||||
full: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//v2.9.6+ 图片地址,优先级高于image 中 url
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
image: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
url: "", //图片地址
|
||||
height: 60, //图片高度
|
||||
width: 60, //图片宽度
|
||||
circle: false
|
||||
}
|
||||
}
|
||||
},
|
||||
//v2.9.6+ 标题文本,优先级高于 title中 text
|
||||
titleText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//标题
|
||||
title: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
text: "", //标题文字
|
||||
size: 30, //字体大小
|
||||
color: "#7A7A7A" //字体颜色
|
||||
}
|
||||
}
|
||||
},
|
||||
//标签文本,优先级高于 tag中 text
|
||||
tagText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//标签,时间等
|
||||
tag: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
text: "", //标签文字
|
||||
size: 24, //字体大小
|
||||
color: "#b2b2b2" //字体颜色
|
||||
}
|
||||
}
|
||||
},
|
||||
header: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
bgcolor: "#fff", //背景颜色
|
||||
line: false //是否去掉底部线条
|
||||
}
|
||||
}
|
||||
},
|
||||
//是否设置外边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: this.index
|
||||
});
|
||||
},
|
||||
longTap() {
|
||||
this.$emit('longclick', {
|
||||
index: this.index
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-card {
|
||||
margin: 0 30rpx;
|
||||
font-size: 28rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0 0 10rpx #eee;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-card-full {
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.tui-card-full::after {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.tui-card-border {
|
||||
position: relative;
|
||||
box-shadow: none !important
|
||||
}
|
||||
|
||||
.tui-card-border::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
height: 200%;
|
||||
width: 200%;
|
||||
border: 1px solid #ddd;
|
||||
transform-origin: 0 0;
|
||||
-webkit-transform-origin: 0 0;
|
||||
-webkit-transform: scale(0.5);
|
||||
transform: scale(0.5);
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 20rpx;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-card-header {
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
border-top-left-radius: 10rpx;
|
||||
border-top-right-radius: 10rpx;
|
||||
}
|
||||
|
||||
.tui-card-header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1rpx solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-header-line::after {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tui-header-thumb {
|
||||
height: 60rpx;
|
||||
width: 60rpx;
|
||||
vertical-align: middle;
|
||||
margin-right: 20rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.tui-thumb-circle {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
||||
.tui-header-title {
|
||||
display: inline-block;
|
||||
font-size: 30rpx;
|
||||
color: #7a7a7a;
|
||||
vertical-align: middle;
|
||||
max-width: 460rpx;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tui-header-right {
|
||||
font-size: 24rpx;
|
||||
color: #b2b2b2;
|
||||
}
|
||||
|
||||
.tui-card-body {
|
||||
font-size: 32rpx;
|
||||
color: #262b3a;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-card-footer {
|
||||
font-size: 28rpx;
|
||||
color: #596d96;
|
||||
border-bottom-left-radius: 10rpx;
|
||||
border-bottom-right-radius: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,615 @@
|
||||
<template>
|
||||
<view class="tui-cascade-selection">
|
||||
<scroll-view :scroll-x="true" scroll-with-animation :scroll-into-view="scrollViewId"
|
||||
:style="{ backgroundColor: headerBgColor }" class="tui-bottom-line"
|
||||
:class="{ 'tui-btm-none': !headerLine }">
|
||||
<view class="tui-selection-header" :style="{ height: tabsHeight, backgroundColor: backgroundColor }">
|
||||
<view class="tui-header-item" :class="{ 'tui-font-bold': idx === currentTab && bold }"
|
||||
:style="{ color: idx === currentTab ? getActiveColor : color, fontSize: size + 'rpx' }"
|
||||
:id="`id_${idx}`" @tap.stop="swichNav" :data-current="idx" v-for="(item, idx) in selectedArr"
|
||||
:key="idx">
|
||||
{{ item[textField] }}
|
||||
<view class="tui-active-line" :style="{ backgroundColor: getLineColor }"
|
||||
v-if="idx === currentTab && showLine"></view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<swiper class="tui-selection-list" :current="defTab" duration="300" @change="switchTab"
|
||||
:style="{ height: height, backgroundColor: backgroundColor }">
|
||||
<swiper-item v-for="(item, index) in selectedArr" :key="index">
|
||||
<scroll-view scroll-y :scroll-into-view="item.scrollViewId" class="tui-selection-item"
|
||||
:style="{ height: height }">
|
||||
<view class="tui-first-item" :style="{ height: firstItemTop }"></view>
|
||||
<view class="tui-selection-cell" :style="{ padding: padding, backgroundColor: backgroundColor }"
|
||||
:id="`id_${subIndex}`" v-for="(subItem, subIndex) in item.list" :key="subIndex"
|
||||
@tap.stop="change(index, subIndex, subItem)">
|
||||
<icon type="success_no_circle" v-if="item.index === subIndex" :color="getCkMarkColor"
|
||||
:size="checkMarkSize" class="tui-icon-success"></icon>
|
||||
<image :src="subItem[srcField]" v-if="subItem[srcField]" class="tui-cell-img"
|
||||
:style="{ width: imgWidth, height: imgHeight, borderRadius: radius }"></image>
|
||||
<view class="tui-cell-title"
|
||||
:class="{ 'tui-font-bold': item.index === subIndex && textBold, 'tui-flex-shrink': nowrap }"
|
||||
:style="{ color: item.index === subIndex ? textActiveColor : textColor, fontSize: textSize + 'rpx' }">
|
||||
{{ subItem[textField] }}
|
||||
</view>
|
||||
<view class="tui-cell-sub_title" :style="{ color: subTextColor, fontSize: subTextSize + 'rpx' }"
|
||||
v-if="subItem[subTextField]">{{ subItem[subTextField] }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiCascadeSelection',
|
||||
emits: ['change', 'complete'],
|
||||
props: {
|
||||
/**
|
||||
* 如果下一级是请求返回,则为第一级数据,否则所有数据
|
||||
* 数据格式
|
||||
* */
|
||||
itemList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
srcField: {
|
||||
type: String,
|
||||
default: 'src'
|
||||
},
|
||||
textField: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
subTextField: {
|
||||
type: String,
|
||||
default: 'subText'
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
childrenField: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
/*
|
||||
初始化默认选中数据
|
||||
[{
|
||||
text: "",//选中text
|
||||
subText: '',//选中subText
|
||||
value: '',//选中value
|
||||
src: '', //选中src,没有则传空或不传
|
||||
index: 0, //选中数据在当前layer索引
|
||||
list: [{src: "", text: "", subText: "", value: 101}] //当前layer下所有数据集合
|
||||
}];
|
||||
|
||||
*/
|
||||
defaultItemList: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
defaultKey: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
//是否显示header底部细线
|
||||
headerLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//header背景颜色
|
||||
headerBgColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
//顶部标签栏高度
|
||||
tabsHeight: {
|
||||
type: String,
|
||||
default: '88rpx'
|
||||
},
|
||||
//默认显示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
//tabs 文字大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
//tabs 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#555'
|
||||
},
|
||||
//选中颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//选中后文字加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//选中后是否显示底部线条
|
||||
showLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//线条颜色
|
||||
lineColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//icon 大小
|
||||
checkMarkSize: {
|
||||
type: Number,
|
||||
default: 15
|
||||
},
|
||||
//icon 颜色
|
||||
checkMarkColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//item 图片宽度
|
||||
imgWidth: {
|
||||
type: String,
|
||||
default: '40rpx'
|
||||
},
|
||||
//item 图片高度
|
||||
imgHeight: {
|
||||
type: String,
|
||||
default: '40rpx'
|
||||
},
|
||||
//图片圆角
|
||||
radius: {
|
||||
type: String,
|
||||
default: '50%'
|
||||
},
|
||||
//item text颜色
|
||||
textColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
textActiveColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//选中后字体是否加粗
|
||||
textBold: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//item text字体大小
|
||||
textSize: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
//text 是否不换行
|
||||
nowrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//item subText颜色
|
||||
subTextColor: {
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
//item subText字体大小
|
||||
subTextSize: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
// item padding
|
||||
padding: {
|
||||
type: String,
|
||||
default: '20rpx 30rpx'
|
||||
},
|
||||
//占位高度,第一条数据距离顶部距离
|
||||
firstItemTop: {
|
||||
type: String,
|
||||
default: '20rpx'
|
||||
},
|
||||
//swiper 高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '300px'
|
||||
},
|
||||
//item swiper 内容部分背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
//子集数据是否请求返回(默认false,一次性返回所有数据)
|
||||
request: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//子级数据(当有改变时,默认当前选中项新增子级数据,request=true时生效)
|
||||
receiveData: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//改变值则重置数据
|
||||
reset: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getActiveColor() {
|
||||
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getLineColor() {
|
||||
return this.lineColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getCkMarkColor() {
|
||||
return this.checkMarkColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemList(val) {
|
||||
this.initData(val, -1);
|
||||
},
|
||||
receiveData(val) {
|
||||
this.subLevelData(val, this.currentTab);
|
||||
},
|
||||
reset() {
|
||||
this.initData(this.itemList, -1);
|
||||
},
|
||||
defaultItemList(val) {
|
||||
this.setDefaultData(val)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setDefaultData(this.defaultItemList)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTab: 0,
|
||||
defTab: 0,
|
||||
//tab栏scrollview滚动的位置
|
||||
scrollViewId: 'id__1',
|
||||
selectedArr: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
setDefaultData(val) {
|
||||
let defaultItemList = JSON.parse(JSON.stringify(val || []));
|
||||
if (defaultItemList.length > 0) {
|
||||
if ((typeof defaultItemList[0] === 'string' || typeof defaultItemList[0] === 'number') && !this
|
||||
.request) {
|
||||
let subi = -1
|
||||
let selectedArr = []
|
||||
for (let j = 0, len = defaultItemList.length; j < len; j++) {
|
||||
let item = defaultItemList[j]
|
||||
let list = []
|
||||
let obj = {}
|
||||
if (j === 0) {
|
||||
list = this.getItemList(-1)
|
||||
} else {
|
||||
list = this.getItemList(j - 1, subi, selectedArr)
|
||||
}
|
||||
subi = this.getDefaultIndex(list, item)
|
||||
if (subi !== -1) {
|
||||
obj = list[subi]
|
||||
selectedArr.push({
|
||||
[this.textField]: obj[this.textField] || this.text,
|
||||
[this.valueField]: obj[this.valueField] || '',
|
||||
[this.srcField]: obj[this.srcField] || '',
|
||||
[this.subTextField]: obj[this.subTextField] || '',
|
||||
index: subi,
|
||||
scrollViewId: `id_${subi}`,
|
||||
list: list
|
||||
})
|
||||
}
|
||||
|
||||
if (subi === -1) break;
|
||||
}
|
||||
this.selectedArr = selectedArr;
|
||||
this.defTab = this.currentTab;
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.currentTab = selectedArr.length - 1;
|
||||
this.defTab = this.currentTab;
|
||||
this.checkCor();
|
||||
}, 20)
|
||||
});
|
||||
} else {
|
||||
defaultItemList.map(item => {
|
||||
item.scrollViewId = `id_${item.index}`;
|
||||
});
|
||||
this.selectedArr = defaultItemList;
|
||||
this.defTab = this.currentTab;
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.currentTab = defaultItemList.length - 1;
|
||||
this.defTab = this.currentTab;
|
||||
this.checkCor();
|
||||
}, 20)
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
this.initData(this.itemList, -1);
|
||||
}
|
||||
},
|
||||
getDefaultIndex(arr, val) {
|
||||
if (!arr || arr.length === 0 || val === undefined) return -1;
|
||||
let index = -1;
|
||||
let key = this.defaultKey || 'text'
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
if (arr[i][key] == val) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
},
|
||||
initData(data, layer) {
|
||||
if (!data || data.length === 0) return;
|
||||
if (this.request) {
|
||||
//第一级数据
|
||||
this.subLevelData(data, layer);
|
||||
} else {
|
||||
let selectedValue = this.selectedValue || {};
|
||||
if (selectedValue.type) {
|
||||
this.setDefaultData(selectedValue);
|
||||
} else {
|
||||
this.subLevelData(this.getItemList(layer, -1), layer);
|
||||
}
|
||||
}
|
||||
},
|
||||
removeChildren(data) {
|
||||
let list = data.map(item => {
|
||||
delete item[this.childrenField];
|
||||
return item;
|
||||
});
|
||||
return list;
|
||||
},
|
||||
getItemList(layer, index, selectedArr) {
|
||||
let list = [];
|
||||
let arr = JSON.parse(JSON.stringify(this.itemList));
|
||||
selectedArr = selectedArr || this.selectedArr
|
||||
if (layer == -1) {
|
||||
list = this.removeChildren(arr);
|
||||
} else {
|
||||
let value = selectedArr[0].index;
|
||||
value = value === undefined || value == -1 ? index : value;
|
||||
if (arr[value] && arr[value][this.childrenField]) {
|
||||
list = arr[value][this.childrenField];
|
||||
}
|
||||
if (layer > 0) {
|
||||
for (let i = 1; i < layer + 1; i++) {
|
||||
let val = layer === i ? index : selectedArr[i].index;
|
||||
list = val === -1 ? [] : (list[val][this.childrenField] || []);
|
||||
if (list.length === 0) break;
|
||||
}
|
||||
}
|
||||
list = this.removeChildren(list);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
//滚动切换
|
||||
switchTab: function(e) {
|
||||
this.currentTab = e.detail.current;
|
||||
this.checkCor();
|
||||
},
|
||||
//点击标题切换当
|
||||
swichNav: function(e) {
|
||||
let cur = e.currentTarget.dataset.current;
|
||||
if (this.currentTab != cur) {
|
||||
this.defTab = this.currentTab;
|
||||
setTimeout(() => {
|
||||
this.currentTab = cur;
|
||||
this.defTab = this.currentTab;
|
||||
}, 20)
|
||||
}
|
||||
},
|
||||
checkCor: function() {
|
||||
let item = this.selectedArr[this.currentTab];
|
||||
item.scrollViewId = 'id__1';
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
let val = item.index < 2 ? 0 : Number(item.index - 2);
|
||||
item.scrollViewId = `id_${val}`;
|
||||
}, 20);
|
||||
});
|
||||
|
||||
if (this.currentTab > 1) {
|
||||
this.scrollViewId = `id_${this.currentTab - 1}`;
|
||||
} else {
|
||||
this.scrollViewId = `id_0`;
|
||||
}
|
||||
},
|
||||
change(index, subIndex, subItem) {
|
||||
let item = this.selectedArr[index];
|
||||
if (item.index == subIndex) return;
|
||||
item.index = subIndex;
|
||||
item[this.textField] = subItem[this.textField]
|
||||
item[this.valueField] = subItem[this.valueField];
|
||||
item[this.subTextField] = subItem[this.subTextField] || '';
|
||||
item[this.srcField] = subItem[this.srcField] || '';
|
||||
this.$emit('change', {
|
||||
layer: index,
|
||||
subIndex: subIndex, //layer=> Array index
|
||||
...subItem
|
||||
});
|
||||
|
||||
if (!this.request) {
|
||||
let data = this.getItemList(index, subIndex);
|
||||
this.subLevelData(data, index);
|
||||
}
|
||||
},
|
||||
//新增子级数据时处理
|
||||
subLevelData(data, layer) {
|
||||
if (!data || data.length === 0) {
|
||||
if (layer == -1) return;
|
||||
//完成选择
|
||||
let arr = this.selectedArr;
|
||||
if (layer < arr.length - 1) {
|
||||
let newArr = arr.slice(0, layer + 1);
|
||||
this.selectedArr = newArr;
|
||||
}
|
||||
let result = JSON.parse(JSON.stringify(this.selectedArr));
|
||||
let lastItem = result[result.length - 1] || {};
|
||||
let text = '';
|
||||
result.map(item => {
|
||||
text += item[this.textField];
|
||||
delete item['list'];
|
||||
//delete item['index'];
|
||||
delete item['scrollViewId'];
|
||||
return item;
|
||||
});
|
||||
this.$emit('complete', {
|
||||
result: result,
|
||||
[this.valueField]: lastItem[this.valueField],
|
||||
[this.textField]: text,
|
||||
[this.subTextField]: lastItem[this.subTextField],
|
||||
[this.srcField]: lastItem[this.srcField]
|
||||
});
|
||||
} else {
|
||||
//重置数据( >layer层级)
|
||||
let item = [{
|
||||
[this.textField]: this.text,
|
||||
[this.subTextField]: '',
|
||||
[this.valueField]: '',
|
||||
[this.srcField]: '',
|
||||
index: -1,
|
||||
scrollViewId: 'id__1',
|
||||
list: data
|
||||
}];
|
||||
if (layer == -1) {
|
||||
this.selectedArr = item;
|
||||
} else {
|
||||
let retainArr = this.selectedArr.slice(0, layer + 1) || [];
|
||||
this.selectedArr = retainArr.concat(item);
|
||||
}
|
||||
|
||||
let current = this.selectedArr.length - 1;
|
||||
if (current >= this.currentTab) {
|
||||
this.defTab = this.currentTab
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.defTab = current;
|
||||
this.currentTab = current;
|
||||
this.scrollViewId = `id_${this.currentTab > 1?this.currentTab - 1:0}`;
|
||||
}, 50)
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-cascade-selection {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-selection-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-bottom-line {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-bottom-line::after {
|
||||
width: 100%;
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1rpx solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-btm-none::after {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tui-header-item {
|
||||
max-width: 240rpx;
|
||||
padding: 15rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-font-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tui-active-line {
|
||||
width: 60rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 4rpx;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tui-selection-cell {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-icon-success {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.tui-cell-img {
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-cell-title {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tui-flex-shrink {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-font-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tui-cell-sub_title {
|
||||
margin-left: 20rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tui-first-item {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
703
src/components/thorui/tui-charts-area/tui-charts-area.vue
Normal file
703
src/components/thorui/tui-charts-area/tui-charts-area.vue
Normal file
@ -0,0 +1,703 @@
|
||||
<template>
|
||||
<view class="tui-charts__line-wrap" :style="{width:width+'rpx'}">
|
||||
<view class="tui-line__legend" v-if="legend.show">
|
||||
<view class="tui-line__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts__line-box" v-if="xAxis.length>0 && dataset.length>0" :style="{width:width+'rpx'}">
|
||||
<scroll-view :scroll-x="scrollable" class="tui-line__scroll-view" :style="{height:scrollViewH+'rpx'}">
|
||||
<view :style="{height:(xAxisVal.height || 48) +'rpx'}"></view>
|
||||
<view class="tui-charts__line" :style="{height:height+'rpx'}">
|
||||
<view class="tui-line__item" :class="{'tui-line__flex-1':!scrollable}"
|
||||
:style="{width:(xAxisLine.itemGap || 120)+'rpx'}" v-for="(item,index) in xAxis" :key="index">
|
||||
<view class="tui-line__xAxis-text"
|
||||
:style="{color:xAxisLabel.color || '#333',fontSize:(xAxisLabel.size || 24)+'rpx' }">
|
||||
{{item}}
|
||||
</view>
|
||||
<view class="tui-yAxis__split-line"
|
||||
:style="{borderRightStyle:yAxisSplitLine.type || 'dashed',borderRightColor:yAxisSplitLine.color || '#e3e3e3'}"
|
||||
v-if="tooltipShow && index==activeIdx">
|
||||
</view>
|
||||
<view class="tui-xAxis__tickmarks"
|
||||
:style="{height:xAxisTick.height || '12rpx',backgroundColor:xAxisTick.color || '#e3e3e3'}">
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="(dot,i) in dots" :key="dot.id">
|
||||
<view class="tui-charts__line-dot"
|
||||
:class="{'tui-charts__dot-enlarge':tooltipShow && j==activeIdx}" @tap.stop="dotClick(i,j)"
|
||||
v-for="(d,j) in dot.source" :key="d.id"
|
||||
:style="{bottom: d.y+'rpx', left: d.x+'rpx',width:(brokenDot.width || 12)+'rpx',height:(brokenDot.width || 12)+'rpx',borderColor:dot.color || brokenDot.color,background:brokenDot.color || dot.color}">
|
||||
<text class="tui-line__val"
|
||||
:style="{fontSize:(xAxisVal.size || 24)+'rpx',color:xAxisVal.color}"
|
||||
v-if="xAxisVal.show">
|
||||
{{getYAxisVal(i,j)}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-for="(line,idx) in lines" :key="line.id">
|
||||
<view class="tui-charts__broken-line" v-for="(l,k) in line.source" :key="l.id"
|
||||
:style="{height:brokenLineHeight+'px',background:line.color,bottom: l.y+'rpx', left: l.x+'rpx',width: l.width+'rpx','-webkit-transform': `rotate(${l.angle}deg)`,transform: `rotate(${l.angle}deg)`}">
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-area__chart-box" v-for="(area,i) in dots" :key="area.key"
|
||||
:style="{left:xGap/2+'rpx',right:xGap/2+'rpx',minWidth:xWidth+'rpx'}">
|
||||
<view class="tui-area__item"
|
||||
:style="{'--tui-area-start': model.start, '--tui-area-end': model.end,background:model.background}"
|
||||
v-for="(model,j) in area.area" :key="j"></view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tui-line__border-left"
|
||||
:style="{height:height+(xAxisVal.height || 48)+'rpx',backgroundColor:yAxisLine.color || '#e3e3e3'}">
|
||||
</view>
|
||||
<view class="tui-xAxis__line" :class="{'tui-line__first':index===0}"
|
||||
:style="{bottom:index*(yAxisLine.itemGap || 60)+(xAxisLabel.height || 60)+'rpx',borderTopStyle:index===0?'solid':splitLine.type,borderTopColor:index===0?xAxisLine.color:splitLine.color}"
|
||||
v-for="(item,index) in yAxisData" :key="index">
|
||||
<text class="tui-yAxis__val"
|
||||
:style="{color:item.color || yAxisLabel.color,fontSize:(yAxisLabel.size||24)+'rpx'}"
|
||||
v-if="yAxisLabel.show">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tui-line__tooltip" v-if="tooltip" :class="{'tui-line__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title">{{xAxis[activeIdx] || ''}}</view>
|
||||
<view class="tui-line__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-charts-area",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//图表宽度
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 620
|
||||
},
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: false,
|
||||
size: 24,
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
xAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//默认选中x轴索引
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
//分割线
|
||||
splitLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//分割线颜色,不显示则将颜色设置为transparent
|
||||
color: "#e3e3e3",
|
||||
type: "dashed"
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴刻度线
|
||||
xAxisTick: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
height: '12rpx',
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3'
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴线条
|
||||
xAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: '#e3e3e3',
|
||||
//x轴item间距 rpx
|
||||
itemGap: 120
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: "#333",
|
||||
size: 24,
|
||||
height: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisVal: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24,
|
||||
//如果show为true且val显示的时候,height需要设置一定的值保证val能显示完整 rpx
|
||||
height: 48
|
||||
}
|
||||
}
|
||||
},
|
||||
//点击坐标点所显示的分割线
|
||||
yAxisSplitLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//分割线颜色,不显示则将颜色设置为transparent
|
||||
color: "transparent",
|
||||
type: "dashed"
|
||||
}
|
||||
}
|
||||
},
|
||||
//折线坐标点宽度 rpx
|
||||
brokenDot: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
width: 12,
|
||||
//点的背景色
|
||||
color: '#F8F8F8'
|
||||
}
|
||||
}
|
||||
},
|
||||
//折线高度/粗细 px
|
||||
brokenLineHeight: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//y轴数据,如果不传则默认使用min,max值计算
|
||||
// {
|
||||
// value: 0,
|
||||
// color: "#333"
|
||||
// }
|
||||
yAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//y轴最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//y轴最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
//y轴分段递增数值
|
||||
splitNumber: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
yAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3',
|
||||
//y轴item间距 rpx
|
||||
itemGap: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
//是否可滚动
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: 0,
|
||||
scrollViewH: 0,
|
||||
sections: 0,
|
||||
yAxisData: [],
|
||||
activeIndex: -1,
|
||||
activeIdx: -1,
|
||||
tooltips: [],
|
||||
tooltipShow: false,
|
||||
timer: null,
|
||||
dots: [],
|
||||
lines: [],
|
||||
/*========options============*/
|
||||
/*
|
||||
name: '',
|
||||
color: '',
|
||||
source: []
|
||||
colorFormatter:Function
|
||||
*/
|
||||
dataset: [],
|
||||
xAxisValFormatter: null,
|
||||
maxValue: 1,
|
||||
xGap: 120,
|
||||
xWidth: 600
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.activeIdx = this.currentIndex;
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
getPx(rpx) {
|
||||
let px = parseInt(uni.upx2px(Number(rpx)))
|
||||
return px % 2 === 0 ? px : px + 1
|
||||
},
|
||||
getYAxisVal(idx, index) {
|
||||
let showVal = this.dataset[idx].source[index];
|
||||
if (this.xAxisVal.formatter && typeof this.xAxisVal.formatter === 'function') {
|
||||
showVal = this.xAxisVal.formatter(showVal)
|
||||
} else if (this.xAxisValFormatter && typeof this.xAxisValFormatter === 'function') {
|
||||
showVal = this.xAxisValFormatter(showVal)
|
||||
}
|
||||
return showVal
|
||||
},
|
||||
generateArray(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
getValue(val) {
|
||||
return val < 0 ? 0 : val;
|
||||
},
|
||||
getCoordinatePoint() {
|
||||
const xAxis = [...this.xAxis];
|
||||
const xSections = xAxis.length;
|
||||
const ySections = this.yAxisData.length - 1;
|
||||
const itemGap = this.scrollable ? (this.xAxisLine.itemGap || 120) : (this.width / xSections);
|
||||
let dots = [];
|
||||
let radius = (this.brokenDot.width || 12) / 2;
|
||||
this.xWidth = itemGap * (xSections - 1)
|
||||
this.xGap = itemGap
|
||||
let yHeight = (this.yAxisLine.itemGap || 60) * ySections
|
||||
this.dataset.map((item, index) => {
|
||||
let source = item.source || []
|
||||
let dotArr = []
|
||||
let areaArr = []
|
||||
source.map((val, idx) => {
|
||||
let y = this.getValue((val - this.min) / (this.maxValue - this.min) * yHeight -
|
||||
radius)
|
||||
dotArr.push({
|
||||
id: 'd' + idx,
|
||||
x: this.getValue((0.5 + idx) * itemGap - radius),
|
||||
y: y
|
||||
})
|
||||
if (idx !== xSections - 1) {
|
||||
let y1 = this.getValue((source[idx + 1] - this.min) / (this.maxValue - this
|
||||
.min) * yHeight -
|
||||
radius)
|
||||
areaArr.push({
|
||||
start: (y + 6) / yHeight,
|
||||
end: (y1 + 6) / yHeight,
|
||||
background: item.color
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
dots.push({
|
||||
id: 'dd' + index,
|
||||
color: item.color,
|
||||
source: dotArr,
|
||||
key: 'ar' + index,
|
||||
area: areaArr
|
||||
})
|
||||
})
|
||||
this.dots = dots;
|
||||
this.drawLines(dots);
|
||||
},
|
||||
drawLines(dots) {
|
||||
let lines = []
|
||||
// dots是点的集合 : Array<{ x: number; y: number; }>
|
||||
let radius = (this.brokenDot.width || 12) / 2;
|
||||
dots.map((item, idx) => {
|
||||
let dotArr = item.source;
|
||||
let lineArr = [];
|
||||
dotArr.map((dot, index) => {
|
||||
if (!dotArr[index + 1]) return;
|
||||
const AB = {
|
||||
x: dotArr[index + 1].x - dot.x,
|
||||
y: dotArr[index + 1].y - dot.y,
|
||||
y1: dot.y - dotArr[index + 1].y
|
||||
}
|
||||
const v = Math.sqrt(Math.pow(AB.x, 2) + Math.pow(AB.y, 2));
|
||||
const angle = Math.atan2(AB.y1, AB.x) * (180 / Math.PI);
|
||||
lineArr.push({
|
||||
id: 'l' + index,
|
||||
x: dot.x + radius,
|
||||
y: dot.y + radius - 1,
|
||||
width: v,
|
||||
angle: AB.y1 > 0 ? Math.sqrt(Math.pow(angle, 2)) : -Math.sqrt(Math.pow(
|
||||
angle,
|
||||
2))
|
||||
})
|
||||
})
|
||||
lines.push({
|
||||
id: 'll' + idx,
|
||||
color: item.color,
|
||||
source: lineArr
|
||||
})
|
||||
})
|
||||
this.lines = lines
|
||||
},
|
||||
init() {
|
||||
this.maxValue = this.max;
|
||||
let itemGap = this.yAxisLine.itemGap || 60;
|
||||
let sections = this.yAxis.length - 1;
|
||||
let yAxis = this.yAxis;
|
||||
if (sections <= 0) {
|
||||
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||
let sectionsArr = this.generateArray(0, sections)
|
||||
yAxis = sectionsArr.map(item => {
|
||||
return {
|
||||
value: item * this.splitNumber + this.min
|
||||
}
|
||||
})
|
||||
this.maxValue = yAxis[yAxis.length - 1].value
|
||||
}
|
||||
this.yAxisData = yAxis;
|
||||
this.sections = sections + 1;
|
||||
this.height = itemGap * sections;
|
||||
const valH = this.xAxisVal.height || 48;
|
||||
this.scrollViewH = this.height + (this.xAxisLabel.height || 60) + valH;
|
||||
this.getCoordinatePoint();
|
||||
},
|
||||
/*
|
||||
dataset:面积图表数据
|
||||
xAxisValFormatter :格式化面积拐点value值
|
||||
*/
|
||||
draw(dataset, xAxisValFormatter) {
|
||||
this.xAxisValFormatter = xAxisValFormatter || null;
|
||||
this.dataset = dataset || [];
|
||||
this.init();
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
tooltipHandle(index) {
|
||||
let data = [...this.dataset]
|
||||
let tooltips = []
|
||||
data.forEach(item => {
|
||||
let color = item.color;
|
||||
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||
color = item.colorFormatter(item.source[index])
|
||||
}
|
||||
tooltips.push({
|
||||
color: color,
|
||||
name: item.name,
|
||||
val: item.source[index]
|
||||
})
|
||||
})
|
||||
this.tooltips = tooltips;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
},
|
||||
dotClick(index, idx) {
|
||||
this.activeIndex = index;
|
||||
this.activeIdx = idx;
|
||||
this.tooltipHandle(idx);
|
||||
this.$emit('click', {
|
||||
datasetIndex: index,
|
||||
sourceIndex: idx,
|
||||
...this.dataset[index]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__line-wrap {
|
||||
position: relative;
|
||||
transform: rotate(0deg) scale(1);
|
||||
/* margin: 0 auto; */
|
||||
}
|
||||
|
||||
.tui-line__legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-line__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-line__legend-circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-charts__line-box {
|
||||
position: relative;
|
||||
padding-left: 1px;
|
||||
box-sizing: border-box;
|
||||
transform-origin: 0 0;
|
||||
overflow: visible;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tui-line__scroll-view {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-charts__line {
|
||||
min-width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
/* overflow: hidden; */
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
.tui-line__between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tui-line__item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-line__flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tui-xAxis__tickmarks {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
transform: translateY(100%);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.tui-yAxis__split-line {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
border-right-width: 1px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.tui-yAxis__area-line {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-line__xAxis-text {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
flex: 1;
|
||||
transform: translate(-50%, 100%);
|
||||
padding-top: 8rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tui-line__border-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.tui-xAxis__line {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-top-width: 1px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.tui-line__first {
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
.tui-yAxis__val {
|
||||
transform: translateX(-100%);
|
||||
padding-right: 12rpx;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.tui-charts__line-dot {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
z-index: 12;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
box-sizing: border-box;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-line__val {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
padding-bottom: 12rpx;
|
||||
transform: translate(-50%, -100%);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
white-space: nowrap;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.tui-charts__dot-enlarge {
|
||||
transform: scale(1.4);
|
||||
}
|
||||
|
||||
.tui-charts__broken-line {
|
||||
position: absolute;
|
||||
transform-origin: 0 0;
|
||||
transition: all 0.3s;
|
||||
z-index: 10;
|
||||
border-color: transparent;
|
||||
box-sizing: border-box;
|
||||
/* transform: translateZ(0); */
|
||||
/* -webkit-backface-visibility:hidden; */
|
||||
}
|
||||
|
||||
.tui-line__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-line__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-tooltip__title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
|
||||
.tui-line__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.tui-tooltip__val-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.tui-area__chart-box {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
.tui-area__item {
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
/* 120%:100%,-20%:0% ,改为px时恢复*/
|
||||
clip-path: polygon(0% calc(100% * (1 - var(--tui-area-start))),
|
||||
100% calc(100% * (1 - var(--tui-area-end))),
|
||||
120% 100%,
|
||||
-20% 100%);
|
||||
}
|
||||
</style>
|
||||
580
src/components/thorui/tui-charts-bar/tui-charts-bar.vue
Normal file
580
src/components/thorui/tui-charts-bar/tui-charts-bar.vue
Normal file
@ -0,0 +1,580 @@
|
||||
<template>
|
||||
<view class="tui-charts__bar-wrap" :style="{width:width+'rpx'}">
|
||||
<view class="tui-bar__legend" v-if="legend.show">
|
||||
<view class="tui-bar__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts__bar-box" :style="{borderLeftColor:yAxisLine.color,borderBottomColor:xAxisLineColor}"
|
||||
v-if="yAxis.length>0 && dataset.length>0">
|
||||
<view class="tui-charts__bar-item"
|
||||
:class="{'tui-bar__item-stack':isStack,'tui-column__item-active':activeIndex===index && clickEffect==1,'tui-column__bar-opacity':clickEffect==2,'tui-column__bar-active':clickEffect==2 && activeIndex==index}"
|
||||
v-for="(item,index) in yAxis" :key="index" :style="{padding:yAxisLine.itemPadding ||'30rpx 0'}">
|
||||
<view class="tui-charts__bar" :class="{'tui-charts__bar-round':columnCap==='round'}"
|
||||
v-for="(bar,idx) in dataset" :key="idx"
|
||||
:style="{height:columnBarHeight+'rpx',borderRightColor:getBarColor(bar.source[index],bar.color,bar.colorFormatter),background:getBarColor(bar.source[index],bar.color,bar.colorFormatter),width:getBarWidth(bar.source[index],dataset),marginLeft:getMarginLeft(bar.source[index])}"
|
||||
@tap.stop="onBarTap(index,idx)">
|
||||
</view>
|
||||
<view class="tui-bar__val"
|
||||
v-if="(yAxisVal.show && clickEffect!=2 ) || (yAxisVal.show && clickEffect==2 && activeIndex===index)"
|
||||
:style="{color:yAxisVal.color,fontSize:(yAxisVal.size || 24)+'rpx',whiteSpace: yAxisVal.nowrap?'nowrap':'normal',left:getLeft(index)}">
|
||||
{{getXAxisVal(index)}}
|
||||
</view>
|
||||
<view class="tui-bar__yAxis-text"
|
||||
:style="{color:yAxisLabel.color || '#333',fontSize:(yAxisLabel.size || 24)+'rpx' }">
|
||||
{{item}}
|
||||
</view>
|
||||
<view class="tui-yAxis__tickmarks"
|
||||
:style="{width:yAxisTick.width || '12rpx',backgroundColor:yAxisTick.color || '#e3e3e3'}"></view>
|
||||
</view>
|
||||
<view class="tui-bar__yAxis-linebox">
|
||||
<view class="tui-bar__yAxis-line" :class="{'tui-yAxis__line-first':idx===0}"
|
||||
v-for="(item,idx) in xAxisData" :key="idx"
|
||||
:style="{borderLeftStyle:splitLine.type,borderLeftColor:splitLine.color}">
|
||||
<text class="tui-xAxis__val"
|
||||
:style="{color:item.color || xAxisLabel.color,fontSize:(xAxisLabel.size||24)+'rpx'}"
|
||||
v-if="xAxisLabel.show">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-column__tooltip" v-if="tooltip" :class="{'tui-column__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title">{{yAxis[activeIndex] || ''}}</view>
|
||||
<view class="tui-column__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-charts-bar",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//图表宽度 rpx
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 600
|
||||
},
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: false,
|
||||
size: 24,
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//x轴数据,如果不传则默认使用min,max值计算
|
||||
// {
|
||||
// value: 0,
|
||||
// color: "#a0a0a0"
|
||||
// }
|
||||
xAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//x轴最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//x轴最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
//x轴分段递增数值
|
||||
splitNumber: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
//分割线
|
||||
splitLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//分割线颜色,不显示则将颜色设置为transparent
|
||||
color: "#e3e3e3",
|
||||
type: "dashed"
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisLineColor: {
|
||||
type: String,
|
||||
//不显示则将颜色设置为transparent
|
||||
default: '#e3e3e3'
|
||||
},
|
||||
xAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//柱状条高度
|
||||
columnBarHeight: {
|
||||
type: [Number, String],
|
||||
default: 32
|
||||
},
|
||||
//y轴刻度线
|
||||
yAxisTick: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
width: '12rpx',
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3'
|
||||
}
|
||||
}
|
||||
},
|
||||
//y轴线条
|
||||
yAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: '#e3e3e3',
|
||||
//y轴item的padding值
|
||||
itemPadding: '30rpx 0'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisVal: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
//默认选中y轴索引
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
//是否堆叠展示
|
||||
isStack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//柱状条点击效果:1-出现背景,2-高亮显示,其他变暗 3-无效果
|
||||
clickEffect: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/*
|
||||
柱状条的端点样式
|
||||
round 向线条的每个末端添加圆形线帽
|
||||
square 向线条的每个末端添加正方形线帽
|
||||
*/
|
||||
columnCap: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
isMinus: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sections: 0,
|
||||
xAxisData: [],
|
||||
activeIndex: -1,
|
||||
activeIdx: -1,
|
||||
tooltips: [],
|
||||
tooltipShow: false,
|
||||
timer: null,
|
||||
/*========options============*/
|
||||
/*
|
||||
name: '',
|
||||
color: '',
|
||||
source: []
|
||||
colorFormatter:Function
|
||||
*/
|
||||
dataset: [],
|
||||
yAxisValFormatter: null,
|
||||
maxValue: 1
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
xAxis(newVal) {
|
||||
this.init()
|
||||
},
|
||||
currentIndex(newVal) {
|
||||
if (newVal != this.activeIndex) {
|
||||
this.activeIndex = newVal
|
||||
}
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
created() {
|
||||
// this.maxValue = this.max;
|
||||
this.init()
|
||||
this.activeIndex = this.currentIndex;
|
||||
},
|
||||
methods: {
|
||||
getBarWidth(value, dataset) {
|
||||
// ((bar.source[index]-(isStack?(min/dataset.length):min))/(maxValue-min))*width +'rpx'
|
||||
let min = this.min
|
||||
if (this.isMinus && !this.isStack) {
|
||||
value = Math.abs(value)
|
||||
min = 0
|
||||
}
|
||||
return ((value - (this.isStack ? (min / dataset.length) : min)) / (this.maxValue - this.min)) * this.width + 'rpx'
|
||||
},
|
||||
getMarginLeft(value) {
|
||||
let ml = 0
|
||||
if (this.isMinus && !this.isStack && Number(this.min) < 0) {
|
||||
const min = value > 0 ? Math.abs(this.min) : (value - this.min)
|
||||
ml = min / (this.maxValue - this.min) * this.width
|
||||
}
|
||||
return ml + 'rpx'
|
||||
},
|
||||
generateArray(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
getXAxisVal(index) {
|
||||
let showVal = '';
|
||||
let val = 0;
|
||||
if (this.dataset.length === 1) {
|
||||
val = this.dataset[0].source[index]
|
||||
showVal = val;
|
||||
} else if (this.dataset.length > 1) {
|
||||
let arr = []
|
||||
this.dataset.forEach(item => {
|
||||
arr.push(item.source[index])
|
||||
})
|
||||
val = arr
|
||||
}
|
||||
if (this.yAxisVal.formatter && typeof this.yAxisVal.formatter === 'function') {
|
||||
showVal = this.yAxisVal.formatter(val)
|
||||
} else if (this.yAxisValFormatter && typeof this.yAxisValFormatter === 'function') {
|
||||
showVal = this.yAxisValFormatter(val)
|
||||
}
|
||||
return showVal
|
||||
},
|
||||
getBarColor(val, color, colorFormatter) {
|
||||
let bgColor = color;
|
||||
if (colorFormatter && typeof colorFormatter === 'function') {
|
||||
let formatColor = colorFormatter(val)
|
||||
if (formatColor) {
|
||||
bgColor = formatColor
|
||||
}
|
||||
}
|
||||
return bgColor
|
||||
},
|
||||
getLeft(index) {
|
||||
let arr = [0]
|
||||
let total = 0
|
||||
this.dataset.forEach(item => {
|
||||
arr.push(item.source[index])
|
||||
total += item.source[index]
|
||||
})
|
||||
return (((this.isStack ? total : Math.max(...arr)) - this.min) / (this.maxValue - this.min)) * this.width +
|
||||
'rpx'
|
||||
},
|
||||
init() {
|
||||
let sections = this.xAxis.length - 1;
|
||||
let xAxis = this.xAxis;
|
||||
this.maxValue = this.max;
|
||||
if (sections <= 0) {
|
||||
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||
let sectionsArr = this.generateArray(0, sections)
|
||||
|
||||
xAxis = sectionsArr.map(item => {
|
||||
return {
|
||||
value: item * this.splitNumber + this.min
|
||||
}
|
||||
})
|
||||
this.maxValue = xAxis[xAxis.length - 1].value
|
||||
}
|
||||
this.xAxisData = xAxis;
|
||||
this.sections = sections + 1;
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
tooltipHandle(index) {
|
||||
let data = [...this.dataset]
|
||||
let tooltips = []
|
||||
data.forEach(item => {
|
||||
let color = item.color;
|
||||
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||
color = item.colorFormatter(item.source[index])
|
||||
}
|
||||
tooltips.push({
|
||||
color: color,
|
||||
name: item.name,
|
||||
val: item.source[index]
|
||||
})
|
||||
})
|
||||
this.tooltips = tooltips;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
},
|
||||
onBarTap(index, idx) {
|
||||
this.activeIndex = index;
|
||||
this.activeIdx = idx;
|
||||
this.tooltipHandle(index);
|
||||
this.$emit('click', {
|
||||
datasetIndex: idx,
|
||||
sourceIndex: index,
|
||||
...this.dataset[idx]
|
||||
})
|
||||
},
|
||||
/*
|
||||
dataset:柱状图表数据
|
||||
yAxisValFormatter :格式化柱状条顶部value值(此处传值是为了做兼容处理)
|
||||
*/
|
||||
draw(dataset, yAxisValFormatter) {
|
||||
this.yAxisValFormatter = yAxisValFormatter || null;
|
||||
this.dataset = dataset || [];
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__bar-wrap {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.tui-bar__legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-bar__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-legend__circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-charts__bar-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-left: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.tui-charts__bar-item {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
padding: 30rpx 0;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-bar__item-stack {
|
||||
flex-direction: row !important;
|
||||
}
|
||||
|
||||
.tui-yAxis__tickmarks {
|
||||
position: absolute;
|
||||
width: 16rpx;
|
||||
height: 1px;
|
||||
background-color: #e3e3e3;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-charts__bar {
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
text-align: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
/* border-right: 1px solid; */
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* position: relative; */
|
||||
flex-shrink: 0;
|
||||
|
||||
}
|
||||
|
||||
.tui-column__bar-opacity {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tui-column__bar-active {
|
||||
opacity: 0.9999;
|
||||
}
|
||||
|
||||
.tui-column__item-active {
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.tui-charts__bar-round {
|
||||
border-top-right-radius: 100px;
|
||||
border-bottom-right-radius: 100px;
|
||||
}
|
||||
|
||||
.tui-bar__val {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
padding-left: 12rpx;
|
||||
word-break: break-all;
|
||||
flex-shrink: 0;
|
||||
transition: left 0.3s;
|
||||
}
|
||||
|
||||
.tui-bar__yAxis-text {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
flex: 1;
|
||||
transform: translate(-100%, -50%);
|
||||
padding-right: 20rpx;
|
||||
word-break: break-all;
|
||||
|
||||
}
|
||||
|
||||
.tui-bar__yAxis-linebox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.tui-bar__yAxis-line {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
border-left: 1px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tui-yAxis__line-first {
|
||||
border-left: 0 !important;
|
||||
}
|
||||
|
||||
.tui-xAxis__val {
|
||||
transform: translateY(100%);
|
||||
padding-top: 12rpx;
|
||||
}
|
||||
|
||||
.tui-column__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-column__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-tooltip__title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
|
||||
.tui-column__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.tui-tooltip__val-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
</style>
|
||||
609
src/components/thorui/tui-charts-column/tui-charts-column.vue
Normal file
609
src/components/thorui/tui-charts-column/tui-charts-column.vue
Normal file
@ -0,0 +1,609 @@
|
||||
<template>
|
||||
<view class="tui-charts__column-wrap">
|
||||
<view class="tui-column__legend" v-if="legend.show">
|
||||
<view class="tui-column__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts__column-box" v-if="xAxis.length>0 && dataset.length>0">
|
||||
<scroll-view :scroll-x="scrollable" class="tui-column__scroll-view" :style="{height:scrollViewH+'rpx'}">
|
||||
<view :style="{height:(xAxisVal.height || 2) +'rpx'}" v-if="xAxisVal.show"></view>
|
||||
<view class="tui-charts__column" :style="{height:height+'rpx'}">
|
||||
<view class="tui-column__item"
|
||||
:class="{'tui-column__flex-1':!scrollable,'tui-column__flex-column':isStack,'tui-column__item-active':activeIndex===index && clickEffect==1,'tui-column__bar-opacity':clickEffect==2,'tui-column__bar-active':clickEffect==2 && activeIndex==index}"
|
||||
:style="{padding:scrollable? (xAxisLine.itemPadding ||'0 30rpx'):'0' }"
|
||||
v-for="(item,index) in xAxis" :key="index">
|
||||
<view class="tui-column__val"
|
||||
v-if="(xAxisVal.show && clickEffect!=2 ) || (xAxisVal.show && clickEffect==2 && activeIndex===index)"
|
||||
:style="{color:xAxisVal.color,fontSize:(xAxisVal.size || 24)+'rpx',whiteSpace: xAxisVal.nowrap?'nowrap':'normal'}">
|
||||
{{getYAxisVal(index)}}
|
||||
</view>
|
||||
<view class="tui-column__bar" :class="{'tui-column__bar-round':columnCap==='round'}"
|
||||
v-for="(bar,idx) in dataset" :key="idx"
|
||||
:style="{width:columnBarWidth+'rpx',borderTopColor:getBarColor(bar.source[index],bar.color,bar.colorFormatter),background:getBarColor(bar.source[index],bar.color,bar.colorFormatter),height:getBarHeight(bar.source[index],dataset,splitNumber,yAxisLine.itemGap),marginBottom:getMarginBottom(bar.source[index],yAxisLine.itemGap)}"
|
||||
@tap.stop="onBarTap(index,idx)">
|
||||
</view>
|
||||
<view class="tui-column__xAxis-text"
|
||||
:style="{color:xAxisLabel.color || '#333',fontSize:(xAxisLabel.size || 24)+'rpx' }">
|
||||
{{item}}
|
||||
</view>
|
||||
<view class="tui-xAxis__tickmarks"
|
||||
:style="{height:xAxisTick.height || '12rpx',backgroundColor:xAxisTick.color || '#e3e3e3'}">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tui-column__border-left"
|
||||
:style="{height:height+(xAxisVal.show?(xAxisVal.height || 2):0)+'rpx',backgroundColor:yAxisLine.color || '#e3e3e3'}">
|
||||
</view>
|
||||
<view class="tui-xAxis__line" :class="{'tui-line__first':index===0}"
|
||||
:style="{bottom:index*(yAxisLine.itemGap || 60)+(xAxisLabel.height || 60)+'rpx',borderTopStyle:index===0?'solid':splitLine.type,borderTopColor:index===0?xAxisLine.color:splitLine.color}"
|
||||
v-for="(item,index) in yAxisData" :key="index">
|
||||
<text class="tui-yAxis__val"
|
||||
:style="{color:item.color || yAxisLabel.color,fontSize:(yAxisLabel.size||24)+'rpx'}"
|
||||
v-if="yAxisLabel.show">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-column__tooltip" v-if="tooltip" :class="{'tui-column__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title">{{xAxis[activeIndex] || ''}}</view>
|
||||
<view class="tui-column__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-charts-column",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: false,
|
||||
size: 24,
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
xAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//默认选中x轴索引
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
//柱状条宽度
|
||||
columnBarWidth: {
|
||||
type: [Number, String],
|
||||
default: 32
|
||||
},
|
||||
//分割线
|
||||
splitLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//分割线颜色,不显示则将颜色设置为transparent
|
||||
color: "#e3e3e3",
|
||||
type: "dashed"
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴刻度线
|
||||
xAxisTick: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
height: '12rpx',
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3'
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴线条
|
||||
xAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: '#e3e3e3',
|
||||
//x轴item的padding值
|
||||
itemPadding: '0 30rpx'
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: "#333",
|
||||
size: 24,
|
||||
height: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisVal: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: false,
|
||||
color: "#333",
|
||||
size: 24,
|
||||
//如果show为true且val显示的时候,height需要设置一定的值保证val能显示完整 rpx
|
||||
height: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
//y轴数据,如果不传则默认使用min,max值计算
|
||||
// {
|
||||
// value: 0,
|
||||
// color: "#333"
|
||||
// }
|
||||
yAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//y轴最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//y轴最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
//y轴分段递增数值
|
||||
splitNumber: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
yAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3',
|
||||
//y轴item间距 rpx
|
||||
itemGap: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
//是否可滚动
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否堆叠展示
|
||||
isStack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//柱状条点击效果:1-出现背景,2-高亮显示,其他变暗 3-无效果
|
||||
clickEffect: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/*
|
||||
柱状条的端点样式
|
||||
round 向线条的每个末端添加圆形线帽
|
||||
square 向线条的每个末端添加正方形线帽
|
||||
*/
|
||||
columnCap: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
//是否支持负数显示
|
||||
isMinus: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: 0,
|
||||
scrollViewH: 0,
|
||||
sections: 0,
|
||||
yAxisData: [],
|
||||
activeIndex: -1,
|
||||
activeIdx: -1,
|
||||
tooltips: [],
|
||||
tooltipShow: false,
|
||||
timer: null,
|
||||
/*========options============*/
|
||||
/*
|
||||
name: '',
|
||||
color: '',
|
||||
source: []
|
||||
colorFormatter:Function
|
||||
*/
|
||||
dataset: [],
|
||||
xAxisValFormatter: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
yAxis(newVal) {
|
||||
this.init()
|
||||
},
|
||||
currentIndex(newVal) {
|
||||
if (newVal != this.activeIndex) {
|
||||
this.activeIndex = newVal
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.activeIndex = this.currentIndex;
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
getBarHeight(value, dataset, splitNumber, itemGap) {
|
||||
// ((bar.source[index]-(isStack?(min/dataset.length):min))/splitNumber)*(yAxisLine.itemGap || 60) +'rpx'
|
||||
let min = this.min
|
||||
if (this.isMinus && !this.isStack) {
|
||||
value = Math.abs(value)
|
||||
min = 0
|
||||
}
|
||||
return ((value - (this.isStack ? (min / dataset.length) : min)) / this.splitNumber) * (itemGap ||
|
||||
60) + 'rpx'
|
||||
},
|
||||
getMarginBottom(value, itemGap) {
|
||||
let mb = 0
|
||||
if (this.isMinus && !this.isStack && Number(this.min) < 0) {
|
||||
const min = value > 0 ? Math.abs(this.min) : (value - this.min)
|
||||
mb = min / this.splitNumber * (itemGap || 60)
|
||||
}
|
||||
return mb + 'rpx'
|
||||
},
|
||||
generateArray(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
getYAxisVal(index) {
|
||||
let showVal = '';
|
||||
let val = 0;
|
||||
if (this.dataset.length === 1) {
|
||||
val = this.dataset[0].source[index]
|
||||
showVal = val;
|
||||
} else if (this.dataset.length > 1) {
|
||||
let arr = []
|
||||
this.dataset.forEach(item => {
|
||||
arr.push(item.source[index])
|
||||
})
|
||||
val = arr
|
||||
}
|
||||
if (this.xAxisVal.formatter && typeof this.xAxisVal.formatter === 'function') {
|
||||
showVal = this.xAxisVal.formatter(val)
|
||||
} else if (this.xAxisValFormatter && typeof this.xAxisValFormatter === 'function') {
|
||||
showVal = this.xAxisValFormatter(val)
|
||||
}
|
||||
return showVal
|
||||
},
|
||||
getBarColor(val, color, colorFormatter) {
|
||||
let bgColor = color;
|
||||
if (colorFormatter && typeof colorFormatter === 'function') {
|
||||
let formatColor = colorFormatter(val)
|
||||
if (formatColor) {
|
||||
bgColor = formatColor
|
||||
}
|
||||
}
|
||||
return bgColor
|
||||
},
|
||||
init() {
|
||||
let itemGap = this.yAxisLine.itemGap || 60;
|
||||
let sections = this.yAxis.length - 1;
|
||||
let yAxis = this.yAxis;
|
||||
if (sections <= 0) {
|
||||
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||
let sectionsArr = this.generateArray(0, sections)
|
||||
|
||||
yAxis = sectionsArr.map(item => {
|
||||
return {
|
||||
value: item * this.splitNumber + this.min
|
||||
}
|
||||
})
|
||||
}
|
||||
this.yAxisData = yAxis;
|
||||
this.sections = sections + 1;
|
||||
this.height = itemGap * sections;
|
||||
const valH = this.xAxisVal.show ? (this.xAxisVal.height || 2) : 0;
|
||||
this.scrollViewH = this.height + (this.xAxisLabel.height || 60) + valH;
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
tooltipHandle(index) {
|
||||
let data = [...this.dataset]
|
||||
let tooltips = []
|
||||
data.forEach(item => {
|
||||
let color = item.color;
|
||||
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||
color = item.colorFormatter(item.source[index])
|
||||
}
|
||||
tooltips.push({
|
||||
color: color,
|
||||
name: item.name,
|
||||
val: item.source[index]
|
||||
})
|
||||
})
|
||||
this.tooltips = tooltips;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
},
|
||||
onBarTap(index, idx) {
|
||||
this.activeIndex = index;
|
||||
this.activeIdx = idx;
|
||||
this.tooltipHandle(index);
|
||||
this.$emit('click', {
|
||||
datasetIndex: idx,
|
||||
sourceIndex: index,
|
||||
...this.dataset[idx]
|
||||
})
|
||||
},
|
||||
/*
|
||||
dataset:柱状图表数据
|
||||
xAxisValFormatter :格式化柱状条顶部value值(此处传值是为了做兼容处理)
|
||||
*/
|
||||
draw(dataset, xAxisValFormatter) {
|
||||
this.xAxisValFormatter = xAxisValFormatter || null;
|
||||
this.dataset = dataset || [];
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__column-wrap {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tui-charts__column-box {
|
||||
position: relative;
|
||||
padding-left: 1px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-column__scroll-view {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-charts__column {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-column__between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tui-column__item {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.tui-column__bar-opacity {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tui-column__bar-active {
|
||||
/*这里请勿随意将值改为1*/
|
||||
opacity: 0.9999;
|
||||
}
|
||||
|
||||
.tui-column__flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tui-column__item-active {
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
padding-top: 20rpx !important;
|
||||
}
|
||||
|
||||
.tui-column__flex-column {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-xAxis__tickmarks {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
transform: translateY(100%);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.tui-column__bar {
|
||||
transition: all 0.3s;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
/* border-top: 1px solid; */
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.tui-column__bar-round {
|
||||
border-top-left-radius: 100px;
|
||||
border-top-right-radius: 100px;
|
||||
}
|
||||
|
||||
.tui-column__val {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
padding-bottom: 12rpx;
|
||||
transform: translate(-50%, -100%);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tui-column__xAxis-text {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
flex: 1;
|
||||
transform: translate(-50%, 100%);
|
||||
padding-top: 8rpx;
|
||||
word-break: break-all;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.tui-column__border-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.tui-xAxis__line {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-top-width: 1px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-line__first {
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
.tui-yAxis__val {
|
||||
transform: translateX(-100%);
|
||||
padding-right: 12rpx;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.tui-column__legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-column__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-legend__circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-column__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-column__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-tooltip__title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
|
||||
.tui-column__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.tui-tooltip__val-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
</style>
|
||||
273
src/components/thorui/tui-charts-funnel/tui-charts-funnel.vue
Normal file
273
src/components/thorui/tui-charts-funnel/tui-charts-funnel.vue
Normal file
@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<view class="tui-charts__funnel-wrap" :class="{'tui-charts__funnel-vertical':legend.direction!=='vertical'}">
|
||||
<view class="tui-funnel__legend" :class="{'tui-legend__flex-column':legend.direction==='vertical'}"
|
||||
v-if="legend.show">
|
||||
<view class="tui-funnel__legend-item" :style="{marginLeft:legend.direction==='vertical'?'0':'24rpx'}"
|
||||
v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts-funnel" :style="{width:w+'px',height:h+'px'}">
|
||||
<view class="tui-charts__funnel-item" :class="['tui-funnel__'+sort]"
|
||||
:style="{width:item.width+'px',height:itemHeight+'px',borderTopWidth:sort==='desc'?itemHeight+'px':0,borderTopColor:sort==='desc'?item.color:'transparent',borderBottomWidth:sort==='asc'?itemHeight+'px':0,borderBottomColor:sort==='asc'?item.color:'transparent',borderLeftWidth:item.diff+'px',borderRightWidth:item.diff+'px'}"
|
||||
v-for="(item,index) in dataset" :key="index" @tap.stop="itemClick(index)"></view>
|
||||
|
||||
<view class="tui-funnel__tooltip" v-if="tooltip" :class="{'tui-funnel__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title" v-if="title">{{title}}</view>
|
||||
<view class="tui-funnel__tooltip-item" :style="{paddingTop:title?'24rpx':'0'}">
|
||||
<view class="tui-legend__circle"
|
||||
:style="{backgroundColor:dataset[activeIndex] && dataset[activeIndex].color}"></view>
|
||||
<text class="tui-tooltip__val">{{dataset[activeIndex] && dataset[activeIndex].name}}</text>
|
||||
<text
|
||||
class="tui-tooltip__val tui-tooltip__val-ml">{{dataset[activeIndex] && dataset[activeIndex].value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function ascSort(a, b) {
|
||||
return a.value - b.value;
|
||||
}
|
||||
|
||||
function descSort(a, b) {
|
||||
return b.value - a.value;
|
||||
}
|
||||
export default {
|
||||
name: "tui-charts-funnel",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//统计名称
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//总宽度 rpx
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 400
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 480
|
||||
},
|
||||
//间距 rpx
|
||||
gap: {
|
||||
type: [Number, String],
|
||||
default: 4
|
||||
},
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
size: 24,
|
||||
color: '#333',
|
||||
//horizontal、vertical
|
||||
direction: 'horizontal'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//asc,desc
|
||||
sort: {
|
||||
type: String,
|
||||
default: 'desc'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tooltips: [],
|
||||
tooltipShow: false,
|
||||
timer: null,
|
||||
activeIndex: -1,
|
||||
dataset: [],
|
||||
itemHeight: 60,
|
||||
w: 200,
|
||||
h: 240
|
||||
};
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
getPx(rpx) {
|
||||
let px = parseInt(uni.upx2px(Number(rpx)))
|
||||
return px % 2 === 0 ? px : px - 1
|
||||
},
|
||||
getEvenNum(px) {
|
||||
px = parseInt(px)
|
||||
return px % 2 === 0 ? px : px - 1
|
||||
},
|
||||
draw(data) {
|
||||
let dataList = JSON.parse(JSON.stringify(data))
|
||||
let dataset = dataList.sort(descSort)
|
||||
let w = this.getPx(this.width || 400)
|
||||
let h = this.getPx(this.height || 480)
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
let len = dataset.length
|
||||
let gap = Number(this.gap) * (len - 1)
|
||||
let max = Number(dataset[0].value)
|
||||
this.itemHeight = (h - this.getPx(gap)) / len
|
||||
dataset.map((item, index) => {
|
||||
let width = index === 0 ? w : this.getEvenNum(Number(item.value) / max * w)
|
||||
if (dataset[index + 1]) {
|
||||
let w1 = this.getEvenNum(Number(dataset[index + 1].value) / max * w)
|
||||
item.diff = Math.abs(width - w1) / 2
|
||||
} else {
|
||||
item.diff = width / 2
|
||||
}
|
||||
item.width = width - (item.diff || 0) * 2
|
||||
})
|
||||
if (this.sort === 'asc') {
|
||||
this.dataset = dataset.sort(ascSort)
|
||||
} else {
|
||||
this.dataset = dataset
|
||||
}
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
itemClick(index) {
|
||||
this.activeIndex = index;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
this.$emit('click', {
|
||||
index: index,
|
||||
...this.dataset[index]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__funnel-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.tui-charts__funnel-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-funnel__legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-legend__flex-column {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-right: 40rpx;
|
||||
}
|
||||
|
||||
.tui-funnel__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.tui-legend__circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-charts-funnel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-charts__funnel-item {
|
||||
border-left-style: solid;
|
||||
border-left-color: transparent;
|
||||
border-right-style: solid;
|
||||
border-right-color: transparent;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-funnel__asc {
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.tui-funnel__desc {
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.tui-funnel__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-funnel__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-tooltip__title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
line-height: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-funnel__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
</style>
|
||||
653
src/components/thorui/tui-charts-line/tui-charts-line.vue
Normal file
653
src/components/thorui/tui-charts-line/tui-charts-line.vue
Normal file
@ -0,0 +1,653 @@
|
||||
<template>
|
||||
<view class="tui-charts__line-wrap" :style="{width:width+'rpx'}">
|
||||
<view class="tui-line__legend" v-if="legend.show">
|
||||
<view class="tui-line__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts__line-box" v-if="xAxis.length>0 && dataset.length>0" :style="{width:width+'rpx'}">
|
||||
<scroll-view :scroll-x="scrollable" class="tui-line__scroll-view" :style="{height:scrollViewH+'rpx'}">
|
||||
<view :style="{height:(xAxisVal.height || 48) +'rpx'}"></view>
|
||||
<view class="tui-charts__line" :style="{height:height+'rpx'}">
|
||||
<view class="tui-line__item" :class="{'tui-line__flex-1':!scrollable}"
|
||||
:style="{width:(xAxisLine.itemGap || 120)+'rpx'}" v-for="(item,index) in xAxis" :key="index">
|
||||
<view class="tui-line__xAxis-text"
|
||||
:style="{color:xAxisLabel.color || '#333',fontSize:(xAxisLabel.size || 24)+'rpx' }">
|
||||
{{item}}
|
||||
</view>
|
||||
<view class="tui-yAxis__split-line"
|
||||
:style="{borderRightStyle:yAxisSplitLine.type || 'dashed',borderRightColor:yAxisSplitLine.color || '#e3e3e3'}"
|
||||
v-if="tooltipShow && index==activeIdx">
|
||||
</view>
|
||||
<view class="tui-xAxis__tickmarks"
|
||||
:style="{height:xAxisTick.height || '12rpx',backgroundColor:xAxisTick.color || '#e3e3e3'}">
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="(dot,i) in dots" :key="dot.id">
|
||||
<view class="tui-charts__line-dot"
|
||||
:class="{'tui-charts__dot-enlarge':tooltipShow && j==activeIdx}" @tap.stop="dotClick(i,j)"
|
||||
v-for="(d,j) in dot.source" :key="d.id"
|
||||
:style="{bottom: d.y+'rpx', left: d.x+'rpx',width:(brokenDot.width || 12)+'rpx',height:(brokenDot.width || 12)+'rpx',borderColor:dot.color || brokenDot.color,background:brokenDot.color || dot.color}">
|
||||
<text class="tui-line__val"
|
||||
:style="{fontSize:(xAxisVal.size || 24)+'rpx',color:xAxisVal.color}"
|
||||
v-if="xAxisVal.show">
|
||||
{{getYAxisVal(i,j)}}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-for="(line,idx) in lines" :key="line.id">
|
||||
<view class="tui-charts__broken-line" v-for="(l,k) in line.source" :key="l.id"
|
||||
:style="{height:brokenLineHeight+'px',background:line.color,bottom: l.y+'rpx', left: l.x+'rpx',width: l.width+'rpx','-webkit-transform': `rotate(${l.angle}deg)`,transform: `rotate(${l.angle}deg)`}">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tui-line__border-left"
|
||||
:style="{height:height+(xAxisVal.height || 48)+'rpx',backgroundColor:yAxisLine.color || '#e3e3e3'}">
|
||||
</view>
|
||||
<view class="tui-xAxis__line" :class="{'tui-line__first':index===0}"
|
||||
:style="{bottom:index*(yAxisLine.itemGap || 60)+(xAxisLabel.height || 60)+'rpx',borderTopStyle:index===0?'solid':splitLine.type,borderTopColor:index===0?xAxisLine.color:splitLine.color}"
|
||||
v-for="(item,index) in yAxisData" :key="index">
|
||||
<text class="tui-yAxis__val"
|
||||
:style="{color:item.color || yAxisLabel.color,fontSize:(yAxisLabel.size||24)+'rpx'}"
|
||||
v-if="yAxisLabel.show">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tui-line__tooltip" v-if="tooltip" :class="{'tui-line__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title">{{xAxis[activeIdx] || ''}}</view>
|
||||
<view class="tui-line__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-charts-line",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//图表宽度
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 620
|
||||
},
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: false,
|
||||
size: 24,
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
xAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//默认选中x轴索引
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
//分割线
|
||||
splitLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//分割线颜色,不显示则将颜色设置为transparent
|
||||
color: "#e3e3e3",
|
||||
type: "dashed"
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴刻度线
|
||||
xAxisTick: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
height: '12rpx',
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3'
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴线条
|
||||
xAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: '#e3e3e3',
|
||||
//x轴item间距 rpx
|
||||
itemGap: 120
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: "#333",
|
||||
size: 24,
|
||||
height: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisVal: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24,
|
||||
//如果show为true且val显示的时候,height需要设置一定的值保证val能显示完整 rpx
|
||||
height: 48
|
||||
}
|
||||
}
|
||||
},
|
||||
//点击坐标点所显示的分割线
|
||||
yAxisSplitLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//分割线颜色,不显示则将颜色设置为transparent
|
||||
color: "transparent",
|
||||
type: "dashed"
|
||||
}
|
||||
}
|
||||
},
|
||||
//折线坐标点宽度 rpx
|
||||
brokenDot: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
width: 12,
|
||||
//点的背景色
|
||||
color: '#F8F8F8'
|
||||
}
|
||||
}
|
||||
},
|
||||
//折线高度/粗细 px
|
||||
brokenLineHeight: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//y轴数据,如果不传则默认使用min,max值计算
|
||||
// {
|
||||
// value: 0,
|
||||
// color: "#333"
|
||||
// }
|
||||
yAxis: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//y轴最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//y轴最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
//y轴分段递增数值
|
||||
splitNumber: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
yAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3',
|
||||
//y轴item间距 rpx
|
||||
itemGap: 60
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
//是否可滚动
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
height: 0,
|
||||
scrollViewH: 0,
|
||||
sections: 0,
|
||||
yAxisData: [],
|
||||
activeIndex: -1,
|
||||
activeIdx: -1,
|
||||
tooltips: [],
|
||||
tooltipShow: false,
|
||||
timer: null,
|
||||
dots: [],
|
||||
lines: [],
|
||||
/*========options============*/
|
||||
/*
|
||||
name: '',
|
||||
color: '',
|
||||
source: []
|
||||
colorFormatter:Function
|
||||
*/
|
||||
dataset: [],
|
||||
xAxisValFormatter: null,
|
||||
maxValue: 1
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.activeIdx = this.currentIndex;
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
getYAxisVal(idx, index) {
|
||||
let showVal = this.dataset[idx].source[index];
|
||||
if (this.xAxisVal.formatter && typeof this.xAxisVal.formatter === 'function') {
|
||||
showVal = this.xAxisVal.formatter(showVal)
|
||||
} else if (this.xAxisValFormatter && typeof this.xAxisValFormatter === 'function') {
|
||||
showVal = this.xAxisValFormatter(showVal)
|
||||
}
|
||||
return showVal
|
||||
},
|
||||
generateArray(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
getValue(val) {
|
||||
return val < 0 ? 0 : val;
|
||||
},
|
||||
getCoordinatePoint() {
|
||||
const xAxis = [...this.xAxis];
|
||||
const xSections = xAxis.length;
|
||||
const ySections = this.yAxisData.length - 1;
|
||||
const itemGap = this.scrollable ? (this.xAxisLine.itemGap || 120) : (this.width / xSections);
|
||||
let dots = [];
|
||||
let radius = (this.brokenDot.width || 12) / 2;
|
||||
|
||||
this.dataset.map((item, index) => {
|
||||
let source = item.source || []
|
||||
let dotArr = []
|
||||
source.map((val, idx) => {
|
||||
dotArr.push({
|
||||
id: 'd' + idx,
|
||||
x: this.getValue((0.5 + idx) * itemGap - radius),
|
||||
y: this.getValue((val - this.min) / (this.maxValue - this.min) * (this
|
||||
.yAxisLine
|
||||
.itemGap || 60) *
|
||||
ySections - radius)
|
||||
})
|
||||
})
|
||||
dots.push({
|
||||
id: 'dd' + index,
|
||||
color: item.color,
|
||||
source: dotArr
|
||||
})
|
||||
})
|
||||
this.dots = dots;
|
||||
this.drawLines(dots);
|
||||
},
|
||||
drawLines(dots) {
|
||||
let lines = []
|
||||
// dots是点的集合 : Array<{ x: number; y: number; }>
|
||||
let radius = (this.brokenDot.width || 12) / 2;
|
||||
dots.map((item, idx) => {
|
||||
let dotArr = item.source;
|
||||
let lineArr = [];
|
||||
dotArr.map((dot, index) => {
|
||||
// 最后一个点没有连线
|
||||
if (!dotArr[index + 1]) return;
|
||||
const AB = {
|
||||
x: dotArr[index + 1].x - dot.x,
|
||||
y: dotArr[index + 1].y - dot.y,
|
||||
y1: dot.y - dotArr[index + 1].y
|
||||
}
|
||||
// 向量的模
|
||||
const v = Math.sqrt(Math.pow(AB.x, 2) + Math.pow(AB.y, 2));
|
||||
// 求出偏转角度
|
||||
const angle = Math.atan2(AB.y1, AB.x) * (180 / Math.PI);
|
||||
lineArr.push({
|
||||
id: 'l' + index,
|
||||
x: dot.x + radius,
|
||||
y: dot.y + radius - 1,
|
||||
width: v,
|
||||
angle: AB.y1 > 0 ? Math.sqrt(Math.pow(angle, 2)) : -Math.sqrt(Math.pow(
|
||||
angle,
|
||||
2))
|
||||
})
|
||||
})
|
||||
lines.push({
|
||||
id: 'll' + idx,
|
||||
color: item.color,
|
||||
source: lineArr
|
||||
})
|
||||
})
|
||||
this.lines = lines
|
||||
},
|
||||
init() {
|
||||
this.maxValue = this.max;
|
||||
let itemGap = this.yAxisLine.itemGap || 60;
|
||||
let sections = this.yAxis.length - 1;
|
||||
let yAxis = this.yAxis;
|
||||
if (sections <= 0) {
|
||||
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||
let sectionsArr = this.generateArray(0, sections)
|
||||
|
||||
yAxis = sectionsArr.map(item => {
|
||||
return {
|
||||
value: item * this.splitNumber + this.min
|
||||
}
|
||||
})
|
||||
this.maxValue = yAxis[yAxis.length - 1].value
|
||||
}
|
||||
this.yAxisData = yAxis;
|
||||
this.sections = sections + 1;
|
||||
this.height = itemGap * sections;
|
||||
const valH = this.xAxisVal.height || 48;
|
||||
this.scrollViewH = this.height + (this.xAxisLabel.height || 60) + valH;
|
||||
this.getCoordinatePoint();
|
||||
},
|
||||
/*
|
||||
dataset:折线图表数据
|
||||
xAxisValFormatter :格式化折线拐点value值(此处传值是为了做兼容处理)
|
||||
*/
|
||||
draw(dataset, xAxisValFormatter) {
|
||||
this.xAxisValFormatter = xAxisValFormatter || null;
|
||||
this.dataset = dataset || [];
|
||||
this.init();
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
tooltipHandle(index) {
|
||||
let data = [...this.dataset]
|
||||
let tooltips = []
|
||||
data.forEach(item => {
|
||||
let color = item.color;
|
||||
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||
color = item.colorFormatter(item.source[index])
|
||||
}
|
||||
tooltips.push({
|
||||
color: color,
|
||||
name: item.name,
|
||||
val: item.source[index]
|
||||
})
|
||||
})
|
||||
this.tooltips = tooltips;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
},
|
||||
dotClick(index, idx) {
|
||||
this.activeIndex = index;
|
||||
this.activeIdx = idx;
|
||||
this.tooltipHandle(idx);
|
||||
this.$emit('click', {
|
||||
datasetIndex: index,
|
||||
sourceIndex: idx,
|
||||
...this.dataset[index]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__line-wrap {
|
||||
position: relative;
|
||||
transform: rotate(0deg) scale(1);
|
||||
/* margin: 0 auto; */
|
||||
}
|
||||
|
||||
.tui-line__legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-line__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-line__legend-circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-charts__line-box {
|
||||
position: relative;
|
||||
padding-left: 1px;
|
||||
box-sizing: border-box;
|
||||
transform-origin: 0 0;
|
||||
overflow: visible;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tui-line__scroll-view {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-charts__line {
|
||||
min-width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
/* overflow: hidden; */
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
.tui-line__between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tui-line__item {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-line__flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tui-xAxis__tickmarks {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
transform: translateY(100%);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.tui-yAxis__split-line {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
border-right-width: 1px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.tui-line__xAxis-text {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
flex: 1;
|
||||
transform: translate(-50%, 100%);
|
||||
padding-top: 8rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.tui-line__border-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.tui-xAxis__line {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-top-width: 1px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.tui-line__first {
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
.tui-yAxis__val {
|
||||
transform: translateX(-100%);
|
||||
padding-right: 12rpx;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.tui-charts__line-dot {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s;
|
||||
z-index: 12;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
box-sizing: border-box;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-line__val {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
padding-bottom: 12rpx;
|
||||
transform: translate(-50%, -100%);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
white-space: nowrap;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.tui-charts__dot-enlarge {
|
||||
transform: scale(1.4);
|
||||
}
|
||||
|
||||
.tui-charts__broken-line {
|
||||
position: absolute;
|
||||
transform-origin: 0 0;
|
||||
transition: all 0.3s;
|
||||
z-index: 10;
|
||||
border-color: transparent;
|
||||
box-sizing: border-box;
|
||||
/* transform: translateZ(0); */
|
||||
/* -webkit-backface-visibility:hidden; */
|
||||
}
|
||||
|
||||
.tui-line__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-line__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-tooltip__title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
|
||||
.tui-line__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.tui-tooltip__val-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
</style>
|
||||
20
src/components/thorui/tui-charts-mixed/tui-charts-mixed.vue
Normal file
20
src/components/thorui/tui-charts-mixed/tui-charts-mixed.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"tui-charts-mixed",
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
282
src/components/thorui/tui-charts-pie/tui-charts-pie.vue
Normal file
282
src/components/thorui/tui-charts-pie/tui-charts-pie.vue
Normal file
@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<view class="tui-charts__pie-wrap" :class="{'tui-charts__pie-vertical':legend.direction!=='vertical'}">
|
||||
<view class="tui-pie__legend" :class="{'tui-legend__flex-column':legend.direction==='vertical'}"
|
||||
v-if="legend.show">
|
||||
<view class="tui-pie__legend-item" :style="{marginLeft:legend.direction==='vertical'?'0':'24rpx'}" v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts__pie-box" :style="{width:diam+'rpx',height:diam+'rpx',backgroundColor:backgroundColor}">
|
||||
<view class="tui-charts__pie-itembox"
|
||||
:style="{width:diam/2+2+'rpx',height:diam+'rpx',backgroundColor:item.angle>180?item.color:'transparent',zIndex:item.angle>180?10:'auto',clip:item.transformAngle>180?`rect(0, ${diam/2}rpx, ${diam}rpx, 0)`:'auto'}"
|
||||
v-for="(item,index) in dataset" :key="index" @tap.stop="itemClick(index)">
|
||||
<view class="tui-charts__pie-item"
|
||||
:style="{width:diam/2+'rpx',height:diam+'rpx',marginLeft:diam/2+'rpx','-webkit-transform': `rotate(${item.transformAngle}deg)`,'transform': `rotate(${item.transformAngle}deg)`,backgroundColor:item.color}">
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="tui-charts__pie-annular" v-if="type==2"
|
||||
:style="{width:annular.width+'rpx',height:annular.width+'rpx',backgroundColor:annular.backgroundColor}">
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-pie__tooltip" v-if="tooltip" :class="{'tui-pie__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title" v-if="title">{{title}}</view>
|
||||
<view class="tui-pie__tooltip-item" :style="{paddingTop:title?'24rpx':'0'}">
|
||||
<view class="tui-legend__circle"
|
||||
:style="{backgroundColor:dataset[activeIndex] && dataset[activeIndex].color}"></view>
|
||||
<text class="tui-tooltip__val">{{dataset[activeIndex] && dataset[activeIndex].name}}</text>
|
||||
<text
|
||||
class="tui-tooltip__val tui-tooltip__val-ml">{{dataset[activeIndex] && dataset[activeIndex].value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-charts-pie",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//统计名称
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//饼状图直径 rpx
|
||||
diam: {
|
||||
type: Number,
|
||||
default: 400
|
||||
},
|
||||
//饼状图背景色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
size: 24,
|
||||
color: '#333',
|
||||
//horizontal,vertical
|
||||
direction: 'horizontal'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//展示类型:1-饼状 2-环状
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//环形中间圆圈样式
|
||||
annular: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
width: 200,
|
||||
backgroundColor: '#f8f8f8'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
data() {
|
||||
return {
|
||||
tooltips: [],
|
||||
tooltipShow: false,
|
||||
timer: null,
|
||||
activeIndex: -1,
|
||||
/*========options============*/
|
||||
/*
|
||||
value:0,
|
||||
name: '',
|
||||
color: ''
|
||||
*/
|
||||
dataset: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getTotalVal(data) {
|
||||
let val = 0;
|
||||
data.forEach((item, index) => {
|
||||
val += item.value;
|
||||
})
|
||||
return val;
|
||||
},
|
||||
init(dataset) {
|
||||
let data = [...dataset]
|
||||
const total = this.getTotalVal(data);
|
||||
let totalAngle = 0;
|
||||
data.map((item, index) => {
|
||||
item.transformAngle = totalAngle;
|
||||
item.angle = Number((item.value / total * 360).toFixed(1))
|
||||
totalAngle += item.angle
|
||||
})
|
||||
this.dataset = data;
|
||||
},
|
||||
draw(dataset) {
|
||||
this.init(dataset)
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
itemClick(index) {
|
||||
this.activeIndex = index;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
this.$emit('click', {
|
||||
index: index,
|
||||
...this.dataset[index]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__pie-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.tui-charts__pie-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-pie__legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-legend__flex-column {
|
||||
flex-direction: column;
|
||||
margin-right: 40rpx;
|
||||
}
|
||||
|
||||
.tui-pie__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.tui-legend__circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-charts__pie-box {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
transform: rotate(0deg) translateZ(0);
|
||||
flex-shrink: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.tui-charts__pie-itembox {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-charts__pie-item {
|
||||
transform-origin: 0 50%;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
/* transform-style:preserve-3d; */
|
||||
/* transition: all 0.3s; */
|
||||
}
|
||||
|
||||
.tui-charts__pie-annular {
|
||||
border-radius: 50%;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
position: absolute;
|
||||
z-index: 12;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-pie__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-pie__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-tooltip__title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
line-height: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-pie__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
</style>
|
||||
371
src/components/thorui/tui-charts-radar/tui-charts-radar.vue
Normal file
371
src/components/thorui/tui-charts-radar/tui-charts-radar.vue
Normal file
@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<view class="tui-charts__radar-box" :style="{width:radar_w+'px'}">
|
||||
<view class="tui-radar__legend" v-if="legend.show">
|
||||
<view class="tui-radar__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-legend__circle" :style="{background:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts-radar" :class="{'tui-radar__mrgin':label.show}"
|
||||
:style="{width:radar_w+'px',height:radar_w+'px'}">
|
||||
<view class="tui-radar__radius" v-for="(item,index) in indicators" :key="index"
|
||||
:style="{height:radar_w/2+'px',transform:`rotate(${item.angle}deg)`,background:axisLineColor,width:lineBold?'2px':'1px'}">
|
||||
<view class="tui-radar__name" v-if="label.show"
|
||||
:style="{color:label.color || '#bbb',fontSize:(label.size || 24) +'rpx'}">{{item.name}}</view>
|
||||
</view>
|
||||
<view class="tui-radar__center" :style="{width:lineBold?'2px':'1px',height:lineBold?'2px':'1px'}">
|
||||
<view class="tui-radar__hypotenuse" v-for="(l,idx) in hypotenuse" :key="l.id"
|
||||
:style="[{bottom: l.y+'px', left: l.x+'px',width: l.width+'px',transform: `rotate(${l.angle}deg)`,background:splitLineColor,height:lineBold?'2px':'1px'}]">
|
||||
</view>
|
||||
<view v-for="(item,index) in dataset" :key="index" @tap.stop="onDotTap(index)">
|
||||
<view class="tui-radar__dataset" v-for="(d,i) in item.lines" :key="d.id"
|
||||
:style="{bottom: d.y+'px', left: d.x+'px',width: d.width+'px',transform: `rotate(${d.angle}deg)`,background:item.color,height:lineBold?'2px':'1px'}">
|
||||
<view class="tui-radar__dot" :style="{background:item.color}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-radar__tooltip" v-if="tooltip" :class="{'tui-radar__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title">{{name}}</view>
|
||||
<view class="tui-radar__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||
<view class="tui-legend__circle" :style="{background:color}"></view>
|
||||
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-charts-radar",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
size: 24,
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 480
|
||||
},
|
||||
splitNumber: {
|
||||
type: [Number, String],
|
||||
default: 5
|
||||
},
|
||||
indicator: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: true,
|
||||
color: '#bbb',
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
axisLineColor: {
|
||||
type: String,
|
||||
default: '#ddd'
|
||||
},
|
||||
splitLineColor: {
|
||||
type: String,
|
||||
default: '#eee'
|
||||
},
|
||||
lineBold:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
data() {
|
||||
return {
|
||||
radar_w: 200,
|
||||
dataset: [],
|
||||
indicators: [],
|
||||
hypotenuse: [],
|
||||
name: '',
|
||||
color: '',
|
||||
tooltips: [],
|
||||
tooltipShow: false,
|
||||
timer: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.radar_w = this.getPx(this.width)
|
||||
this.draw(this.dataset)
|
||||
},
|
||||
methods: {
|
||||
getPx(rpx) {
|
||||
let px = parseInt(uni.upx2px(Number(rpx)))
|
||||
return px % 2 === 0 ? px : px + 1
|
||||
},
|
||||
getDrawData(dots, idx, type = 'l') {
|
||||
let data = [];
|
||||
dots.map((dot, index) => {
|
||||
let obj = !dots[index + 1] ? dots[0] : dots[index + 1]
|
||||
const AB = {
|
||||
x: obj.x - dot.x,
|
||||
y: obj.y - dot.y,
|
||||
y1: dot.y - obj.y
|
||||
}
|
||||
const v = Math.sqrt(Math.pow(AB.x, 2) + Math.pow(AB.y, 2));
|
||||
const angle = Math.atan2(AB.y1, AB.x) * (180 / Math.PI);
|
||||
data.push({
|
||||
id: type + idx + '_' + index,
|
||||
x: dot.x + 1,
|
||||
y: dot.y,
|
||||
width: v,
|
||||
angle: AB.y1 > 0 ? Math.sqrt(Math.pow(angle, 2)) : -Math.sqrt(Math.pow(angle, 2))
|
||||
})
|
||||
})
|
||||
return data
|
||||
},
|
||||
initData(dataset, r, indicator, angle) {
|
||||
let total = this.radar_w
|
||||
dataset = JSON.parse(JSON.stringify(dataset))
|
||||
dataset.map((item, index) => {
|
||||
let lines = []
|
||||
item.source.map((val, idx) => {
|
||||
let dsR = val / indicator[idx].max * r
|
||||
lines.push({
|
||||
y: dsR * Math.cos(angle * idx * Math.PI / 180),
|
||||
x: dsR * Math.sin(angle * idx * Math.PI / 180)
|
||||
})
|
||||
})
|
||||
item.lines = this.getDrawData(lines, index, 'd')
|
||||
})
|
||||
this.dataset = dataset
|
||||
},
|
||||
draw(dataset) {
|
||||
if (!dataset || !dataset[0] || !this.indicator) return
|
||||
let len = this.indicator.length
|
||||
let angle = 360 / len
|
||||
let r = this.radar_w / 2
|
||||
let indicator = JSON.parse(JSON.stringify(this.indicator))
|
||||
indicator.map((item, i) => {
|
||||
item.angle = angle * i
|
||||
})
|
||||
this.indicators = indicator
|
||||
|
||||
let dots = []
|
||||
let sn = Number(this.splitNumber)
|
||||
for (let i = 0; i < sn; i++) {
|
||||
let dotArr = []
|
||||
let dsDot = []
|
||||
let radius = r - i * (r / sn)
|
||||
for (let j = 0; j < len; j++) {
|
||||
dotArr.push({
|
||||
y: radius * Math.cos(angle * j * Math.PI / 180),
|
||||
x: radius * Math.sin(angle * j * Math.PI / 180)
|
||||
})
|
||||
}
|
||||
dots.push(dotArr)
|
||||
}
|
||||
|
||||
let lineArr = [];
|
||||
dots.map((dotArr, idx) => {
|
||||
lineArr = lineArr.concat(this.getDrawData(dotArr, idx))
|
||||
})
|
||||
|
||||
this.hypotenuse = lineArr
|
||||
|
||||
this.initData(dataset, r, indicator, angle)
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
tooltipHandle(index) {
|
||||
let data = this.dataset[index]
|
||||
let tooltips = []
|
||||
let indicator = JSON.parse(JSON.stringify(this.indicator))
|
||||
indicator.map((item, idx) => {
|
||||
item.value = data.source[idx]
|
||||
})
|
||||
this.name = data.name || ''
|
||||
this.color = data.color || '#333'
|
||||
this.tooltips = indicator;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
},
|
||||
onDotTap(index) {
|
||||
this.tooltipHandle(index);
|
||||
this.$emit('click', {
|
||||
datasetIndex: index
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__radar-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-charts-radar {
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.tui-radar__mrgin {
|
||||
margin-top: 32rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.tui-radar__radius {
|
||||
/* width: 1px; */
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
transform-origin: 50% 100%;
|
||||
}
|
||||
|
||||
.tui-radar__name {
|
||||
min-width: 120rpx;
|
||||
font-size: 24rpx;
|
||||
position: absolute;
|
||||
top: -20rpx;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -100%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tui-radar__center {
|
||||
position: absolute;
|
||||
/* width: 2px;
|
||||
height: 2px; */
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
.tui-radar__hypotenuse,
|
||||
.tui-radar__dataset {
|
||||
/* height: 1px; */
|
||||
position: absolute;
|
||||
transform-origin: 0 50%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-radar__dataset {
|
||||
z-index: 3;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-radar__dot {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-radar__legend {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.tui-radar__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-legend__circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-radar__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-tooltip__title {
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
|
||||
.tui-radar__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
.tui-radar__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.tui-tooltip__val-ml {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
</style>
|
||||
392
src/components/thorui/tui-charts-scatter/tui-charts-scatter.vue
Normal file
392
src/components/thorui/tui-charts-scatter/tui-charts-scatter.vue
Normal file
@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<view class="tui-charts__scatter-wrap" :style="{width:width+'rpx'}">
|
||||
<view class="tui-scatter__legend" v-if="legend.show">
|
||||
<view class="tui-scatter__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||
<view class="tui-legend__circle" :style="{background:item.color}"></view>
|
||||
<text
|
||||
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-charts__scatter-box" :style="{width:width+'rpx',height:height+'rpx'}">
|
||||
<view class="tui-xAxis__line" v-for="(item,index) in xAxisData" :key="item.id"
|
||||
:style="{left:index*(xAxisLine.itemGap || 100) +'rpx',borderLeftStyle:index===0?'solid':splitLine.type,borderLeftColor:index===0?yAxisLine.color:splitLine.color}">
|
||||
<view class="tui-xAxis__tickmarks"
|
||||
:style="{height:xAxisTick.height || '12rpx',background:xAxisTick.color || '#e3e3e3'}"></view>
|
||||
<view class="tui-xAxis__val" :style="{color:xAxisLabel.color,fontSize:xAxisLabel.size+'rpx'}">
|
||||
{{item.value}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-yAxis__line" v-for="(item,index) in yAxisData" :key="item.id"
|
||||
:style="{bottom:index* (yAxisLine.itemGap || 80) +'rpx',borderBottomStyle:index===0?'solid':splitLine.type,borderBottomColor:index===0?xAxisLine.color:splitLine.color}">
|
||||
<view class="tui-yAxis__tickmarks"
|
||||
:style="{width:yAxisTick.width || '12rpx',background:yAxisTick.color || '#e3e3e3'}"></view>
|
||||
<view class="tui-yAxis__val" :style="{color:yAxisLabel.color,fontSize:yAxisLabel.size+'rpx'}">
|
||||
{{item.value}}
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="(item,index) in dataset" :key="item.id">
|
||||
<view @tap.stop="onDotTap(index,idx)" class="tui-scatter__item"
|
||||
:class="{'tui-scatter__item-active':activeIdx===idx && activeIndex===index && tooltipShow}"
|
||||
v-for="(model,idx) in item.source" :key="idx"
|
||||
:style="{width:(item.width || 12)+'rpx',height:(item.width || 12)+'rpx',background:item.color || '#5677fc',left:model.x+'rpx',bottom:model.y+'rpx'}">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-scatter__tooltip" v-if="tooltip" :class="{'tui-scatter__tooltip-show':tooltipShow}">
|
||||
<view class="tui-tooltip__title"></view>
|
||||
<view class="tui-scatter__tooltip-item">
|
||||
<view class="tui-legend__circle" :style="{background:tooltips.color}"></view>
|
||||
<text class="tui-tooltip__val">{{tooltips.val}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-charts-scatter",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//图例,说明
|
||||
legend: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
show: false,
|
||||
size: 24,
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
xAxis: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
splitNumber: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴刻度线
|
||||
xAxisTick: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
height: '12rpx',
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3'
|
||||
}
|
||||
}
|
||||
},
|
||||
//x轴线条
|
||||
xAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: '#e3e3e3',
|
||||
itemGap: 100
|
||||
}
|
||||
}
|
||||
},
|
||||
xAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
splitNumber: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3',
|
||||
//y轴item间距 rpx
|
||||
itemGap: 80
|
||||
}
|
||||
}
|
||||
},
|
||||
//y轴刻度线
|
||||
yAxisTick: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
width: '12rpx',
|
||||
//不显示则将颜色设置为transparent
|
||||
color: '#e3e3e3'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxisLabel: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: "#333",
|
||||
size: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
//分割线
|
||||
splitLine: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
//分割线颜色,不显示则将颜色设置为transparent
|
||||
color: "#e3e3e3",
|
||||
type: "dashed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
data() {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
xAxisData: [],
|
||||
yAxisData: [],
|
||||
activeIndex: -1,
|
||||
activeIdx: -1,
|
||||
dataset: [],
|
||||
tooltips: {},
|
||||
tooltipShow: false,
|
||||
timer: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
generateArray(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
init(dataset, xAxisValFormatter) {
|
||||
let xTotal = this.xAxis.max - this.xAxis.min
|
||||
let yTotal = this.yAxis.max - this.yAxis.min
|
||||
let xSections = Math.ceil(xTotal / this.xAxis.splitNumber)
|
||||
let ySections = Math.ceil(yTotal / this.yAxis.splitNumber)
|
||||
let xSectionsArr = this.generateArray(0, xSections)
|
||||
let ySectionsArr = this.generateArray(0, ySections)
|
||||
this.xAxisData = xSectionsArr.map((item, index) => {
|
||||
let val = item * this.xAxis.splitNumber + this.xAxis.min
|
||||
val = xAxisValFormatter ? xAxisValFormatter(val) : val
|
||||
return {
|
||||
id: 'x_' + index,
|
||||
value: val
|
||||
}
|
||||
})
|
||||
this.yAxisData = ySectionsArr.map((item, idx) => {
|
||||
return {
|
||||
id: 'y_' + idx,
|
||||
value: item * this.yAxis.splitNumber + this.yAxis.min
|
||||
}
|
||||
})
|
||||
this.width = (this.xAxisLine.itemGap || 100) * xSections
|
||||
this.height = (this.yAxisLine.itemGap || 80) * ySections;
|
||||
dataset.map((item, i) => {
|
||||
item.id = 'd_' + i;
|
||||
item.source = item.source.map(model => {
|
||||
return {
|
||||
x: (Number(model[0]) - this.xAxis.min) / xTotal * this.width,
|
||||
y: (Number(model[1]) - this.yAxis.min) / yTotal * this.height,
|
||||
name: model[2],
|
||||
x1: model[0],
|
||||
y1: model[1]
|
||||
}
|
||||
})
|
||||
})
|
||||
this.dataset = dataset
|
||||
},
|
||||
draw(dataset, xAxisValFormatter) {
|
||||
dataset = dataset || [];
|
||||
this.init(dataset, xAxisValFormatter);
|
||||
},
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
},
|
||||
tooltipHandle(index, idx) {
|
||||
let data = this.dataset[index]
|
||||
let item = data.source[idx]
|
||||
let tooltips = {
|
||||
color: data.color,
|
||||
val: item.name || `${item.x1},${item.y1}`
|
||||
}
|
||||
this.tooltips = tooltips;
|
||||
this.clearTimer()
|
||||
this.tooltipShow = true;
|
||||
this.timer = setTimeout(() => {
|
||||
this.tooltipShow = false
|
||||
}, 5000)
|
||||
},
|
||||
onDotTap(index, idx) {
|
||||
this.activeIndex = index;
|
||||
this.activeIdx = idx;
|
||||
this.tooltipHandle(index, idx);
|
||||
this.$emit('click', {
|
||||
datasetIndex: index,
|
||||
sourceIndex: idx
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-charts__scatter-wrap {
|
||||
position: relative;
|
||||
font-weight: normal;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.tui-charts__scatter-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-xAxis__line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
border-left-width: 1px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tui-xAxis__tickmarks {
|
||||
width: 1px;
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
bottom: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.tui-yAxis__line {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
border-bottom-width: 1px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tui-yAxis__tickmarks {
|
||||
width: 12rpx;
|
||||
height: 1px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-xAxis__val {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -16rpx;
|
||||
transform: translate(-50%, 100%);
|
||||
}
|
||||
|
||||
.tui-yAxis__val {
|
||||
position: absolute;
|
||||
left: -16rpx;
|
||||
top: 0;
|
||||
transform: translate(-100%, -50%);
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.tui-scatter__item {
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
z-index: 12;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
transform: translate(-50%, 50%) scale(1);
|
||||
transition: transform .3s;
|
||||
}
|
||||
|
||||
.tui-scatter__item-active {
|
||||
transform: translate(-50%, 50%) scale(1.2);
|
||||
}
|
||||
|
||||
.tui-scatter__legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-scatter__legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-legend__circle {
|
||||
height: 20rpx;
|
||||
width: 20rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-scatter__tooltip {
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
z-index: 20;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tui-scatter__tooltip-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
.tui-scatter__tooltip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tui-tooltip__val {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: #fff;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
</style>
|
||||
105
src/components/thorui/tui-checkbox-group/tui-checkbox-group.vue
Normal file
105
src/components/thorui/tui-checkbox-group/tui-checkbox-group.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<!-- #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-TOUTIAO || MP-LARK || MP-JD || MP-360 -->
|
||||
<checkbox-group :name="name">
|
||||
<slot></slot>
|
||||
</checkbox-group>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ -->
|
||||
<tui-form-field :name="name" v-model="vals">
|
||||
<slot></slot>
|
||||
</tui-form-field>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-checkbox-group",
|
||||
emits: ['change', 'input', 'update:modelValue'],
|
||||
// #ifdef MP-WEIXIN
|
||||
behaviors: ['wx://form-field-group'],
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
behaviors: ['swan://form-field'],
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
behaviors: ['qq://form-field'],
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
behaviors: ['uni://form-field'],
|
||||
// #endif
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
value: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
vals: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// #ifdef VUE3
|
||||
modelValue(vals) {
|
||||
this.modelChange(vals)
|
||||
},
|
||||
// #endif
|
||||
value(vals) {
|
||||
this.modelChange(vals)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
checkboxChange(e) {
|
||||
this.$emit('change', e)
|
||||
this.$emit('input', e.detail.value)
|
||||
// #ifdef VUE3
|
||||
this.$emit("update:modelValue", e.detail.value);
|
||||
// #endif
|
||||
},
|
||||
changeValue(checked, target) {
|
||||
let vals = []
|
||||
this.childrens.forEach(item => {
|
||||
if (item.val) {
|
||||
vals.push(item.value);
|
||||
}
|
||||
})
|
||||
this.vals = vals;
|
||||
let e = {
|
||||
detail: {
|
||||
value: vals
|
||||
}
|
||||
}
|
||||
this.checkboxChange(e)
|
||||
},
|
||||
modelChange(vals) {
|
||||
this.childrens.forEach(item => {
|
||||
if (vals.includes(item.value)) {
|
||||
item.val = true;
|
||||
} else {
|
||||
item.val = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
229
src/components/thorui/tui-checkbox/tui-checkbox.vue
Normal file
229
src/components/thorui/tui-checkbox/tui-checkbox.vue
Normal file
@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<view class="tui-checkbox__input" :class="{'tui-checkbox__disabled':disabled}"
|
||||
:style="{backgroundColor:getBackgroundStyle(val,isCheckMark),border:getBorderStyle(val,isCheckMark),zoom:nvue?1:scaleRatio,transform:`scale(${nvue?scaleRatio:1})`}"
|
||||
@tap.stop="checkboxChange">
|
||||
<view class="tui-check__mark" :style="{borderBottomColor:checkMarkColor,borderRightColor:checkMarkColor}"
|
||||
v-if="val"></view>
|
||||
<checkbox class="tui-checkbox__hidden" style="position: absolute;opacity: 0;" hidden :color="color"
|
||||
:disabled="disabled" :value="value" :checked="val">
|
||||
</checkbox>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-checkbox",
|
||||
emits: ['change'],
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//当设置checked 属性值时是否触发父级change方法
|
||||
triggerGroup: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//checkbox选中背景颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//checkbox未选中时边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#ccc'
|
||||
},
|
||||
//是否只展示对号,无边框背景
|
||||
isCheckMark: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//对号颜色
|
||||
checkMarkColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
scaleRatio: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.unInstall()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.unInstall()
|
||||
},
|
||||
// #endif
|
||||
created() {
|
||||
this.val = this.checked;
|
||||
this.group = this.getParent()
|
||||
if (this.group) {
|
||||
this.group.childrens.push(this);
|
||||
if (this.group.value && this.group.value.length > 0) {
|
||||
this.val = this.group.value.includes(this.value)
|
||||
}
|
||||
// #ifdef VUE3
|
||||
if (this.group.modelValue && this.group.modelValue.length > 0) {
|
||||
this.val = this.group.modelValue.includes(this.value)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
this.label = this.getParent('tui-label')
|
||||
if (this.label) {
|
||||
this.label.childrens.push(this);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
checked(newVal) {
|
||||
this.val = newVal;
|
||||
},
|
||||
val(newVal) {
|
||||
if (this.triggerGroup && this.group) {
|
||||
this.group.changeValue(this.val, this);
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let nvue = false;
|
||||
// #ifdef APP-NVUE
|
||||
nvue = true;
|
||||
// #endif
|
||||
return {
|
||||
val: false,
|
||||
nvue: nvue
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getBackgroundStyle(val, isCheckMark) {
|
||||
const primary = (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
let color = val ? (this.color || primary) : '#fff'
|
||||
if (isCheckMark) {
|
||||
color = 'transparent'
|
||||
}
|
||||
return color;
|
||||
},
|
||||
getBorderStyle(val, isCheckMark) {
|
||||
const primary = (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
let color = val ? (this.color || primary) : this.borderColor;
|
||||
if (isCheckMark) {
|
||||
color = 'transparent'
|
||||
}
|
||||
return `1px solid ${color}`;
|
||||
},
|
||||
checkboxChange(e) {
|
||||
if (this.disabled) return;
|
||||
this.val = !this.val;
|
||||
if (!this.triggerGroup && this.group) {
|
||||
this.group.changeValue(this.val, this);
|
||||
}
|
||||
this.$emit('change', {
|
||||
checked: this.val,
|
||||
value: this.value
|
||||
})
|
||||
},
|
||||
getParent(name = 'tui-checkbox-group') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false;
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
labelClick() {
|
||||
this.checkboxChange()
|
||||
},
|
||||
unInstall() {
|
||||
if (this.group) {
|
||||
this.group.childrens.forEach((item, index) => {
|
||||
if (item === this) {
|
||||
this.group.childrens.splice(index, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-checkbox__input {
|
||||
font-size: 0;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-radius: 40rpx;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
vertical-align: top;
|
||||
flex-shrink: 0;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-check__mark {
|
||||
width: 20rpx;
|
||||
height: 40rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 3px;
|
||||
border-bottom-color: #FFFFFF;
|
||||
border-right-style: solid;
|
||||
border-right-width: 3px;
|
||||
border-right-color: #FFFFFF;
|
||||
transform: rotate(45deg) scale(0.5);
|
||||
transform-origin: 54% 48%;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-checkbox__hidden {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0 none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
pointer-events: none;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
width: 100wx;
|
||||
height: 100wx;
|
||||
border-width: 0;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-checkbox__disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
|
||||
:style="{ width: diam + 'px', height: (height || diam) + 'px' }"
|
||||
v-if="defaultShow && defaultCanvasId"></canvas>
|
||||
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
|
||||
:style="{ width: diam + 'px', height: (height || diam) + 'px' }" v-if="progressCanvasId"></canvas>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
|
||||
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }" v-if="defaultShow"></canvas>
|
||||
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
|
||||
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }"></canvas>
|
||||
<!-- #endif -->
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiCircularProgress',
|
||||
emits: ['change', 'end'],
|
||||
props: {
|
||||
/*
|
||||
传值需使用rpx进行转换保证各终端兼容
|
||||
px = rpx / 750 * wx.getSystemInfoSync().windowWidth
|
||||
圆形进度条(画布)宽度,直径 [px]
|
||||
*/
|
||||
diam: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
//圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//进度条线条宽度[px]
|
||||
lineWidth: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
/*
|
||||
线条的端点样式
|
||||
butt:向线条的每个末端添加平直的边缘
|
||||
round 向线条的每个末端添加圆形线帽
|
||||
square 向线条的每个末端添加正方形线帽
|
||||
*/
|
||||
lineCap: {
|
||||
type: String,
|
||||
default: 'round'
|
||||
},
|
||||
//圆环进度字体大小 [px]
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
//圆环进度字体颜色
|
||||
fontColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//是否显示进度文字
|
||||
fontShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/*
|
||||
自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
|
||||
可以使用 slot自定义显示内容
|
||||
*/
|
||||
percentText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//是否显示默认(背景)进度条
|
||||
defaultShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//默认进度条颜色
|
||||
defaultColor: {
|
||||
type: String,
|
||||
default: '#CCCCCC'
|
||||
},
|
||||
//进度条颜色
|
||||
progressColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//进度条渐变颜色[结合progressColor使用,默认为空]
|
||||
gradualColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//起始弧度,单位弧度
|
||||
sAngle: {
|
||||
type: Number,
|
||||
default: -Math.PI / 2
|
||||
},
|
||||
//指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
|
||||
counterclockwise: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//进度百分比 [10% 传值 10]
|
||||
percentage: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//进度百分比缩放倍数[使用半弧为100%时,则可传2]
|
||||
multiple: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//动画执行时间[单位毫秒,低于50无动画]
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 800
|
||||
},
|
||||
//backwards: 动画从头播;forwards:动画从上次结束点接着播
|
||||
activeMode: {
|
||||
type: String,
|
||||
default: 'backwards'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
percentage(val) {
|
||||
this.initDraw();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
// #ifndef MP-WEIXIN || MP-QQ
|
||||
let cid = `id01_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
let did = `id02_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
// #endif
|
||||
return {
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
progressCanvasId: 'progressCanvasId',
|
||||
defaultCanvasId: 'defaultCanvasId',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN || MP-QQ
|
||||
progressCanvasId: cid,
|
||||
defaultCanvasId: did,
|
||||
// #endif
|
||||
progressContext: null,
|
||||
linearGradient: null,
|
||||
//起始百分比
|
||||
startPercentage: 0
|
||||
// dpi
|
||||
//pixelRatio: uni.getSystemInfoSync().pixelRatio
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.initDraw(true);
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
//初始化绘制
|
||||
initDraw(init) {
|
||||
let start = this.activeMode === 'backwards' ? 0 : this.startPercentage;
|
||||
start = start > this.percentage ? 0 : start;
|
||||
if (this.defaultShow && init) {
|
||||
this.drawDefaultCircular();
|
||||
}
|
||||
this.drawProgressCircular(start);
|
||||
},
|
||||
//默认(背景)圆环
|
||||
drawDefaultCircular() {
|
||||
let ctx = uni.createCanvasContext(this.defaultCanvasId, this);
|
||||
let lineWidth = Number(this.lineWidth)
|
||||
// #ifdef MP-ALIPAY
|
||||
lineWidth = lineWidth * 4
|
||||
// #endif
|
||||
ctx.setLineWidth(lineWidth);
|
||||
ctx.setStrokeStyle(this.defaultColor);
|
||||
//终止弧度
|
||||
let eAngle = Math.PI * (this.height ? 1 : 2) + this.sAngle;
|
||||
this.drawArc(ctx, eAngle);
|
||||
},
|
||||
//进度圆环
|
||||
drawProgressCircular(startPercentage) {
|
||||
let ctx = this.progressContext;
|
||||
let gradient = this.linearGradient;
|
||||
if (!ctx) {
|
||||
ctx = uni.createCanvasContext(this.progressCanvasId, this);
|
||||
//创建一个线性的渐变颜色 CanvasGradient对象
|
||||
let diam = Number(this.diam)
|
||||
// #ifdef MP-ALIPAY
|
||||
diam = diam * 4
|
||||
// #endif
|
||||
const progressColor = this.progressColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
gradient = ctx.createLinearGradient(0, 0, diam, 0);
|
||||
gradient.addColorStop('0', progressColor);
|
||||
if (this.gradualColor) {
|
||||
gradient.addColorStop('1', this.gradualColor);
|
||||
}
|
||||
// #ifdef APP-PLUS || MP
|
||||
const res = uni.getSystemInfoSync();
|
||||
if (!this.gradualColor && res.platform.toLocaleLowerCase() == 'android') {
|
||||
gradient.addColorStop('1', progressColor);
|
||||
}
|
||||
// #endif
|
||||
this.progressContext = ctx;
|
||||
this.linearGradient = gradient;
|
||||
}
|
||||
let lineWidth = Number(this.lineWidth)
|
||||
// #ifdef MP-ALIPAY
|
||||
lineWidth = lineWidth * 4
|
||||
// #endif
|
||||
ctx.setLineWidth(lineWidth);
|
||||
ctx.setStrokeStyle(gradient);
|
||||
let time = this.percentage == 0 || this.duration < 50 ? 0 : this.duration / this.percentage;
|
||||
if (this.percentage > 0) {
|
||||
startPercentage = this.duration < 50 ? this.percentage - 1 : startPercentage;
|
||||
startPercentage++;
|
||||
}
|
||||
if (this.fontShow) {
|
||||
let fontSize = Number(this.fontSize)
|
||||
// #ifdef MP-ALIPAY
|
||||
fontSize = fontSize * 4
|
||||
// #endif
|
||||
ctx.setFontSize(fontSize);
|
||||
const fontColor = this.fontColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
ctx.setFillStyle(fontColor);
|
||||
ctx.setTextAlign('center');
|
||||
ctx.setTextBaseline('middle');
|
||||
let percentage = this.percentText;
|
||||
if (!percentage) {
|
||||
percentage = this.counterclockwise ? 100 - startPercentage * this.multiple : startPercentage * this
|
||||
.multiple;
|
||||
percentage = `${percentage}%`;
|
||||
}
|
||||
let radius = this.diam / 2;
|
||||
// #ifdef MP-ALIPAY
|
||||
radius = radius * 4
|
||||
// #endif
|
||||
ctx.fillText(percentage, radius, radius);
|
||||
}
|
||||
if (this.percentage == 0 || (this.counterclockwise && startPercentage == 100)) {
|
||||
ctx.draw();
|
||||
} else {
|
||||
let eAngle = ((2 * Math.PI) / 100) * startPercentage + this.sAngle;
|
||||
this.drawArc(ctx, eAngle);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.startPercentage = startPercentage;
|
||||
if (startPercentage >= this.percentage) {
|
||||
this.$emit('end', {
|
||||
canvasId: this.progressCanvasId,
|
||||
percentage: startPercentage
|
||||
});
|
||||
} else {
|
||||
this.drawProgressCircular(startPercentage);
|
||||
}
|
||||
this.$emit('change', {
|
||||
percentage: startPercentage
|
||||
});
|
||||
}, time);
|
||||
// #ifdef H5
|
||||
// requestAnimationFrame(()=>{})
|
||||
// #endif
|
||||
},
|
||||
//创建弧线
|
||||
drawArc(ctx, eAngle) {
|
||||
ctx.setLineCap(this.lineCap);
|
||||
ctx.beginPath();
|
||||
let radius = this.diam / 2; //x=y
|
||||
let lineWidth = Number(this.lineWidth)
|
||||
// #ifdef MP-ALIPAY
|
||||
radius = radius * 4
|
||||
lineWidth = lineWidth * 4
|
||||
// #endif
|
||||
ctx.arc(radius, radius, radius - lineWidth, this.sAngle, eAngle, this.counterclockwise);
|
||||
ctx.stroke();
|
||||
ctx.draw();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-circular-container,
|
||||
.tui-circular-default {
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
/* #ifdef MP-ALIPAY */
|
||||
.tui-circular-default,
|
||||
.tui-circular-progress {
|
||||
zoom: 0.25;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
|
||||
.tui-circular-progress {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
389
src/components/thorui/tui-code-input/tui-code-input.vue
Normal file
389
src/components/thorui/tui-code-input/tui-code-input.vue
Normal file
@ -0,0 +1,389 @@
|
||||
<template>
|
||||
<view class="tui-code__input" :style="{marginTop:marginTop+'rpx',marginBottom:marginBottom+'rpx'}" @tap="onClick">
|
||||
<view class="tui-code__input" :style="{paddingLeft:gap+'rpx',paddingRight:gap+'rpx'}">
|
||||
<view class="tui-cinput__item"
|
||||
:style="{width:width+'rpx',height:height+'rpx',background:background,borderRadius:radius+'rpx',borderColor:activeIndex===index || inputVal[index]?getActiveColor:borderColor,borderTopWidth:(borderType==1?borderWidth:0)+'rpx',borderLeftWidth:(borderType==1?borderWidth:0)+'rpx',borderRightWidth:(borderType==1?borderWidth:0)+'rpx',borderBottomWidth:(borderType==1 || borderType==2?borderWidth:0)+'rpx'}"
|
||||
@tap="onTap" v-for="(item,index) in inputArr" :key="index">
|
||||
<text class="tui-cinput__text"
|
||||
:style="{width:width+'rpx',height:height+'rpx',fontSize:size+'rpx',lineHeight:height+'rpx',color:color,fontWeight:fontWeight}">{{password?(inputVal[index] ? '●':''):(inputVal[index] || '')}}</text>
|
||||
<text class="tui-cinput__placeholder"
|
||||
:style="{fontSize:size+'rpx',fontWeight:fontWeight}">{{password?(inputVal[index] ? '●':''):(inputVal[index] || '')}}</text>
|
||||
<view class="tui-cinput__cursor" :class="{'tui-cinput__cursor-ani':activeIndex===index && focus}"
|
||||
v-if="cursor && !disabled" :style="{height:cursorHeight+'rpx',background:getCursorColor}">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<input :value="val" :password="password" :type="type" class="tui-cinput__hidden"
|
||||
:class="{'tui-cinput__ali':ali}" @input="onInput" @blur="onBlur" :focus="focus" :maxlength="length"
|
||||
:disabled="disabled" @confirm="onConfirm" @focus="onTap" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiCodeInput",
|
||||
emits: ['complete', 'focus', 'input', 'blur', 'confirm'],
|
||||
props: {
|
||||
//组件外层左右间距
|
||||
gap: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
marginTop: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
marginBottom: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//初始值,不可超过length长度
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//H5不支持动态切换type类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
password: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//获取焦点
|
||||
isFocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
cursor: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
cursorColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
cursorHeight: {
|
||||
type: [Number, String],
|
||||
default: 60
|
||||
},
|
||||
//输入框个数
|
||||
length: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 108
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 108
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//1-显示所有边框 2-只显示底部边框,3-无边框
|
||||
borderType: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#eaeef1'
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
borderWidth: {
|
||||
type: [Number, String],
|
||||
default: 2
|
||||
},
|
||||
radius: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 48
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
fontWeight: {
|
||||
type: [Number, String],
|
||||
default: 600
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
getCursorColor(){
|
||||
return this.cursorColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
},
|
||||
getActiveColor(){
|
||||
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputArr: [],
|
||||
inputVal: [],
|
||||
focus: false,
|
||||
activeIndex: -1,
|
||||
ali: false,
|
||||
val: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
length(val) {
|
||||
const nums = Number(val);
|
||||
if (nums !== this.inputArr.length) {
|
||||
this.inputArr = this.getArr(nums)
|
||||
}
|
||||
},
|
||||
value(val) {
|
||||
this.focus = true;
|
||||
val = val.replace(/\s+/g, "")
|
||||
this.getVals(val)
|
||||
},
|
||||
isFocus(val) {
|
||||
this.initFocus(val)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.inputArr = this.getArr(Number(this.length))
|
||||
let val = this.value.replace(/\s+/g, "")
|
||||
this.getVals(val, true)
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(()=>{
|
||||
setTimeout(() => {
|
||||
this.initFocus(this.isFocus)
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
initFocus(val) {
|
||||
if (this.disabled) return;
|
||||
if (val && this.activeIndex === -1) {
|
||||
this.activeIndex = 0
|
||||
}
|
||||
if (!this.value && !val) {
|
||||
this.activeIndex = -1
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.focus = val
|
||||
})
|
||||
},
|
||||
getArr(end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(1);
|
||||
},
|
||||
getVals(val, init = false) {
|
||||
this.val = val
|
||||
if (!val) {
|
||||
this.inputVal = []
|
||||
this.activeIndex = init ? -1 : 0;
|
||||
} else {
|
||||
let vals = val.split('')
|
||||
let arr = []
|
||||
this.inputArr.forEach((item, index) => {
|
||||
arr.push(vals[index] || '')
|
||||
})
|
||||
this.inputVal = arr
|
||||
const len = vals.length;
|
||||
this.activeIndex = len > this.length ? this.length : len;
|
||||
if (len === this.length) {
|
||||
this.$emit('complete', {
|
||||
detail: {
|
||||
value: val
|
||||
}
|
||||
})
|
||||
this.focus = false;
|
||||
uni.hideKeyboard()
|
||||
}
|
||||
}
|
||||
},
|
||||
onTap() {
|
||||
if (this.disabled) return;
|
||||
this.focus = true;
|
||||
if (this.activeIndex === -1) {
|
||||
this.activeIndex = 0
|
||||
}
|
||||
if (this.activeIndex === this.length) {
|
||||
this.activeIndex--;
|
||||
}
|
||||
this.$emit('focus', {})
|
||||
},
|
||||
onInput(e) {
|
||||
let value = e.detail.value;
|
||||
value = value.replace(/\s+/g, "")
|
||||
this.getVals(value)
|
||||
this.$emit('input', {
|
||||
detail: {
|
||||
value: value
|
||||
}
|
||||
})
|
||||
},
|
||||
onBlur(e) {
|
||||
let value = e.detail.value;
|
||||
value = value.replace(/\s+/g, "")
|
||||
this.focus = false
|
||||
// #ifdef MP-ALIPAY
|
||||
this.ali = false
|
||||
// #endif
|
||||
if (!value) {
|
||||
this.activeIndex = -1;
|
||||
}
|
||||
this.$emit('blur', {
|
||||
detail: {
|
||||
value: value
|
||||
}
|
||||
})
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.focus = false;
|
||||
uni.hideKeyboard()
|
||||
this.$emit('confirm', e)
|
||||
},
|
||||
onClick() {
|
||||
// #ifdef MP-ALIPAY
|
||||
setTimeout(() => {
|
||||
this.ali = true
|
||||
}, 50)
|
||||
// #endif
|
||||
},
|
||||
clear() {
|
||||
this.val = ''
|
||||
this.inputVal = []
|
||||
this.activeIndex = -1;
|
||||
this.$nextTick(() => {
|
||||
this.onTap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-code__input {
|
||||
position: relative;
|
||||
/* #ifdef MP-BAIDU */
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-code__input {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
.tui-cinput__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-style: solid;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-cinput__text {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tui-cinput__placeholder {
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tui-cinput__cursor {
|
||||
border-radius: 2px;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.tui-cinput__cursor-ani {
|
||||
width: 2px;
|
||||
animation: ani_cursor 1s infinite steps(1, start);
|
||||
}
|
||||
|
||||
@keyframes ani_cursor {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-cinput__hidden {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
/* #ifndef MP */
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* #endif */
|
||||
/* #ifndef MP-WEIXIN || MP-QQ */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* #endif */
|
||||
z-index: 2;
|
||||
/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
|
||||
height: 0;
|
||||
width: 0;
|
||||
border: none;
|
||||
/* #endif */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
opacity: 0;
|
||||
/* #ifdef MP-BAIDU || MP-TOUTIAO */
|
||||
font-size: 0;
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-BAIDU */
|
||||
transform: scaleX(2);
|
||||
transform-origin: 100% center;
|
||||
/* #endif */
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
/* #ifdef MP-ALIPAY */
|
||||
.tui-cinput__ali {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
3215
src/components/thorui/tui-col/tui-col.vue
Normal file
3215
src/components/thorui/tui-col/tui-col.vue
Normal file
File diff suppressed because it is too large
Load Diff
167
src/components/thorui/tui-collapse/tui-collapse.vue
Normal file
167
src/components/thorui/tui-collapse/tui-collapse.vue
Normal file
@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<view class="tui-collapse" :style="{backgroundColor:bgColor}">
|
||||
<view class="tui-collapse-head" :style="{backgroundColor:hdBgColor}" @tap.stop="handleClick">
|
||||
<view class="tui-header" :class="{'tui-opacity':disabled}">
|
||||
<slot name="title"></slot>
|
||||
<view class="tui-collapse-icon tui-icon-arrow" :class="{'tui-icon-active':isOpen}" :style="{color:arrowColor}" v-if="arrow"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-collapse-body_box" :style="{backgroundColor:bdBgColor,height:isOpen?height:'0rpx'}">
|
||||
<view class="tui-collapse-body" :class="{'tui-collapse-transform':height=='auto','tui-collapse-body_show':isOpen && height=='auto'}">
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiCollapse",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//collapse背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//collapse-head 背景颜色
|
||||
hdBgColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//collapse-body 背景颜色
|
||||
bdBgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//collapse-body实际高度 open时使用
|
||||
height: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
//索引
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//当前索引,index==current时展开
|
||||
current: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
//是否带箭头
|
||||
arrow: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
//箭头颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: "#333"
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current() {
|
||||
this.updateCurrentChange()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateCurrentChange()
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateCurrentChange() {
|
||||
this.isOpen = this.index == this.current
|
||||
},
|
||||
handleClick() {
|
||||
if (this.disabled) return;
|
||||
this.$emit("click", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: 'tuiCollapse';
|
||||
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAQ4AA0AAAAABlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEHAAAABoAAAAciRx3B0dERUYAAAP8AAAAHgAAAB4AKQAKT1MvMgAAAaAAAABCAAAAVjxuR/JjbWFwAAAB9AAAAD4AAAFCAA/pq2dhc3AAAAP0AAAACAAAAAj//wADZ2x5ZgAAAkAAAABEAAAARCs1U/toZWFkAAABMAAAADAAAAA2FpaT+mhoZWEAAAFgAAAAHQAAACQHngOFaG10eAAAAeQAAAAPAAAAEAwAAEBsb2NhAAACNAAAAAoAAAAKACIAAG1heHAAAAGAAAAAHwAAACABDwAdbmFtZQAAAoQAAAFJAAACiCnmEVVwb3N0AAAD0AAAACMAAAA1DunpUnjaY2BkYGAAYja/oO54fpuvDNwsDCBwc4/6fzjtwNDNfICpBMjlYGACiQIAGVAKZnjaY2BkYGBu+N/AEMPCAALMBxgYGVABCwBVNgMsAAAAeNpjYGRgYGBhEGQA0QwMTEDMBYQMDP/BfAYACnYBLQB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ4xMDf8b2CIYW5gaAAKM4LkANq9C9sAAHjaY2GAABYIdgAAAMAATQB42mNgYGBmgGAZBkYGELAB8hjBfBYGBSDNAoRA/jOG//8hpBQzVCUDIxsDjMnAyAQkmBhQASPDsAcAMCAGoQAAAAAAAAAAAAAAIgAAAAEAQACLA8ACdAAQAAAlASYiBhQXARYyNwE2NCYiBwIA/oYNIBkMAZcNIA0BlwwZIA3uAXoMGSAN/mkMDAGXDSAZDAB42n2QPU4DMRCFn/MHJBJCIKhdUQDa/JQpEyn0CKWjSDbekGjXXnmdSDkBLRUHoOUYHIAbINFyCl6WSZMia+3o85uZ57EBnOMbCv/fJe6EFY7xKFzBETLhKvUX4Rr5XbiOFj6FG9R/hJu4VQPhFi7UGx1U7YS7m9JtywpnGAhXcIon4Sr1lXCN/CpcxxU+hBvUv4SbGONXuIVrZakM4WEwQWCcQWOKDeMCMRwskjIG1qE59GYSzExPN3oRO5s4GyjvV2KXAx5oOeeAKe09t2a+Sif+YMuB1JhuHgVLtimNLiJ0KBtfLJzV3ahzsP2e7ba02L9rgTXH7FENbNT8Pdsz0khsDK+QkjXyMrekElOPaGus8btnKdbzXgiJTrzL9IjHmjR1OvduaeLA4ufyjBx9tLmSPfeoHD5jWQh5v91OxCCKXYY/k9hxGQAAAHjaY2BigAAuMMnIgA5YwKJMjExciUVF+eW6KfnleQAZ0wQyAAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAMAAQAEAAAAAgAAAAB42mNgYGBkAIKrS9Q5QPTNPer/YTQAQ+0HIAAA) format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tui-collapse-icon {
|
||||
font-family: "tuiCollapse" !important;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.tui-icon-arrow:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.tui-icon-arrow {
|
||||
font-size: 32rpx;
|
||||
transform: rotate(0);
|
||||
transform-origin: center center;
|
||||
transition: all 0.3s;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
right: 30rpx;
|
||||
}
|
||||
|
||||
.tui-arrow-padding {
|
||||
padding-right: 62rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-icon-active {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.tui-header {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.tui-collapse-body_box{
|
||||
transition: all 0.25s;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tui-collapse-body {
|
||||
transition: all 0.25s;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tui-collapse-transform {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
-webkit-transform: translateY(-40%);
|
||||
transform: translateY(-40%);
|
||||
}
|
||||
|
||||
.tui-collapse-body_show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
-webkit-transform: translateY(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.tui-opacity {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
129
src/components/thorui/tui-config/index.js
Normal file
129
src/components/thorui/tui-config/index.js
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
基础组件props属性全局配置文件。优先级:全局配置文件props < 单独设置组件props
|
||||
温馨提示:未设置则使用组件内默认值,避免出错,请勿删减以下配置
|
||||
组件属性介绍请查看文档
|
||||
*/
|
||||
|
||||
//组件内主色配置
|
||||
const color = {
|
||||
primary: '#5677fc',
|
||||
success: '#07c160',
|
||||
warning: '#ff7900',
|
||||
danger: '#EB0909',
|
||||
pink: '#f74d54',
|
||||
blue: '#007AFF',
|
||||
link: '#586c94'
|
||||
}
|
||||
|
||||
const propsConfig = {
|
||||
//组件内主色配置
|
||||
color,
|
||||
//组件名称,字体图标组件 tui-icon
|
||||
tuiIcon: {
|
||||
//组件属性值
|
||||
size: 32,
|
||||
unit: 'px',
|
||||
color: '#999'
|
||||
},
|
||||
//按钮组件 tui-button
|
||||
tuiButton: {
|
||||
height: '96rpx',
|
||||
size: 32
|
||||
},
|
||||
//列表项组件 tui-list-cell
|
||||
tuiListCell: {
|
||||
arrowColor: '#c0c0c0',
|
||||
lineColor: '#eaeef1',
|
||||
lineLeft: 30,
|
||||
padding: '26rpx 44rpx',
|
||||
color: '#333',
|
||||
size: 28
|
||||
},
|
||||
//按钮组件 tui-form-button
|
||||
tuiFormButton: {
|
||||
background: color.primary,
|
||||
color: '#fff',
|
||||
height: '96rpx',
|
||||
size: 32,
|
||||
radius: '6rpx'
|
||||
},
|
||||
//文本组件 tui-text
|
||||
tuiText: {
|
||||
size: 32,
|
||||
unit: 'rpx',
|
||||
color: ''
|
||||
},
|
||||
//输入框组件 tui-input
|
||||
tuiInput: {
|
||||
requiredColor: color.danger,
|
||||
labelSize: 32,
|
||||
labelColor: '#333',
|
||||
size: 32,
|
||||
color: '#333',
|
||||
padding: '26rpx 30rpx',
|
||||
backgroundColor: '#FFFFFF',
|
||||
radius: 0
|
||||
},
|
||||
//表单项组件 tui-form-item
|
||||
tuiFormItem: {
|
||||
padding: '28rpx 30rpx',
|
||||
labelSize: 32,
|
||||
labelColor: '#333',
|
||||
labelFontWeight: 400,
|
||||
asteriskColor: color.danger,
|
||||
background: '#fff',
|
||||
arrowColor: '#c0c0c0',
|
||||
borderColor: '#eaeef1',
|
||||
radius: '0rpx',
|
||||
position: 2
|
||||
},
|
||||
//表单校验组件 tui-form
|
||||
tuiForm: {
|
||||
tipBackgroundColor: color.pink,
|
||||
duration: 2000
|
||||
},
|
||||
//全局方法,调用 uni.$tui.toast
|
||||
toast(text, duration, success) {
|
||||
uni.showToast({
|
||||
// #ifndef MP-ALIPAY
|
||||
duration: duration || 2000,
|
||||
// #endif
|
||||
title: text || "出错啦~",
|
||||
icon: success ? 'success' : 'none'
|
||||
})
|
||||
},
|
||||
modal(title, content, showCancel, callback, confirmColor, confirmText) {
|
||||
uni.showModal({
|
||||
title: title || '提示',
|
||||
content: content,
|
||||
showCancel: showCancel,
|
||||
cancelColor: "#555",
|
||||
confirmColor: confirmColor || color.primary,
|
||||
confirmText: confirmText || "确定",
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
callback && callback(true)
|
||||
} else {
|
||||
callback && callback(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
//跳转页面
|
||||
href(url, isMain) {
|
||||
if (isMain) {
|
||||
uni.switchTab({
|
||||
url: url
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
});
|
||||
}
|
||||
},
|
||||
rpx2px(value) {
|
||||
return uni.upx2px(value)
|
||||
}
|
||||
}
|
||||
|
||||
export default propsConfig
|
||||
279
src/components/thorui/tui-copy-text/tui-copy-text.vue
Normal file
279
src/components/thorui/tui-copy-text/tui-copy-text.vue
Normal file
@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<view class="tui-copy__box">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<text :selectable="systemCopy" class="tui-ccpy__text" @longpress.stop="handleCopy" @mousedown="handleCopyByPC"
|
||||
@touchstart="handlePC"
|
||||
:style="{ color: color, fontSize: size + 'rpx', fontWeight: bold ? 'bold' : 'normal', backgroundColor: showToolTip ? backgroundColor : 'transparent' }">
|
||||
{{ value }}
|
||||
</text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<view :selectable="systemCopy" class="tui-ccpy__text" @longpress.stop="handleCopy" @touchstart="handlePC"
|
||||
:style="{ color: color, fontSize: size + 'rpx', fontWeight: bold ? 'bold' : 'normal', backgroundColor: showToolTip ? backgroundColor : 'transparent' }">
|
||||
{{ value }}
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view v-if="showToolTip && !systemCopy" class="tui-tooltip__list"
|
||||
:class="['tui-tooltip__' + direction, (direction == 'left' || direction == 'right') && buttons.length > 0 ? 'tui-tooltip__column' : '']"
|
||||
:style="{ zIndex: zIndex }">
|
||||
<view v-if="!removeCopy || buttons.length === 0" class="tui-tooltip__cell"
|
||||
:class="[(direction == 'left' || direction == 'right') && buttons.length > 0 ? 'tui-tooltip__cell-column' : '']"
|
||||
@tap.stop="copy">
|
||||
复制
|
||||
</view>
|
||||
<view class="tui-tooltip__cell tui-tooltip__extend"
|
||||
:class="[direction == 'left' || direction == 'right' ? 'tui-tooltip__cell-column' : '']"
|
||||
v-for="(item, index) in buttons" :key="index" :data-index="index" @tap.stop="buttonTap">
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import thorui from '@/components/common/tui-clipboard/tui-clipboard.js'
|
||||
export default {
|
||||
name: 'tuiCopyText',
|
||||
emits: ['click', 'copy'],
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: 'copy text'
|
||||
},
|
||||
//需要复制的文本,默认空,则默认复制的值为value
|
||||
copyValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//字体大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
//是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//选中后背景色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//是否显示复制tooltip,为false时,长按直接复制(无扩展buttons时生效)
|
||||
showCopyBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//复制按钮显示方向:top,left,right,bottom
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
//复制按钮z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 997
|
||||
},
|
||||
//使用系统长按操作,扩展按钮失效
|
||||
systemCopy: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否移除复制按钮(当只需要扩展按钮时使用且有扩展按钮时生效)
|
||||
removeCopy: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//扩展按钮(1~3个左右)
|
||||
buttons: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showToolTip: false,
|
||||
isCopy: true
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handlePC(e) {
|
||||
this.isCopy = false;
|
||||
},
|
||||
handleCopyByPC(e) {
|
||||
if (this.isCopy) {
|
||||
this.handleCopy(e);
|
||||
}
|
||||
},
|
||||
handleCopy(e) {
|
||||
if (this.systemCopy) return;
|
||||
if (this.showCopyBtn || this.buttons.length > 0) {
|
||||
this.showToolTip = true;
|
||||
} else {
|
||||
this.copy(e);
|
||||
}
|
||||
},
|
||||
copy(e) {
|
||||
thorui.getClipboardData(
|
||||
this.copyValue || this.value,
|
||||
res => {
|
||||
if (res) {
|
||||
uni.showToast({
|
||||
title: '复制成功',
|
||||
icon: 'none'
|
||||
});
|
||||
this.$emit('copy', {
|
||||
value: this.value,
|
||||
copyValue: this.copyValue
|
||||
});
|
||||
}
|
||||
},
|
||||
e
|
||||
);
|
||||
this.cancel();
|
||||
},
|
||||
cancel() {
|
||||
this.showToolTip = false;
|
||||
},
|
||||
buttonTap(e) {
|
||||
let index = Number(e.currentTarget.dataset.index);
|
||||
this.$emit('click', {
|
||||
index: index,
|
||||
value: this.value,
|
||||
copyValue: this.copyValue
|
||||
});
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-copy__box {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
font-weight: initial;
|
||||
}
|
||||
|
||||
.tui-ccpy__text {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-tooltip__list {
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
background-color: #060607;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-tooltip__column {
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tui-tooltip__cell {
|
||||
min-width: 60px;
|
||||
box-sizing: border-box;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.tui-tooltip__extend {
|
||||
border-left: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.tui-tooltip__cell-column {
|
||||
padding: 5px !important;
|
||||
border-left: 0 !important;
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.tui-tooltip__cell:last-child {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tui-tooltip__top {
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translate(-50%, -100%);
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.tui-tooltip__top::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #060607 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tui-tooltip__bottom {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
transform: translate(-50%, 0);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tui-tooltip__bottom::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -100%);
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #060607 transparent;
|
||||
}
|
||||
|
||||
.tui-tooltip__left {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate(-100%, -50%);
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.tui-tooltip__left::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
transform: translate(0, -50%);
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent transparent #060607;
|
||||
}
|
||||
|
||||
.tui-tooltip__right {
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.tui-tooltip__right::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translate(-100%, -50%);
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: transparent #060607 transparent transparent;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<view class="tui-countdown__verify" :class="{ 'tui-verify__opacity': status > 1 && isOpacity }"
|
||||
:style="{ width: width, height: height, padding: padding, margin: margin, borderRadius: radius, fontSize: size + 'rpx', color: getColor, background: background }"
|
||||
:hover-class="hover && status == 1 ? 'tui-verify__opacity' : ''" :hover-stay-time="150" @tap.stop="sendCode">
|
||||
{{ showText }}
|
||||
<view class="tui-verify__line"
|
||||
:style="{ borderWidth: borderWidth, borderColor: getBorderColor, borderRadius: radius }"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiCountdownVerify',
|
||||
emits: ['send', 'countdown', 'end'],
|
||||
props: {
|
||||
//发送前显示文本
|
||||
text: {
|
||||
type: String,
|
||||
default: '发送验证码'
|
||||
},
|
||||
//发送中显示文本
|
||||
sendText: {
|
||||
type: String,
|
||||
default: '请稍候...'
|
||||
},
|
||||
//发送后显示文本(前面会自动加上seconds)
|
||||
countdownText: {
|
||||
type: String,
|
||||
default: 's后获取'
|
||||
},
|
||||
//倒计时秒数
|
||||
seconds: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
//宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '182rpx'
|
||||
},
|
||||
//高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '56rpx'
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//圆角
|
||||
radius: {
|
||||
type: String,
|
||||
default: '6rpx'
|
||||
},
|
||||
//字体大小 rpx
|
||||
size: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
//字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//背景色
|
||||
background: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//边框宽度
|
||||
borderWidth: {
|
||||
type: String,
|
||||
default: '1px'
|
||||
},
|
||||
//边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//倒计时的时候是否改变opacity值
|
||||
isOpacity: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否需要点击效果
|
||||
hover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//短信发送成功(改变数值且数值大于0表示发送成功,多次发送数值递增即可)
|
||||
successVal: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//重置组件状态(改变数值且数值大于0,多次重置数值递增即可)
|
||||
resetVal: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//是否默认为倒计时状态
|
||||
start: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//自定义参数
|
||||
params: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getColor() {
|
||||
return this.color || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
},
|
||||
getBorderColor() {
|
||||
return this.borderColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showText: '',
|
||||
//1-发送前,2-发送中 3-发送成功,倒计时
|
||||
status: 1,
|
||||
countdownTimer: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.start) {
|
||||
this.doLoop();
|
||||
} else {
|
||||
this.showText = this.text;
|
||||
this.clearTimer();
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer();
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer();
|
||||
},
|
||||
// #endif
|
||||
watch: {
|
||||
successVal(val) {
|
||||
if (val && val > 0) {
|
||||
this.doLoop();
|
||||
}
|
||||
},
|
||||
resetVal(val) {
|
||||
if (val && val > 0) {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sendCode() {
|
||||
if (this.status > 1) return;
|
||||
this.clearTimer();
|
||||
this.status = 2;
|
||||
this.showText = this.sendText;
|
||||
this.$emit('send', {
|
||||
params: this.params
|
||||
});
|
||||
},
|
||||
doLoop: function() {
|
||||
this.clearTimer();
|
||||
this.status = 3;
|
||||
let seconds = this.seconds || 60;
|
||||
this.showText = seconds + this.countdownText;
|
||||
this.countdownTimer = setInterval(() => {
|
||||
if (seconds > 1) {
|
||||
--seconds;
|
||||
this.showText = seconds + this.countdownText;
|
||||
//倒计时
|
||||
this.$emit('countdown', {
|
||||
seconds: seconds,
|
||||
params: this.params
|
||||
});
|
||||
} else {
|
||||
this.reset();
|
||||
//倒计时结束
|
||||
this.$emit('end', {
|
||||
params: this.params
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
//验证码发送成功
|
||||
success() {
|
||||
this.doLoop();
|
||||
},
|
||||
//重置发送组件
|
||||
reset() {
|
||||
this.clearTimer();
|
||||
this.showText = this.text;
|
||||
this.status = 1;
|
||||
},
|
||||
clearTimer() {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-countdown__verify {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-verify__opacity {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tui-verify__line {
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform-origin: 0 0;
|
||||
transform: scale(0.5, 0.5) translateZ(0);
|
||||
box-sizing: border-box;
|
||||
border-style: solid;
|
||||
left: 0;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
343
src/components/thorui/tui-countdown/tui-countdown.vue
Normal file
343
src/components/thorui/tui-countdown/tui-countdown.vue
Normal file
@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<view class="tui-countdown-box">
|
||||
<view class="tui-countdown-item"
|
||||
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(d, width) + 'rpx', height: height + 'rpx' }"
|
||||
v-if="days">
|
||||
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||
{{ d }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }" v-if="days">
|
||||
{{ isColon ? ':' : '天' }}
|
||||
</view>
|
||||
<view class="tui-countdown-item"
|
||||
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(h, width) + 'rpx', height: height + 'rpx' }"
|
||||
v-if="hours">
|
||||
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||
{{ h }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }" v-if="hours">
|
||||
{{ isColon ? ':' : '时' }}
|
||||
</view>
|
||||
<view class="tui-countdown-item"
|
||||
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(i, width) + 'rpx', height: height + 'rpx' }"
|
||||
v-if="minutes">
|
||||
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||
{{ i }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }" v-if="minutes">
|
||||
{{ isColon ? ':' : '分' }}
|
||||
</view>
|
||||
<view class="tui-countdown-item"
|
||||
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(s, width) + 'rpx', height: height + 'rpx' }"
|
||||
v-if="seconds">
|
||||
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||
{{ s }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
|
||||
v-if="seconds && !isColon">
|
||||
{{ unitEn ? 's' : '秒' }}
|
||||
</view>
|
||||
|
||||
<view class="tui-countdown-colon"
|
||||
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
|
||||
v-if="seconds && isMs && isColon">.</view>
|
||||
<view class="tui-countdown__ms" :style="{
|
||||
background: backgroundColor,
|
||||
borderColor: borderColor,
|
||||
fontSize: msSize + 'rpx',
|
||||
color: msColor,
|
||||
height: height + 'rpx',
|
||||
width: msWidth > 0 ? msWidth + 'rpx' : 'auto'
|
||||
}" v-if="seconds && isMs">
|
||||
<view :class="{ 'tui-ms__list': ani }">
|
||||
<view class="tui-ms__item" :style="{ height: height + 'rpx' }" v-for="(item, index) in ms" :key="index">
|
||||
<view :class="[scale ? 'tui-countdown-scale' : '']">{{item}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiCountdown',
|
||||
emits: ['end', 'time'],
|
||||
props: {
|
||||
//数字框宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
//数字框高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
//数字框border颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//数字框背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//数字框字体大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
//数字框字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//是否缩放 0.9
|
||||
scale: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//冒号大小
|
||||
colonSize: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
//冒号颜色
|
||||
colonColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//剩余时间 (单位:秒)
|
||||
time: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//是否包含天
|
||||
days: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否包含小时
|
||||
hours: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否包含分钟
|
||||
minutes: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否包含秒
|
||||
seconds: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//单位用英文缩写表示 仅seconds秒数有效
|
||||
unitEn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否展示为冒号,false为文字
|
||||
isColon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否返回剩余时间
|
||||
returnTime: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否显示毫秒
|
||||
isMs: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
msWidth: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
msSize: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
msColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
time(val) {
|
||||
this.clearTimer();
|
||||
this.doLoop();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
countdown: null,
|
||||
d: '0',
|
||||
h: '00',
|
||||
i: '00',
|
||||
s: '00',
|
||||
//此处若从9到1,结束需要特殊处理
|
||||
ms: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
ani: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.clearTimer();
|
||||
let seconds = Number(this.time || 0);
|
||||
if (seconds > 0) {
|
||||
this.doLoop();
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer();
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer();
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
getWidth: function(num, width) {
|
||||
return num > 99 ? (width / 2) * num.toString().length : width;
|
||||
},
|
||||
clearTimer() {
|
||||
clearInterval(this.countdown);
|
||||
this.countdown = null;
|
||||
},
|
||||
endOfTime(isStop = false) {
|
||||
this.ani = false;
|
||||
this.clearTimer();
|
||||
if (!isStop) {
|
||||
this.$emit('end', {});
|
||||
}
|
||||
},
|
||||
doLoop(time = 0) {
|
||||
let seconds = time || Number(this.time || 0);
|
||||
this.ani = true;
|
||||
this.countDown(seconds);
|
||||
this.countdown = setInterval(() => {
|
||||
seconds--;
|
||||
this.countDown(seconds);
|
||||
if (this.returnTime) {
|
||||
this.$emit('time', {
|
||||
seconds: seconds
|
||||
});
|
||||
}
|
||||
if (seconds <= 0) {
|
||||
this.endOfTime();
|
||||
return;
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
countDown(seconds) {
|
||||
let [day, hour, minute, second] = [0, 0, 0, 0];
|
||||
if (seconds > 0) {
|
||||
day = this.days ? Math.floor(seconds / (60 * 60 * 24)) : 0;
|
||||
hour = this.hours ? Math.floor(seconds / (60 * 60)) - day * 24 : 0;
|
||||
minute = this.minutes ? Math.floor(seconds / 60) - hour * 60 - day * 24 * 60 : 0;
|
||||
second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
|
||||
}
|
||||
hour = hour < 10 ? '0' + hour : hour;
|
||||
minute = minute < 10 ? '0' + minute : minute;
|
||||
second = second < 10 ? '0' + second : second;
|
||||
this.d = day;
|
||||
this.h = hour;
|
||||
this.i = minute;
|
||||
this.s = second;
|
||||
},
|
||||
reset(seconds = 0) {
|
||||
let time = seconds || Number(this.time);
|
||||
this.clearTimer();
|
||||
if (time > 0) {
|
||||
this.doLoop(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-countdown-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-countdown-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-countdown-item {
|
||||
border: 1rpx solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6rpx;
|
||||
white-space: nowrap;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.tui-countdown-time {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tui-countdown-colon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 5rpx;
|
||||
}
|
||||
|
||||
.tui-colon-pad {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.tui-countdown-scale {
|
||||
transform: scale(0.9);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.tui-countdown__ms {
|
||||
border: 1rpx solid;
|
||||
overflow: hidden;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
/*ms使用css3代替js频繁更新操作,性能优化*/
|
||||
.tui-ms__list {
|
||||
animation: loop 1s steps(10) infinite;
|
||||
}
|
||||
|
||||
@keyframes loop {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.tui-ms__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
763
src/components/thorui/tui-cropper-app/tui-cropper-app.vue
Normal file
763
src/components/thorui/tui-cropper-app/tui-cropper-app.vue
Normal file
@ -0,0 +1,763 @@
|
||||
<template>
|
||||
<view class="tui-container" @touchmove.stop.prevent="stop">
|
||||
<view class="tui-image-cropper" :change:prop="parse.propsChange" :prop="props" :data-lockRatio="lockRatio"
|
||||
:data-lockWidth="lockWidth" :data-lockHeight="lockHeight" :data-maxWidth="maxWidth"
|
||||
:data-minWidth="minWidth" :data-maxHeight="maxHeight" :data-minHeight="minHeight" :data-width="width"
|
||||
:data-height="height" :data-limitMove="limitMove" :data-windowHeight="sysInfo.windowHeight || 600"
|
||||
:data-windowWidth="sysInfo.windowWidth || 400" :data-imgTop="imgTop" :data-imgLeft="imgLeft"
|
||||
:data-imgWidth="imgWidth" :data-imgHeight="imgHeight" :data-angle="angle" @touchend="parse.cutTouchEnd"
|
||||
@touchstart="parse.cutTouchStart" @touchmove="parse.cutTouchMove">
|
||||
<view class="tui-content">
|
||||
<view class="tui-content-top tui-bg-transparent"
|
||||
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||
<view class="tui-content-middle">
|
||||
<view class="tui-bg-transparent tui-wxs-bg"
|
||||
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||
<view class="tui-cropper-box"
|
||||
:style="{ borderColor: borderColor, transitionProperty: cutAnimation ? '' : 'background' }">
|
||||
<view v-for="(item, index) in 4" :key="index" class="tui-edge"
|
||||
:class="[`tui-${index < 2 ? 'top' : 'bottom'}-${index === 0 || index === 2 ? 'left' : 'right'}`]"
|
||||
:style="{
|
||||
width: edgeWidth,
|
||||
height: edgeWidth,
|
||||
borderColor: edgeColor,
|
||||
borderWidth: edgeBorderWidth,
|
||||
left: index === 0 || index === 2 ? `-${edgeOffsets}` : 'auto',
|
||||
right: index === 1 || index === 3 ? `-${edgeOffsets}` : 'auto',
|
||||
top: index < 2 ? `-${edgeOffsets}` : 'auto',
|
||||
bottom: index > 1 ? `-${edgeOffsets}` : 'auto'
|
||||
}"></view>
|
||||
</view>
|
||||
<view class="tui-flex-auto tui-bg-transparent"
|
||||
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||
</view>
|
||||
<view class="tui-flex-auto tui-bg-transparent"
|
||||
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||
</view>
|
||||
<image @load="imageLoad" @error="imageLoad" @touchstart="parse.touchstart" @touchmove="parse.touchmove"
|
||||
@touchend="parse.touchend" :data-minScale="minScale" :data-maxScale="maxScale"
|
||||
:data-disableRotate="disableRotate" :style="{
|
||||
width: imgWidth ? imgWidth + 'px' : 'auto',
|
||||
height: imgHeight ? imgHeight + 'px' : 'auto',
|
||||
transitionDuration: (cutAnimation ? 0.3 : 0) + 's'
|
||||
}" class="tui-cropper-image" :class="{'tui-cropper__image-hidden':!picturePath}" :src="picturePath"
|
||||
mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="tui-cropper-tabbar" v-if="!custom">
|
||||
<view class="tui-op-btn" @tap.stop="back">取消</view>
|
||||
<image :src="rotateImg" class="tui-rotate-img" @tap="setAngle"></image>
|
||||
<view class="tui-op-btn" @tap.stop="getImage">完成</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script src="./tui-cropper-app.wxs" module="parse" lang="wxs"></script>
|
||||
<script>
|
||||
/**
|
||||
* 注意:组件中使用的图片地址,将文件复制到自己项目中
|
||||
* 如果图片位置与组件同级,编译成小程序时图片会丢失
|
||||
* 拷贝static下整个components文件夹
|
||||
*也可直接转成base64(不建议)
|
||||
*
|
||||
* */
|
||||
export default {
|
||||
name: 'tuiCropperApp',
|
||||
emits: ['ready', 'cropper', 'imageLoad', 'initAngle'],
|
||||
props: {
|
||||
//图片路径
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/*
|
||||
默认正方形,可修改大小控制比例
|
||||
裁剪框高度 px
|
||||
*/
|
||||
height: {
|
||||
type: Number,
|
||||
default: 280
|
||||
},
|
||||
//裁剪框宽度 px
|
||||
width: {
|
||||
type: Number,
|
||||
default: 280
|
||||
},
|
||||
//裁剪框最小宽度 px
|
||||
minWidth: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
//裁剪框最小高度 px
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
//裁剪框最大宽度 px
|
||||
maxWidth: {
|
||||
type: Number,
|
||||
default: 360
|
||||
},
|
||||
//裁剪框最大高度 px
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: 360
|
||||
},
|
||||
//裁剪框border颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: 'rgba(255,255,255,0.1)'
|
||||
},
|
||||
//裁剪框边缘线颜色
|
||||
edgeColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
//裁剪框边缘线宽度 w=h
|
||||
edgeWidth: {
|
||||
type: String,
|
||||
default: '34rpx'
|
||||
},
|
||||
//裁剪框边缘线border宽度
|
||||
edgeBorderWidth: {
|
||||
type: String,
|
||||
default: '6rpx'
|
||||
},
|
||||
//偏移距离,根据edgeBorderWidth进行调整
|
||||
edgeOffsets: {
|
||||
type: String,
|
||||
default: '6rpx'
|
||||
},
|
||||
/**
|
||||
* 如果宽度和高度都为true则裁剪框禁止拖动
|
||||
* 裁剪框宽度锁定
|
||||
*/
|
||||
lockWidth: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//裁剪框高度锁定
|
||||
lockHeight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//锁定裁剪框比例(放大或缩小)
|
||||
lockRatio: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//生成的图片尺寸相对剪裁框的比例
|
||||
scaleRatio: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
//图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 0.8
|
||||
},
|
||||
//图片旋转角度[当前版本只能传90的倍数,否则可能导致裁剪失败或不正确]
|
||||
rotateAngle: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//图片最小缩放比
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: 0.5
|
||||
},
|
||||
//图片最大缩放比
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
//是否禁用触摸旋转(为false则可以触摸转动图片,limitMove为false生效)
|
||||
//此属性App端当前版本不可用,否则可能导致裁剪失败或不正确
|
||||
disableRotate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否限制移动范围(剪裁框只能在图片内,为true不可触摸转动图片)
|
||||
//注意:此属性当前版本不可传false,由于api不支持超出裁剪区域裁剪,所以只能限制在裁剪框内
|
||||
limitMove: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//自定义操作栏(为true时隐藏底部操作栏)
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//值发生改变开始裁剪(custom为true时生效)
|
||||
startCutting: {
|
||||
type: [Number, Boolean],
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 是否返回base64(H5端默认base64)
|
||||
* 支持平台:App,微信小程序,支付宝小程序,H5(默认url就是base64)
|
||||
**/
|
||||
isBase64: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//裁剪时是否显示loadding
|
||||
loadding: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//旋转icon
|
||||
rotateImg: {
|
||||
type: String,
|
||||
default: '/static/components/cropper/img_rotate.png'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
TIME_CUT_CENTER: null,
|
||||
cutX: 0, //画布x轴起点
|
||||
cutY: 0, //画布y轴起点0
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
naturalWidth: 0,
|
||||
naturalHeight: 0,
|
||||
imgWidth: 0, //图片宽度
|
||||
imgHeight: 0, //图片高度
|
||||
scale: 1, //图片缩放比
|
||||
angle: 0, //图片旋转角度
|
||||
cutAnimation: false, //是否开启图片和裁剪框过渡
|
||||
cutAnimationTime: null,
|
||||
imgTop: 0, //图片上边距
|
||||
imgLeft: 0, //图片左边距
|
||||
sysInfo: {},
|
||||
props: '',
|
||||
sizeChange: 0, //2
|
||||
angleChange: 0, //3
|
||||
resetChange: 0, //4
|
||||
centerChange: 0, //5
|
||||
orientation: '',
|
||||
picturePath: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
//定义变量然后利用change触发
|
||||
imageUrl(val, oldVal) {
|
||||
this.imageReset();
|
||||
this.showLoading();
|
||||
uni.getImageInfo({
|
||||
src: val,
|
||||
success: res => {
|
||||
//计算图片尺寸
|
||||
this.naturalWidth = res.width;
|
||||
this.naturalHeight = res.height;
|
||||
this.orientation = res.orientation;
|
||||
if (this.orientation != 'up') {
|
||||
//宽高传值颠倒
|
||||
let width = this.orientation == 'down' ? res.width : res.height;
|
||||
let height = this.orientation == 'down' ? res.height : res.width;
|
||||
this.compressImage(val, width, height);
|
||||
} else {
|
||||
this.picturePath = val;
|
||||
this.imgComputeSize(res.width, res.height);
|
||||
if (this.limitMove) {
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: err => {
|
||||
this.imgComputeSize();
|
||||
if (this.limitMove) {
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
rotateAngle(val) {
|
||||
this.cutAnimation = true;
|
||||
this.angle = val;
|
||||
this.angleChanged(val);
|
||||
},
|
||||
cutAnimation(val) {
|
||||
//开启过渡260毫秒之后自动关闭
|
||||
clearTimeout(this.cutAnimationTime);
|
||||
if (val) {
|
||||
this.cutAnimationTime = setTimeout(() => {
|
||||
this.cutAnimation = false;
|
||||
}, 260);
|
||||
}
|
||||
},
|
||||
limitMove(val) {
|
||||
if (val) {
|
||||
this.angleChanged(this.angle);
|
||||
}
|
||||
},
|
||||
startCutting(val) {
|
||||
if (this.custom && val) {
|
||||
this.getImage();
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.picturePath = this.imageUrl;
|
||||
},
|
||||
mounted() {
|
||||
this.sysInfo = uni.getSystemInfoSync();
|
||||
this.imgTop = this.sysInfo.windowHeight / 2;
|
||||
this.imgLeft = this.sysInfo.windowWidth / 2;
|
||||
this.canvasHeight = this.height;
|
||||
this.canvasWidth = this.width;
|
||||
//初始化
|
||||
setTimeout(() => {
|
||||
this.props = '1,1';
|
||||
}, 0);
|
||||
setTimeout(() => {
|
||||
this.$emit('ready', {});
|
||||
}, 200);
|
||||
},
|
||||
methods: {
|
||||
toast(title) {
|
||||
uni.showToast({
|
||||
title: title || '出错啦~',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
async compressImage(url, width, height) {
|
||||
let imgUrl = url;
|
||||
if (~imgUrl.indexOf('https:')) {
|
||||
imgUrl = await this.getLocalImage(url);
|
||||
if (!imgUrl) {
|
||||
this.toast('网络图片处理失败~');
|
||||
return;
|
||||
}
|
||||
}
|
||||
let defaultAngle = {
|
||||
up: 0,
|
||||
down: 180,
|
||||
left: 270,
|
||||
right: 90
|
||||
} [this.orientation] || 0;
|
||||
let f_dst = `_documents/${this.unique()}.jpg`;
|
||||
plus.zip.compressImage({
|
||||
src: imgUrl,
|
||||
dst: f_dst,
|
||||
overwrite: true,
|
||||
rotate: defaultAngle,
|
||||
format: 'jpg'
|
||||
},
|
||||
i => {
|
||||
this.picturePath = i.target;
|
||||
this.imgComputeSize(width, height);
|
||||
if (this.limitMove) {
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
}
|
||||
},
|
||||
e => {
|
||||
this.picturePath = imgUrl;
|
||||
this.imgComputeSize(width, height);
|
||||
if (this.limitMove) {
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
//网络图片转成本地文件[同步执行]
|
||||
async getLocalImage(url) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url: url,
|
||||
success: res => {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: res => {
|
||||
reject(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
//返回裁剪后图片信息
|
||||
getImage() {
|
||||
if (!this.picturePath) {
|
||||
this.toast('请选择图片');
|
||||
return;
|
||||
}
|
||||
this.loadding && this.showLoading();
|
||||
let cutting = async () => {
|
||||
//图片实际大小
|
||||
let imgUrl = this.picturePath;
|
||||
if (~this.picturePath.indexOf('https:')) {
|
||||
imgUrl = await this.getLocalImage(this.picturePath);
|
||||
if (!imgUrl) {
|
||||
this.toast('网络图片处理失败~');
|
||||
return;
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
url: '',
|
||||
base64: ''
|
||||
};
|
||||
let f_dst = `_documents/${this.unique()}.jpg`;
|
||||
let multiple = Math.round(this.angle / 90);
|
||||
let angle = 0,
|
||||
x = 0,
|
||||
y = 0,
|
||||
left = 0,
|
||||
top = 0,
|
||||
clipHeight = 0,
|
||||
clipWidth = 0;
|
||||
|
||||
let isAndroid = this.sysInfo.platform.toLocaleLowerCase() == 'android';
|
||||
|
||||
if (multiple % 2 == 0) {
|
||||
angle = this.angle % 360 == 0 || this.angle == 0 ? 0 : 180;
|
||||
if (!isAndroid || (isAndroid && angle == 0)) {
|
||||
x = this.imgLeft - (this.imgWidth * this.scale) / 2;
|
||||
y = this.imgTop - (this.imgHeight * this.scale) / 2;
|
||||
left = (((this.cutX - x) / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
top = (((this.cutY - y) / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
clipWidth = ((this.canvasWidth / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
clipHeight = ((this.canvasHeight / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
} else {
|
||||
x = this.imgLeft - (this.imgWidth * this.scale) / 2;
|
||||
y = this.imgTop - (this.imgHeight * this.scale) / 2;
|
||||
left = (((this.imgWidth * this.scale - (this.cutX - x) - this.canvasWidth) / this
|
||||
.imgWidth / this.scale) * 100).toFixed(2);
|
||||
top = (((this.imgHeight * this.scale - (this.cutY - y) - this.canvasHeight) / this
|
||||
.imgHeight / this.scale) * 100).toFixed(2);
|
||||
clipWidth = ((this.canvasWidth / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
clipHeight = ((this.canvasHeight / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
}
|
||||
} else {
|
||||
angle = this.angle % 270 == 0 ? 270 : 90;
|
||||
if (isAndroid) {
|
||||
if (angle == 90) {
|
||||
x = this.imgLeft - (this.imgHeight * this.scale) / 2;
|
||||
y = this.imgTop - (this.imgWidth * this.scale) / 2;
|
||||
top = (((this.imgHeight * this.scale - (this.cutX - x) - this.canvasHeight) / this
|
||||
.imgHeight / this.scale) * 100).toFixed(2);
|
||||
left = (((this.imgWidth * this.scale - (this.cutY - y) - this.canvasWidth) / this
|
||||
.imgWidth / this.scale) * 100).toFixed(2);
|
||||
clipHeight = ((this.canvasWidth / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
clipWidth = ((this.canvasHeight / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
} else {
|
||||
x = this.imgLeft - (this.imgHeight * this.scale) / 2;
|
||||
y = this.imgTop - (this.imgWidth * this.scale) / 2;
|
||||
top = (((this.cutX - x) / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
left = (((this.cutY - y) / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
clipHeight = ((this.canvasWidth / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
clipWidth = ((this.canvasHeight / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
}
|
||||
} else {
|
||||
x = this.imgLeft - (this.imgHeight * this.scale) / 2;
|
||||
y = this.imgTop - (this.imgWidth * this.scale) / 2;
|
||||
left = (((this.cutX - x) / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
top = (((this.cutY - y) / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
clipWidth = ((this.canvasWidth / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||
clipHeight = ((this.canvasHeight / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||
}
|
||||
}
|
||||
let width = (this.imgWidth < this.naturalWidth ? this.naturalWidth : this.imgWidth) * this
|
||||
.scale;
|
||||
let height = (this.imgHeight < this.naturalHeight ? this.naturalHeight : this.imgHeight) * this
|
||||
.scale;
|
||||
// if (isAndroid && width > 800) {
|
||||
// width = '800px';
|
||||
// height = 'auto'
|
||||
// } else {
|
||||
// width = `${width}px`;
|
||||
// height = `${height}px`
|
||||
// }
|
||||
width = `${width}px`;
|
||||
height = `${height}px`;
|
||||
left = Number(left) <= 0 ? 0 : left;
|
||||
top = Number(top) <= 0 ? 0 : top;
|
||||
plus.zip.compressImage({
|
||||
src: imgUrl,
|
||||
dst: f_dst,
|
||||
quality: this.quality * 100,
|
||||
overwrite: true,
|
||||
format: 'jpg',
|
||||
width: width,
|
||||
height: height,
|
||||
rotate: angle,
|
||||
clip: {
|
||||
top: `${top}%`,
|
||||
left: `${left}%`,
|
||||
width: `${clipWidth}%`,
|
||||
height: `${clipHeight}%`
|
||||
}
|
||||
},
|
||||
i => {
|
||||
this.loadding && uni.hideLoading();
|
||||
data.url = i.target;
|
||||
if (this.isBase64) {
|
||||
plus.io.resolveLocalFileSystemURL(f_dst, entry => {
|
||||
entry.file(
|
||||
file => {
|
||||
let reader = new plus.io.FileReader();
|
||||
reader.onloadend = e => {
|
||||
data.base64 = e.target.result;
|
||||
this.$emit('cropper', data);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
e => {
|
||||
//转换base64失败,传回图片url
|
||||
this.$emit('cropper', data);
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.$emit('cropper', data);
|
||||
}
|
||||
},
|
||||
e => {
|
||||
console.log(e);
|
||||
this.toast('图片裁剪失败,请稍候再试~');
|
||||
//原图输出
|
||||
// data.url = imgUrl;
|
||||
// this.$emit('cropper', data);
|
||||
}
|
||||
);
|
||||
};
|
||||
setTimeout(() => {
|
||||
cutting();
|
||||
}, 20);
|
||||
},
|
||||
unique(n = 6) {
|
||||
let rnd = '';
|
||||
for (let i = 0; i < n; i++) rnd += Math.floor(Math.random() * 10);
|
||||
return 'thorui_' + new Date().getTime() + rnd;
|
||||
},
|
||||
change(e) {
|
||||
this.cutX = e.cutX || 0;
|
||||
this.cutY = e.cutY || 0;
|
||||
this.canvasWidth = e.canvasWidth || this.width;
|
||||
this.canvasHeight = e.canvasHeight || this.height;
|
||||
this.imgWidth = e.imgWidth || this.imgWidth;
|
||||
this.imgHeight = e.imgHeight || this.imgHeight;
|
||||
this.scale = e.scale || 1;
|
||||
this.angle = e.angle || 0;
|
||||
this.imgTop = e.imgTop || 0;
|
||||
this.imgLeft = e.imgLeft || 0;
|
||||
},
|
||||
imageReset() {
|
||||
this.scale = 1;
|
||||
this.angle = 0;
|
||||
let sys = this.sysInfo.windowHeight ? this.sysInfo : uni.getSystemInfoSync();
|
||||
this.imgTop = sys.windowHeight / 2;
|
||||
this.imgLeft = sys.windowWidth / 2;
|
||||
this.resetChange++;
|
||||
this.props = `4,${this.resetChange}`;
|
||||
//初始化旋转角度 0deg
|
||||
this.$emit('initAngle', {});
|
||||
},
|
||||
imageLoad(e) {
|
||||
this.imageReset();
|
||||
uni.hideLoading();
|
||||
this.$emit('imageLoad', {});
|
||||
},
|
||||
|
||||
imgComputeSize(width, height) {
|
||||
//默认按图片最小边 = 对应裁剪框尺寸
|
||||
let imgWidth = width,
|
||||
imgHeight = height;
|
||||
if (imgWidth && imgHeight) {
|
||||
if (imgWidth / imgHeight > this.width / this.height) {
|
||||
imgHeight = this.height;
|
||||
imgWidth = (width / height) * imgHeight;
|
||||
} else {
|
||||
imgWidth = this.width;
|
||||
imgHeight = (height / width) * imgWidth;
|
||||
}
|
||||
} else {
|
||||
let sys = this.sysInfo || uni.getSystemInfoSync();
|
||||
imgWidth = sys.windowWidth;
|
||||
imgHeight = 0;
|
||||
}
|
||||
this.imgWidth = imgWidth;
|
||||
this.imgHeight = imgHeight;
|
||||
this.sizeChange++;
|
||||
this.props = `2,${this.sizeChange}`;
|
||||
},
|
||||
moveStop() {
|
||||
clearTimeout(this.TIME_CUT_CENTER);
|
||||
this.TIME_CUT_CENTER = setTimeout(() => {
|
||||
if (!this.cutAnimation) {
|
||||
this.cutAnimation = true;
|
||||
}
|
||||
this.centerChange++;
|
||||
this.props = `5,${this.centerChange}`;
|
||||
}, 666);
|
||||
},
|
||||
moveDuring() {
|
||||
clearTimeout(this.TIME_CUT_CENTER);
|
||||
},
|
||||
showLoading() {
|
||||
uni.showLoading({
|
||||
title: '请稍候...',
|
||||
mask: true
|
||||
});
|
||||
},
|
||||
stop() {},
|
||||
back() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
angleChanged(val) {
|
||||
this.moveStop();
|
||||
if (this.limitMove && val % 90) {
|
||||
this.angle = Math.round(val / 90) * 90;
|
||||
}
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
},
|
||||
setAngle() {
|
||||
this.cutAnimation = true;
|
||||
this.angle = this.angle + 90;
|
||||
this.angleChanged(this.angle);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tui-image-cropper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.tui-cropper__image-hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tui-content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-bg-transparent {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.tui-content-top {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-content-middle {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-cropper-box {
|
||||
position: relative;
|
||||
/* transition-duration: 0.2s; */
|
||||
border-style: solid;
|
||||
border-width: 1rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-flex-auto {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.tui-cropper-image {
|
||||
width: 100%;
|
||||
border-style: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.tui-edge {
|
||||
border-style: solid;
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-top-left {
|
||||
border-bottom-width: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
.tui-top-right {
|
||||
border-bottom-width: 0 !important;
|
||||
border-left-width: 0 !important;
|
||||
}
|
||||
|
||||
.tui-bottom-left {
|
||||
border-top-width: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
.tui-bottom-right {
|
||||
border-top-width: 0 !important;
|
||||
border-left-width: 0 !important;
|
||||
}
|
||||
|
||||
.tui-cropper-tabbar {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
padding: 0 40rpx;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.tui-cropper-tabbar::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
}
|
||||
|
||||
.tui-op-btn {
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-rotate-img {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
</style>
|
||||
571
src/components/thorui/tui-cropper-app/tui-cropper-app.wxs
Normal file
571
src/components/thorui/tui-cropper-app/tui-cropper-app.wxs
Normal file
@ -0,0 +1,571 @@
|
||||
var cropper = {
|
||||
CUT_START: null,
|
||||
cutX: 0, //画布x轴起点
|
||||
cutY: 0, //画布y轴起点0
|
||||
touchRelative: [{
|
||||
x: 0,
|
||||
y: 0
|
||||
}], //手指或鼠标和图片中心的相对位置
|
||||
flagCutTouch: false, //是否是拖动裁剪框
|
||||
hypotenuseLength: 0, //双指触摸时斜边长度
|
||||
flagEndTouch: false, //是否结束触摸
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
imgWidth: 0, //图片宽度
|
||||
imgHeight: 0, //图片高度
|
||||
scale: 1, //图片缩放比
|
||||
angle: 0, //图片旋转角度
|
||||
imgTop: 0, //图片上边距
|
||||
imgLeft: 0, //图片左边距
|
||||
//是否限制移动范围(剪裁框只能在图片内,为true不可触摸转动图片)
|
||||
limitMove: true,
|
||||
minHeight: 0,
|
||||
maxHeight: 0,
|
||||
minWidth: 0,
|
||||
maxWidth: 0,
|
||||
windowHeight: 0,
|
||||
windowWidth: 0,
|
||||
init: true
|
||||
}
|
||||
|
||||
function bool(str) {
|
||||
return str === 'true' || str == true ? true : false
|
||||
}
|
||||
|
||||
function touchstart(e, ins) {
|
||||
//var instance = e.instance;
|
||||
// var state = instance.getState();
|
||||
var touch = e.touches || e.changedTouches;
|
||||
cropper.flagEndTouch = false;
|
||||
if (touch.length == 1) {
|
||||
cropper.touchRelative[0] = {
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
};
|
||||
} else {
|
||||
var width = Math.abs(touch[0].pageX - touch[1].pageX);
|
||||
var height = Math.abs(touch[0].pageY - touch[1].pageY);
|
||||
cropper.touchRelative = [{
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
},
|
||||
{
|
||||
x: touch[1].pageX - cropper.imgLeft,
|
||||
y: touch[1].pageY - cropper.imgTop
|
||||
}
|
||||
];
|
||||
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function moveDuring(ins) {
|
||||
if (!ins) return;
|
||||
ins.callMethod('moveDuring')
|
||||
}
|
||||
|
||||
function moveStop(ins) {
|
||||
if (!ins) return;
|
||||
ins.callMethod('moveStop')
|
||||
};
|
||||
|
||||
function setCutCenter(ins) {
|
||||
var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5;
|
||||
var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5;
|
||||
//顺序不能变
|
||||
cropper.imgTop = cropper.imgTop - cropper.cutY + cutY;
|
||||
cropper.cutY = cutY; //截取的框上边距
|
||||
cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX;
|
||||
cropper.cutX = cutX; //截取的框左边距
|
||||
styleUpdate(ins)
|
||||
cutDetectionPosition(ins)
|
||||
imgTransform(ins)
|
||||
updateData(ins)
|
||||
}
|
||||
|
||||
function touchmove(e, ins) {
|
||||
var touch = e.touches || e.changedTouches;
|
||||
if (cropper.flagEndTouch) return;
|
||||
moveDuring(ins);
|
||||
if (e.touches.length == 1) {
|
||||
var left = touch[0].pageX - cropper.touchRelative[0].x,
|
||||
top = touch[0].pageY - cropper.touchRelative[0].y;
|
||||
cropper.imgLeft = left;
|
||||
cropper.imgTop = top;
|
||||
imgTransform(ins);
|
||||
imgMarginDetectionPosition(ins);
|
||||
} else {
|
||||
var res = e.instance.getDataset();
|
||||
var minScale = +res.minscale;
|
||||
var maxScale = +res.maxscale;
|
||||
var disableRotate = bool(res.disablerotate)
|
||||
var width = Math.abs(touch[0].pageX - touch[1].pageX),
|
||||
height = Math.abs(touch[0].pageY - touch[1].pageY),
|
||||
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
|
||||
scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
|
||||
current_deg = 0;
|
||||
scale = scale <= minScale ? minScale : scale;
|
||||
scale = scale >= maxScale ? maxScale : scale;
|
||||
cropper.scale = scale;
|
||||
imgMarginDetectionScale(ins, true);
|
||||
var touchRelative = [{
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
},
|
||||
{
|
||||
x: touch[1].pageX - cropper.imgLeft,
|
||||
y: touch[1].pageY - cropper.imgTop
|
||||
}
|
||||
];
|
||||
if (!disableRotate) {
|
||||
var first_atan = (180 / Math.PI) * Math.atan2(touchRelative[0].y, touchRelative[0].x);
|
||||
var first_atan_old = (180 / Math.PI) * Math.atan2(cropper.touchRelative[0].y, cropper.touchRelative[0].x);
|
||||
var second_atan = (180 / Math.PI) * Math.atan2(touchRelative[1].y, touchRelative[1].x);
|
||||
var second_atan_old = (180 / Math.PI) * Math.atan2(cropper.touchRelative[1].y, cropper.touchRelative[1].x);
|
||||
var first_deg = first_atan - first_atan_old,
|
||||
second_deg = second_atan - second_atan_old;
|
||||
if (first_deg != 0) {
|
||||
current_deg = first_deg;
|
||||
} else if (second_deg != 0) {
|
||||
current_deg = second_deg;
|
||||
}
|
||||
}
|
||||
cropper.touchRelative = touchRelative;
|
||||
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||
//更新视图
|
||||
cropper.angle = cropper.angle + current_deg;
|
||||
imgTransform(ins);
|
||||
}
|
||||
}
|
||||
|
||||
function touchend(e, ins) {
|
||||
cropper.flagEndTouch = true;
|
||||
moveStop(ins);
|
||||
updateData(ins)
|
||||
}
|
||||
|
||||
|
||||
function cutTouchStart(e, ins) {
|
||||
var touch = e.touches || e.changedTouches;
|
||||
var currentX = touch[0].pageX;
|
||||
var currentY = touch[0].pageY;
|
||||
|
||||
/*
|
||||
* (右下-1 右上-2 左上-3 左下-4)
|
||||
* left_x [3,4]
|
||||
* top_y [2,3]
|
||||
* right_x [1,2]
|
||||
* bottom_y [1,4]
|
||||
*/
|
||||
var left_x1 = cropper.cutX - 30;
|
||||
var left_x2 = cropper.cutX + 30;
|
||||
|
||||
var top_y1 = cropper.cutY - 30;
|
||||
var top_y2 = cropper.cutY + 30;
|
||||
|
||||
var right_x1 = cropper.cutX + cropper.canvasWidth - 30;
|
||||
var right_x2 = cropper.cutX + cropper.canvasWidth + 30;
|
||||
|
||||
var bottom_y1 = cropper.cutY + cropper.canvasHeight - 30;
|
||||
var bottom_y2 = cropper.cutY + cropper.canvasHeight + 30;
|
||||
|
||||
if (currentX > right_x1 && currentX < right_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
|
||||
moveDuring();
|
||||
cropper.flagCutTouch = true;
|
||||
cropper.flagEndTouch = true;
|
||||
cropper.CUT_START = {
|
||||
width: cropper.canvasWidth,
|
||||
height: cropper.canvasHeight,
|
||||
x: currentX,
|
||||
y: currentY,
|
||||
corner: 1
|
||||
};
|
||||
} else if (currentX > right_x1 && currentX < right_x2 && currentY > top_y1 && currentY < top_y2) {
|
||||
moveDuring();
|
||||
cropper.flagCutTouch = true;
|
||||
cropper.flagEndTouch = true;
|
||||
cropper.CUT_START = {
|
||||
width: cropper.canvasWidth,
|
||||
height: cropper.canvasHeight,
|
||||
x: currentX,
|
||||
y: currentY,
|
||||
cutY: cropper.cutY,
|
||||
cutX: cropper.cutX,
|
||||
corner: 2
|
||||
};
|
||||
} else if (currentX > left_x1 && currentX < left_x2 && currentY > top_y1 && currentY < top_y2) {
|
||||
moveDuring();
|
||||
cropper.flagCutTouch = true;
|
||||
cropper.flagEndTouch = true;
|
||||
cropper.CUT_START = {
|
||||
width: cropper.canvasWidth,
|
||||
height: cropper.canvasHeight,
|
||||
cutY: cropper.cutY,
|
||||
cutX: cropper.cutX,
|
||||
x: currentX,
|
||||
y: currentY,
|
||||
corner: 3
|
||||
};
|
||||
} else if (currentX > left_x1 && currentX < left_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
|
||||
moveDuring();
|
||||
cropper.flagCutTouch = true;
|
||||
cropper.flagEndTouch = true;
|
||||
cropper.CUT_START = {
|
||||
width: cropper.canvasWidth,
|
||||
height: cropper.canvasHeight,
|
||||
cutY: cropper.cutY,
|
||||
cutX: cropper.cutX,
|
||||
x: currentX,
|
||||
y: currentY,
|
||||
corner: 4
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function cutTouchMove(e, ins) {
|
||||
if (!cropper.CUT_START || cropper.CUT_START === 'null') return;
|
||||
if (cropper.flagCutTouch) {
|
||||
var touch = e.touches || e.changedTouches;
|
||||
var res = e.instance.getDataset();
|
||||
var lockRatio = bool(res.lockratio);
|
||||
var lockWidth = bool(res.lockwidth);
|
||||
var lockHeight = bool(res.lockheight);
|
||||
if (lockRatio && (lockWidth || lockHeight)) return;
|
||||
var width = cropper.canvasWidth,
|
||||
height = cropper.canvasHeight,
|
||||
cutY = cropper.cutY,
|
||||
cutX = cropper.cutX;
|
||||
|
||||
var size_correct = function() {
|
||||
width = width <= cropper.maxWidth ? (width >= cropper.minWidth ? width : cropper.minWidth) : cropper
|
||||
.maxWidth;
|
||||
height = height <= cropper.maxHeight ? (height >= cropper.minHeight ? height : cropper.minHeight) :
|
||||
cropper.maxHeight;
|
||||
}
|
||||
|
||||
var size_inspect = function() {
|
||||
if ((width > cropper.maxWidth || width < cropper.minWidth || height > cropper.maxHeight || height <
|
||||
cropper.minHeight) &&
|
||||
lockRatio) {
|
||||
size_correct();
|
||||
return false;
|
||||
} else {
|
||||
size_correct();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
height = cropper.CUT_START.height + (cropper.CUT_START.corner > 1 && cropper.CUT_START.corner < 4 ? 1 : -1) * (
|
||||
cropper.CUT_START.y - touch[0].pageY);
|
||||
switch (cropper.CUT_START.corner) {
|
||||
case 1:
|
||||
width = cropper.CUT_START.width - cropper.CUT_START.x + touch[0].pageX;
|
||||
if (lockRatio) {
|
||||
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||
}
|
||||
if (!size_inspect()) return;
|
||||
break;
|
||||
case 2:
|
||||
width = cropper.CUT_START.width - cropper.CUT_START.x + touch[0].pageX;
|
||||
if (lockRatio) {
|
||||
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||
}
|
||||
if (!size_inspect()) return;
|
||||
cutY = cropper.CUT_START.cutY - (height - cropper.CUT_START.height);
|
||||
break;
|
||||
case 3:
|
||||
width = cropper.CUT_START.width + cropper.CUT_START.x - touch[0].pageX;
|
||||
if (lockRatio) {
|
||||
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||
}
|
||||
if (!size_inspect()) return;
|
||||
cutY = cropper.CUT_START.cutY - (height - cropper.CUT_START.height);
|
||||
cutX = cropper.CUT_START.cutX - (width - cropper.CUT_START.width);
|
||||
break;
|
||||
case 4:
|
||||
width = cropper.CUT_START.width + cropper.CUT_START.x - touch[0].pageX;
|
||||
if (lockRatio) {
|
||||
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||
}
|
||||
if (!size_inspect()) return;
|
||||
cutX = cropper.CUT_START.cutX - (width - cropper.CUT_START.width);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!lockWidth && !lockHeight) {
|
||||
cropper.canvasWidth = width;
|
||||
cropper.cutX = cutX;
|
||||
cropper.canvasHeight = height;
|
||||
cropper.cutY = cutY;
|
||||
canvasHeight(ins);
|
||||
canvasWidth(ins);
|
||||
} else if (!lockWidth) {
|
||||
cropper.canvasWidth = width;
|
||||
cropper.cutX = cutX;
|
||||
canvasWidth(ins);
|
||||
} else if (!lockHeight) {
|
||||
cropper.canvasHeight = height;
|
||||
cropper.cutY = cutY;
|
||||
canvasHeight(ins);
|
||||
}
|
||||
styleUpdate(ins)
|
||||
imgMarginDetectionScale(ins);
|
||||
}
|
||||
}
|
||||
|
||||
//检测剪裁框位置是否在允许的范围内(屏幕内)
|
||||
function cutDetectionPosition(ins) {
|
||||
var windowHeight = cropper.windowHeight,
|
||||
windowWidth = cropper.windowWidth;
|
||||
|
||||
var cutDetectionPositionTop = function() {
|
||||
//检测上边距是否在范围内
|
||||
if (cropper.cutY < 0) {
|
||||
cropper.cutY = 0;
|
||||
}
|
||||
if (cropper.cutY > windowHeight - cropper.canvasHeight) {
|
||||
cropper.cutY = windowHeight - cropper.canvasHeight;
|
||||
}
|
||||
}
|
||||
|
||||
var cutDetectionPositionLeft = function() {
|
||||
//检测左边距是否在范围内
|
||||
if (cropper.cutX < 0) {
|
||||
cropper.cutX = 0;
|
||||
}
|
||||
if (cropper.cutX > windowWidth - cropper.canvasWidth) {
|
||||
cropper.cutX = windowWidth - cropper.canvasWidth;
|
||||
}
|
||||
}
|
||||
//裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
|
||||
if (cropper.cutY == null && cropper.cutX == null) {
|
||||
var cutY = (windowHeight - cropper.canvasHeight) * 0.5;
|
||||
var cutX = (windowWidth - cropper.canvasWidth) * 0.5;
|
||||
cropper.cutY = cutY; //截取的框上边距
|
||||
cropper.cutX = cutX; //截取的框左边距
|
||||
} else if (cropper.cutY != null && cropper.cutX != null) {
|
||||
cutDetectionPositionTop();
|
||||
cutDetectionPositionLeft();
|
||||
} else if (cropper.cutY != null && cropper.cutX == null) {
|
||||
cutDetectionPositionTop();
|
||||
cropper.cutX = (windowWidth - cropper.canvasWidth) / 2;
|
||||
} else if (cropper.cutY == null && cropper.cutX != null) {
|
||||
cutDetectionPositionLeft();
|
||||
cropper.cutY = (windowHeight - cropper.canvasHeight) / 2;
|
||||
}
|
||||
|
||||
styleUpdate(ins)
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片边缘检测-缩放
|
||||
*/
|
||||
function imgMarginDetectionScale(ins, delay) {
|
||||
if (!cropper.limitMove) return;
|
||||
var scale = cropper.scale;
|
||||
var imgWidth = cropper.imgWidth;
|
||||
var imgHeight = cropper.imgHeight;
|
||||
if ((cropper.angle / 90) % 2) {
|
||||
imgWidth = cropper.imgHeight;
|
||||
imgHeight = cropper.imgWidth;
|
||||
}
|
||||
if (imgWidth * scale < cropper.canvasWidth) {
|
||||
scale = cropper.canvasWidth / imgWidth;
|
||||
}
|
||||
if (imgHeight * scale < cropper.canvasHeight) {
|
||||
scale = Math.max(scale, cropper.canvasHeight / imgHeight);
|
||||
}
|
||||
imgMarginDetectionPosition(ins, scale, delay);
|
||||
}
|
||||
/**
|
||||
* 图片边缘检测-位置
|
||||
*/
|
||||
function imgMarginDetectionPosition(ins, scale, delay) {
|
||||
if (!cropper.limitMove) return;
|
||||
var left = cropper.imgLeft;
|
||||
var top = cropper.imgTop;
|
||||
scale = scale || cropper.scale;
|
||||
var imgWidth = cropper.imgWidth;
|
||||
var imgHeight = cropper.imgHeight;
|
||||
if ((cropper.angle / 90) % 2) {
|
||||
imgWidth = cropper.imgHeight;
|
||||
imgHeight = cropper.imgWidth;
|
||||
}
|
||||
left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2;
|
||||
left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper
|
||||
.canvasWidth -
|
||||
(imgWidth * scale) / 2;
|
||||
top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2;
|
||||
top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper
|
||||
.canvasHeight -
|
||||
(imgHeight * scale) / 2;
|
||||
|
||||
cropper.imgLeft = left;
|
||||
cropper.imgTop = top;
|
||||
cropper.scale = scale;
|
||||
styleUpdate(ins)
|
||||
if (!delay || delay === 'null') {
|
||||
imgTransform(ins);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cutTouchEnd(e, ins) {
|
||||
moveStop(ins);
|
||||
cropper.flagCutTouch = false;
|
||||
updateData(ins)
|
||||
}
|
||||
|
||||
|
||||
//改变截取框大小
|
||||
function computeCutSize(ins) {
|
||||
if (cropper.canvasWidth > cropper.windowWidth) {
|
||||
cropper.canvasWidth = cropper.windowWidth;
|
||||
// canvasWidth(ins)
|
||||
} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
|
||||
cropper.cutX = cropper.windowWidth - cropper.cutX;
|
||||
}
|
||||
if (cropper.canvasHeight > cropper.windowHeight) {
|
||||
cropper.canvasHeight = cropper.windowHeight;
|
||||
// canvasHeight(ins)
|
||||
} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
|
||||
cropper.cutY = cropper.windowHeight - cropper.cutY;
|
||||
}
|
||||
// styleUpdate(ins)
|
||||
}
|
||||
|
||||
function styleUpdate(ins) {
|
||||
if (!ins) return;
|
||||
var tcb = ins.selectComponent('.tui-cropper-box')
|
||||
var tcm = ins.selectComponent('.tui-content-middle')
|
||||
var tct = ins.selectComponent('.tui-content-top')
|
||||
var twb = ins.selectComponent('.tui-wxs-bg')
|
||||
if (!tcb || !tcm || !tct || !twb) return;
|
||||
tcb.setStyle({
|
||||
'width': cropper.canvasWidth + 'px',
|
||||
'height': cropper.canvasHeight + 'px'
|
||||
})
|
||||
tcm.setStyle({
|
||||
'height': cropper.canvasHeight + 'px'
|
||||
})
|
||||
tct.setStyle({
|
||||
'height': cropper.cutY + 'px'
|
||||
})
|
||||
twb.setStyle({
|
||||
'width': cropper.cutX + 'px'
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function imgTransform(ins) {
|
||||
var owner = ins.selectComponent('.tui-cropper-image')
|
||||
if (!owner) return
|
||||
var x = cropper.imgLeft - cropper.imgWidth / 2;
|
||||
var y = cropper.imgTop - cropper.imgHeight / 2;
|
||||
owner.setStyle({
|
||||
'transform': 'translate3d(' + x + 'px,' + y + 'px,0) scale(' + cropper.scale + ') rotate(' + cropper
|
||||
.angle + 'deg)'
|
||||
})
|
||||
}
|
||||
|
||||
function imageReset(ins) {
|
||||
cropper.scale = 1;
|
||||
cropper.angle = 0;
|
||||
imgTransform(ins);
|
||||
}
|
||||
//监听截取框宽高变化
|
||||
function canvasWidth(ins) {
|
||||
if (cropper.canvasWidth < cropper.minWidth) {
|
||||
cropper.canvasWidth = cropper.minWidth;
|
||||
}
|
||||
if (!ins) return;
|
||||
computeCutSize(ins);
|
||||
}
|
||||
|
||||
function canvasHeight(ins) {
|
||||
if (cropper.canvasHeight < cropper.minHeight) {
|
||||
cropper.canvasHeight = cropper.minHeight;
|
||||
}
|
||||
if (!ins) return;
|
||||
computeCutSize(ins);
|
||||
}
|
||||
|
||||
function updateData(ins) {
|
||||
if (!ins) return;
|
||||
ins.callMethod('change', {
|
||||
cutX: cropper.cutX,
|
||||
cutY: cropper.cutY,
|
||||
canvasWidth: cropper.canvasWidth,
|
||||
canvasHeight: cropper.canvasHeight,
|
||||
imgWidth: cropper.imgWidth,
|
||||
imgHeight: cropper.imgHeight,
|
||||
scale: cropper.scale,
|
||||
angle: cropper.angle,
|
||||
imgTop: cropper.imgTop,
|
||||
imgLeft: cropper.imgLeft
|
||||
})
|
||||
}
|
||||
|
||||
function propsChange(prop, oldProp, ownerInstance, ins) {
|
||||
if (prop && prop !== 'null') {
|
||||
var params = prop.split(',')
|
||||
var type = +params[0]
|
||||
var dataset = ins.getDataset();
|
||||
if (cropper.init || type == 4) {
|
||||
cropper.maxHeight = +dataset.maxheight;
|
||||
cropper.minHeight = +dataset.minheight;
|
||||
cropper.maxWidth = +dataset.maxwidth;
|
||||
cropper.minWidth = +dataset.minwidth;
|
||||
cropper.canvasWidth = +dataset.width;
|
||||
cropper.canvasHeight = +dataset.height;
|
||||
cropper.imgTop = dataset.windowheight / 2;
|
||||
cropper.imgLeft = dataset.windowwidth / 2;
|
||||
cropper.imgWidth = +dataset.imgwidth;
|
||||
cropper.imgHeight = +dataset.imgheight;
|
||||
cropper.windowHeight = +dataset.windowheight;
|
||||
cropper.windowWidth = +dataset.windowwidth;
|
||||
cropper.init = false
|
||||
} else if (type == 2 || type == 3) {
|
||||
cropper.imgWidth = +dataset.imgwidth;
|
||||
cropper.imgHeight = +dataset.imgheight;
|
||||
}
|
||||
cropper.limitMove = bool(dataset.limitmove);
|
||||
cropper.angle = +dataset.angle;
|
||||
if (type == 3) {
|
||||
imgTransform(ownerInstance);
|
||||
}
|
||||
switch (type) {
|
||||
case 1:
|
||||
setCutCenter(ownerInstance);
|
||||
//设置裁剪框大小>设置图片尺寸>绘制canvas
|
||||
computeCutSize(ownerInstance);
|
||||
//检查裁剪框是否在范围内
|
||||
cutDetectionPosition(ownerInstance);
|
||||
break;
|
||||
case 2:
|
||||
setCutCenter(ownerInstance);
|
||||
break;
|
||||
case 3:
|
||||
imgMarginDetectionScale(ownerInstance)
|
||||
break;
|
||||
case 4:
|
||||
imageReset(ownerInstance);
|
||||
break;
|
||||
case 5:
|
||||
setCutCenter(ownerInstance);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchstart: touchstart,
|
||||
touchmove: touchmove,
|
||||
touchend: touchend,
|
||||
cutTouchStart: cutTouchStart,
|
||||
cutTouchMove: cutTouchMove,
|
||||
cutTouchEnd: cutTouchEnd,
|
||||
propsChange: propsChange
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
function isObject(value) {
|
||||
let type = typeof value
|
||||
return !!value && (type == 'object' || type == 'function')
|
||||
}
|
||||
|
||||
function debounce(func, wait, options) {
|
||||
let lastArgs,
|
||||
lastThis,
|
||||
maxWait,
|
||||
result,
|
||||
timerId,
|
||||
lastCallTime
|
||||
|
||||
let lastInvokeTime = 0
|
||||
let leading = false
|
||||
let maxing = false
|
||||
let trailing = true
|
||||
|
||||
// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
|
||||
const useRAF = false
|
||||
|
||||
if (typeof func !== 'function') {
|
||||
throw new TypeError('Expected a function')
|
||||
}
|
||||
wait = +wait || 0
|
||||
if (isObject(options)) {
|
||||
leading = !!options.leading
|
||||
maxing = 'maxWait' in options
|
||||
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
|
||||
trailing = 'trailing' in options ? !!options.trailing : trailing
|
||||
}
|
||||
|
||||
function invokeFunc(time) {
|
||||
const args = lastArgs
|
||||
const thisArg = lastThis
|
||||
|
||||
lastArgs = lastThis = undefined
|
||||
lastInvokeTime = time
|
||||
result = func.apply(thisArg, args)
|
||||
return result
|
||||
}
|
||||
|
||||
function startTimer(pendingFunc, wait) {
|
||||
return setTimeout(pendingFunc, wait)
|
||||
}
|
||||
|
||||
function cancelTimer(id) {
|
||||
clearTimeout(id)
|
||||
}
|
||||
|
||||
function leadingEdge(time) {
|
||||
// Reset any `maxWait` timer.
|
||||
lastInvokeTime = time
|
||||
// Start the timer for the trailing edge.
|
||||
timerId = startTimer(timerExpired, wait)
|
||||
// Invoke the leading edge.
|
||||
return leading ? invokeFunc(time) : result
|
||||
}
|
||||
|
||||
function remainingWait(time) {
|
||||
const timeSinceLastCall = time - lastCallTime
|
||||
const timeSinceLastInvoke = time - lastInvokeTime
|
||||
const timeWaiting = wait - timeSinceLastCall
|
||||
|
||||
return maxing
|
||||
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
|
||||
: timeWaiting
|
||||
}
|
||||
|
||||
function shouldInvoke(time) {
|
||||
const timeSinceLastCall = time - lastCallTime
|
||||
const timeSinceLastInvoke = time - lastInvokeTime
|
||||
|
||||
// Either this is the first call, activity has stopped and we're at the
|
||||
// trailing edge, the system time has gone backwards and we're treating
|
||||
// it as the trailing edge, or we've hit the `maxWait` limit.
|
||||
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
|
||||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
|
||||
}
|
||||
|
||||
function timerExpired() {
|
||||
const time = Date.now()
|
||||
if (shouldInvoke(time)) {
|
||||
return trailingEdge(time)
|
||||
}
|
||||
// Restart the timer.
|
||||
timerId = startTimer(timerExpired, remainingWait(time))
|
||||
}
|
||||
|
||||
function trailingEdge(time) {
|
||||
timerId = undefined
|
||||
|
||||
// Only invoke if we have `lastArgs` which means `func` has been
|
||||
// debounced at least once.
|
||||
if (trailing && lastArgs) {
|
||||
return invokeFunc(time)
|
||||
}
|
||||
lastArgs = lastThis = undefined
|
||||
return result
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
if (timerId !== undefined) {
|
||||
cancelTimer(timerId)
|
||||
}
|
||||
lastInvokeTime = 0
|
||||
lastArgs = lastCallTime = lastThis = timerId = undefined
|
||||
}
|
||||
|
||||
function flush() {
|
||||
return timerId === undefined ? result : trailingEdge(Date.now())
|
||||
}
|
||||
|
||||
function pending() {
|
||||
return timerId !== undefined
|
||||
}
|
||||
|
||||
function debounced(...args) {
|
||||
const time = Date.now()
|
||||
const isInvoking = shouldInvoke(time)
|
||||
|
||||
lastArgs = args
|
||||
lastThis = this
|
||||
lastCallTime = time
|
||||
|
||||
if (isInvoking) {
|
||||
if (timerId === undefined) {
|
||||
return leadingEdge(lastCallTime)
|
||||
}
|
||||
if (maxing) {
|
||||
// Handle invocations in a tight loop.
|
||||
timerId = startTimer(timerExpired, wait)
|
||||
return invokeFunc(lastCallTime)
|
||||
}
|
||||
}
|
||||
if (timerId === undefined) {
|
||||
timerId = startTimer(timerExpired, wait)
|
||||
}
|
||||
return result
|
||||
}
|
||||
debounced.cancel = cancel
|
||||
debounced.flush = flush
|
||||
debounced.pending = pending
|
||||
return debounced
|
||||
}
|
||||
|
||||
export default debounce
|
||||
@ -0,0 +1,32 @@
|
||||
export const mapVirtualToProps = ({
|
||||
items,
|
||||
itemHeight
|
||||
}, {
|
||||
startIndex,
|
||||
endIndex
|
||||
}) => {
|
||||
const visibleItems = endIndex > -1 ? items.slice(startIndex, endIndex + 1) : []
|
||||
|
||||
// style
|
||||
const height = items.length * itemHeight
|
||||
const paddingTop = startIndex * itemHeight
|
||||
|
||||
return {
|
||||
items: visibleItems,
|
||||
style: `height: ${height}px; padding-top: ${paddingTop}px; box-sizing: border-box;`
|
||||
}
|
||||
}
|
||||
|
||||
export const getVisibleItemBounds = (viewTop, viewHeight, itemCount, itemHeight, itemBuffer) => {
|
||||
// visible list inside view
|
||||
const listViewTop = Math.max(0, viewTop)
|
||||
|
||||
// visible item indexes
|
||||
const startIndex = Math.max(0, Math.floor(listViewTop / itemHeight) - 12)
|
||||
const endIndex = Math.min(startIndex + Math.ceil(viewHeight / itemHeight) + itemBuffer - 1, itemCount)
|
||||
|
||||
return {
|
||||
startIndex,
|
||||
endIndex,
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
517
src/components/thorui/tui-cropper/tui-cropper.vue
Normal file
517
src/components/thorui/tui-cropper/tui-cropper.vue
Normal file
@ -0,0 +1,517 @@
|
||||
<template>
|
||||
<view class="tui-cropper__box" @touchmove.stop.prevent="stop">
|
||||
<image @load="imageLoad" @error="imageLoad" @touchstart="parse.touchstart" @touchmove="parse.touchmove"
|
||||
@touchend="parse.touchend" :data-minScale="minScale" :data-maxScale="maxScale" :style="{
|
||||
width: (imgWidth ? imgWidth : width) + 'px',
|
||||
height: imgHeight ? imgHeight + 'px' : 'auto',
|
||||
transitionDuration: (animation ? 0.3 : 0) + 's'
|
||||
}" class="tui-cropper__image" :class="{'tui-cropper__image-hidden':!imageUrl}" :src="imageUrl" mode="widthFix">
|
||||
</image>
|
||||
<view class="tui-backdrop__cropper"
|
||||
:style="{ width: width + 'px', height: height + 'px', borderRadius: isRound ? '50%' : '0' }">
|
||||
<view class="tui-cropper__border" :change:prop="parse.propsChange" :prop="props" :data-width="width"
|
||||
:data-height="height" :data-windowHeight="sysInfo.windowHeight || 600"
|
||||
:data-windowWidth="sysInfo.windowWidth || 400" :data-imgTop="imgTop" :data-imgLeft="imgLeft"
|
||||
:data-imgWidth="imgWidth" :data-imgHeight="imgHeight" :data-angle="angle"
|
||||
:style="{ borderRadius: isRound ? '50%' : '0', border: border }"></view>
|
||||
</view>
|
||||
<canvas canvas-id="tui-image__cropper" id="tui-image__cropper" :disable-scroll="true"
|
||||
:style="{ width: width * scaleRatio + 'px', height: height * scaleRatio + 'px' }"
|
||||
class="tui-cropper__canvas"></canvas>
|
||||
<view class="tui-cropper__tabbar" v-if="!custom">
|
||||
<view class="tui-op__btn" @tap.stop="back">取消</view>
|
||||
<image :src="rotateImg" class="tui-rotate__img" @tap="setAngle"></image>
|
||||
<view class="tui-op__btn" @tap.stop="getImage">完成</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script src="./tui-cropper.wxs" module="parse" lang="wxs"></script>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiCropper',
|
||||
emits: ['ready', 'cropper', 'imageLoad', 'initAngle'],
|
||||
props: {
|
||||
//图片路径
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//裁剪框高度 px
|
||||
height: {
|
||||
type: Number,
|
||||
default: 280
|
||||
},
|
||||
//裁剪框宽度 px
|
||||
width: {
|
||||
type: Number,
|
||||
default: 280
|
||||
},
|
||||
//是否为圆形裁剪框
|
||||
isRound: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//裁剪框边框
|
||||
border: {
|
||||
type: String,
|
||||
default: '1px solid #fff'
|
||||
},
|
||||
//生成的图片尺寸相对剪裁框的比例
|
||||
scaleRatio: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
//图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 0.8
|
||||
},
|
||||
//图片旋转角度
|
||||
rotateAngle: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//图片最小缩放比
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: 0.5
|
||||
},
|
||||
//图片最大缩放比
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
//自定义操作栏(为true时隐藏底部操作栏)
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//值发生改变开始裁剪(custom为true时生效)
|
||||
startCutting: {
|
||||
type: [Number, Boolean],
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 是否返回base64(H5端默认base64)
|
||||
* 支持平台:App,微信小程序,支付宝小程序,H5(默认url就是base64)
|
||||
**/
|
||||
isBase64: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//裁剪时是否显示loadding
|
||||
loadding: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//旋转icon
|
||||
rotateImg: {
|
||||
type: String,
|
||||
default: '/static/components/cropper/img_rotate.png'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
TIME_CUT_CENTER: null,
|
||||
cutX: 0, //画布x轴起点
|
||||
cutY: 0, //画布y轴起点0
|
||||
imgWidth: 0,
|
||||
imgHeight: 0,
|
||||
scale: 1, //图片缩放比
|
||||
angle: 0, //图片旋转角度
|
||||
animation: false, //是否开启图片过渡效果
|
||||
animationTime: null,
|
||||
imgTop: 0,
|
||||
imgLeft: 0,
|
||||
ctx: null,
|
||||
sysInfo: {},
|
||||
props: '',
|
||||
sizeChange: 0, //2
|
||||
angleChange: 0, //3
|
||||
resetChange: 0, //4
|
||||
centerChange: 0 //5
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
//定义变量然后利用change触发
|
||||
imageUrl(val, oldVal) {
|
||||
this.imageReset();
|
||||
this.showLoading();
|
||||
uni.getImageInfo({
|
||||
src: val,
|
||||
success: res => {
|
||||
//计算图片尺寸
|
||||
this.imgComputeSize(res.width, res.height);
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
},
|
||||
fail: err => {
|
||||
this.imgComputeSize();
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
rotateAngle(val) {
|
||||
this.animation = true;
|
||||
this.angle = val;
|
||||
this.angleChanged(val);
|
||||
},
|
||||
animation(val) {
|
||||
//开启过渡220毫秒之后自动关闭
|
||||
clearTimeout(this.animationTime);
|
||||
if (val) {
|
||||
this.animationTime = setTimeout(() => {
|
||||
this.animation = false;
|
||||
}, 220);
|
||||
}
|
||||
},
|
||||
startCutting(val) {
|
||||
if (this.custom && val) {
|
||||
this.getImage();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.sysInfo = uni.getSystemInfoSync();
|
||||
this.imgTop = this.sysInfo.windowHeight / 2;
|
||||
this.imgLeft = this.sysInfo.windowWidth / 2;
|
||||
this.ctx = uni.createCanvasContext('tui-image__cropper', this);
|
||||
//初始化
|
||||
setTimeout(() => {
|
||||
this.props = '1,1';
|
||||
}, 0);
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.$emit('ready', {});
|
||||
}, 200);
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
//网络图片转成本地文件[同步执行]
|
||||
async getLocalImage(url) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url: url,
|
||||
success: res => {
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: res => {
|
||||
reject(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
//返回裁剪后图片信息
|
||||
getImage() {
|
||||
if (!this.imageUrl) {
|
||||
uni.showToast({
|
||||
title: '请选择图片',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.loadding && this.showLoading();
|
||||
let draw = async () => {
|
||||
//图片实际大小
|
||||
let imgWidth = this.imgWidth * this.scale * this.scaleRatio;
|
||||
let imgHeight = this.imgHeight * this.scale * this.scaleRatio;
|
||||
//canvas和图片的相对距离
|
||||
let xpos = this.imgLeft - this.cutX;
|
||||
let ypos = this.imgTop - this.cutY;
|
||||
//旋转画布
|
||||
this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio);
|
||||
this.ctx.rotate((this.angle * Math.PI) / 180);
|
||||
let imgUrl = this.imageUrl;
|
||||
// #ifdef APP-PLUS || MP-WEIXIN
|
||||
if (~this.imageUrl.indexOf('https:')) {
|
||||
imgUrl = await this.getLocalImage(this.imageUrl);
|
||||
}
|
||||
// #endif
|
||||
this.ctx.drawImage(imgUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
|
||||
this.ctx.draw(false, () => {
|
||||
let params = {
|
||||
width: this.width * this.scaleRatio,
|
||||
height: Math.round(this.height * this.scaleRatio),
|
||||
destWidth: this.width * this.scaleRatio,
|
||||
destHeight: Math.round(this.height) * this.scaleRatio,
|
||||
fileType: 'png',
|
||||
quality: this.quality
|
||||
};
|
||||
let data = {
|
||||
url: '',
|
||||
base64: '',
|
||||
width: this.width * this.scaleRatio,
|
||||
height: this.height * this.scaleRatio
|
||||
};
|
||||
// #ifdef MP-ALIPAY
|
||||
|
||||
if (this.isBase64) {
|
||||
this.ctx.toDataURL(params).then(dataURL => {
|
||||
data.base64 = dataURL;
|
||||
this.loadding && uni.hideLoading();
|
||||
this.$emit('cropper', data);
|
||||
});
|
||||
} else {
|
||||
this.ctx.toTempFilePath({
|
||||
...params,
|
||||
success: res => {
|
||||
data.url = res.apFilePath;
|
||||
this.loadding && uni.hideLoading();
|
||||
this.$emit('cropper', data);
|
||||
}
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-ALIPAY
|
||||
let isBase64 = this.isBase64
|
||||
// #ifdef MP-BAIDU || MP-TOUTIAO || H5
|
||||
isBase64 = false;
|
||||
// #endif
|
||||
if (isBase64) {
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.canvasGetImageData({
|
||||
canvasId: 'tui-image__cropper',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.width * this.scaleRatio,
|
||||
height: Math.round(this.height * this.scaleRatio),
|
||||
success: res => {
|
||||
const arrayBuffer = new Uint8Array(res.data);
|
||||
const base64 = uni.arrayBufferToBase64(arrayBuffer);
|
||||
data.base64 = base64;
|
||||
this.loadding && uni.hideLoading();
|
||||
this.$emit('cropper', data);
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.canvasToTempFilePath({
|
||||
...params,
|
||||
canvasId: 'tui-image__cropper',
|
||||
success: res => {
|
||||
data.url = res.tempFilePath;
|
||||
const fs = wx.getFileSystemManager()
|
||||
// 读取文件, base64 格式
|
||||
const base64Str = fs.readFileSync(res.tempFilePath,
|
||||
'base64')
|
||||
const imgBase64 = `data:image/png;base64,${base64Str}`
|
||||
data.base64 = imgBase64;
|
||||
this.loadding && uni.hideLoading();
|
||||
this.$emit('cropper', data);
|
||||
},
|
||||
fail(res) {
|
||||
console.log(res);
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
// #endif
|
||||
} else {
|
||||
uni.canvasToTempFilePath({
|
||||
...params,
|
||||
canvasId: 'tui-image__cropper',
|
||||
success: res => {
|
||||
data.url = res.tempFilePath;
|
||||
// #ifdef H5
|
||||
data.base64 = res.tempFilePath;
|
||||
// #endif
|
||||
this.loadding && uni.hideLoading();
|
||||
this.$emit('cropper', data);
|
||||
},
|
||||
fail(res) {
|
||||
console.log(res);
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
// #endif
|
||||
});
|
||||
};
|
||||
draw();
|
||||
},
|
||||
change(e) {
|
||||
this.cutX = e.cutX || 0;
|
||||
this.cutY = e.cutY || 0;
|
||||
this.imgWidth = e.imgWidth || this.imgWidth;
|
||||
this.imgHeight = e.imgHeight || this.imgHeight;
|
||||
this.scale = e.scale || 1;
|
||||
this.angle = e.angle || 0;
|
||||
this.imgTop = e.imgTop || 0;
|
||||
this.imgLeft = e.imgLeft || 0;
|
||||
},
|
||||
imageReset() {
|
||||
this.scale = 1;
|
||||
this.angle = 0;
|
||||
let sys = this.sysInfo.windowHeight ? this.sysInfo : uni.getSystemInfoSync();
|
||||
this.imgTop = sys.windowHeight / 2;
|
||||
this.imgLeft = sys.windowWidth / 2;
|
||||
this.resetChange++;
|
||||
this.props = `4,${this.resetChange}`;
|
||||
//初始化旋转角度 0deg
|
||||
this.$emit('initAngle', {});
|
||||
},
|
||||
imgComputeSize(width, height) {
|
||||
//默认按图片最小边 = 对应裁剪框尺寸
|
||||
let imgWidth = width,
|
||||
imgHeight = height;
|
||||
if (imgWidth && imgHeight) {
|
||||
if (imgWidth / imgHeight > this.width / this.height) {
|
||||
imgHeight = this.height;
|
||||
imgWidth = (width / height) * imgHeight;
|
||||
} else {
|
||||
imgWidth = this.width;
|
||||
imgHeight = (height / width) * imgWidth;
|
||||
}
|
||||
} else {
|
||||
let sys = this.sysInfo.windowHeight ? this.sysInfo : uni.getSystemInfoSync();
|
||||
imgWidth = sys.windowWidth;
|
||||
imgHeight = 0;
|
||||
}
|
||||
this.imgWidth = imgWidth;
|
||||
this.imgHeight = imgHeight;
|
||||
this.sizeChange++;
|
||||
this.props = `2,${this.sizeChange}`;
|
||||
},
|
||||
imageLoad(e) {
|
||||
this.imageReset();
|
||||
uni.hideLoading();
|
||||
this.$emit('imageLoad', {});
|
||||
},
|
||||
moveStop() {
|
||||
clearTimeout(this.TIME_CUT_CENTER);
|
||||
this.TIME_CUT_CENTER = setTimeout(() => {
|
||||
this.centerChange++;
|
||||
this.props = `5,${this.centerChange}`;
|
||||
}, 666);
|
||||
},
|
||||
moveDuring() {
|
||||
clearTimeout(this.TIME_CUT_CENTER);
|
||||
},
|
||||
showLoading() {
|
||||
uni.showLoading({
|
||||
title: '请稍候...',
|
||||
mask: true
|
||||
});
|
||||
},
|
||||
stop() {},
|
||||
back() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
angleChanged(val) {
|
||||
this.moveStop();
|
||||
if (val % 90) {
|
||||
this.angle = Math.round(val / 90) * 90;
|
||||
}
|
||||
this.angleChange++;
|
||||
this.props = `3,${this.angleChange}`;
|
||||
},
|
||||
setAngle() {
|
||||
this.animation = true;
|
||||
this.angle = this.angle + 90;
|
||||
this.angleChanged(this.angle);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-cropper__box {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tui-cropper__image {
|
||||
width: 100%;
|
||||
border-style: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.tui-cropper__image-hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tui-cropper__canvas {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
left: -2000px;
|
||||
top: -2000px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-backdrop__cropper {
|
||||
position: fixed;
|
||||
z-index: 4;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 3000px solid rgba(0, 0, 0, 0.55);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-cropper__border {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-cropper__tabbar {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
padding: 0 40rpx;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.tui-cropper__tabbar::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
}
|
||||
|
||||
.tui-op__btn {
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-rotate__img {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
</style>
|
||||
321
src/components/thorui/tui-cropper/tui-cropper.wxs
Normal file
321
src/components/thorui/tui-cropper/tui-cropper.wxs
Normal file
@ -0,0 +1,321 @@
|
||||
var cropper = {
|
||||
cutX: 0, //画布x轴起点
|
||||
cutY: 0, //画布y轴起点0
|
||||
touchRelative: [{
|
||||
x: 0,
|
||||
y: 0
|
||||
}], //手指或鼠标和图片中心的相对位置
|
||||
hypotenuseLength: 0, //双指触摸时斜边长度
|
||||
flagEndTouch: false, //是否结束触摸
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
imgWidth: 0, //图片宽度
|
||||
imgHeight: 0, //图片高度
|
||||
scale: 1, //图片缩放比
|
||||
angle: 0, //图片旋转角度
|
||||
imgTop: 0, //图片上边距
|
||||
imgLeft: 0, //图片左边距
|
||||
windowHeight: 0,
|
||||
windowWidth: 0,
|
||||
init: true
|
||||
}
|
||||
|
||||
function bool(str) {
|
||||
return str === 'true' || str == true ? true : false
|
||||
}
|
||||
|
||||
function touchstart(e, ins) {
|
||||
var touch = e.touches || e.changedTouches;
|
||||
cropper.flagEndTouch = false;
|
||||
if (touch.length == 1) {
|
||||
cropper.touchRelative[0] = {
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
};
|
||||
} else {
|
||||
var width = Math.abs(touch[0].pageX - touch[1].pageX);
|
||||
var height = Math.abs(touch[0].pageY - touch[1].pageY);
|
||||
cropper.touchRelative = [{
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
},
|
||||
{
|
||||
x: touch[1].pageX - cropper.imgLeft,
|
||||
y: touch[1].pageY - cropper.imgTop
|
||||
}
|
||||
];
|
||||
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function moveDuring(ins) {
|
||||
if (!ins) return;
|
||||
ins.callMethod('moveDuring')
|
||||
}
|
||||
|
||||
function moveStop(ins) {
|
||||
if (!ins) return;
|
||||
ins.callMethod('moveStop')
|
||||
};
|
||||
|
||||
function setCutCenter(ins) {
|
||||
var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5;
|
||||
var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5;
|
||||
//顺序不能变
|
||||
cropper.imgTop = cropper.imgTop - cropper.cutY + cutY;
|
||||
cropper.cutY = cutY; //截取的框上边距
|
||||
cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX;
|
||||
cropper.cutX = cutX; //截取的框左边距
|
||||
cutDetectionPosition(ins)
|
||||
imgTransform(ins)
|
||||
updateData(ins)
|
||||
}
|
||||
|
||||
function touchmove(e, ins) {
|
||||
var touch = e.touches || e.changedTouches;
|
||||
if (cropper.flagEndTouch) return;
|
||||
moveDuring(ins);
|
||||
if (e.touches.length == 1) {
|
||||
var left = touch[0].pageX - cropper.touchRelative[0].x,
|
||||
top = touch[0].pageY - cropper.touchRelative[0].y;
|
||||
cropper.imgLeft = left;
|
||||
cropper.imgTop = top;
|
||||
imgTransform(ins);
|
||||
imgMarginDetectionPosition(ins);
|
||||
} else {
|
||||
var res = e.instance.getDataset();
|
||||
var minScale = +res.minscale;
|
||||
var maxScale = +res.maxscale;
|
||||
var width = Math.abs(touch[0].pageX - touch[1].pageX),
|
||||
height = Math.abs(touch[0].pageY - touch[1].pageY),
|
||||
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
|
||||
scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
|
||||
current_deg = 0;
|
||||
scale = scale <= minScale ? minScale : scale;
|
||||
scale = scale >= maxScale ? maxScale : scale;
|
||||
cropper.scale = scale;
|
||||
imgMarginDetectionScale(ins, true);
|
||||
var touchRelative = [{
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
},
|
||||
{
|
||||
x: touch[1].pageX - cropper.imgLeft,
|
||||
y: touch[1].pageY - cropper.imgTop
|
||||
}
|
||||
];
|
||||
cropper.touchRelative = touchRelative;
|
||||
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||
//更新视图
|
||||
cropper.angle = cropper.angle + current_deg;
|
||||
imgTransform(ins);
|
||||
}
|
||||
}
|
||||
|
||||
function touchend(e, ins) {
|
||||
cropper.flagEndTouch = true;
|
||||
moveStop(ins);
|
||||
updateData(ins)
|
||||
}
|
||||
|
||||
|
||||
//检测剪裁框位置是否在允许的范围内(屏幕内)
|
||||
function cutDetectionPosition(ins) {
|
||||
var windowHeight = cropper.windowHeight,
|
||||
windowWidth = cropper.windowWidth;
|
||||
|
||||
var cutDetectionPositionTop = function() {
|
||||
//检测上边距是否在范围内
|
||||
if (cropper.cutY < 0) {
|
||||
cropper.cutY = 0;
|
||||
}
|
||||
if (cropper.cutY > windowHeight - cropper.canvasHeight) {
|
||||
cropper.cutY = windowHeight - cropper.canvasHeight;
|
||||
}
|
||||
}
|
||||
|
||||
var cutDetectionPositionLeft = function() {
|
||||
//检测左边距是否在范围内
|
||||
if (cropper.cutX < 0) {
|
||||
cropper.cutX = 0;
|
||||
}
|
||||
if (cropper.cutX > windowWidth - cropper.canvasWidth) {
|
||||
cropper.cutX = windowWidth - cropper.canvasWidth;
|
||||
}
|
||||
}
|
||||
//裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
|
||||
if (cropper.cutY == null && cropper.cutX == null) {
|
||||
var cutY = (windowHeight - cropper.canvasHeight) * 0.5;
|
||||
var cutX = (windowWidth - cropper.canvasWidth) * 0.5;
|
||||
cropper.cutY = cutY; //截取的框上边距
|
||||
cropper.cutX = cutX; //截取的框左边距
|
||||
} else if (cropper.cutY != null && cropper.cutX != null) {
|
||||
cutDetectionPositionTop();
|
||||
cutDetectionPositionLeft();
|
||||
} else if (cropper.cutY != null && cropper.cutX == null) {
|
||||
cutDetectionPositionTop();
|
||||
cropper.cutX = (windowWidth - cropper.canvasWidth) / 2;
|
||||
} else if (cropper.cutY == null && cropper.cutX != null) {
|
||||
cutDetectionPositionLeft();
|
||||
cropper.cutY = (windowHeight - cropper.canvasHeight) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片边缘检测-缩放
|
||||
*/
|
||||
function imgMarginDetectionScale(ins, delay) {
|
||||
var scale = cropper.scale;
|
||||
var imgWidth = cropper.imgWidth;
|
||||
var imgHeight = cropper.imgHeight;
|
||||
if ((cropper.angle / 90) % 2) {
|
||||
imgWidth = cropper.imgHeight;
|
||||
imgHeight = cropper.imgWidth;
|
||||
}
|
||||
if (imgWidth * scale < cropper.canvasWidth) {
|
||||
scale = cropper.canvasWidth / imgWidth;
|
||||
}
|
||||
if (imgHeight * scale < cropper.canvasHeight) {
|
||||
scale = Math.max(scale, cropper.canvasHeight / imgHeight);
|
||||
}
|
||||
imgMarginDetectionPosition(ins, scale, delay);
|
||||
}
|
||||
/**
|
||||
* 图片边缘检测-位置
|
||||
*/
|
||||
function imgMarginDetectionPosition(ins, scale, delay) {
|
||||
var left = cropper.imgLeft;
|
||||
var top = cropper.imgTop;
|
||||
scale = scale || cropper.scale;
|
||||
var imgWidth = cropper.imgWidth;
|
||||
var imgHeight = cropper.imgHeight;
|
||||
if ((cropper.angle / 90) % 2) {
|
||||
imgWidth = cropper.imgHeight;
|
||||
imgHeight = cropper.imgWidth;
|
||||
}
|
||||
left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2;
|
||||
left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper.canvasWidth -
|
||||
(imgWidth * scale) / 2;
|
||||
top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2;
|
||||
top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper.canvasHeight -
|
||||
(imgHeight * scale) / 2;
|
||||
|
||||
cropper.imgLeft = left;
|
||||
cropper.imgTop = top;
|
||||
cropper.scale = scale;
|
||||
if (!delay || delay === 'null') {
|
||||
imgTransform(ins);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//改变截取框大小
|
||||
function computeCutSize(ins) {
|
||||
if (cropper.canvasWidth > cropper.windowWidth) {
|
||||
cropper.canvasWidth = cropper.windowWidth;
|
||||
} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
|
||||
cropper.cutX = cropper.windowWidth - cropper.cutX;
|
||||
}
|
||||
if (cropper.canvasHeight > cropper.windowHeight) {
|
||||
cropper.canvasHeight = cropper.windowHeight;
|
||||
} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
|
||||
cropper.cutY = cropper.windowHeight - cropper.cutY;
|
||||
}
|
||||
}
|
||||
|
||||
function imgTransform(ins) {
|
||||
var owner = ins.selectComponent('.tui-cropper__image')
|
||||
if (!owner) return
|
||||
var x = cropper.imgLeft - cropper.imgWidth / 2;
|
||||
var y = cropper.imgTop - cropper.imgHeight / 2;
|
||||
owner.setStyle({
|
||||
'transform': 'translate3d(' + x + 'px,' + y + 'px,0) scale(' + cropper.scale + ') rotate(' + cropper.angle + 'deg)'
|
||||
})
|
||||
}
|
||||
|
||||
function imageReset(ins) {
|
||||
cropper.scale = 1;
|
||||
cropper.angle = 0;
|
||||
imgTransform(ins);
|
||||
}
|
||||
//监听截取框宽高变化
|
||||
function canvasWidth(ins) {
|
||||
if (!ins) return;
|
||||
computeCutSize(ins);
|
||||
}
|
||||
|
||||
function canvasHeight(ins) {
|
||||
if (!ins) return;
|
||||
computeCutSize(ins);
|
||||
}
|
||||
|
||||
function updateData(ins) {
|
||||
if (!ins) return;
|
||||
ins.callMethod('change', {
|
||||
cutX: cropper.cutX,
|
||||
cutY: cropper.cutY,
|
||||
imgWidth: cropper.imgWidth,
|
||||
imgHeight: cropper.imgHeight,
|
||||
scale: cropper.scale,
|
||||
angle: cropper.angle,
|
||||
imgTop: cropper.imgTop,
|
||||
imgLeft: cropper.imgLeft
|
||||
})
|
||||
}
|
||||
|
||||
function propsChange(prop, oldProp, ownerInstance, ins) {
|
||||
if (prop && prop !== 'null') {
|
||||
var params = prop.split(',')
|
||||
var type = +params[0]
|
||||
var dataset = ins.getDataset();
|
||||
if (cropper.init || type == 4) {
|
||||
cropper.canvasWidth = +dataset.width;
|
||||
cropper.canvasHeight = +dataset.height;
|
||||
cropper.imgTop = dataset.windowheight / 2;
|
||||
cropper.imgLeft = dataset.windowwidth / 2;
|
||||
cropper.imgWidth = +dataset.imgwidth;
|
||||
cropper.imgHeight = +dataset.imgheight;
|
||||
cropper.windowHeight = +dataset.windowheight;
|
||||
cropper.windowWidth = +dataset.windowwidth;
|
||||
cropper.init = false
|
||||
} else if (type == 2 || type == 3) {
|
||||
cropper.imgWidth = +dataset.imgwidth;
|
||||
cropper.imgHeight = +dataset.imgheight;
|
||||
}
|
||||
cropper.angle = +dataset.angle;
|
||||
if (type == 3) {
|
||||
imgTransform(ownerInstance);
|
||||
}
|
||||
switch (type) {
|
||||
case 1:
|
||||
setCutCenter(ownerInstance);
|
||||
//设置裁剪框大小>设置图片尺寸>绘制canvas
|
||||
computeCutSize(ownerInstance);
|
||||
//检查裁剪框是否在范围内
|
||||
cutDetectionPosition(ownerInstance);
|
||||
break;
|
||||
case 2:
|
||||
setCutCenter(ownerInstance);
|
||||
break;
|
||||
case 3:
|
||||
imgMarginDetectionScale(ownerInstance)
|
||||
break;
|
||||
case 4:
|
||||
imageReset(ownerInstance);
|
||||
break;
|
||||
case 5:
|
||||
setCutCenter(ownerInstance);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchstart: touchstart,
|
||||
touchmove: touchmove,
|
||||
touchend: touchend,
|
||||
propsChange: propsChange
|
||||
}
|
||||
196
src/components/thorui/tui-cubic-bezier/tui-cubic-bezier.vue
Normal file
196
src/components/thorui/tui-cubic-bezier/tui-cubic-bezier.vue
Normal file
@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<view class="tui-cubic__bezier" :class="{ 'tui-animate__bezier': animate }" :style="{ left: left, top: top, transform: transform_x }" @tap.stop="handleClick">
|
||||
<view
|
||||
class="tui-badge__bezier"
|
||||
:class="{ 'tui-animate__bezier_y': animate }"
|
||||
:style="{ width: width, height: height, borderRadius: height, backgroundColor: getBgColor, transform: transform_y }"
|
||||
>
|
||||
<text v-if="!custom">1</text>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiCubicBezier',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//屏幕宽度[总宽度] px
|
||||
windowWidth: {
|
||||
type: Number,
|
||||
default: 375
|
||||
},
|
||||
//屏幕高度[总高度] px
|
||||
windowHeight: {
|
||||
type: Number,
|
||||
default: 667
|
||||
},
|
||||
//badge宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '40rpx'
|
||||
},
|
||||
//badge高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '40rpx'
|
||||
},
|
||||
//动画badge背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//badge left
|
||||
left: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//badge top
|
||||
top: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//是否自定义动画显示内容
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//动画方向: up-右上(top,right),down-右下(right,bottom), bottom-左下(left,bottom),top-左上(left,top)
|
||||
//括号中为position必传值
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'down'
|
||||
},
|
||||
//根据方向传值,终点的坐标点,一般为元素中心位置
|
||||
position: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
top: 50,
|
||||
bottom: 80,
|
||||
left: 30,
|
||||
right: 40
|
||||
};
|
||||
}
|
||||
},
|
||||
//列表中索引 index
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//自定义参数
|
||||
params: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
getBgColor(){
|
||||
return this.backgroundColor || (uni && uni.$tui && uni.$tui.color.danger) || '#EB0909'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
time: 0,
|
||||
animate: false,
|
||||
transform_x: '',
|
||||
transform_y: '',
|
||||
sys: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleClick(e) {
|
||||
//550ms内防止重复点击
|
||||
if (new Date().getTime() - this.time <= 550) return;
|
||||
this.time = new Date().getTime();
|
||||
setTimeout(() => {
|
||||
this.time = 0;
|
||||
}, 550);
|
||||
this.bezierEffect(e);
|
||||
this.$emit('click', {
|
||||
index: this.index,
|
||||
params: this.params
|
||||
});
|
||||
},
|
||||
bezierEffect(e) {
|
||||
let touch = null;
|
||||
// #ifdef MP-ALIPAY
|
||||
touch = e.detail;
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-BAIDU
|
||||
touch = e.changedTouches[0]
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-ALIPAY || MP-BAIDU
|
||||
touch = e.touches[0];
|
||||
// #endif
|
||||
let diff = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
//up-右上,down-右下, bottom-左下,top-左上
|
||||
switch (this.direction) {
|
||||
case 'up':
|
||||
diff.x = this.windowWidth - touch.clientX - this.position.right;
|
||||
diff.y = this.position.top - touch.clientY;
|
||||
break;
|
||||
case 'down':
|
||||
diff.x = this.windowWidth - touch.clientX - this.position.right;
|
||||
diff.y = this.windowHeight - touch.clientY - this.position.bottom;
|
||||
break;
|
||||
case 'bottom':
|
||||
diff.x = this.position.left - touch.clientX;
|
||||
diff.y = this.windowHeight - touch.clientY - this.position.bottom;
|
||||
break;
|
||||
case 'top':
|
||||
diff.x = this.position.left - touch.clientX;
|
||||
diff.y = this.position.top - touch.clientY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//移动距离
|
||||
this.animate = true;
|
||||
this.transform_x = `translate3d(${diff.x}px,0,0)`;
|
||||
this.transform_y = `translate3d(0,${diff.y}px,0) rotate(350deg) scale(0.8)`;
|
||||
setTimeout(() => {
|
||||
this.animate = false;
|
||||
//恢复到最初状态
|
||||
this.transform_x = 'none';
|
||||
this.transform_y = 'none';
|
||||
}, 550);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-cubic__bezier {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.tui-animate__bezier {
|
||||
opacity: 1;
|
||||
z-index: 990;
|
||||
transition: opacity 0.1s, transform cubic-bezier(0, 0, 0, 0) 0.5s;
|
||||
}
|
||||
|
||||
.tui-animate__bezier_y {
|
||||
opacity: 1;
|
||||
z-index: 990;
|
||||
transition: opacity 0.1s, transform cubic-bezier(0.3, -0.2, 1, 0) 0.5s;
|
||||
}
|
||||
|
||||
.tui-badge__bezier {
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
</style>
|
||||
321
src/components/thorui/tui-data-checkbox/tui-data-checkbox.vue
Normal file
321
src/components/thorui/tui-data-checkbox/tui-data-checkbox.vue
Normal file
@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<view class="tui-data-checkbox" :style="{marginBottom:'-'+gap+'rpx'}">
|
||||
<view class="tui-data__cb-item" :class="{'tui-data__cb-disabled':item.disable}"
|
||||
:style="{width:width?width+'rpx':'auto',padding:padding,borderRadius:radius,marginRight:gap+'rpx',marginBottom:gap+'rpx',background:item.checked?activeBgColor:background,borderColor:item.checked?getBorderColor:(defaultBorderColor || background)}"
|
||||
v-for="(item,index) in itemList" :key="index" @tap.stop="itemTap(index)">
|
||||
<text class="tui-cb__text"
|
||||
:style="{fontSize:size+'rpx',lineHeight:size+'rpx',color:item.checked?getActiveColor:color}">{{item[textField]}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-data-checkbox",
|
||||
emits: ['change', 'input', 'update:modelValue'],
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
textField: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
disableField: {
|
||||
type: String,
|
||||
default: 'disable'
|
||||
},
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
value: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//最小选择数,仅单选时有效,可选值0、1
|
||||
min: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//最大选择数
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: -1
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '20rpx 32rpx'
|
||||
},
|
||||
gap: {
|
||||
type: [Number, String],
|
||||
default: 20
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: '8rpx'
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 24
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
activeBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(86, 119, 252, 0.1)'
|
||||
},
|
||||
defaultBorderColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getActiveColor() {
|
||||
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
},
|
||||
getBorderColor() {
|
||||
return this.borderColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options(vals) {
|
||||
//value,text,disable
|
||||
this.initData(vals)
|
||||
},
|
||||
// #ifdef VUE3
|
||||
modelValue(vals) {
|
||||
this.modelChange(vals)
|
||||
},
|
||||
// #endif
|
||||
value(vals) {
|
||||
this.modelChange(vals)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initData(this.options)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
itemList: [],
|
||||
singleVal: '',
|
||||
vals: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initData(vals) {
|
||||
vals = JSON.parse(JSON.stringify(vals))
|
||||
if (vals && vals.length > 0) {
|
||||
if (typeof vals[0] !== 'object') {
|
||||
vals = vals.map((item, index) => {
|
||||
return {
|
||||
[this.textField]: item,
|
||||
[this.valueField]: item,
|
||||
checked: false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
vals.map((item, index) => {
|
||||
item.checked = item.checked || false
|
||||
if (item[this.valueField] === undefined) {
|
||||
item[this.valueField] = item[this.textField]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.itemList = vals;
|
||||
// #ifdef VUE3
|
||||
this.modelChange(this.modelValue)
|
||||
// #endif
|
||||
|
||||
// #ifndef VUE3
|
||||
this.modelChange(this.value)
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
emitsChange(e) {
|
||||
this.$emit('change', e)
|
||||
this.$emit('input', e.detail.value)
|
||||
// #ifdef VUE3
|
||||
this.$emit("update:modelValue", e.detail.value);
|
||||
// #endif
|
||||
},
|
||||
radioChange(index, model) {
|
||||
let min = Number(this.min)
|
||||
if (this.singleVal === model[this.valueField] && min > 0) return;
|
||||
let val = '';
|
||||
let i = index
|
||||
this.itemList.forEach((item, idx) => {
|
||||
if (idx === index) {
|
||||
const bool = this.singleVal === item[this.valueField] && min <= 0
|
||||
val = bool ? '' : item[this.valueField]
|
||||
i = bool ? -1 : index
|
||||
item.checked = bool ? false : true
|
||||
} else {
|
||||
item.checked = false
|
||||
}
|
||||
})
|
||||
this.singleVal = val
|
||||
let e = {
|
||||
detail: {
|
||||
index: i,
|
||||
value: val
|
||||
}
|
||||
}
|
||||
this.emitsChange(e)
|
||||
},
|
||||
checkboxChange(index, model) {
|
||||
let max = Number(this.max)
|
||||
let vals = this.vals
|
||||
let i = vals.indexOf(model[this.valueField])
|
||||
if (vals.length >= max && max !== -1 && i === -1) {
|
||||
uni.showToast({
|
||||
title: '已超出最大选择数!',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
this.itemList.forEach((item, idx) => {
|
||||
if (idx === index) {
|
||||
item.checked = i !== -1 ? false : true
|
||||
if (item.checked) {
|
||||
vals.push(item[this.valueField])
|
||||
} else {
|
||||
vals.splice(i, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
this.vals = vals
|
||||
let e = {
|
||||
detail: {
|
||||
value: vals
|
||||
}
|
||||
}
|
||||
this.emitsChange(e)
|
||||
},
|
||||
itemTap(index) {
|
||||
let item = this.itemList[index]
|
||||
if (item[this.disableField]) return;
|
||||
if (this.multiple) {
|
||||
this.checkboxChange(index, item)
|
||||
} else {
|
||||
this.radioChange(index, item)
|
||||
}
|
||||
},
|
||||
modelChange(vals) {
|
||||
if (this.multiple) {
|
||||
this.itemList.forEach(item => {
|
||||
if (vals.includes(item[this.valueField])) {
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false
|
||||
}
|
||||
})
|
||||
this.vals = vals
|
||||
|
||||
} else {
|
||||
this.itemList.forEach(item => {
|
||||
if (vals == item[this.valueField]) {
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false
|
||||
}
|
||||
})
|
||||
this.singleVal = vals
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-data-checkbox {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-data__cb-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-cb__text {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tui-data__cb-disabled {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.tui-cb-checkmark {
|
||||
width: 20rpx;
|
||||
height: 40rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 3px;
|
||||
border-bottom-color: #FFFFFF;
|
||||
border-right-style: solid;
|
||||
border-right-width: 3px;
|
||||
border-right-color: #FFFFFF;
|
||||
transform: rotate(45deg) scale(0.5);
|
||||
transform-origin: 54% 48%;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
</style>
|
||||
775
src/components/thorui/tui-datetime/tui-datetime.vue
Normal file
775
src/components/thorui/tui-datetime/tui-datetime.vue
Normal file
@ -0,0 +1,775 @@
|
||||
<template>
|
||||
<view class="tui-datetime-picker" :style="{zIndex}">
|
||||
<view class="tui-datetime__mask" :class="{ 'tui-datetime__mask-show': isShow }" :style="{zIndex:getMaskZIndex}"
|
||||
@touchmove.stop.prevent="stop" catchtouchmove="stop" @tap="maskClick"></view>
|
||||
<view class="tui-datetime__header" :class="{ 'tui-show': isShow }" :style="{zIndex:getPickerZIndex}">
|
||||
<view class="tui-picker-header" :class="{ 'tui-date-radius': radius }"
|
||||
:style="{ backgroundColor: headerBackground }" @touchmove.stop.prevent="stop" catchtouchmove="stop">
|
||||
<view class="tui-btn-picker" :style="{ color: cancelColor }" hover-class="tui-opacity"
|
||||
:hover-stay-time="150" @tap="hide">取消</view>
|
||||
<view class="tui-pickerdate__title" :style="{fontSize:titleSize+'rpx',color:titleColor}">{{title}}
|
||||
</view>
|
||||
<view class="tui-btn-picker" :style="{ color: getColor }" hover-class="tui-opacity"
|
||||
:hover-stay-time="150" @tap="btnFix">确定</view>
|
||||
</view>
|
||||
<view class="tui-date-header" :style="{ backgroundColor: unitBackground }" v-if="unitTop">
|
||||
<view class="tui-date-unit" v-if="type < 4 || type == 7 || type==8">年</view>
|
||||
<view class="tui-date-unit" v-if="(type < 4 && type>0) || type == 7 || type==8">月</view>
|
||||
<view class="tui-date-unit" v-if="type == 1 || type == 2 || type == 7 || type==8">日</view>
|
||||
<view class="tui-date-unit" v-if="type == 1 || type == 4 || type == 5 || type == 7 || type==8">时</view>
|
||||
<view class="tui-date-unit" v-if="(type == 1 || type > 3) && type!=8">分</view>
|
||||
<view class="tui-date-unit" v-if="type > 4 && type !=8">秒</view>
|
||||
</view>
|
||||
<view @touchstart.stop="pickerstart" class="tui-date__picker-body"
|
||||
:style="{ backgroundColor: bodyBackground,height:height+'rpx' }">
|
||||
<picker-view :key="type" :immediate-change="immediate" :value="value" @change="change"
|
||||
class="tui-datetime__picker-view">
|
||||
<picker-view-column v-if="type < 4 || type == 7 || type==8">
|
||||
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||
v-for="(item, index) in years" :key="index">
|
||||
{{ item }}
|
||||
<text class="tui-date__unit-text" v-if="!unitTop">年</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="(type < 4 && type>0) || type == 7 || type==8">
|
||||
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||
v-for="(item, index) in months" :key="index">
|
||||
{{ formatNum(item) }}
|
||||
<text class="tui-date__unit-text" v-if="!unitTop">月</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="type == 1 || type == 2 || type == 7 || type==8">
|
||||
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||
v-for="(item, index) in days" :key="index">
|
||||
{{ formatNum(item) }}
|
||||
<text class="tui-date__unit-text" v-if="!unitTop">日</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="type == 1 || type == 4 || type == 5 || type == 7 || type==8">
|
||||
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||
v-for="(item, index) in hours" :key="index">
|
||||
{{ formatNum(item) }}
|
||||
<text class="tui-date__unit-text" v-if="!unitTop">时</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="(type == 1 || type > 3) && type!=8">
|
||||
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||
v-for="(item, index) in minutes" :key="index">
|
||||
{{ formatNum(item) }}
|
||||
<text class="tui-date__unit-text" v-if="!unitTop">分</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="type > 4 && type!=8">
|
||||
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||
v-for="(item, index) in seconds" :key="index">
|
||||
{{ formatNum(item) }}
|
||||
<text class="tui-date__unit-text" v-if="!unitTop">秒</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiDatetime',
|
||||
emits: ['cancel', 'confirm'],
|
||||
props: {
|
||||
//0-年 1-日期+时间(年月日+时分) 2-日期(年月日) 3-日期(年月) 4-时间(时分) 5-时分秒 6-分秒 7-年月日 时分秒 8-年月日+小时
|
||||
type: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//年份区间
|
||||
startYear: {
|
||||
type: Number,
|
||||
default: 1980
|
||||
},
|
||||
//年份区间
|
||||
endYear: {
|
||||
type: Number,
|
||||
default: 2050
|
||||
},
|
||||
hoursData: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
minutesData: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
secondsData: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//显示标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//标题字体大小
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
//标题字体颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//"取消"字体颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: '#888'
|
||||
},
|
||||
//"确定"字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//设置默认显示日期 2019-08-01 || 2019-08-01 17:01 || 2019/08/01
|
||||
setDateTime: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//单位置顶
|
||||
unitTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//圆角设置
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//头部背景色
|
||||
headerBackground: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//根据实际调整,不建议使用深颜色
|
||||
bodyBackground: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//单位置顶时,单位条背景色
|
||||
unitBackground: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 520
|
||||
},
|
||||
//点击遮罩 是否可关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 998
|
||||
}
|
||||
|
||||
},
|
||||
data() {
|
||||
let immediate = true;
|
||||
// #ifdef MP-TOUTIAO
|
||||
immediate = false
|
||||
// #endif
|
||||
return {
|
||||
immediate,
|
||||
isShow: false,
|
||||
years: [],
|
||||
months: [],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
value: [],
|
||||
isEnd: true,
|
||||
isChange: false,
|
||||
isSelect: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.initData();
|
||||
}, 20)
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
yearOrMonth() {
|
||||
return `${this.year}-${this.month}`;
|
||||
},
|
||||
propsChange() {
|
||||
return `${this.type}-${this.startYear}-${this.endYear}-${this.hoursData}-${this.minutesData}-${this.secondsData}`;
|
||||
},
|
||||
getColor() {
|
||||
return this.color || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getMaskZIndex() {
|
||||
return Number(this.zIndex) + 1
|
||||
},
|
||||
getPickerZIndex() {
|
||||
return Number(this.zIndex) + 2
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
yearOrMonth() {
|
||||
this.setDays();
|
||||
},
|
||||
propsChange() {
|
||||
if (this.isChange) return;
|
||||
this.isChange = true;
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.isChange = false;
|
||||
this.initData();
|
||||
}, 50);
|
||||
})
|
||||
},
|
||||
setDateTime(val) {
|
||||
if (val && val !== true) {
|
||||
setTimeout(() => {
|
||||
this.initData();
|
||||
}, 20);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
stop() {},
|
||||
formatNum: function(num) {
|
||||
return num < 10 ? '0' + num : num + '';
|
||||
},
|
||||
generateArray: function(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
getIndex: function(arr, val) {
|
||||
if (!arr || arr.length === 0 || val === undefined) return 0;
|
||||
let index = arr.indexOf(val);
|
||||
return index == -1 ? 0 : index;
|
||||
},
|
||||
getCharCount(str) {
|
||||
let regex = new RegExp('/', 'g');
|
||||
let result = str.match(regex);
|
||||
return !result ? 0 : result.length;
|
||||
},
|
||||
//日期时间处理
|
||||
initSelectValue() {
|
||||
let fdate = ''
|
||||
if (this.setDateTime && this.setDateTime !== true) {
|
||||
fdate = this.setDateTime.replace(/\-/g, '/');
|
||||
if (this.type == 3 && this.getCharCount(fdate) === 1) {
|
||||
fdate += '/01'
|
||||
} else if (this.type == 0) {
|
||||
fdate += '/01/01'
|
||||
}
|
||||
fdate = fdate && fdate.indexOf('/') == -1 ? `2023/01/01 ${fdate}` : fdate;
|
||||
}
|
||||
let time = null;
|
||||
if (fdate) time = new Date(fdate);
|
||||
else time = new Date();
|
||||
let year = time.getFullYear();
|
||||
if (year > this.endYear) {
|
||||
year = this.endYear
|
||||
} else if (year < this.startYear) {
|
||||
year = this.startYear
|
||||
}
|
||||
const month = time.getMonth() + 1;
|
||||
const day = time.getDate();
|
||||
const hour = time.getHours();
|
||||
const minute = time.getMinutes();
|
||||
const second = time.getSeconds();
|
||||
this.year = year;
|
||||
this.month = month;
|
||||
this.day = day;
|
||||
this.hour = hour;
|
||||
this.minute = minute;
|
||||
this.second = second;
|
||||
|
||||
return [year, month, day, hour, minute, second]
|
||||
},
|
||||
initData() {
|
||||
const def = this.initSelectValue();
|
||||
const type = Number(this.type)
|
||||
switch (type) {
|
||||
case 0:
|
||||
this.setYears();
|
||||
break;
|
||||
case 1:
|
||||
this.setYears();
|
||||
this.setMonths();
|
||||
this.setDays();
|
||||
this.setHours();
|
||||
this.setMinutes();
|
||||
break;
|
||||
case 2:
|
||||
this.setYears();
|
||||
this.setMonths();
|
||||
this.setDays();
|
||||
break;
|
||||
case 3:
|
||||
this.setYears();
|
||||
this.setMonths();
|
||||
break;
|
||||
case 4:
|
||||
this.setHours();
|
||||
this.setMinutes();
|
||||
break;
|
||||
case 5:
|
||||
this.setHours();
|
||||
this.setMinutes();
|
||||
this.setSeconds();
|
||||
break;
|
||||
case 6:
|
||||
this.setMinutes();
|
||||
this.setSeconds();
|
||||
break;
|
||||
case 7:
|
||||
this.setYears();
|
||||
this.setMonths();
|
||||
this.setDays();
|
||||
this.setHours();
|
||||
this.setMinutes();
|
||||
this.setSeconds();
|
||||
break;
|
||||
case 8:
|
||||
this.setYears();
|
||||
this.setMonths();
|
||||
this.setDays();
|
||||
this.setHours();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.setDefaultValues(def);
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
setDefaultValues(def) {
|
||||
let vals = []
|
||||
// 1-年月日+时分 2-年月日 3-年月 4-时分 5-时分秒 6-分秒 7-年月日 时分秒 8-年月日+小时
|
||||
const year = this.getIndex(this.years, def[0]);
|
||||
|
||||
const month = this.getIndex(this.months, def[1])
|
||||
const day = this.getIndex(this.days, def[2])
|
||||
const hour = this.getIndex(this.hours, def[3])
|
||||
const minute = this.getIndex(this.minutes, def[4])
|
||||
const second = this.getIndex(this.seconds, def[5])
|
||||
|
||||
// console.log(year, month, day, hour, minute, second)
|
||||
const type = Number(this.type)
|
||||
switch (type) {
|
||||
case 0:
|
||||
vals = [year]
|
||||
case 1:
|
||||
vals = [year, month, day, hour, minute]
|
||||
break;
|
||||
case 2:
|
||||
vals = [year, month, day]
|
||||
break;
|
||||
case 3:
|
||||
vals = [year, month]
|
||||
break;
|
||||
case 4:
|
||||
vals = [hour, minute]
|
||||
break;
|
||||
case 5:
|
||||
vals = [hour, minute, second]
|
||||
break;
|
||||
case 6:
|
||||
vals = [minute, second]
|
||||
break;
|
||||
case 7:
|
||||
vals = [year, month, day, hour, minute, second]
|
||||
break;
|
||||
case 8:
|
||||
vals = [year, month, day, hour]
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (this.value.join(',') === vals.join(',')) return;
|
||||
setTimeout(() => {
|
||||
this.value = vals;
|
||||
}, 150);
|
||||
|
||||
},
|
||||
setYears() {
|
||||
this.years = this.generateArray(this.startYear, this.endYear);
|
||||
},
|
||||
setMonths() {
|
||||
this.months = this.generateArray(1, 12);
|
||||
|
||||
},
|
||||
setDays() {
|
||||
if (this.type == 3 || this.type == 4) return;
|
||||
let totalDays = new Date(this.year, this.month, 0).getDate();
|
||||
totalDays = !totalDays || totalDays < 1 ? 1 : totalDays
|
||||
this.days = this.generateArray(1, totalDays);
|
||||
},
|
||||
setHours() {
|
||||
if (this.hoursData && this.hoursData.length > 0) {
|
||||
this.hours = this.hoursData;
|
||||
} else {
|
||||
this.hours = this.generateArray(0, 23);
|
||||
}
|
||||
},
|
||||
setMinutes() {
|
||||
if (this.minutesData && this.minutesData.length > 0) {
|
||||
this.minutes = this.minutesData
|
||||
} else {
|
||||
this.minutes = this.generateArray(0, 59);
|
||||
}
|
||||
},
|
||||
setSeconds() {
|
||||
if (this.secondsData && this.secondsData.length > 0) {
|
||||
this.seconds = this.secondsData;
|
||||
} else {
|
||||
this.seconds = this.generateArray(0, 59);
|
||||
}
|
||||
},
|
||||
show() {
|
||||
setTimeout(() => {
|
||||
this.isShow = true;
|
||||
}, 250);
|
||||
},
|
||||
hide() {
|
||||
this.isShow = false;
|
||||
this.$emit('cancel', {});
|
||||
},
|
||||
maskClick() {
|
||||
if (!this.maskClosable) return;
|
||||
this.hide()
|
||||
},
|
||||
change(e) {
|
||||
this.value = e.detail.value;
|
||||
const type = Number(this.type)
|
||||
switch (type) {
|
||||
case 0:
|
||||
this.year = this.years[this.value[0]];
|
||||
break;
|
||||
case 1:
|
||||
this.year = this.years[this.value[0]];
|
||||
this.month = this.months[this.value[1]];
|
||||
this.day = this.days[this.value[2]];
|
||||
this.hour = this.hours[this.value[3]];
|
||||
this.minute = this.minutes[this.value[4]];
|
||||
break;
|
||||
case 2:
|
||||
this.year = this.years[this.value[0]];
|
||||
this.month = this.months[this.value[1]];
|
||||
this.day = this.days[this.value[2]];
|
||||
break;
|
||||
case 3:
|
||||
this.year = this.years[this.value[0]];
|
||||
this.month = this.months[this.value[1]];
|
||||
break;
|
||||
case 4:
|
||||
this.hour = this.hours[this.value[0]];
|
||||
this.minute = this.minutes[this.value[1]];
|
||||
break;
|
||||
case 5:
|
||||
this.hour = this.hours[this.value[0]];
|
||||
this.minute = this.minutes[this.value[1]];
|
||||
this.second = this.seconds[this.value[2]];
|
||||
break;
|
||||
case 6:
|
||||
this.minute = this.minutes[this.value[0]];
|
||||
this.second = this.seconds[this.value[1]];
|
||||
break;
|
||||
case 7:
|
||||
this.year = this.years[this.value[0]];
|
||||
this.month = this.months[this.value[1]];
|
||||
this.day = this.days[this.value[2]];
|
||||
this.hour = this.hours[this.value[3]];
|
||||
this.minute = this.minutes[this.value[4]];
|
||||
this.second = this.seconds[this.value[5]];
|
||||
break;
|
||||
case 8:
|
||||
this.year = this.years[this.value[0]];
|
||||
this.month = this.months[this.value[1]];
|
||||
this.day = this.days[this.value[2]];
|
||||
this.hour = this.hours[this.value[3]];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.isEnd = true
|
||||
},
|
||||
selectResult() {
|
||||
let result = {};
|
||||
let year = this.year;
|
||||
let month = this.formatNum(this.month || 0);
|
||||
let day = this.formatNum(this.day || 0);
|
||||
let hour = this.formatNum(this.hour || 0);
|
||||
let minute = this.formatNum(this.minute || 0);
|
||||
let second = this.formatNum(this.second || 0);
|
||||
const type = Number(this.type)
|
||||
switch (type) {
|
||||
case 0:
|
||||
result = {
|
||||
year: year,
|
||||
result: year
|
||||
};
|
||||
break;
|
||||
case 1:
|
||||
result = {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
result: `${year}-${month}-${day} ${hour}:${minute}`
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
result = {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
result: `${year}-${month}-${day}`
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
result = {
|
||||
year: year,
|
||||
month: month,
|
||||
result: `${year}-${month}`
|
||||
};
|
||||
break;
|
||||
case 4:
|
||||
result = {
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
result: `${hour}:${minute}`
|
||||
};
|
||||
break;
|
||||
case 5:
|
||||
result = {
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
result: `${hour}:${minute}:${second}`
|
||||
};
|
||||
break;
|
||||
case 6:
|
||||
result = {
|
||||
minute: minute,
|
||||
second: second,
|
||||
result: `${minute}:${second}`
|
||||
};
|
||||
break;
|
||||
case 7:
|
||||
result = {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
result: `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
||||
};
|
||||
break;
|
||||
case 8:
|
||||
result = {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
result: `${year}-${month}-${day} ${hour}:00`
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.$emit('confirm', result);
|
||||
},
|
||||
waitFix(index = 0) {
|
||||
if (this.isEnd) {
|
||||
this.selectResult()
|
||||
} else {
|
||||
index++;
|
||||
if (index >= 20) {
|
||||
this.isEnd = true
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.waitFix(index)
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
btnFix() {
|
||||
setTimeout(() => {
|
||||
this.waitFix()
|
||||
this.hide();
|
||||
}, 80);
|
||||
},
|
||||
pickerstart() {
|
||||
this.isEnd = false
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-datetime-picker {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-datetime__picker-view {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-datetime__mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.tui-datetime__mask-show {
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.tui-datetime__header {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.tui-date-header {
|
||||
width: 100%;
|
||||
height: 52rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 26rpx;
|
||||
line-height: 26rpx;
|
||||
/* #ifdef MP */
|
||||
box-shadow: 0 15rpx 10rpx -15rpx #efefef;
|
||||
/* #endif */
|
||||
/* #ifndef MP */
|
||||
box-shadow: 0 15rpx 10rpx -15rpx #888;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tui-date-unit {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tui-show {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-picker-header {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
font-size: 32rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-date-radius {
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-picker-header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1rpx solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tui-date__picker-body {
|
||||
width: 100%;
|
||||
/* height: 520rpx; */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-date__column-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tui-font-size_32 {
|
||||
font-size: 32rpx !important;
|
||||
}
|
||||
|
||||
.tui-date__unit-text {
|
||||
font-size: 24rpx !important;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.tui-btn-picker {
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-opacity {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tui-pickerdate__title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
193
src/components/thorui/tui-dialog/tui-dialog.vue
Normal file
193
src/components/thorui/tui-dialog/tui-dialog.vue
Normal file
@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="show" class="tui-dialog" :style="{background:backgroundColor,borderRadius:radius}" @tap.stop="stopEvent">
|
||||
<view class="tui-dialog__hd">
|
||||
<view class="tui-dialog__title" :style="{color:titleColor}">{{title}}
|
||||
<slot name="title"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-dialog__bd" :style="{color:contentColor}">
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<view class="tui-dialog__ft">
|
||||
<block v-if="buttons && buttons.length">
|
||||
<view v-for="(item,index) in buttons" :key="index" :style="{color:item.color || '#333'}" class="tui-dialog__btn" :data-index="index" @tap="buttonTap">{{item.text}}</view>
|
||||
</block>
|
||||
<slot name="footer" v-else></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view @tap="close" class="tui-dialog__mask" :class="{'tui-mask_hidden':!show}" v-if="mask"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:'tuiDialog',
|
||||
emits: ['click','close'],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
backgroundColor:{
|
||||
type:String,
|
||||
default:'#fff'
|
||||
},
|
||||
radius:{
|
||||
type:String,
|
||||
default:'12px'
|
||||
},
|
||||
titleColor:{
|
||||
type:String,
|
||||
default:'#333'
|
||||
},
|
||||
contentColor:{
|
||||
type:String,
|
||||
default:'#999'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buttonTap(e) {
|
||||
const {
|
||||
index
|
||||
} = e.currentTarget.dataset;
|
||||
this.$emit('click', {
|
||||
index,
|
||||
item: this.buttons[index]
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
if (!this.maskClosable) return;
|
||||
this.$emit('close', {});
|
||||
},
|
||||
|
||||
stopEvent() {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tui-dialog {
|
||||
position: fixed;
|
||||
z-index: 5000;
|
||||
top: 50%;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90%;
|
||||
}
|
||||
|
||||
.tui-dialog__hd {
|
||||
padding: 32px 24px 16px
|
||||
}
|
||||
|
||||
.tui-dialog__title {
|
||||
font-weight: 700;
|
||||
font-size: 17px;
|
||||
line-height: 1.4
|
||||
}
|
||||
|
||||
.tui-dialog__bd {
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 32px;
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
word-wrap: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
|
||||
.tui-dialog__ft {
|
||||
display: flex;
|
||||
position: relative;
|
||||
line-height: 56px;
|
||||
min-height: 56px;
|
||||
font-size: 17px
|
||||
}
|
||||
|
||||
.tui-dialog__ft:after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-top: 1px solid rgba(0, 0, 0, .1);
|
||||
transform-origin: 0 0;
|
||||
transform: scaleY(.5)
|
||||
}
|
||||
|
||||
.tui-dialog__btn {
|
||||
display: block;
|
||||
flex: 1;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
position: relative
|
||||
}
|
||||
|
||||
.tui-dialog__btn:active {
|
||||
background-color: #ECECEC
|
||||
}
|
||||
|
||||
.tui-dialog__btn:first-child::after {
|
||||
width: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.tui-dialog__btn::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
bottom: 0;
|
||||
border-left: 1px solid rgba(0,0,0,.1);
|
||||
transform-origin: 0 0;
|
||||
transform: scaleX(.5)
|
||||
}
|
||||
|
||||
.tui-dialog__mask.tui-mask_hidden {
|
||||
opacity: 0;
|
||||
transform: scale3d(1, 1, 0)
|
||||
}
|
||||
|
||||
.tui-dialog__mask {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, .6);
|
||||
opacity: 1;
|
||||
transform: scale3d(1, 1, 1);
|
||||
transition: all .2s ease-in
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<view class="tui-digital-keyboard" :class="{'tui-keyboard__action':show}"
|
||||
:style="{backgroundColor:darkMode?'#1E1E1E':backgroundColor,zIndex:zIndex}">
|
||||
<!--自定义内容,如关闭键盘按钮-->
|
||||
<slot></slot>
|
||||
<view class="tui-keyboard__grids">
|
||||
<view class="tui-keyboard__left">
|
||||
<view class="tui-keyboard__grid" :class="{'tui-flex__2':item=='0'}" v-for="(item,index) in keyArr"
|
||||
:key="index">
|
||||
<view class="tui-key__item"
|
||||
:style="{color:darkMode?'#D1D1D1':color,backgroundColor:darkMode?'#2C2C2C':keyBackground}"
|
||||
:hover-class="!isDecimal && index==10?'':'tui-key__item-active'" :hover-stay-time="150"
|
||||
@tap.stop="handleClick" :data-value="item">{{item}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-keyboard__right">
|
||||
<view class="tui-keyboard__grid">
|
||||
<view class="tui-key__item" @tap.stop="backspace" hover-class="tui-key__item-active"
|
||||
:hover-stay-time="150" :style="{backgroundColor:darkMode?'#2C2C2C':keyBackground}">
|
||||
<text class="tui-dk__icon tui-icon__backspace" :style="{color:darkMode?'#D1D1D1':color}"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-keyboard__grid tui-dk__btn">
|
||||
<view class="tui-key__item" :class="{'tui-dkBtn__disabled':disabled}" @tap.stop="confirm"
|
||||
:style="{color:buttonColor,background:getButtonBackground,fontSize:buttonSize+'rpx',fontWeight:buttonFontBold?'bold':'normal'}"
|
||||
:hover-class="disabled?'':'tui-key__item-active'" :hover-stay-time="150">{{buttonText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiDigitalKeyboard',
|
||||
emits: ['click', 'backspace', 'confirm'],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 996
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#F7F7F7'
|
||||
},
|
||||
//数字以及删除键字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#1a1a1a'
|
||||
},
|
||||
keyBackground: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
buttonBackground: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
buttonColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//rpx
|
||||
buttonSize: {
|
||||
type: [Number, String],
|
||||
default: 32
|
||||
},
|
||||
buttonFontBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否需要小数点
|
||||
isDecimal: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//可自定义颜色,设置为true时,自定义设置失效
|
||||
darkMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isDecimal(val) {
|
||||
this.initData(val)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getButtonBackground() {
|
||||
return this.buttonBackground || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyArr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.']
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initData(this.isDecimal)
|
||||
},
|
||||
methods: {
|
||||
initData(val) {
|
||||
let keyArr = this.keyArr;
|
||||
if (val) {
|
||||
keyArr = keyArr.splice(10, 1, '.')
|
||||
} else {
|
||||
keyArr = keyArr.splice(10, 1, '')
|
||||
}
|
||||
},
|
||||
handleClick(e) {
|
||||
if (!this.show) return;
|
||||
const keyVal = e.currentTarget.dataset.value;
|
||||
if (keyVal) {
|
||||
this.$emit('click', {
|
||||
value: keyVal
|
||||
})
|
||||
}
|
||||
},
|
||||
backspace() {
|
||||
this.$emit('backspace', {})
|
||||
},
|
||||
confirm() {
|
||||
if (this.disabled) return;
|
||||
this.$emit('confirm', {})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: 'digitalKeyboard';
|
||||
src: url('data:font/truetype;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTY0VTDkAAAacAAAAHEdERUYAKQAKAAAGfAAAAB5PUy8yPH5IAgAAAVgAAABWY21hcAAP6bsAAAHAAAABQmdhc3D//wADAAAGdAAAAAhnbHlmkTxXZQAAAxAAAACgaGVhZBpqaSwAAADcAAAANmhoZWEHfAOFAAABFAAAACRobXR4DAAAPQAAAbAAAAAQbG9jYQBQAAAAAAMEAAAACm1heHABEAA9AAABOAAAACBuYW1lKeYRVQAAA7AAAAKIcG9zdGw+RqkAAAY4AAAAOQABAAAAAQAAb8Q5j18PPPUACwQAAAAAANu4kpgAAAAA27iSmAA9AFADngKvAAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAOeAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEADEAAgAAAAAAAgAAAAoACgAAAP8AAAAAAAAAAQQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5hDmEAOA/4AAXAOAAIAAAAABAAAAAAAABAAAAAAAAAAEAAAABAAAPQAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAOYQ//8AAOYQ//8Z8wABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAACAD0AUAOeAq8AEwAwAAABISIGDwEGFB8BHgEXIT4BNxEuAQMGIi8BBwYiLgE/AScmNDYyHwE3NjIWFA8BFxYUA03+AhMhDMcLDcUMIRMB/iMtAQEtxAkYCURFCRkQAQlFRggSFwlFRQkYEQhERAkCrxAO8A4nEO4ODwEBLiIBvSIu/mMJCURECREZCURGCRgRCEVFCBIYCURFCRgAAAAAAAASAN4AAQAAAAAAAAAVACwAAQAAAAAAAQAIAFQAAQAAAAAAAgAHAG0AAQAAAAAAAwAIAIcAAQAAAAAABAAIAKIAAQAAAAAABQALAMMAAQAAAAAABgAIAOEAAQAAAAAACgArAUIAAQAAAAAACwATAZYAAwABBAkAAAAqAAAAAwABBAkAAQAQAEIAAwABBAkAAgAOAF0AAwABBAkAAwAQAHUAAwABBAkABAAQAJAAAwABBAkABQAWAKsAAwABBAkABgAQAM8AAwABBAkACgBWAOoAAwABBAkACwAmAW4ACgBDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AAoAAApDcmVhdGVkIGJ5IGljb25mb250CgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAUgBlAGcAdQBsAGEAcgAAUmVndWxhcgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAVgBlAHIAcwBpAG8AbgAgADEALgAwAABWZXJzaW9uIDEuMAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgAAR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0LgAAaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAaHR0cDovL2ZvbnRlbGxvLmNvbQAAAgAAAAAAAAAKAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACAQIOYmFja3NwYWNlLWZpbGwAAAAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAwADAAEABAAAAAIAAAAAAAAAAQAAAADVpCcIAAAAANu4kpgAAAAA27iSmA==') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tui-dk__icon {
|
||||
font-family: 'digitalKeyboard' !important;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.tui-icon__backspace:before {
|
||||
content: '\e610';
|
||||
}
|
||||
|
||||
.tui-digital-keyboard {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
visibility: hidden;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.25s ease-in-out;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tui-digital-keyboard::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-top: 1px solid rgba(51, 51, 51, .1);
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-keyboard__action {
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-keyboard__grids {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.tui-keyboard__left {
|
||||
width: 75%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding-right: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-keyboard__right {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.tui-keyboard__grid {
|
||||
width: 33.3333%;
|
||||
flex-shrink: 0;
|
||||
padding-left: 16rpx;
|
||||
padding-bottom: 16rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-keyboard__right .tui-keyboard__grid {
|
||||
width: 100%;
|
||||
padding-left: 0;
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
|
||||
|
||||
.tui-dk__btn {
|
||||
padding-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.tui-dkBtn__disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tui-key__item {
|
||||
width: 100%;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
height: 88rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-key__item-active::after {
|
||||
content: ' ';
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.tui-dk__btn .tui-key__item {
|
||||
height: 296rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tui-flex__2 {
|
||||
flex: 2;
|
||||
}
|
||||
</style>
|
||||
147
src/components/thorui/tui-digital-roller/tui-digital-roller.vue
Normal file
147
src/components/thorui/tui-digital-roller/tui-digital-roller.vue
Normal file
@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<view class="tui-digital__roller">
|
||||
<view class="tui-digital__box" v-for="(item,index) in columns" :key="index"
|
||||
:style="{width:cellWidth,height:height+'px'}">
|
||||
<view class="tui-digital__column"
|
||||
:style="{transform:`translate3d(0, -${keys[index] * height}px, 0)`,transitionDuration:`${duration}s`,transitionTimingFunction:animation}">
|
||||
<view class="tui-digital__item" v-for="(val,idx) in item" :key="idx"
|
||||
:style="{color:getColor,fontSize:size+'rpx',fontWeight:bold?'bold':'normal',height:height+'px',lineHeight:size+'rpx'}">
|
||||
{{val}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var numArr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
export default {
|
||||
name: 'tuiDigitalRoller',
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//rpx
|
||||
size: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//滚动行高度 rpx
|
||||
cellHeight: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
//单个数字宽度
|
||||
cellWidth: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 动画过渡效果
|
||||
animation: {
|
||||
type: String,
|
||||
default: 'cubic-bezier(0, 1, 0, 1)'
|
||||
},
|
||||
//动画时间
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1.2
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.initColumn(this.value)
|
||||
},
|
||||
cellHeight(val) {
|
||||
this.handleHeight(val)
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
getColor(){
|
||||
return this.color || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: [],
|
||||
keys: [],
|
||||
height: 0
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.handleHeight(this.cellHeight)
|
||||
this.initColumn(this.value)
|
||||
},
|
||||
methods: {
|
||||
handleHeight(h) {
|
||||
this.height = uni.upx2px(h || 0)
|
||||
},
|
||||
initColumn(val) {
|
||||
val = val + '';
|
||||
let vals = val.split('');
|
||||
let digit = vals.length,
|
||||
arr = [];
|
||||
for (let i = 0; i < digit; i++) {
|
||||
if (~numArr.indexOf(vals[i])) {
|
||||
arr.push(numArr)
|
||||
} else {
|
||||
arr.push([vals[i]])
|
||||
}
|
||||
}
|
||||
this.columns = arr;
|
||||
this.roll(vals)
|
||||
|
||||
},
|
||||
roll(vals) {
|
||||
let lengths = this.columns.length,
|
||||
indexs = [];
|
||||
|
||||
while (vals.length) {
|
||||
let num = vals.pop();
|
||||
if (~numArr.indexOf(num)) {
|
||||
indexs.unshift(Number(num))
|
||||
} else {
|
||||
indexs.unshift(0)
|
||||
}
|
||||
}
|
||||
while (indexs.length < lengths) {
|
||||
indexs.unshift(0)
|
||||
}
|
||||
this.keys = indexs
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-digital__roller {
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-digital__box {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-digital__column {
|
||||
transform: translate3d(0, 0, 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-digital__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
198
src/components/thorui/tui-divide-list/tui-divide-list.vue
Normal file
198
src/components/thorui/tui-divide-list/tui-divide-list.vue
Normal file
@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<view class="tui-divide-list" :style="{background:background}">
|
||||
<view class="tui-divide--item" @tap="itemTap(index)"
|
||||
:style="{paddingTop:padding+'rpx',paddingBottom:padding+'rpx'}" v-for="(item,index) in list" :key="index">
|
||||
<view class="tui-divide--value">
|
||||
<view class="tui-divide--val" v-if="item[valueField] && !item[srcField]"
|
||||
:style="{color:item.valueColor || valueColor,fontSize:(item.valueSize || valueSize)+'rpx',fontWeight:item.valueFontWeight || valueFontWeight}">
|
||||
{{item[valueField]}}
|
||||
</view>
|
||||
<image :src="item[srcField]"
|
||||
:style="{width:(item.width || width) +'rpx',height:(item.height || height) +'rpx'}"
|
||||
v-if="item[srcField]">
|
||||
</image>
|
||||
<view :class="[item.isDot ? 'tui-badge-dot' : 'tui-badge']"
|
||||
:style="{ color: badgeColor, background: getBadgeBgColor }" v-if="item[numField] || item.isDot">
|
||||
{{ item.isDot ? '' : item[numField] }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-divide--text" v-if="item[textField]"
|
||||
:style="{color:item.textColor || textColor,fontSize:(item.textSize || textSize)+'rpx',fontWeight:item.textFontWeight || textFontWeight}">
|
||||
{{item[textField]}}
|
||||
</view>
|
||||
<view class="tui-divide--line" :style="{background:dividerColor,height:dividerHeight+'%',top:getTop}"
|
||||
v-if="divider && index!==list.length-1"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-divide-list",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
textField: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
srcField: {
|
||||
type: String,
|
||||
default: 'src'
|
||||
},
|
||||
numField: {
|
||||
type: String,
|
||||
default: 'num'
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
textSize: {
|
||||
type: [Number, String],
|
||||
default: 24
|
||||
},
|
||||
textColor: {
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
textFontWeight: {
|
||||
type: [Number, String],
|
||||
default: 400
|
||||
},
|
||||
valueSize: {
|
||||
type: [Number, String],
|
||||
default: 32
|
||||
},
|
||||
valueColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
valueFontWeight: {
|
||||
type: [Number, String],
|
||||
default: 400
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
divider: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
dividerColor: {
|
||||
type: String,
|
||||
default: '#eaeef1'
|
||||
},
|
||||
//百分比:1~100
|
||||
dividerHeight: {
|
||||
type: [Number, String],
|
||||
default: 60
|
||||
},
|
||||
padding: {
|
||||
type: [Number, String],
|
||||
default: 20
|
||||
},
|
||||
//角标字体颜色
|
||||
badgeColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//角标背景颜色
|
||||
badgeBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getTop() {
|
||||
let height = Number(this.dividerHeight) || 60
|
||||
return (100 - height) / 2 + '%'
|
||||
},
|
||||
getBadgeBgColor() {
|
||||
return this.badgeBgColor || (uni && uni.$tui && uni.$tui.color.pink) || '#f74d54'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
itemTap(index) {
|
||||
this.$emit('click', {
|
||||
index
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-divide-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tui-divide--item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-divide--line {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
transform: scaleX(0.5);
|
||||
transform-origin: 100% 0;
|
||||
}
|
||||
|
||||
.tui-divide--text {
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
.tui-divide--value {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-badge {
|
||||
position: absolute;
|
||||
font-size: 24rpx;
|
||||
line-height: 32rpx;
|
||||
height: 32rpx;
|
||||
min-width: 20rpx;
|
||||
padding: 0 6rpx;
|
||||
border-radius: 40rpx;
|
||||
right: 0;
|
||||
top: -8rpx;
|
||||
transform: translateX(88%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-badge-dot {
|
||||
position: absolute;
|
||||
height: 16rpx;
|
||||
width: 16rpx;
|
||||
border-radius: 50%;
|
||||
right: -8rpx;
|
||||
top: -4rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
103
src/components/thorui/tui-divider/tui-divider.vue
Normal file
103
src/components/thorui/tui-divider/tui-divider.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view class="tui-divider" :style="{ height: height + 'rpx' }">
|
||||
<view class="tui-divider-line"
|
||||
:style="{ width: width, background: getBgColor(gradual, gradualColor, dividerColor) }"></view>
|
||||
<view class="tui-divider-text"
|
||||
:style="{ color: color, fontSize: size + 'rpx', lineHeight: size + 'rpx', backgroundColor: backgroundColor, fontWeight: bold ? 'bold' : 'normal' }">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiDivider',
|
||||
props: {
|
||||
//divider占据高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
//divider宽度,可填写具体长度,如400rpx
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
//divider颜色,如果为渐变线条,此属性失效
|
||||
dividerColor: {
|
||||
type: String,
|
||||
default: '#e5e5e5'
|
||||
},
|
||||
//文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
//文字大小 rpx
|
||||
size: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//背景颜色,和当前页面背景色保持一致
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fafafa'
|
||||
},
|
||||
//是否为渐变线条,为true,divideColor失效
|
||||
gradual: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//渐变色值,to right ,提供两个色值即可,由浅至深
|
||||
gradualColor: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return ['#eee', '#ccc'];
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getBgColor: function(gradual, gradualColor, dividerColor) {
|
||||
let bgColor = dividerColor;
|
||||
if (gradual) {
|
||||
bgColor = 'linear-gradient(to right,' + gradualColor[0] + ',' + gradualColor[1] + ',' +
|
||||
gradualColor[1] + ',' + gradualColor[0] + ')';
|
||||
}
|
||||
return bgColor;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-divider {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-divider-line {
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: scaleY(0.5) translateX(-50%) translateZ(0);
|
||||
transform: scaleY(0.5) translateX(-50%) translateZ(0);
|
||||
}
|
||||
|
||||
.tui-divider-text {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 0 18rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
158
src/components/thorui/tui-drag/tui-drag.js
Normal file
158
src/components/thorui/tui-drag/tui-drag.js
Normal file
@ -0,0 +1,158 @@
|
||||
// #ifndef APP-PLUS || MP-WEIXIN || H5
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
current: -1,
|
||||
transX: '0px',
|
||||
transY: '0px',
|
||||
startTouch: null,
|
||||
tDragging: false,
|
||||
sId: null,
|
||||
preStartKey: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOutRange(x1, y1, x2, y2, x3, y3) {
|
||||
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
|
||||
},
|
||||
sortCore(sKey, eKey) {
|
||||
var excludeFix = (cKey, type) => {
|
||||
// fixed 元素位置不会变化, 这里直接用 cKey(sortKey) 获取, 更加快捷
|
||||
if (this.list[cKey].fixed) {
|
||||
var _cKey = type ? --cKey : ++cKey;
|
||||
return excludeFix(cKey, type);
|
||||
}
|
||||
return cKey;
|
||||
}
|
||||
let endRealKey = -1;
|
||||
this.list.forEach((item) => {
|
||||
if (item.sortKey === eKey) endRealKey = item.realKey;
|
||||
});
|
||||
|
||||
return this.list.map((item) => {
|
||||
let cKey = item.sortKey;
|
||||
let rKey = item.realKey;
|
||||
if (sKey < eKey) {
|
||||
if (cKey > sKey && cKey <= eKey) {
|
||||
--rKey;
|
||||
cKey = excludeFix(--cKey, true);
|
||||
} else if (cKey === sKey) {
|
||||
rKey = endRealKey;
|
||||
cKey = eKey;
|
||||
}
|
||||
} else if (sKey > eKey) {
|
||||
if (cKey >= eKey && cKey < sKey) {
|
||||
++rKey;
|
||||
cKey = excludeFix(++cKey, false);
|
||||
} else if (cKey === sKey) {
|
||||
rKey = endRealKey;
|
||||
cKey = eKey;
|
||||
}
|
||||
}
|
||||
if (item.sortKey !== cKey && !item.fixed) {
|
||||
let columns = Number(this.columns)
|
||||
item.transX = (cKey % columns) * this.baseData.itemWidth + "px";
|
||||
item.transY = Math.floor(cKey / columns) * this.baseData.itemHeight + "px";
|
||||
item.sortKey = cKey;
|
||||
item.realKey = rKey;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
},
|
||||
emitsEvent(list, type) {
|
||||
let changeList = [],
|
||||
itemList = [];
|
||||
|
||||
list.forEach((item) => {
|
||||
changeList[item.sortKey] = item;
|
||||
});
|
||||
|
||||
changeList.forEach((item) => {
|
||||
itemList.push(item.data);
|
||||
});
|
||||
if (type == "change") {
|
||||
this.change({
|
||||
listData: itemList
|
||||
});
|
||||
} else {
|
||||
this.sort_end({
|
||||
listData: itemList
|
||||
});
|
||||
}
|
||||
},
|
||||
longPress(e) {
|
||||
if (!this.isEdit) return;
|
||||
let touch = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0] : this.startTouch;
|
||||
if (!touch) return;
|
||||
this.current = Number(e.currentTarget.dataset.index);
|
||||
// 初始项是固定项则返回
|
||||
let item = this.list[this.current];
|
||||
if (item && item.fixed) return;
|
||||
|
||||
if (this.tDragging) return;
|
||||
//pageX
|
||||
this.transX = this.columns == 1 ? 0 : touch.clientX - (this.baseData.itemWidth / 2 + this.baseData.wrapLeft) + 'px';
|
||||
this.transY = touch.clientY - (this.baseData.itemHeight / 2 + this.baseData.wrapTop) + 'px';
|
||||
this.sId = touch.identifier;
|
||||
this.tDragging = true;
|
||||
},
|
||||
touchstart(e) {
|
||||
if (!this.isEdit) return;
|
||||
this.startTouch = e.changedTouches[0] || e.touches[0];
|
||||
},
|
||||
touchmove(e) {
|
||||
if (!this.isEdit || !this.tDragging) return;
|
||||
let touch = e.changedTouches[0] || e.touches[0];
|
||||
if (!touch || this.sId !== touch.identifier) return;
|
||||
let transX = this.columns == 1 ? 0 : touch.clientX - (this.baseData.itemWidth / 2 + this.baseData.wrapLeft);
|
||||
let transY = touch.clientY - (this.baseData.itemHeight / 2 + this.baseData.wrapTop);
|
||||
|
||||
if (touch.clientY > this.baseData.windowHeight - this.baseData.itemHeight) {
|
||||
this.pageScroll({
|
||||
scrollTop: touch.pageY + this.baseData.itemHeight - this.baseData.windowHeight
|
||||
})
|
||||
} else if (touch.clientY < this.baseData.itemHeight) {
|
||||
this.pageScroll({
|
||||
scrollTop: touch.pageY - this.baseData.itemHeight
|
||||
})
|
||||
}
|
||||
this.transX = transX + 'px'
|
||||
this.transY = transY + 'px'
|
||||
|
||||
let startKey = this.list[this.current].sortKey;
|
||||
let curX = Math.round(transX / this.baseData.itemWidth);
|
||||
let curY = Math.round(transY / this.baseData.itemHeight);
|
||||
let endKey = curX + Number(this.columns) * curY;
|
||||
// 目标项是固定项则返回
|
||||
var item = this.list[endKey];
|
||||
if (item && item.fixed) return;
|
||||
|
||||
if (this.isOutRange(curX, Number(this.columns), curY, this.rows, endKey, this.list.length)) return;
|
||||
|
||||
if (startKey === endKey || startKey === this.preStartKey) return;
|
||||
this.preStartKey = startKey;
|
||||
|
||||
let list = this.sortCore(startKey, endKey);
|
||||
this.listChange({
|
||||
list: list
|
||||
})
|
||||
this.emitsEvent(list, "change");
|
||||
},
|
||||
touchend(e) {
|
||||
if (!this.isEdit || !this.tDragging) return;
|
||||
this.emitsEvent(this.list, "sortend");
|
||||
this.preStartKey = -1;
|
||||
this.tDragging = false;
|
||||
this.current = -1;
|
||||
this.transX = '0px';
|
||||
this.transY = '0px';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS|| MP-WEIXIN || H5
|
||||
export default {}
|
||||
// #endif
|
||||
310
src/components/thorui/tui-drag/tui-drag.vue
Normal file
310
src/components/thorui/tui-drag/tui-drag.vue
Normal file
@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<!-- #ifdef APP-PLUS || MP-WEIXIN || H5 -->
|
||||
<view class="tui-drag__wrap" :list="list" :style="{ height: getHeight + 'rpx' }" :basedata="baseData"
|
||||
:change:list="handler.listObserver" :change:basedata="handler.baseDataObserver"
|
||||
:catch:touchmove="wxDrag?true:''">
|
||||
<view class="tui-drag__item" v-for="(item, index) in list" :key="item.id" :data-index="index"
|
||||
:style="{ width: 100 / columns + '%', height: itemHeight + 'rpx' }" @longpress="handler.longPress"
|
||||
:data-basedata="baseData" :data-edit="isEdit && canMove?true:false" :data-app="isApp"
|
||||
@touchstart="handler.touchStart" @touchmove="handler.touchMove" @touchend="handler.touchEnd"
|
||||
@mousedown="handler.mousedown">
|
||||
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="isEdit">
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-PLUS || MP-WEIXIN || H5 -->
|
||||
<view class="tui-drag__wrap" :style="{ height: getHeight + 'rpx' }">
|
||||
<view class="tui-drag__item"
|
||||
:class="{'tui-drag__current':current===index,'tui-drag__transition':current!==index && !isInit,'tui-drag__fixed':item.fixed,'tui-drag__hidden':isInit}"
|
||||
v-for="(item, index) in list" :key="item.id" :data-index="index"
|
||||
:style="{ width: 100 / columns + '%', height: itemHeight + 'rpx',transform:`translate3d(${index === current && !item.fixed ? transX : item.transX}, ${index === current && !item.fixed ? transY: item.transY}, 0px)`}"
|
||||
@longpress="longPress" @touchstart="touchstart" @touchmove.stop.prevent="touchmove" @touchend="touchend">
|
||||
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="isEdit">
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
<!-- #ifdef APP-PLUS || H5 || MP-WEIXIN -->
|
||||
<script src="./tui-drag.wxs" lang="wxs" module="handler"></script>
|
||||
<!-- #endif -->
|
||||
<script>
|
||||
import mpdrag from './tui-drag.js'
|
||||
export default {
|
||||
name: 'tuiDrag',
|
||||
mixins: [mpdrag],
|
||||
emits: ['click', 'sortend', 'change'],
|
||||
props: {
|
||||
|
||||
/*
|
||||
数据源
|
||||
约定属性 fixed(是否固定,表示此内容不可拖拽,也不可换位置)
|
||||
*/
|
||||
listData: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 列数
|
||||
columns: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
// 顶部固定高度
|
||||
topSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 底部固定高度
|
||||
bottomSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 每个 item 高度, 用于计算 item-wrap 高度 rpx
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 页面滚动高度
|
||||
scrollTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//编辑状态:为true时才可拖拽
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let isApp = 0;
|
||||
// #ifdef APP
|
||||
isApp = 1;
|
||||
// #endif
|
||||
return {
|
||||
isApp,
|
||||
/* 未渲染数据 */
|
||||
baseData: {},
|
||||
platform: '', // 平台信息
|
||||
listWxs: [], // wxs 传回的最新 list 数据
|
||||
rows: 4, // 行数
|
||||
|
||||
list: [], // 渲染数据列
|
||||
dragging: true,
|
||||
isInit: true,
|
||||
wxDrag: true,
|
||||
canMove: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
listData(val) {
|
||||
this.list = []
|
||||
setTimeout(() => {
|
||||
this.init()
|
||||
}, 0);
|
||||
},
|
||||
columns(val) {
|
||||
this.list = []
|
||||
setTimeout(() => {
|
||||
this.init()
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getHeight() {
|
||||
return this.rows * this.itemHeight;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.init();
|
||||
}, 50);
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
vibrate() {
|
||||
// #ifndef H5
|
||||
if (this.platform !== 'devtools') uni.vibrateShort();
|
||||
// #endif
|
||||
},
|
||||
pageScroll(e) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: e.scrollTop,
|
||||
duration: 0
|
||||
});
|
||||
},
|
||||
drag(e) {
|
||||
this.wxDrag = e.wxdrag;
|
||||
// this.dragging = e.dragging;
|
||||
},
|
||||
listChange(e) {
|
||||
this.listWxs = e.list;
|
||||
},
|
||||
itemClick(e) {
|
||||
let index = e.currentTarget.dataset.index;
|
||||
let item = this.listWxs[index];
|
||||
|
||||
this.$emit('click', {
|
||||
key: item.realKey,
|
||||
data: item.data,
|
||||
extra: e.detail
|
||||
});
|
||||
},
|
||||
unique(n = 6) {
|
||||
let rnd = '';
|
||||
for (let i = 0; i < n; i++) rnd += Math.floor(Math.random() * 10);
|
||||
return 'tui_' + new Date().getTime() + rnd;
|
||||
},
|
||||
/**
|
||||
* 初始化获取 dom 信息
|
||||
*/
|
||||
initDom(callback) {
|
||||
let {
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
platform
|
||||
} = uni.getSystemInfoSync();
|
||||
let remScale = (windowWidth || 375) / 375;
|
||||
|
||||
this.platform = platform;
|
||||
|
||||
let baseData = {};
|
||||
baseData.windowHeight = windowHeight;
|
||||
baseData.realTopSize = (this.topSize * remScale) / 2;
|
||||
baseData.realBottomSize = (this.bottomSize * remScale) / 2;
|
||||
baseData.columns = this.columns;
|
||||
baseData.rows = this.rows;
|
||||
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select('.tui-drag__item').boundingClientRect();
|
||||
query.select('.tui-drag__wrap').boundingClientRect();
|
||||
query.exec(res => {
|
||||
if (res && res.length > 0 && res[0] && res[0].width) {
|
||||
baseData.itemWidth = res[0].width;
|
||||
baseData.itemHeight = res[0].height;
|
||||
baseData.wrapLeft = res[1].left;
|
||||
baseData.wrapTop = res[1].top + this.scrollTop;
|
||||
this.dragging = false;
|
||||
this.baseData = baseData;
|
||||
callback && callback()
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 初始化函数
|
||||
* {listData, topSize, bottomSize, itemHeight} 参数改变需要手动调用初始化方法
|
||||
*/
|
||||
init() {
|
||||
// 初始必须为true以绑定wxs中的函数,
|
||||
this.wxDrag = true
|
||||
this.isInit = true
|
||||
this.dragging = false
|
||||
this.canMove = false
|
||||
let delItem = item => {
|
||||
let obj = {
|
||||
...item
|
||||
}
|
||||
let fixed = obj.fixed || false;
|
||||
delete obj["fixed"];
|
||||
return {
|
||||
id: this.unique(),
|
||||
fixed: fixed,
|
||||
data: {
|
||||
...obj
|
||||
}
|
||||
};
|
||||
};
|
||||
let listData = this.listData,
|
||||
_list = [];
|
||||
// 遍历数据源增加扩展项, 以用作排序使用
|
||||
listData.forEach((item, index) => {
|
||||
_list.push(delItem(item));
|
||||
});
|
||||
|
||||
let i = 0,
|
||||
columns = this.columns;
|
||||
let list = (_list || []).map((item, index) => {
|
||||
item.realKey = i++; // 真实顺序
|
||||
item.sortKey = index; // 整体顺序
|
||||
|
||||
item.tranX = `${(item.sortKey % columns) * 100}%`;
|
||||
item.tranY = `${Math.floor(item.sortKey / columns) * 100}%`;
|
||||
return item;
|
||||
});
|
||||
this.rows = Math.ceil(list.length / columns);
|
||||
this.list = list;
|
||||
this.listWxs = list;
|
||||
if (list.length === 0) return;
|
||||
let delay_time = 1000
|
||||
// 异步加载数据时候, 延迟执行 initDom 方法, 防止无法正确获取 dom 信息
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.initDom(() => {
|
||||
// #ifndef APP-PLUS || MP-WEIXIN || H5
|
||||
list.map((item, index) => {
|
||||
item.transX =
|
||||
`${index%columns * this.baseData.itemWidth}px`;
|
||||
item.transY =
|
||||
`${Math.floor(index/columns) * this.baseData.itemHeight}px`;
|
||||
})
|
||||
this.list = list;
|
||||
this.listWxs = list;
|
||||
setTimeout(() => {
|
||||
this.isInit = false
|
||||
this.dragging = true;
|
||||
}, 500)
|
||||
// #endif
|
||||
this.canMove = true
|
||||
})
|
||||
}, delay_time);
|
||||
})
|
||||
},
|
||||
sort_end(e) {
|
||||
this.$emit('sortend', {
|
||||
listData: e.listData
|
||||
});
|
||||
},
|
||||
change(e) {
|
||||
this.$emit('change', {
|
||||
listData: e.listData
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-drag__wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-drag__wrap .tui-drag__item {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* #ifndef APP-PLUS || MP-WEIXIN || H5 */
|
||||
transition: transform 0s;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-drag__transition {
|
||||
transition: transform 0.35s !important;
|
||||
}
|
||||
|
||||
.tui-drag__wrap .tui-drag__current {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
.tui-drag__wrap .tui-drag__fixed {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
.tui-drag__hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
343
src/components/thorui/tui-drag/tui-drag.wxs
Normal file
343
src/components/thorui/tui-drag/tui-drag.wxs
Normal file
@ -0,0 +1,343 @@
|
||||
var _st = {}
|
||||
|
||||
function isPC() {
|
||||
if (typeof navigator !== 'object') return false;
|
||||
var userAgentInfo = navigator.userAgent;
|
||||
var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
|
||||
var flag = true;
|
||||
for (var v = 0; v < Agents.length - 1; v++) {
|
||||
if (userAgentInfo.indexOf(Agents[v]) > 0) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
var isH5 = false
|
||||
if (typeof window === 'object') isH5 = true
|
||||
|
||||
var isOutRange = function(x1, y1, x2, y2, x3, y3) {
|
||||
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
|
||||
};
|
||||
var isEdit = false;
|
||||
|
||||
function bool(str) {
|
||||
return str === 'true' || str == true ? true : false
|
||||
}
|
||||
|
||||
var sortCore = function(sKey, eKey, st) {
|
||||
var _ = st.basedata;
|
||||
var excludeFix = function(cKey, type) {
|
||||
// fixed 元素位置不会变化, 这里直接用 cKey(sortKey) 获取, 更加快捷
|
||||
if (st.list[cKey].fixed) {
|
||||
var _cKey = type ? --cKey : ++cKey;
|
||||
return excludeFix(cKey, type);
|
||||
}
|
||||
return cKey;
|
||||
}
|
||||
|
||||
// 先获取到 endKey 对应的 realKey, 防止下面排序过程中该 realKey 被修改
|
||||
var endRealKey = -1;
|
||||
st.list.forEach(function(item) {
|
||||
if (item.sortKey === eKey) endRealKey = item.realKey;
|
||||
});
|
||||
|
||||
return st.list.map(function(item) {
|
||||
if (item.fixed) return item;
|
||||
var cKey = item.sortKey;
|
||||
var rKey = item.realKey;
|
||||
|
||||
if (sKey < eKey) {
|
||||
// 正序拖动
|
||||
if (cKey > sKey && cKey <= eKey) {
|
||||
--rKey;
|
||||
cKey = excludeFix(--cKey, true);
|
||||
} else if (cKey === sKey) {
|
||||
rKey = endRealKey;
|
||||
cKey = eKey;
|
||||
}
|
||||
} else if (sKey > eKey) {
|
||||
// 倒序拖动
|
||||
if (cKey >= eKey && cKey < sKey) {
|
||||
++rKey
|
||||
cKey = excludeFix(++cKey, false);
|
||||
} else if (cKey === sKey) {
|
||||
rKey = endRealKey;
|
||||
cKey = eKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.sortKey !== cKey) {
|
||||
item.tranX = (cKey % _.columns) * 100 + "%";
|
||||
item.tranY = Math.floor(cKey / _.columns) * 100 + "%";
|
||||
item.sortKey = cKey;
|
||||
item.realKey = rKey;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
var triggerCustomEvent = function(list, type, ins) {
|
||||
if (!ins) return;
|
||||
var _list = [],
|
||||
listData = [];
|
||||
|
||||
list.forEach(function(item) {
|
||||
_list[item.sortKey] = item;
|
||||
});
|
||||
|
||||
_list.forEach(function(item) {
|
||||
listData.push(item.data);
|
||||
});
|
||||
|
||||
//编译到小程序 funcName作为参数传递导致事件不执行
|
||||
if (type == "change") {
|
||||
ins.callMethod("change", {
|
||||
listData: listData
|
||||
});
|
||||
} else {
|
||||
ins.callMethod("sort_end", {
|
||||
listData: listData
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var longPress = function(event, ownerInstance) {
|
||||
var ins = event.instance;
|
||||
var dataset = ins.getDataset()
|
||||
isEdit = bool(dataset.edit)
|
||||
if (!isEdit) return;
|
||||
var st = ins.getState();
|
||||
if (!st.basedata || st.basedata == 'undefined') {
|
||||
st.basedata = JSON.parse(dataset.basedata)
|
||||
}
|
||||
var _ = st.basedata;
|
||||
var sTouch = null;
|
||||
if (isH5 && isPC()) {
|
||||
sTouch = event;
|
||||
} else {
|
||||
sTouch = event.changedTouches ? event.changedTouches[0] : st.sTouch;
|
||||
}
|
||||
if (!sTouch) return;
|
||||
|
||||
st.cur = +dataset.index;
|
||||
|
||||
st.app = +dataset.app == 1 ? true : false;
|
||||
|
||||
// 初始项是固定项则返回
|
||||
var item = st.list[st.cur];
|
||||
if (item && item.fixed) return;
|
||||
|
||||
// 如果已经在 drag 中则返回, 防止多指触发 drag 动作, touchstart 事件中有效果
|
||||
if (st.dragging) return;
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
ownerInstance.callMethod("drag", {
|
||||
wxdrag: true
|
||||
});
|
||||
// #endif
|
||||
|
||||
// 计算X,Y轴初始位移, 使 item 中心移动到点击处, 单列时候X轴初始不做位移
|
||||
st.tranX = _.columns === 1 ? 0 : sTouch.pageX - (_.itemWidth / 2 + _.wrapLeft);
|
||||
st.tranY = sTouch.pageY - (_.itemHeight / 2 + _.wrapTop);
|
||||
st.sId = sTouch.identifier;
|
||||
|
||||
ins.setStyle({
|
||||
'transform': 'translate3d(' + st.tranX + 'px, ' + st.tranY + 'px, 0)'
|
||||
});
|
||||
st.itemsInstance.forEach(function(item, index) {
|
||||
item.removeClass("tui-drag__transition").removeClass("tui-drag__current");
|
||||
item.addClass(index == st.cur ? "tui-drag__current" : "tui-drag__transition");
|
||||
})
|
||||
|
||||
ownerInstance.callMethod("vibrate");
|
||||
st.dragging = true;
|
||||
};
|
||||
|
||||
var touchStart = function(event, ownerInstance) {
|
||||
var ins = event.instance;
|
||||
var state = ins.getState()
|
||||
state.list = _st.list
|
||||
state.itemsInstance = _st.itemsInstance
|
||||
state.basedata = _st.basedata
|
||||
if (isH5 && isPC()) {
|
||||
state.sTouch = event;
|
||||
} else {
|
||||
state.sTouch = event.changedTouches[0] || event.touches[0]
|
||||
}
|
||||
var dataset = ins.getDataset()
|
||||
isEdit = bool(dataset.edit)
|
||||
// #ifdef MP-WEIXIN
|
||||
ownerInstance.callMethod("drag", {
|
||||
wxdrag: false
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
|
||||
var touchMove = function(e, ownerInstance, pc_e) {
|
||||
var ins = null
|
||||
var st = {}
|
||||
var mTouch = null;
|
||||
if (isH5 && isPC()) {
|
||||
mTouch = e;
|
||||
st = pc_e.instance.getState()
|
||||
ins = pc_e.instance;
|
||||
} else {
|
||||
mTouch = e.changedTouches[0] || e.touches[0]
|
||||
ins = e.instance
|
||||
st = ins.getState()
|
||||
}
|
||||
if (e.preventDefault && st.dragging) {
|
||||
e.preventDefault()
|
||||
}
|
||||
if (st.app && st.dragging && event && event.preventDefault) {
|
||||
event.preventDefault()
|
||||
}
|
||||
var _ = st.basedata;
|
||||
|
||||
if (!st.dragging || !isEdit || !mTouch) return;
|
||||
|
||||
// 如果不是同一个触发点则返回
|
||||
if (st.sId !== mTouch.identifier) return;
|
||||
|
||||
// 计算X,Y轴位移, 单列时候X轴初始不做位移
|
||||
var tranX = _.columns === 1 ? 0 : mTouch.pageX - (_.itemWidth / 2 + _.wrapLeft);
|
||||
var tranY = mTouch.pageY - (_.itemHeight / 2 + _.wrapTop);
|
||||
|
||||
// 到顶到底自动滑动
|
||||
if (mTouch.clientY > _.windowHeight - _.itemHeight - _.realBottomSize) {
|
||||
// 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
|
||||
ownerInstance.callMethod("pageScroll", {
|
||||
scrollTop: mTouch.pageY + _.itemHeight - (_.windowHeight - _.realBottomSize)
|
||||
});
|
||||
} else if (mTouch.clientY < _.itemHeight + _.realTopSize) {
|
||||
// 当前触摸点pageY - item高度 - 顶部固定区域高度
|
||||
ownerInstance.callMethod("pageScroll", {
|
||||
scrollTop: mTouch.pageY - _.itemHeight - _.realTopSize
|
||||
});
|
||||
}
|
||||
|
||||
// 设置当前激活元素偏移量
|
||||
ins.setStyle({
|
||||
'transform': 'translate3d(' + tranX + 'px, ' + tranY + 'px, 0)'
|
||||
})
|
||||
|
||||
var startKey = st.list[st.cur].sortKey;
|
||||
var curX = Math.round(tranX / _.itemWidth);
|
||||
var curY = Math.round(tranY / _.itemHeight);
|
||||
var endKey = curX + _.columns * curY;
|
||||
|
||||
// 目标项是固定项则返回
|
||||
var item = st.list[endKey];
|
||||
if (item && item.fixed) return;
|
||||
|
||||
// X轴或Y轴超出范围则返回
|
||||
if (isOutRange(curX, _.columns, curY, _.rows, endKey, st.list.length)) return;
|
||||
|
||||
// 防止拖拽过程中发生乱序问题
|
||||
if (startKey === endKey || startKey === st.preStartKey) return;
|
||||
st.preStartKey = startKey;
|
||||
|
||||
var list = sortCore(startKey, endKey, st);
|
||||
st.itemsInstance.forEach(function(itemIns, index) {
|
||||
var item = list[index];
|
||||
if (index !== st.cur) {
|
||||
itemIns.setStyle({
|
||||
'transform': 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ownerInstance.callMethod("vibrate");
|
||||
ownerInstance.callMethod("listChange", {
|
||||
list: list
|
||||
});
|
||||
triggerCustomEvent(list, "change", ownerInstance);
|
||||
}
|
||||
|
||||
var touchEnd = function(event, ownerInstance, pc_e) {
|
||||
var ins = null
|
||||
var st = {}
|
||||
if (isH5 && isPC()) {
|
||||
ins = pc_e.instance
|
||||
st = pc_e.instance.getState()
|
||||
} else {
|
||||
st = event.instance.getState()
|
||||
ins = event.instance
|
||||
}
|
||||
|
||||
if (!st.dragging || !isEdit) return;
|
||||
triggerCustomEvent(st.list, "sort_end", ownerInstance);
|
||||
|
||||
ins.addClass("tui-drag__transition");
|
||||
ins.setStyle({
|
||||
'transform': 'translate3d(' + st.list[st.cur].tranX + ',' + st.list[st.cur].tranY + ', 0)'
|
||||
});
|
||||
st.itemsInstance.forEach(function(item, index) {
|
||||
item.removeClass("tui-drag__transition");
|
||||
})
|
||||
st.preStartKey = -1;
|
||||
st.dragging = false;
|
||||
// #ifdef MP-WEIXIN
|
||||
ownerInstance.callMethod("drag", {
|
||||
wxdrag: false
|
||||
});
|
||||
// #endif
|
||||
st.cur = -1;
|
||||
st.tranX = 0;
|
||||
st.tranY = 0;
|
||||
}
|
||||
|
||||
var baseDataObserver = function(newVal, oldVal, ownerInstance, ins) {
|
||||
// var st = ownerInstance.getState();
|
||||
_st.basedata = newVal;
|
||||
}
|
||||
|
||||
var listObserver = function(newVal, oldVal, ownerInstance, ins) {
|
||||
// var st = ownerInstance.getState();
|
||||
_st.itemsInstance = ownerInstance.selectAllComponents('.tui-drag__item');
|
||||
|
||||
_st.list = newVal || [];
|
||||
if (!_st.itemsInstance || _st.itemsInstance.length === 0) return;
|
||||
_st.list.forEach(function(item, index) {
|
||||
var itemIns = _st.itemsInstance[index];
|
||||
if (item && itemIns) {
|
||||
itemIns.setStyle({
|
||||
'transform': 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)'
|
||||
});
|
||||
if (item.fixed) itemIns.addClass("tui-drag__fixed");
|
||||
}
|
||||
})
|
||||
}
|
||||
var movable = false;
|
||||
|
||||
function mousedown(e, ins) {
|
||||
if (!isH5 || !isPC()) return
|
||||
touchStart(e, ins)
|
||||
longPress(e, ins)
|
||||
movable = true
|
||||
window.onmousemove = function(event) {
|
||||
if (!isH5 || !isPC() || !movable) return
|
||||
touchMove(event, ins, e)
|
||||
}
|
||||
window.onmouseup = function(event) {
|
||||
if (!isH5 || !isPC() || !movable) return
|
||||
touchEnd(event, ins, e)
|
||||
movable = false
|
||||
}
|
||||
}
|
||||
|
||||
function stopMove() {
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
longPress: longPress,
|
||||
touchStart: touchStart,
|
||||
touchMove: touchMove,
|
||||
touchEnd: touchEnd,
|
||||
mousedown: mousedown,
|
||||
baseDataObserver: baseDataObserver,
|
||||
listObserver: listObserver,
|
||||
stopMove: stopMove
|
||||
}
|
||||
140
src/components/thorui/tui-drawer/tui-drawer.vue
Normal file
140
src/components/thorui/tui-drawer/tui-drawer.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<!-- @touchmove.stop.prevent -->
|
||||
<view>
|
||||
<view v-if="mask" class="tui-drawer-mask" :class="{ 'tui-drawer-mask_show': visible }" :style="{ zIndex: maskZIndex }" @tap="handleMaskClick"></view>
|
||||
<view
|
||||
class="tui-drawer-container"
|
||||
:class="[`tui-drawer-container_${mode}`, visible ? `tui-drawer-${mode}__show` : '']"
|
||||
:style="{ zIndex: zIndex, backgroundColor: backgroundColor }"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 超过一屏时插槽使用scroll-view
|
||||
**/
|
||||
export default {
|
||||
name: 'tuiDrawer',
|
||||
emits: ['close'],
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// left right bottom top
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
//drawer z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 990
|
||||
},
|
||||
//mask z-index
|
||||
maskZIndex: {
|
||||
type: [Number, String],
|
||||
default: 980
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleMaskClick() {
|
||||
if (!this.maskClosable) {
|
||||
return;
|
||||
}
|
||||
this.$emit('close', {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-drawer-mask {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.tui-drawer-mask_show {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-drawer-container {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
height: 100.2%;
|
||||
top: 0;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-ms-touch-action: pan-y cross-slide-y;
|
||||
-ms-scroll-chaining: none;
|
||||
-ms-scroll-limit: 0 50 0 50;
|
||||
}
|
||||
.tui-drawer-container_left {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate3d(-100%, -50%, 0);
|
||||
}
|
||||
|
||||
.tui-drawer-container_right {
|
||||
right: 0;
|
||||
top: 50%;
|
||||
left: auto;
|
||||
transform: translate3d(100%, -50%, 0);
|
||||
}
|
||||
|
||||
.tui-drawer-container_bottom,
|
||||
.tui-drawer-container_top {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
min-height: 20rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.tui-drawer-container_bottom {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
.tui-drawer-container_top {
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
.tui-drawer-left__show,
|
||||
.tui-drawer-right__show {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
}
|
||||
.tui-drawer-top__show,
|
||||
.tui-drawer-bottom__show {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="tui-selected-class tui-dropdown-list"
|
||||
:style="{ height: selectHeight ? selectHeight + 'rpx' : 'auto' }">
|
||||
<slot name="selectionbox"></slot>
|
||||
<view class="tui-dropdown-view" :class="[show ? 'tui-dropdownlist-show' : '']"
|
||||
:style="{ backgroundColor: backgroundColor, height: show ? height + 'rpx' : 0, top: top + 'rpx' }">
|
||||
<slot name="dropdownbox"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-dropdown__mask" :style="{backgroundColor:maskBackground}" v-if="isMask && show"
|
||||
@tap.stop="maskClick">
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiDropdownList',
|
||||
emits: ['close'],
|
||||
props: {
|
||||
//控制显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//top rpx
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//下拉框高度 rpx
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//选择框高度 单位rpx
|
||||
selectHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isMask: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maskBackground: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
maskClick() {
|
||||
this.$emit('close', {})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-dropdown-list {
|
||||
position: relative;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
.tui-dropdown-view {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -99;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
/* visibility: hidden; */
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tui-dropdownlist-show {
|
||||
opacity: 1;
|
||||
z-index: 996;
|
||||
/* visibility: visible; */
|
||||
}
|
||||
|
||||
.tui-dropdown__mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
z-index: 5;
|
||||
}
|
||||
</style>
|
||||
308
src/components/thorui/tui-fab/tui-fab.vue
Normal file
308
src/components/thorui/tui-fab/tui-fab.vue
Normal file
@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="tui-fab-box"
|
||||
:class="{'tui-fab-right':left==0 || (left && right),'tui-fab__box-bottom':bottom && top == 0,'tui-fab__box-top':!(bottom && top == 0)}"
|
||||
:style="getStyles">
|
||||
<view class="tui-fab-btn" :class="{'tui-visible':isOpen,'tui-fab-hidden':isHidden}">
|
||||
<view class="tui-fab-item-box"
|
||||
:class="{'tui-fab-item-left':left && right==0 && item[imgField],'tui-fab__pb40':bottom && top == 0,'tui-fab__pt40':!(bottom && top == 0)}"
|
||||
v-for="(item,index) in btnList" :key="index" @tap.stop="handleClick(index)">
|
||||
<view :class="[left && right==0?'tui-text-left':'tui-text-right']" v-if="item[imgField]"
|
||||
:style="{fontSize:(item.fontSize || size)+'rpx',color:item.color}">{{item[textField] || ""}}
|
||||
</view>
|
||||
<view class="tui-fab-item"
|
||||
:style="{width:width+'rpx',height:height+'rpx',background:item.bgColor || getBgColor,borderRadius:radius}">
|
||||
<view class="tui-fab-title" v-if="!item[imgField]"
|
||||
:style="{fontSize:(item.fontSize || size)+'rpx',color:item.color}">{{item[textField] || ""}}
|
||||
</view>
|
||||
<image :src="item[imgField]" class="tui-fab-img" v-else
|
||||
:style="{width:(item.imgWidth || 64)+'rpx',height:(item.imgHeight || 64)+'rpx'}"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-fab-item" :class="{'tui-active':isOpen}"
|
||||
:style="{width:width+'rpx',height:height+'rpx',borderRadius:radius,background:getBgColor,color:color}"
|
||||
@tap.stop="handleClick(-1)">
|
||||
<text class="tui-fab-icon tui-icon-plus" v-if="!custom"></text>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-fab-mask" :style="getZIndex" :class="{'tui-visible':isOpen}" @tap="handleClickCancel"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//拓展出来的按钮不应多于6个,否则违反了作为悬浮按钮的快速、高效的原则
|
||||
export default {
|
||||
name: "tuiFab",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//rpx 为0时值为auto
|
||||
left: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//rpx 当为0时且left不为0,值为auto
|
||||
right: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
//rpx bottom值
|
||||
bottom: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
//rpx top值
|
||||
top: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 997
|
||||
},
|
||||
//默认按钮 宽度 rpx
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 108
|
||||
},
|
||||
//默认按钮 高度 rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 108
|
||||
},
|
||||
//圆角值
|
||||
radius: {
|
||||
type: String,
|
||||
default: "50%"
|
||||
},
|
||||
//默认按钮自定义内容[替换加号]
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//默认按钮背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
//字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: "#fff"
|
||||
},
|
||||
btnList: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
textField: {
|
||||
type: String,
|
||||
default: "text"
|
||||
},
|
||||
imgField: {
|
||||
type: String,
|
||||
default: "imgUrl"
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
//点击遮罩 是否可关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getBgColor() {
|
||||
return this.bgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getStyles() {
|
||||
let style = `z-index:${this.zIndex};`;
|
||||
if (this.left && this.right == 0) {
|
||||
style += `left:${this.left}rpx;`
|
||||
} else {
|
||||
style += `right:${this.right}rpx;`
|
||||
}
|
||||
|
||||
if (this.bottom && this.top == 0) {
|
||||
style += `bottom:${this.bottom}rpx;`
|
||||
} else {
|
||||
style += `top:${this.top}rpx;`
|
||||
}
|
||||
return style
|
||||
},
|
||||
getZIndex() {
|
||||
return Number(this.zIndex) - 2
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false,
|
||||
isHidden: true,
|
||||
timer: null
|
||||
};
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
handleClick: function(index) {
|
||||
this.isHidden = false
|
||||
clearTimeout(this.timer)
|
||||
if (index == -1 && this.btnList.length) {
|
||||
this.isOpen = !this.isOpen
|
||||
} else {
|
||||
this.$emit("click", {
|
||||
index: index
|
||||
})
|
||||
this.isOpen = false
|
||||
}
|
||||
if (!this.isOpen) {
|
||||
this.timer = setTimeout(() => {
|
||||
this.isHidden = true
|
||||
}, 200)
|
||||
}
|
||||
},
|
||||
handleClickCancel: function() {
|
||||
if (!this.maskClosable) return;
|
||||
this.isOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: 'tuifab';
|
||||
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAREAA0AAAAABnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEKAAAABoAAAAciPExJUdERUYAAAQIAAAAHgAAAB4AKQAKT1MvMgAAAaAAAABCAAAAVjyBSAVjbWFwAAAB9AAAAD4AAAFCAA/pvmdhc3AAAAQAAAAACAAAAAj//wADZ2x5ZgAAAkAAAABRAAAAYFkYQQNoZWFkAAABMAAAADAAAAA2Fm5OF2hoZWEAAAFgAAAAHQAAACQH3QOFaG10eAAAAeQAAAAPAAAAEAwAAANsb2NhAAACNAAAAAoAAAAKADAAAG1heHAAAAGAAAAAHwAAACABDwAobmFtZQAAApQAAAFJAAACiCnmEVVwb3N0AAAD4AAAAB8AAAAx2XRuznjaY2BkYGAAYtGolt54fpuvDNwsDCBwc1krH5xm/t/I/J+5FsjlYGACiQIAGAEKZHjaY2BkYGBu+N/AEMPCAALM/xkYGVABCwBZ4wNrAAAAeNpjYGRgYGBhkGEA0QwMTEDMBYQMDP/BfAYAC4kBOAB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PhJ8JMzf8b2CIYW5gaAAKM4LkAN21DAEAAHjaY2GAABYIZgYAAIMAEAB42mNgYGBmgGAZBkYGELAB8hjBfBYGBSDNAoRA/jPh//8hpOQHqEoGRjYGGJOBkQlIMDGgAkaGYQ8AUSIHswAAAAAAAAAAAAAAMAAAeNpjYGRg/t/I/J+5lkGagYFRUVCPUYmNXVCRj1FETFxRUI7RyMxcUNGO0USN+fS/HEY5XTnGfznicnLijFPAHMYpYnJyjFvBlBgWBQBNJxKpAAAAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMCiTIxMbFmZiRmJ+QALXAKKAAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAMAAQAEAAAAAgAAAAB42mNgYGBkAIKrS9Q5QPTNZa18MBoAPbcFzgAA) format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tui-fab-icon {
|
||||
font-family: "tuifab" !important;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.tui-icon-plus:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.tui-fab-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.tui-fab__box-bottom {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-fab__box-top {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.tui-fab-right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.tui-fab-btn {
|
||||
transform: scale(0);
|
||||
transition: all 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-fab-hidden {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
|
||||
.tui-fab-item-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tui-fab__pb40 {
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.tui-fab__pt40 {
|
||||
padding-top: 40rpx;
|
||||
}
|
||||
|
||||
.tui-fab-item-left {
|
||||
flex-flow: row-reverse;
|
||||
}
|
||||
|
||||
.tui-fab-title {
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tui-text-left {
|
||||
padding-left: 28rpx;
|
||||
}
|
||||
|
||||
.tui-text-right {
|
||||
padding-right: 28rpx;
|
||||
}
|
||||
|
||||
.tui-fab-img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tui-fab-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s linear;
|
||||
}
|
||||
|
||||
.tui-radius {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.tui-active {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.tui-fab-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
transition: all 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
</style>
|
||||
137
src/components/thorui/tui-footer/tui-footer.vue
Normal file
137
src/components/thorui/tui-footer/tui-footer.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<view class="tui-footer-class tui-footer" :class="[fixed?'tui-fixed':'']"
|
||||
:style='{backgroundColor:backgroundColor}'>
|
||||
<view class="tui-footer-link" v-if="navigate.length>0" :style="{color:getLinkColor}">
|
||||
<block v-for="(item,index) in navigate" :key="index">
|
||||
<navigator class="tui-link" hover-class="tui-link-hover" :hover-stop-propagation="true"
|
||||
:style="{color:(item.color || getLinkColor),fontSize:(item.size || 28)+'rpx'}"
|
||||
:open-type="item.type" :url="item[urlField]" :target="item.target" :delta="item.delta"
|
||||
:app-id="item.appid" :path="item.path" :extra-data="item.extradata" :bindsuccess="item.bindsuccess"
|
||||
:bindfail="item.bindfail">{{item[textField]}}</navigator>
|
||||
</block>
|
||||
</view>
|
||||
<view class="tui-footer-copyright" :style="{color:color,fontSize:size+'rpx'}">
|
||||
{{copyright}}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiFooter",
|
||||
props: {
|
||||
//type target url delta appid path extradata bindsuccess bindfail text color size
|
||||
//链接设置 数据格式对应上面注释的属性值
|
||||
navigate: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
urlField: {
|
||||
type: String,
|
||||
default: "url"
|
||||
},
|
||||
textField: {
|
||||
type: String,
|
||||
default: "text"
|
||||
},
|
||||
//底部文本
|
||||
copyright: {
|
||||
type: String,
|
||||
default: "All Rights Reserved."
|
||||
},
|
||||
//copyright 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: "#A7A7A7"
|
||||
},
|
||||
//copyright 字体大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
//footer背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "transparent"
|
||||
},
|
||||
//V2.8.0+
|
||||
linkColor: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
//是否固定在底部
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getLinkColor() {
|
||||
return this.linkColor || (uni && uni.$tui && uni.$tui.color.link) || '#586c94'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-footer {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 30rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-fixed {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tui-footer-link {
|
||||
/* color: #586c94; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.tui-link {
|
||||
position: relative;
|
||||
padding: 0 18rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tui-link::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
bottom: 0;
|
||||
border-right: 1px solid #d3d3d3;
|
||||
-webkit-transform-origin: 100% 0;
|
||||
transform-origin: 100% 0;
|
||||
-webkit-transform: scaleX(0.5);
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
|
||||
.tui-link:last-child::before {
|
||||
border-right: 0 !important
|
||||
}
|
||||
|
||||
.tui-link-hover {
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
.tui-footer-copyright {
|
||||
font-size: 24rpx;
|
||||
color: #A7A7A7;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
padding-top: 16rpx;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
</style>
|
||||
349
src/components/thorui/tui-form-button/tui-form-button.vue
Normal file
349
src/components/thorui/tui-form-button/tui-form-button.vue
Normal file
@ -0,0 +1,349 @@
|
||||
<template>
|
||||
<view class="tui-button__container" :style="{width: getWidth,height: height,margin:margin,borderRadius: getRadius}"
|
||||
@touchstart="handleStart" @touchend="handleClick" @touchcancel="handleEnd">
|
||||
<button class="tui-button" :class="[
|
||||
bold ? 'tui-text__bold' : '',
|
||||
time && (plain || link) ? 'tui-button__opacity' : '',
|
||||
disabled && !disabledBackground ? 'tui-button__opacity' : '',
|
||||
(!width || width==='100%' || width===true) && (!btnSize || btnSize===true)?'tui-button__flex-1':'',
|
||||
time && !plain && !link ? 'tui-button__active' : ''
|
||||
]" :style="{
|
||||
width: getWidth,
|
||||
height: getHeight,
|
||||
lineHeight: getHeight,
|
||||
background: disabled && disabledBackground ? disabledBackground : (plain ? 'transparent' : getBackground),
|
||||
borderWidth:borderWidth,
|
||||
borderColor: borderColor ? borderColor : disabled && disabledBackground ? disabledBackground : (link?'transparent':getBackground),
|
||||
borderRadius: getRadius,
|
||||
fontSize: getSize + 'rpx',
|
||||
color: disabled && disabledBackground ? disabledColor : getColor
|
||||
}" :loading="loading" :form-type="formType" :open-type="openType" :app-parameter="appParameter"
|
||||
@getuserinfo="bindgetuserinfo" @getphonenumber="bindgetphonenumber" @contact="bindcontact"
|
||||
@error="binderror" @opensetting="bindopensetting" @chooseavatar="bindchooseavatar"
|
||||
@launchapp="bindlaunchapp" :disabled="disabled" :scope="scope" @tap.stop="handleTap">
|
||||
<text class="tui-button__text" :class="{'tui-text__bold':bold}" v-if="text"
|
||||
:style="{fontSize: getSize + 'rpx',lineHeight:getSize + 'rpx',color: disabled && disabledBackground ? disabledColor : getColor}">{{text}}</text>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tui-form-button',
|
||||
emits: ['click', 'getuserinfo', 'contact', 'getphonenumber', 'error', 'opensetting', 'chooseavatar', 'launchapp'],
|
||||
// #ifdef MP-WEIXIN
|
||||
behaviors: ['wx://form-field-button'],
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
behaviors: ['swan://form-field'],
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
behaviors: ['qq://form-field'],
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
behaviors: ['uni://form-field'],
|
||||
// #endif
|
||||
props: {
|
||||
//按钮背景色
|
||||
background: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//按钮显示文本
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//按钮字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//按钮禁用背景色
|
||||
disabledBackground: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//按钮禁用字体颜色
|
||||
disabledColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
borderWidth: {
|
||||
type: String,
|
||||
// #ifdef APP-NVUE
|
||||
default: '0.5px'
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
default: '1px'
|
||||
// #endif
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
//高度
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//medium 368*80 / small 240*80/ mini 116*64
|
||||
btnSize: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//字体大小,单位rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//圆角
|
||||
radius: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//支付宝小程序
|
||||
//当 open-type 为 getAuthorize 时,可以设置 scope 为:phoneNumber、userInfo
|
||||
scope: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getWidth() {
|
||||
//medium 184*40 / small 120 40/ mini 58*32
|
||||
let width = this.width;
|
||||
if (this.btnSize && this.btnSize !== true) {
|
||||
width = {
|
||||
'medium': '368rpx',
|
||||
'small': '240rpx',
|
||||
'mini': '116rpx'
|
||||
} [this.btnSize] || this.width
|
||||
}
|
||||
return width
|
||||
},
|
||||
getHeight() {
|
||||
//medium 184*40 / small 120 40/ mini 58*32
|
||||
let height = this.height || (uni && uni.$tui && uni.$tui.tuiFormButton.height) || '96rpx';
|
||||
if (this.btnSize && this.btnSize !== true) {
|
||||
height = {
|
||||
'medium': '80rpx',
|
||||
'small': '80rpx',
|
||||
'mini': '64rpx'
|
||||
} [this.btnSize] || height
|
||||
}
|
||||
return height
|
||||
},
|
||||
getBackground() {
|
||||
return this.background || (uni && uni.$tui && uni.$tui.tuiFormButton.background) || '#5677fc';
|
||||
},
|
||||
getColor() {
|
||||
return this.color || (uni && uni.$tui && uni.$tui.tuiFormButton.color) || '#fff';
|
||||
},
|
||||
getRadius() {
|
||||
return this.radius || (uni && uni.$tui && uni.$tui.tuiFormButton.radius) || '6rpx';
|
||||
},
|
||||
getSize() {
|
||||
return this.size || (uni && uni.$tui && uni.$tui.tuiFormButton.size) || 32;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
time: 0,
|
||||
trigger: false,
|
||||
tap: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleStart() {
|
||||
if (this.disabled) return;
|
||||
this.trigger = false;
|
||||
this.tap = true;
|
||||
if (new Date().getTime() - this.time <= 150) return;
|
||||
this.trigger = true;
|
||||
this.time = new Date().getTime();
|
||||
},
|
||||
handleClick() {
|
||||
if (this.disabled || !this.trigger) return;
|
||||
this.time = 0;
|
||||
},
|
||||
handleTap() {
|
||||
if (this.disabled) return;
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
});
|
||||
},
|
||||
handleEnd() {
|
||||
if (this.disabled) return;
|
||||
setTimeout(() => {
|
||||
this.time = 0;
|
||||
}, 150);
|
||||
},
|
||||
bindgetuserinfo({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('getuserinfo', detail);
|
||||
},
|
||||
bindcontact({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('contact', detail);
|
||||
},
|
||||
bindgetphonenumber({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('getphonenumber', detail);
|
||||
},
|
||||
binderror({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('error', detail);
|
||||
},
|
||||
bindopensetting({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('opensetting', detail);
|
||||
},
|
||||
bindchooseavatar({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('chooseavatar', detail);
|
||||
},
|
||||
bindlaunchapp({
|
||||
detail = {}
|
||||
} = {}) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('launchapp', detail);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-button__container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-button {
|
||||
/* #ifdef APP-NVUE */
|
||||
border-width: 0.5px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
border-width: 1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* #endif */
|
||||
border-style: solid;
|
||||
position: relative;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
transform: translateZ(0);
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-button__flex-1 {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-button::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.tui-button__active {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.tui-button__active::after {
|
||||
content: ' ';
|
||||
background: var(--tui-button-active, rgba(0, 0, 0, .15));
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: none;
|
||||
z-index: 1;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.tui-button__text {
|
||||
text-align: center;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center !important;
|
||||
padding-left: 0 !important;
|
||||
|
||||
}
|
||||
|
||||
.tui-button__opacity {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tui-text__bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
47
src/components/thorui/tui-form-field/tui-form-field.vue
Normal file
47
src/components/thorui/tui-form-field/tui-form-field.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<view :class="{'tui-form__field':hidden}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//此组件只为form表单提交传递数据使用,暂时用于微信/百度小程序
|
||||
export default {
|
||||
emits: ['input', 'update:modelValue'],
|
||||
name: "tui-form-field",
|
||||
// #ifdef MP-WEIXIN
|
||||
behaviors: ['wx://form-field'],
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
behaviors: ['swan://form-field'],
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
behaviors: ['qq://form-field'],
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
behaviors: ['uni://form-field'],
|
||||
// #endif
|
||||
props: {
|
||||
//是否为隐藏域
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [Number, String, Array],
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: [Number, String, Array],
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-form__field {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
482
src/components/thorui/tui-form-item/tui-form-item.vue
Normal file
482
src/components/thorui/tui-form-item/tui-form-item.vue
Normal file
@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<view class="tui-form__item-outer"
|
||||
:style="{marginTop:marginTop+'rpx',marginBottom:marginBottom+'rpx',borderRadius:getRadius,background:getBgColor}">
|
||||
<view class="tui-form__item-wrap" :class="{'tui-form__highlight':highlight}"
|
||||
:style="{padding:getPadding,background:getBgColor,borderRadius:getRadius}" @tap="handleClick">
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view class="tui-form__asterisk" v-if="asterisk">
|
||||
<text :style="{color:getAsteriskColor}">*</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view class="tui-form__asterisk" v-if="asterisk" :style="{color:getAsteriskColor}">*</view>
|
||||
<!-- #endif -->
|
||||
<view class="tui-form__label" :style="getLabelStyl" v-if="label">{{label}}</view>
|
||||
<view class="tui-form__item-content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<slot name="right"></slot>
|
||||
<view v-if="bottomBorder" :style="{background:getBorderColor,left:left+'rpx',right:right+'rpx'}"
|
||||
class="tui-form__item-bottom"></view>
|
||||
<view class="tui-form__item-arrow" v-if="arrow" :style="{'border-color':getArrowColor}">
|
||||
</view>
|
||||
</view>
|
||||
<slot name="row"></slot>
|
||||
<view class="tui-form__item-error"
|
||||
:class="[absolute?'tui-form__error-absolute':'tui-form__error-relative',errorPosition==3?'tui-form__error-right':'',errorMsg && errorMsg!==true?'tui-form__error-active':'']"
|
||||
v-if="((!absolute && errorMsg && errorMsg!==true) || absolute) && prop"
|
||||
:style="{paddingLeft:getErrorLeft,paddingRight:getErrorRight}">
|
||||
<text class="tui-form__error-text" :class="{'tui-form__error-right':errorPosition==3}"
|
||||
:style="{color:getAsteriskColor}">{{errorMsg}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tui-form-item',
|
||||
emits: ['click'],
|
||||
inject: {
|
||||
form: {
|
||||
value: "form",
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
marginTop: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
marginBottom: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
labelSize: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//2.3.0+
|
||||
labelFontWeight: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//v2.9.2+
|
||||
labelWidth: {
|
||||
type: [Number, String],
|
||||
default: 160
|
||||
},
|
||||
labelRight: {
|
||||
type: [Number, String],
|
||||
default: 16
|
||||
},
|
||||
asterisk: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
asteriskColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
highlight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
bottomBorder: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
left: {
|
||||
type: [Number, String],
|
||||
default: 30
|
||||
},
|
||||
right: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//表单域 model 字段,在使用校验时该属性是必填的
|
||||
prop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//错误提示信息布局方式是否使用绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//错误提示位置:1-标题左对齐 2-内容左对齐 3-居右对齐
|
||||
position: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//表单验证规则,如果平台不支持传入函数,则使用setRules方法传入
|
||||
rules: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getPadding() {
|
||||
return this.padding || (uni && uni.$tui && uni.$tui.tuiFormItem.padding) || '28rpx 30rpx';
|
||||
},
|
||||
getBgColor() {
|
||||
return this.background || (uni && uni.$tui && uni.$tui.tuiFormItem.background) || '#fff';
|
||||
},
|
||||
getRadius() {
|
||||
return this.radius || (uni && uni.$tui && uni.$tui.tuiFormItem.radius) || '0';
|
||||
},
|
||||
getAsteriskColor() {
|
||||
return this.asteriskColor || (uni && uni.$tui && uni.$tui.tuiFormItem.asteriskColor) || '#EB0909';
|
||||
},
|
||||
getLabelStyl() {
|
||||
const labelSize = this.labelSize || (uni && uni.$tui && uni.$tui.tuiFormItem.labelSize) || 32;
|
||||
const labelColor = this.labelColor || (uni && uni.$tui && uni.$tui.tuiFormItem.labelColor) || '#333';
|
||||
const weight = this.labelFontWeight || (uni && uni.$tui && uni.$tui.tuiFormItem.labelFontWeight) || 400;
|
||||
return `width:${this.labelWidth}rpx;font-size:${labelSize}rpx;color:${labelColor};padding-right:${this.labelRight}rpx;font-weight:${weight};`
|
||||
},
|
||||
getArrowColor() {
|
||||
return this.arrowColor || (uni && uni.$tui && uni.$tui.tuiFormItem.arrowColor) || '#c0c0c0';
|
||||
},
|
||||
getBorderColor() {
|
||||
return this.borderColor || (uni && uni.$tui && uni.$tui.tuiFormItem.borderColor) || '#eaeef1';
|
||||
},
|
||||
errorPosition() {
|
||||
return this.position || (uni && uni.$tui && uni.$tui.tuiFormItem.position) || 2
|
||||
},
|
||||
getErrorRight() {
|
||||
//padding值不可为空,因为需要放置错误信息
|
||||
const padding = this.getPadding || '28rpx 30rpx'
|
||||
const arr = padding.split(' ')
|
||||
return arr[1] || arr[0] || '30rpx'
|
||||
},
|
||||
getErrorLeft() {
|
||||
const position = this.errorPosition
|
||||
let left = '30rpx'
|
||||
if (position == 2) {
|
||||
const pr = this.getErrorRight
|
||||
const pdr = pr ? pr.replace('rpx', '').replace('px', '') : 0;
|
||||
left = (Number(this.labelWidth) + Number(pdr)) + 'rpx'
|
||||
}
|
||||
return left;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMsg: '',
|
||||
showError: false,
|
||||
itemValue: '',
|
||||
watchKey: '',
|
||||
//是否即时校验
|
||||
isImmediate: false,
|
||||
//item项自己的rules
|
||||
formItemRules: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
prop: {
|
||||
handler(val) {
|
||||
const key = `form.model.${val || 'prop_key_empty'}`
|
||||
if (val && val !== true && this.form && key != this.watchKey) {
|
||||
this.watchKey = key
|
||||
this.$watch(key, (val) => {
|
||||
if (this.isImmediate && this.prop && this.form) {
|
||||
this.form.immediateValidator(this.prop).then(res => {
|
||||
if (res.isPass) {
|
||||
this.errorMsg = ''
|
||||
} else {
|
||||
this.errorMsg = res.errorMsg
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err.errorMsg)
|
||||
})
|
||||
} else {
|
||||
if (this.showError && val != this.itemValue) {
|
||||
this.errorMsg = ''
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.uninstall()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.uninstall()
|
||||
},
|
||||
// #endif
|
||||
created() {
|
||||
if (this.form) {
|
||||
this.form.children.push(this)
|
||||
//主要用于动态表单初始化值
|
||||
this.isImmediate = this.form.isImmediate;
|
||||
this.showError = this.form.showMessage ? false : true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: this.index
|
||||
});
|
||||
},
|
||||
//设置校验规则
|
||||
setRules(rules) {
|
||||
this.formItemRules = rules
|
||||
},
|
||||
//设置校验规则,并合并或替换Form组件中该prop对应的rules【当页面调用Form组件校验方法传入rules时进行合并操作】
|
||||
//该方法作为备用方法,如果有需要再进行放开【仅当动态formItem组件,并且当前rules未设置在总rules数据里时使用】
|
||||
setRulesMerge(rules) {
|
||||
this.formItemRules = rules || this.rules
|
||||
if (this.form) {
|
||||
const index = this.form.concatRules.findIndex(e => e.name === rules.name || e.name === this.prop)
|
||||
const rule = this.getRules()
|
||||
if (!rule) return;
|
||||
if (index === -1) {
|
||||
this.form.concatRules.push(rule)
|
||||
} else {
|
||||
this.form.concatRules[index] = rule
|
||||
}
|
||||
}
|
||||
},
|
||||
//是否开启即时校验
|
||||
immediateValidate(isOpen) {
|
||||
this.isImmediate = isOpen;
|
||||
},
|
||||
// Form组件获取当前FormItem 项 rules数据
|
||||
getRules() {
|
||||
//优先使用setRules 方法传入的rules值
|
||||
const rules = this.formItemRules || this.rules
|
||||
if (!rules.name && (rules.rule || rules.validator)) {
|
||||
rules['name'] = this.prop
|
||||
}
|
||||
//当未传入prop则不进行校验
|
||||
return !rules.name ? null : rules
|
||||
},
|
||||
/**
|
||||
* 验证方法
|
||||
* @param {any} value 值,不传则使用Form组件model中值
|
||||
*/
|
||||
validate(value) {
|
||||
const rules = this.getRules()
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.form && rules) {
|
||||
const model = {}
|
||||
let val = value;
|
||||
if (val === undefined || val === null) {
|
||||
val = this.form.model[rules.name] || null
|
||||
}
|
||||
model[rules.name] = val;
|
||||
|
||||
this.form.immediateValidator(rules.name, model, [rules]).then(res => {
|
||||
if (res.isPass) {
|
||||
this.errorMsg = ''
|
||||
} else {
|
||||
this.errorMsg = res.errorMsg
|
||||
}
|
||||
resolve(res)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
console.log(err.errorMsg)
|
||||
})
|
||||
}else{
|
||||
reject({
|
||||
isPass: false,
|
||||
errorMsg: '未检测到Form组件或rules校验规则数据!'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
clearValidate() {
|
||||
this.errorMsg = ''
|
||||
},
|
||||
uninstall() {
|
||||
this.form && this.form.uninstall(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-form__item-outer {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-form__item-wrap {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-form__highlight:active {
|
||||
background-color: #f1f1f1 !important;
|
||||
}
|
||||
|
||||
.tui-form__asterisk {
|
||||
position: absolute;
|
||||
left: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
height: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
line-height: 1.15;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-form__item-label {
|
||||
padding-right: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-form__item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tui-form__item-bottom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
/* #ifdef APP-NVUE */
|
||||
height: 0.5px;
|
||||
z-index: -1;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
height: 1px;
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
z-index: 1;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-form__item-arrow {
|
||||
height: 40rpx;
|
||||
width: 40rpx;
|
||||
border-width: 3px 3px 0 0;
|
||||
border-style: solid;
|
||||
transform: rotate(45deg) scale(0.5);
|
||||
/* #ifndef APP-NVUE */
|
||||
border-radius: 4rpx;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
border-top-right-radius: 3rpx;
|
||||
/* #endif */
|
||||
transform-origin: center center;
|
||||
margin-right: -5.8579rpx;
|
||||
}
|
||||
|
||||
.tui-form__item-error {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
font-size: 24rpx;
|
||||
line-height: 32rpx;
|
||||
}
|
||||
|
||||
.tui-form__error-relative {
|
||||
position: relative;
|
||||
padding-top: 4rpx;
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.tui-form__error-absolute {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
transform: translateY(-100%);
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
transform: translateY(-24rpx);
|
||||
/* #endif */
|
||||
opacity: 0;
|
||||
transition-property: transform, opacity;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.tui-form__error-active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.tui-form__error-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.tui-form__error-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tui-form__label {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
370
src/components/thorui/tui-form/tui-form.vue
Normal file
370
src/components/thorui/tui-form/tui-form.vue
Normal file
@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<view class="tui-form__box" :style="{backgroundColor:backgroundColor,padding:padding,borderRadius:radius}">
|
||||
<slot></slot>
|
||||
<view class="tui-form__errmsg"
|
||||
:style="{top:tipTop+'px',padding:tipPadding,backgroundColor:getTipBgColor,borderRadius:tipRidus}"
|
||||
v-if="showMessage" :class="{'tui-message__show':errorMsg}"><text class="tui-form__text"
|
||||
:style="{fontSize:tipSize+'rpx',color:tipColor}">{{errorMsg}}</text></view>
|
||||
<view class="tui-form__mask" v-if="disabled"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import form from "./tui-validation.js"
|
||||
export default {
|
||||
name: "tui-form",
|
||||
provide() {
|
||||
return {
|
||||
form: this
|
||||
}
|
||||
},
|
||||
props: {
|
||||
//表单数据对象
|
||||
model: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
//表单验证规则,即将废弃
|
||||
rules: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//表单背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
//表单padding值
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//是否顶部弹出方式显示校验错误信息【为false时则可关联FormItem组件显示校验错误信息】
|
||||
showMessage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//表单圆角值
|
||||
radius: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//是否禁用该表单内的所有组件,透明遮罩层
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//提示框top值 px
|
||||
tipTop: {
|
||||
type: [Number, String],
|
||||
// #ifdef H5
|
||||
default: 44,
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
default: 0
|
||||
// #endif
|
||||
},
|
||||
//错误提示框padding值
|
||||
tipPadding: {
|
||||
type: String,
|
||||
default: '20rpx'
|
||||
},
|
||||
//错误提示框背景色
|
||||
tipBackgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//错误提示字体大小
|
||||
tipSize: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
//错误提示字体颜色
|
||||
tipColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//错误提示框圆角值
|
||||
tipRidus: {
|
||||
type: String,
|
||||
default: '12rpx'
|
||||
},
|
||||
//错误消息显示时间 ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getTipBgColor() {
|
||||
return this.tipBackgroundColor || (uni && uni.$tui && uni.$tui.tuiForm.tipBackgroundColor) ||
|
||||
'#f74d54';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showMessage(val) {
|
||||
if (this.children && this.children.length > 0) {
|
||||
this.children.forEach(item => {
|
||||
item.showError = val ? false : true
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMsg: '',
|
||||
timer: null,
|
||||
formRules: [],
|
||||
isImmediate: false,
|
||||
concatRules: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.clearTimer()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
clearTimer() {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null;
|
||||
this.children = null
|
||||
},
|
||||
getFormItemRules() {
|
||||
let rules = []
|
||||
if (this.children && this.children.length > 0) {
|
||||
this.children.forEach(child => {
|
||||
let rule = child.getRules()
|
||||
rule && rules.push(rule)
|
||||
})
|
||||
}
|
||||
return rules;
|
||||
},
|
||||
getMergeRules(rules) {
|
||||
if (this.concatRules.length === 0) return rules;
|
||||
let formRules = [...rules]
|
||||
//合并并替换当前rules数据
|
||||
this.concatRules.forEach(item => {
|
||||
const index = rules.findIndex(e => e.name === item.name)
|
||||
if (index === -1) {
|
||||
formRules.push(item)
|
||||
} else {
|
||||
formRules[index] = item;
|
||||
}
|
||||
})
|
||||
return formRules;
|
||||
},
|
||||
//{Object} model 表单数据对象,传null则使用属性中model值
|
||||
//{Array} rules 表单验证规则,传null则使用FormItem项内传入的rules值
|
||||
//{Boolean} checkAll 是否返回所有错误信息
|
||||
validate(model, rules, checkAll = false) {
|
||||
model = model || this.model
|
||||
rules = rules || this.rules || []
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
if (rules.length === 0) {
|
||||
rules = this.getFormItemRules()
|
||||
} else {
|
||||
rules = this.getMergeRules(rules)
|
||||
}
|
||||
let res = form.validation(model, rules, checkAll);
|
||||
if (!res.isPass) {
|
||||
let errors = res.errorMsg;
|
||||
if (this.showMessage) {
|
||||
this.clearTimer()
|
||||
if (checkAll) {
|
||||
errors = errors[0].msg
|
||||
}
|
||||
this.errorMsg = errors;
|
||||
const duration = this.duration || (uni && uni.$tui && uni.$tui.tuiForm.duration) ||
|
||||
2000
|
||||
this.timer = setTimeout(() => {
|
||||
this.errorMsg = ''
|
||||
}, Number(duration))
|
||||
} else {
|
||||
if (checkAll && this.children && this.children.length > 0) {
|
||||
//FormItem组件 显示提示
|
||||
this.children.forEach(item => {
|
||||
const index = errors.findIndex(err => err.name === item.prop)
|
||||
if (item.prop && item.prop !== true && ~index) {
|
||||
item.errorMsg = errors[index].msg
|
||||
item.itemValue = model[item.prop]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(res)
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
reject({
|
||||
isPass: false,
|
||||
errorMsg: '校验出错,请检查数据格式是否有误!'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
//结合FormItem组件进行校验时 是否开启即时校验
|
||||
immediateValidate(isOpen, rules = []) {
|
||||
this.isImmediate = isOpen;
|
||||
if (isOpen) {
|
||||
if (!rules || rules.length === 0) {
|
||||
rules = this.getFormItemRules()
|
||||
} else {
|
||||
rules = this.getMergeRules(rules)
|
||||
}
|
||||
this.formRules = rules || []
|
||||
}
|
||||
if (this.children && this.children.length > 0) {
|
||||
this.children.forEach(item => {
|
||||
item.immediateValidate(isOpen)
|
||||
})
|
||||
}
|
||||
},
|
||||
//校验
|
||||
immediateValidator(prop, model, rules) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let res = form.validation(model || this.model, rules || this.formRules, true);
|
||||
if (!res.isPass) {
|
||||
//显示提示
|
||||
let errors = res.errorMsg;
|
||||
const index = errors.findIndex(err => err.name === prop)
|
||||
if (~index) {
|
||||
res.errorMsg = errors[index].msg
|
||||
} else {
|
||||
res.isPass = true
|
||||
res.errorMsg = ''
|
||||
}
|
||||
}
|
||||
resolve(res)
|
||||
} catch (e) {
|
||||
reject({
|
||||
isPass: false,
|
||||
errorMsg: '校验出错,请检查数据格式是否有误!'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
clearValidate(props = []) {
|
||||
let arr = props;
|
||||
arr = !arr ? [] : arr
|
||||
if (typeof props === 'string') {
|
||||
arr = [props]
|
||||
}
|
||||
if (this.children && this.children.length > 0) {
|
||||
//清除指定字段的表单验证信息
|
||||
if (arr && arr.length > 0) {
|
||||
this.children.forEach(item => {
|
||||
if (item.prop && ~arr.indexOf(item.prop)) {
|
||||
item.errorMsg = ''
|
||||
}
|
||||
})
|
||||
} else {
|
||||
//清除所有字段的表单验证信息
|
||||
this.children.forEach(item => {
|
||||
item.errorMsg = ''
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 验证具体的某个字段
|
||||
* @param {Array<string> | String} props 字段key
|
||||
* @param {Array} rules 表单验证规则,当传null 或空数组时使用FormItem组件内rules
|
||||
* @param {Object} model 表单数据对象,不传则使用属性中model值
|
||||
*/
|
||||
validateField(props, rules, model) {
|
||||
if (!rules || rules.length === 0) {
|
||||
rules = this.getFormItemRules()
|
||||
} else {
|
||||
rules = this.getMergeRules(rules)
|
||||
}
|
||||
const isString = typeof props === 'string';
|
||||
const formRules = rules.filter(item => props === item.name || (!isString && props
|
||||
.indexOf(item.name) !== -1));
|
||||
model = model || this.model
|
||||
return this.validate(model, formRules, true)
|
||||
},
|
||||
// 移除表单项
|
||||
uninstall(instance) {
|
||||
if (this.children && this.children.length > 0) {
|
||||
const index = this.children.findIndex(item => item === instance)
|
||||
if (index !== -1) {
|
||||
this.children.splice(index, 1)
|
||||
}
|
||||
const rules = instance.getRules() || {}
|
||||
const prop = instance.prop || rules.name || ''
|
||||
const idx = this.concatRules.findIndex(ru => ru.name === prop)
|
||||
if (idx !== -1) {
|
||||
this.concatRules.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-form__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-form__errmsg {
|
||||
position: fixed;
|
||||
z-index: 900;
|
||||
text-align: center;
|
||||
left: 20rpx;
|
||||
right: 20rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
word-break: break-all;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx;
|
||||
opacity: 0;
|
||||
transform: translateZ(0) translateY(-100%);
|
||||
transition-property: transform, opacity;
|
||||
transition-duration: 0.25s;
|
||||
transition-delay: 0s;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.tui-form__text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tui-message__show {
|
||||
transform: translateY(0) translateZ(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-form__mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
346
src/components/thorui/tui-form/tui-validation.js
Normal file
346
src/components/thorui/tui-form/tui-validation.js
Normal file
@ -0,0 +1,346 @@
|
||||
/**
|
||||
* 表单验证
|
||||
* @author echo.
|
||||
* @version 2.9.2+
|
||||
**/
|
||||
|
||||
const form = {
|
||||
//非必填情况下,如果值为空,则不进行校验
|
||||
//当出现错误时返回错误消息,否则返回空即为验证通过
|
||||
/*
|
||||
formData:Object 表单对象。{key:value,key:value},key==rules.name
|
||||
rules: Array [{name:name,rule:[],msg:[],validator:[],{name:name,rule:[],msg:[],validator:[]}]
|
||||
name:name 属性=> 元素的名称
|
||||
rule:字符串数组 ["required","isMobile","isEmail","isCarNo","isIdCard","isAmount","isNum","isChinese","isNotChinese","isEnglish",isEnAndNo","isSpecial","isEmoji",""isDate","isUrl","isSame:key","range:[1,9]","minLength:9","maxLength:Number","isKeyword:key1,key2,key3..."]
|
||||
msg:数组 []。 与数组 rule 长度相同,对应的错误提示信息
|
||||
validator:[{msg:'错误消息',method:Function}],自定义验证方法组,函数约定:(value)=>{ return true or false}
|
||||
checkAll:是否返回所有错误信息
|
||||
*/
|
||||
validation: function(formData, rules, checkAll = false) {
|
||||
let result = {
|
||||
isPass: true,
|
||||
errorMsg: checkAll ? [] : ''
|
||||
};
|
||||
for (let item of rules) {
|
||||
let key = item.name;
|
||||
let rule = item.rule || [];
|
||||
let validator = item.validator || [];
|
||||
let msgArr = item.msg;
|
||||
const ruleLen = rule.length;
|
||||
const validatorLen = validator.length;
|
||||
let itemVal = formData[key];
|
||||
itemVal = (itemVal === null || itemVal === undefined ? '' : itemVal).toString();
|
||||
if (!key || (ruleLen === 0 && validatorLen === 0) || (!~rule.indexOf("required") &&
|
||||
itemVal.length === 0)) {
|
||||
continue;
|
||||
}
|
||||
for (let i = 0, length = rule.length; i < length; i++) {
|
||||
let ruleItem = rule[i];
|
||||
let msg = msgArr[i];
|
||||
if (!msg || !ruleItem) continue;
|
||||
//数据处理
|
||||
let value = null;
|
||||
if (~ruleItem.indexOf(":")) {
|
||||
let temp = ruleItem.split(":");
|
||||
ruleItem = temp[0];
|
||||
value = temp[1];
|
||||
}
|
||||
let isError = false;
|
||||
switch (ruleItem) {
|
||||
case "required":
|
||||
isError = form._isNullOrEmpty(formData[key]);
|
||||
break;
|
||||
case "isMobile":
|
||||
isError = !form._isMobile(formData[key]);
|
||||
break;
|
||||
case "isEmail":
|
||||
isError = !form._isEmail(formData[key]);
|
||||
break;
|
||||
case "isCarNo":
|
||||
isError = !form._isCarNo(formData[key]);
|
||||
break;
|
||||
case "isIdCard":
|
||||
isError = !form._isIdCard(formData[key]);
|
||||
break;
|
||||
case "isAmount":
|
||||
isError = !form._isAmount(formData[key]);
|
||||
break;
|
||||
case "isNum":
|
||||
isError = !form._isNum(formData[key]);
|
||||
break;
|
||||
case "isChinese":
|
||||
isError = !form._isChinese(formData[key]);
|
||||
break;
|
||||
case "isNotChinese":
|
||||
isError = !form._isNotChinese(formData[key]);
|
||||
break;
|
||||
case "isEnglish":
|
||||
isError = !form._isEnglish(formData[key]);
|
||||
break;
|
||||
case "isEnAndNo":
|
||||
isError = !form._isEnAndNo(formData[key]);
|
||||
break;
|
||||
case "isEnOrNo":
|
||||
isError = !form._isEnOrNo(formData[key]);
|
||||
break;
|
||||
case "isSpecial":
|
||||
isError = form._isSpecial(formData[key]);
|
||||
break;
|
||||
case "isEmoji":
|
||||
isError = form._isEmoji(formData[key]);
|
||||
break;
|
||||
case "isDate":
|
||||
isError = !form._isDate(formData[key]);
|
||||
break;
|
||||
case "isUrl":
|
||||
isError = !form._isUrl(formData[key]);
|
||||
break;
|
||||
case "isSame":
|
||||
isError = !form._isSame(formData[key], formData[value]);
|
||||
break;
|
||||
case "range":
|
||||
let range = null;
|
||||
try {
|
||||
range = JSON.parse(value);
|
||||
if (range.length <= 1) {
|
||||
throw new Error("range值传入有误!")
|
||||
}
|
||||
} catch (e) {
|
||||
return "range值传入有误!"
|
||||
}
|
||||
isError = !form._isRange(formData[key], range[0], range[1])
|
||||
break;
|
||||
case "minLength":
|
||||
isError = !form._minLength(formData[key], value)
|
||||
break;
|
||||
case "maxLength":
|
||||
isError = !form._maxLength(formData[key], value)
|
||||
break;
|
||||
case "isKeyword":
|
||||
isError = !form._isKeyword(formData[key], value)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
result.isPass = false;
|
||||
if (checkAll) {
|
||||
result.errorMsg.push({
|
||||
name: key,
|
||||
msg: msg
|
||||
})
|
||||
break;
|
||||
} else {
|
||||
result.errorMsg = msg;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (validator && validator.length > 0) {
|
||||
for (let model of validator) {
|
||||
let func = model.method;
|
||||
if (func && !func(formData[key])) {
|
||||
result.isPass = false;
|
||||
if (checkAll) {
|
||||
const index = result.errorMsg.findIndex(item => item.name === key)
|
||||
if (index === -1) {
|
||||
result.errorMsg.push({
|
||||
name: key,
|
||||
msg: model.msg || `${key} error !`
|
||||
})
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
result.errorMsg = model.msg || `${key} error !`;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
//允许填写字符串null或者undefined
|
||||
_isNullOrEmpty: function(value) {
|
||||
return (value === null || value === '' || value === undefined) ? true : false;
|
||||
},
|
||||
_isMobile: function(value) {
|
||||
return /^(?:13\d|14\d|15\d|16\d|17\d|18\d|19\d)\d{5}(\d{3}|\*{3})$/.test(value);
|
||||
},
|
||||
_isEmail: function(value) {
|
||||
return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(value);
|
||||
},
|
||||
_isCarNo: function(value) {
|
||||
// 新能源车牌
|
||||
const xreg =
|
||||
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
|
||||
// 旧车牌
|
||||
const creg =
|
||||
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
|
||||
if (value.length === 7) {
|
||||
return creg.test(value);
|
||||
} else if (value.length === 8) {
|
||||
return xreg.test(value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
_isIdCard: function(value) {
|
||||
let idCard = value;
|
||||
if (idCard.length == 15) {
|
||||
return this.__isValidityBrithBy15IdCard;
|
||||
} else if (idCard.length == 18) {
|
||||
let arrIdCard = idCard.split("");
|
||||
if (this.__isValidityBrithBy18IdCard(idCard) && this.__isTrueValidateCodeBy18IdCard(arrIdCard)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
__isTrueValidateCodeBy18IdCard: function(arrIdCard) {
|
||||
let sum = 0;
|
||||
let Wi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
|
||||
let ValideCode = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2];
|
||||
if (arrIdCard[17].toLowerCase() == 'x') {
|
||||
arrIdCard[17] = 10;
|
||||
}
|
||||
for (let i = 0; i < 17; i++) {
|
||||
sum += Wi[i] * arrIdCard[i];
|
||||
}
|
||||
let valCodePosition = sum % 11;
|
||||
if (arrIdCard[17] == ValideCode[valCodePosition]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
__isValidityBrithBy18IdCard: function(idCard18) {
|
||||
let year = idCard18.substring(6, 10);
|
||||
let month = idCard18.substring(10, 12);
|
||||
let day = idCard18.substring(12, 14);
|
||||
let temp_date = new Date(year, parseFloat(month) - 1, parseFloat(day));
|
||||
if (temp_date.getFullYear() != parseFloat(year) || temp_date.getMonth() != parseFloat(month) - 1 ||
|
||||
temp_date.getDate() !=
|
||||
parseFloat(day)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
__isValidityBrithBy15IdCard: function(idCard15) {
|
||||
let year = idCard15.substring(6, 8);
|
||||
let month = idCard15.substring(8, 10);
|
||||
let day = idCard15.substring(10, 12);
|
||||
let temp_date = new Date(year, parseFloat(month) - 1, parseFloat(day));
|
||||
|
||||
if (temp_date.getFullYear() != parseFloat(year) || temp_date.getMonth() != parseFloat(month) - 1 ||
|
||||
temp_date.getDate() !=
|
||||
parseFloat(day)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
_isAmount: function(value) {
|
||||
//金额,只允许保留两位小数
|
||||
return /^([0-9]*[.]?[0-9])[0-9]{0,1}$/.test(value);
|
||||
},
|
||||
_isNum: function(value) {
|
||||
//只能为数字
|
||||
return /^[0-9]+$/.test(value);
|
||||
},
|
||||
//是否全部为中文
|
||||
_isChinese: function(value) {
|
||||
let reg = /^[\u4e00-\u9fa5]+$/;
|
||||
return value !== "" && reg.test(value) && !form._isSpecial(value) && !form._isEmoji(value)
|
||||
},
|
||||
//是否不包含中文,可以有特殊字符
|
||||
_isNotChinese: function(value) {
|
||||
let reg = /.*[\u4e00-\u9fa5]+.*$/;
|
||||
let result = true;
|
||||
if (reg.test(value)) {
|
||||
result = false
|
||||
}
|
||||
return result
|
||||
},
|
||||
_isEnglish: function(value) {
|
||||
return /^[a-zA-Z]*$/.test(value)
|
||||
},
|
||||
_isEnAndNo: function(value) {
|
||||
//8~20位数字和字母组合
|
||||
return /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,20}$/.test(value);
|
||||
},
|
||||
_isEnOrNo: function(value) {
|
||||
//英文或者数字
|
||||
let reg = /.*[\u4e00-\u9fa5]+.*$/;
|
||||
let result = true;
|
||||
if (reg.test(value) || form._isSpecial(value) || form._isEmoji(value)) {
|
||||
result = false
|
||||
}
|
||||
return result
|
||||
},
|
||||
_isSpecial: function(value) {
|
||||
//是否包含特殊字符
|
||||
let regEn = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im,
|
||||
regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]]/im;
|
||||
if (regEn.test(value) || regCn.test(value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
_isEmoji: function(value) {
|
||||
//是否包含表情
|
||||
return /\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g.test(value);
|
||||
},
|
||||
_isDate: function(value) {
|
||||
//2019-10-12
|
||||
const reg =
|
||||
/^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;
|
||||
return reg.test(value);
|
||||
},
|
||||
_isUrl: function(value) {
|
||||
return /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})(:[0-9]{1,5})?((\/?)|(\/[\\\w_!~*\\'()\\\.;?:@&=+$,%#-]+)+\/?)$/
|
||||
.test(value);
|
||||
},
|
||||
_isSame: function(value1, value2) {
|
||||
return value1 === value2
|
||||
},
|
||||
_isRange: function(value, range1, range2) {
|
||||
if ((!range1 && range1 != 0) && (!range2 && range2 != 0)) {
|
||||
return true;
|
||||
} else if (!range1 && range1 != 0) {
|
||||
return value <= range2
|
||||
} else if (!range2 && range2 != 0) {
|
||||
return value >= range1
|
||||
} else {
|
||||
return value >= range1 && value <= range2
|
||||
}
|
||||
},
|
||||
_minLength: function(value, min) {
|
||||
return value.length >= Number(min)
|
||||
},
|
||||
_maxLength: function(value, max) {
|
||||
return value.length <= Number(max)
|
||||
},
|
||||
_isKeyword: function(value, keywords) {
|
||||
//是否包含关键词,敏感词,多个以英文逗号分隔,包含则为false,弹出提示语!
|
||||
let result = true;
|
||||
if (!keywords) return result;
|
||||
let key = keywords.split(',');
|
||||
for (let i = 0, len = key.length; i < len; i++) {
|
||||
if (~value.indexOf(key[i])) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
export default {
|
||||
validation: form.validation
|
||||
};
|
||||
212
src/components/thorui/tui-gallery/tui-gallery.vue
Normal file
212
src/components/thorui/tui-gallery/tui-gallery.vue
Normal file
@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<view class="tui-gallery" :class="{'tui-gallery_show':show}" @tap="hideGallery">
|
||||
<view class="tui-gallery__info">{{currentIndex+1}}/{{getLen}}</view>
|
||||
<swiper class="tui-gallery__img__wrap" :indicator-dots="false" @change="change" :current="defCurIndex"
|
||||
:autoplay="false" :duration="500">
|
||||
<swiper-item v-for="(item,index) in imgUrls" :key="index">
|
||||
<image mode="aspectFit" class="tui-gallery__img" :src="item[srcField]"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="tui-gallery__desc" v-if="!showDelete">
|
||||
{{getDesc(currentIndex,imgUrls)}}
|
||||
</view>
|
||||
<view class="tui-gallery__operate" hover-class="tui-opacity__del" :hover-start-time="150" @tap.stop="deleteImg"
|
||||
v-if="showDelete">
|
||||
删除
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiGallery',
|
||||
emits: ['change', 'delete', 'hide'],
|
||||
props: {
|
||||
urls: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
srcField: {
|
||||
type: String,
|
||||
default: 'src'
|
||||
},
|
||||
descField: {
|
||||
type: String,
|
||||
default: 'desc'
|
||||
},
|
||||
showDelete: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
hideOnClick: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getLen() {
|
||||
return this.imgUrls.length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
urls(newVal, oldVal) {
|
||||
this.imgUrls = newVal
|
||||
},
|
||||
current(newVal) {
|
||||
this.defCurIndex = this.currentIndex;
|
||||
let val = Number(newVal)
|
||||
setTimeout(() => {
|
||||
this.defCurIndex = val;
|
||||
this.currentIndex = val;
|
||||
}, 20)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.defCurIndex = Number(this.current);
|
||||
this.currentIndex = this.defCurIndex;
|
||||
this.imgUrls = this.urls;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgUrls: [],
|
||||
currentIndex: 0,
|
||||
defCurIndex: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getDesc(index, imgUrls) {
|
||||
let desc = ''
|
||||
let item = imgUrls[index]
|
||||
if (item) {
|
||||
desc = item[this.descField]
|
||||
}
|
||||
return desc
|
||||
},
|
||||
change(e) {
|
||||
this.currentIndex = e.detail.current
|
||||
this.$emit('change', {
|
||||
current: e.detail.current
|
||||
});
|
||||
},
|
||||
deleteImg() {
|
||||
const imgs = this.imgUrls;
|
||||
const url = imgs.splice(this.current, 1);
|
||||
this.$emit('delete', {
|
||||
url: url[0],
|
||||
index: this.current
|
||||
});
|
||||
|
||||
if (imgs.length === 0) {
|
||||
this.hideGallery();
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentIndex = 0;
|
||||
this.imgUrls = imgs
|
||||
},
|
||||
hideGallery() {
|
||||
if (this.hideOnClick) {
|
||||
this.$emit('hide', {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-gallery {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #000;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tui-gallery__img,
|
||||
.tui-gallery__operate,
|
||||
.tui-gallery__desc {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
left: constant(safe-area-inset-left);
|
||||
left: env(safe-area-inset-left);
|
||||
right: 0;
|
||||
right: constant(safe-area-inset-right);
|
||||
right: env(safe-area-inset-right)
|
||||
}
|
||||
|
||||
.tui-gallery__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
top: constant(safe-area-inset-top);
|
||||
top: env(safe-area-inset-top);
|
||||
bottom: 60px;
|
||||
bottom: calc(60px + constant(safe-area-inset-bottom));
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
background: 50% no-repeat;
|
||||
background-size: contain
|
||||
}
|
||||
|
||||
.tui-gallery__operate,
|
||||
.tui-gallery__desc {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding-bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background-color: #0d0d0d;
|
||||
color: #fff;
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 30rpx;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
.tui-gallery__info {
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
line-height: 60px;
|
||||
min-height: 60px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.tui-gallery__img__wrap {
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
font-size: 0
|
||||
}
|
||||
|
||||
.tui-gallery__operate {
|
||||
position: static
|
||||
}
|
||||
|
||||
.tui-gallery_show {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: nowrap !important;
|
||||
}
|
||||
|
||||
.tui-opacity__del {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
215
src/components/thorui/tui-grade/tui-grade.vue
Normal file
215
src/components/thorui/tui-grade/tui-grade.vue
Normal file
@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<view class="tui-grade__box" :class="[classBaidu]" @touchstart="touchStart" @touchmove="touchMove">
|
||||
<block v-for="(item, index) in quantityArr" :key="index">
|
||||
<view class="tui-icon__grade"
|
||||
:class="['tui-icon__star' + (index < intScore ? 'full' : '') + (decimalScore > 0 && index == intScore ? 'half' : '')]"
|
||||
@tap="touchMoveTo" :style="{
|
||||
width: (width < size ? size : width) + 'px',
|
||||
fontSize: size + 'px',
|
||||
color: index < intScore || (decimalScore > 0 && index == intScore) ? getActiveColor : normal
|
||||
}"></view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiGrade',
|
||||
emits: ['change'],
|
||||
props: {
|
||||
//数量
|
||||
quantity: {
|
||||
type: [Number, String],
|
||||
default: 5
|
||||
},
|
||||
//当前评分,小数只支持0.5
|
||||
score: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//禁用点击
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//星星大小 px
|
||||
size: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
//星星宽度 px,大于等于size值,设置间距使用
|
||||
width: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
//未选中颜色
|
||||
normal: {
|
||||
type: String,
|
||||
default: '#b2b2b2'
|
||||
},
|
||||
//选中颜色
|
||||
active: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//是否支持半星选择/展示
|
||||
isHalf: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//超过多少比例选中半星(取值区间0~0.5)
|
||||
halfRate: {
|
||||
type: Number,
|
||||
default: 0.25
|
||||
},
|
||||
//自定义参数
|
||||
params: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
getActiveColor(){
|
||||
return this.active || (uni && uni.$tui && uni.$tui.color.danger) || '#EB0909'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let classBaidu = `tui_11_${Math.ceil(Math.random() * 10e5).toString(36)}`;
|
||||
return {
|
||||
classBaidu,
|
||||
pageX: 0,
|
||||
intScore: 0,
|
||||
decimalScore: 0,
|
||||
quantityArr: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.quantityArr = this.generateArray(1, Number(this.quantity))
|
||||
this.starActive(this.score);
|
||||
},
|
||||
watch: {
|
||||
quantity(val) {
|
||||
this.quantityArr = this.generateArray(1, Number(val))
|
||||
},
|
||||
score(val) {
|
||||
this.starActive(val);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateArray: function(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
starActive(val) {
|
||||
val = Number(val);
|
||||
let intVal = parseInt(val);
|
||||
let decimalVal = val % 1;
|
||||
if (!this.isHalf) {
|
||||
intVal = decimalVal > 0 ? intVal + 1 : intVal;
|
||||
decimalVal = 0;
|
||||
}
|
||||
this.intScore = intVal;
|
||||
this.decimalScore = decimalVal;
|
||||
},
|
||||
touchMoveTo(e) {
|
||||
// #ifdef H5
|
||||
this.touchMove(e)
|
||||
// #endif
|
||||
},
|
||||
touchStart(e) {
|
||||
// #ifndef H5
|
||||
this.touchMove(e)
|
||||
// #endif
|
||||
},
|
||||
touchMove(e) {
|
||||
if (this.disabled || !e.changedTouches[0]) return;
|
||||
const movePageX = e.changedTouches[0].pageX;
|
||||
const distance = movePageX - this.pageX;
|
||||
let score = 0;
|
||||
if (distance > 0) {
|
||||
score = distance / this.width;
|
||||
let decimalScore = score % 1;
|
||||
if (!this.isHalf) {
|
||||
decimalScore = decimalScore > 0 ? 1 : 0;
|
||||
} else {
|
||||
if (decimalScore > this.halfRate) {
|
||||
decimalScore = decimalScore <= 0.5 ? 0.5 : 1;
|
||||
} else {
|
||||
decimalScore = 0;
|
||||
}
|
||||
}
|
||||
score = parseInt(score) + decimalScore;
|
||||
score = score > Number(this.quantity) ? Number(this.quantity) : score;
|
||||
}
|
||||
this.$emit('change', {
|
||||
score: score,
|
||||
params: this.params
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
// #ifndef MP-BAIDU
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.tui-grade__box')
|
||||
.boundingClientRect(res => {
|
||||
this.pageX = res.left || 0;
|
||||
})
|
||||
.exec();
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-BAIDU
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.' + this.classBaidu)
|
||||
.boundingClientRect(res => {
|
||||
this.pageX = res.left || 0;
|
||||
})
|
||||
.exec();
|
||||
|
||||
// #endif
|
||||
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: 'tuiGradeFont';
|
||||
src: url('data:font/truetype;charset=utf-8;base64,AAEAAAAOAIAAAwBgRkZUTY3VyXMAAAiUAAAAHEdERUYAKQAMAAAIdAAAAB5PUy8yQA9LlgAAAWgAAABWY21hcAAP7U4AAAHUAAABQmN2dCAAIgKIAAADGAAAAARnYXNw//8AAwAACGwAAAAIZ2x5Zgdi75IAAAMsAAACcGhlYWQbUeZgAAAA7AAAADZoaGVhB98DhgAAASQAAAAkaG10eAwDAB4AAAHAAAAAFGxvY2EB0gEyAAADHAAAAA5tYXhwARIAfAAAAUgAAAAgbmFtZSnmEVUAAAWcAAACiHBvc3QWS2RPAAAIJAAAAEcAAQAAAAEAAL1bP3tfDzz1AAsEAAAAAADcGNE1AAAAANwY0TX//v+UBAMDZQAAAAgAAgAAAAAAAAABAAADgP+AAFwEAf/+AAAEAwABAAAAAAAAAAAAAAAAAAAABAABAAAABgBCAAIAAAAAAAIAAAAKAAoAAAD/AC4AAAAAAAEEAQGQAAUAAAKJAswAAACPAokCzAAAAesAMgEIAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOmh6aMDgP+AAFwDgACAAAAAAQAAAAAAAAQBACIAAAAABAEAAAQB//7//v/+AAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAA6aP//wAA6aH//xZiAAEAAAAAAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACICiAAAACoAKgAqAHAA3gE4AAAAAgAiAAABMgKqAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIgEQ7szMAqr9ViICZgAAAf/+/5QEAwNlACcAACUGFxMWBgciJyUmIgcFBiMuATcTNi8BJjY3JTY3EzYyFxMWFwUeAQcDEAYCNwEJBwQE/twDCAT+3AQDCAkBOAEG7AcGCgFGCASSBRMFkgQIAUcKBQb6Bgj+uwgKAQKaAgKaAgEKCAFFCAbnBxICMAEHASgJCf7YBwEwAhIHAAAC//7/lAQDA2UAGABBAAABFx4BHwEHDgEfAScmIg8BNzYmLwE3PgE3EyIHAwYHBQ4BHwEWBwMGFhcyNyU2MhcFFjM+AScDJj8BNiYnJSYnAyYCAWcJHxTmpg8MBCfOEicRzicDDA6n5xMgCWcKBZIDCf66CgYH7AYBOAEKBwQDASQECAMBJAQEBwkBNwIG7QYFCv65CASSBQLp0RIXAyGjDiUU5WwJCWzlFCUOoyEDFxIBTQn+2AcCLwISB+cGCP67CAoBApoCApoCAQoIAUUIBucHEgIvAgcBKAkAAv/+/5QEAwNlACcAMwAAATYmJyUmJwMmIgcDBgcFDgEfARYHAwYWFzI3JTYyFwUWMz4BJwMmNwcXJxEXHgEfAQcOAQP9BgUK/rkIBJIFEgWSBAj+uQoGB+wGATgBCgcEAwEkBAgDASUDBAcKATgBBUMn00cJHxTmpg8MAeEHEgIwAQcBKAkJ/tgHATACEgfnBgj+uwgKAQKaAgKaAgEKCAFFCAYZ5W8CPZASFwMhow4lAAAAAAASAN4AAQAAAAAAAAAVACwAAQAAAAAAAQAIAFQAAQAAAAAAAgAHAG0AAQAAAAAAAwAIAIcAAQAAAAAABAAIAKIAAQAAAAAABQALAMMAAQAAAAAABgAIAOEAAQAAAAAACgArAUIAAQAAAAAACwATAZYAAwABBAkAAAAqAAAAAwABBAkAAQAQAEIAAwABBAkAAgAOAF0AAwABBAkAAwAQAHUAAwABBAkABAAQAJAAAwABBAkABQAWAKsAAwABBAkABgAQAM8AAwABBAkACgBWAOoAAwABBAkACwAmAW4ACgBDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AAoAAApDcmVhdGVkIGJ5IGljb25mb250CgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAUgBlAGcAdQBsAGEAcgAAUmVndWxhcgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAVgBlAHIAcwBpAG8AbgAgADEALgAwAABWZXJzaW9uIDEuMAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgAAR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0LgAAaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAaHR0cDovL2ZvbnRlbGxvLmNvbQAAAgAAAAAAAAAKAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAGAAAAAQACAQIBAwEECXN0YXItZnVsbARzdGFyCXN0YXItaGFsZgAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAwAFAAEABAAAAAIAAAAAAAAAAQAAAADVpCcIAAAAANwY0TUAAAAA3BjRNQ==') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tui-icon__grade {
|
||||
font-family: 'tuiGradeFont' !important;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tui-icon__starfull:before {
|
||||
content: '\e9a1';
|
||||
}
|
||||
|
||||
.tui-icon__star:before {
|
||||
content: '\e9a2';
|
||||
}
|
||||
|
||||
.tui-icon__starhalf:before {
|
||||
content: '\e9a3';
|
||||
}
|
||||
|
||||
.tui-grade__box {
|
||||
display: -webkit-inline-flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
153
src/components/thorui/tui-grid-item/tui-grid-item.vue
Normal file
153
src/components/thorui/tui-grid-item/tui-grid-item.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<view class="tui-grid" :class="[bottomLine?'':'tui-grid-bottom',border?'':'tui-grid__unlined','tui-grid-'+(cell<2?3:cell)]" :hover-class="hover?'tui-item-hover':''"
|
||||
:hover-stay-time="150" :style="{backgroundColor:backgroundColor}" @tap="handleClick">
|
||||
<view class='tui-grid-bg'>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiGridItem",
|
||||
emits: ['click'],
|
||||
props: {
|
||||
cell: {
|
||||
type: [Number,String],
|
||||
default: 3
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#fff"
|
||||
},
|
||||
//是否有点击效果
|
||||
hover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否需要底部线条
|
||||
bottomLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否需要纵向边框线条
|
||||
border:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: this.index
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-grid {
|
||||
position: relative;
|
||||
padding: 40rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
float: left;
|
||||
}
|
||||
/* #ifdef MP-BAIDU */
|
||||
.tui-grid:active{
|
||||
background-color: #f7f7f9;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.tui-grid-2 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.tui-grid-3 {
|
||||
width: 33.333333333%;
|
||||
}
|
||||
|
||||
.tui-grid-4 {
|
||||
width: 25%;
|
||||
padding: 30rpx 20rpx !important;
|
||||
}
|
||||
|
||||
.tui-grid-5 {
|
||||
width: 20%;
|
||||
padding: 20rpx !important;
|
||||
}
|
||||
|
||||
.tui-grid-2:nth-of-type(2n)::before {
|
||||
width: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.tui-grid-3:nth-of-type(3n)::before {
|
||||
width: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.tui-grid-4:nth-of-type(4n)::before {
|
||||
width: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.tui-grid-5:nth-of-type(5n)::before {
|
||||
width: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.tui-grid::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
bottom: 0;
|
||||
border-right: 1px solid #eaeef1;
|
||||
-webkit-transform-origin: 100% 0;
|
||||
transform-origin: 100% 0;
|
||||
-webkit-transform: scaleX(0.5);
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
|
||||
.tui-grid__unlined::before{
|
||||
width: 0 !important;
|
||||
border-right: 0 !important;
|
||||
}
|
||||
|
||||
.tui-grid::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid #eaeef1;
|
||||
-webkit-transform-origin: 0 100%;
|
||||
transform-origin: 0 100%;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.tui-grid-bottom::after {
|
||||
height: 0 !important;
|
||||
border-bottom: 0 !important
|
||||
}
|
||||
|
||||
.tui-grid-bg {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tui-item-hover {
|
||||
background-color: #f7f7f9 !important;
|
||||
}
|
||||
</style>
|
||||
44
src/components/thorui/tui-grid/tui-grid.vue
Normal file
44
src/components/thorui/tui-grid/tui-grid.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<view class="tui-grids" :class="{'tui-border-top':unlined}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"tuiGrid",
|
||||
props: {
|
||||
//是否去掉上线条
|
||||
unlined: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-grids {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-grids::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-top: 1px solid #eaeef1;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.tui-border-top::after {
|
||||
border-top: 0 !important;
|
||||
}
|
||||
</style>
|
||||
190
src/components/thorui/tui-icon/tui-icon.js
Normal file
190
src/components/thorui/tui-icon/tui-icon.js
Normal file
@ -0,0 +1,190 @@
|
||||
export default {
|
||||
"about": "\ue772",
|
||||
"about-fill": "\ue771",
|
||||
"add": "\ue770",
|
||||
"add-fill": "\ue76f",
|
||||
"addmessage": "\ue76e",
|
||||
"addressbook": "\ue76d",
|
||||
"agree": "\ue76c",
|
||||
"agree-fill": "\ue76b",
|
||||
"alarm": "\ue76a",
|
||||
"alarm-fill": "\ue769",
|
||||
"alipay": "\ue768",
|
||||
"android": "\ue767",
|
||||
"applets": "\ue766",
|
||||
"arrowdown": "\ue765",
|
||||
"arrowleft": "\ue764",
|
||||
"arrowright": "\ue763",
|
||||
"arrowup": "\ue762",
|
||||
"attestation": "\ue761",
|
||||
"back": "\ue760",
|
||||
"bag": "\ue75f",
|
||||
"bag-fill": "\ue75e",
|
||||
"balloon": "\ue75d",
|
||||
"bankcard": "\ue75c",
|
||||
"bankcard-fill": "\ue75b",
|
||||
"bottom": "\ue75a",
|
||||
"calendar": "\ue759",
|
||||
"camera": "\ue758",
|
||||
"camera-fill": "\ue757",
|
||||
"camera-add": "\ue756",
|
||||
"card": "\ue755",
|
||||
"card-fill": "\ue754",
|
||||
"cart": "\ue753",
|
||||
"cart-fill": "\ue752",
|
||||
"category": "\ue751",
|
||||
"category-fill": "\ue750",
|
||||
"check": "\ue74f",
|
||||
"circle": "\ue74e",
|
||||
"circle-fill": "\ue74d",
|
||||
"circle-selected": "\ue74c",
|
||||
"clock": "\ue74b",
|
||||
"clock-fill": "\ue74a",
|
||||
"close": "\ue749",
|
||||
"close-fill": "\ue748",
|
||||
"community": "\ue747",
|
||||
"community-fill": "\ue746",
|
||||
"computer": "\ue745",
|
||||
"computer-fill": "\ue744",
|
||||
"coupon": "\ue743",
|
||||
"delete": "\ue742",
|
||||
"deletekey": "\ue741",
|
||||
"dingtalk": "\ue740",
|
||||
"dissatisfied": "\ue73f",
|
||||
"down": "\ue73e",
|
||||
"download": "\ue73d",
|
||||
"edit": "\ue73c",
|
||||
"ellipsis": "\ue73b",
|
||||
"enlarge": "\ue73a",
|
||||
"evaluate": "\ue739",
|
||||
"exchange": "\ue738",
|
||||
"explain": "\ue737",
|
||||
"explain-fill": "\ue736",
|
||||
"explore": "\ue735",
|
||||
"explore-fill": "\ue734",
|
||||
"eye": "\ue733",
|
||||
"feedback": "\ue732",
|
||||
"fingerprint": "\ue730",
|
||||
"friendadd": "\ue72f",
|
||||
"friendadd-fill": "\ue72e",
|
||||
"gps": "\ue72d",
|
||||
"histogram": "\ue72c",
|
||||
"home": "\ue72b",
|
||||
"home-fill": "\ue72a",
|
||||
"house": "\ue729",
|
||||
"imface": "\ue728",
|
||||
"imkeyboard": "\ue727",
|
||||
"immore": "\ue726",
|
||||
"imvoice": "\ue725",
|
||||
"ios": "\ue724",
|
||||
"kefu": "\ue723",
|
||||
"label": "\ue722",
|
||||
"label-fill": "\ue721",
|
||||
"like": "\ue720",
|
||||
"like-fill": "\ue71f",
|
||||
"link": "\ue71e",
|
||||
"listview": "\ue71d",
|
||||
"loading": "\ue71c",
|
||||
"location": "\ue71b",
|
||||
"mail": "\ue71a",
|
||||
"mail-fill": "\ue719",
|
||||
"manage": "\ue718",
|
||||
"manage-fill": "\ue717",
|
||||
"member": "\ue716",
|
||||
"member-fill": "\ue715",
|
||||
"message": "\ue714",
|
||||
"message-fill": "\ue713",
|
||||
"mobile": "\ue712",
|
||||
"moments": "\ue711",
|
||||
"more": "\ue710",
|
||||
"more-fill": "\ue70f",
|
||||
"narrow": "\ue70e",
|
||||
"news": "\ue70d",
|
||||
"news-fill": "\ue70c",
|
||||
"nodata": "\ue70b",
|
||||
"notice": "\ue699",
|
||||
"notice-fill": "\ue698",
|
||||
"offline": "\ue697",
|
||||
"offline-fill": "\ue696",
|
||||
"oppose": "\ue695",
|
||||
"oppose-fill": "\ue694",
|
||||
"order": "\ue693",
|
||||
"partake": "\ue692",
|
||||
"people": "\ue691",
|
||||
"people-fill": "\ue690",
|
||||
"pic": "\ue68f",
|
||||
"pic-fill": "\ue68e",
|
||||
"picture": "\ue68d",
|
||||
"pie": "\ue68c",
|
||||
"plus": "\ue689",
|
||||
"polygonal": "\ue688",
|
||||
"position": "\ue686",
|
||||
"pwd": "\ue685",
|
||||
"qq": "\ue684",
|
||||
"qrcode": "\ue682",
|
||||
"redpacket": "\ue681",
|
||||
"redpacket-fill": "\ue680",
|
||||
"reduce": "\ue67f",
|
||||
"refresh": "\ue67e",
|
||||
"revoke": "\ue67d",
|
||||
"satisfied": "\ue67c",
|
||||
"screen": "\ue67b",
|
||||
"search": "\ue67a",
|
||||
"search-2": "\ue679",
|
||||
"send": "\ue678",
|
||||
"service": "\ue677",
|
||||
"service-fill": "\ue676",
|
||||
"setup": "\ue675",
|
||||
"setup-fill": "\ue674",
|
||||
"share": "\ue673",
|
||||
"share-fill": "\ue672",
|
||||
"shield": "\ue671",
|
||||
"shop": "\ue670",
|
||||
"shop-fill": "\ue66f",
|
||||
"shut": "\ue66e",
|
||||
"signin": "\ue66d",
|
||||
"sina": "\ue66c",
|
||||
"skin": "\ue66b",
|
||||
"soso": "\ue669",
|
||||
"square": "\ue668",
|
||||
"square-fill": "\ue667",
|
||||
"square-selected": "\ue666",
|
||||
"star": "\ue665",
|
||||
"star-fill": "\ue664",
|
||||
"strategy": "\ue663",
|
||||
"sweep": "\ue662",
|
||||
"time": "\ue661",
|
||||
"time-fill": "\ue660",
|
||||
"todown": "\ue65f",
|
||||
"toleft": "\ue65e",
|
||||
"tool": "\ue65d",
|
||||
"top": "\ue65c",
|
||||
"toright": "\ue65b",
|
||||
"towardsleft": "\ue65a",
|
||||
"towardsright": "\ue659",
|
||||
"towardsright-fill": "\ue658",
|
||||
"transport": "\ue657",
|
||||
"transport-fill": "\ue656",
|
||||
"turningdown": "\ue654",
|
||||
"turningleft": "\ue653",
|
||||
"turningright": "\ue652",
|
||||
"turningup": "\ue651",
|
||||
"unreceive": "\ue650",
|
||||
"seen": "\ue7d2",
|
||||
"unseen": "\ue7d1",
|
||||
"up": "\ue64e",
|
||||
"upload": "\ue64c",
|
||||
"video": "\ue64b",
|
||||
"voice": "\ue649",
|
||||
"voice-fill": "\ue648",
|
||||
"voipphone": "\ue647",
|
||||
"wallet": "\ue646",
|
||||
"warning": "\ue645",
|
||||
"wealth": "\ue644",
|
||||
"wealth-fill": "\ue643",
|
||||
"weather": "\ue642",
|
||||
"wechat": "\ue641",
|
||||
"wifi": "\ue640",
|
||||
"play": "\ue7d5",
|
||||
"suspend": "\ue7d4"
|
||||
}
|
||||
95
src/components/thorui/tui-icon/tui-icon.vue
Normal file
95
src/components/thorui/tui-icon/tui-icon.vue
Normal file
File diff suppressed because one or more lines are too long
1116
src/components/thorui/tui-image-cropper/tui-image-cropper.vue
Normal file
1116
src/components/thorui/tui-image-cropper/tui-image-cropper.vue
Normal file
File diff suppressed because it is too large
Load Diff
164
src/components/thorui/tui-image-group/tui-image-group.vue
Normal file
164
src/components/thorui/tui-image-group/tui-image-group.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<view
|
||||
class="tui-image-container"
|
||||
:style="{ marginBottom: multiLine ? `-${distance}rpx` : 0 }"
|
||||
:class="{ 'tui-image-direction': direction == 'column', 'tui-image__warp': multiLine }"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in imageList"
|
||||
:key="index"
|
||||
class="tui-image__itembox"
|
||||
:style="{
|
||||
width: width,
|
||||
height: height,
|
||||
borderRadius: radius,
|
||||
marginLeft: direction == 'column' || multiLine ? 0 : (index && distance) + 'rpx',
|
||||
marginRight: multiLine ? distance + 'rpx' : 0,
|
||||
marginBottom: multiLine ? distance + 'rpx' : 0,
|
||||
marginTop: direction == 'row' ? 0 : (index && distance) + 'rpx'
|
||||
}"
|
||||
@tap="bindClick(index, item.id)"
|
||||
>
|
||||
<image
|
||||
class="tui-image-item"
|
||||
:mode="mode"
|
||||
:lazy-load="lazyLoad"
|
||||
fade-show="fadeShow"
|
||||
:webp="webp"
|
||||
:show-menu-by-longpress="longpress"
|
||||
@error="error"
|
||||
@load="load"
|
||||
:style="{ width: width, height: height, borderRadius: radius, borderWidth: borderWidth, borderColor: borderColor }"
|
||||
:src="item.src"
|
||||
></image>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiImageGroup',
|
||||
emits: ['errorEvent','loaded','click'],
|
||||
props: {
|
||||
//图片集合
|
||||
/*
|
||||
[{id:1,src:"1.png"}]
|
||||
*/
|
||||
imageList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//图片宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '120rpx'
|
||||
},
|
||||
//图片高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '120rpx'
|
||||
},
|
||||
//图片边框宽度 rpx
|
||||
borderWidth: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//图片边框颜色 可传rgba
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//图片圆角
|
||||
radius: {
|
||||
type: String,
|
||||
default: '50%'
|
||||
},
|
||||
//图片裁剪、缩放的模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'scaleToFill'
|
||||
},
|
||||
//图片懒加载。只针对page与scroll-view下的image有效
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//图片显示动画效果 | 仅App-nvue 2.3.4+ Android有效
|
||||
fadeShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//默认不解析 webP 格式,只支持网络资源 | 微信小程序2.9.0
|
||||
webp: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//开启长按图片显示识别小程序码菜单 | 微信小程序2.7.0
|
||||
longpress: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否组合排列
|
||||
isGroup: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//排列方向 row ,column
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
//偏移距离 rpx
|
||||
distance: {
|
||||
type: [Number, String],
|
||||
default: -16
|
||||
},
|
||||
//是否可多行展示,排列方向 row时生效,distance需设置为大于0的数
|
||||
multiLine: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
error(e) {
|
||||
this.$emit('errorEvent', e);
|
||||
},
|
||||
load(e) {
|
||||
this.$emit('loaded', e);
|
||||
},
|
||||
bindClick(index, id) {
|
||||
this.$emit('click', {
|
||||
index: index,
|
||||
id: id || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-image-container {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.tui-image-direction {
|
||||
flex-direction: column;
|
||||
}
|
||||
.tui-image__warp {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tui-image__itembox {
|
||||
position: relative;
|
||||
}
|
||||
.tui-image-item {
|
||||
border-style: solid;
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
629
src/components/thorui/tui-index-list/tui-index-list.vue
Normal file
629
src/components/thorui/tui-index-list/tui-index-list.vue
Normal file
@ -0,0 +1,629 @@
|
||||
<template>
|
||||
<view class="tui-index-list">
|
||||
<scroll-view class="tui-scroll__view" :style="{ height: getHeight }" scroll-y :scroll-top="scrollTop"
|
||||
@scroll="scroll">
|
||||
<slot name="header"></slot>
|
||||
<view class="tui-content__box">
|
||||
<view class="tui-item__select" v-for="(item, index) in listData" :key="index">
|
||||
<view v-if="index == listItemCur" class="tui-content__title"
|
||||
:class="{ 'tui-line__top': topLine, 'tui-line__bottom': bottomLine }">
|
||||
<view class="tui-title__item"
|
||||
:style="{ background: background_cur, color: color_cur, fontSize: size, height: height, padding: padding }">
|
||||
{{ item.letter }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="index == listItemCur + 1" class="tui-content__title"
|
||||
:class="{ 'tui-line__top': topLine, 'tui-line__bottom': bottomLine }">
|
||||
<view class="tui-title__item"
|
||||
:style="{ background: background_next, color: color_next, fontSize: size, height: height, padding: padding }">
|
||||
{{ item.letter }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="tui-content__title"
|
||||
:class="{ 'tui-line__top': topLine, 'tui-line__bottom': bottomLine }">
|
||||
<view class="tui-title__item"
|
||||
:style="{ background: background, color: color, fontSize: size, height: height, padding: padding }">
|
||||
{{ item.letter }}
|
||||
</view>
|
||||
</view>
|
||||
<slot name="item" :entity="item.data" :index="index"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<slot name="footer"></slot>
|
||||
</scroll-view>
|
||||
<view class="tui-index__indicator"
|
||||
:class="[touching && indicatorTop != -1 ? 'tui-indicator__show' : '', treeKeyTran ? 'tui-indicator__tran' : '']"
|
||||
:style="{ top: indicatorTop + 'px' }">
|
||||
{{ listData[treeItemCur] && listData[treeItemCur].letter }}
|
||||
</view>
|
||||
<view id="tui_index__letter" class="tui-index__letter" @touchstart.stop="touchStart"
|
||||
@touchmove.stop.prevent="touchMove" @touchend.stop="touchEnd" @touchcancel.stop="touchEnd">
|
||||
<view class="tui-letter__item" :class="[index === treeItemCur ? 'tui-letter__cur' : '']"
|
||||
v-for="(item, index) in listData" :key="index" @tap="letterClick(index,item.letter)">
|
||||
<view class="tui-letter__key"
|
||||
:style="{ background: index === treeItemCur ? getActiveKeyBgColor : '', color: index === treeItemCur ? activeKeyColor : keyColor }">
|
||||
{{ item.letter }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let ColorUtil = {
|
||||
rgbToHex(r, g, b) {
|
||||
let hex = ((r << 16) | (g << 8) | b).toString(16);
|
||||
return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex;
|
||||
},
|
||||
hexToRgb(hex) {
|
||||
let rgb = [];
|
||||
if (hex.length === 4) {
|
||||
let text = hex.substring(1, 4);
|
||||
hex = '#' + text + text;
|
||||
}
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
rgb.push(parseInt('0x' + hex.slice(i, i + 2)));
|
||||
}
|
||||
return rgb;
|
||||
},
|
||||
/**
|
||||
* 生成渐变过渡色数组 {startColor: 开始颜色值, endColor: 结束颜色值, step: 生成色值数组长度}
|
||||
*/
|
||||
gradient(startColor, endColor, step) {
|
||||
// 将hex转换为rgb
|
||||
let sColor = this.hexToRgb(startColor),
|
||||
eColor = this.hexToRgb(endColor);
|
||||
|
||||
// 计算R\G\B每一步的差值
|
||||
let rStep = (eColor[0] - sColor[0]) / step,
|
||||
gStep = (eColor[1] - sColor[1]) / step,
|
||||
bStep = (eColor[2] - sColor[2]) / step;
|
||||
|
||||
let gradientColorArr = [];
|
||||
for (let i = 0; i < step; i++) {
|
||||
// 计算每一步的hex值
|
||||
gradientColorArr.push(this.rgbToHex(parseInt(rStep * i + sColor[0]), parseInt(gStep * i + sColor[1]),
|
||||
parseInt(bStep * i + sColor[2])));
|
||||
}
|
||||
return gradientColorArr;
|
||||
},
|
||||
/**
|
||||
* 生成随机颜色值
|
||||
*/
|
||||
generateColor() {
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += ((Math.random() * 16) | 0).toString(16);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
};
|
||||
export default {
|
||||
name: 'tuiIndexList',
|
||||
emits: ['letterClick'],
|
||||
props: {
|
||||
// 数据源
|
||||
listData: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 顶部高度
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 底部高度
|
||||
bottom: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//top和bottom单位,可传rpx 或 px
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'px'
|
||||
},
|
||||
//sticky letter 是否显示上边线条
|
||||
topLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//sticky letter 是否显示下边线条
|
||||
bottomLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '60rpx'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#666'
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '26rpx'
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#ededed'
|
||||
},
|
||||
activeBackground: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0 20rpx'
|
||||
},
|
||||
keyColor: {
|
||||
type: String,
|
||||
default: '#666'
|
||||
},
|
||||
activeKeyColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
activeKeyBackground: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//重新初始化[可异步加载时使用,设置大于0的数]
|
||||
reinit: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getHeight() {
|
||||
return `calc(100vh - ${this.top + this.bottom + this.unit})`;
|
||||
},
|
||||
getChange() {
|
||||
return `${this.top}-${this.bottom}-${this.reinit}`;
|
||||
},
|
||||
getActiveColor(){
|
||||
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
},
|
||||
getActiveKeyBgColor(){
|
||||
return this.activeKeyBackground || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
listData(val) {
|
||||
this.init();
|
||||
},
|
||||
getChange(val) {
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
remScale: 1, // 缩放比例
|
||||
realTop: 0, // 计算后顶部高度实际值
|
||||
realBottom: 0, // 计算后底部高度实际值
|
||||
treeInfo: {
|
||||
// 索引树节点信息
|
||||
treeTop: 0,
|
||||
treeBottom: 0,
|
||||
itemHeight: 0,
|
||||
itemMount: 0
|
||||
},
|
||||
indicatorTopList: [], // 指示器节点信息列表
|
||||
maxScrollTop: 0, // 最大滚动高度
|
||||
blocks: [], // 节点组信息
|
||||
|
||||
/* 渲染数据 */
|
||||
treeItemCur: -1, // 索引树的聚焦项
|
||||
listItemCur: -1, // 节点树的聚焦项
|
||||
touching: false, // 是否在触摸索引树中
|
||||
scrollTop: 0, // 节点树滚动高度
|
||||
indicatorTop: -1, // 指示器顶部距离
|
||||
treeKeyTran: false,
|
||||
background_cur: '',
|
||||
color_cur: '',
|
||||
background_next: '',
|
||||
color_next: '',
|
||||
colors: [],
|
||||
backgroundColors: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
scroll(e) {
|
||||
if (this.touching) return;
|
||||
|
||||
let scrollTop = e.detail.scrollTop;
|
||||
if (scrollTop > this.maxScrollTop) return;
|
||||
|
||||
let blocks = this.blocks,
|
||||
stickyTitleHeight = this.remScale * 30;
|
||||
let len = blocks.length - 1;
|
||||
this.background_cur = this.background;
|
||||
this.color_cur = this.color;
|
||||
for (let i = len; i >= 0; i--) {
|
||||
let block = blocks[i];
|
||||
// 判断当前滚动值 scrollTop 所在区间, 以得到当前聚焦项
|
||||
if (scrollTop >= block.itemTop && scrollTop < block.itemBottom) {
|
||||
// 判断当前滚动值 scrollTop 是否在当前聚焦项底一个 .block__title 高度范围内, 如果是则开启过度色值计算
|
||||
if (scrollTop > block.itemBottom - stickyTitleHeight) {
|
||||
let percent = Math.floor(((scrollTop - (block.itemBottom - stickyTitleHeight)) /
|
||||
stickyTitleHeight) * 100);
|
||||
this.background_cur = this.backgroundColors[percent];
|
||||
this.color_cur = this.colors[percent];
|
||||
this.background_next = this.backgroundColors[100 - percent];
|
||||
this.color_next = this.colors[100 - percent];
|
||||
this.treeItemCur = i;
|
||||
this.listItemCur = i;
|
||||
} else if (scrollTop <= block.itemBottom - stickyTitleHeight) {
|
||||
this.background_cur = this.activeBackground;
|
||||
this.color_cur = this.getActiveColor;
|
||||
this.background_next = this.background;
|
||||
this.color_next = this.color;
|
||||
this.treeItemCur = i;
|
||||
this.listItemCur = i;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* tree 触摸开始
|
||||
*/
|
||||
touchStart(e) {
|
||||
// 获取触摸点信息
|
||||
let startTouch = e.changedTouches[0];
|
||||
if (!startTouch) return;
|
||||
this.touching = true;
|
||||
let treeItemCur = this.getCurrentTreeItem(startTouch.pageY);
|
||||
this.setValue(treeItemCur);
|
||||
},
|
||||
/**
|
||||
* tree 触摸移动
|
||||
*/
|
||||
touchMove(e) {
|
||||
// 获取触摸点信息
|
||||
let currentTouch = e.changedTouches[0];
|
||||
if (!currentTouch) return;
|
||||
|
||||
// 滑动结束后迅速开始第二次滑动时候 touching 为 false 造成不显示 indicator 问题
|
||||
if (!this.touching) {
|
||||
this.touching = true;
|
||||
}
|
||||
|
||||
let treeItemCur = this.getCurrentTreeItem(currentTouch.pageY);
|
||||
this.setValue(treeItemCur);
|
||||
},
|
||||
/**
|
||||
* tree 触摸结束
|
||||
*/
|
||||
touchEnd(e) {
|
||||
let treeItemCur = this.treeItemCur;
|
||||
let listItemCur = this.listItemCur;
|
||||
if (treeItemCur !== listItemCur) {
|
||||
this.treeItemCur = listItemCur;
|
||||
this.indicatorTop = this.indicatorTopList[treeItemCur];
|
||||
}
|
||||
this.treeKeyTran = true;
|
||||
setTimeout(() => {
|
||||
this.touching = false;
|
||||
this.treeKeyTran = false;
|
||||
}, 300);
|
||||
},
|
||||
letterClick(index, letter) {
|
||||
// #ifdef H5
|
||||
this.setValue(index);
|
||||
this.touchEnd()
|
||||
// #endif
|
||||
this.$emit('letterClick', {
|
||||
index: index,
|
||||
letter: letter
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取当前触摸的 tree-item
|
||||
* @param pageY: 当前触摸点pageY
|
||||
*/
|
||||
getCurrentTreeItem(pageY) {
|
||||
let {
|
||||
treeTop,
|
||||
treeBottom,
|
||||
itemHeight,
|
||||
itemMount
|
||||
} = this.treeInfo;
|
||||
|
||||
if (pageY < treeTop) {
|
||||
return 0;
|
||||
} else if (pageY >= treeBottom) {
|
||||
return itemMount - 1;
|
||||
} else {
|
||||
return Math.floor((pageY - treeTop) / itemHeight);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 触摸之后后设置对应value
|
||||
*/
|
||||
setValue(treeItemCur) {
|
||||
if (treeItemCur === this.treeItemCur) return;
|
||||
|
||||
let block = this.blocks[treeItemCur];
|
||||
if (!block) return;
|
||||
|
||||
let {
|
||||
scrollTop,
|
||||
scrollIndex
|
||||
} = block,
|
||||
indicatorTop = this.indicatorTopList[treeItemCur];
|
||||
|
||||
this.background_cur = this.activeBackground;
|
||||
this.color_cur = this.getActiveColor;
|
||||
this.background_next = this.background;
|
||||
this.color_next = this.color;
|
||||
this.treeItemCur = treeItemCur;
|
||||
this.scrollTop = scrollTop;
|
||||
this.listItemCur = scrollIndex;
|
||||
this.indicatorTop = indicatorTop;
|
||||
},
|
||||
/**
|
||||
* 清除参数
|
||||
*/
|
||||
clearData() {
|
||||
this.treeItemCur = 0; // 索引树的聚焦项
|
||||
this.listItemCur = 0; // 节点树的聚焦项
|
||||
this.touching = false; // 是否在触摸索引树中
|
||||
this.scrollTop = 0; // 节点树滚动高度
|
||||
this.indicatorTop = -1; // 指示器顶部距离
|
||||
this.treeKeyTran = false;
|
||||
this.background_cur = this.background;
|
||||
this.color_cur = this.color;
|
||||
this.background_next = this.background;
|
||||
this.color_next = this.color;
|
||||
},
|
||||
/**
|
||||
* 初始化获取 dom 信息
|
||||
*/
|
||||
initDom() {
|
||||
let {
|
||||
windowHeight,
|
||||
windowWidth
|
||||
} = uni.getSystemInfoSync();
|
||||
let remScale = (windowWidth || 375) / 375,
|
||||
realTop = (this.top * remScale) / 2,
|
||||
realBottom = (this.bottom * remScale) / 2,
|
||||
colors = ColorUtil.gradient(this.getActiveColor, this.color, 100),
|
||||
backgroundColors = ColorUtil.gradient(this.activeBackground, this.background, 100);
|
||||
|
||||
this.remScale = remScale;
|
||||
this.realTop = realTop;
|
||||
this.realBottom = realBottom;
|
||||
this.colors = colors;
|
||||
this.backgroundColors = backgroundColors;
|
||||
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('#tui_index__letter')
|
||||
.boundingClientRect(res => {
|
||||
let treeTop = res.top,
|
||||
treeBottom = res.top + res.height,
|
||||
itemHeight = res.height / this.listData.length,
|
||||
itemMount = this.listData.length;
|
||||
|
||||
let indicatorTopList = this.listData.map((item, index) => {
|
||||
return itemHeight / 2 + index * itemHeight + treeTop - remScale * 25;
|
||||
});
|
||||
this.treeInfo = {
|
||||
treeTop: treeTop,
|
||||
treeBottom: treeBottom,
|
||||
itemHeight: itemHeight,
|
||||
itemMount: itemMount
|
||||
};
|
||||
this.indicatorTopList = indicatorTopList;
|
||||
})
|
||||
.exec();
|
||||
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.tui-content__box')
|
||||
.boundingClientRect(res => {
|
||||
let maxScrollTop = res.height - (windowHeight - realTop - realBottom);
|
||||
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.selectAll('.tui-item__select')
|
||||
.boundingClientRect(res => {
|
||||
let maxScrollIndex = -1;
|
||||
|
||||
let blocks = res.map((item, index) => {
|
||||
// Math.ceil 向上取整, 防止索引树切换列表时候造成真机固定头部上边线显示过粗问题
|
||||
let itemTop = Math.ceil(item.top - realTop),
|
||||
itemBottom = Math.ceil(itemTop + item.height);
|
||||
|
||||
if (maxScrollTop >= itemTop && maxScrollTop < itemBottom)
|
||||
maxScrollIndex = index;
|
||||
|
||||
return {
|
||||
itemTop: itemTop,
|
||||
itemBottom: itemBottom,
|
||||
scrollTop: itemTop >= maxScrollTop ? maxScrollTop : itemTop,
|
||||
scrollIndex: maxScrollIndex === -1 ? index : maxScrollIndex
|
||||
};
|
||||
});
|
||||
|
||||
this.maxScrollTop = maxScrollTop;
|
||||
this.blocks = blocks;
|
||||
})
|
||||
.exec();
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
init() {
|
||||
this.clearData();
|
||||
// 避免获取不到节点信息报错问题
|
||||
if (this.listData.length === 0) {
|
||||
return;
|
||||
}
|
||||
// 异步加载数据时候, 延迟执行 initDom 方法
|
||||
setTimeout(() => this.initDom(), 1200);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(()=>{
|
||||
this.init();
|
||||
})
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-index-list {
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-scroll__view {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.tui-content__box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tui-content__title {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tui-content__title .tui-title__item {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-line__top::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-top: 1px solid #ebedf0;
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-line__bottom::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
border-bottom: 1px solid #ebedf0;
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tui-index__indicator {
|
||||
position: fixed;
|
||||
right: 100rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
border-radius: 10rpx;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
display: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-index__indicator:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
border-radius: 100% 0% 100% 100%;
|
||||
background: #c9c9c9;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.tui-indicator__show {
|
||||
display: block;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-indicator__tran {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s linear;
|
||||
}
|
||||
|
||||
.tui-index__letter {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-letter__item {
|
||||
padding: 0 8rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tui-letter__key {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
font-size: 26rpx;
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-list__item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-list__item .tui-avatar {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 8rpx;
|
||||
flex-shrink: 0;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.tui-list__item view {
|
||||
width: 90%;
|
||||
font-size: 32rpx;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 40rpx;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
578
src/components/thorui/tui-input/tui-input.vue
Normal file
578
src/components/thorui/tui-input/tui-input.vue
Normal file
@ -0,0 +1,578 @@
|
||||
<template>
|
||||
<view class="tui-input__wrap"
|
||||
:class="{'tui-border__top':borderTop && !inputBorder,'tui-border__bottom':borderBottom && !inputBorder,'tui-radius__fillet':isFillet && !getRadius,'tui-input__border-nvue':inputBorder}"
|
||||
:style="getStyles" @tap="fieldClick">
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view class="tui-input__border-top" v-if="borderTop && !inputBorder" :style="{borderTopColor:borderColor}">
|
||||
</view>
|
||||
<view class="tui-input__border-bottom" :class="{'tui-line__left':lineLeft}" v-if="borderBottom && !inputBorder"
|
||||
:style="{borderBottomColor:borderColor}"></view>
|
||||
<view class="tui-input__border" :class="{'tui-radius__fillet':isFillet && !getRadius}" v-if="inputBorder"
|
||||
:style="{borderColor:borderColor,borderRadius:(getRadius*2)+'rpx'}"></view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view class="tui-input__required" v-if="required">
|
||||
<text :style="{color:getRequiredColor}">*</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view class="tui-input__required" :style="{color:getRequiredColor}" v-if="required">*</view>
|
||||
<!-- #endif -->
|
||||
<view class="tui-input__label"
|
||||
:style="{fontSize:getLabelSize+'rpx',color:getLabelColor,minWidth:labelWidth+'rpx'}" v-if="label">
|
||||
<text :style="{fontSize:getLabelSize+'rpx',color:getLabelColor}">{{label}}</text>
|
||||
</view>
|
||||
<slot name="left"></slot>
|
||||
<input class="tui-input__self" :class="{'tui-text__right':textRight,'tui-input__disabled':disabled}"
|
||||
:style="{fontSize:getSize+'rpx',color:color}" placeholder-class="tui-input__placeholder" :type="type"
|
||||
:name="name" :value="inputVal" :password="password" :placeholder="inputVal?'':placeholder"
|
||||
:placeholder-style="placeholderStyl" :disabled="disabled" :cursor-spacing="cursorSpacing"
|
||||
:maxlength="maxlength" :focus="focused" :confirm-type="confirmType" :confirm-hold="confirmHold"
|
||||
:cursor="cursor" :selection-start="selectionStart" :selection-end="selectionEnd"
|
||||
:adjust-position="adjustPosition" :hold-keyboard="holdKeyboard" :auto-blur="autoBlur" @focus="onFocus"
|
||||
@blur="onBlur" @input="onInput" @confirm="onConfirm" @keyboardheightchange="onKeyboardheightchange" />
|
||||
<icon type="clear" :size="clearSize" :color="clearColor" v-if="clearable && inputVal != ''" @tap.stop="onClear">
|
||||
</icon>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-input",
|
||||
emits: ['input', 'update:modelValue', 'focus', 'blur', 'confirm', 'click', 'keyboardheightchange'],
|
||||
//这里加group是为了避免在表单中使用时给组件加value属性
|
||||
// #ifdef MP-WEIXIN
|
||||
behaviors: ['wx://form-field-group'],
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
behaviors: ['swan://form-field'],
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
behaviors: ['qq://form-field'],
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
behaviors: ['uni://form-field'],
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
options: {
|
||||
addGlobalClass: true,
|
||||
virtualHost: true
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
//是否为必填项
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//左侧标题
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//标题字体大小
|
||||
labelSize: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//label 最小宽度 rpx
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 140
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//px
|
||||
clearSize: {
|
||||
type: Number,
|
||||
default: 15
|
||||
},
|
||||
clearColor: {
|
||||
type: String,
|
||||
default: '#bfbfbf'
|
||||
},
|
||||
//获取焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholderStyle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//输入框名称
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//输入框值
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// #ifdef VUE3
|
||||
//输入框值
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// #endif
|
||||
modelModifiers: {
|
||||
default: () => ({})
|
||||
},
|
||||
//与官方input type属性一致
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
password: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 140
|
||||
},
|
||||
min: {
|
||||
type: [Number, String],
|
||||
default: 'NaN'
|
||||
},
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: 'NaN'
|
||||
},
|
||||
cursorSpacing: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
confirmHold: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
cursor: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
selectionStart: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
selectionEnd: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
adjustPosition: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
holdKeyboard: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
autoBlur: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//输入框字体大小 rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//输入框字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示 input 边框
|
||||
inputBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.1)'
|
||||
},
|
||||
//input是否显示为圆角
|
||||
isFillet: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示上边框
|
||||
borderTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//下边框线条是否有左偏移距离
|
||||
lineLeft: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否自动去除两端的空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
textRight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//输入框padding值
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//输入框背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
radius: {
|
||||
type: [Number, String],
|
||||
default: -1
|
||||
},
|
||||
//输入框margin-top值 rpx
|
||||
marginTop: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getLabelSize() {
|
||||
return this.labelSize || (uni && uni.$tui && uni.$tui.tuiInput.labelSize) || 32
|
||||
},
|
||||
getLabelColor() {
|
||||
return this.labelColor || (uni && uni.$tui && uni.$tui.tuiInput.labelColor) || '#333'
|
||||
},
|
||||
getSize() {
|
||||
return this.size || (uni && uni.$tui && uni.$tui.tuiInput.size) || 32
|
||||
},
|
||||
getColor() {
|
||||
return this.color || (uni && uni.$tui && uni.$tui.tuiInput.color) || '#333'
|
||||
},
|
||||
getRadius() {
|
||||
let radius = this.radius
|
||||
if (radius === -1 || radius === true) {
|
||||
radius = uni && uni.$tui && uni.$tui.tuiInput.radius
|
||||
}
|
||||
return Number(radius || 0)
|
||||
},
|
||||
getStyles() {
|
||||
const padding = this.padding || (uni && uni.$tui && uni.$tui.tuiInput.padding) || '26rpx 30rpx';
|
||||
const bgColor = this.backgroundColor || (uni && uni.$tui && uni.$tui.tuiInput.backgroundColor) ||
|
||||
'#FFFFFF';
|
||||
let radius = this.getRadius;
|
||||
let styles = `padding:${padding};background:${bgColor};margin-top:${this.marginTop}rpx;`
|
||||
if (radius && radius !== true && radius !== -1) {
|
||||
styles += `border-radius:${radius}rpx;`
|
||||
}
|
||||
if (this.borderTop || this.borderBottom || this.inputBorder) {
|
||||
styles += `border-color:${this.borderColor};`
|
||||
}
|
||||
return styles
|
||||
},
|
||||
getRequiredColor() {
|
||||
return this.requiredColor || (uni && uni.$tui && uni.$tui.tuiInput.requiredColor) || '#EB0909'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
placeholderStyl: '',
|
||||
focused: false,
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
focus(val) {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.focused = val
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
placeholderStyle() {
|
||||
this.fieldPlaceholderStyle()
|
||||
},
|
||||
// #ifdef VUE3
|
||||
modelValue(newVal) {
|
||||
this.inputVal = newVal
|
||||
},
|
||||
// #endif
|
||||
value(newVal) {
|
||||
this.inputVal = newVal
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fieldPlaceholderStyle()
|
||||
setTimeout(() => {
|
||||
// #ifndef VUE3
|
||||
this.inputVal = this.value
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
if (this.value && !this.modelValue) {
|
||||
this.inputVal = this.value
|
||||
} else {
|
||||
this.inputVal = this.modelValue
|
||||
}
|
||||
// #endif
|
||||
}, 50)
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.focused = this.focus
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
fieldPlaceholderStyle() {
|
||||
if (this.placeholderStyle) {
|
||||
this.placeholderStyl = this.placeholderStyle
|
||||
} else {
|
||||
const size = uni.upx2px(this.size || (uni && uni.$tui && uni.$tui.tuiInput.size) || 32)
|
||||
this.placeholderStyl = `font-size:${size}px`
|
||||
}
|
||||
},
|
||||
onInput(event) {
|
||||
let value = event.detail.value;
|
||||
if (this.trim) value = this.trimStr(value);
|
||||
this.inputVal = value
|
||||
//数字类型 数值不能超过最大整数安全范围,一但超过则返回字符串
|
||||
const cVal = Number(value)
|
||||
if ((this.modelModifiers.number || this.type === 'digit' || this.type === 'number') && !isNaN(cVal) &&
|
||||
Number.isSafeInteger(cVal)) {
|
||||
let eVal = this.type === 'digit' ? value : cVal
|
||||
if (typeof cVal === 'number') {
|
||||
const min = Number(this.min)
|
||||
const max = Number(this.max)
|
||||
if (typeof min === 'number' && cVal < min) {
|
||||
eVal = min
|
||||
} else if (typeof max === 'number' && max < cVal) {
|
||||
eVal = max
|
||||
}
|
||||
}
|
||||
value = isNaN(eVal) ? value : eVal
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
event.detail.value !== '' && (this.inputVal = value);
|
||||
})
|
||||
const inputValue = event.detail.value !== '' ? value : ''
|
||||
this.$emit('input', inputValue);
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:modelValue', inputValue)
|
||||
// #endif
|
||||
},
|
||||
onFocus(event) {
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.$emit('confirm', e);
|
||||
},
|
||||
onClear(event) {
|
||||
if (this.disabled) return;
|
||||
uni.hideKeyboard()
|
||||
this.inputVal = '';
|
||||
this.$emit('input', '');
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:modelValue', '')
|
||||
// #endif
|
||||
},
|
||||
fieldClick() {
|
||||
this.$emit('click', {
|
||||
name: this.name
|
||||
});
|
||||
},
|
||||
onKeyboardheightchange(e) {
|
||||
this.$emit('keyboardheightchange', e.detail)
|
||||
},
|
||||
trimStr(str) {
|
||||
return str.replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-input__wrap {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
/* #ifdef APP-NVUE */
|
||||
padding: 26rpx 30rpx;
|
||||
/* #endif */
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
|
||||
.tui-input__border-top {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-top: 1px solid var(--thorui-line-color, rgba(0, 0, 0, 0.1));
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
transform-origin: 0 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-input__border-bottom {
|
||||
position: absolute;
|
||||
border-bottom: 1px solid var(--thorui-line-color, rgba(0, 0, 0, 0.1));
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
transform-origin: 0 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-line__left {
|
||||
left: 30rpx !important;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
.tui-border__top {
|
||||
border-top-width: 0.5px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.tui-border__bottom {
|
||||
border-top-width: 0.5px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.tui-input__required {
|
||||
position: absolute;
|
||||
left: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
height: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
line-height: 1.15;
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-input__label {
|
||||
padding-right: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
flex-shrink: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-input__self {
|
||||
flex: 1;
|
||||
padding-right: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
overflow: visible;
|
||||
/* #endif */
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.tui-input__placeholder {
|
||||
/* #ifndef APP-NVUE */
|
||||
color: var(--thorui-text-color-placeholder, #ccc);
|
||||
overflow: visible;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
color: #ccc;
|
||||
font-size: 32rpx;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifdef MP */
|
||||
::v-deep .tui-input__placeholder {
|
||||
color: var(--thorui-text-color-placeholder, #ccc);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
.tui-input__border-nvue {
|
||||
border-width: 0.5px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.tui-input__disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-input__border {
|
||||
position: absolute;
|
||||
height: 200%;
|
||||
width: 200%;
|
||||
border: 1px solid var(--thorui-border-color, #d1d1d1);
|
||||
transform-origin: 0 0;
|
||||
transform: scale(0.5);
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 8rpx;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.tui-radius__fillet {
|
||||
border-radius: 100px !important;
|
||||
}
|
||||
|
||||
.tui-text__right {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<view class="tui-keyboard-input tui-pwd-box" :style="{backgroundColor:backgroundColor}">
|
||||
<view class="tui-inner-box">
|
||||
<view class="tui-input" :class="[inputvalue.length===4?'tui-margin-right':'']" :style="{fontSize:size+'rpx',color:color,width:(inputvalue.length===4?90:70)+'rpx' }"
|
||||
v-for="(item,index) in inputvalue" :key="index">{{item}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiKeyboardInput",
|
||||
props: {
|
||||
//背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#fff"
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#333"
|
||||
},
|
||||
//输入框的值:数组格式,长度即为输入框个数
|
||||
inputvalue: {
|
||||
type: Array,
|
||||
default: ["", "", "", "", "", ""] //密码圆点 ●
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-pwd-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tui-inner-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-input {
|
||||
height: 80rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
border-bottom: 2px solid #666;
|
||||
}
|
||||
|
||||
.tui-margin-right {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.tui-input:last-child {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
</style>
|
||||
241
src/components/thorui/tui-keyboard/tui-keyboard.vue
Normal file
241
src/components/thorui/tui-keyboard/tui-keyboard.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="tui-keyboard-mask" :class="[show?'tui-mask-show':'']" v-if="mask" @tap="handleClose"></view>
|
||||
<view class="tui-keyboard" :class="{'tui-keyboard-radius':radius,'tui-keyboard-action':action,'tui-keyboard-show':show}">
|
||||
<slot></slot>
|
||||
<view class="tui-keyboard-grids">
|
||||
<!--{{(index==9 || index==10 || index==11)?'tui-grid-bottom':''}}-->
|
||||
<view class="tui-keyboard-grid" :class="{'tui-bg-gray':index==9 || index==11}" v-for="(item,index) in itemList"
|
||||
:key="index" hover-class="tui-keyboard-hover" :hover-stay-time="150" @tap="handleClick" :data-index="index">
|
||||
<view v-if="index<11" class="tui-keyboard-item" :class="{'tui-fontsize-32':index==9}">{{getKeyBoard(index,action)}}</view>
|
||||
<view v-else class="tui-keyboard-item">
|
||||
<view class="tui-icon tui-keyboard-delete"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiKeyboard",
|
||||
emits: ['click','close'],
|
||||
props: {
|
||||
//是否需要mask
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//控制键盘显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否直接显示,不需要动画,一般使用在锁屏密码
|
||||
action: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否带圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
itemList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getKeyBoard: function(index, action) {
|
||||
var content = index + 1;
|
||||
if (index == 9) {
|
||||
content = action ? "取消" : "清除";
|
||||
} else if (index == 10) {
|
||||
content = 0;
|
||||
}
|
||||
return content;
|
||||
},
|
||||
//关闭
|
||||
handleClose() {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
this.$emit('close', {});
|
||||
},
|
||||
handleClick(e) {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
const dataset = e.currentTarget.dataset;
|
||||
this.$emit('click', {
|
||||
index: Number(dataset.index)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: 'keyboardFont';
|
||||
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAASgAA0AAAAABugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEhAAAABoAAAAch/nJvUdERUYAAARkAAAAHgAAAB4AKQAKT1MvMgAAAZwAAABDAAAAVj4mSapjbWFwAAAB8AAAAD4AAAFCAA/rY2dhc3AAAARcAAAACAAAAAj//wADZ2x5ZgAAAjwAAACsAAAA0BLVU2FoZWFkAAABMAAAAC0AAAA2FXPmsWhoZWEAAAFgAAAAHAAAACQH3gOFaG10eAAAAeAAAAAOAAAAEAwAAABsb2NhAAACMAAAAAoAAAAKAGgAAG1heHAAAAF8AAAAHwAAACABEQBLbmFtZQAAAugAAAFJAAACiCnmEVVwb3N0AAAENAAAACgAAAA6nLlLs3jaY2BkYGAAYukqK754fpuvDNwsDCBwU+tiFBKtwMLA9ABIczAwgUQB4ccH+gAAAHjaY2BkYGBu+N/AEMPCAAJAkpEBFbAAAEcKAm142mNgZGBgYGGwZ2BmAAEmIOYCQgaG/2A+AwAPIgFdAHjaY2BkYWCcwMDKwMDUyXSGgYGhH0IzvmYwYuQAijKwMjNgBQFprikMDs93PN/B3PC/gSGGuYGhASjMCJIDAPenDU0AeNpjYYAAFigGAACAAA0AAHjaY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChED+8x3//0NICW+oSgZGNgYYk4GRCUgwMaACRoZhDwAItAhZAAAAAAAAAAAAAABoAAB42l3MTQqCUBSG4fNpqBxECS/+YFTXRGcFKteZjW0nuoqWVtOgPbgKZ1cqaBDN3snzkklE+xUZEwUkqSOCzGx4EGGEsJYd2vURgQdbomhayC0iu8h8lEVmiR1sS4TVGVFYqeaEVjXmVT8TsWjf83yYIjFq1QM9I0/1c9HMMI06zfHgmMeRY8HDwOKnjSlYZvdQ5u4yB+gVbqrX97cAOxsHn9GF/9G3iV4WbSWBeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMCiTIxM/FmZiXkFiXnxxRmJeckZpQA1nQZRAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMAAwABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9E2ti1EwGgA9dwYGAAA=) format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tui-icon {
|
||||
font-family: "keyboardFont" !important;
|
||||
font-size: 22px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
line-height: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tui-keyboard-delete:before {
|
||||
content: "\e7b8";
|
||||
}
|
||||
|
||||
.tui-keyboard-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
z-index: 998;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-keyboard {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 999;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.tui-keyboard-radius {
|
||||
border-top-left-radius: 16rpx;
|
||||
border-top-right-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-keyboard-action {
|
||||
visibility: hidden;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.tui-keyboard-show {
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-bg-gray {
|
||||
background-color: #e7e6eb !important;
|
||||
}
|
||||
|
||||
.tui-keyboard-grids {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tui-keyboard-grids::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-top: 1px solid #eaeef1;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.tui-keyboard-grid {
|
||||
position: relative;
|
||||
padding: 24rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
|
||||
.tui-keyboard-grid:nth-of-type(3n)::before {
|
||||
width: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.tui-keyboard-grid::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
bottom: 0;
|
||||
border-right: 1px solid #eaeef1;
|
||||
-webkit-transform-origin: 100% 0;
|
||||
transform-origin: 100% 0;
|
||||
-webkit-transform: scaleX(0.5);
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
|
||||
.tui-keyboard-grid::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid #eaeef1;
|
||||
-webkit-transform-origin: 0 100%;
|
||||
transform-origin: 0 100%;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.tui-grid-bottom::after {
|
||||
height: 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tui-keyboard-hover {
|
||||
background-color: #f7f7f9 !important;
|
||||
}
|
||||
|
||||
.tui-keyboard-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48rpx;
|
||||
height: 60rpx;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.tui-fontsize-32 {
|
||||
font-size: 32rpx;
|
||||
color: #333 !important;
|
||||
}
|
||||
</style>
|
||||
54
src/components/thorui/tui-label/tui-label.vue
Normal file
54
src/components/thorui/tui-label/tui-label.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<view class="tui-label__box" :class="{'tui-label__full':isFull}" :style="{padding:padding,margin:margin}"
|
||||
@tap.stop="onClick">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//该组件主要用于tui-radio,tui-checkbox,tui-switch组件外层,类似label功能
|
||||
export default {
|
||||
name: "tui-label",
|
||||
props: {
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
isFull: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = [];
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
if (this.childrens && this.childrens.length > 0) {
|
||||
for (let child of this.childrens) {
|
||||
child.labelClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-label__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-label__full {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
158
src/components/thorui/tui-landscape/tui-landscape.vue
Normal file
158
src/components/thorui/tui-landscape/tui-landscape.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<view class="tui-landscape__box">
|
||||
<view class="tui-landscape__inner" :style="{zIndex:zIndex}" v-if="show">
|
||||
<slot></slot>
|
||||
<view class="tui-icon__close"
|
||||
:style="{top:position!=1?iconTop:'auto',bottom:position==1?iconBottom:'auto',left:position==3?iconLeft:(position==1?'50%':'auto'),right:position==2?iconRight:'auto'}"
|
||||
:class="{'tui-icon__bottom':position==1}" v-if="closeIcon" @tap.stop="close">
|
||||
<icon type="clear" :color="iconColor" :size="iconSize"></icon>
|
||||
</view>
|
||||
</view>
|
||||
<view :style="{backgroundColor:maskBgColor,zIndex:maskZIndex}" @tap.stop="close(1)" class="tui-landscape__mask"
|
||||
:class="{'tui-mask_hidden':!show}" v-if="mask"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-landscape",
|
||||
emits: ['close'],
|
||||
props: {
|
||||
//是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//显示内容区z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1001
|
||||
},
|
||||
//是否需要关闭图标
|
||||
closeIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//关闭图标颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//关闭图标大小 px
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 25
|
||||
},
|
||||
//icon位置:1-底部 2-右上角 3-左上角
|
||||
position: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//关闭图标top值,position为2或3的时候生效
|
||||
iconTop: {
|
||||
type: String,
|
||||
default: '-120rpx'
|
||||
},
|
||||
//关闭图标bottom值,position为1的时候生效
|
||||
iconBottom: {
|
||||
type: String,
|
||||
default: '-120rpx'
|
||||
},
|
||||
//关闭图标left值,position为3的时候生效
|
||||
iconLeft: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//关闭图标right值,position为2的时候生效
|
||||
iconRight: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//点击遮罩是否可以关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否需要遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//遮罩背景色
|
||||
maskBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(0,0,0,.6)'
|
||||
},
|
||||
//遮罩z-index值
|
||||
maskZIndex: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
//自定义参数
|
||||
params: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close(isMask) {
|
||||
if (isMask == 1 && !this.maskClosable) return;
|
||||
this.$emit('close', {
|
||||
params: this.params
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-landscape__box {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-landscape__inner {
|
||||
max-width: 100%;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.tui-icon__close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-icon__bottom {
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tui-landscape__mask {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
opacity: 1;
|
||||
transform: scale3d(1, 1, 1);
|
||||
transition: all .2s ease-in
|
||||
}
|
||||
|
||||
.tui-mask_hidden {
|
||||
opacity: 0 !important;
|
||||
transform: scale3d(1, 1, 0) !important;
|
||||
}
|
||||
</style>
|
||||
225
src/components/thorui/tui-lazyload-img/tui-lazyload-img.vue
Normal file
225
src/components/thorui/tui-lazyload-img/tui-lazyload-img.vue
Normal file
@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view class="tui-lazyload__box"
|
||||
:style="{backgroundColor:placeholder?'transparent':backgroundColor,width:width,height:height?height:'auto',borderRadius:radius}"
|
||||
@tap="handleClick">
|
||||
<image class="tui-lazyload__img"
|
||||
:class="{'tui-img__hidden':!placeholder && fadeShow && !show,'tui-img__appear':show && !placeholder && fadeShow}"
|
||||
:style="{height:height,borderRadius:radius}" :src="show?src:placeholder"
|
||||
:mode="imgMode" :webp="webp" :show-menu-by-longpress="showMenuByLongpress" :draggable="draggable"
|
||||
@load="load" @error="error" :id="elId">
|
||||
</image>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tui-lazyload-img",
|
||||
emits: ['error', 'load', 'click'],
|
||||
options: {
|
||||
virtualHost: true
|
||||
},
|
||||
props: {
|
||||
//图片路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//占位图路径
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//占位背景色,placeholder有值时失效
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#E7E7E7'
|
||||
},
|
||||
//图片的裁剪模式,参考image组件mode属性
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'widthFix'
|
||||
},
|
||||
//图片显示动画效果,无占位图时有效
|
||||
fadeShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//默认不解析 webP 格式,只支持网络资源 微信小程序2.9.0
|
||||
webp: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//开启长按图片显示识别小程序码菜单 微信小程序2.7.0
|
||||
showMenuByLongpress: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//鼠标长按是否能拖动图片 仅H5平台 3.1.1+ 有效
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//图片宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '340rpx'
|
||||
},
|
||||
//图片高度,如果高度设置为auto,mode值需要设置为widthFix
|
||||
height: {
|
||||
type: String,
|
||||
default: '340rpx'
|
||||
},
|
||||
//图片圆角值,如:10rpx
|
||||
radius: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//节点布局区域的下边界,目标节点区域以下 bottom(px) 时,就会触发回调函数
|
||||
bottom: {
|
||||
type: [Number, String],
|
||||
default: 50
|
||||
},
|
||||
//是否停止监听,设置为true时回调函数将不再触发
|
||||
disconnect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//图片在列表中的索引值
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let elId = this.unique() + this.index
|
||||
return {
|
||||
show: false,
|
||||
elId: elId,
|
||||
imgMode: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
disconnect(val) {
|
||||
if (val) {
|
||||
this.removeObserver()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.observer = null;
|
||||
// this.elId = this.unique() + this.index;
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
// #ifndef H5
|
||||
if (!this.disconnect) {
|
||||
this.initObserver()
|
||||
} else {
|
||||
this.show = true;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
if (!this.disconnect && window.self === window.top) {
|
||||
this.initObserver()
|
||||
} else {
|
||||
this.show = true;
|
||||
}
|
||||
// #endif
|
||||
this.imgMode = this.mode;
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
// #ifndef VUE3
|
||||
beforeDestroy() {
|
||||
this.removeObserver()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.removeObserver()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
unique: function(n) {
|
||||
n = n || 6;
|
||||
let rnd = '';
|
||||
for (let i = 0; i < n; i++)
|
||||
rnd += Math.floor(Math.random() * 10);
|
||||
return 'tui_' + new Date().getTime() + rnd;
|
||||
},
|
||||
removeObserver() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect()
|
||||
this.observer = null;
|
||||
}
|
||||
},
|
||||
initObserver() {
|
||||
if (this.observer || this.show) return;
|
||||
try {
|
||||
let element = this.elId ? `#${this.elId}` : '.tui-lazyload__img';
|
||||
const observer = uni.createIntersectionObserver(this)
|
||||
observer.relativeToViewport({
|
||||
bottom: Number(this.bottom) || 50
|
||||
}).observe(element, (res) => {
|
||||
if (res.intersectionRatio > 0 && !this.show) {
|
||||
this.show = true;
|
||||
this.removeObserver()
|
||||
}
|
||||
})
|
||||
this.observer = observer
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
this.show = true;
|
||||
this.removeObserver()
|
||||
}
|
||||
},
|
||||
error(e) {
|
||||
if (!this.show) return;
|
||||
this.$emit('error', {
|
||||
detail: e.detail,
|
||||
index: this.index
|
||||
})
|
||||
},
|
||||
load(e) {
|
||||
if (!this.show) return;
|
||||
this.$emit('load', {
|
||||
detail: e.detail,
|
||||
index: this.index
|
||||
})
|
||||
},
|
||||
handleClick(e) {
|
||||
this.$emit('click', {
|
||||
index: this.index
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-lazyload__box {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-lazyload__img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
transition: opacity .3s linear;
|
||||
}
|
||||
|
||||
.tui-img__hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-img__appear {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
119
src/components/thorui/tui-link/tui-link.vue
Normal file
119
src/components/thorui/tui-link/tui-link.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<a v-if="isShowA" class="tui-link__text" :href="href" :class="{'tui-link__underline':underline}"
|
||||
:style="{color:getColor,fontSize:size+'rpx'}" :download="download">
|
||||
<slot>{{text || href}}</slot>
|
||||
</a>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<text v-else class="tui-link__text" :class="{'tui-link__underline':underline}"
|
||||
:style="{color:getColor,fontSize:size+'rpx'}" @tap="openURL">
|
||||
<slot>{{text || href}}</slot>
|
||||
</text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<text v-else class="tui-link__text" :class="{'tui-link__underline':underline}"
|
||||
:style="{color:getColor,fontSize:size+'rpx'}" @tap="openURL">{{text || href}}</text>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiLink',
|
||||
options: {
|
||||
virtualHost: true
|
||||
},
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
download: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
underline: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
copyTips: {
|
||||
type: String,
|
||||
default: '链接已复制'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShowA() {
|
||||
let h5 = false
|
||||
// #ifdef H5
|
||||
h5 = true;
|
||||
// #endif
|
||||
if ((this.isMail() || this.isTel()) && h5) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getColor() {
|
||||
return this.color || (uni && uni.$tui && uni.$tui.color.link) || '#586c94'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isMail() {
|
||||
return this.href.startsWith('mailto:');
|
||||
},
|
||||
isTel() {
|
||||
return this.href.startsWith('tel:');
|
||||
},
|
||||
openURL() {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.isTel()) {
|
||||
this.makePhoneCall(this.href.replace('tel:', ''));
|
||||
} else {
|
||||
plus.runtime.openURL(this.href);
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
window.open(this.href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.setClipboardData({
|
||||
data: this.href,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: this.copyTips,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// #endif
|
||||
},
|
||||
makePhoneCall(phoneNumber) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* #ifdef H5 */
|
||||
.tui-link__text {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.tui-link__underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user