fix:重构前的保存

This commit is contained in:
MJ 2025-08-02 12:49:52 +08:00
parent 012c7c8c17
commit feeaa6a561
231 changed files with 49327 additions and 1046 deletions

View File

@ -48,5 +48,5 @@
"src/uni_modules/uni-search-bar/components/uni-search-bar/i18n", "src/uni_modules/uni-search-bar/components/uni-search-bar/i18n",
"src/uni_modules/z-paging/components/z-paging/i18n" "src/uni_modules/z-paging/components/z-paging/i18n"
], ],
"cSpell.words": ["xuexiaole"] "cSpell.words": ["iconfont", "pinia", "VITE", "xuexiaole"]
} }

View File

@ -68,6 +68,7 @@
"unplugin-vue-define-options": "^1.4.2", "unplugin-vue-define-options": "^1.4.2",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
"lodash": "4.17.21",
"vue-qrcode-reader": "^5.5.7", "vue-qrcode-reader": "^5.5.7",
"weixin-js-sdk": "^1.6.5" "weixin-js-sdk": "^1.6.5"
}, },
@ -93,6 +94,7 @@
"prettier": "^3.0.3", "prettier": "^3.0.3",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "4.1.4", "vite": "4.1.4",
"@types/lodash": "4.17.16",
"vue-eslint-parser": "^9.3.2", "vue-eslint-parser": "^9.3.2",
"vue-tsc": "^1.0.24" "vue-tsc": "^1.0.24"
}, },

11
pnpm-lock.yaml generated
View File

@ -62,6 +62,9 @@ importers:
lint-staged: lint-staged:
specifier: ^15.0.1 specifier: ^15.0.1
version: 15.0.1 version: 15.0.1
lodash:
specifier: 4.17.21
version: 4.17.21
pinia: pinia:
specifier: ^2.0.36 specifier: ^2.0.36
version: 2.0.36(typescript@4.9.5)(vue@3.3.4) version: 2.0.36(typescript@4.9.5)(vue@3.3.4)
@ -114,6 +117,9 @@ importers:
'@dcloudio/vite-plugin-uni': '@dcloudio/vite-plugin-uni':
specifier: 3.0.0-3081220230817001 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) 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': '@typescript-eslint/eslint-plugin':
specifier: ^6.8.0 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) 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': '@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} 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': '@types/minimist@1.2.3':
resolution: {integrity: sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==} resolution: {integrity: sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==}
@ -7009,6 +7018,8 @@ snapshots:
'@types/json5@0.0.29': {} '@types/json5@0.0.29': {}
'@types/lodash@4.17.16': {}
'@types/minimist@1.2.3': {} '@types/minimist@1.2.3': {}
'@types/node@20.5.1': {} '@types/node@20.5.1': {}

View File

@ -1,24 +1,24 @@
import $req from './request'; import $req from './request'
// 获取用户信息 // 获取用户信息
export const getUserInfo = () => export const getUserInfo = () =>
$req({ $req({
url: '/sysUser/selectUser', url: '/sysUser/selectUser',
}); })
// 获取客服信息 // 获取客服信息
export const getCsInfo = (id: string) => export const getCsInfo = (id: string) =>
$req({ $req({
url: `/customerService/getInfoByUserId/${id}`, url: `/customerService/getInfoByUserId/${id}`,
}); })
// 更新用户信息 // 更新用户信息
export const updateUserInfo = (data: anyObj) => export const updateUserInfo = (data: any) =>
$req({ $req({
url: '/sysUser/editPerfectMessage', url: '/sysUser/editPerfectMessage',
method: 'post', method: 'post',
data, data,
}); })
// 获取协议 // 获取协议
export const getAgreeInfo = (id: string, isChat = false) => export const getAgreeInfo = (id: string, isChat = false) =>
@ -27,30 +27,30 @@ export const getAgreeInfo = (id: string, isChat = false) =>
headers: { headers: {
Authorization: '', Authorization: '',
}, },
}); })
// 获取字典 // 获取字典
export const getDict = () => export const getDict = () =>
$req({ $req({
url: '/sysDictType/tree', url: '/sysDictType/tree',
}); })
// 设备通知注册 // 设备通知注册
export const bindRegId = (data: anyObj) => export const bindRegId = (data: any) =>
$req({ $req({
url: '/sysUser/dealRegistrationId', url: '/sysUser/dealRegistrationId',
method: 'post', method: 'post',
data, 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({ return $req({
method: 'post', method: 'post',
url: '/orderManagement/createInspectorPrepayOrder', url: '/orderManagement/createInspectorPrepayOrder',
data, data,
}); })
}; }

View File

@ -1,4 +1,4 @@
import $req from './request'; import $req from './request'
/** /**
* *
* @param data * @param data
@ -8,18 +8,18 @@ export const getBindPhoneTypeApi = data => {
method: 'post', method: 'post',
url: '/xcx/login/getBindRelationList', url: '/xcx/login/getBindRelationList',
data, data,
}); })
}; }
/** /**
* *
* @param data * @param data
*/ */
export const getAdminTypeByPhoneApi = (params: anyObj) => export const getAdminTypeByPhoneApi = (params: any) =>
$req({ $req({
method: 'get', method: 'get',
url: '/sysUser/getAdminTypeByPhone', url: '/sysUser/getAdminTypeByPhone',
params, params,
}); })
/** /**
* *
@ -29,7 +29,7 @@ export const sendCodeMessageApi = data =>
$req({ $req({
method: 'post', method: 'post',
url: `/sms/sendMessage?phoneNumbers=${data}`, url: `/sms/sendMessage?phoneNumbers=${data}`,
}); })
/** /**
* *
@ -43,7 +43,7 @@ export const smsLoginApi = data =>
...data, ...data,
clientType: 'MOBILE', clientType: 'MOBILE',
}, },
}); })
/** /**
* *
@ -53,17 +53,17 @@ export const getUserInfoApi = () =>
$req({ $req({
method: 'get', method: 'get',
url: '/sysUser/selectUser', url: '/sysUser/selectUser',
}); })
/** /**
* *
* @param data * @param data
*/ */
export const updateUserInfoApi = (data: anyObj) => export const updateUserInfoApi = (data: any) =>
$req({ $req({
method: 'post', method: 'post',
url: '/sysUser/updateInfo', url: '/sysUser/updateInfo',
data, data,
}); })
/** /**
* *
* @param data * @param data
@ -73,7 +73,7 @@ export const bindAuthInfoApi = data =>
method: 'post', method: 'post',
url: '/wechatPublic/bindAuthInfo', url: '/wechatPublic/bindAuthInfo',
data, data,
}); })
/** /**
* *
* @param data * @param data
@ -82,10 +82,10 @@ export const getAuthUrlApi = () =>
$req({ $req({
method: 'get', method: 'get',
url: '/wechatPublic/getAuthUrl', url: '/wechatPublic/getAuthUrl',
}); })
// 退出登录 // 退出登录
export const logout = () => export const logout = () =>
$req({ $req({
url: '/logout', url: '/logout',
}); })

View File

@ -1,60 +1,60 @@
import $req from '../request'; import $req from '../request'
/** /**
* - * -
* @param data * @param data
*/ */
export const subjectApi = (gradeId: string) => export const subjectApi = (gradeId: string) =>
$req({ $req({
method: 'get', method: 'get',
url: `/subject/list/${gradeId}`, url: `/subject/list/${gradeId}`,
}); })
/** /**
* - * -
* @param data * @param data
*/ */
export const studyTimeStatApi = (params: anyobj) => export const studyTimeStatApi = (params: any) =>
$req({ $req({
method: 'get', method: 'get',
url: '/userSubjectReport/study/duration', url: '/userSubjectReport/study/duration',
params, params,
}); })
/** /**
* - * -
* @param data * @param data
*/ */
export const videoStudyStatApi = (params: anyobj) => export const videoStudyStatApi = (params: any) =>
$req({ $req({
method: 'get', method: 'get',
url: '/userSubjectReport/study/video', url: '/userSubjectReport/study/video',
params, params,
}); })
/** /**
* - * -
* @param data * @param data
*/ */
export const knowledgeStudyStatApi = (params: anyobj) => export const knowledgeStudyStatApi = (params: any) =>
$req({ $req({
method: 'get', method: 'get',
url: '/userSubjectReport/study/knowledge', url: '/userSubjectReport/study/knowledge',
params, params,
}); })
/** /**
* - * -
* @param data * @param data
*/ */
export const studyErrorStatApi = (params: anyobj) => export const studyErrorStatApi = (params: any) =>
$req({ $req({
method: 'get', method: 'get',
url: '/userSubjectReport/study/error', url: '/userSubjectReport/study/error',
params, params,
}); })
/** /**
* - * -
* @param data * @param data
*/ */
export const englishLanguageStatApi = (data: anyobj) => export const englishLanguageStatApi = (data: any) =>
$req({ $req({
method: 'post', method: 'post',
url: '/userSentenceLearn/queryStatListByTimeType', url: '/userSentenceLearn/queryStatListByTimeType',
data, data,
}); })

View File

@ -1,30 +1,27 @@
import $req from '../request'; import $req from '../request'
import { request } from '../request/request'
/** /**
* *
* @param data * @param data
*/ */
export const getJsapiSignatureApi = (params: anyobj) => export const getJsapiSignatureApi = async (params: any) =>
$req({ await request.get<any>('/wechatPublic/createJsapiSignature', params)
method: 'get',
url: '/wechatPublic/createJsapiSignature',
params,
});
/** /**
* *
* @param data * @param data
*/ */
export const parentBindInviteApi = (data: anyobj) => export const parentBindInviteApi = (data: any) =>
$req({ $req({
method: 'post', method: 'post',
url: '/parentBindInvite', url: '/parentBindInvite',
data, data,
}); })
/** /**
* *
* @param data * @param data
*/ */
export const getInviteInfoApi = id => export const getInviteInfoApi = id =>
$req({ $req({
method: 'get', method: 'get',
url: `/parentBindInvite/${id}`, url: `/parentBindInvite/${id}`,
}); })

View File

@ -5,7 +5,7 @@ import { request } from '../request/request'
* *
* @param params * @param params
*/ */
export const getParentBindChildApi = (params: anyObj) => export const getParentBindChildApi = (params: any) =>
$req({ $req({
method: 'get', method: 'get',
url: '/parentBindChild/list', url: '/parentBindChild/list',
@ -15,7 +15,7 @@ export const getParentBindChildApi = (params: anyObj) =>
* *
* @param params * @param params
*/ */
export const getParentBindDeviceApi = async (params: anyObj) => export const getParentBindDeviceApi = async (params: any) =>
await request.get<any>('/parentBindDevice/list', params) await request.get<any>('/parentBindDevice/list', params)
/** /**
* - * -
@ -30,7 +30,7 @@ export const parentBindChildApi = (id: string) =>
* - * -
* @param params * @param params
*/ */
export const childUnBoundParentApi = (data: anyObj) => export const childUnBoundParentApi = (data: any) =>
$req({ $req({
method: 'post', method: 'post',
url: '/parentBindChild/unBound', url: '/parentBindChild/unBound',
@ -40,7 +40,7 @@ export const childUnBoundParentApi = (data: anyObj) =>
* - * -
* @param params * @param params
*/ */
export const parentUnBoundDeviceApi = (data: anyObj) => export const parentUnBoundDeviceApi = (data: any) =>
$req({ $req({
method: 'post', method: 'post',
url: '/parentBindDevice/unBound', url: '/parentBindDevice/unBound',

View File

@ -1,31 +1,31 @@
import $req from '../request'; import $req from '../request'
/** /**
* - * -
* @param data * @param data
*/ */
export const getWarrantyCardMsgApi = () => export const getWarrantyCardMsgApi = () =>
$req({ $req({
method: 'get', method: 'get',
url: '/parentBind/getBindDeviceList', url: '/parentBind/getBindDeviceList',
}); })
/** /**
* - * -
* @param data * @param data
*/ */
export const applyServiceApi = (data: anyObj) => export const applyServiceApi = (data: any) =>
$req({ $req({
method: 'post', method: 'post',
url: '/deviceWarrantyRecord', url: '/deviceWarrantyRecord',
data, data,
}); })
/** /**
* - * -
* @param data * @param data
*/ */
export const getServiceHistoryApi = (params: anyObj) => export const getServiceHistoryApi = (params: any) =>
$req({ $req({
method: 'get', method: 'get',
url: '/deviceWarrantyRecord/list', url: '/deviceWarrantyRecord/list',
params, params,
}); })

View File

@ -1,6 +1,7 @@
import { user } from '@/store' import { user } from '@/store'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { getCache } from '@/utils' import { getCache } from '@/utils'
import router from '@/router/router'
import db from '@/utils/db' import db from '@/utils/db'
const CONFIG = { const CONFIG = {
@ -50,7 +51,7 @@ const request = async (config: Record<string, any>): Promise<any> => {
Authorization: mergerToken ? `Bearer ${mergerToken}` : '', Authorization: mergerToken ? `Bearer ${mergerToken}` : '',
...config.headers, ...config.headers,
}, },
complete(res: anyObj) { complete(res: any) {
if (res.statusCode === 200) { if (res.statusCode === 200) {
if (res.data.code === 200) { if (res.data.code === 200) {
return resolve(res.data) 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 res.href = (config.baseURL || CONFIG.baseURL) + config.url
if (res.statusCode === 401) { if (res.statusCode === 401) {
clear() clear()
uni.reLaunch({ router.reLaunch('/pages/login/index')
url: '/pages/login/index',
})
} else { } else {
uni.showToast({ uni.showToast({
icon: 'none', icon: 'none',

View File

@ -2,7 +2,7 @@ import { type RequestOptions, type ResponseData, type ErrorResponse } from './ty
import { HTTP_STATUS, ERROR_MSG } from './config' import { HTTP_STATUS, ERROR_MSG } from './config'
import router from '@/router/router' import router from '@/router/router'
import db from '@/utils/db' import db from '@/utils/db'
import toast from '@/utils/toast' import toast from '@/utils/hud'
// 请求拦截器 // 请求拦截器
export const requestInterceptor = async (options: RequestOptions): Promise<RequestOptions> => { export const requestInterceptor = async (options: RequestOptions): Promise<RequestOptions> => {

View File

@ -13,6 +13,8 @@
</uni-nav-bar> </uni-nav-bar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import router from '@/router/router'
const props = defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
@ -26,32 +28,19 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}); })
const emits = defineEmits(['back']); const emits = defineEmits(['back'])
function onLeftClick() { function onLeftClick() {
emits('back'); emits('back')
if (props.custom) { if (props.custom) {
console.log('自定义返回路径'); return
return;
} }
console.log('默认组件返回路径'); const canNavBack = getCurrentPages()
const canNavBack = getCurrentPages();
if (canNavBack && canNavBack.length > 1) { if (canNavBack && canNavBack.length > 1) {
uni.navigateBack({ router.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,
// });
// }
} else { } else {
history.back(); history.back()
} }
} }
</script> </script>

View File

@ -8,7 +8,11 @@
</view> </view>
</slot> </slot>
</view> </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> <student-calender v-model:value="currentDate"></student-calender>
<view class="calender-popup-footer"> <view class="calender-popup-footer">
<wd-button :round="false" class="cancel" @click="closeCalender">取消</wd-button> <wd-button :round="false" class="cancel" @click="closeCalender">取消</wd-button>
@ -18,22 +22,22 @@
</view> </view>
</template> </template>
<script setup lang=ts> <script setup lang="ts">
import {ref, watch} from "vue"; import { ref, watch } from 'vue'
import StudentCalender from "@/components/student-calendar/index" import StudentCalender from '@/components/student-calendar/index.vue'
import dayjs from "dayjs"; import dayjs from 'dayjs'
const props = defineProps({ const props = defineProps({
value: { value: {
type: [String, dayjs], type: [String, dayjs],
}, },
}); })
const emit = defineEmits(['update:value', 'change']) const emit = defineEmits(['update:value', 'change'])
const showCalenderPopup = ref(false) const showCalenderPopup = ref(false)
const currentDate = ref("") const currentDate = ref('')
const openCalender = () => { const openCalender = () => {
showCalenderPopup.value = true showCalenderPopup.value = true
@ -43,17 +47,21 @@ const closeCalender = () => {
showCalenderPopup.value = false showCalenderPopup.value = false
} }
watch(() => props.value, value => { watch(
if (value) { () => props.value,
currentDate.value = typeof value === "string" ? value : value.format('YYYY-MM-DD') value => {
} else { if (value) {
currentDate.value = dayjs().format('YYYY-MM-DD') currentDate.value = typeof value === 'string' ? value : value.format('YYYY-MM-DD')
} } else {
}, {immediate: true, deep: true}) currentDate.value = dayjs().format('YYYY-MM-DD')
}
},
{ immediate: true, deep: true },
)
const confirm = () => { const confirm = () => {
emit('update:value', dayjs(currentDate.value)) emit('update:value', dayjs(currentDate.value))
emit("change", dayjs(currentDate.value)) emit('change', dayjs(currentDate.value))
closeCalender() closeCalender()
} }
</script> </script>
@ -84,8 +92,8 @@ const confirm = () => {
width: 230rpx; width: 230rpx;
height: 90rpx; height: 90rpx;
border-radius: 20rpx; border-radius: 20rpx;
background-color: #EFEFFF; background-color: #efefff;
color: #615DFF; color: #615dff;
font-size: 30rpx; font-size: 30rpx;
font-weight: 500; font-weight: 500;
} }
@ -93,7 +101,7 @@ const confirm = () => {
width: 440rpx; width: 440rpx;
height: 90rpx; height: 90rpx;
border-radius: 20rpx; border-radius: 20rpx;
background-color: #615DFF; background-color: #615dff;
color: #fff; color: #fff;
font-size: 30rpx; font-size: 30rpx;
font-weight: 500; font-weight: 500;

View File

@ -1,46 +1,48 @@
<script setup lang="ts"> <script setup lang="ts">
import router from '@/router/router'
// import { handleBack } from '../../hooks'; // import { handleBack } from '../../hooks';
const props = defineProps<{ const props = defineProps<{
title: string; title: string
}>(); }>()
function handleBack() { function handleBack() {
uni.navigateBack({ delta: 1 }); router.navigateBack()
} }
</script> </script>
<template> <template>
<wd-navbar <wd-navbar
:title="title" :title="title"
:bordered="false" :bordered="false"
safeAreaInsetTop safeAreaInsetTop
fixed fixed
placeholder placeholder
left-arrow left-arrow
:z-index="0" :z-index="0"
@click-left="handleBack" @click-left="handleBack"
> >
<template #left> <template #left>
<image <image
src="https://box-1313840333.cos.ap-guangzhou.myqcloud.com/subpackage/back.svg" src="https://box-1313840333.cos.ap-guangzhou.myqcloud.com/subpackage/back.svg"
class="back_icon" class="back_icon"
></image> ></image>
</template> </template>
</wd-navbar> </wd-navbar>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.back_icon { .back_icon {
width: 48rpx; width: 48rpx;
height: 48rpx; height: 48rpx;
} }
:deep(.wd-navbar) { :deep(.wd-navbar) {
.wd-navbar { .wd-navbar {
&__left { &__left {
padding-left: $space; padding-left: $space;
width: 48rpx; width: 48rpx;
}
&__title {
text-align: center;
}
} }
&__title {
text-align: center;
}
}
} }
</style> </style>

View File

@ -22,11 +22,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onShow } from '@dcloudio/uni-app' import { onMounted, ref } from 'vue'
import { onMounted, reactive, ref } from 'vue'
import { bindApplyStore } from '@/store' import { bindApplyStore } from '@/store'
import router from '@/router/router'
import { getCache } from '@/utils'
import db from '@/utils/db' import db from '@/utils/db'
const props = withDefaults( const props = withDefaults(
@ -45,7 +44,7 @@ const notice = ref(false)
function switchTab(item, index) { function switchTab(item, index) {
currentIndex.value = index currentIndex.value = index
let url = item.pagePath let url = item.pagePath
uni.redirectTo({ url: url }) router.redirectTo({ path: url })
} }
onMounted(async () => { onMounted(async () => {

View File

@ -1,44 +1,44 @@
<script setup lang="ts"> <script setup lang="ts">
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
name: string; name: string
size?: string | number; size?: string | number
color?: string; color?: string
img?: boolean; img?: boolean
}>(), }>(),
{ img: false, size: '40rpx' }, { img: false, size: '40rpx' },
); )
const emit = defineEmits(['click']); const emit = defineEmits(['click'])
defineOptions({ defineOptions({
options: { options: {
virtualHost: true, virtualHost: true,
}, },
}); })
</script> </script>
<template> <template>
<nut-icon <nut-icon
v-if="props.img" v-if="props.img"
:name="`https://box-1313840333.cos.ap-guangzhou.myqcloud.com/mobole-terminal/icon/${props.name}.svg`" :name="`https://box-1313840333.cos.ap-guangzhou.myqcloud.com/mobole-terminal/icon/${props.name}.svg`"
:width="props.size" :width="props.size"
:height="props.size" :height="props.size"
@click="(e: anyObj) => emit('click', e)" @click="(e: any) => emit('click', e)"
></nut-icon> ></nut-icon>
<nut-icon <nut-icon
v-else v-else
font-class-name="iconfont" font-class-name="iconfont"
class-prefix="icon" class-prefix="icon"
:name="props.name" :name="props.name"
:custom-color="props.color" :custom-color="props.color"
:size="props.size" :size="props.size"
:width="props.size" :width="props.size"
:height="props.size" :height="props.size"
@click="(e: anyObj) => emit('click', e)" @click="(e: any) => emit('click', e)"
></nut-icon> ></nut-icon>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.iconfont { .iconfont {
color: $uni-text-color; color: $uni-text-color;
} }
</style> </style>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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,
}
})

View 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>

View File

@ -0,0 +1,6 @@
.mj-list.round {
.tui-list-content {
border-radius: 20rpx !important;
overflow: hidden !important;
}
}

View 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>

View 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>

View 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'
},
//iconpx
iconSize: {
type: Number,
default: 24
},
closable: {
type: Boolean,
default: false
},
closeColor: {
type: String,
default: '#fff'
},
//iconpx
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>

View 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>

View 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,whiteblackgray,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>

View 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>

View File

@ -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) {
//type1-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>

View 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>

View 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>

View 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, grayblack,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
},
//linkplain使
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>

View 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 (02930)
* @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 (-12930)
* @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 (-128293031)
* @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
};

View 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/06type=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>

View 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>

View File

@ -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,
//tabscrollview
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>

View 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',
//xitem 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,
//showtruevalheightval 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',
//yitem 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>

View 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',
//yitempadding
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>

View 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',
//xitempadding
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,
//showtruevalheightval 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',
//yitem 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>

View 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',
//horizontalvertical
direction: 'horizontal'
}
}
},
tooltip: {
type: Boolean,
default: true
},
//ascdesc
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>

View 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',
//xitem 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,
//showtruevalheightval 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',
//yitem 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>

View File

@ -0,0 +1,20 @@
<template>
<view>
</view>
</template>
<script>
export default {
name:"tui-charts-mixed",
data() {
return {
};
}
}
</script>
<style scoped>
</style>

View 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',
//horizontalvertical
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>

View 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>

View 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',
//yitem 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>

View 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>

View 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>

View File

@ -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[heightheight]
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>

View 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: ''
},
//H5type
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>

File diff suppressed because it is too large Load Diff

View 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>

View 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

View 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'
},
//tooltipfalsebuttons
showCopyBtn: {
type: Boolean,
default: true
},
//topleftrightbottom
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>

View File

@ -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>

View 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',
//91
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>

View 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
},
//falselimitMovefalse
//App
disableRotate: {
type: Boolean,
default: true
},
//(,true)
//:falseapi
limitMove: {
type: Boolean,
default: true
},
//true
custom: {
type: Boolean,
default: false
},
//customtrue
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, //y0
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 => {
//base64url
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>

View 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
}

View File

@ -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

View File

@ -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

View 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
},
//customtrue
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, //y0
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>

View 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
}

View 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-(topright)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>

View 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
},
//01
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>

View 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>

View 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>

View File

@ -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>

View 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>

View 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>

View 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
},
//divider400rpx
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'
},
//线truedivideColor
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>

View 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

View 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() {
// truewxs,
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>

View 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
}

View 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>

View File

@ -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>

View 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 0auto
left: {
type: [Number, String],
default: 0
},
//rpx 0left0auto
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>

View 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>

View 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 phoneNumberuserInfo
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>

View 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>

View 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,
//itemrules
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
},
//FormproprulesFormrules
//formItemrulesrules使
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;
},
// FormFormItem 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>

View 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'
},
//falseFormItem
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使FormItemrules
//{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>

View 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
};

View 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>

View 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
},
// pxsize使
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>

View 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>

View 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>

View 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"
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View 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'
},
//pagescroll-viewimage
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
},
// rowdistance0
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>

View 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) {
// hexrgb
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
},
//topbottomrpx 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>

View 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>

View File

@ -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>

View 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>

View 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-radiotui-checkboxtui-switchlabel
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>

View 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
},
//icon1- 2- 3-
position: {
type: [Number, String],
default: 1
},
//topposition23
iconTop: {
type: String,
default: '-120rpx'
},
//bottomposition1
iconBottom: {
type: String,
default: '-120rpx'
},
//leftposition3
iconLeft: {
type: String,
default: '0'
},
//rightposition2
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>

View 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'
},
//imagemode
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'
},
//automodewidthFix
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>

View 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