fix:重构前的保存
This commit is contained in:
parent
012c7c8c17
commit
feeaa6a561
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -48,5 +48,5 @@
|
|||||||
"src/uni_modules/uni-search-bar/components/uni-search-bar/i18n",
|
"src/uni_modules/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"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
11
pnpm-lock.yaml
generated
@ -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': {}
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|||||||
@ -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',
|
||||||
});
|
})
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
})
|
||||||
|
|||||||
@ -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}`,
|
||||||
});
|
})
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
})
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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> => {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
12
src/components/mjui/mj-empty/mj-empty.vue
Normal file
12
src/components/mjui/mj-empty/mj-empty.vue
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({ name: 'mj-empty' })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tui-no-data imgUrl="/static/nodata.png">暂无数据</tui-no-data>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mj-empty {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
src/components/mjui/mj-list-cell/mj-list-cell.vue
Normal file
80
src/components/mjui/mj-list-cell/mj-list-cell.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({ name: 'mj-list-cell' })
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
icon?: string
|
||||||
|
iconWidth?: any
|
||||||
|
title?: string
|
||||||
|
height?: any
|
||||||
|
arrow?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
iconWidth: 40,
|
||||||
|
height: 60,
|
||||||
|
arrow: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const { icon, iconWidth, title, arrow, height } = props
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tui-list-cell lineLeft="0" v-bind="$attrs" :arrow="arrow" @click="emit('click')">
|
||||||
|
<view
|
||||||
|
class="cell-box"
|
||||||
|
:style="{
|
||||||
|
height: height + 'rpx',
|
||||||
|
paddingRight: arrow ? 40 + 'rpx' : 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<view class="left">
|
||||||
|
<view class="mj-icon" v-if="icon">
|
||||||
|
<image
|
||||||
|
:style="{
|
||||||
|
width: iconWidth + 'rpx',
|
||||||
|
}"
|
||||||
|
:src="icon"
|
||||||
|
class="cell-logo"
|
||||||
|
mode="widthFix"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
<text class="cell-title">{{ title }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="right">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</tui-list-cell>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.cell-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-logo {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
}
|
||||||
|
|
||||||
|
.mj-icon {
|
||||||
|
width: 50rpx;
|
||||||
|
margin-right: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
28
src/components/mjui/mj-list/mj-list.vue
Normal file
28
src/components/mjui/mj-list/mj-list.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({ name: 'mj-list' })
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
round?: boolean
|
||||||
|
margin?: string
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
round: false,
|
||||||
|
margin: '0',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<!-- 支付宝小程序中,必须要再套一层。class在自定义组件上几乎是失效的 -->
|
||||||
|
<view
|
||||||
|
class="mj-list"
|
||||||
|
:class="{
|
||||||
|
round: props.round,
|
||||||
|
}"
|
||||||
|
:style="{ margin: props.margin }"
|
||||||
|
>
|
||||||
|
<tui-list-view v-bind="$attrs"><slot /></tui-list-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
188
src/components/mjui/mj-page/mj-page.vue
Normal file
188
src/components/mjui/mj-page/mj-page.vue
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import dict from '@/utils/dict'
|
||||||
|
import page from '@/utils/page'
|
||||||
|
import {
|
||||||
|
dialog_dict_key,
|
||||||
|
type DialogOptions,
|
||||||
|
hide_dict_key,
|
||||||
|
loading_dict_key,
|
||||||
|
toast_bg_colors,
|
||||||
|
toast_dict_key,
|
||||||
|
} from '@/utils/hud'
|
||||||
|
import { onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
const props = defineProps<{
|
||||||
|
background?: any
|
||||||
|
padding?: any
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineOptions({ name: 'MjPage' })
|
||||||
|
|
||||||
|
// 页面路径
|
||||||
|
let pagePath = ''
|
||||||
|
|
||||||
|
const bgColor = ref(toast_bg_colors.info)
|
||||||
|
// toast的引用
|
||||||
|
const toastRef = ref(null as any)
|
||||||
|
// 显示toast
|
||||||
|
function toast(type: string, msg: string, duration: number) {
|
||||||
|
bgColor.value = toast_bg_colors[type]
|
||||||
|
toastRef?.value.showTips({
|
||||||
|
msg,
|
||||||
|
duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// loading
|
||||||
|
const loadingRef = ref({
|
||||||
|
show: false,
|
||||||
|
text: undefined,
|
||||||
|
})
|
||||||
|
// 显示loading
|
||||||
|
function loading(text: any) {
|
||||||
|
loadingRef.value.show = true
|
||||||
|
loadingRef.value.text = text
|
||||||
|
}
|
||||||
|
// 隐藏loading
|
||||||
|
function hide() {
|
||||||
|
loadingRef.value.show = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialog的引用
|
||||||
|
const dialog_default_title = '提示'
|
||||||
|
const dialog_default_buttons = [
|
||||||
|
{
|
||||||
|
text: '取消',
|
||||||
|
color: '#999',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '确定',
|
||||||
|
color: '#333',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const dialogRef = ref({
|
||||||
|
show: false,
|
||||||
|
title: dialog_default_title,
|
||||||
|
buttons: dialog_default_buttons,
|
||||||
|
close: undefined as any,
|
||||||
|
click: undefined as any,
|
||||||
|
content: '',
|
||||||
|
maskClosable: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 显示dialog
|
||||||
|
function dialog(options: DialogOptions) {
|
||||||
|
if (!options.title) {
|
||||||
|
options.title = dialog_default_title
|
||||||
|
}
|
||||||
|
if (!options.buttons) {
|
||||||
|
options.buttons = cloneDeep(dialog_default_buttons)
|
||||||
|
// 取消按钮
|
||||||
|
if (options.hideCancel) {
|
||||||
|
// 删除取消按钮
|
||||||
|
options.buttons.shift()
|
||||||
|
options.cancel = undefined
|
||||||
|
} else {
|
||||||
|
if (options.cancelText) {
|
||||||
|
options.buttons[0].text = options.cancelText
|
||||||
|
}
|
||||||
|
if (options.cancelColor) {
|
||||||
|
options.buttons[0].color = options.cancelColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定按钮
|
||||||
|
const okIdx = options.buttons.length - 1
|
||||||
|
if (options.okText) {
|
||||||
|
options.buttons[okIdx].text = options.okText
|
||||||
|
}
|
||||||
|
if (options.okColor) {
|
||||||
|
options.buttons[okIdx].color = options.okColor
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 自定义按钮
|
||||||
|
options.cancel = undefined
|
||||||
|
options.ok = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击事件处理
|
||||||
|
const clickFn = options.click
|
||||||
|
const okFn = options.ok
|
||||||
|
const cancelFn = options.cancel
|
||||||
|
options.click = (e: any) => {
|
||||||
|
const hide = () => {
|
||||||
|
dialogRef.value.show = false
|
||||||
|
}
|
||||||
|
if (clickFn) {
|
||||||
|
clickFn(hide, e)
|
||||||
|
}
|
||||||
|
// 点击了取消按钮
|
||||||
|
if (cancelFn && e.index === 0) {
|
||||||
|
cancelFn()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
// 点击了确定按钮
|
||||||
|
const okIdx = (options.buttons?.length || 1) - 1
|
||||||
|
if (okFn && e.index === okIdx) {
|
||||||
|
okFn(hide)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialogRef.value = options as any
|
||||||
|
dialogRef.value.show = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
pagePath = page.getPagePath()
|
||||||
|
dict.setFn(toast_dict_key, toast)
|
||||||
|
dict.setFn(loading_dict_key, loading)
|
||||||
|
dict.setFn(hide_dict_key, hide)
|
||||||
|
dict.setFn(dialog_dict_key, dialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
function deinit() {
|
||||||
|
dict.popPageFns(pagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(init)
|
||||||
|
onActivated(init)
|
||||||
|
onUnmounted(deinit)
|
||||||
|
onDeactivated(deinit)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="global-wrapper"
|
||||||
|
:style="{
|
||||||
|
background: props.background,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<view class="global-page">
|
||||||
|
<view
|
||||||
|
class="global-content"
|
||||||
|
:style="{
|
||||||
|
padding: props.padding,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<tui-tips ref="toastRef" :backgroundColor="bgColor" position="center"></tui-tips>
|
||||||
|
<tui-loading
|
||||||
|
v-if="loadingRef.show"
|
||||||
|
maskColor="rgba(0, 0, 0, 0)"
|
||||||
|
:text="loadingRef.text"
|
||||||
|
is-mask
|
||||||
|
></tui-loading>
|
||||||
|
<tui-dialog
|
||||||
|
:buttons="dialogRef.buttons"
|
||||||
|
:show="dialogRef.show"
|
||||||
|
:title="dialogRef.title"
|
||||||
|
:maskClosable="dialogRef.maskClosable"
|
||||||
|
@close="dialogRef.close"
|
||||||
|
@click="dialogRef.click"
|
||||||
|
>
|
||||||
|
<template v-slot:content>{{ dialogRef.content }}</template>
|
||||||
|
</tui-dialog>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
37
src/components/mjui/mj-password/mj-password.vue
Normal file
37
src/components/mjui/mj-password/mj-password.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({ name: 'mj-password' })
|
||||||
|
|
||||||
|
// 对于双向绑定的属性,小程序端不能依靠v-bind="$attrs"来实现双向绑定
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue?: any
|
||||||
|
}>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const modelValue = computed({
|
||||||
|
get() {
|
||||||
|
return props.modelValue
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const password = ref(true)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tui-input v-model="modelValue" placeholder="请输入密码" :password="password" v-bind="$attrs">
|
||||||
|
<template v-slot:right>
|
||||||
|
<tui-icon
|
||||||
|
:name="password ? 'unseen' : 'seen'"
|
||||||
|
color="#B2B2B2"
|
||||||
|
margin="0 0 0
|
||||||
|
20rpx"
|
||||||
|
@click="password = !password"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</tui-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
41
src/components/mjui/mj-tabbar/mj-tabbar.ts
Normal file
41
src/components/mjui/mj-tabbar/mj-tabbar.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import router from '@/router/router'
|
||||||
|
import page from '@/utils/page'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
export const useTabbarStore = defineStore('tabbar', () => {
|
||||||
|
const list = [
|
||||||
|
{
|
||||||
|
pagePath: '/pages/home/home',
|
||||||
|
text: '首页',
|
||||||
|
iconPath: '/static/footerBar/home-icon-link.png',
|
||||||
|
selectedIconPath: '/static/footerBar/home-icon-active.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: '/pages/bill/bill',
|
||||||
|
text: '学费账单',
|
||||||
|
iconPath: '/static/footerBar/tuitionBill-icon-link.png',
|
||||||
|
selectedIconPath: '/static/footerBar/tuitionBill-icon-active.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pagePath: '/pages/mine/mine',
|
||||||
|
text: '我的',
|
||||||
|
iconPath: '/static/footerBar/main-icon-link.png',
|
||||||
|
selectedIconPath: '/static/footerBar/main-icon-active.png',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const currentUrl = page.getPagePath()
|
||||||
|
const current = ref(list.findIndex(item => item.pagePath === currentUrl))
|
||||||
|
|
||||||
|
function switchTab(e: any) {
|
||||||
|
if (current.value === e.index) return
|
||||||
|
current.value = e.index
|
||||||
|
router.switchTab(list[e.index].pagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
current,
|
||||||
|
switchTab,
|
||||||
|
}
|
||||||
|
})
|
||||||
23
src/components/mjui/mj-tabbar/mj-tabbar.vue
Normal file
23
src/components/mjui/mj-tabbar/mj-tabbar.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useTabbarStore } from './mj-tabbar'
|
||||||
|
|
||||||
|
const { list, current, switchTab } = useTabbarStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tui-tabbar
|
||||||
|
:current="current"
|
||||||
|
:unlined="true"
|
||||||
|
:tabBar="list"
|
||||||
|
color="#777"
|
||||||
|
selectedColor="#4575f5"
|
||||||
|
@click="switchTab"
|
||||||
|
></tui-tabbar>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
/* 修复bug */
|
||||||
|
.tui-unlined::before {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
6
src/components/mjui/mjui.scss
Normal file
6
src/components/mjui/mjui.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.mj-list.round {
|
||||||
|
.tui-list-content {
|
||||||
|
border-radius: 20rpx !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
219
src/components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal file
219
src/components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<view @touchmove.stop.prevent>
|
||||||
|
<view class="tui-actionsheet" :class="{'tui-actionsheet-show':show,'tui-actionsheet-radius':radius}"
|
||||||
|
:style="{zIndex:getZIndex}">
|
||||||
|
<view class="tui-actionsheet-tips" :style="{fontSize:size+'rpx',color:color}" v-if="tips">
|
||||||
|
{{tips}}
|
||||||
|
</view>
|
||||||
|
<view :class="[isCancel?'tui-operate-box':'']">
|
||||||
|
<block v-for="(item,index) in itemList" :key="index">
|
||||||
|
<view class="tui-actionsheet-btn tui-actionsheet-divider"
|
||||||
|
:class="{'tui-btn-last':!isCancel && index==itemList.length-1}"
|
||||||
|
hover-class="tui-actionsheet-hover" :hover-stay-time="150" :data-index="index"
|
||||||
|
:style="{color:item.color || textColor,fontSize:(item.size || textSize)+'rpx'}"
|
||||||
|
@tap="handleClickItem">{{item[textField]}}</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
<view class="tui-actionsheet-btn tui-actionsheet-cancel" hover-class="tui-actionsheet-hover"
|
||||||
|
:hover-stay-time="150" :style="{fontSize:textSize+'rpx',color:cancelColor}" v-if="isCancel"
|
||||||
|
@tap="handleClickCancel">取消</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-actionsheet-mask" :class="{'tui-mask-show':show}" :style="{background:maskColor,zIndex:zIndex}"
|
||||||
|
@tap="handleClickMask"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiActionsheet",
|
||||||
|
emits: ['click', 'cancel'],
|
||||||
|
props: {
|
||||||
|
//显示操作菜单
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//菜单按钮数组,自定义文本颜色,红色参考色:#e53a37
|
||||||
|
itemList: {
|
||||||
|
type: Array,
|
||||||
|
default: function() {
|
||||||
|
return [{
|
||||||
|
text: "确定",
|
||||||
|
color: "#2B2B2B",
|
||||||
|
size: 34
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
type: String,
|
||||||
|
default: "text"
|
||||||
|
},
|
||||||
|
textColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#2B2B2B'
|
||||||
|
},
|
||||||
|
textSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 34
|
||||||
|
},
|
||||||
|
//点击遮罩 是否可关闭
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//v2.1.0
|
||||||
|
maskColor: {
|
||||||
|
type: String,
|
||||||
|
default: "rgba(0, 0, 0, 0.6)"
|
||||||
|
},
|
||||||
|
//提示文字
|
||||||
|
tips: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
//提示文字颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "#808080"
|
||||||
|
},
|
||||||
|
//提示文字大小 rpx
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 26
|
||||||
|
},
|
||||||
|
//是否需要圆角
|
||||||
|
radius: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否需要取消按钮
|
||||||
|
isCancel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
cancelColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#1a1a1a'
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 998
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getZIndex() {
|
||||||
|
return Number(this.zIndex) + 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClickMask() {
|
||||||
|
if (!this.maskClosable) return;
|
||||||
|
this.handleClickCancel();
|
||||||
|
},
|
||||||
|
handleClickItem(e) {
|
||||||
|
if (!this.show) return;
|
||||||
|
const index = Number(e.currentTarget.dataset.index);
|
||||||
|
this.$emit('click', {
|
||||||
|
index: index,
|
||||||
|
...this.itemList[index]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleClickCancel() {
|
||||||
|
this.$emit('cancel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-actionsheet {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translate3d(0, 100%, 0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.25s ease-in-out;
|
||||||
|
background-color: #F7F7F7;
|
||||||
|
min-height: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-radius {
|
||||||
|
border-top-left-radius: 20rpx;
|
||||||
|
border-top-right-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-show {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-tips {
|
||||||
|
width: 100%;
|
||||||
|
padding: 40rpx 60rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-operate-box {
|
||||||
|
padding-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 100rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn-last {
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-divider::before {
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1rpx solid #E7E7E7;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-cancel {
|
||||||
|
color: #1a1a1a;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-hover {
|
||||||
|
background-color: #f7f7f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-actionsheet-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-mask-show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
140
src/components/thorui/tui-alert/tui-alert.vue
Normal file
140
src/components/thorui/tui-alert/tui-alert.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="tui-alert-class tui-alert-box" :class="[show?'tui-alert-show':'']">
|
||||||
|
<view class="tui-alert-content" :style="{fontSize:size+'rpx',color:color}">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view class="tui-alert-btn" :style="{color:getColor}" hover-class="tui-alert-btn-hover"
|
||||||
|
:hover-stay-time="150" @tap.stop="handleClick">{{btnText}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-alert-mask" :class="[show?'tui-alert-mask-show':'']" @tap.stop="handleClickCancel"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiAlert",
|
||||||
|
emits: ['click', 'cancel'],
|
||||||
|
props: {
|
||||||
|
//控制显示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//提示信息字体大小
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 30
|
||||||
|
},
|
||||||
|
//提示信息字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "#333"
|
||||||
|
},
|
||||||
|
//按钮字体颜色
|
||||||
|
btnColor: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
btnText: {
|
||||||
|
type: String,
|
||||||
|
default: "确定"
|
||||||
|
},
|
||||||
|
//点击遮罩 是否可关闭
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getColor() {
|
||||||
|
return this.btnColor || (uni && uni.$tui && uni.$tui.color.danger) || '#EB0909';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick(e) {
|
||||||
|
if (!this.show) return;
|
||||||
|
this.$emit('click', {});
|
||||||
|
},
|
||||||
|
handleClickCancel() {
|
||||||
|
if (!this.maskClosable) return;
|
||||||
|
this.$emit('cancel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-alert-box {
|
||||||
|
position: fixed;
|
||||||
|
width: 560rpx;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
transform: translate(-50%, -50%) scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert-show {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 999;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert-mask-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert-content {
|
||||||
|
text-align: center;
|
||||||
|
color: #333333;
|
||||||
|
padding: 98rpx 48rpx 92rpx 48rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 90rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert-btn-hover {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert-btn::before {
|
||||||
|
width: 100%;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border-top: 1rpx solid #E0E0E0;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
205
src/components/thorui/tui-alerts/tui-alerts.vue
Normal file
205
src/components/thorui/tui-alerts/tui-alerts.vue
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-alert__wrap"
|
||||||
|
:style="{backgroundColor:backgroundColor?backgroundColor:getColor(type),borderRadius:radius,paddingTop:padding[0] || 0,paddingRight:padding[1]||0,paddingBottom:padding[2] || padding[0]||0,paddingLeft:padding[3] || padding[1]||0}">
|
||||||
|
<view class="tui-alert__shrink" @tap.stop="leftClick">
|
||||||
|
<slot name="left"></slot>
|
||||||
|
<icon :type="type" :size="iconSize" :color="iconColor" v-if="!isLeft"></icon>
|
||||||
|
</view>
|
||||||
|
<view class="tui-alert__content" :class="{'tui-text__p-left':!isLeft,'tui-text__p-right':closable}"
|
||||||
|
@tap.stop="onClick">
|
||||||
|
<text class="tui-alert__text" :style="{fontSize:size,color:color}" v-if="title">{{title}}</text>
|
||||||
|
<text class="tui-alert__text tui-desc__padding" :class="{'tui-alert__single':single}"
|
||||||
|
:style="{fontSize:descSize,color:descColor}" v-if="desc">{{desc}}</text>
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</view>
|
||||||
|
<view class="tui-alert__shrink">
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</view>
|
||||||
|
<icon @tap.stop="close" type="cancel" :size="closeSize" :color="closeColor" v-if="closable"
|
||||||
|
:class="{'tui-alert__icon-close':desc}">
|
||||||
|
</icon>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-alerts",
|
||||||
|
emits: ['leftClick', 'click', 'close'],
|
||||||
|
props: {
|
||||||
|
//info, success, warn, waiting,clear
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'info'
|
||||||
|
},
|
||||||
|
//背景色,如果设置type对应颜色失效
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//nvue不支持简写,['20rpx','30rpx','20rpx','30rpx']=>[上,右,下,左]
|
||||||
|
padding: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return ['20rpx', '30rpx']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '6rpx'
|
||||||
|
},
|
||||||
|
iconColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//icon字体大小,px
|
||||||
|
iconSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
closable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
closeColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//关闭icon字体大小,px
|
||||||
|
closeSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
//是否自定义左侧内容
|
||||||
|
isLeft: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isRight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '14px'
|
||||||
|
},
|
||||||
|
desc: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
descColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
descSize: {
|
||||||
|
type: String,
|
||||||
|
default: '12px'
|
||||||
|
},
|
||||||
|
//描述文字单行展示,超出隐藏
|
||||||
|
single: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getColor(type) {
|
||||||
|
const global = uni && uni.$tui && uni.$tui.color;
|
||||||
|
const color = (global && global.primary) || '#5677fc'
|
||||||
|
const colors = {
|
||||||
|
'success': (global && global.success) || '#07c160',
|
||||||
|
'warn': (global && global.warning) || '#ff7900',
|
||||||
|
'clear': (global && global.danger) || '#EB0909'
|
||||||
|
}
|
||||||
|
return colors[type] ? colors[type] : color;
|
||||||
|
},
|
||||||
|
leftClick() {
|
||||||
|
this.$emit('leftClick', {})
|
||||||
|
},
|
||||||
|
onClick() {
|
||||||
|
this.$emit('click', {})
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$emit('close', {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-alert__wrap {
|
||||||
|
flex: 1;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert__shrink {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert__content {
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert__text {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
word-break: break-all;
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-desc__padding {
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-text__p-left {
|
||||||
|
padding-left: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-text__p-right {
|
||||||
|
padding-right: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert__single {
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
lines: 1;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-alert__icon-close {
|
||||||
|
position: absolute;
|
||||||
|
right: 30rpx;
|
||||||
|
top: 16rpx;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
151
src/components/thorui/tui-amount-inwords/tui-amount-inwords.vue
Normal file
151
src/components/thorui/tui-amount-inwords/tui-amount-inwords.vue
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<text v-if="show && money" class="tui-amount-inwords"
|
||||||
|
:style="{fontSize:size+'rpx',color:color,fontWeight:fontWeight,padding:padding}">{{money}}</text>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-amount-inwords",
|
||||||
|
emits: ['change'],
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
fontWeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
money: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(newValue, oldValue) {
|
||||||
|
this.money = this.getAmountInWords(newValue)
|
||||||
|
this.$emit('change', {
|
||||||
|
moeny: this.money
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.money = this.getAmountInWords(this.value)
|
||||||
|
this.$emit('change', {
|
||||||
|
moeny: this.money
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
trimAll: function(value) {
|
||||||
|
return value.toString().replace(/\s+/g, "").replace('¥',"")
|
||||||
|
},
|
||||||
|
getAmountInWords(money) {
|
||||||
|
money = this.trimAll(money)
|
||||||
|
//汉字的数字
|
||||||
|
let cnNums = new Array('零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖');
|
||||||
|
//基本单位
|
||||||
|
let cnIntRadice = new Array('', '拾', '佰', '仟');
|
||||||
|
//对应整数部分扩展单位
|
||||||
|
let cnIntUnits = new Array('', '万', '亿', '兆');
|
||||||
|
//对应小数部分单位
|
||||||
|
let cnDecUnits = new Array('角', '分', '毫', '厘');
|
||||||
|
//整数金额时后面跟的字符
|
||||||
|
let cnInteger = '整';
|
||||||
|
//整型完以后的单位
|
||||||
|
let cnIntLast = '元';
|
||||||
|
//最大处理的数字
|
||||||
|
let maxNum = 999999999999999.9999;
|
||||||
|
//金额整数部分
|
||||||
|
let integerNum;
|
||||||
|
//金额小数部分
|
||||||
|
let decimalNum;
|
||||||
|
//输出的中文金额字符串
|
||||||
|
let chineseStr = '';
|
||||||
|
//分离金额后用的数组,预定义
|
||||||
|
let parts;
|
||||||
|
if (money == '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
money = parseFloat(money);
|
||||||
|
if (money >= maxNum) {
|
||||||
|
//超出最大处理数字
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (money == 0) {
|
||||||
|
chineseStr = cnNums[0] + cnIntLast + cnInteger;
|
||||||
|
return chineseStr;
|
||||||
|
}
|
||||||
|
//转换为字符串
|
||||||
|
money = money.toString();
|
||||||
|
if (money.indexOf('.') == -1) {
|
||||||
|
integerNum = money;
|
||||||
|
decimalNum = '';
|
||||||
|
} else {
|
||||||
|
parts = money.split('.');
|
||||||
|
integerNum = parts[0];
|
||||||
|
decimalNum = parts[1].substr(0, 4);
|
||||||
|
}
|
||||||
|
//获取整型部分转换
|
||||||
|
if (parseInt(integerNum, 10) > 0) {
|
||||||
|
var zeroCount = 0;
|
||||||
|
var IntLen = integerNum.length;
|
||||||
|
for (var i = 0; i < IntLen; i++) {
|
||||||
|
var n = integerNum.substr(i, 1);
|
||||||
|
var p = IntLen - i - 1;
|
||||||
|
var q = p / 4;
|
||||||
|
var m = p % 4;
|
||||||
|
if (n == '0') {
|
||||||
|
zeroCount++;
|
||||||
|
} else {
|
||||||
|
if (zeroCount > 0) {
|
||||||
|
chineseStr += cnNums[0];
|
||||||
|
}
|
||||||
|
//归零
|
||||||
|
zeroCount = 0;
|
||||||
|
chineseStr += cnNums[parseInt(n)] + cnIntRadice[m];
|
||||||
|
}
|
||||||
|
if (m == 0 && zeroCount < 4) {
|
||||||
|
chineseStr += cnIntUnits[q];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chineseStr += cnIntLast;
|
||||||
|
}
|
||||||
|
//小数部分
|
||||||
|
if (decimalNum != '') {
|
||||||
|
let decLen = decimalNum.length;
|
||||||
|
for (let i = 0; i < decLen; i++) {
|
||||||
|
let n = decimalNum.substr(i, 1);
|
||||||
|
if (n != '0') {
|
||||||
|
chineseStr += cnNums[Number(n)] + cnDecUnits[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chineseStr == '') {
|
||||||
|
chineseStr += cnNums[0] + cnIntLast + cnInteger;
|
||||||
|
} else if (decimalNum == '') {
|
||||||
|
chineseStr += cnInteger;
|
||||||
|
}
|
||||||
|
return chineseStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
129
src/components/thorui/tui-badge/tui-badge.vue
Normal file
129
src/components/thorui/tui-badge/tui-badge.vue
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="[dot ? 'tui-badge-dot' : 'tui-badge', !dot ? 'tui-badge-scale' : '']"
|
||||||
|
:style="{ top: top, right: right, position: absolute ? 'absolute' : 'static', transform: getStyle, margin: margin,background:getBackground,color:getColor }"
|
||||||
|
@tap="handleClick">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiBadge',
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//primary,warning,green,danger,white,black,gray,white_red
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary'
|
||||||
|
},
|
||||||
|
//是否是圆点
|
||||||
|
dot: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//是否绝对定位
|
||||||
|
absolute: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
type: String,
|
||||||
|
default: '-8rpx'
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//缩放比例
|
||||||
|
scaleRatio: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//水平方向移动距离
|
||||||
|
translateX: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getStyle() {
|
||||||
|
return `scale(${this.scaleRatio}) translateX(${this.translateX})`;
|
||||||
|
},
|
||||||
|
getBackground() {
|
||||||
|
const global = uni && uni.$tui && uni.$tui.color;
|
||||||
|
let color = {
|
||||||
|
'primary': (global && global.primary) || '#5677fc',
|
||||||
|
'green': (global && global.success) || '#07c160',
|
||||||
|
'warning': (global && global.warning) || '#ff7900',
|
||||||
|
'danger': (global && global.danger) || '#EB0909',
|
||||||
|
'white': '#fff',
|
||||||
|
'black': '#000',
|
||||||
|
'gray': '#ededed',
|
||||||
|
'red': (global && global.pink) || '#f74d54',
|
||||||
|
'pink': (global && global.pink) || '#f74d54',
|
||||||
|
'white_red': '#fff',
|
||||||
|
'white_primary': '#fff',
|
||||||
|
'white_green': '#fff',
|
||||||
|
'white_warning': '#fff',
|
||||||
|
'white_pink': '#fff'
|
||||||
|
} [this.type]
|
||||||
|
return color
|
||||||
|
},
|
||||||
|
getColor() {
|
||||||
|
const global = uni && uni.$tui && uni.$tui.color;
|
||||||
|
let color = {
|
||||||
|
'primary': '#fff',
|
||||||
|
'green': '#fff',
|
||||||
|
'warning': '#fff',
|
||||||
|
'danger': '#fff',
|
||||||
|
'white': '#333',
|
||||||
|
'black': '#fff',
|
||||||
|
'gray': '#999',
|
||||||
|
'red': '#fff',
|
||||||
|
'pink': (global && global.pink) || '#f74d54',
|
||||||
|
'white_red': (global && global.danger) || '#EB0909',
|
||||||
|
'white_primary': (global && global.primary) || '#5677fc',
|
||||||
|
'white_green': (global && global.success) || '#07c160',
|
||||||
|
'white_warning': (global && global.warning) || '#ff7900',
|
||||||
|
'white_pink': (global && global.pink) || '#f74d54',
|
||||||
|
} [this.type]
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick() {
|
||||||
|
this.$emit('click', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-badge-dot {
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-badge {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
min-width: 36rpx;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-badge-scale {
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
72
src/components/thorui/tui-banner-arc/tui-banner-arc.vue
Normal file
72
src/components/thorui/tui-banner-arc/tui-banner-arc.vue
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-banner-arc" :style="{height:height+'rpx'}">
|
||||||
|
<view class="tui-banner--box"
|
||||||
|
:style="{background:background,height:height+'rpx',width:width+'%',paddingLeft:(width-100)/2+'%',paddingRight:(width-100)/2+'%',left:'-'+(width-100)/2+'%'}">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-banner-arc",
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
options: {
|
||||||
|
virtualHost: true
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
percent: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 120
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.width = this.getPercent(this.percent)
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
percent(val) {
|
||||||
|
this.width = this.getPercent(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
width: 120
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPercent(val) {
|
||||||
|
//最低值120,尽量传偶数值
|
||||||
|
val = Number(val || 0)
|
||||||
|
return val < 120 ? 120 : val
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-banner-arc {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-banner--box {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 0 0 50% 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,392 @@
|
|||||||
|
<template>
|
||||||
|
<view @touchmove.stop.prevent="stop">
|
||||||
|
<view class="tui-bottom-navigation" :style="{ backgroundColor: isDarkMode ? '#202020' : backgroundColor }" :class="{ 'tui-navigation-fixed': isFixed, 'tui-remove-splitLine': unlined }">
|
||||||
|
<view
|
||||||
|
class="tui-navigation-item"
|
||||||
|
:class="{ 'tui-item-after_height': splitLineScale, 'tui-last-item': index == itemList.length - 1 }"
|
||||||
|
v-for="(item, index) in itemList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<view class="tui-item-inner" @tap="menuClick(index, item.parameter, item.type)">
|
||||||
|
<image
|
||||||
|
:src="getIcon(current,index, item)"
|
||||||
|
class="tui-navigation-img"
|
||||||
|
v-if="item.iconPath || (current == index && item.selectedIconPath && item.type == 1)"
|
||||||
|
></image>
|
||||||
|
<text
|
||||||
|
class="tui-navigation-text"
|
||||||
|
:style="{
|
||||||
|
color: isDarkMode ? '#fff' : current == index && item.type == 1 ? getSelectColor : item.color || color,
|
||||||
|
fontWeight: current == index && bold && item.type == 1 ? 'bold' : 'normal',
|
||||||
|
fontSize: fontSize
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="tui-navigation-popup"
|
||||||
|
:class="{ 'tui-navigation-popup_show': showMenuIndex == index }"
|
||||||
|
:style="{ backgroundColor: isDarkMode ? '#4c4c4c' : subMenuBgColor, left: item.popupLeft || '50%' }"
|
||||||
|
v-if="item.itemList"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="tui-popup-cell"
|
||||||
|
:class="{ 'tui-first-cell': subIndex === 0, 'tui-last-cell': subIndex === item.itemList.length - 1 }"
|
||||||
|
:hover-class="subMenuHover ? (isDarkMode ? 'tui-item-dark_hover' : 'tui-item-hover') : ''"
|
||||||
|
:hover-stay-time="150"
|
||||||
|
v-for="(subItem, subIndex) in item.itemList || []"
|
||||||
|
:key="subIndex"
|
||||||
|
@tap="subMenuClick(index, item.type, subIndex, subItem.parameter || '')"
|
||||||
|
>
|
||||||
|
<text class="tui-ellipsis" :style="{ color: isDarkMode ? '#fff' : subMenuColor, fontSize: subMenufontSize, lineHeight: subMenufontSize }">
|
||||||
|
{{ subItem.text }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="tui-popup-triangle" :style="{ borderTopColor: isDarkMode ? '#4c4c4c' : subMenuBgColor }"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-navigation-mask" :class="{ 'tui-navigation-mask_show': showMenuIndex != -1 }" @tap="handleClose"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiBottomNavigation',
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//当前索引
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* {
|
||||||
|
text: 'ThorUI',
|
||||||
|
iconPath: '/static/images/common/icon_menu_gray.png',
|
||||||
|
selectedIconPath: '/static/images/common/icon_menu_gray.png',
|
||||||
|
color: '#666',
|
||||||
|
//1-选中切换,2-跳转、请求、其他操作,3-菜单
|
||||||
|
type: 3,
|
||||||
|
//自定义参数,类型自定义
|
||||||
|
parameter: null,
|
||||||
|
//子菜单left值,不传默认50%,当菜单贴近左右两边可用此参数调整
|
||||||
|
popupLeft: '',
|
||||||
|
itemList: [
|
||||||
|
{
|
||||||
|
//不建议超过6个字,请自行控制
|
||||||
|
text: '自定义参',
|
||||||
|
//自定义参数,类型自定义
|
||||||
|
parameter: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '自定义参数',
|
||||||
|
//自定义参数,类型自定义
|
||||||
|
parameter: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
itemList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#666'
|
||||||
|
},
|
||||||
|
//选中颜色
|
||||||
|
selectedColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
type: String,
|
||||||
|
default: '28rpx'
|
||||||
|
},
|
||||||
|
//选中后字体是否加粗
|
||||||
|
bold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//导航条背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#F8F8F8'
|
||||||
|
},
|
||||||
|
//item分割线高度是否缩小
|
||||||
|
splitLineScale: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//二级菜单字体颜色
|
||||||
|
subMenuColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//二级菜单字体大小
|
||||||
|
subMenufontSize: {
|
||||||
|
type: String,
|
||||||
|
default: '28rpx'
|
||||||
|
},
|
||||||
|
//二级菜单背景色 深色:#4c4c4c
|
||||||
|
subMenuBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//二级菜单是否有点击效果
|
||||||
|
subMenuHover: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否固定在底部
|
||||||
|
isFixed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//去除导航栏顶部的线条
|
||||||
|
unlined: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否暗黑模式 (true:所有设置颜色失效)
|
||||||
|
isDarkMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showMenuIndex: -1 //显示的菜单index
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
getSelectColor(){
|
||||||
|
return this.selectedColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getIcon: function(current, index, item) {
|
||||||
|
let url = item.iconPath;
|
||||||
|
if (item.type == 1) {
|
||||||
|
url = current == index ? item.selectedIconPath || item.iconPath : item.iconPath;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleClose() {
|
||||||
|
this.showMenuIndex = -1;
|
||||||
|
},
|
||||||
|
menuClick(index, parameter, type) {
|
||||||
|
//type:1-选中切换,2-跳转、请求、其他操作,3-菜单
|
||||||
|
if (type == 3) {
|
||||||
|
this.showMenuIndex = this.showMenuIndex == index ? -1 : index;
|
||||||
|
} else {
|
||||||
|
this.showMenuIndex = -1;
|
||||||
|
this.$emit('click', {
|
||||||
|
menu: 'main', //main,sub 主菜单,子菜单
|
||||||
|
type: type,
|
||||||
|
index: index,
|
||||||
|
parameter: parameter || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subMenuClick(index, type, subIndex, parameter) {
|
||||||
|
this.showMenuIndex = -1;
|
||||||
|
this.$emit('click', {
|
||||||
|
menu: 'sub', //main,sub 主菜单,子菜单
|
||||||
|
type: type,
|
||||||
|
index: index,
|
||||||
|
subIndex: subIndex,
|
||||||
|
parameter: parameter || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-bottom-navigation {
|
||||||
|
width: 100%;
|
||||||
|
height: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-fixed {
|
||||||
|
position: fixed !important;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bottom-navigation::after {
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid #bfbfbf;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.tui-remove-splitLine::before {
|
||||||
|
border-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-item {
|
||||||
|
flex: 1;
|
||||||
|
height: 100rpx;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-item-inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-item::after {
|
||||||
|
height: 100%;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-right: 1px solid #bfbfbf;
|
||||||
|
transform: scaleX(0.5) translateZ(0);
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-item-after_height::after {
|
||||||
|
height: 40% !important;
|
||||||
|
top: 30% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-last-item::after {
|
||||||
|
border-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-img {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-popup {
|
||||||
|
max-width: 160%;
|
||||||
|
width: auto;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(-50%, 0, 0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.12s ease-in-out;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-popup_show {
|
||||||
|
transform: translate3d(-50%, -124rpx, 0);
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-triangle {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 9rpx solid transparent;
|
||||||
|
border-right: 9rpx solid transparent;
|
||||||
|
border-top: 18rpx solid;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -18rpx;
|
||||||
|
-webkit-transform: translateX(-50%);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 997;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-cell {
|
||||||
|
width: 100%;
|
||||||
|
padding: 32rpx 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-ellipsis {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-cell::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1rpx solid #eaeef1;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
bottom: 0;
|
||||||
|
right: 24rpx;
|
||||||
|
left: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-item-hover {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-item-dark_hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-first-cell {
|
||||||
|
border-top-left-radius: 8rpx;
|
||||||
|
border-top-right-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-last-cell {
|
||||||
|
border-bottom-left-radius: 8rpx;
|
||||||
|
border-bottom-right-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-last-cell::after {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 995;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-navigation-mask_show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
119
src/components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal file
119
src/components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<view @touchmove.stop.prevent>
|
||||||
|
<view class="tui-popup-class tui-bottom-popup"
|
||||||
|
:class="{ 'tui-popup-show': show, 'tui-popup-radius': radius,'tui-bp__safearea':isSafeArea }"
|
||||||
|
:style="{ background: backgroundColor, height: height ? height + 'rpx' : 'auto', zIndex: zIndex,transform:`translate3d(0, ${show?translateY:'100%'}, 0)`}">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view class="tui-popup-mask" :class="[show ? 'tui-mask-show' : '']" :style="{ zIndex: maskZIndex }" v-if="mask"
|
||||||
|
@tap="handleClose"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiBottomPopup',
|
||||||
|
emits: ['close'],
|
||||||
|
props: {
|
||||||
|
//是否需要mask
|
||||||
|
mask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//控制显示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//高度 rpx
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//设置圆角
|
||||||
|
radius: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 997
|
||||||
|
},
|
||||||
|
maskZIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 996
|
||||||
|
},
|
||||||
|
//弹层显示时,垂直方向移动的距离
|
||||||
|
translateY: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//是否需要判断底部安全区域(主要针对iphonex以上机型)
|
||||||
|
isSafeArea: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose() {
|
||||||
|
if (!this.show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('close', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-bottom-popup {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 100%, 0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
min-height: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bp__safearea {
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-radius {
|
||||||
|
border-top-left-radius: 24rpx;
|
||||||
|
border-top-right-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-popup-show {
|
||||||
|
opacity: 1;
|
||||||
|
/* transform: translate3d(0, 0, 0); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-mask-show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
204
src/components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal file
204
src/components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="{ 'tui-flex-end': flexEnd }">
|
||||||
|
<view class="tui-popup-list" :class="{ 'tui-popup-show': show,'tui-z_index':show && position!='relative' }" :style="{ width: width, backgroundColor: backgroundColor, borderRadius: radius, color: color, position: position, left: left, right: right, bottom: bottom, top: top,transform:`translate(${translateX},${translateY})` }">
|
||||||
|
<view class="tui-triangle" :style="{
|
||||||
|
borderWidth: borderWidth,
|
||||||
|
borderColor: `transparent transparent ${backgroundColor} transparent`,
|
||||||
|
left: triangleLeft,
|
||||||
|
right: triangleRight,
|
||||||
|
top: triangleTop,
|
||||||
|
bottom: triangleBottom
|
||||||
|
}"
|
||||||
|
v-if="direction == 'top'"></view>
|
||||||
|
<view class="tui-triangle" :style="{
|
||||||
|
borderWidth: borderWidth,
|
||||||
|
borderColor: `${backgroundColor} transparent transparent transparent`,
|
||||||
|
left: triangleLeft,
|
||||||
|
right: triangleRight,
|
||||||
|
top: triangleTop,
|
||||||
|
bottom: triangleBottom
|
||||||
|
}"
|
||||||
|
v-if="direction == 'bottom'"></view>
|
||||||
|
<view class="tui-triangle" :style="{
|
||||||
|
borderWidth: borderWidth,
|
||||||
|
borderColor: `transparent ${backgroundColor} transparent transparent`,
|
||||||
|
left: triangleLeft,
|
||||||
|
right: triangleRight,
|
||||||
|
top: triangleTop,
|
||||||
|
bottom: triangleBottom
|
||||||
|
}"
|
||||||
|
v-if="direction == 'left'"></view>
|
||||||
|
<view class="tui-triangle" :style="{
|
||||||
|
borderWidth: borderWidth,
|
||||||
|
borderColor: `transparent transparent transparent ${backgroundColor}`,
|
||||||
|
left: triangleLeft,
|
||||||
|
right: triangleRight,
|
||||||
|
top: triangleTop,
|
||||||
|
bottom: triangleBottom
|
||||||
|
}"
|
||||||
|
v-if="direction == 'right'"></view>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view @touchmove.stop.prevent="stop" class="tui-popup-mask" :class="{ 'tui-popup-show': show }" :style="{ backgroundColor: maskBgColor }"
|
||||||
|
v-if="mask" @tap="handleClose"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiBubblePopup',
|
||||||
|
emits: ['close'],
|
||||||
|
props: {
|
||||||
|
//宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '300rpx'
|
||||||
|
},
|
||||||
|
//popup圆角
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '8rpx'
|
||||||
|
},
|
||||||
|
//popup 定位 left right top bottom值
|
||||||
|
left: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
bottom: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
translateX:{
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
translateY:{
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#4c4c4c'
|
||||||
|
},
|
||||||
|
//字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//三角border-width
|
||||||
|
borderWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '12rpx'
|
||||||
|
},
|
||||||
|
//三角形方向 top left right bottom
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'top'
|
||||||
|
},
|
||||||
|
//定位 left right top bottom值
|
||||||
|
triangleLeft: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
triangleRight: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
triangleTop: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
triangleBottom: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
//定位 relative absolute fixed
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
default: 'fixed'
|
||||||
|
},
|
||||||
|
//flex-end
|
||||||
|
flexEnd: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否需要mask
|
||||||
|
mask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
maskBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(0, 0, 0, 0.4)'
|
||||||
|
},
|
||||||
|
//控制显示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClose() {
|
||||||
|
if (!this.show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('close', {});
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-popup-list {
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-flex-end {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-triangle {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
z-index: 997;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 995;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-z_index {
|
||||||
|
z-index: 996;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
405
src/components/thorui/tui-button/tui-button.vue
Normal file
405
src/components/thorui/tui-button/tui-button.vue
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-button__wrap"
|
||||||
|
:class="[(width==='100%' || !width || width===true) && (!btnSize || btnSize===true)?'tui-btn__flex-1':'',getShapeClass(shape, plain),!disabled?'tui-button__hover':'']"
|
||||||
|
:style="{width: getWidth, height: getHeight, margin: margin}">
|
||||||
|
<button class="tui-btn" :class="[
|
||||||
|
plain ? 'tui-' + type + '-outline' : 'tui-btn-' + (type || 'primary'),
|
||||||
|
getDisabledClass(disabled, type, plain),
|
||||||
|
getShapeClass(shape, plain),
|
||||||
|
bold ? 'tui-text-bold' : '',
|
||||||
|
link ? 'tui-btn__link' : ''
|
||||||
|
]" :style="{ width: getWidth, height: getHeight, lineHeight: getHeight, fontSize: getSize + 'rpx',background:getBgColor(type,plain),color:getColor(type,plain),boxShadow:shadow?getShadow(type,plain):'none' }"
|
||||||
|
:loading="loading" :form-type="formType" :open-type="openType" :app-parameter="appParameter"
|
||||||
|
@getuserinfo="bindgetuserinfo" @getphonenumber="bindgetphonenumber" @contact="bindcontact"
|
||||||
|
@error="binderror" @chooseavatar="bindchooseavatar" @launchapp="bindlaunchapp" :disabled="disabled"
|
||||||
|
@tap="handleClick">
|
||||||
|
<slot></slot>
|
||||||
|
</button>
|
||||||
|
<view class="tui-button__border" :class="[getShapeClass(shape, plain),getDisabledClass(disabled, type, plain)]"
|
||||||
|
:style="{borderColor:getBgColor(type)}" v-if="!link && plain"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiButton',
|
||||||
|
emits: ['click', 'getuserinfo', 'contact', 'getphonenumber', 'error','chooseavatar','launchapp'],
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
behaviors: ['wx://form-field-button'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-BAIDU
|
||||||
|
behaviors: ['swan://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-QQ
|
||||||
|
behaviors: ['qq://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
behaviors: ['uni://form-field'],
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
//样式类型 primary, white, danger, warning, green,blue, gray,black,brown,gray-primary,gray-danger,gray-warning,gray-green
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary'
|
||||||
|
},
|
||||||
|
//是否加阴影 移除
|
||||||
|
shadow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 宽度 rpx或 %
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
//高度 rpx
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//medium 184*40 / small 120 40/ mini 58*32
|
||||||
|
btnSize: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//字体大小 rpx
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
bold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//形状 circle(圆角), square(默认方形),rightAngle(平角)
|
||||||
|
shape: {
|
||||||
|
type: String,
|
||||||
|
default: 'square'
|
||||||
|
},
|
||||||
|
plain: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//link样式,去掉边框,结合plain一起使用
|
||||||
|
link: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//禁用后背景是否为灰色 (非空心button生效)
|
||||||
|
disabledGray: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
formType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
openType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
appParameter: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//是否需要阻止重复点击【默认200ms】
|
||||||
|
preventClick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getWidth() {
|
||||||
|
//medium 184*40 / small 120 40/ mini 58*32
|
||||||
|
let width = this.width;
|
||||||
|
if (this.btnSize && this.btnSize !== true) {
|
||||||
|
width = {
|
||||||
|
'medium': '368rpx',
|
||||||
|
'small': '240rpx',
|
||||||
|
'mini': '116rpx'
|
||||||
|
} [this.btnSize] || this.width
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
},
|
||||||
|
getHeight() {
|
||||||
|
//medium 184*40 / small 120 40/ mini 58*32
|
||||||
|
let height = this.height || (uni && uni.$tui && uni.$tui.tuiButton.height) || '96rpx';
|
||||||
|
if (this.btnSize && this.btnSize !== true) {
|
||||||
|
height = {
|
||||||
|
'medium': '80rpx',
|
||||||
|
'small': '80rpx',
|
||||||
|
'mini': '64rpx'
|
||||||
|
} [this.btnSize] || '96rpx'
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
},
|
||||||
|
getSize() {
|
||||||
|
return this.size || (uni && uni.$tui && uni.$tui.tuiButton.size) || 32;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
time: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hexToRGB(hex) {
|
||||||
|
if (hex.length === 4) {
|
||||||
|
let text = hex.substring(1, 4);
|
||||||
|
hex = '#' + text + text;
|
||||||
|
}
|
||||||
|
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
|
return result ? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
} : {};
|
||||||
|
},
|
||||||
|
getColorByType(type, isText, plain) {
|
||||||
|
const global = uni && uni.$tui && uni.$tui.color;
|
||||||
|
let color = ''
|
||||||
|
const colors = {
|
||||||
|
'primary': (global && global.primary) || '#5677fc',
|
||||||
|
'white': '#fff',
|
||||||
|
'danger': (global && global.danger) || '#EB0909',
|
||||||
|
'warning': (global && global.warning) || '#ff7900',
|
||||||
|
'green': (global && global.success) || '#07c160',
|
||||||
|
'blue': (global && global.blue) || '#007aff',
|
||||||
|
'gray': '#bfbfbf',
|
||||||
|
'black': '#333333',
|
||||||
|
'brown': '#ac9157',
|
||||||
|
'gray-primary': '#f2f2f2',
|
||||||
|
'gray-danger': '#f2f2f2',
|
||||||
|
'gray-warning': '#f2f2f2',
|
||||||
|
'gray-green': '#f2f2f2'
|
||||||
|
}
|
||||||
|
if (isText) {
|
||||||
|
if (type && ~type.indexOf('gray-')) {
|
||||||
|
const tp = type.replace('gray-', '')
|
||||||
|
color = colors[tp]
|
||||||
|
} else if (type === 'white') {
|
||||||
|
color = '#333'
|
||||||
|
} else {
|
||||||
|
if (plain) {
|
||||||
|
color = colors[type]
|
||||||
|
} else {
|
||||||
|
color = '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
color = colors[type] || colors.primary
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
},
|
||||||
|
getShadow(type, plain) {
|
||||||
|
const color = this.getColorByType(type)
|
||||||
|
if (plain || !color) return 'none';
|
||||||
|
const rgb = this.hexToRGB(color)
|
||||||
|
return `0 10rpx 14rpx 0 rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.2)`
|
||||||
|
},
|
||||||
|
getBgColor(type, plain) {
|
||||||
|
return plain ? 'transparent' : this.getColorByType(type)
|
||||||
|
},
|
||||||
|
getColor(type, plain) {
|
||||||
|
return this.getColorByType(type, true, plain)
|
||||||
|
},
|
||||||
|
handleClick() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
if (this.preventClick) {
|
||||||
|
if (new Date().getTime() - this.time <= 200) return;
|
||||||
|
this.time = new Date().getTime();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.time = 0;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
this.$emit('click', {
|
||||||
|
index: Number(this.index)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
bindgetuserinfo({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
this.$emit('getuserinfo', detail);
|
||||||
|
},
|
||||||
|
bindcontact({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
this.$emit('contact', detail);
|
||||||
|
},
|
||||||
|
bindgetphonenumber({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
this.$emit('getphonenumber', detail);
|
||||||
|
},
|
||||||
|
binderror({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
this.$emit('error', detail);
|
||||||
|
},
|
||||||
|
bindchooseavatar({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
this.$emit('chooseavatar', detail);
|
||||||
|
},
|
||||||
|
bindlaunchapp({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
this.$emit('launchapp', detail);
|
||||||
|
},
|
||||||
|
getDisabledClass: function(disabled, type, plain) {
|
||||||
|
let className = '';
|
||||||
|
if (disabled && type != 'white' && type.indexOf('-') == -1) {
|
||||||
|
let classVal = this.disabledGray ? 'tui-gray-disabled' : 'tui-dark-disabled';
|
||||||
|
className = plain ? 'tui-dark-disabled-outline' : classVal;
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
},
|
||||||
|
getShapeClass: function(shape, plain) {
|
||||||
|
let className = '';
|
||||||
|
if (shape == 'circle') {
|
||||||
|
className = plain ? 'tui-outline-fillet' : 'tui-fillet';
|
||||||
|
} else if (shape == 'rightAngle') {
|
||||||
|
className = plain ? 'tui-outline-rightAngle' : 'tui-rightAngle';
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
},
|
||||||
|
getHoverClass: function(disabled, type, plain) {
|
||||||
|
let className = '';
|
||||||
|
if (!disabled) {
|
||||||
|
className = plain ? 'tui-outline-hover' : 'tui-' + (type || 'primary') + '-hover';
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-button__wrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-button__hover:active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 6rpx;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* button start*/
|
||||||
|
|
||||||
|
.tui-btn {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
border: 0 !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
overflow: visible;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn::after {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn__flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-button__border {
|
||||||
|
position: absolute;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scale(0.5, 0.5) translateZ(0);
|
||||||
|
box-sizing: border-box;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-text-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dark-disabled {
|
||||||
|
opacity: 0.6 !important;
|
||||||
|
color: #fafbfc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dark-disabled-outline {
|
||||||
|
opacity: 0.5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-gray-disabled {
|
||||||
|
background: #f3f3f3 !important;
|
||||||
|
color: #919191 !important;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*圆角 */
|
||||||
|
|
||||||
|
.tui-fillet {
|
||||||
|
border-radius: 220rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fillet::after {
|
||||||
|
border-radius: 220rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-outline-fillet {
|
||||||
|
border-radius: 220rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-outline-fillet::after {
|
||||||
|
border-radius: 220rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*平角*/
|
||||||
|
.tui-rightAngle {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-rightAngle::after {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-outline-rightAngle {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-outline-rightAngle::after {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn__link::after {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
562
src/components/thorui/tui-calendar/tui-calendar.js
Normal file
562
src/components/thorui/tui-calendar/tui-calendar.js
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
/**
|
||||||
|
* @1900-2100区间内的公历、农历互转
|
||||||
|
* @公历转农历:solar2lunar(1987,11,01);
|
||||||
|
* @农历转公历:lunar2solar(1987,09,10);
|
||||||
|
*/
|
||||||
|
let calendar = {
|
||||||
|
/**
|
||||||
|
* 农历1900-2100的润大小信息表
|
||||||
|
* @Array Of Property
|
||||||
|
* @return Hex
|
||||||
|
*/
|
||||||
|
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, //1900-1909
|
||||||
|
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, //1910-1919
|
||||||
|
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, //1920-1929
|
||||||
|
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, //1930-1939
|
||||||
|
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, //1940-1949
|
||||||
|
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, //1950-1959
|
||||||
|
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, //1960-1969
|
||||||
|
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, //1970-1979
|
||||||
|
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, //1980-1989
|
||||||
|
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, //1990-1999
|
||||||
|
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, //2000-2009
|
||||||
|
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, //2010-2019
|
||||||
|
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, //2020-2029
|
||||||
|
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, //2030-2039
|
||||||
|
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, //2040-2049
|
||||||
|
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, //2050-2059
|
||||||
|
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, //2060-2069
|
||||||
|
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, //2070-2079
|
||||||
|
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, //2080-2089
|
||||||
|
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, //2090-2099
|
||||||
|
0x0d520
|
||||||
|
], //2100
|
||||||
|
/**
|
||||||
|
* 公历每个月份的天数普通表
|
||||||
|
* @Array Of Property
|
||||||
|
* @return Number
|
||||||
|
*/
|
||||||
|
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||||
|
/**
|
||||||
|
* 天干地支之天干速查表
|
||||||
|
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
Gan: ["\u7532", "\u4e59", "\u4e19", "\u4e01", "\u620a", "\u5df1", "\u5e9a", "\u8f9b", "\u58ec", "\u7678"],
|
||||||
|
/**
|
||||||
|
* 天干地支之地支速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
Zhi: ["\u5b50", "\u4e11", "\u5bc5", "\u536f", "\u8fb0", "\u5df3", "\u5348", "\u672a", "\u7533", "\u9149", "\u620c",
|
||||||
|
"\u4ea5"
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* 天干地支之地支速查表<=>生肖
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
Animals: ["\u9f20", "\u725b", "\u864e", "\u5154", "\u9f99", "\u86c7", "\u9a6c", "\u7f8a", "\u7334", "\u9e21",
|
||||||
|
"\u72d7", "\u732a"
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* 24节气速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
solarTerm: ["\u5c0f\u5bd2", "\u5927\u5bd2", "\u7acb\u6625", "\u96e8\u6c34", "\u60ca\u86f0", "\u6625\u5206",
|
||||||
|
"\u6e05\u660e", "\u8c37\u96e8", "\u7acb\u590f", "\u5c0f\u6ee1", "\u8292\u79cd", "\u590f\u81f3", "\u5c0f\u6691",
|
||||||
|
"\u5927\u6691", "\u7acb\u79cb", "\u5904\u6691", "\u767d\u9732", "\u79cb\u5206", "\u5bd2\u9732", "\u971c\u964d",
|
||||||
|
"\u7acb\u51ac", "\u5c0f\u96ea", "\u5927\u96ea", "\u51ac\u81f3"
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* 1900-2100各年的24节气日期速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @return 0x string For splice
|
||||||
|
*/
|
||||||
|
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
|
||||||
|
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
|
||||||
|
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||||
|
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
|
||||||
|
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
|
||||||
|
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
|
||||||
|
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
|
||||||
|
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||||
|
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||||
|
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||||
|
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
|
||||||
|
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||||
|
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||||
|
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||||
|
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||||
|
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||||
|
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
|
||||||
|
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||||
|
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||||
|
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||||
|
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
|
||||||
|
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
|
||||||
|
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||||
|
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
|
||||||
|
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
|
||||||
|
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
|
||||||
|
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||||
|
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
|
||||||
|
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
|
||||||
|
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||||
|
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||||
|
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
|
||||||
|
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* 数字转中文速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
nStr1: ["\u65e5", "\u4e00", "\u4e8c", "\u4e09", "\u56db", "\u4e94", "\u516d", "\u4e03", "\u516b", "\u4e5d", "\u5341"],
|
||||||
|
/**
|
||||||
|
* 日期转农历称呼速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans ['初','十','廿','卅']
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
nStr2: ["\u521d", "\u5341", "\u5eff", "\u5345"],
|
||||||
|
/**
|
||||||
|
* 月份转农历称呼速查表
|
||||||
|
* @Array Of Property
|
||||||
|
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
nStr3: ["\u6b63", "\u4e8c", "\u4e09", "\u56db", "\u4e94", "\u516d", "\u4e03", "\u516b", "\u4e5d", "\u5341", "\u51ac",
|
||||||
|
"\u814a"
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* 返回农历y年一整年的总天数
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number
|
||||||
|
* @eg:let count = calendar.lYearDays(1987) ;//count=387
|
||||||
|
*/
|
||||||
|
lYearDays: function(y) {
|
||||||
|
let i, sum = 348;
|
||||||
|
for (i = 0x8000; i > 0x8; i >>= 1) {
|
||||||
|
sum += (calendar.lunarInfo[y - 1900] & i) ? 1 : 0;
|
||||||
|
}
|
||||||
|
return (sum + calendar.leapDays(y));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number (0-12)
|
||||||
|
* @eg:let leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
|
||||||
|
*/
|
||||||
|
leapMonth: function(y) { //闰字编码 \u95f0
|
||||||
|
return (calendar.lunarInfo[y - 1900] & 0xf);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 返回农历y年闰月的天数 若该年没有闰月则返回0
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number (0、29、30)
|
||||||
|
* @eg:let leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
|
||||||
|
*/
|
||||||
|
leapDays: function(y) {
|
||||||
|
if (calendar.leapMonth(y)) {
|
||||||
|
return ((calendar.lunarInfo[y - 1900] & 0x10000) ? 30 : 29);
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
|
||||||
|
* @param lunar Year
|
||||||
|
* @return Number (-1、29、30)
|
||||||
|
* @eg:let MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
|
||||||
|
*/
|
||||||
|
monthDays: function(y, m) {
|
||||||
|
if (m > 12 || m < 1) {
|
||||||
|
return -1
|
||||||
|
} //月份参数从1至12,参数错误返回-1
|
||||||
|
return ((calendar.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 返回公历(!)y年m月的天数
|
||||||
|
* @param solar Year
|
||||||
|
* @return Number (-1、28、29、30、31)
|
||||||
|
* @eg:let solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
|
||||||
|
*/
|
||||||
|
solarDays: function(y, m) {
|
||||||
|
if (m > 12 || m < 1) {
|
||||||
|
return -1
|
||||||
|
} //若参数错误 返回-1
|
||||||
|
let ms = m - 1;
|
||||||
|
if (ms == 1) { //2月份的闰平规律测算后确认返回28或29
|
||||||
|
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28);
|
||||||
|
} else {
|
||||||
|
return (calendar.solarMonth[ms]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 农历年份转换为干支纪年
|
||||||
|
* @param lYear 农历年的年份数
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
toGanZhiYear: function(lYear) {
|
||||||
|
let ganKey = (lYear - 3) % 10;
|
||||||
|
let zhiKey = (lYear - 3) % 12;
|
||||||
|
if (ganKey == 0) ganKey = 10; //如果余数为0则为最后一个天干
|
||||||
|
if (zhiKey == 0) zhiKey = 12; //如果余数为0则为最后一个地支
|
||||||
|
return calendar.Gan[ganKey - 1] + calendar.Zhi[zhiKey - 1];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 公历月、日判断所属星座
|
||||||
|
* @param cMonth [description]
|
||||||
|
* @param cDay [description]
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
toAstro: function(cMonth, cDay) {
|
||||||
|
let s =
|
||||||
|
"\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf";
|
||||||
|
let arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22];
|
||||||
|
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + "\u5ea7"; //座
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传入offset偏移量返回干支
|
||||||
|
* @param offset 相对甲子的偏移量
|
||||||
|
* @return Cn string
|
||||||
|
*/
|
||||||
|
toGanZhi: function(offset) {
|
||||||
|
return calendar.Gan[offset % 10] + calendar.Zhi[offset % 12];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传入公历(!)y年获得该年第n个节气的公历日期
|
||||||
|
* @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
|
||||||
|
* @return day Number
|
||||||
|
* @eg:let _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
|
||||||
|
*/
|
||||||
|
getTerm: function(y, n) {
|
||||||
|
if (y < 1900 || y > 2100) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (n < 1 || n > 24) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
let _table = calendar.sTermInfo[y - 1900];
|
||||||
|
let _info = [
|
||||||
|
parseInt('0x' + _table.substr(0, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(5, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(10, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(15, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(20, 5)).toString(),
|
||||||
|
parseInt('0x' + _table.substr(25, 5)).toString()
|
||||||
|
];
|
||||||
|
let _calday = [
|
||||||
|
_info[0].substr(0, 1),
|
||||||
|
_info[0].substr(1, 2),
|
||||||
|
_info[0].substr(3, 1),
|
||||||
|
_info[0].substr(4, 2),
|
||||||
|
_info[1].substr(0, 1),
|
||||||
|
_info[1].substr(1, 2),
|
||||||
|
_info[1].substr(3, 1),
|
||||||
|
_info[1].substr(4, 2),
|
||||||
|
_info[2].substr(0, 1),
|
||||||
|
_info[2].substr(1, 2),
|
||||||
|
_info[2].substr(3, 1),
|
||||||
|
_info[2].substr(4, 2),
|
||||||
|
_info[3].substr(0, 1),
|
||||||
|
_info[3].substr(1, 2),
|
||||||
|
_info[3].substr(3, 1),
|
||||||
|
_info[3].substr(4, 2),
|
||||||
|
_info[4].substr(0, 1),
|
||||||
|
_info[4].substr(1, 2),
|
||||||
|
_info[4].substr(3, 1),
|
||||||
|
_info[4].substr(4, 2),
|
||||||
|
_info[5].substr(0, 1),
|
||||||
|
_info[5].substr(1, 2),
|
||||||
|
_info[5].substr(3, 1),
|
||||||
|
_info[5].substr(4, 2),
|
||||||
|
];
|
||||||
|
return parseInt(_calday[n - 1]);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传入农历数字月份返回汉语通俗表示法
|
||||||
|
* @param lunar month
|
||||||
|
* @return Cn string
|
||||||
|
* @eg:let cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
|
||||||
|
*/
|
||||||
|
toChinaMonth: function(m) { // 月 => \u6708
|
||||||
|
if (m > 12 || m < 1) {
|
||||||
|
return -1
|
||||||
|
} //若参数错误 返回-1
|
||||||
|
let s = calendar.nStr3[m - 1];
|
||||||
|
s += "\u6708"; //加上月字
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传入农历日期数字返回汉字表示法
|
||||||
|
* @param lunar day
|
||||||
|
* @return Cn string
|
||||||
|
* @eg:let cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
|
||||||
|
*/
|
||||||
|
toChinaDay: function(d) { //日 => \u65e5
|
||||||
|
let s;
|
||||||
|
switch (d) {
|
||||||
|
case 10:
|
||||||
|
s = '\u521d\u5341';
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
s = '\u4e8c\u5341';
|
||||||
|
break;
|
||||||
|
break;
|
||||||
|
case 30:
|
||||||
|
s = '\u4e09\u5341';
|
||||||
|
break;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
s = calendar.nStr2[Math.floor(d / 10)];
|
||||||
|
s += calendar.nStr1[d % 10];
|
||||||
|
}
|
||||||
|
return (s);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
|
||||||
|
* @param y year
|
||||||
|
* @return Cn string
|
||||||
|
* @eg:let animal = calendar.getAnimal(1987) ;//animal='兔'
|
||||||
|
*/
|
||||||
|
getAnimal: function(y) {
|
||||||
|
return calendar.Animals[(y - 4) % 12]
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
|
||||||
|
* @param y solar year
|
||||||
|
* @param m solar month
|
||||||
|
* @param d solar day
|
||||||
|
* @return JSON object
|
||||||
|
* @eg:console.log(calendar.solar2lunar(1987,11,01));
|
||||||
|
*/
|
||||||
|
solar2lunar: function(y, m, d) { //参数区间1900.1.31~2100.12.31
|
||||||
|
if (y < 1900 || y > 2100) {
|
||||||
|
return -1;
|
||||||
|
} //年份限定、上限
|
||||||
|
if (y == 1900 && m == 1 && d < 31) {
|
||||||
|
return -1;
|
||||||
|
} //下限
|
||||||
|
let objDate;
|
||||||
|
if (!y) { //未传参 获得当天
|
||||||
|
objDate = new Date();
|
||||||
|
} else {
|
||||||
|
objDate = new Date(y, parseInt(m) - 1, d)
|
||||||
|
}
|
||||||
|
let i, leap = 0,
|
||||||
|
temp = 0;
|
||||||
|
//修正ymd参数
|
||||||
|
y = objDate.getFullYear();
|
||||||
|
m = objDate.getMonth() + 1;
|
||||||
|
d = objDate.getDate();
|
||||||
|
let offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) /
|
||||||
|
86400000;
|
||||||
|
for (i = 1900; i < 2101 && offset > 0; i++) {
|
||||||
|
temp = calendar.lYearDays(i);
|
||||||
|
offset -= temp;
|
||||||
|
}
|
||||||
|
if (offset < 0) {
|
||||||
|
offset += temp;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
//是否今天
|
||||||
|
let isTodayObj = new Date(),
|
||||||
|
isToday = false;
|
||||||
|
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
|
||||||
|
isToday = true;
|
||||||
|
}
|
||||||
|
//星期几
|
||||||
|
let nWeek = objDate.getDay(),
|
||||||
|
cWeek = calendar.nStr1[nWeek];
|
||||||
|
if (nWeek == 0) {
|
||||||
|
nWeek = 7;
|
||||||
|
} //数字表示周几顺应天朝周一开始的惯例
|
||||||
|
//农历年
|
||||||
|
let year = i;
|
||||||
|
leap = calendar.leapMonth(i); //闰哪个月
|
||||||
|
let isLeap = false;
|
||||||
|
//效验闰月
|
||||||
|
for (i = 1; i < 13 && offset > 0; i++) {
|
||||||
|
//闰月
|
||||||
|
if (leap > 0 && i == (leap + 1) && isLeap == false) {
|
||||||
|
--i;
|
||||||
|
isLeap = true;
|
||||||
|
temp = calendar.leapDays(year); //计算农历闰月天数
|
||||||
|
} else {
|
||||||
|
temp = calendar.monthDays(year, i); //计算农历普通月天数
|
||||||
|
}
|
||||||
|
//解除闰月
|
||||||
|
if (isLeap == true && i == (leap + 1)) {
|
||||||
|
isLeap = false;
|
||||||
|
}
|
||||||
|
offset -= temp;
|
||||||
|
}
|
||||||
|
if (offset == 0 && leap > 0 && i == leap + 1)
|
||||||
|
if (isLeap) {
|
||||||
|
isLeap = false;
|
||||||
|
} else {
|
||||||
|
isLeap = true;
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
if (offset < 0) {
|
||||||
|
offset += temp;
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
//农历月
|
||||||
|
let month = i;
|
||||||
|
//农历日
|
||||||
|
let day = offset + 1;
|
||||||
|
//天干地支处理
|
||||||
|
let sm = m - 1;
|
||||||
|
let gzY = calendar.toGanZhiYear(year);
|
||||||
|
//月柱 1900年1月小寒以前为 丙子月(60进制12)
|
||||||
|
let firstNode = calendar.getTerm(year, (m * 2 - 1)); //返回当月「节」为几日开始
|
||||||
|
let secondNode = calendar.getTerm(year, (m * 2)); //返回当月「节」为几日开始
|
||||||
|
//依据12节气修正干支月
|
||||||
|
let gzM = calendar.toGanZhi((y - 1900) * 12 + m + 11);
|
||||||
|
if (d >= firstNode) {
|
||||||
|
gzM = calendar.toGanZhi((y - 1900) * 12 + m + 12);
|
||||||
|
}
|
||||||
|
//传入的日期的节气与否
|
||||||
|
let isTerm = false;
|
||||||
|
let Term = null;
|
||||||
|
if (firstNode == d) {
|
||||||
|
isTerm = true;
|
||||||
|
Term = calendar.solarTerm[m * 2 - 2];
|
||||||
|
}
|
||||||
|
if (secondNode == d) {
|
||||||
|
isTerm = true;
|
||||||
|
Term = calendar.solarTerm[m * 2 - 1];
|
||||||
|
}
|
||||||
|
//日柱 当月一日与 1900/1/1 相差天数
|
||||||
|
let dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10;
|
||||||
|
let gzD = calendar.toGanZhi(dayCyclical + d - 1);
|
||||||
|
//该日期所属的星座
|
||||||
|
let astro = calendar.toAstro(m, d);
|
||||||
|
return {
|
||||||
|
'lYear': year,
|
||||||
|
'lMonth': month,
|
||||||
|
'lDay': day,
|
||||||
|
'Animal': calendar.getAnimal(year),
|
||||||
|
'IMonthCn': (isLeap ? "\u95f0" : '') + calendar.toChinaMonth(month),
|
||||||
|
'IDayCn': calendar.toChinaDay(day),
|
||||||
|
'cYear': y,
|
||||||
|
'cMonth': m,
|
||||||
|
'cDay': d,
|
||||||
|
'gzYear': gzY,
|
||||||
|
'gzMonth': gzM,
|
||||||
|
'gzDay': gzD,
|
||||||
|
'isToday': isToday,
|
||||||
|
'isLeap': isLeap,
|
||||||
|
'nWeek': nWeek,
|
||||||
|
'ncWeek': "\u661f\u671f" + cWeek,
|
||||||
|
'isTerm': isTerm,
|
||||||
|
'Term': Term,
|
||||||
|
'astro': astro
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
|
||||||
|
* @param y lunar year
|
||||||
|
* @param m lunar month
|
||||||
|
* @param d lunar day
|
||||||
|
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
|
||||||
|
* @return JSON object
|
||||||
|
* @eg:console.log(calendar.lunar2solar(1987,9,10));
|
||||||
|
*/
|
||||||
|
lunar2solar: function(y, m, d, isLeapMonth) { //参数区间1900.1.31~2100.12.1
|
||||||
|
isLeapMonth = !!isLeapMonth;
|
||||||
|
let leapOffset = 0;
|
||||||
|
let leapMonth = calendar.leapMonth(y);
|
||||||
|
let leapDay = calendar.leapDays(y);
|
||||||
|
if (isLeapMonth && (leapMonth != m)) {
|
||||||
|
return -1;
|
||||||
|
} //传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
|
||||||
|
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) {
|
||||||
|
return -1;
|
||||||
|
} //超出了最大极限值
|
||||||
|
let day = calendar.monthDays(y, m);
|
||||||
|
let _day = day;
|
||||||
|
//bugFix 2016-9-25
|
||||||
|
//if month is leap, _day use leapDays method
|
||||||
|
if (isLeapMonth) {
|
||||||
|
_day = calendar.leapDays(y, m);
|
||||||
|
}
|
||||||
|
if (y < 1900 || y > 2100 || d > _day) {
|
||||||
|
return -1;
|
||||||
|
} //参数合法性效验
|
||||||
|
//计算农历的时间差
|
||||||
|
let offset = 0;
|
||||||
|
for (let i = 1900; i < y; i++) {
|
||||||
|
offset += calendar.lYearDays(i);
|
||||||
|
}
|
||||||
|
let leap = 0,
|
||||||
|
isAdd = false;
|
||||||
|
for (let i = 1; i < m; i++) {
|
||||||
|
leap = calendar.leapMonth(y);
|
||||||
|
if (!isAdd) { //处理闰月
|
||||||
|
if (leap <= i && leap > 0) {
|
||||||
|
offset += calendar.leapDays(y);
|
||||||
|
isAdd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += calendar.monthDays(y, i);
|
||||||
|
}
|
||||||
|
//转换闰月农历 需补充该年闰月的前一个月的时差
|
||||||
|
if (isLeapMonth) {
|
||||||
|
offset += day;
|
||||||
|
}
|
||||||
|
//1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
|
||||||
|
let stmap = Date.UTC(1900, 1, 30, 0, 0, 0);
|
||||||
|
let calObj = new Date((offset + d - 31) * 86400000 + stmap);
|
||||||
|
let cY = calObj.getUTCFullYear();
|
||||||
|
let cM = calObj.getUTCMonth() + 1;
|
||||||
|
let cD = calObj.getUTCDate();
|
||||||
|
return calendar.solar2lunar(cY, cM, cD);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
solar2lunar: calendar.solar2lunar,
|
||||||
|
lunar2solar: calendar.lunar2solar
|
||||||
|
};
|
||||||
964
src/components/thorui/tui-calendar/tui-calendar.vue
Normal file
964
src/components/thorui/tui-calendar/tui-calendar.vue
Normal file
@ -0,0 +1,964 @@
|
|||||||
|
<template>
|
||||||
|
<view @touchmove.stop.prevent="stop" v-if="isFixed">
|
||||||
|
<view class="tui-bottom-popup" :class="{'tui-popup-show': isShow}">
|
||||||
|
<view class="tui-calendar-header" :class="{ 'tui-calendar-radius': radius }">
|
||||||
|
<view>{{title}}</view>
|
||||||
|
<view class="tui-iconfont tui-font-close" hover-class="tui-opacity" :hover-stay-time="150" @tap="hide">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tui-date-box">
|
||||||
|
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: yearArrowColor }"
|
||||||
|
hover-class="tui-opacity" :hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(0)"></view>
|
||||||
|
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: monthArrowColor }"
|
||||||
|
hover-class="tui-opacity" :hover-stay-time="150" @tap="changeMonth(0)"></view>
|
||||||
|
<view class="tui-date_time">{{ showTitle }}</view>
|
||||||
|
<view class="tui-iconfont tui-font-arrowright" :style="{ color: monthArrowColor }"
|
||||||
|
hover-class="tui-opacity" :hover-stay-time="150" @tap="changeMonth(1)"></view>
|
||||||
|
<view class="tui-iconfont tui-font-arrowright" :style="{ color: yearArrowColor }"
|
||||||
|
hover-class="tui-opacity" :hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(1)"></view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-header">
|
||||||
|
<view class="tui-date">日</view>
|
||||||
|
<view class="tui-date">一</view>
|
||||||
|
<view class="tui-date">二</view>
|
||||||
|
<view class="tui-date">三</view>
|
||||||
|
<view class="tui-date">四</view>
|
||||||
|
<view class="tui-date">五</view>
|
||||||
|
<view class="tui-date">六</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-content" :class="{ 'tui-flex-start': isFixed && fixedHeight }"
|
||||||
|
:style="{ height: isFixed && fixedHeight ? dateHeight * 6 + 'px' : 'auto' }">
|
||||||
|
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||||
|
<view class="tui-date"></view>
|
||||||
|
</block>
|
||||||
|
<view class="tui-date" :class="{
|
||||||
|
'tui-date-pd_0': isFixed && fixedHeight,
|
||||||
|
'tui-opacity': openDisAbled(year, month, index + 1),
|
||||||
|
'tui-start-date': (type == 2 && startDate == `${year}-${month}-${index + 1}`) || type == 1,
|
||||||
|
'tui-end-date': (type == 2 && endDate == `${year}-${month}-${index + 1}`) || type == 1
|
||||||
|
}" :style="{ backgroundColor: isFixed ? getColor(index, 1) : 'transparent', height: isFixed && fixedHeight ? dateHeight + 'px' : 'auto' }"
|
||||||
|
v-for="(item, index) in daysArr" :key="index" @tap="dateClick(index)">
|
||||||
|
<view class="tui-date-text"
|
||||||
|
:style="{ color: isFixed ? getColor(index, 2) : getStatusData(3, index), backgroundColor: getStatusData(2, index) }">
|
||||||
|
<view v-if="isFixed || !getStatusData(4, index)">{{ index + 1 }}</view>
|
||||||
|
<view v-if="!getStatusData(4, index)" class="tui-custom-desc"
|
||||||
|
:class="{ 'tui-lunar-unshow': !lunar && isFixed }">
|
||||||
|
{{ getDescText(index, startDate, endDate) }}
|
||||||
|
</view>
|
||||||
|
<text class="tui-iconfont tui-font-check" v-if="getStatusData(4, index)"></text>
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||||
|
v-if="!lunar && type == 2 && startDate == `${year}-${month}-${index + 1}` && startDate != endDate">
|
||||||
|
{{ startText }}
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||||
|
v-if="!lunar && type == 2 && endDate == `${year}-${month}-${index + 1}`">{{ endText }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-bg-month">{{ month }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tui-calendar-op">
|
||||||
|
<view class="tui-calendar-result">
|
||||||
|
<text>{{ type == 1 ? activeDate : startDate }}</text>
|
||||||
|
<text v-if="endDate">至{{ endDate }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="tui-calendar-btn_box">
|
||||||
|
<view :style="{background:getBtnBgColor}" @tap.stop="btnFix(false)" class="tui-btn__confirm"
|
||||||
|
:class="{'tui-btn__disabled':disabled,'tui-btn__confirm-hover':!disabled}">确定</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tui-popup-mask" :class="[isShow ? 'tui-mask-show' : '']" @tap="hide"></view>
|
||||||
|
</view>
|
||||||
|
<view v-else>
|
||||||
|
<view class="tui-date-box">
|
||||||
|
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: yearArrowColor }" hover-class="tui-opacity"
|
||||||
|
:hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(0)"></view>
|
||||||
|
<view class="tui-iconfont tui-font-arrowleft" :style="{ color: monthArrowColor }" hover-class="tui-opacity"
|
||||||
|
:hover-stay-time="150" @tap="changeMonth(0)"></view>
|
||||||
|
<view class="tui-date_time">{{ showTitle }}</view>
|
||||||
|
<view class="tui-iconfont tui-font-arrowright" :style="{ color: monthArrowColor }" hover-class="tui-opacity"
|
||||||
|
:hover-stay-time="150" @tap="changeMonth(1)"></view>
|
||||||
|
<view class="tui-iconfont tui-font-arrowright" :style="{ color: yearArrowColor }" hover-class="tui-opacity"
|
||||||
|
:hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(1)"></view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-header">
|
||||||
|
<view class="tui-date">日</view>
|
||||||
|
<view class="tui-date">一</view>
|
||||||
|
<view class="tui-date">二</view>
|
||||||
|
<view class="tui-date">三</view>
|
||||||
|
<view class="tui-date">四</view>
|
||||||
|
<view class="tui-date">五</view>
|
||||||
|
<view class="tui-date">六</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-content" :style="{ height: isFixed && fixedHeight ? dateHeight * 6 + 'px' : 'auto' }">
|
||||||
|
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||||
|
<view class="tui-date"></view>
|
||||||
|
</block>
|
||||||
|
<view class="tui-date" :class="{
|
||||||
|
'tui-date-pd_0': isFixed && fixedHeight,
|
||||||
|
'tui-opacity': openDisAbled(year, month, index + 1),
|
||||||
|
'tui-start-date': (type == 2 && startDate == `${year}-${month}-${index + 1}`) || type == 1,
|
||||||
|
'tui-end-date': (type == 2 && endDate == `${year}-${month}-${index + 1}`) || type == 1
|
||||||
|
}" :style="{ backgroundColor: isFixed ? getColor(index, 1) : 'transparent', height: isFixed && fixedHeight ? dateHeight + 'px' : 'auto' }"
|
||||||
|
v-for="(item, index) in daysArr" :key="index" @tap="dateClick(index)">
|
||||||
|
<view class="tui-date-text"
|
||||||
|
:style="{ color: isFixed ? getColor(index, 2) : getStatusData(3, index), backgroundColor: getStatusData(2, index) }">
|
||||||
|
<view v-if="isFixed || !getStatusData(4, index)">{{ index + 1 }}</view>
|
||||||
|
<view v-if="!getStatusData(4, index)" class="tui-custom-desc"
|
||||||
|
:class="{ 'tui-lunar-unshow': !lunar && isFixed }">
|
||||||
|
{{ getDescText(index, startDate, endDate) }}
|
||||||
|
</view>
|
||||||
|
<text class="tui-iconfont tui-font-check" v-if="getStatusData(4, index)"></text>
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||||
|
v-if="!lunar && type == 2 && startDate == `${year}-${month}-${index + 1}` && startDate != endDate">
|
||||||
|
{{ startText }}
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-desc" :style="{ color: activeColor }"
|
||||||
|
v-if="!lunar && type == 2 && endDate == `${year}-${month}-${index + 1}`">{{ endText }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-bg-month">{{ month }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import calendar from './tui-calendar.js';
|
||||||
|
export default {
|
||||||
|
name: 'tuiCalendar',
|
||||||
|
emits: ['hide', 'change'],
|
||||||
|
props: {
|
||||||
|
//1-切换月份和年份 2-切换月份
|
||||||
|
arrowType: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//1-单个日期选择 2-开始日期+结束日期选择
|
||||||
|
type: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//可切换最大年份
|
||||||
|
maxYear: {
|
||||||
|
type: Number,
|
||||||
|
default: 2030
|
||||||
|
},
|
||||||
|
//可切换最小年份
|
||||||
|
minYear: {
|
||||||
|
type: Number,
|
||||||
|
default: 1920
|
||||||
|
},
|
||||||
|
//最小可选日期(不在范围内日期禁用不可选)
|
||||||
|
minDate: {
|
||||||
|
type: String,
|
||||||
|
default: '1920-01-01'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 最大可选日期
|
||||||
|
* 默认最大值为今天,之后的日期不可选
|
||||||
|
* 2030-12-31
|
||||||
|
* */
|
||||||
|
maxDate: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '日期选择'
|
||||||
|
},
|
||||||
|
//显示圆角
|
||||||
|
radius: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//状态 数据顺序与当月天数一致,index=>day
|
||||||
|
/**
|
||||||
|
* [{
|
||||||
|
* text:"", 描述:2字以内
|
||||||
|
* value:"",状态值
|
||||||
|
* bgColor:"",背景色
|
||||||
|
* color:"" 文字颜色,
|
||||||
|
* check:false //是否显示对勾
|
||||||
|
*
|
||||||
|
}]
|
||||||
|
*
|
||||||
|
* **/
|
||||||
|
status: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//月份切换箭头颜色
|
||||||
|
monthArrowColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#999'
|
||||||
|
},
|
||||||
|
//年份切换箭头颜色
|
||||||
|
yearArrowColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#bcbcbc'
|
||||||
|
},
|
||||||
|
//默认日期字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//选中|起始结束日期背景色
|
||||||
|
activeBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//选中|起始结束日期字体颜色
|
||||||
|
activeColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//范围内日期背景色
|
||||||
|
rangeBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(86,119,252,0.1)'
|
||||||
|
},
|
||||||
|
//范围内日期字体颜色
|
||||||
|
rangeColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//type=2时生效,起始日期自定义文案
|
||||||
|
startText: {
|
||||||
|
type: String,
|
||||||
|
default: '开始'
|
||||||
|
},
|
||||||
|
//type=2时生效,结束日期自定义文案
|
||||||
|
endText: {
|
||||||
|
type: String,
|
||||||
|
default: '结束'
|
||||||
|
},
|
||||||
|
//按钮背景色 V2.3.0+
|
||||||
|
btnBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//固定在底部
|
||||||
|
isFixed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//固定日历容器高度,isFixed=true时生效
|
||||||
|
fixedHeight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//当前选中日期带选中效果
|
||||||
|
isActiveCurrent: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//切换年月是否触发事件 type=1时生效
|
||||||
|
isChange: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否显示农历
|
||||||
|
lunar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//初始化起始选中日期 格式: 2020-06-06 或 2020/06/06 【type=1 or 2】
|
||||||
|
initStartDate: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//初始化结束日期 格式: 2020-06-06 或 2020/06/06【type=2】
|
||||||
|
initEndDate: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isShow: false,
|
||||||
|
weekday: 1, // 星期几,值为1-7
|
||||||
|
weekdayArr: [],
|
||||||
|
days: 0, //当前月有多少天
|
||||||
|
daysArr: [],
|
||||||
|
showTitle: '',
|
||||||
|
year: 2020,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
startYear: 0,
|
||||||
|
startMonth: 0,
|
||||||
|
startDay: 0,
|
||||||
|
endYear: 0,
|
||||||
|
endMonth: 0,
|
||||||
|
endDay: 0,
|
||||||
|
today: '',
|
||||||
|
activeDate: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
isStart: true,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
dateHeight: 20
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dataChange() {
|
||||||
|
return `${this.type}-${this.minDate}-${this.maxDate}-${this.initStartDate}-${this.initEndDate}`;
|
||||||
|
},
|
||||||
|
disabled() {
|
||||||
|
return this.type == 2 && (!this.startDate || !this.endDate)
|
||||||
|
},
|
||||||
|
getActiveBgColor(){
|
||||||
|
return this.activeBgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
},
|
||||||
|
getBtnBgColor() {
|
||||||
|
return this.btnBgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
},
|
||||||
|
getRangeColor(){
|
||||||
|
return this.rangeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
dataChange(val) {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
fixedHeight(val) {
|
||||||
|
if (val) {
|
||||||
|
this.initDateHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getColor(index, type) {
|
||||||
|
let color = type == 1 ? '' : this.color;
|
||||||
|
let day = index + 1;
|
||||||
|
let date = `${this.year}-${this.month}-${day}`;
|
||||||
|
let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
|
||||||
|
let start = this.startDate.replace(/\-/g, '/');
|
||||||
|
let end = this.endDate.replace(/\-/g, '/');
|
||||||
|
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
|
||||||
|
color = type == 1 ? this.getActiveBgColor : this.activeColor;
|
||||||
|
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
|
||||||
|
color = type == 1 ? this.rangeBgColor : this.getRangeColor;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
},
|
||||||
|
//获取状态数据
|
||||||
|
getStatusData(type, index) {
|
||||||
|
//1-描述text,2-bgColor背景色,3-color文字颜色 4-check 是否显示对勾
|
||||||
|
let val = ['', 'transparent', '#333', ''][type - 1];
|
||||||
|
if (!this.isFixed && this.status && this.status.length > 0) {
|
||||||
|
let item = this.status[index];
|
||||||
|
if (item) {
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
val = item.text;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
val = item.bgColor;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
val = item.color;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
val = item.check;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
getDescText(index, startDate, endDate) {
|
||||||
|
let text = this.lunar ? this.getLunar(this.year, this.month, index + 1) : '';
|
||||||
|
if (this.isFixed && this.type == 2) {
|
||||||
|
//此判断不能与上面条件一起判断
|
||||||
|
if (this.lunar) {
|
||||||
|
let date = `${this.year}-${this.month}-${index + 1}`;
|
||||||
|
if (startDate == date && startDate != endDate) {
|
||||||
|
text = this.startText;
|
||||||
|
} else if (endDate == date) {
|
||||||
|
text = this.endText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let status = this.getStatusData(1, index);
|
||||||
|
if (status) text = status;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
getLunar(year, month, day) {
|
||||||
|
let obj = calendar.solar2lunar(year, month, day);
|
||||||
|
return obj.IDayCn;
|
||||||
|
},
|
||||||
|
initDateHeight() {
|
||||||
|
if (this.fixedHeight && this.isFixed) {
|
||||||
|
this.dateHeight = uni.getSystemInfoSync().windowWidth / 7;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.initDateHeight();
|
||||||
|
let now = new Date();
|
||||||
|
this.year = now.getFullYear();
|
||||||
|
this.month = now.getMonth() + 1;
|
||||||
|
this.day = now.getDate();
|
||||||
|
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
||||||
|
this.activeDate = this.today;
|
||||||
|
this.min = this.initDate(this.minDate);
|
||||||
|
this.max = this.initDate(this.maxDate || this.today);
|
||||||
|
if (this.openDisAbled(this.year, this.month, this.day)) {
|
||||||
|
this.year = this.max.year;
|
||||||
|
this.month = this.max.month;
|
||||||
|
this.day = this.max.day;
|
||||||
|
this.activeDate = `${this.max.year}-${this.max.month}-${this.max.day}`;
|
||||||
|
this.max = this.initDate(this.maxDate || this.minDate);
|
||||||
|
}
|
||||||
|
this.startDate = '';
|
||||||
|
this.startYear = 0;
|
||||||
|
this.startMonth = 0;
|
||||||
|
this.startDay = 0;
|
||||||
|
if (this.initStartDate) {
|
||||||
|
let start = new Date(this.initStartDate.replace(/\-/g, '/'));
|
||||||
|
if (this.type == 1) {
|
||||||
|
this.year = start.getFullYear();
|
||||||
|
this.month = start.getMonth() + 1;
|
||||||
|
this.day = start.getDate();
|
||||||
|
this.activeDate = `${start.getFullYear()}-${start.getMonth() + 1}-${start.getDate()}`;
|
||||||
|
} else {
|
||||||
|
this.startDate = `${start.getFullYear()}-${start.getMonth() + 1}-${start.getDate()}`;
|
||||||
|
this.startYear = start.getFullYear();
|
||||||
|
this.startMonth = start.getMonth() + 1;
|
||||||
|
this.startDay = start.getDate();
|
||||||
|
this.activeDate = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
this.endYear = 0;
|
||||||
|
this.endMonth = 0;
|
||||||
|
this.endDay = 0;
|
||||||
|
this.endDate = '';
|
||||||
|
if (this.initEndDate && this.type == 2) {
|
||||||
|
let end = new Date(this.initEndDate.replace(/\-/g, '/'));
|
||||||
|
this.endDate = `${end.getFullYear()}-${end.getMonth() + 1}-${end.getDate()}`;
|
||||||
|
this.endYear = end.getFullYear();
|
||||||
|
this.endMonth = end.getMonth() + 1;
|
||||||
|
this.endDay = end.getDate();
|
||||||
|
this.activeDate = '';
|
||||||
|
this.year = end.getFullYear();
|
||||||
|
this.month = end.getMonth() + 1;
|
||||||
|
this.day = end.getDate();
|
||||||
|
}
|
||||||
|
this.isStart = true;
|
||||||
|
this.changeData();
|
||||||
|
},
|
||||||
|
//日期处理
|
||||||
|
initDate(date) {
|
||||||
|
let fdate = date.split('-');
|
||||||
|
return {
|
||||||
|
year: Number(fdate[0] || 1920),
|
||||||
|
month: Number(fdate[1] || 1),
|
||||||
|
day: Number(fdate[2] || 1)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
openDisAbled: function(year, month, day) {
|
||||||
|
let bool = true;
|
||||||
|
let date = `${year}/${month}/${day}`;
|
||||||
|
// let today = this.today.replace(/\-/g, '/');
|
||||||
|
let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
|
||||||
|
let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
|
||||||
|
let timestamp = new Date(date).getTime();
|
||||||
|
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
|
||||||
|
bool = false;
|
||||||
|
}
|
||||||
|
return bool;
|
||||||
|
},
|
||||||
|
generateArray: function(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
formatNum: function(num) {
|
||||||
|
return num < 10 ? '0' + num : num + '';
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
//一个月有多少天
|
||||||
|
getMonthDay(year, month) {
|
||||||
|
let days = new Date(year, month, 0).getDate();
|
||||||
|
return days;
|
||||||
|
},
|
||||||
|
getWeekday(year, month) {
|
||||||
|
let date = new Date(`${year}/${month}/01 00:00:00`);
|
||||||
|
return date.getDay();
|
||||||
|
},
|
||||||
|
checkRange(year) {
|
||||||
|
let overstep = false;
|
||||||
|
if (year < this.minYear || year > this.maxYear) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '日期超出范围啦~',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
overstep = true;
|
||||||
|
}
|
||||||
|
return overstep;
|
||||||
|
},
|
||||||
|
changeMonth(isAdd) {
|
||||||
|
if (isAdd) {
|
||||||
|
let month = this.month + 1;
|
||||||
|
let year = month > 12 ? this.year + 1 : this.year;
|
||||||
|
if (!this.checkRange(year)) {
|
||||||
|
this.month = month > 12 ? 1 : month;
|
||||||
|
this.year = year;
|
||||||
|
this.changeData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let month = this.month - 1;
|
||||||
|
let year = month < 1 ? this.year - 1 : this.year;
|
||||||
|
if (!this.checkRange(year)) {
|
||||||
|
this.month = month < 1 ? 12 : month;
|
||||||
|
this.year = year;
|
||||||
|
this.changeData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeYear(isAdd) {
|
||||||
|
let year = isAdd ? this.year + 1 : this.year - 1;
|
||||||
|
if (!this.checkRange(year)) {
|
||||||
|
this.year = year;
|
||||||
|
this.changeData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeData() {
|
||||||
|
this.days = this.getMonthDay(this.year, this.month);
|
||||||
|
this.daysArr = this.generateArray(1, this.days);
|
||||||
|
this.weekday = this.getWeekday(this.year, this.month);
|
||||||
|
this.weekdayArr = this.generateArray(1, this.weekday);
|
||||||
|
this.showTitle = `${this.year}年${this.month}月`;
|
||||||
|
if (this.isChange && this.type == 1) {
|
||||||
|
this.btnFix(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dateClick: function(day) {
|
||||||
|
day += 1;
|
||||||
|
if (!this.openDisAbled(this.year, this.month, day)) {
|
||||||
|
this.day = day;
|
||||||
|
let date = `${this.year}-${this.month}-${day}`;
|
||||||
|
if (this.type == 1) {
|
||||||
|
this.activeDate = date;
|
||||||
|
} else {
|
||||||
|
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(
|
||||||
|
/\-/g, '/')).getTime();
|
||||||
|
if (this.isStart || compare) {
|
||||||
|
this.startDate = date;
|
||||||
|
this.startYear = this.year;
|
||||||
|
this.startMonth = this.month;
|
||||||
|
this.startDay = this.day;
|
||||||
|
this.endYear = 0;
|
||||||
|
this.endMonth = 0;
|
||||||
|
this.endDay = 0;
|
||||||
|
this.endDate = '';
|
||||||
|
this.activeDate = '';
|
||||||
|
this.isStart = false;
|
||||||
|
} else {
|
||||||
|
this.endDate = date;
|
||||||
|
this.endYear = this.year;
|
||||||
|
this.endMonth = this.month;
|
||||||
|
this.endDay = this.day;
|
||||||
|
this.isStart = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.isFixed) {
|
||||||
|
this.btnFix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
this.isShow = true;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.isShow = false;
|
||||||
|
this.$emit('hide', {})
|
||||||
|
},
|
||||||
|
getWeekText(date) {
|
||||||
|
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
|
||||||
|
let week = date.getDay();
|
||||||
|
return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
|
||||||
|
},
|
||||||
|
btnFix(show) {
|
||||||
|
if (!show) {
|
||||||
|
if(this.disabled) return;
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
if (this.type == 1) {
|
||||||
|
let arr = this.activeDate.split('-');
|
||||||
|
let year = this.isChange ? this.year : Number(arr[0]);
|
||||||
|
let month = this.isChange ? this.month : Number(arr[1]);
|
||||||
|
let day = this.isChange ? this.day : Number(arr[2]);
|
||||||
|
//当前月有多少天
|
||||||
|
let days = this.getMonthDay(year, month);
|
||||||
|
let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
|
||||||
|
let weekText = this.getWeekText(result);
|
||||||
|
let isToday = false;
|
||||||
|
if (`${year}-${month}-${day}` == this.today) {
|
||||||
|
//今天
|
||||||
|
isToday = true;
|
||||||
|
}
|
||||||
|
let lunar = calendar.solar2lunar(year, month, day);
|
||||||
|
this.$emit('change', {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
days: days,
|
||||||
|
result: result,
|
||||||
|
week: weekText,
|
||||||
|
isToday: isToday,
|
||||||
|
switch: show, //是否是切换年月操作
|
||||||
|
lunar: lunar
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!this.startDate || !this.endDate) return;
|
||||||
|
let startMonth = this.formatNum(this.startMonth);
|
||||||
|
let startDay = this.formatNum(this.startDay);
|
||||||
|
let startDate = `${this.startYear}-${startMonth}-${startDay}`;
|
||||||
|
let startWeek = this.getWeekText(startDate);
|
||||||
|
let startLunar = calendar.solar2lunar(this.startYear, startMonth, startDay);
|
||||||
|
|
||||||
|
let endMonth = this.formatNum(this.endMonth);
|
||||||
|
let endDay = this.formatNum(this.endDay);
|
||||||
|
let endDate = `${this.endYear}-${endMonth}-${endDay}`;
|
||||||
|
let endWeek = this.getWeekText(endDate);
|
||||||
|
let endLunar = calendar.solar2lunar(this.endYear, endMonth, endDay);
|
||||||
|
this.$emit('change', {
|
||||||
|
startYear: this.startYear,
|
||||||
|
startMonth: this.startMonth,
|
||||||
|
startDay: this.startDay,
|
||||||
|
startDate: startDate,
|
||||||
|
startWeek: startWeek,
|
||||||
|
startLunar: startLunar,
|
||||||
|
endYear: this.endYear,
|
||||||
|
endMonth: this.endMonth,
|
||||||
|
endDay: this.endDay,
|
||||||
|
endDate: endDate,
|
||||||
|
endWeek: endWeek,
|
||||||
|
endLunar: endLunar
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'tuiDateFont';
|
||||||
|
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAVgAA0AAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAFRAAAABoAAAAci0/w50dERUYAAAUkAAAAHgAAAB4AKQANT1MvMgAAAaAAAABDAAAAVjxuSNNjbWFwAAAB+AAAAEoAAAFS5iPQt2dhc3AAAAUcAAAACAAAAAj//wADZ2x5ZgAAAlQAAAFHAAABvPf29TBoZWFkAAABMAAAADAAAAA2GMsN3WhoZWEAAAFgAAAAHQAAACQHjAOFaG10eAAAAeQAAAATAAAAFgzQAPJsb2NhAAACRAAAABAAAAAQAOoBSG1heHAAAAGAAAAAHgAAACABEwA3bmFtZQAAA5wAAAFJAAACiCnmEVVwb3N0AAAE6AAAADQAAABLUwjqHHjaY2BkYGAAYp5Gj5/x/DZfGbhZGEDg1tUn7+F00P/LzOuY9YFcDgYmkCgAa0gNlHjaY2BkYGBu+N/AEMPCAALM6xgYGVABCwBT4AMaAAAAeNpjYGRgYGBn0GZgYgABEMkFhAwM/8F8BgANaAFLAAB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ49ZG7438AQw9zA0AAUZgTJAQDrcAy8AHjaY2GAABYIDgLCBQx1AAcEAc8AeNpjYGBgZoBgGQZGBhDwAfIYwXwWBgMgzQGETAwMzxifcTx7+P8/kMUAYUkxS/6VVIXqAgNGNgY4lxGoB6QPBTAyDHsAADDkDYkAAAAAAAAAAAAAADQAagC2AN542m2QsU7DMBCG/Tt1bNPUiUnkSgiVtqKpxJAgVLVbeAa6MaK+B4JXgJWBjY21UtW5gpkdMTFX7dzApaJLhXU6n8+n//ttxtn458N79XJWZ8eMxS00C4wy9A1EP8PQncAlIQzS4WgsVtPpSmwzV3OFRqLetH5TSQMK939X61ptPZ2p2EAttNMLBRMrtschQblDeS34aY50cIkCzg/B2Y5C+VpyQxhFkRgu515O8jvU5mmPM2O0wJ5Z27vhX+yMsV437WvCdTM+GI40MgwKfuGammC0uURqeqFMfe9cxaJclkt5GMaB1hIR1VobOgpEiKq+sLZcIrJWhO3/Jw7qWlYj1Jf21FaCtmd5bevrlk28O/7A4spXTl4KTh9MTlqQ8PESBRstReic+sRj0Dni9fIqmNS/pXNWCvWOeYBmx5S9Bsn9Ah+5WtAAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAO2MGiTIxMjMyMLIys7GmJeRmlmWZQ2pQ5OSORLaU0Mz2/FACDfwlbAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABgABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9K2rT97DaABNlwiuAAA=) format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-iconfont {
|
||||||
|
font-family: 'tuiDateFont' !important;
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-close:before {
|
||||||
|
content: '\e608';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-check:before {
|
||||||
|
content: '\e6e1';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-arrowright:before {
|
||||||
|
content: '\e600';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-arrowleft:before {
|
||||||
|
content: '\e601';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-box {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20rpx 0 30rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-calendar-radius {
|
||||||
|
border-top-left-radius: 20rpx;
|
||||||
|
border-top-right-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date_time {
|
||||||
|
padding: 0 16rpx;
|
||||||
|
color: #333;
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-arrowleft {
|
||||||
|
margin-right: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-arrowright {
|
||||||
|
margin-left: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #555;
|
||||||
|
box-shadow: 0 15rpx 20rpx -15rpx #efefef;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-flex-start {
|
||||||
|
align-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bg-month {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 260rpx;
|
||||||
|
line-height: 260rpx;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: #f5f5f7;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date {
|
||||||
|
width: 14.2857%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-pd_0 {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-start-date {
|
||||||
|
border-top-left-radius: 8rpx;
|
||||||
|
border-bottom-left-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-end-date {
|
||||||
|
border-top-right-radius: 8rpx;
|
||||||
|
border-bottom-right-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-text {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn-calendar {
|
||||||
|
padding: 16rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-opacity {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bottom-popup {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translate3d(0, 100%, 0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
min-height: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-show {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 9996;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-mask-show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-calendar-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 30rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #555;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-close {
|
||||||
|
position: absolute;
|
||||||
|
right: 30rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn-calendar {
|
||||||
|
padding: 16rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-check {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 54rpx;
|
||||||
|
line-height: 54rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-custom-desc {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transform-origin: center center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-lunar-unshow {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 8rpx;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-desc {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transform-origin: center center;
|
||||||
|
text-align: center;
|
||||||
|
bottom: 8rpx;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-calendar-op {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 0 42rpx 30rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-calendar-result {
|
||||||
|
height: 48rpx;
|
||||||
|
transform: scale(0.9);
|
||||||
|
transform-origin: center 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-calendar-btn_box {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn__confirm {
|
||||||
|
width: 100%;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 72rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn__confirm-hover:active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 72rpx;
|
||||||
|
background: rgba(0, 0, 0, .2);
|
||||||
|
border-radius: 72rpx;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn__disabled {
|
||||||
|
opacity: .5;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
234
src/components/thorui/tui-card/tui-card.vue
Normal file
234
src/components/thorui/tui-card/tui-card.vue
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-card-class tui-card" :class="[full?'tui-card-full':'',border?'tui-card-border':'']"
|
||||||
|
@tap="handleClick" @longpress="longTap">
|
||||||
|
<slot>
|
||||||
|
<view class="tui-card-header" :class="{'tui-header-line':header.line}"
|
||||||
|
:style="{background:header.bgcolor || '#fff'}">
|
||||||
|
<view class="tui-header-left">
|
||||||
|
<image :src="imageUrl || image.url" class="tui-header-thumb"
|
||||||
|
:class="{'tui-thumb-circle':image.circle}" mode="widthFix" v-if="imageUrl || image.url"
|
||||||
|
:style="{height:(image.height || 60)+'rpx',width:(image.width || 60)+'rpx'}"></image>
|
||||||
|
<text class="tui-header-title"
|
||||||
|
:style="{fontSize:(title.size || 30)+'rpx',color:(title.color || '#7A7A7A')}"
|
||||||
|
v-if="titleText || title.text">{{titleText || title.text}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="tui-header-right" :style="{fontSize:(tag.size || 24)+'rpx',color:(tag.color || '#b2b2b2')}"
|
||||||
|
v-if="tagText || tag.text">
|
||||||
|
{{tagText || tag.text}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</slot>
|
||||||
|
<view class="tui-card-body">
|
||||||
|
<slot name="body"></slot>
|
||||||
|
</view>
|
||||||
|
<view class="tui-card-footer">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiCard",
|
||||||
|
emits: ['click', 'longclick'],
|
||||||
|
props: {
|
||||||
|
//是否铺满
|
||||||
|
full: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//v2.9.6+ 图片地址,优先级高于image 中 url
|
||||||
|
imageUrl: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return {
|
||||||
|
url: "", //图片地址
|
||||||
|
height: 60, //图片高度
|
||||||
|
width: 60, //图片宽度
|
||||||
|
circle: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//v2.9.6+ 标题文本,优先级高于 title中 text
|
||||||
|
titleText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//标题
|
||||||
|
title: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return {
|
||||||
|
text: "", //标题文字
|
||||||
|
size: 30, //字体大小
|
||||||
|
color: "#7A7A7A" //字体颜色
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//标签文本,优先级高于 tag中 text
|
||||||
|
tagText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//标签,时间等
|
||||||
|
tag: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return {
|
||||||
|
text: "", //标签文字
|
||||||
|
size: 24, //字体大小
|
||||||
|
color: "#b2b2b2" //字体颜色
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
type: Object,
|
||||||
|
default: function() {
|
||||||
|
return {
|
||||||
|
bgcolor: "#fff", //背景颜色
|
||||||
|
line: false //是否去掉底部线条
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//是否设置外边框
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick() {
|
||||||
|
this.$emit('click', {
|
||||||
|
index: this.index
|
||||||
|
});
|
||||||
|
},
|
||||||
|
longTap() {
|
||||||
|
this.$emit('longclick', {
|
||||||
|
index: this.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-card {
|
||||||
|
margin: 0 30rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
box-shadow: 0 0 10rpx #eee;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-full {
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-full::after {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-border {
|
||||||
|
position: relative;
|
||||||
|
box-shadow: none !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-border::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
height: 200%;
|
||||||
|
width: 200%;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
-webkit-transform: scale(0.5);
|
||||||
|
transform: scale(0.5);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-header {
|
||||||
|
width: 100%;
|
||||||
|
padding: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
border-top-left-radius: 10rpx;
|
||||||
|
border-top-right-radius: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-header::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1rpx solid #eaeef1;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-header-line::after {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-header-thumb {
|
||||||
|
height: 60rpx;
|
||||||
|
width: 60rpx;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-thumb-circle {
|
||||||
|
border-radius: 50% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-header-title {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #7a7a7a;
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 460rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-header-right {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #b2b2b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-body {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #262b3a;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-card-footer {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #596d96;
|
||||||
|
border-bottom-left-radius: 10rpx;
|
||||||
|
border-bottom-right-radius: 10rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,615 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-cascade-selection">
|
||||||
|
<scroll-view :scroll-x="true" scroll-with-animation :scroll-into-view="scrollViewId"
|
||||||
|
:style="{ backgroundColor: headerBgColor }" class="tui-bottom-line"
|
||||||
|
:class="{ 'tui-btm-none': !headerLine }">
|
||||||
|
<view class="tui-selection-header" :style="{ height: tabsHeight, backgroundColor: backgroundColor }">
|
||||||
|
<view class="tui-header-item" :class="{ 'tui-font-bold': idx === currentTab && bold }"
|
||||||
|
:style="{ color: idx === currentTab ? getActiveColor : color, fontSize: size + 'rpx' }"
|
||||||
|
:id="`id_${idx}`" @tap.stop="swichNav" :data-current="idx" v-for="(item, idx) in selectedArr"
|
||||||
|
:key="idx">
|
||||||
|
{{ item[textField] }}
|
||||||
|
<view class="tui-active-line" :style="{ backgroundColor: getLineColor }"
|
||||||
|
v-if="idx === currentTab && showLine"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<swiper class="tui-selection-list" :current="defTab" duration="300" @change="switchTab"
|
||||||
|
:style="{ height: height, backgroundColor: backgroundColor }">
|
||||||
|
<swiper-item v-for="(item, index) in selectedArr" :key="index">
|
||||||
|
<scroll-view scroll-y :scroll-into-view="item.scrollViewId" class="tui-selection-item"
|
||||||
|
:style="{ height: height }">
|
||||||
|
<view class="tui-first-item" :style="{ height: firstItemTop }"></view>
|
||||||
|
<view class="tui-selection-cell" :style="{ padding: padding, backgroundColor: backgroundColor }"
|
||||||
|
:id="`id_${subIndex}`" v-for="(subItem, subIndex) in item.list" :key="subIndex"
|
||||||
|
@tap.stop="change(index, subIndex, subItem)">
|
||||||
|
<icon type="success_no_circle" v-if="item.index === subIndex" :color="getCkMarkColor"
|
||||||
|
:size="checkMarkSize" class="tui-icon-success"></icon>
|
||||||
|
<image :src="subItem[srcField]" v-if="subItem[srcField]" class="tui-cell-img"
|
||||||
|
:style="{ width: imgWidth, height: imgHeight, borderRadius: radius }"></image>
|
||||||
|
<view class="tui-cell-title"
|
||||||
|
:class="{ 'tui-font-bold': item.index === subIndex && textBold, 'tui-flex-shrink': nowrap }"
|
||||||
|
:style="{ color: item.index === subIndex ? textActiveColor : textColor, fontSize: textSize + 'rpx' }">
|
||||||
|
{{ subItem[textField] }}
|
||||||
|
</view>
|
||||||
|
<view class="tui-cell-sub_title" :style="{ color: subTextColor, fontSize: subTextSize + 'rpx' }"
|
||||||
|
v-if="subItem[subTextField]">{{ subItem[subTextField] }}</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiCascadeSelection',
|
||||||
|
emits: ['change', 'complete'],
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* 如果下一级是请求返回,则为第一级数据,否则所有数据
|
||||||
|
* 数据格式
|
||||||
|
* */
|
||||||
|
itemList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
srcField: {
|
||||||
|
type: String,
|
||||||
|
default: 'src'
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
subTextField: {
|
||||||
|
type: String,
|
||||||
|
default: 'subText'
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value'
|
||||||
|
},
|
||||||
|
childrenField: {
|
||||||
|
type: String,
|
||||||
|
default: 'children'
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
初始化默认选中数据
|
||||||
|
[{
|
||||||
|
text: "",//选中text
|
||||||
|
subText: '',//选中subText
|
||||||
|
value: '',//选中value
|
||||||
|
src: '', //选中src,没有则传空或不传
|
||||||
|
index: 0, //选中数据在当前layer索引
|
||||||
|
list: [{src: "", text: "", subText: "", value: 101}] //当前layer下所有数据集合
|
||||||
|
}];
|
||||||
|
|
||||||
|
*/
|
||||||
|
defaultItemList: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
//是否显示header底部细线
|
||||||
|
headerLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//header背景颜色
|
||||||
|
headerBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#FFFFFF'
|
||||||
|
},
|
||||||
|
//顶部标签栏高度
|
||||||
|
tabsHeight: {
|
||||||
|
type: String,
|
||||||
|
default: '88rpx'
|
||||||
|
},
|
||||||
|
//默认显示文字
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择'
|
||||||
|
},
|
||||||
|
//tabs 文字大小
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 28
|
||||||
|
},
|
||||||
|
//tabs 文字颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#555'
|
||||||
|
},
|
||||||
|
//选中颜色
|
||||||
|
activeColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//选中后文字加粗
|
||||||
|
bold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//选中后是否显示底部线条
|
||||||
|
showLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//线条颜色
|
||||||
|
lineColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//icon 大小
|
||||||
|
checkMarkSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 15
|
||||||
|
},
|
||||||
|
//icon 颜色
|
||||||
|
checkMarkColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//item 图片宽度
|
||||||
|
imgWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '40rpx'
|
||||||
|
},
|
||||||
|
//item 图片高度
|
||||||
|
imgHeight: {
|
||||||
|
type: String,
|
||||||
|
default: '40rpx'
|
||||||
|
},
|
||||||
|
//图片圆角
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '50%'
|
||||||
|
},
|
||||||
|
//item text颜色
|
||||||
|
textColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
textActiveColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//选中后字体是否加粗
|
||||||
|
textBold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//item text字体大小
|
||||||
|
textSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 28
|
||||||
|
},
|
||||||
|
//text 是否不换行
|
||||||
|
nowrap: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//item subText颜色
|
||||||
|
subTextColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#999'
|
||||||
|
},
|
||||||
|
//item subText字体大小
|
||||||
|
subTextSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
// item padding
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '20rpx 30rpx'
|
||||||
|
},
|
||||||
|
//占位高度,第一条数据距离顶部距离
|
||||||
|
firstItemTop: {
|
||||||
|
type: String,
|
||||||
|
default: '20rpx'
|
||||||
|
},
|
||||||
|
//swiper 高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '300px'
|
||||||
|
},
|
||||||
|
//item swiper 内容部分背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#FFFFFF'
|
||||||
|
},
|
||||||
|
//子集数据是否请求返回(默认false,一次性返回所有数据)
|
||||||
|
request: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//子级数据(当有改变时,默认当前选中项新增子级数据,request=true时生效)
|
||||||
|
receiveData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//改变值则重置数据
|
||||||
|
reset: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getActiveColor() {
|
||||||
|
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
},
|
||||||
|
getLineColor() {
|
||||||
|
return this.lineColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
},
|
||||||
|
getCkMarkColor() {
|
||||||
|
return this.checkMarkColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemList(val) {
|
||||||
|
this.initData(val, -1);
|
||||||
|
},
|
||||||
|
receiveData(val) {
|
||||||
|
this.subLevelData(val, this.currentTab);
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.initData(this.itemList, -1);
|
||||||
|
},
|
||||||
|
defaultItemList(val) {
|
||||||
|
this.setDefaultData(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.setDefaultData(this.defaultItemList)
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTab: 0,
|
||||||
|
defTab: 0,
|
||||||
|
//tab栏scrollview滚动的位置
|
||||||
|
scrollViewId: 'id__1',
|
||||||
|
selectedArr: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setDefaultData(val) {
|
||||||
|
let defaultItemList = JSON.parse(JSON.stringify(val || []));
|
||||||
|
if (defaultItemList.length > 0) {
|
||||||
|
if ((typeof defaultItemList[0] === 'string' || typeof defaultItemList[0] === 'number') && !this
|
||||||
|
.request) {
|
||||||
|
let subi = -1
|
||||||
|
let selectedArr = []
|
||||||
|
for (let j = 0, len = defaultItemList.length; j < len; j++) {
|
||||||
|
let item = defaultItemList[j]
|
||||||
|
let list = []
|
||||||
|
let obj = {}
|
||||||
|
if (j === 0) {
|
||||||
|
list = this.getItemList(-1)
|
||||||
|
} else {
|
||||||
|
list = this.getItemList(j - 1, subi, selectedArr)
|
||||||
|
}
|
||||||
|
subi = this.getDefaultIndex(list, item)
|
||||||
|
if (subi !== -1) {
|
||||||
|
obj = list[subi]
|
||||||
|
selectedArr.push({
|
||||||
|
[this.textField]: obj[this.textField] || this.text,
|
||||||
|
[this.valueField]: obj[this.valueField] || '',
|
||||||
|
[this.srcField]: obj[this.srcField] || '',
|
||||||
|
[this.subTextField]: obj[this.subTextField] || '',
|
||||||
|
index: subi,
|
||||||
|
scrollViewId: `id_${subi}`,
|
||||||
|
list: list
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subi === -1) break;
|
||||||
|
}
|
||||||
|
this.selectedArr = selectedArr;
|
||||||
|
this.defTab = this.currentTab;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.currentTab = selectedArr.length - 1;
|
||||||
|
this.defTab = this.currentTab;
|
||||||
|
this.checkCor();
|
||||||
|
}, 20)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
defaultItemList.map(item => {
|
||||||
|
item.scrollViewId = `id_${item.index}`;
|
||||||
|
});
|
||||||
|
this.selectedArr = defaultItemList;
|
||||||
|
this.defTab = this.currentTab;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.currentTab = defaultItemList.length - 1;
|
||||||
|
this.defTab = this.currentTab;
|
||||||
|
this.checkCor();
|
||||||
|
}, 20)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.initData(this.itemList, -1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDefaultIndex(arr, val) {
|
||||||
|
if (!arr || arr.length === 0 || val === undefined) return -1;
|
||||||
|
let index = -1;
|
||||||
|
let key = this.defaultKey || 'text'
|
||||||
|
for (let i = 0, len = arr.length; i < len; i++) {
|
||||||
|
if (arr[i][key] == val) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
},
|
||||||
|
initData(data, layer) {
|
||||||
|
if (!data || data.length === 0) return;
|
||||||
|
if (this.request) {
|
||||||
|
//第一级数据
|
||||||
|
this.subLevelData(data, layer);
|
||||||
|
} else {
|
||||||
|
let selectedValue = this.selectedValue || {};
|
||||||
|
if (selectedValue.type) {
|
||||||
|
this.setDefaultData(selectedValue);
|
||||||
|
} else {
|
||||||
|
this.subLevelData(this.getItemList(layer, -1), layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeChildren(data) {
|
||||||
|
let list = data.map(item => {
|
||||||
|
delete item[this.childrenField];
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
getItemList(layer, index, selectedArr) {
|
||||||
|
let list = [];
|
||||||
|
let arr = JSON.parse(JSON.stringify(this.itemList));
|
||||||
|
selectedArr = selectedArr || this.selectedArr
|
||||||
|
if (layer == -1) {
|
||||||
|
list = this.removeChildren(arr);
|
||||||
|
} else {
|
||||||
|
let value = selectedArr[0].index;
|
||||||
|
value = value === undefined || value == -1 ? index : value;
|
||||||
|
if (arr[value] && arr[value][this.childrenField]) {
|
||||||
|
list = arr[value][this.childrenField];
|
||||||
|
}
|
||||||
|
if (layer > 0) {
|
||||||
|
for (let i = 1; i < layer + 1; i++) {
|
||||||
|
let val = layer === i ? index : selectedArr[i].index;
|
||||||
|
list = val === -1 ? [] : (list[val][this.childrenField] || []);
|
||||||
|
if (list.length === 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list = this.removeChildren(list);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
//滚动切换
|
||||||
|
switchTab: function(e) {
|
||||||
|
this.currentTab = e.detail.current;
|
||||||
|
this.checkCor();
|
||||||
|
},
|
||||||
|
//点击标题切换当
|
||||||
|
swichNav: function(e) {
|
||||||
|
let cur = e.currentTarget.dataset.current;
|
||||||
|
if (this.currentTab != cur) {
|
||||||
|
this.defTab = this.currentTab;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.currentTab = cur;
|
||||||
|
this.defTab = this.currentTab;
|
||||||
|
}, 20)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkCor: function() {
|
||||||
|
let item = this.selectedArr[this.currentTab];
|
||||||
|
item.scrollViewId = 'id__1';
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
let val = item.index < 2 ? 0 : Number(item.index - 2);
|
||||||
|
item.scrollViewId = `id_${val}`;
|
||||||
|
}, 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.currentTab > 1) {
|
||||||
|
this.scrollViewId = `id_${this.currentTab - 1}`;
|
||||||
|
} else {
|
||||||
|
this.scrollViewId = `id_0`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
change(index, subIndex, subItem) {
|
||||||
|
let item = this.selectedArr[index];
|
||||||
|
if (item.index == subIndex) return;
|
||||||
|
item.index = subIndex;
|
||||||
|
item[this.textField] = subItem[this.textField]
|
||||||
|
item[this.valueField] = subItem[this.valueField];
|
||||||
|
item[this.subTextField] = subItem[this.subTextField] || '';
|
||||||
|
item[this.srcField] = subItem[this.srcField] || '';
|
||||||
|
this.$emit('change', {
|
||||||
|
layer: index,
|
||||||
|
subIndex: subIndex, //layer=> Array index
|
||||||
|
...subItem
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.request) {
|
||||||
|
let data = this.getItemList(index, subIndex);
|
||||||
|
this.subLevelData(data, index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//新增子级数据时处理
|
||||||
|
subLevelData(data, layer) {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
if (layer == -1) return;
|
||||||
|
//完成选择
|
||||||
|
let arr = this.selectedArr;
|
||||||
|
if (layer < arr.length - 1) {
|
||||||
|
let newArr = arr.slice(0, layer + 1);
|
||||||
|
this.selectedArr = newArr;
|
||||||
|
}
|
||||||
|
let result = JSON.parse(JSON.stringify(this.selectedArr));
|
||||||
|
let lastItem = result[result.length - 1] || {};
|
||||||
|
let text = '';
|
||||||
|
result.map(item => {
|
||||||
|
text += item[this.textField];
|
||||||
|
delete item['list'];
|
||||||
|
//delete item['index'];
|
||||||
|
delete item['scrollViewId'];
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.$emit('complete', {
|
||||||
|
result: result,
|
||||||
|
[this.valueField]: lastItem[this.valueField],
|
||||||
|
[this.textField]: text,
|
||||||
|
[this.subTextField]: lastItem[this.subTextField],
|
||||||
|
[this.srcField]: lastItem[this.srcField]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//重置数据( >layer层级)
|
||||||
|
let item = [{
|
||||||
|
[this.textField]: this.text,
|
||||||
|
[this.subTextField]: '',
|
||||||
|
[this.valueField]: '',
|
||||||
|
[this.srcField]: '',
|
||||||
|
index: -1,
|
||||||
|
scrollViewId: 'id__1',
|
||||||
|
list: data
|
||||||
|
}];
|
||||||
|
if (layer == -1) {
|
||||||
|
this.selectedArr = item;
|
||||||
|
} else {
|
||||||
|
let retainArr = this.selectedArr.slice(0, layer + 1) || [];
|
||||||
|
this.selectedArr = retainArr.concat(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = this.selectedArr.length - 1;
|
||||||
|
if (current >= this.currentTab) {
|
||||||
|
this.defTab = this.currentTab
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.defTab = current;
|
||||||
|
this.currentTab = current;
|
||||||
|
this.scrollViewId = `id_${this.currentTab > 1?this.currentTab - 1:0}`;
|
||||||
|
}, 50)
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-cascade-selection {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-selection-header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bottom-line {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bottom-line::after {
|
||||||
|
width: 100%;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1rpx solid #eaeef1;
|
||||||
|
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btm-none::after {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-header-item {
|
||||||
|
max-width: 240rpx;
|
||||||
|
padding: 15rpx 30rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-active-line {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-selection-cell {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon-success {
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cell-img {
|
||||||
|
margin-right: 12rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cell-title {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-flex-shrink {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cell-sub_title {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-first-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
703
src/components/thorui/tui-charts-area/tui-charts-area.vue
Normal file
703
src/components/thorui/tui-charts-area/tui-charts-area.vue
Normal file
@ -0,0 +1,703 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__line-wrap" :style="{width:width+'rpx'}">
|
||||||
|
<view class="tui-line__legend" v-if="legend.show">
|
||||||
|
<view class="tui-line__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts__line-box" v-if="xAxis.length>0 && dataset.length>0" :style="{width:width+'rpx'}">
|
||||||
|
<scroll-view :scroll-x="scrollable" class="tui-line__scroll-view" :style="{height:scrollViewH+'rpx'}">
|
||||||
|
<view :style="{height:(xAxisVal.height || 48) +'rpx'}"></view>
|
||||||
|
<view class="tui-charts__line" :style="{height:height+'rpx'}">
|
||||||
|
<view class="tui-line__item" :class="{'tui-line__flex-1':!scrollable}"
|
||||||
|
:style="{width:(xAxisLine.itemGap || 120)+'rpx'}" v-for="(item,index) in xAxis" :key="index">
|
||||||
|
<view class="tui-line__xAxis-text"
|
||||||
|
:style="{color:xAxisLabel.color || '#333',fontSize:(xAxisLabel.size || 24)+'rpx' }">
|
||||||
|
{{item}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-yAxis__split-line"
|
||||||
|
:style="{borderRightStyle:yAxisSplitLine.type || 'dashed',borderRightColor:yAxisSplitLine.color || '#e3e3e3'}"
|
||||||
|
v-if="tooltipShow && index==activeIdx">
|
||||||
|
</view>
|
||||||
|
<view class="tui-xAxis__tickmarks"
|
||||||
|
:style="{height:xAxisTick.height || '12rpx',backgroundColor:xAxisTick.color || '#e3e3e3'}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-for="(dot,i) in dots" :key="dot.id">
|
||||||
|
<view class="tui-charts__line-dot"
|
||||||
|
:class="{'tui-charts__dot-enlarge':tooltipShow && j==activeIdx}" @tap.stop="dotClick(i,j)"
|
||||||
|
v-for="(d,j) in dot.source" :key="d.id"
|
||||||
|
:style="{bottom: d.y+'rpx', left: d.x+'rpx',width:(brokenDot.width || 12)+'rpx',height:(brokenDot.width || 12)+'rpx',borderColor:dot.color || brokenDot.color,background:brokenDot.color || dot.color}">
|
||||||
|
<text class="tui-line__val"
|
||||||
|
:style="{fontSize:(xAxisVal.size || 24)+'rpx',color:xAxisVal.color}"
|
||||||
|
v-if="xAxisVal.show">
|
||||||
|
{{getYAxisVal(i,j)}}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-for="(line,idx) in lines" :key="line.id">
|
||||||
|
<view class="tui-charts__broken-line" v-for="(l,k) in line.source" :key="l.id"
|
||||||
|
:style="{height:brokenLineHeight+'px',background:line.color,bottom: l.y+'rpx', left: l.x+'rpx',width: l.width+'rpx','-webkit-transform': `rotate(${l.angle}deg)`,transform: `rotate(${l.angle}deg)`}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-area__chart-box" v-for="(area,i) in dots" :key="area.key"
|
||||||
|
:style="{left:xGap/2+'rpx',right:xGap/2+'rpx',minWidth:xWidth+'rpx'}">
|
||||||
|
<view class="tui-area__item"
|
||||||
|
:style="{'--tui-area-start': model.start, '--tui-area-end': model.end,background:model.background}"
|
||||||
|
v-for="(model,j) in area.area" :key="j"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="tui-line__border-left"
|
||||||
|
:style="{height:height+(xAxisVal.height || 48)+'rpx',backgroundColor:yAxisLine.color || '#e3e3e3'}">
|
||||||
|
</view>
|
||||||
|
<view class="tui-xAxis__line" :class="{'tui-line__first':index===0}"
|
||||||
|
:style="{bottom:index*(yAxisLine.itemGap || 60)+(xAxisLabel.height || 60)+'rpx',borderTopStyle:index===0?'solid':splitLine.type,borderTopColor:index===0?xAxisLine.color:splitLine.color}"
|
||||||
|
v-for="(item,index) in yAxisData" :key="index">
|
||||||
|
<text class="tui-yAxis__val"
|
||||||
|
:style="{color:item.color || yAxisLabel.color,fontSize:(yAxisLabel.size||24)+'rpx'}"
|
||||||
|
v-if="yAxisLabel.show">{{item.value}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tui-line__tooltip" v-if="tooltip" :class="{'tui-line__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title">{{xAxis[activeIdx] || ''}}</view>
|
||||||
|
<view class="tui-line__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||||
|
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||||
|
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-area",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//图表宽度
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 620
|
||||||
|
},
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
size: 24,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//默认选中x轴索引
|
||||||
|
currentIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
//分割线
|
||||||
|
splitLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//分割线颜色,不显示则将颜色设置为transparent
|
||||||
|
color: "#e3e3e3",
|
||||||
|
type: "dashed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴刻度线
|
||||||
|
xAxisTick: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
height: '12rpx',
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴线条
|
||||||
|
xAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//x轴item间距 rpx
|
||||||
|
itemGap: 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: "#333",
|
||||||
|
size: 24,
|
||||||
|
height: 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisVal: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24,
|
||||||
|
//如果show为true且val显示的时候,height需要设置一定的值保证val能显示完整 rpx
|
||||||
|
height: 48
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//点击坐标点所显示的分割线
|
||||||
|
yAxisSplitLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//分割线颜色,不显示则将颜色设置为transparent
|
||||||
|
color: "transparent",
|
||||||
|
type: "dashed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//折线坐标点宽度 rpx
|
||||||
|
brokenDot: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
width: 12,
|
||||||
|
//点的背景色
|
||||||
|
color: '#F8F8F8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//折线高度/粗细 px
|
||||||
|
brokenLineHeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//y轴数据,如果不传则默认使用min,max值计算
|
||||||
|
// {
|
||||||
|
// value: 0,
|
||||||
|
// color: "#333"
|
||||||
|
// }
|
||||||
|
yAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//y轴最小值
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//y轴最大值
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//y轴分段递增数值
|
||||||
|
splitNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
yAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//y轴item间距 rpx
|
||||||
|
itemGap: 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//是否可滚动
|
||||||
|
scrollable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
height: 0,
|
||||||
|
scrollViewH: 0,
|
||||||
|
sections: 0,
|
||||||
|
yAxisData: [],
|
||||||
|
activeIndex: -1,
|
||||||
|
activeIdx: -1,
|
||||||
|
tooltips: [],
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null,
|
||||||
|
dots: [],
|
||||||
|
lines: [],
|
||||||
|
/*========options============*/
|
||||||
|
/*
|
||||||
|
name: '',
|
||||||
|
color: '',
|
||||||
|
source: []
|
||||||
|
colorFormatter:Function
|
||||||
|
*/
|
||||||
|
dataset: [],
|
||||||
|
xAxisValFormatter: null,
|
||||||
|
maxValue: 1,
|
||||||
|
xGap: 120,
|
||||||
|
xWidth: 600
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init()
|
||||||
|
this.activeIdx = this.currentIndex;
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
getPx(rpx) {
|
||||||
|
let px = parseInt(uni.upx2px(Number(rpx)))
|
||||||
|
return px % 2 === 0 ? px : px + 1
|
||||||
|
},
|
||||||
|
getYAxisVal(idx, index) {
|
||||||
|
let showVal = this.dataset[idx].source[index];
|
||||||
|
if (this.xAxisVal.formatter && typeof this.xAxisVal.formatter === 'function') {
|
||||||
|
showVal = this.xAxisVal.formatter(showVal)
|
||||||
|
} else if (this.xAxisValFormatter && typeof this.xAxisValFormatter === 'function') {
|
||||||
|
showVal = this.xAxisValFormatter(showVal)
|
||||||
|
}
|
||||||
|
return showVal
|
||||||
|
},
|
||||||
|
generateArray(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
getValue(val) {
|
||||||
|
return val < 0 ? 0 : val;
|
||||||
|
},
|
||||||
|
getCoordinatePoint() {
|
||||||
|
const xAxis = [...this.xAxis];
|
||||||
|
const xSections = xAxis.length;
|
||||||
|
const ySections = this.yAxisData.length - 1;
|
||||||
|
const itemGap = this.scrollable ? (this.xAxisLine.itemGap || 120) : (this.width / xSections);
|
||||||
|
let dots = [];
|
||||||
|
let radius = (this.brokenDot.width || 12) / 2;
|
||||||
|
this.xWidth = itemGap * (xSections - 1)
|
||||||
|
this.xGap = itemGap
|
||||||
|
let yHeight = (this.yAxisLine.itemGap || 60) * ySections
|
||||||
|
this.dataset.map((item, index) => {
|
||||||
|
let source = item.source || []
|
||||||
|
let dotArr = []
|
||||||
|
let areaArr = []
|
||||||
|
source.map((val, idx) => {
|
||||||
|
let y = this.getValue((val - this.min) / (this.maxValue - this.min) * yHeight -
|
||||||
|
radius)
|
||||||
|
dotArr.push({
|
||||||
|
id: 'd' + idx,
|
||||||
|
x: this.getValue((0.5 + idx) * itemGap - radius),
|
||||||
|
y: y
|
||||||
|
})
|
||||||
|
if (idx !== xSections - 1) {
|
||||||
|
let y1 = this.getValue((source[idx + 1] - this.min) / (this.maxValue - this
|
||||||
|
.min) * yHeight -
|
||||||
|
radius)
|
||||||
|
areaArr.push({
|
||||||
|
start: (y + 6) / yHeight,
|
||||||
|
end: (y1 + 6) / yHeight,
|
||||||
|
background: item.color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dots.push({
|
||||||
|
id: 'dd' + index,
|
||||||
|
color: item.color,
|
||||||
|
source: dotArr,
|
||||||
|
key: 'ar' + index,
|
||||||
|
area: areaArr
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.dots = dots;
|
||||||
|
this.drawLines(dots);
|
||||||
|
},
|
||||||
|
drawLines(dots) {
|
||||||
|
let lines = []
|
||||||
|
// dots是点的集合 : Array<{ x: number; y: number; }>
|
||||||
|
let radius = (this.brokenDot.width || 12) / 2;
|
||||||
|
dots.map((item, idx) => {
|
||||||
|
let dotArr = item.source;
|
||||||
|
let lineArr = [];
|
||||||
|
dotArr.map((dot, index) => {
|
||||||
|
if (!dotArr[index + 1]) return;
|
||||||
|
const AB = {
|
||||||
|
x: dotArr[index + 1].x - dot.x,
|
||||||
|
y: dotArr[index + 1].y - dot.y,
|
||||||
|
y1: dot.y - dotArr[index + 1].y
|
||||||
|
}
|
||||||
|
const v = Math.sqrt(Math.pow(AB.x, 2) + Math.pow(AB.y, 2));
|
||||||
|
const angle = Math.atan2(AB.y1, AB.x) * (180 / Math.PI);
|
||||||
|
lineArr.push({
|
||||||
|
id: 'l' + index,
|
||||||
|
x: dot.x + radius,
|
||||||
|
y: dot.y + radius - 1,
|
||||||
|
width: v,
|
||||||
|
angle: AB.y1 > 0 ? Math.sqrt(Math.pow(angle, 2)) : -Math.sqrt(Math.pow(
|
||||||
|
angle,
|
||||||
|
2))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
lines.push({
|
||||||
|
id: 'll' + idx,
|
||||||
|
color: item.color,
|
||||||
|
source: lineArr
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.lines = lines
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.maxValue = this.max;
|
||||||
|
let itemGap = this.yAxisLine.itemGap || 60;
|
||||||
|
let sections = this.yAxis.length - 1;
|
||||||
|
let yAxis = this.yAxis;
|
||||||
|
if (sections <= 0) {
|
||||||
|
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||||
|
let sectionsArr = this.generateArray(0, sections)
|
||||||
|
yAxis = sectionsArr.map(item => {
|
||||||
|
return {
|
||||||
|
value: item * this.splitNumber + this.min
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.maxValue = yAxis[yAxis.length - 1].value
|
||||||
|
}
|
||||||
|
this.yAxisData = yAxis;
|
||||||
|
this.sections = sections + 1;
|
||||||
|
this.height = itemGap * sections;
|
||||||
|
const valH = this.xAxisVal.height || 48;
|
||||||
|
this.scrollViewH = this.height + (this.xAxisLabel.height || 60) + valH;
|
||||||
|
this.getCoordinatePoint();
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
dataset:面积图表数据
|
||||||
|
xAxisValFormatter :格式化面积拐点value值
|
||||||
|
*/
|
||||||
|
draw(dataset, xAxisValFormatter) {
|
||||||
|
this.xAxisValFormatter = xAxisValFormatter || null;
|
||||||
|
this.dataset = dataset || [];
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
tooltipHandle(index) {
|
||||||
|
let data = [...this.dataset]
|
||||||
|
let tooltips = []
|
||||||
|
data.forEach(item => {
|
||||||
|
let color = item.color;
|
||||||
|
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||||
|
color = item.colorFormatter(item.source[index])
|
||||||
|
}
|
||||||
|
tooltips.push({
|
||||||
|
color: color,
|
||||||
|
name: item.name,
|
||||||
|
val: item.source[index]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.tooltips = tooltips;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
dotClick(index, idx) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.activeIdx = idx;
|
||||||
|
this.tooltipHandle(idx);
|
||||||
|
this.$emit('click', {
|
||||||
|
datasetIndex: index,
|
||||||
|
sourceIndex: idx,
|
||||||
|
...this.dataset[index]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__line-wrap {
|
||||||
|
position: relative;
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
|
/* margin: 0 auto; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__legend-circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__line-box {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
overflow: visible;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__scroll-view {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__line {
|
||||||
|
min-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
/* overflow: hidden; */
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__item {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 10;
|
||||||
|
transition: all 0.3s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__tickmarks {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 1px;
|
||||||
|
transform: translateY(100%);
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__split-line {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
border-right-width: 1px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__area-line {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 1px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__xAxis-text {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
flex: 1;
|
||||||
|
transform: translate(-50%, 100%);
|
||||||
|
padding-top: 8rpx;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__border-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__line {
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
border-top-width: 1px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__first {
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__val {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
padding-right: 12rpx;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__line-dot {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s;
|
||||||
|
z-index: 12;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__val {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
padding-bottom: 12rpx;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__dot-enlarge {
|
||||||
|
transform: scale(1.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__broken-line {
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
z-index: 10;
|
||||||
|
border-color: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* transform: translateZ(0); */
|
||||||
|
/* -webkit-backface-visibility:hidden; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val-ml {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-area__chart-box {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-area__item {
|
||||||
|
flex: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 120%:100%,-20%:0% ,改为px时恢复*/
|
||||||
|
clip-path: polygon(0% calc(100% * (1 - var(--tui-area-start))),
|
||||||
|
100% calc(100% * (1 - var(--tui-area-end))),
|
||||||
|
120% 100%,
|
||||||
|
-20% 100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
580
src/components/thorui/tui-charts-bar/tui-charts-bar.vue
Normal file
580
src/components/thorui/tui-charts-bar/tui-charts-bar.vue
Normal file
@ -0,0 +1,580 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__bar-wrap" :style="{width:width+'rpx'}">
|
||||||
|
<view class="tui-bar__legend" v-if="legend.show">
|
||||||
|
<view class="tui-bar__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts__bar-box" :style="{borderLeftColor:yAxisLine.color,borderBottomColor:xAxisLineColor}"
|
||||||
|
v-if="yAxis.length>0 && dataset.length>0">
|
||||||
|
<view class="tui-charts__bar-item"
|
||||||
|
:class="{'tui-bar__item-stack':isStack,'tui-column__item-active':activeIndex===index && clickEffect==1,'tui-column__bar-opacity':clickEffect==2,'tui-column__bar-active':clickEffect==2 && activeIndex==index}"
|
||||||
|
v-for="(item,index) in yAxis" :key="index" :style="{padding:yAxisLine.itemPadding ||'30rpx 0'}">
|
||||||
|
<view class="tui-charts__bar" :class="{'tui-charts__bar-round':columnCap==='round'}"
|
||||||
|
v-for="(bar,idx) in dataset" :key="idx"
|
||||||
|
:style="{height:columnBarHeight+'rpx',borderRightColor:getBarColor(bar.source[index],bar.color,bar.colorFormatter),background:getBarColor(bar.source[index],bar.color,bar.colorFormatter),width:getBarWidth(bar.source[index],dataset),marginLeft:getMarginLeft(bar.source[index])}"
|
||||||
|
@tap.stop="onBarTap(index,idx)">
|
||||||
|
</view>
|
||||||
|
<view class="tui-bar__val"
|
||||||
|
v-if="(yAxisVal.show && clickEffect!=2 ) || (yAxisVal.show && clickEffect==2 && activeIndex===index)"
|
||||||
|
:style="{color:yAxisVal.color,fontSize:(yAxisVal.size || 24)+'rpx',whiteSpace: yAxisVal.nowrap?'nowrap':'normal',left:getLeft(index)}">
|
||||||
|
{{getXAxisVal(index)}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-bar__yAxis-text"
|
||||||
|
:style="{color:yAxisLabel.color || '#333',fontSize:(yAxisLabel.size || 24)+'rpx' }">
|
||||||
|
{{item}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-yAxis__tickmarks"
|
||||||
|
:style="{width:yAxisTick.width || '12rpx',backgroundColor:yAxisTick.color || '#e3e3e3'}"></view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-bar__yAxis-linebox">
|
||||||
|
<view class="tui-bar__yAxis-line" :class="{'tui-yAxis__line-first':idx===0}"
|
||||||
|
v-for="(item,idx) in xAxisData" :key="idx"
|
||||||
|
:style="{borderLeftStyle:splitLine.type,borderLeftColor:splitLine.color}">
|
||||||
|
<text class="tui-xAxis__val"
|
||||||
|
:style="{color:item.color || xAxisLabel.color,fontSize:(xAxisLabel.size||24)+'rpx'}"
|
||||||
|
v-if="xAxisLabel.show">{{item.value}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-column__tooltip" v-if="tooltip" :class="{'tui-column__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title">{{yAxis[activeIndex] || ''}}</view>
|
||||||
|
<view class="tui-column__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||||
|
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-bar",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//图表宽度 rpx
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 600
|
||||||
|
},
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
size: 24,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//x轴数据,如果不传则默认使用min,max值计算
|
||||||
|
// {
|
||||||
|
// value: 0,
|
||||||
|
// color: "#a0a0a0"
|
||||||
|
// }
|
||||||
|
xAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴最小值
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//x轴最大值
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//x轴分段递增数值
|
||||||
|
splitNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
//分割线
|
||||||
|
splitLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//分割线颜色,不显示则将颜色设置为transparent
|
||||||
|
color: "#e3e3e3",
|
||||||
|
type: "dashed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisLineColor: {
|
||||||
|
type: String,
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
default: '#e3e3e3'
|
||||||
|
},
|
||||||
|
xAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//柱状条高度
|
||||||
|
columnBarHeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
//y轴刻度线
|
||||||
|
yAxisTick: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
width: '12rpx',
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//y轴线条
|
||||||
|
yAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//y轴item的padding值
|
||||||
|
itemPadding: '30rpx 0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxisVal: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//默认选中y轴索引
|
||||||
|
currentIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
//是否堆叠展示
|
||||||
|
isStack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//柱状条点击效果:1-出现背景,2-高亮显示,其他变暗 3-无效果
|
||||||
|
clickEffect: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
柱状条的端点样式
|
||||||
|
round 向线条的每个末端添加圆形线帽
|
||||||
|
square 向线条的每个末端添加正方形线帽
|
||||||
|
*/
|
||||||
|
columnCap: {
|
||||||
|
type: String,
|
||||||
|
default: 'square'
|
||||||
|
},
|
||||||
|
isMinus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sections: 0,
|
||||||
|
xAxisData: [],
|
||||||
|
activeIndex: -1,
|
||||||
|
activeIdx: -1,
|
||||||
|
tooltips: [],
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null,
|
||||||
|
/*========options============*/
|
||||||
|
/*
|
||||||
|
name: '',
|
||||||
|
color: '',
|
||||||
|
source: []
|
||||||
|
colorFormatter:Function
|
||||||
|
*/
|
||||||
|
dataset: [],
|
||||||
|
yAxisValFormatter: null,
|
||||||
|
maxValue: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
xAxis(newVal) {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
currentIndex(newVal) {
|
||||||
|
if (newVal != this.activeIndex) {
|
||||||
|
this.activeIndex = newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
created() {
|
||||||
|
// this.maxValue = this.max;
|
||||||
|
this.init()
|
||||||
|
this.activeIndex = this.currentIndex;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getBarWidth(value, dataset) {
|
||||||
|
// ((bar.source[index]-(isStack?(min/dataset.length):min))/(maxValue-min))*width +'rpx'
|
||||||
|
let min = this.min
|
||||||
|
if (this.isMinus && !this.isStack) {
|
||||||
|
value = Math.abs(value)
|
||||||
|
min = 0
|
||||||
|
}
|
||||||
|
return ((value - (this.isStack ? (min / dataset.length) : min)) / (this.maxValue - this.min)) * this.width + 'rpx'
|
||||||
|
},
|
||||||
|
getMarginLeft(value) {
|
||||||
|
let ml = 0
|
||||||
|
if (this.isMinus && !this.isStack && Number(this.min) < 0) {
|
||||||
|
const min = value > 0 ? Math.abs(this.min) : (value - this.min)
|
||||||
|
ml = min / (this.maxValue - this.min) * this.width
|
||||||
|
}
|
||||||
|
return ml + 'rpx'
|
||||||
|
},
|
||||||
|
generateArray(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
getXAxisVal(index) {
|
||||||
|
let showVal = '';
|
||||||
|
let val = 0;
|
||||||
|
if (this.dataset.length === 1) {
|
||||||
|
val = this.dataset[0].source[index]
|
||||||
|
showVal = val;
|
||||||
|
} else if (this.dataset.length > 1) {
|
||||||
|
let arr = []
|
||||||
|
this.dataset.forEach(item => {
|
||||||
|
arr.push(item.source[index])
|
||||||
|
})
|
||||||
|
val = arr
|
||||||
|
}
|
||||||
|
if (this.yAxisVal.formatter && typeof this.yAxisVal.formatter === 'function') {
|
||||||
|
showVal = this.yAxisVal.formatter(val)
|
||||||
|
} else if (this.yAxisValFormatter && typeof this.yAxisValFormatter === 'function') {
|
||||||
|
showVal = this.yAxisValFormatter(val)
|
||||||
|
}
|
||||||
|
return showVal
|
||||||
|
},
|
||||||
|
getBarColor(val, color, colorFormatter) {
|
||||||
|
let bgColor = color;
|
||||||
|
if (colorFormatter && typeof colorFormatter === 'function') {
|
||||||
|
let formatColor = colorFormatter(val)
|
||||||
|
if (formatColor) {
|
||||||
|
bgColor = formatColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bgColor
|
||||||
|
},
|
||||||
|
getLeft(index) {
|
||||||
|
let arr = [0]
|
||||||
|
let total = 0
|
||||||
|
this.dataset.forEach(item => {
|
||||||
|
arr.push(item.source[index])
|
||||||
|
total += item.source[index]
|
||||||
|
})
|
||||||
|
return (((this.isStack ? total : Math.max(...arr)) - this.min) / (this.maxValue - this.min)) * this.width +
|
||||||
|
'rpx'
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
let sections = this.xAxis.length - 1;
|
||||||
|
let xAxis = this.xAxis;
|
||||||
|
this.maxValue = this.max;
|
||||||
|
if (sections <= 0) {
|
||||||
|
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||||
|
let sectionsArr = this.generateArray(0, sections)
|
||||||
|
|
||||||
|
xAxis = sectionsArr.map(item => {
|
||||||
|
return {
|
||||||
|
value: item * this.splitNumber + this.min
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.maxValue = xAxis[xAxis.length - 1].value
|
||||||
|
}
|
||||||
|
this.xAxisData = xAxis;
|
||||||
|
this.sections = sections + 1;
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
tooltipHandle(index) {
|
||||||
|
let data = [...this.dataset]
|
||||||
|
let tooltips = []
|
||||||
|
data.forEach(item => {
|
||||||
|
let color = item.color;
|
||||||
|
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||||
|
color = item.colorFormatter(item.source[index])
|
||||||
|
}
|
||||||
|
tooltips.push({
|
||||||
|
color: color,
|
||||||
|
name: item.name,
|
||||||
|
val: item.source[index]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.tooltips = tooltips;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
onBarTap(index, idx) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.activeIdx = idx;
|
||||||
|
this.tooltipHandle(index);
|
||||||
|
this.$emit('click', {
|
||||||
|
datasetIndex: idx,
|
||||||
|
sourceIndex: index,
|
||||||
|
...this.dataset[idx]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
dataset:柱状图表数据
|
||||||
|
yAxisValFormatter :格式化柱状条顶部value值(此处传值是为了做兼容处理)
|
||||||
|
*/
|
||||||
|
draw(dataset, yAxisValFormatter) {
|
||||||
|
this.yAxisValFormatter = yAxisValFormatter || null;
|
||||||
|
this.dataset = dataset || [];
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__bar-wrap {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bar__legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bar__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__bar-box {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
border-left: 1px solid;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__bar-item {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 10;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bar__item-stack {
|
||||||
|
flex-direction: row !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__tickmarks {
|
||||||
|
position: absolute;
|
||||||
|
width: 16rpx;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__bar {
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-align: center;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
/* border-right: 1px solid; */
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* position: relative; */
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__bar-opacity {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__bar-active {
|
||||||
|
opacity: 0.9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__item-active {
|
||||||
|
background-color: rgba(0, 0, 0, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__bar-round {
|
||||||
|
border-top-right-radius: 100px;
|
||||||
|
border-bottom-right-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bar__val {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
padding-left: 12rpx;
|
||||||
|
word-break: break-all;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: left 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bar__yAxis-text {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
flex: 1;
|
||||||
|
transform: translate(-100%, -50%);
|
||||||
|
padding-right: 20rpx;
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bar__yAxis-linebox {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bar__yAxis-line {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
border-left: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__line-first {
|
||||||
|
border-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__val {
|
||||||
|
transform: translateY(100%);
|
||||||
|
padding-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val-ml {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
609
src/components/thorui/tui-charts-column/tui-charts-column.vue
Normal file
609
src/components/thorui/tui-charts-column/tui-charts-column.vue
Normal file
@ -0,0 +1,609 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__column-wrap">
|
||||||
|
<view class="tui-column__legend" v-if="legend.show">
|
||||||
|
<view class="tui-column__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts__column-box" v-if="xAxis.length>0 && dataset.length>0">
|
||||||
|
<scroll-view :scroll-x="scrollable" class="tui-column__scroll-view" :style="{height:scrollViewH+'rpx'}">
|
||||||
|
<view :style="{height:(xAxisVal.height || 2) +'rpx'}" v-if="xAxisVal.show"></view>
|
||||||
|
<view class="tui-charts__column" :style="{height:height+'rpx'}">
|
||||||
|
<view class="tui-column__item"
|
||||||
|
:class="{'tui-column__flex-1':!scrollable,'tui-column__flex-column':isStack,'tui-column__item-active':activeIndex===index && clickEffect==1,'tui-column__bar-opacity':clickEffect==2,'tui-column__bar-active':clickEffect==2 && activeIndex==index}"
|
||||||
|
:style="{padding:scrollable? (xAxisLine.itemPadding ||'0 30rpx'):'0' }"
|
||||||
|
v-for="(item,index) in xAxis" :key="index">
|
||||||
|
<view class="tui-column__val"
|
||||||
|
v-if="(xAxisVal.show && clickEffect!=2 ) || (xAxisVal.show && clickEffect==2 && activeIndex===index)"
|
||||||
|
:style="{color:xAxisVal.color,fontSize:(xAxisVal.size || 24)+'rpx',whiteSpace: xAxisVal.nowrap?'nowrap':'normal'}">
|
||||||
|
{{getYAxisVal(index)}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-column__bar" :class="{'tui-column__bar-round':columnCap==='round'}"
|
||||||
|
v-for="(bar,idx) in dataset" :key="idx"
|
||||||
|
:style="{width:columnBarWidth+'rpx',borderTopColor:getBarColor(bar.source[index],bar.color,bar.colorFormatter),background:getBarColor(bar.source[index],bar.color,bar.colorFormatter),height:getBarHeight(bar.source[index],dataset,splitNumber,yAxisLine.itemGap),marginBottom:getMarginBottom(bar.source[index],yAxisLine.itemGap)}"
|
||||||
|
@tap.stop="onBarTap(index,idx)">
|
||||||
|
</view>
|
||||||
|
<view class="tui-column__xAxis-text"
|
||||||
|
:style="{color:xAxisLabel.color || '#333',fontSize:(xAxisLabel.size || 24)+'rpx' }">
|
||||||
|
{{item}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-xAxis__tickmarks"
|
||||||
|
:style="{height:xAxisTick.height || '12rpx',backgroundColor:xAxisTick.color || '#e3e3e3'}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="tui-column__border-left"
|
||||||
|
:style="{height:height+(xAxisVal.show?(xAxisVal.height || 2):0)+'rpx',backgroundColor:yAxisLine.color || '#e3e3e3'}">
|
||||||
|
</view>
|
||||||
|
<view class="tui-xAxis__line" :class="{'tui-line__first':index===0}"
|
||||||
|
:style="{bottom:index*(yAxisLine.itemGap || 60)+(xAxisLabel.height || 60)+'rpx',borderTopStyle:index===0?'solid':splitLine.type,borderTopColor:index===0?xAxisLine.color:splitLine.color}"
|
||||||
|
v-for="(item,index) in yAxisData" :key="index">
|
||||||
|
<text class="tui-yAxis__val"
|
||||||
|
:style="{color:item.color || yAxisLabel.color,fontSize:(yAxisLabel.size||24)+'rpx'}"
|
||||||
|
v-if="yAxisLabel.show">{{item.value}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-column__tooltip" v-if="tooltip" :class="{'tui-column__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title">{{xAxis[activeIndex] || ''}}</view>
|
||||||
|
<view class="tui-column__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||||
|
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-column",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
size: 24,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//默认选中x轴索引
|
||||||
|
currentIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
//柱状条宽度
|
||||||
|
columnBarWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
//分割线
|
||||||
|
splitLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//分割线颜色,不显示则将颜色设置为transparent
|
||||||
|
color: "#e3e3e3",
|
||||||
|
type: "dashed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴刻度线
|
||||||
|
xAxisTick: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
height: '12rpx',
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴线条
|
||||||
|
xAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//x轴item的padding值
|
||||||
|
itemPadding: '0 30rpx'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: "#333",
|
||||||
|
size: 24,
|
||||||
|
height: 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisVal: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
color: "#333",
|
||||||
|
size: 24,
|
||||||
|
//如果show为true且val显示的时候,height需要设置一定的值保证val能显示完整 rpx
|
||||||
|
height: 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//y轴数据,如果不传则默认使用min,max值计算
|
||||||
|
// {
|
||||||
|
// value: 0,
|
||||||
|
// color: "#333"
|
||||||
|
// }
|
||||||
|
yAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//y轴最小值
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//y轴最大值
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//y轴分段递增数值
|
||||||
|
splitNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
yAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//y轴item间距 rpx
|
||||||
|
itemGap: 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//是否可滚动
|
||||||
|
scrollable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否堆叠展示
|
||||||
|
isStack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//柱状条点击效果:1-出现背景,2-高亮显示,其他变暗 3-无效果
|
||||||
|
clickEffect: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
柱状条的端点样式
|
||||||
|
round 向线条的每个末端添加圆形线帽
|
||||||
|
square 向线条的每个末端添加正方形线帽
|
||||||
|
*/
|
||||||
|
columnCap: {
|
||||||
|
type: String,
|
||||||
|
default: 'square'
|
||||||
|
},
|
||||||
|
//是否支持负数显示
|
||||||
|
isMinus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
height: 0,
|
||||||
|
scrollViewH: 0,
|
||||||
|
sections: 0,
|
||||||
|
yAxisData: [],
|
||||||
|
activeIndex: -1,
|
||||||
|
activeIdx: -1,
|
||||||
|
tooltips: [],
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null,
|
||||||
|
/*========options============*/
|
||||||
|
/*
|
||||||
|
name: '',
|
||||||
|
color: '',
|
||||||
|
source: []
|
||||||
|
colorFormatter:Function
|
||||||
|
*/
|
||||||
|
dataset: [],
|
||||||
|
xAxisValFormatter: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
yAxis(newVal) {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
currentIndex(newVal) {
|
||||||
|
if (newVal != this.activeIndex) {
|
||||||
|
this.activeIndex = newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init()
|
||||||
|
this.activeIndex = this.currentIndex;
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
getBarHeight(value, dataset, splitNumber, itemGap) {
|
||||||
|
// ((bar.source[index]-(isStack?(min/dataset.length):min))/splitNumber)*(yAxisLine.itemGap || 60) +'rpx'
|
||||||
|
let min = this.min
|
||||||
|
if (this.isMinus && !this.isStack) {
|
||||||
|
value = Math.abs(value)
|
||||||
|
min = 0
|
||||||
|
}
|
||||||
|
return ((value - (this.isStack ? (min / dataset.length) : min)) / this.splitNumber) * (itemGap ||
|
||||||
|
60) + 'rpx'
|
||||||
|
},
|
||||||
|
getMarginBottom(value, itemGap) {
|
||||||
|
let mb = 0
|
||||||
|
if (this.isMinus && !this.isStack && Number(this.min) < 0) {
|
||||||
|
const min = value > 0 ? Math.abs(this.min) : (value - this.min)
|
||||||
|
mb = min / this.splitNumber * (itemGap || 60)
|
||||||
|
}
|
||||||
|
return mb + 'rpx'
|
||||||
|
},
|
||||||
|
generateArray(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
getYAxisVal(index) {
|
||||||
|
let showVal = '';
|
||||||
|
let val = 0;
|
||||||
|
if (this.dataset.length === 1) {
|
||||||
|
val = this.dataset[0].source[index]
|
||||||
|
showVal = val;
|
||||||
|
} else if (this.dataset.length > 1) {
|
||||||
|
let arr = []
|
||||||
|
this.dataset.forEach(item => {
|
||||||
|
arr.push(item.source[index])
|
||||||
|
})
|
||||||
|
val = arr
|
||||||
|
}
|
||||||
|
if (this.xAxisVal.formatter && typeof this.xAxisVal.formatter === 'function') {
|
||||||
|
showVal = this.xAxisVal.formatter(val)
|
||||||
|
} else if (this.xAxisValFormatter && typeof this.xAxisValFormatter === 'function') {
|
||||||
|
showVal = this.xAxisValFormatter(val)
|
||||||
|
}
|
||||||
|
return showVal
|
||||||
|
},
|
||||||
|
getBarColor(val, color, colorFormatter) {
|
||||||
|
let bgColor = color;
|
||||||
|
if (colorFormatter && typeof colorFormatter === 'function') {
|
||||||
|
let formatColor = colorFormatter(val)
|
||||||
|
if (formatColor) {
|
||||||
|
bgColor = formatColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bgColor
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
let itemGap = this.yAxisLine.itemGap || 60;
|
||||||
|
let sections = this.yAxis.length - 1;
|
||||||
|
let yAxis = this.yAxis;
|
||||||
|
if (sections <= 0) {
|
||||||
|
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||||
|
let sectionsArr = this.generateArray(0, sections)
|
||||||
|
|
||||||
|
yAxis = sectionsArr.map(item => {
|
||||||
|
return {
|
||||||
|
value: item * this.splitNumber + this.min
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.yAxisData = yAxis;
|
||||||
|
this.sections = sections + 1;
|
||||||
|
this.height = itemGap * sections;
|
||||||
|
const valH = this.xAxisVal.show ? (this.xAxisVal.height || 2) : 0;
|
||||||
|
this.scrollViewH = this.height + (this.xAxisLabel.height || 60) + valH;
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
tooltipHandle(index) {
|
||||||
|
let data = [...this.dataset]
|
||||||
|
let tooltips = []
|
||||||
|
data.forEach(item => {
|
||||||
|
let color = item.color;
|
||||||
|
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||||
|
color = item.colorFormatter(item.source[index])
|
||||||
|
}
|
||||||
|
tooltips.push({
|
||||||
|
color: color,
|
||||||
|
name: item.name,
|
||||||
|
val: item.source[index]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.tooltips = tooltips;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
onBarTap(index, idx) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.activeIdx = idx;
|
||||||
|
this.tooltipHandle(index);
|
||||||
|
this.$emit('click', {
|
||||||
|
datasetIndex: idx,
|
||||||
|
sourceIndex: index,
|
||||||
|
...this.dataset[idx]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
dataset:柱状图表数据
|
||||||
|
xAxisValFormatter :格式化柱状条顶部value值(此处传值是为了做兼容处理)
|
||||||
|
*/
|
||||||
|
draw(dataset, xAxisValFormatter) {
|
||||||
|
this.xAxisValFormatter = xAxisValFormatter || null;
|
||||||
|
this.dataset = dataset || [];
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__column-wrap {
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__column-box {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__scroll-view {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__column {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 10;
|
||||||
|
transition: all 0.3s;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__bar-opacity {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__bar-active {
|
||||||
|
/*这里请勿随意将值改为1*/
|
||||||
|
opacity: 0.9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__item-active {
|
||||||
|
background-color: rgba(0, 0, 0, .1);
|
||||||
|
padding-top: 20rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__flex-column {
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__tickmarks {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 1px;
|
||||||
|
transform: translateY(100%);
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__bar {
|
||||||
|
transition: all 0.3s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: center;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
/* border-top: 1px solid; */
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__bar-round {
|
||||||
|
border-top-left-radius: 100px;
|
||||||
|
border-top-right-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__val {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
padding-bottom: 12rpx;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__xAxis-text {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
flex: 1;
|
||||||
|
transform: translate(-50%, 100%);
|
||||||
|
padding-top: 8rpx;
|
||||||
|
word-break: break-all;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__border-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__line {
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
border-top-width: 1px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__first {
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__val {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
padding-right: 12rpx;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-column__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val-ml {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
273
src/components/thorui/tui-charts-funnel/tui-charts-funnel.vue
Normal file
273
src/components/thorui/tui-charts-funnel/tui-charts-funnel.vue
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__funnel-wrap" :class="{'tui-charts__funnel-vertical':legend.direction!=='vertical'}">
|
||||||
|
<view class="tui-funnel__legend" :class="{'tui-legend__flex-column':legend.direction==='vertical'}"
|
||||||
|
v-if="legend.show">
|
||||||
|
<view class="tui-funnel__legend-item" :style="{marginLeft:legend.direction==='vertical'?'0':'24rpx'}"
|
||||||
|
v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts-funnel" :style="{width:w+'px',height:h+'px'}">
|
||||||
|
<view class="tui-charts__funnel-item" :class="['tui-funnel__'+sort]"
|
||||||
|
:style="{width:item.width+'px',height:itemHeight+'px',borderTopWidth:sort==='desc'?itemHeight+'px':0,borderTopColor:sort==='desc'?item.color:'transparent',borderBottomWidth:sort==='asc'?itemHeight+'px':0,borderBottomColor:sort==='asc'?item.color:'transparent',borderLeftWidth:item.diff+'px',borderRightWidth:item.diff+'px'}"
|
||||||
|
v-for="(item,index) in dataset" :key="index" @tap.stop="itemClick(index)"></view>
|
||||||
|
|
||||||
|
<view class="tui-funnel__tooltip" v-if="tooltip" :class="{'tui-funnel__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title" v-if="title">{{title}}</view>
|
||||||
|
<view class="tui-funnel__tooltip-item" :style="{paddingTop:title?'24rpx':'0'}">
|
||||||
|
<view class="tui-legend__circle"
|
||||||
|
:style="{backgroundColor:dataset[activeIndex] && dataset[activeIndex].color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{dataset[activeIndex] && dataset[activeIndex].name}}</text>
|
||||||
|
<text
|
||||||
|
class="tui-tooltip__val tui-tooltip__val-ml">{{dataset[activeIndex] && dataset[activeIndex].value}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function ascSort(a, b) {
|
||||||
|
return a.value - b.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function descSort(a, b) {
|
||||||
|
return b.value - a.value;
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-funnel",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//统计名称
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//总宽度 rpx
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 480
|
||||||
|
},
|
||||||
|
//间距 rpx
|
||||||
|
gap: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 4
|
||||||
|
},
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
size: 24,
|
||||||
|
color: '#333',
|
||||||
|
//horizontal、vertical
|
||||||
|
direction: 'horizontal'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//asc,desc
|
||||||
|
sort: {
|
||||||
|
type: String,
|
||||||
|
default: 'desc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltips: [],
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null,
|
||||||
|
activeIndex: -1,
|
||||||
|
dataset: [],
|
||||||
|
itemHeight: 60,
|
||||||
|
w: 200,
|
||||||
|
h: 240
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
getPx(rpx) {
|
||||||
|
let px = parseInt(uni.upx2px(Number(rpx)))
|
||||||
|
return px % 2 === 0 ? px : px - 1
|
||||||
|
},
|
||||||
|
getEvenNum(px) {
|
||||||
|
px = parseInt(px)
|
||||||
|
return px % 2 === 0 ? px : px - 1
|
||||||
|
},
|
||||||
|
draw(data) {
|
||||||
|
let dataList = JSON.parse(JSON.stringify(data))
|
||||||
|
let dataset = dataList.sort(descSort)
|
||||||
|
let w = this.getPx(this.width || 400)
|
||||||
|
let h = this.getPx(this.height || 480)
|
||||||
|
this.w = w;
|
||||||
|
this.h = h;
|
||||||
|
let len = dataset.length
|
||||||
|
let gap = Number(this.gap) * (len - 1)
|
||||||
|
let max = Number(dataset[0].value)
|
||||||
|
this.itemHeight = (h - this.getPx(gap)) / len
|
||||||
|
dataset.map((item, index) => {
|
||||||
|
let width = index === 0 ? w : this.getEvenNum(Number(item.value) / max * w)
|
||||||
|
if (dataset[index + 1]) {
|
||||||
|
let w1 = this.getEvenNum(Number(dataset[index + 1].value) / max * w)
|
||||||
|
item.diff = Math.abs(width - w1) / 2
|
||||||
|
} else {
|
||||||
|
item.diff = width / 2
|
||||||
|
}
|
||||||
|
item.width = width - (item.diff || 0) * 2
|
||||||
|
})
|
||||||
|
if (this.sort === 'asc') {
|
||||||
|
this.dataset = dataset.sort(ascSort)
|
||||||
|
} else {
|
||||||
|
this.dataset = dataset
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
itemClick(index) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
this.$emit('click', {
|
||||||
|
index: index,
|
||||||
|
...this.dataset[index]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__funnel-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__funnel-vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-funnel__legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__flex-column {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-right: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-funnel__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts-funnel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__funnel-item {
|
||||||
|
border-left-style: solid;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-right-style: solid;
|
||||||
|
border-right-color: transparent;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-funnel__asc {
|
||||||
|
border-bottom-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-funnel__desc {
|
||||||
|
border-top-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-funnel__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-funnel__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 30rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-funnel__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val-ml {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
653
src/components/thorui/tui-charts-line/tui-charts-line.vue
Normal file
653
src/components/thorui/tui-charts-line/tui-charts-line.vue
Normal file
@ -0,0 +1,653 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__line-wrap" :style="{width:width+'rpx'}">
|
||||||
|
<view class="tui-line__legend" v-if="legend.show">
|
||||||
|
<view class="tui-line__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts__line-box" v-if="xAxis.length>0 && dataset.length>0" :style="{width:width+'rpx'}">
|
||||||
|
<scroll-view :scroll-x="scrollable" class="tui-line__scroll-view" :style="{height:scrollViewH+'rpx'}">
|
||||||
|
<view :style="{height:(xAxisVal.height || 48) +'rpx'}"></view>
|
||||||
|
<view class="tui-charts__line" :style="{height:height+'rpx'}">
|
||||||
|
<view class="tui-line__item" :class="{'tui-line__flex-1':!scrollable}"
|
||||||
|
:style="{width:(xAxisLine.itemGap || 120)+'rpx'}" v-for="(item,index) in xAxis" :key="index">
|
||||||
|
<view class="tui-line__xAxis-text"
|
||||||
|
:style="{color:xAxisLabel.color || '#333',fontSize:(xAxisLabel.size || 24)+'rpx' }">
|
||||||
|
{{item}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-yAxis__split-line"
|
||||||
|
:style="{borderRightStyle:yAxisSplitLine.type || 'dashed',borderRightColor:yAxisSplitLine.color || '#e3e3e3'}"
|
||||||
|
v-if="tooltipShow && index==activeIdx">
|
||||||
|
</view>
|
||||||
|
<view class="tui-xAxis__tickmarks"
|
||||||
|
:style="{height:xAxisTick.height || '12rpx',backgroundColor:xAxisTick.color || '#e3e3e3'}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-for="(dot,i) in dots" :key="dot.id">
|
||||||
|
<view class="tui-charts__line-dot"
|
||||||
|
:class="{'tui-charts__dot-enlarge':tooltipShow && j==activeIdx}" @tap.stop="dotClick(i,j)"
|
||||||
|
v-for="(d,j) in dot.source" :key="d.id"
|
||||||
|
:style="{bottom: d.y+'rpx', left: d.x+'rpx',width:(brokenDot.width || 12)+'rpx',height:(brokenDot.width || 12)+'rpx',borderColor:dot.color || brokenDot.color,background:brokenDot.color || dot.color}">
|
||||||
|
<text class="tui-line__val"
|
||||||
|
:style="{fontSize:(xAxisVal.size || 24)+'rpx',color:xAxisVal.color}"
|
||||||
|
v-if="xAxisVal.show">
|
||||||
|
{{getYAxisVal(i,j)}}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-for="(line,idx) in lines" :key="line.id">
|
||||||
|
<view class="tui-charts__broken-line" v-for="(l,k) in line.source" :key="l.id"
|
||||||
|
:style="{height:brokenLineHeight+'px',background:line.color,bottom: l.y+'rpx', left: l.x+'rpx',width: l.width+'rpx','-webkit-transform': `rotate(${l.angle}deg)`,transform: `rotate(${l.angle}deg)`}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="tui-line__border-left"
|
||||||
|
:style="{height:height+(xAxisVal.height || 48)+'rpx',backgroundColor:yAxisLine.color || '#e3e3e3'}">
|
||||||
|
</view>
|
||||||
|
<view class="tui-xAxis__line" :class="{'tui-line__first':index===0}"
|
||||||
|
:style="{bottom:index*(yAxisLine.itemGap || 60)+(xAxisLabel.height || 60)+'rpx',borderTopStyle:index===0?'solid':splitLine.type,borderTopColor:index===0?xAxisLine.color:splitLine.color}"
|
||||||
|
v-for="(item,index) in yAxisData" :key="index">
|
||||||
|
<text class="tui-yAxis__val"
|
||||||
|
:style="{color:item.color || yAxisLabel.color,fontSize:(yAxisLabel.size||24)+'rpx'}"
|
||||||
|
v-if="yAxisLabel.show">{{item.value}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tui-line__tooltip" v-if="tooltip" :class="{'tui-line__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title">{{xAxis[activeIdx] || ''}}</view>
|
||||||
|
<view class="tui-line__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||||
|
<view class="tui-line__legend-circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||||
|
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.val}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-line",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//图表宽度
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 620
|
||||||
|
},
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
size: 24,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//默认选中x轴索引
|
||||||
|
currentIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
//分割线
|
||||||
|
splitLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//分割线颜色,不显示则将颜色设置为transparent
|
||||||
|
color: "#e3e3e3",
|
||||||
|
type: "dashed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴刻度线
|
||||||
|
xAxisTick: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
height: '12rpx',
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴线条
|
||||||
|
xAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//x轴item间距 rpx
|
||||||
|
itemGap: 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: "#333",
|
||||||
|
size: 24,
|
||||||
|
height: 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisVal: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24,
|
||||||
|
//如果show为true且val显示的时候,height需要设置一定的值保证val能显示完整 rpx
|
||||||
|
height: 48
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//点击坐标点所显示的分割线
|
||||||
|
yAxisSplitLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//分割线颜色,不显示则将颜色设置为transparent
|
||||||
|
color: "transparent",
|
||||||
|
type: "dashed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//折线坐标点宽度 rpx
|
||||||
|
brokenDot: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
width: 12,
|
||||||
|
//点的背景色
|
||||||
|
color: '#F8F8F8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//折线高度/粗细 px
|
||||||
|
brokenLineHeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//y轴数据,如果不传则默认使用min,max值计算
|
||||||
|
// {
|
||||||
|
// value: 0,
|
||||||
|
// color: "#333"
|
||||||
|
// }
|
||||||
|
yAxis: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//y轴最小值
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//y轴最大值
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//y轴分段递增数值
|
||||||
|
splitNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
yAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//y轴item间距 rpx
|
||||||
|
itemGap: 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//是否可滚动
|
||||||
|
scrollable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
height: 0,
|
||||||
|
scrollViewH: 0,
|
||||||
|
sections: 0,
|
||||||
|
yAxisData: [],
|
||||||
|
activeIndex: -1,
|
||||||
|
activeIdx: -1,
|
||||||
|
tooltips: [],
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null,
|
||||||
|
dots: [],
|
||||||
|
lines: [],
|
||||||
|
/*========options============*/
|
||||||
|
/*
|
||||||
|
name: '',
|
||||||
|
color: '',
|
||||||
|
source: []
|
||||||
|
colorFormatter:Function
|
||||||
|
*/
|
||||||
|
dataset: [],
|
||||||
|
xAxisValFormatter: null,
|
||||||
|
maxValue: 1
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init()
|
||||||
|
this.activeIdx = this.currentIndex;
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
getYAxisVal(idx, index) {
|
||||||
|
let showVal = this.dataset[idx].source[index];
|
||||||
|
if (this.xAxisVal.formatter && typeof this.xAxisVal.formatter === 'function') {
|
||||||
|
showVal = this.xAxisVal.formatter(showVal)
|
||||||
|
} else if (this.xAxisValFormatter && typeof this.xAxisValFormatter === 'function') {
|
||||||
|
showVal = this.xAxisValFormatter(showVal)
|
||||||
|
}
|
||||||
|
return showVal
|
||||||
|
},
|
||||||
|
generateArray(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
getValue(val) {
|
||||||
|
return val < 0 ? 0 : val;
|
||||||
|
},
|
||||||
|
getCoordinatePoint() {
|
||||||
|
const xAxis = [...this.xAxis];
|
||||||
|
const xSections = xAxis.length;
|
||||||
|
const ySections = this.yAxisData.length - 1;
|
||||||
|
const itemGap = this.scrollable ? (this.xAxisLine.itemGap || 120) : (this.width / xSections);
|
||||||
|
let dots = [];
|
||||||
|
let radius = (this.brokenDot.width || 12) / 2;
|
||||||
|
|
||||||
|
this.dataset.map((item, index) => {
|
||||||
|
let source = item.source || []
|
||||||
|
let dotArr = []
|
||||||
|
source.map((val, idx) => {
|
||||||
|
dotArr.push({
|
||||||
|
id: 'd' + idx,
|
||||||
|
x: this.getValue((0.5 + idx) * itemGap - radius),
|
||||||
|
y: this.getValue((val - this.min) / (this.maxValue - this.min) * (this
|
||||||
|
.yAxisLine
|
||||||
|
.itemGap || 60) *
|
||||||
|
ySections - radius)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
dots.push({
|
||||||
|
id: 'dd' + index,
|
||||||
|
color: item.color,
|
||||||
|
source: dotArr
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.dots = dots;
|
||||||
|
this.drawLines(dots);
|
||||||
|
},
|
||||||
|
drawLines(dots) {
|
||||||
|
let lines = []
|
||||||
|
// dots是点的集合 : Array<{ x: number; y: number; }>
|
||||||
|
let radius = (this.brokenDot.width || 12) / 2;
|
||||||
|
dots.map((item, idx) => {
|
||||||
|
let dotArr = item.source;
|
||||||
|
let lineArr = [];
|
||||||
|
dotArr.map((dot, index) => {
|
||||||
|
// 最后一个点没有连线
|
||||||
|
if (!dotArr[index + 1]) return;
|
||||||
|
const AB = {
|
||||||
|
x: dotArr[index + 1].x - dot.x,
|
||||||
|
y: dotArr[index + 1].y - dot.y,
|
||||||
|
y1: dot.y - dotArr[index + 1].y
|
||||||
|
}
|
||||||
|
// 向量的模
|
||||||
|
const v = Math.sqrt(Math.pow(AB.x, 2) + Math.pow(AB.y, 2));
|
||||||
|
// 求出偏转角度
|
||||||
|
const angle = Math.atan2(AB.y1, AB.x) * (180 / Math.PI);
|
||||||
|
lineArr.push({
|
||||||
|
id: 'l' + index,
|
||||||
|
x: dot.x + radius,
|
||||||
|
y: dot.y + radius - 1,
|
||||||
|
width: v,
|
||||||
|
angle: AB.y1 > 0 ? Math.sqrt(Math.pow(angle, 2)) : -Math.sqrt(Math.pow(
|
||||||
|
angle,
|
||||||
|
2))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
lines.push({
|
||||||
|
id: 'll' + idx,
|
||||||
|
color: item.color,
|
||||||
|
source: lineArr
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.lines = lines
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.maxValue = this.max;
|
||||||
|
let itemGap = this.yAxisLine.itemGap || 60;
|
||||||
|
let sections = this.yAxis.length - 1;
|
||||||
|
let yAxis = this.yAxis;
|
||||||
|
if (sections <= 0) {
|
||||||
|
sections = Math.ceil((this.max - this.min) / this.splitNumber)
|
||||||
|
let sectionsArr = this.generateArray(0, sections)
|
||||||
|
|
||||||
|
yAxis = sectionsArr.map(item => {
|
||||||
|
return {
|
||||||
|
value: item * this.splitNumber + this.min
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.maxValue = yAxis[yAxis.length - 1].value
|
||||||
|
}
|
||||||
|
this.yAxisData = yAxis;
|
||||||
|
this.sections = sections + 1;
|
||||||
|
this.height = itemGap * sections;
|
||||||
|
const valH = this.xAxisVal.height || 48;
|
||||||
|
this.scrollViewH = this.height + (this.xAxisLabel.height || 60) + valH;
|
||||||
|
this.getCoordinatePoint();
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
dataset:折线图表数据
|
||||||
|
xAxisValFormatter :格式化折线拐点value值(此处传值是为了做兼容处理)
|
||||||
|
*/
|
||||||
|
draw(dataset, xAxisValFormatter) {
|
||||||
|
this.xAxisValFormatter = xAxisValFormatter || null;
|
||||||
|
this.dataset = dataset || [];
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
tooltipHandle(index) {
|
||||||
|
let data = [...this.dataset]
|
||||||
|
let tooltips = []
|
||||||
|
data.forEach(item => {
|
||||||
|
let color = item.color;
|
||||||
|
if (item.colorFormatter && typeof item.colorFormatter === 'function') {
|
||||||
|
color = item.colorFormatter(item.source[index])
|
||||||
|
}
|
||||||
|
tooltips.push({
|
||||||
|
color: color,
|
||||||
|
name: item.name,
|
||||||
|
val: item.source[index]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.tooltips = tooltips;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
dotClick(index, idx) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.activeIdx = idx;
|
||||||
|
this.tooltipHandle(idx);
|
||||||
|
this.$emit('click', {
|
||||||
|
datasetIndex: index,
|
||||||
|
sourceIndex: idx,
|
||||||
|
...this.dataset[index]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__line-wrap {
|
||||||
|
position: relative;
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
|
/* margin: 0 auto; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__legend-circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__line-box {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 1px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
overflow: visible;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__scroll-view {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__line {
|
||||||
|
min-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
/* overflow: hidden; */
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__item {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 10;
|
||||||
|
transition: all 0.3s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__tickmarks {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 1px;
|
||||||
|
transform: translateY(100%);
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__split-line {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
border-right-width: 1px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__xAxis-text {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
flex: 1;
|
||||||
|
transform: translate(-50%, 100%);
|
||||||
|
padding-top: 8rpx;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__border-left {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__line {
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
border-top-width: 1px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__first {
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__val {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
padding-right: 12rpx;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__line-dot {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s;
|
||||||
|
z-index: 12;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__val {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
padding-bottom: 12rpx;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__dot-enlarge {
|
||||||
|
transform: scale(1.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__broken-line {
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
z-index: 10;
|
||||||
|
border-color: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* transform: translateZ(0); */
|
||||||
|
/* -webkit-backface-visibility:hidden; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val-ml {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
src/components/thorui/tui-charts-mixed/tui-charts-mixed.vue
Normal file
20
src/components/thorui/tui-charts-mixed/tui-charts-mixed.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name:"tui-charts-mixed",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
282
src/components/thorui/tui-charts-pie/tui-charts-pie.vue
Normal file
282
src/components/thorui/tui-charts-pie/tui-charts-pie.vue
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__pie-wrap" :class="{'tui-charts__pie-vertical':legend.direction!=='vertical'}">
|
||||||
|
<view class="tui-pie__legend" :class="{'tui-legend__flex-column':legend.direction==='vertical'}"
|
||||||
|
v-if="legend.show">
|
||||||
|
<view class="tui-pie__legend-item" :style="{marginLeft:legend.direction==='vertical'?'0':'24rpx'}" v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{backgroundColor:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts__pie-box" :style="{width:diam+'rpx',height:diam+'rpx',backgroundColor:backgroundColor}">
|
||||||
|
<view class="tui-charts__pie-itembox"
|
||||||
|
:style="{width:diam/2+2+'rpx',height:diam+'rpx',backgroundColor:item.angle>180?item.color:'transparent',zIndex:item.angle>180?10:'auto',clip:item.transformAngle>180?`rect(0, ${diam/2}rpx, ${diam}rpx, 0)`:'auto'}"
|
||||||
|
v-for="(item,index) in dataset" :key="index" @tap.stop="itemClick(index)">
|
||||||
|
<view class="tui-charts__pie-item"
|
||||||
|
:style="{width:diam/2+'rpx',height:diam+'rpx',marginLeft:diam/2+'rpx','-webkit-transform': `rotate(${item.transformAngle}deg)`,'transform': `rotate(${item.transformAngle}deg)`,backgroundColor:item.color}">
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts__pie-annular" v-if="type==2"
|
||||||
|
:style="{width:annular.width+'rpx',height:annular.width+'rpx',backgroundColor:annular.backgroundColor}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-pie__tooltip" v-if="tooltip" :class="{'tui-pie__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title" v-if="title">{{title}}</view>
|
||||||
|
<view class="tui-pie__tooltip-item" :style="{paddingTop:title?'24rpx':'0'}">
|
||||||
|
<view class="tui-legend__circle"
|
||||||
|
:style="{backgroundColor:dataset[activeIndex] && dataset[activeIndex].color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{dataset[activeIndex] && dataset[activeIndex].name}}</text>
|
||||||
|
<text
|
||||||
|
class="tui-tooltip__val tui-tooltip__val-ml">{{dataset[activeIndex] && dataset[activeIndex].value}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-pie",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//统计名称
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//饼状图直径 rpx
|
||||||
|
diam: {
|
||||||
|
type: Number,
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
//饼状图背景色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
size: 24,
|
||||||
|
color: '#333',
|
||||||
|
//horizontal,vertical
|
||||||
|
direction: 'horizontal'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//展示类型:1-饼状 2-环状
|
||||||
|
type: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//环形中间圆圈样式
|
||||||
|
annular: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
width: 200,
|
||||||
|
backgroundColor: '#f8f8f8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tooltips: [],
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null,
|
||||||
|
activeIndex: -1,
|
||||||
|
/*========options============*/
|
||||||
|
/*
|
||||||
|
value:0,
|
||||||
|
name: '',
|
||||||
|
color: ''
|
||||||
|
*/
|
||||||
|
dataset: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTotalVal(data) {
|
||||||
|
let val = 0;
|
||||||
|
data.forEach((item, index) => {
|
||||||
|
val += item.value;
|
||||||
|
})
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
init(dataset) {
|
||||||
|
let data = [...dataset]
|
||||||
|
const total = this.getTotalVal(data);
|
||||||
|
let totalAngle = 0;
|
||||||
|
data.map((item, index) => {
|
||||||
|
item.transformAngle = totalAngle;
|
||||||
|
item.angle = Number((item.value / total * 360).toFixed(1))
|
||||||
|
totalAngle += item.angle
|
||||||
|
})
|
||||||
|
this.dataset = data;
|
||||||
|
},
|
||||||
|
draw(dataset) {
|
||||||
|
this.init(dataset)
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
itemClick(index) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
this.$emit('click', {
|
||||||
|
index: index,
|
||||||
|
...this.dataset[index]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__pie-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__pie-vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-pie__legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__flex-column {
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-pie__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__pie-box {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: rotate(0deg) translateZ(0);
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__pie-itembox {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__pie-item {
|
||||||
|
transform-origin: 0 50%;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
/* transform-style:preserve-3d; */
|
||||||
|
/* transition: all 0.3s; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__pie-annular {
|
||||||
|
border-radius: 50%;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 12;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-pie__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-pie__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 30rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-pie__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val-ml {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
371
src/components/thorui/tui-charts-radar/tui-charts-radar.vue
Normal file
371
src/components/thorui/tui-charts-radar/tui-charts-radar.vue
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__radar-box" :style="{width:radar_w+'px'}">
|
||||||
|
<view class="tui-radar__legend" v-if="legend.show">
|
||||||
|
<view class="tui-radar__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{background:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts-radar" :class="{'tui-radar__mrgin':label.show}"
|
||||||
|
:style="{width:radar_w+'px',height:radar_w+'px'}">
|
||||||
|
<view class="tui-radar__radius" v-for="(item,index) in indicators" :key="index"
|
||||||
|
:style="{height:radar_w/2+'px',transform:`rotate(${item.angle}deg)`,background:axisLineColor,width:lineBold?'2px':'1px'}">
|
||||||
|
<view class="tui-radar__name" v-if="label.show"
|
||||||
|
:style="{color:label.color || '#bbb',fontSize:(label.size || 24) +'rpx'}">{{item.name}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-radar__center" :style="{width:lineBold?'2px':'1px',height:lineBold?'2px':'1px'}">
|
||||||
|
<view class="tui-radar__hypotenuse" v-for="(l,idx) in hypotenuse" :key="l.id"
|
||||||
|
:style="[{bottom: l.y+'px', left: l.x+'px',width: l.width+'px',transform: `rotate(${l.angle}deg)`,background:splitLineColor,height:lineBold?'2px':'1px'}]">
|
||||||
|
</view>
|
||||||
|
<view v-for="(item,index) in dataset" :key="index" @tap.stop="onDotTap(index)">
|
||||||
|
<view class="tui-radar__dataset" v-for="(d,i) in item.lines" :key="d.id"
|
||||||
|
:style="{bottom: d.y+'px', left: d.x+'px',width: d.width+'px',transform: `rotate(${d.angle}deg)`,background:item.color,height:lineBold?'2px':'1px'}">
|
||||||
|
<view class="tui-radar__dot" :style="{background:item.color}"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-radar__tooltip" v-if="tooltip" :class="{'tui-radar__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title">{{name}}</view>
|
||||||
|
<view class="tui-radar__tooltip-item" v-for="(item,index) in tooltips" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{background:color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{item.name}}</text>
|
||||||
|
<text class="tui-tooltip__val tui-tooltip__val-ml">{{item.value}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-radar",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
size: 24,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 480
|
||||||
|
},
|
||||||
|
splitNumber: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
indicator: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: true,
|
||||||
|
color: '#bbb',
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLineColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#ddd'
|
||||||
|
},
|
||||||
|
splitLineColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#eee'
|
||||||
|
},
|
||||||
|
lineBold:{
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
radar_w: 200,
|
||||||
|
dataset: [],
|
||||||
|
indicators: [],
|
||||||
|
hypotenuse: [],
|
||||||
|
name: '',
|
||||||
|
color: '',
|
||||||
|
tooltips: [],
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.radar_w = this.getPx(this.width)
|
||||||
|
this.draw(this.dataset)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getPx(rpx) {
|
||||||
|
let px = parseInt(uni.upx2px(Number(rpx)))
|
||||||
|
return px % 2 === 0 ? px : px + 1
|
||||||
|
},
|
||||||
|
getDrawData(dots, idx, type = 'l') {
|
||||||
|
let data = [];
|
||||||
|
dots.map((dot, index) => {
|
||||||
|
let obj = !dots[index + 1] ? dots[0] : dots[index + 1]
|
||||||
|
const AB = {
|
||||||
|
x: obj.x - dot.x,
|
||||||
|
y: obj.y - dot.y,
|
||||||
|
y1: dot.y - obj.y
|
||||||
|
}
|
||||||
|
const v = Math.sqrt(Math.pow(AB.x, 2) + Math.pow(AB.y, 2));
|
||||||
|
const angle = Math.atan2(AB.y1, AB.x) * (180 / Math.PI);
|
||||||
|
data.push({
|
||||||
|
id: type + idx + '_' + index,
|
||||||
|
x: dot.x + 1,
|
||||||
|
y: dot.y,
|
||||||
|
width: v,
|
||||||
|
angle: AB.y1 > 0 ? Math.sqrt(Math.pow(angle, 2)) : -Math.sqrt(Math.pow(angle, 2))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
initData(dataset, r, indicator, angle) {
|
||||||
|
let total = this.radar_w
|
||||||
|
dataset = JSON.parse(JSON.stringify(dataset))
|
||||||
|
dataset.map((item, index) => {
|
||||||
|
let lines = []
|
||||||
|
item.source.map((val, idx) => {
|
||||||
|
let dsR = val / indicator[idx].max * r
|
||||||
|
lines.push({
|
||||||
|
y: dsR * Math.cos(angle * idx * Math.PI / 180),
|
||||||
|
x: dsR * Math.sin(angle * idx * Math.PI / 180)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
item.lines = this.getDrawData(lines, index, 'd')
|
||||||
|
})
|
||||||
|
this.dataset = dataset
|
||||||
|
},
|
||||||
|
draw(dataset) {
|
||||||
|
if (!dataset || !dataset[0] || !this.indicator) return
|
||||||
|
let len = this.indicator.length
|
||||||
|
let angle = 360 / len
|
||||||
|
let r = this.radar_w / 2
|
||||||
|
let indicator = JSON.parse(JSON.stringify(this.indicator))
|
||||||
|
indicator.map((item, i) => {
|
||||||
|
item.angle = angle * i
|
||||||
|
})
|
||||||
|
this.indicators = indicator
|
||||||
|
|
||||||
|
let dots = []
|
||||||
|
let sn = Number(this.splitNumber)
|
||||||
|
for (let i = 0; i < sn; i++) {
|
||||||
|
let dotArr = []
|
||||||
|
let dsDot = []
|
||||||
|
let radius = r - i * (r / sn)
|
||||||
|
for (let j = 0; j < len; j++) {
|
||||||
|
dotArr.push({
|
||||||
|
y: radius * Math.cos(angle * j * Math.PI / 180),
|
||||||
|
x: radius * Math.sin(angle * j * Math.PI / 180)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
dots.push(dotArr)
|
||||||
|
}
|
||||||
|
|
||||||
|
let lineArr = [];
|
||||||
|
dots.map((dotArr, idx) => {
|
||||||
|
lineArr = lineArr.concat(this.getDrawData(dotArr, idx))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.hypotenuse = lineArr
|
||||||
|
|
||||||
|
this.initData(dataset, r, indicator, angle)
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
tooltipHandle(index) {
|
||||||
|
let data = this.dataset[index]
|
||||||
|
let tooltips = []
|
||||||
|
let indicator = JSON.parse(JSON.stringify(this.indicator))
|
||||||
|
indicator.map((item, idx) => {
|
||||||
|
item.value = data.source[idx]
|
||||||
|
})
|
||||||
|
this.name = data.name || ''
|
||||||
|
this.color = data.color || '#333'
|
||||||
|
this.tooltips = indicator;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
onDotTap(index) {
|
||||||
|
this.tooltipHandle(index);
|
||||||
|
this.$emit('click', {
|
||||||
|
datasetIndex: index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__radar-box {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts-radar {
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__mrgin {
|
||||||
|
margin-top: 32rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__radius {
|
||||||
|
/* width: 1px; */
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
transform-origin: 50% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__name {
|
||||||
|
min-width: 120rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
position: absolute;
|
||||||
|
top: -20rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__center {
|
||||||
|
position: absolute;
|
||||||
|
/* width: 2px;
|
||||||
|
height: 2px; */
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__hypotenuse,
|
||||||
|
.tui-radar__dataset {
|
||||||
|
/* height: 1px; */
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: 0 50%;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__dataset {
|
||||||
|
z-index: 3;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__dot {
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 5;
|
||||||
|
transform: translate(-50%, -50%) rotate(0);
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__legend {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radar__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-radar__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 24rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val-ml {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
392
src/components/thorui/tui-charts-scatter/tui-charts-scatter.vue
Normal file
392
src/components/thorui/tui-charts-scatter/tui-charts-scatter.vue
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-charts__scatter-wrap" :style="{width:width+'rpx'}">
|
||||||
|
<view class="tui-scatter__legend" v-if="legend.show">
|
||||||
|
<view class="tui-scatter__legend-item" v-for="(item,index) in dataset" :key="index">
|
||||||
|
<view class="tui-legend__circle" :style="{background:item.color}"></view>
|
||||||
|
<text
|
||||||
|
:style="{fontSize:(legend.size || 24)+'rpx',lineHeight:(legend.size || 24)+'rpx',color:legend.color || '#333'}">{{item.name}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-charts__scatter-box" :style="{width:width+'rpx',height:height+'rpx'}">
|
||||||
|
<view class="tui-xAxis__line" v-for="(item,index) in xAxisData" :key="item.id"
|
||||||
|
:style="{left:index*(xAxisLine.itemGap || 100) +'rpx',borderLeftStyle:index===0?'solid':splitLine.type,borderLeftColor:index===0?yAxisLine.color:splitLine.color}">
|
||||||
|
<view class="tui-xAxis__tickmarks"
|
||||||
|
:style="{height:xAxisTick.height || '12rpx',background:xAxisTick.color || '#e3e3e3'}"></view>
|
||||||
|
<view class="tui-xAxis__val" :style="{color:xAxisLabel.color,fontSize:xAxisLabel.size+'rpx'}">
|
||||||
|
{{item.value}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-yAxis__line" v-for="(item,index) in yAxisData" :key="item.id"
|
||||||
|
:style="{bottom:index* (yAxisLine.itemGap || 80) +'rpx',borderBottomStyle:index===0?'solid':splitLine.type,borderBottomColor:index===0?xAxisLine.color:splitLine.color}">
|
||||||
|
<view class="tui-yAxis__tickmarks"
|
||||||
|
:style="{width:yAxisTick.width || '12rpx',background:yAxisTick.color || '#e3e3e3'}"></view>
|
||||||
|
<view class="tui-yAxis__val" :style="{color:yAxisLabel.color,fontSize:yAxisLabel.size+'rpx'}">
|
||||||
|
{{item.value}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-for="(item,index) in dataset" :key="item.id">
|
||||||
|
<view @tap.stop="onDotTap(index,idx)" class="tui-scatter__item"
|
||||||
|
:class="{'tui-scatter__item-active':activeIdx===idx && activeIndex===index && tooltipShow}"
|
||||||
|
v-for="(model,idx) in item.source" :key="idx"
|
||||||
|
:style="{width:(item.width || 12)+'rpx',height:(item.width || 12)+'rpx',background:item.color || '#5677fc',left:model.x+'rpx',bottom:model.y+'rpx'}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-scatter__tooltip" v-if="tooltip" :class="{'tui-scatter__tooltip-show':tooltipShow}">
|
||||||
|
<view class="tui-tooltip__title"></view>
|
||||||
|
<view class="tui-scatter__tooltip-item">
|
||||||
|
<view class="tui-legend__circle" :style="{background:tooltips.color}"></view>
|
||||||
|
<text class="tui-tooltip__val">{{tooltips.val}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-charts-scatter",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//图例,说明
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
size: 24,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴刻度线
|
||||||
|
xAxisTick: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
height: '12rpx',
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//x轴线条
|
||||||
|
xAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: '#e3e3e3',
|
||||||
|
itemGap: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
splitNumber: 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxisLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3',
|
||||||
|
//y轴item间距 rpx
|
||||||
|
itemGap: 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//y轴刻度线
|
||||||
|
yAxisTick: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
width: '12rpx',
|
||||||
|
//不显示则将颜色设置为transparent
|
||||||
|
color: '#e3e3e3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxisLabel: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
color: "#333",
|
||||||
|
size: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//分割线
|
||||||
|
splitLine: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {
|
||||||
|
//分割线颜色,不显示则将颜色设置为transparent
|
||||||
|
color: "#e3e3e3",
|
||||||
|
type: "dashed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
xAxisData: [],
|
||||||
|
yAxisData: [],
|
||||||
|
activeIndex: -1,
|
||||||
|
activeIdx: -1,
|
||||||
|
dataset: [],
|
||||||
|
tooltips: {},
|
||||||
|
tooltipShow: false,
|
||||||
|
timer: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateArray(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
init(dataset, xAxisValFormatter) {
|
||||||
|
let xTotal = this.xAxis.max - this.xAxis.min
|
||||||
|
let yTotal = this.yAxis.max - this.yAxis.min
|
||||||
|
let xSections = Math.ceil(xTotal / this.xAxis.splitNumber)
|
||||||
|
let ySections = Math.ceil(yTotal / this.yAxis.splitNumber)
|
||||||
|
let xSectionsArr = this.generateArray(0, xSections)
|
||||||
|
let ySectionsArr = this.generateArray(0, ySections)
|
||||||
|
this.xAxisData = xSectionsArr.map((item, index) => {
|
||||||
|
let val = item * this.xAxis.splitNumber + this.xAxis.min
|
||||||
|
val = xAxisValFormatter ? xAxisValFormatter(val) : val
|
||||||
|
return {
|
||||||
|
id: 'x_' + index,
|
||||||
|
value: val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.yAxisData = ySectionsArr.map((item, idx) => {
|
||||||
|
return {
|
||||||
|
id: 'y_' + idx,
|
||||||
|
value: item * this.yAxis.splitNumber + this.yAxis.min
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.width = (this.xAxisLine.itemGap || 100) * xSections
|
||||||
|
this.height = (this.yAxisLine.itemGap || 80) * ySections;
|
||||||
|
dataset.map((item, i) => {
|
||||||
|
item.id = 'd_' + i;
|
||||||
|
item.source = item.source.map(model => {
|
||||||
|
return {
|
||||||
|
x: (Number(model[0]) - this.xAxis.min) / xTotal * this.width,
|
||||||
|
y: (Number(model[1]) - this.yAxis.min) / yTotal * this.height,
|
||||||
|
name: model[2],
|
||||||
|
x1: model[0],
|
||||||
|
y1: model[1]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.dataset = dataset
|
||||||
|
},
|
||||||
|
draw(dataset, xAxisValFormatter) {
|
||||||
|
dataset = dataset || [];
|
||||||
|
this.init(dataset, xAxisValFormatter);
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
},
|
||||||
|
tooltipHandle(index, idx) {
|
||||||
|
let data = this.dataset[index]
|
||||||
|
let item = data.source[idx]
|
||||||
|
let tooltips = {
|
||||||
|
color: data.color,
|
||||||
|
val: item.name || `${item.x1},${item.y1}`
|
||||||
|
}
|
||||||
|
this.tooltips = tooltips;
|
||||||
|
this.clearTimer()
|
||||||
|
this.tooltipShow = true;
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.tooltipShow = false
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
onDotTap(index, idx) {
|
||||||
|
this.activeIndex = index;
|
||||||
|
this.activeIdx = idx;
|
||||||
|
this.tooltipHandle(index, idx);
|
||||||
|
this.$emit('click', {
|
||||||
|
datasetIndex: index,
|
||||||
|
sourceIndex: idx
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-charts__scatter-wrap {
|
||||||
|
position: relative;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-charts__scatter-box {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__line {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
border-left-width: 1px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__tickmarks {
|
||||||
|
width: 1px;
|
||||||
|
position: absolute;
|
||||||
|
left: -1px;
|
||||||
|
bottom: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__line {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__tickmarks {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 1px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-xAxis__val {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -16rpx;
|
||||||
|
transform: translate(-50%, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-yAxis__val {
|
||||||
|
position: absolute;
|
||||||
|
left: -16rpx;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-100%, -50%);
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-scatter__item {
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 12;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
transform: translate(-50%, 50%) scale(1);
|
||||||
|
transition: transform .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-scatter__item-active {
|
||||||
|
transform: translate(-50%, 50%) scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-scatter__legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-scatter__legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-legend__circle {
|
||||||
|
height: 20rpx;
|
||||||
|
width: 20rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-scatter__tooltip {
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
z-index: 20;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-scatter__tooltip-show {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-scatter__tooltip-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__val {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
105
src/components/thorui/tui-checkbox-group/tui-checkbox-group.vue
Normal file
105
src/components/thorui/tui-checkbox-group/tui-checkbox-group.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-TOUTIAO || MP-LARK || MP-JD || MP-360 -->
|
||||||
|
<checkbox-group :name="name">
|
||||||
|
<slot></slot>
|
||||||
|
</checkbox-group>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ -->
|
||||||
|
<tui-form-field :name="name" v-model="vals">
|
||||||
|
<slot></slot>
|
||||||
|
</tui-form-field>
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-checkbox-group",
|
||||||
|
emits: ['change', 'input', 'update:modelValue'],
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
behaviors: ['wx://form-field-group'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-BAIDU
|
||||||
|
behaviors: ['swan://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-QQ
|
||||||
|
behaviors: ['qq://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
behaviors: ['uni://form-field'],
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
vals: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue(vals) {
|
||||||
|
this.modelChange(vals)
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
value(vals) {
|
||||||
|
this.modelChange(vals)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.childrens = []
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkboxChange(e) {
|
||||||
|
this.$emit('change', e)
|
||||||
|
this.$emit('input', e.detail.value)
|
||||||
|
// #ifdef VUE3
|
||||||
|
this.$emit("update:modelValue", e.detail.value);
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
changeValue(checked, target) {
|
||||||
|
let vals = []
|
||||||
|
this.childrens.forEach(item => {
|
||||||
|
if (item.val) {
|
||||||
|
vals.push(item.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.vals = vals;
|
||||||
|
let e = {
|
||||||
|
detail: {
|
||||||
|
value: vals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.checkboxChange(e)
|
||||||
|
},
|
||||||
|
modelChange(vals) {
|
||||||
|
this.childrens.forEach(item => {
|
||||||
|
if (vals.includes(item.value)) {
|
||||||
|
item.val = true;
|
||||||
|
} else {
|
||||||
|
item.val = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
229
src/components/thorui/tui-checkbox/tui-checkbox.vue
Normal file
229
src/components/thorui/tui-checkbox/tui-checkbox.vue
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-checkbox__input" :class="{'tui-checkbox__disabled':disabled}"
|
||||||
|
:style="{backgroundColor:getBackgroundStyle(val,isCheckMark),border:getBorderStyle(val,isCheckMark),zoom:nvue?1:scaleRatio,transform:`scale(${nvue?scaleRatio:1})`}"
|
||||||
|
@tap.stop="checkboxChange">
|
||||||
|
<view class="tui-check__mark" :style="{borderBottomColor:checkMarkColor,borderRightColor:checkMarkColor}"
|
||||||
|
v-if="val"></view>
|
||||||
|
<checkbox class="tui-checkbox__hidden" style="position: absolute;opacity: 0;" hidden :color="color"
|
||||||
|
:disabled="disabled" :value="value" :checked="val">
|
||||||
|
</checkbox>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-checkbox",
|
||||||
|
emits: ['change'],
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
checked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//当设置checked 属性值时是否触发父级change方法
|
||||||
|
triggerGroup: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//checkbox选中背景颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//checkbox未选中时边框颜色
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#ccc'
|
||||||
|
},
|
||||||
|
//是否只展示对号,无边框背景
|
||||||
|
isCheckMark: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//对号颜色
|
||||||
|
checkMarkColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
scaleRatio: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.unInstall()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.unInstall()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
created() {
|
||||||
|
this.val = this.checked;
|
||||||
|
this.group = this.getParent()
|
||||||
|
if (this.group) {
|
||||||
|
this.group.childrens.push(this);
|
||||||
|
if (this.group.value && this.group.value.length > 0) {
|
||||||
|
this.val = this.group.value.includes(this.value)
|
||||||
|
}
|
||||||
|
// #ifdef VUE3
|
||||||
|
if (this.group.modelValue && this.group.modelValue.length > 0) {
|
||||||
|
this.val = this.group.modelValue.includes(this.value)
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
this.label = this.getParent('tui-label')
|
||||||
|
if (this.label) {
|
||||||
|
this.label.childrens.push(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
checked(newVal) {
|
||||||
|
this.val = newVal;
|
||||||
|
},
|
||||||
|
val(newVal) {
|
||||||
|
if (this.triggerGroup && this.group) {
|
||||||
|
this.group.changeValue(this.val, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let nvue = false;
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
nvue = true;
|
||||||
|
// #endif
|
||||||
|
return {
|
||||||
|
val: false,
|
||||||
|
nvue: nvue
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getBackgroundStyle(val, isCheckMark) {
|
||||||
|
const primary = (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
let color = val ? (this.color || primary) : '#fff'
|
||||||
|
if (isCheckMark) {
|
||||||
|
color = 'transparent'
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
},
|
||||||
|
getBorderStyle(val, isCheckMark) {
|
||||||
|
const primary = (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
let color = val ? (this.color || primary) : this.borderColor;
|
||||||
|
if (isCheckMark) {
|
||||||
|
color = 'transparent'
|
||||||
|
}
|
||||||
|
return `1px solid ${color}`;
|
||||||
|
},
|
||||||
|
checkboxChange(e) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.val = !this.val;
|
||||||
|
if (!this.triggerGroup && this.group) {
|
||||||
|
this.group.changeValue(this.val, this);
|
||||||
|
}
|
||||||
|
this.$emit('change', {
|
||||||
|
checked: this.val,
|
||||||
|
value: this.value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getParent(name = 'tui-checkbox-group') {
|
||||||
|
let parent = this.$parent;
|
||||||
|
let parentName = parent.$options.name;
|
||||||
|
while (parentName !== name) {
|
||||||
|
parent = parent.$parent;
|
||||||
|
if (!parent) return false;
|
||||||
|
parentName = parent.$options.name;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
},
|
||||||
|
labelClick() {
|
||||||
|
this.checkboxChange()
|
||||||
|
},
|
||||||
|
unInstall() {
|
||||||
|
if (this.group) {
|
||||||
|
this.group.childrens.forEach((item, index) => {
|
||||||
|
if (item === this) {
|
||||||
|
this.group.childrens.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-checkbox__input {
|
||||||
|
font-size: 0;
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
border-radius: 40rpx;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: inline-flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 50%;
|
||||||
|
vertical-align: top;
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-check__mark {
|
||||||
|
width: 20rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-width: 3px;
|
||||||
|
border-bottom-color: #FFFFFF;
|
||||||
|
border-right-style: solid;
|
||||||
|
border-right-width: 3px;
|
||||||
|
border-right-color: #FFFFFF;
|
||||||
|
transform: rotate(45deg) scale(0.5);
|
||||||
|
transform-origin: 54% 48%;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-checkbox__hidden {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0 none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
pointer-events: none;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
width: 100wx;
|
||||||
|
height: 100wx;
|
||||||
|
border-width: 0;
|
||||||
|
/* #endif */
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-checkbox__disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,309 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
|
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
|
||||||
|
:style="{ width: diam + 'px', height: (height || diam) + 'px' }"
|
||||||
|
v-if="defaultShow && defaultCanvasId"></canvas>
|
||||||
|
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
|
||||||
|
:style="{ width: diam + 'px', height: (height || diam) + 'px' }" v-if="progressCanvasId"></canvas>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
|
||||||
|
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }" v-if="defaultShow"></canvas>
|
||||||
|
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
|
||||||
|
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }"></canvas>
|
||||||
|
<!-- #endif -->
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiCircularProgress',
|
||||||
|
emits: ['change', 'end'],
|
||||||
|
props: {
|
||||||
|
/*
|
||||||
|
传值需使用rpx进行转换保证各终端兼容
|
||||||
|
px = rpx / 750 * wx.getSystemInfoSync().windowWidth
|
||||||
|
圆形进度条(画布)宽度,直径 [px]
|
||||||
|
*/
|
||||||
|
diam: {
|
||||||
|
type: Number,
|
||||||
|
default: 60
|
||||||
|
},
|
||||||
|
//圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//进度条线条宽度[px]
|
||||||
|
lineWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 4
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
线条的端点样式
|
||||||
|
butt:向线条的每个末端添加平直的边缘
|
||||||
|
round 向线条的每个末端添加圆形线帽
|
||||||
|
square 向线条的每个末端添加正方形线帽
|
||||||
|
*/
|
||||||
|
lineCap: {
|
||||||
|
type: String,
|
||||||
|
default: 'round'
|
||||||
|
},
|
||||||
|
//圆环进度字体大小 [px]
|
||||||
|
fontSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 12
|
||||||
|
},
|
||||||
|
//圆环进度字体颜色
|
||||||
|
fontColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//是否显示进度文字
|
||||||
|
fontShow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
|
||||||
|
可以使用 slot自定义显示内容
|
||||||
|
*/
|
||||||
|
percentText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//是否显示默认(背景)进度条
|
||||||
|
defaultShow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//默认进度条颜色
|
||||||
|
defaultColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#CCCCCC'
|
||||||
|
},
|
||||||
|
//进度条颜色
|
||||||
|
progressColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//进度条渐变颜色[结合progressColor使用,默认为空]
|
||||||
|
gradualColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//起始弧度,单位弧度
|
||||||
|
sAngle: {
|
||||||
|
type: Number,
|
||||||
|
default: -Math.PI / 2
|
||||||
|
},
|
||||||
|
//指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
|
||||||
|
counterclockwise: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//进度百分比 [10% 传值 10]
|
||||||
|
percentage: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//进度百分比缩放倍数[使用半弧为100%时,则可传2]
|
||||||
|
multiple: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//动画执行时间[单位毫秒,低于50无动画]
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 800
|
||||||
|
},
|
||||||
|
//backwards: 动画从头播;forwards:动画从上次结束点接着播
|
||||||
|
activeMode: {
|
||||||
|
type: String,
|
||||||
|
default: 'backwards'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
percentage(val) {
|
||||||
|
this.initDraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
// #ifndef MP-WEIXIN || MP-QQ
|
||||||
|
let cid = `id01_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||||
|
let did = `id02_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||||
|
// #endif
|
||||||
|
return {
|
||||||
|
// #ifdef MP-WEIXIN || MP-QQ
|
||||||
|
progressCanvasId: 'progressCanvasId',
|
||||||
|
defaultCanvasId: 'defaultCanvasId',
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN || MP-QQ
|
||||||
|
progressCanvasId: cid,
|
||||||
|
defaultCanvasId: did,
|
||||||
|
// #endif
|
||||||
|
progressContext: null,
|
||||||
|
linearGradient: null,
|
||||||
|
//起始百分比
|
||||||
|
startPercentage: 0
|
||||||
|
// dpi
|
||||||
|
//pixelRatio: uni.getSystemInfoSync().pixelRatio
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initDraw(true);
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
//初始化绘制
|
||||||
|
initDraw(init) {
|
||||||
|
let start = this.activeMode === 'backwards' ? 0 : this.startPercentage;
|
||||||
|
start = start > this.percentage ? 0 : start;
|
||||||
|
if (this.defaultShow && init) {
|
||||||
|
this.drawDefaultCircular();
|
||||||
|
}
|
||||||
|
this.drawProgressCircular(start);
|
||||||
|
},
|
||||||
|
//默认(背景)圆环
|
||||||
|
drawDefaultCircular() {
|
||||||
|
let ctx = uni.createCanvasContext(this.defaultCanvasId, this);
|
||||||
|
let lineWidth = Number(this.lineWidth)
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
lineWidth = lineWidth * 4
|
||||||
|
// #endif
|
||||||
|
ctx.setLineWidth(lineWidth);
|
||||||
|
ctx.setStrokeStyle(this.defaultColor);
|
||||||
|
//终止弧度
|
||||||
|
let eAngle = Math.PI * (this.height ? 1 : 2) + this.sAngle;
|
||||||
|
this.drawArc(ctx, eAngle);
|
||||||
|
},
|
||||||
|
//进度圆环
|
||||||
|
drawProgressCircular(startPercentage) {
|
||||||
|
let ctx = this.progressContext;
|
||||||
|
let gradient = this.linearGradient;
|
||||||
|
if (!ctx) {
|
||||||
|
ctx = uni.createCanvasContext(this.progressCanvasId, this);
|
||||||
|
//创建一个线性的渐变颜色 CanvasGradient对象
|
||||||
|
let diam = Number(this.diam)
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
diam = diam * 4
|
||||||
|
// #endif
|
||||||
|
const progressColor = this.progressColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
gradient = ctx.createLinearGradient(0, 0, diam, 0);
|
||||||
|
gradient.addColorStop('0', progressColor);
|
||||||
|
if (this.gradualColor) {
|
||||||
|
gradient.addColorStop('1', this.gradualColor);
|
||||||
|
}
|
||||||
|
// #ifdef APP-PLUS || MP
|
||||||
|
const res = uni.getSystemInfoSync();
|
||||||
|
if (!this.gradualColor && res.platform.toLocaleLowerCase() == 'android') {
|
||||||
|
gradient.addColorStop('1', progressColor);
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
this.progressContext = ctx;
|
||||||
|
this.linearGradient = gradient;
|
||||||
|
}
|
||||||
|
let lineWidth = Number(this.lineWidth)
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
lineWidth = lineWidth * 4
|
||||||
|
// #endif
|
||||||
|
ctx.setLineWidth(lineWidth);
|
||||||
|
ctx.setStrokeStyle(gradient);
|
||||||
|
let time = this.percentage == 0 || this.duration < 50 ? 0 : this.duration / this.percentage;
|
||||||
|
if (this.percentage > 0) {
|
||||||
|
startPercentage = this.duration < 50 ? this.percentage - 1 : startPercentage;
|
||||||
|
startPercentage++;
|
||||||
|
}
|
||||||
|
if (this.fontShow) {
|
||||||
|
let fontSize = Number(this.fontSize)
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
fontSize = fontSize * 4
|
||||||
|
// #endif
|
||||||
|
ctx.setFontSize(fontSize);
|
||||||
|
const fontColor = this.fontColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
ctx.setFillStyle(fontColor);
|
||||||
|
ctx.setTextAlign('center');
|
||||||
|
ctx.setTextBaseline('middle');
|
||||||
|
let percentage = this.percentText;
|
||||||
|
if (!percentage) {
|
||||||
|
percentage = this.counterclockwise ? 100 - startPercentage * this.multiple : startPercentage * this
|
||||||
|
.multiple;
|
||||||
|
percentage = `${percentage}%`;
|
||||||
|
}
|
||||||
|
let radius = this.diam / 2;
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
radius = radius * 4
|
||||||
|
// #endif
|
||||||
|
ctx.fillText(percentage, radius, radius);
|
||||||
|
}
|
||||||
|
if (this.percentage == 0 || (this.counterclockwise && startPercentage == 100)) {
|
||||||
|
ctx.draw();
|
||||||
|
} else {
|
||||||
|
let eAngle = ((2 * Math.PI) / 100) * startPercentage + this.sAngle;
|
||||||
|
this.drawArc(ctx, eAngle);
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.startPercentage = startPercentage;
|
||||||
|
if (startPercentage >= this.percentage) {
|
||||||
|
this.$emit('end', {
|
||||||
|
canvasId: this.progressCanvasId,
|
||||||
|
percentage: startPercentage
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.drawProgressCircular(startPercentage);
|
||||||
|
}
|
||||||
|
this.$emit('change', {
|
||||||
|
percentage: startPercentage
|
||||||
|
});
|
||||||
|
}, time);
|
||||||
|
// #ifdef H5
|
||||||
|
// requestAnimationFrame(()=>{})
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
//创建弧线
|
||||||
|
drawArc(ctx, eAngle) {
|
||||||
|
ctx.setLineCap(this.lineCap);
|
||||||
|
ctx.beginPath();
|
||||||
|
let radius = this.diam / 2; //x=y
|
||||||
|
let lineWidth = Number(this.lineWidth)
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
radius = radius * 4
|
||||||
|
lineWidth = lineWidth * 4
|
||||||
|
// #endif
|
||||||
|
ctx.arc(radius, radius, radius - lineWidth, this.sAngle, eAngle, this.counterclockwise);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-circular-container,
|
||||||
|
.tui-circular-default {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #ifdef MP-ALIPAY */
|
||||||
|
.tui-circular-default,
|
||||||
|
.tui-circular-progress {
|
||||||
|
zoom: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
|
||||||
|
.tui-circular-progress {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
389
src/components/thorui/tui-code-input/tui-code-input.vue
Normal file
389
src/components/thorui/tui-code-input/tui-code-input.vue
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-code__input" :style="{marginTop:marginTop+'rpx',marginBottom:marginBottom+'rpx'}" @tap="onClick">
|
||||||
|
<view class="tui-code__input" :style="{paddingLeft:gap+'rpx',paddingRight:gap+'rpx'}">
|
||||||
|
<view class="tui-cinput__item"
|
||||||
|
:style="{width:width+'rpx',height:height+'rpx',background:background,borderRadius:radius+'rpx',borderColor:activeIndex===index || inputVal[index]?getActiveColor:borderColor,borderTopWidth:(borderType==1?borderWidth:0)+'rpx',borderLeftWidth:(borderType==1?borderWidth:0)+'rpx',borderRightWidth:(borderType==1?borderWidth:0)+'rpx',borderBottomWidth:(borderType==1 || borderType==2?borderWidth:0)+'rpx'}"
|
||||||
|
@tap="onTap" v-for="(item,index) in inputArr" :key="index">
|
||||||
|
<text class="tui-cinput__text"
|
||||||
|
:style="{width:width+'rpx',height:height+'rpx',fontSize:size+'rpx',lineHeight:height+'rpx',color:color,fontWeight:fontWeight}">{{password?(inputVal[index] ? '●':''):(inputVal[index] || '')}}</text>
|
||||||
|
<text class="tui-cinput__placeholder"
|
||||||
|
:style="{fontSize:size+'rpx',fontWeight:fontWeight}">{{password?(inputVal[index] ? '●':''):(inputVal[index] || '')}}</text>
|
||||||
|
<view class="tui-cinput__cursor" :class="{'tui-cinput__cursor-ani':activeIndex===index && focus}"
|
||||||
|
v-if="cursor && !disabled" :style="{height:cursorHeight+'rpx',background:getCursorColor}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<input :value="val" :password="password" :type="type" class="tui-cinput__hidden"
|
||||||
|
:class="{'tui-cinput__ali':ali}" @input="onInput" @blur="onBlur" :focus="focus" :maxlength="length"
|
||||||
|
:disabled="disabled" @confirm="onConfirm" @focus="onTap" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiCodeInput",
|
||||||
|
emits: ['complete', 'focus', 'input', 'blur', 'confirm'],
|
||||||
|
props: {
|
||||||
|
//组件外层左右间距
|
||||||
|
gap: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 80
|
||||||
|
},
|
||||||
|
marginTop: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
marginBottom: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//初始值,不可超过length长度
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//H5不支持动态切换type类型
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//获取焦点
|
||||||
|
isFocus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
cursorColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
cursorHeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 60
|
||||||
|
},
|
||||||
|
//输入框个数
|
||||||
|
length: {
|
||||||
|
type: Number,
|
||||||
|
default: 4
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 108
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 108
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//1-显示所有边框 2-只显示底部边框,3-无边框
|
||||||
|
borderType: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#eaeef1'
|
||||||
|
},
|
||||||
|
activeColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
borderWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 48
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
fontWeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
getCursorColor(){
|
||||||
|
return this.cursorColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
},
|
||||||
|
getActiveColor(){
|
||||||
|
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputArr: [],
|
||||||
|
inputVal: [],
|
||||||
|
focus: false,
|
||||||
|
activeIndex: -1,
|
||||||
|
ali: false,
|
||||||
|
val: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
length(val) {
|
||||||
|
const nums = Number(val);
|
||||||
|
if (nums !== this.inputArr.length) {
|
||||||
|
this.inputArr = this.getArr(nums)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value(val) {
|
||||||
|
this.focus = true;
|
||||||
|
val = val.replace(/\s+/g, "")
|
||||||
|
this.getVals(val)
|
||||||
|
},
|
||||||
|
isFocus(val) {
|
||||||
|
this.initFocus(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.inputArr = this.getArr(Number(this.length))
|
||||||
|
let val = this.value.replace(/\s+/g, "")
|
||||||
|
this.getVals(val, true)
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(()=>{
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initFocus(this.isFocus)
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initFocus(val) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
if (val && this.activeIndex === -1) {
|
||||||
|
this.activeIndex = 0
|
||||||
|
}
|
||||||
|
if (!this.value && !val) {
|
||||||
|
this.activeIndex = -1
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.focus = val
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getArr(end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(1);
|
||||||
|
},
|
||||||
|
getVals(val, init = false) {
|
||||||
|
this.val = val
|
||||||
|
if (!val) {
|
||||||
|
this.inputVal = []
|
||||||
|
this.activeIndex = init ? -1 : 0;
|
||||||
|
} else {
|
||||||
|
let vals = val.split('')
|
||||||
|
let arr = []
|
||||||
|
this.inputArr.forEach((item, index) => {
|
||||||
|
arr.push(vals[index] || '')
|
||||||
|
})
|
||||||
|
this.inputVal = arr
|
||||||
|
const len = vals.length;
|
||||||
|
this.activeIndex = len > this.length ? this.length : len;
|
||||||
|
if (len === this.length) {
|
||||||
|
this.$emit('complete', {
|
||||||
|
detail: {
|
||||||
|
value: val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.focus = false;
|
||||||
|
uni.hideKeyboard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.focus = true;
|
||||||
|
if (this.activeIndex === -1) {
|
||||||
|
this.activeIndex = 0
|
||||||
|
}
|
||||||
|
if (this.activeIndex === this.length) {
|
||||||
|
this.activeIndex--;
|
||||||
|
}
|
||||||
|
this.$emit('focus', {})
|
||||||
|
},
|
||||||
|
onInput(e) {
|
||||||
|
let value = e.detail.value;
|
||||||
|
value = value.replace(/\s+/g, "")
|
||||||
|
this.getVals(value)
|
||||||
|
this.$emit('input', {
|
||||||
|
detail: {
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onBlur(e) {
|
||||||
|
let value = e.detail.value;
|
||||||
|
value = value.replace(/\s+/g, "")
|
||||||
|
this.focus = false
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
this.ali = false
|
||||||
|
// #endif
|
||||||
|
if (!value) {
|
||||||
|
this.activeIndex = -1;
|
||||||
|
}
|
||||||
|
this.$emit('blur', {
|
||||||
|
detail: {
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onConfirm(e) {
|
||||||
|
this.focus = false;
|
||||||
|
uni.hideKeyboard()
|
||||||
|
this.$emit('confirm', e)
|
||||||
|
},
|
||||||
|
onClick() {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
setTimeout(() => {
|
||||||
|
this.ali = true
|
||||||
|
}, 50)
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.val = ''
|
||||||
|
this.inputVal = []
|
||||||
|
this.activeIndex = -1;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.onTap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-code__input {
|
||||||
|
position: relative;
|
||||||
|
/* #ifdef MP-BAIDU */
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-code__input {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-cinput__item {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-style: solid;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cinput__text {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cinput__placeholder {
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cinput__cursor {
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cinput__cursor-ani {
|
||||||
|
width: 2px;
|
||||||
|
animation: ani_cursor 1s infinite steps(1, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ani_cursor {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cinput__hidden {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
/* #ifndef MP */
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef MP-WEIXIN || MP-QQ */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* #endif */
|
||||||
|
z-index: 2;
|
||||||
|
/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
border: none;
|
||||||
|
/* #endif */
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
opacity: 0;
|
||||||
|
/* #ifdef MP-BAIDU || MP-TOUTIAO */
|
||||||
|
font-size: 0;
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
/* #ifdef MP-BAIDU */
|
||||||
|
transform: scaleX(2);
|
||||||
|
transform-origin: 100% center;
|
||||||
|
/* #endif */
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #ifdef MP-ALIPAY */
|
||||||
|
.tui-cinput__ali {
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
</style>
|
||||||
3215
src/components/thorui/tui-col/tui-col.vue
Normal file
3215
src/components/thorui/tui-col/tui-col.vue
Normal file
File diff suppressed because it is too large
Load Diff
167
src/components/thorui/tui-collapse/tui-collapse.vue
Normal file
167
src/components/thorui/tui-collapse/tui-collapse.vue
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-collapse" :style="{backgroundColor:bgColor}">
|
||||||
|
<view class="tui-collapse-head" :style="{backgroundColor:hdBgColor}" @tap.stop="handleClick">
|
||||||
|
<view class="tui-header" :class="{'tui-opacity':disabled}">
|
||||||
|
<slot name="title"></slot>
|
||||||
|
<view class="tui-collapse-icon tui-icon-arrow" :class="{'tui-icon-active':isOpen}" :style="{color:arrowColor}" v-if="arrow"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-collapse-body_box" :style="{backgroundColor:bdBgColor,height:isOpen?height:'0rpx'}">
|
||||||
|
<view class="tui-collapse-body" :class="{'tui-collapse-transform':height=='auto','tui-collapse-body_show':isOpen && height=='auto'}">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiCollapse",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//collapse背景颜色
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//collapse-head 背景颜色
|
||||||
|
hdBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//collapse-body 背景颜色
|
||||||
|
bdBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//collapse-body实际高度 open时使用
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
//索引
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//当前索引,index==current时展开
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否带箭头
|
||||||
|
arrow: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//箭头颜色
|
||||||
|
arrowColor: {
|
||||||
|
type: String,
|
||||||
|
default: "#333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
current() {
|
||||||
|
this.updateCurrentChange()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.updateCurrentChange()
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isOpen: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateCurrentChange() {
|
||||||
|
this.isOpen = this.index == this.current
|
||||||
|
},
|
||||||
|
handleClick() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit("click", {
|
||||||
|
index: Number(this.index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'tuiCollapse';
|
||||||
|
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAQ4AA0AAAAABlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEHAAAABoAAAAciRx3B0dERUYAAAP8AAAAHgAAAB4AKQAKT1MvMgAAAaAAAABCAAAAVjxuR/JjbWFwAAAB9AAAAD4AAAFCAA/pq2dhc3AAAAP0AAAACAAAAAj//wADZ2x5ZgAAAkAAAABEAAAARCs1U/toZWFkAAABMAAAADAAAAA2FpaT+mhoZWEAAAFgAAAAHQAAACQHngOFaG10eAAAAeQAAAAPAAAAEAwAAEBsb2NhAAACNAAAAAoAAAAKACIAAG1heHAAAAGAAAAAHwAAACABDwAdbmFtZQAAAoQAAAFJAAACiCnmEVVwb3N0AAAD0AAAACMAAAA1DunpUnjaY2BkYGAAYja/oO54fpuvDNwsDCBwc4/6fzjtwNDNfICpBMjlYGACiQIAGVAKZnjaY2BkYGBu+N/AEMPCAALMBxgYGVABCwBVNgMsAAAAeNpjYGRgYGBhEGQA0QwMTEDMBYQMDP/BfAYACnYBLQB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ4xMDf8b2CIYW5gaAAKM4LkANq9C9sAAHjaY2GAABYIdgAAAMAATQB42mNgYGBmgGAZBkYGELAB8hjBfBYGBSDNAoRA/jOG//8hpBQzVCUDIxsDjMnAyAQkmBhQASPDsAcAMCAGoQAAAAAAAAAAAAAAIgAAAAEAQACLA8ACdAAQAAAlASYiBhQXARYyNwE2NCYiBwIA/oYNIBkMAZcNIA0BlwwZIA3uAXoMGSAN/mkMDAGXDSAZDAB42n2QPU4DMRCFn/MHJBJCIKhdUQDa/JQpEyn0CKWjSDbekGjXXnmdSDkBLRUHoOUYHIAbINFyCl6WSZMia+3o85uZ57EBnOMbCv/fJe6EFY7xKFzBETLhKvUX4Rr5XbiOFj6FG9R/hJu4VQPhFi7UGx1U7YS7m9JtywpnGAhXcIon4Sr1lXCN/CpcxxU+hBvUv4SbGONXuIVrZakM4WEwQWCcQWOKDeMCMRwskjIG1qE59GYSzExPN3oRO5s4GyjvV2KXAx5oOeeAKe09t2a+Sif+YMuB1JhuHgVLtimNLiJ0KBtfLJzV3ahzsP2e7ba02L9rgTXH7FENbNT8Pdsz0khsDK+QkjXyMrekElOPaGus8btnKdbzXgiJTrzL9IjHmjR1OvduaeLA4ufyjBx9tLmSPfeoHD5jWQh5v91OxCCKXYY/k9hxGQAAAHjaY2BigAAuMMnIgA5YwKJMjExciUVF+eW6KfnleQAZ0wQyAAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAMAAQAEAAAAAgAAAAB42mNgYGBkAIKrS9Q5QPTNPer/YTQAQ+0HIAAA) format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-collapse-icon {
|
||||||
|
font-family: "tuiCollapse" !important;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon-arrow:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon-arrow {
|
||||||
|
font-size: 32rpx;
|
||||||
|
transform: rotate(0);
|
||||||
|
transform-origin: center center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -8px;
|
||||||
|
right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-arrow-padding {
|
||||||
|
padding-right: 62rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon-active {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-header {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.tui-collapse-body_box{
|
||||||
|
transition: all 0.25s;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.tui-collapse-body {
|
||||||
|
transition: all 0.25s;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-collapse-transform {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
-webkit-transform: translateY(-40%);
|
||||||
|
transform: translateY(-40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-collapse-body_show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
-webkit-transform: translateY(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-opacity {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
129
src/components/thorui/tui-config/index.js
Normal file
129
src/components/thorui/tui-config/index.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
基础组件props属性全局配置文件。优先级:全局配置文件props < 单独设置组件props
|
||||||
|
温馨提示:未设置则使用组件内默认值,避免出错,请勿删减以下配置
|
||||||
|
组件属性介绍请查看文档
|
||||||
|
*/
|
||||||
|
|
||||||
|
//组件内主色配置
|
||||||
|
const color = {
|
||||||
|
primary: '#5677fc',
|
||||||
|
success: '#07c160',
|
||||||
|
warning: '#ff7900',
|
||||||
|
danger: '#EB0909',
|
||||||
|
pink: '#f74d54',
|
||||||
|
blue: '#007AFF',
|
||||||
|
link: '#586c94'
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsConfig = {
|
||||||
|
//组件内主色配置
|
||||||
|
color,
|
||||||
|
//组件名称,字体图标组件 tui-icon
|
||||||
|
tuiIcon: {
|
||||||
|
//组件属性值
|
||||||
|
size: 32,
|
||||||
|
unit: 'px',
|
||||||
|
color: '#999'
|
||||||
|
},
|
||||||
|
//按钮组件 tui-button
|
||||||
|
tuiButton: {
|
||||||
|
height: '96rpx',
|
||||||
|
size: 32
|
||||||
|
},
|
||||||
|
//列表项组件 tui-list-cell
|
||||||
|
tuiListCell: {
|
||||||
|
arrowColor: '#c0c0c0',
|
||||||
|
lineColor: '#eaeef1',
|
||||||
|
lineLeft: 30,
|
||||||
|
padding: '26rpx 44rpx',
|
||||||
|
color: '#333',
|
||||||
|
size: 28
|
||||||
|
},
|
||||||
|
//按钮组件 tui-form-button
|
||||||
|
tuiFormButton: {
|
||||||
|
background: color.primary,
|
||||||
|
color: '#fff',
|
||||||
|
height: '96rpx',
|
||||||
|
size: 32,
|
||||||
|
radius: '6rpx'
|
||||||
|
},
|
||||||
|
//文本组件 tui-text
|
||||||
|
tuiText: {
|
||||||
|
size: 32,
|
||||||
|
unit: 'rpx',
|
||||||
|
color: ''
|
||||||
|
},
|
||||||
|
//输入框组件 tui-input
|
||||||
|
tuiInput: {
|
||||||
|
requiredColor: color.danger,
|
||||||
|
labelSize: 32,
|
||||||
|
labelColor: '#333',
|
||||||
|
size: 32,
|
||||||
|
color: '#333',
|
||||||
|
padding: '26rpx 30rpx',
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
radius: 0
|
||||||
|
},
|
||||||
|
//表单项组件 tui-form-item
|
||||||
|
tuiFormItem: {
|
||||||
|
padding: '28rpx 30rpx',
|
||||||
|
labelSize: 32,
|
||||||
|
labelColor: '#333',
|
||||||
|
labelFontWeight: 400,
|
||||||
|
asteriskColor: color.danger,
|
||||||
|
background: '#fff',
|
||||||
|
arrowColor: '#c0c0c0',
|
||||||
|
borderColor: '#eaeef1',
|
||||||
|
radius: '0rpx',
|
||||||
|
position: 2
|
||||||
|
},
|
||||||
|
//表单校验组件 tui-form
|
||||||
|
tuiForm: {
|
||||||
|
tipBackgroundColor: color.pink,
|
||||||
|
duration: 2000
|
||||||
|
},
|
||||||
|
//全局方法,调用 uni.$tui.toast
|
||||||
|
toast(text, duration, success) {
|
||||||
|
uni.showToast({
|
||||||
|
// #ifndef MP-ALIPAY
|
||||||
|
duration: duration || 2000,
|
||||||
|
// #endif
|
||||||
|
title: text || "出错啦~",
|
||||||
|
icon: success ? 'success' : 'none'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
modal(title, content, showCancel, callback, confirmColor, confirmText) {
|
||||||
|
uni.showModal({
|
||||||
|
title: title || '提示',
|
||||||
|
content: content,
|
||||||
|
showCancel: showCancel,
|
||||||
|
cancelColor: "#555",
|
||||||
|
confirmColor: confirmColor || color.primary,
|
||||||
|
confirmText: confirmText || "确定",
|
||||||
|
success(res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
callback && callback(true)
|
||||||
|
} else {
|
||||||
|
callback && callback(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
//跳转页面
|
||||||
|
href(url, isMain) {
|
||||||
|
if (isMain) {
|
||||||
|
uni.switchTab({
|
||||||
|
url: url
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: url
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rpx2px(value) {
|
||||||
|
return uni.upx2px(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default propsConfig
|
||||||
279
src/components/thorui/tui-copy-text/tui-copy-text.vue
Normal file
279
src/components/thorui/tui-copy-text/tui-copy-text.vue
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-copy__box">
|
||||||
|
<!-- #ifndef MP-ALIPAY -->
|
||||||
|
<text :selectable="systemCopy" class="tui-ccpy__text" @longpress.stop="handleCopy" @mousedown="handleCopyByPC"
|
||||||
|
@touchstart="handlePC"
|
||||||
|
:style="{ color: color, fontSize: size + 'rpx', fontWeight: bold ? 'bold' : 'normal', backgroundColor: showToolTip ? backgroundColor : 'transparent' }">
|
||||||
|
{{ value }}
|
||||||
|
</text>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<view :selectable="systemCopy" class="tui-ccpy__text" @longpress.stop="handleCopy" @touchstart="handlePC"
|
||||||
|
:style="{ color: color, fontSize: size + 'rpx', fontWeight: bold ? 'bold' : 'normal', backgroundColor: showToolTip ? backgroundColor : 'transparent' }">
|
||||||
|
{{ value }}
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<view v-if="showToolTip && !systemCopy" class="tui-tooltip__list"
|
||||||
|
:class="['tui-tooltip__' + direction, (direction == 'left' || direction == 'right') && buttons.length > 0 ? 'tui-tooltip__column' : '']"
|
||||||
|
:style="{ zIndex: zIndex }">
|
||||||
|
<view v-if="!removeCopy || buttons.length === 0" class="tui-tooltip__cell"
|
||||||
|
:class="[(direction == 'left' || direction == 'right') && buttons.length > 0 ? 'tui-tooltip__cell-column' : '']"
|
||||||
|
@tap.stop="copy">
|
||||||
|
复制
|
||||||
|
</view>
|
||||||
|
<view class="tui-tooltip__cell tui-tooltip__extend"
|
||||||
|
:class="[direction == 'left' || direction == 'right' ? 'tui-tooltip__cell-column' : '']"
|
||||||
|
v-for="(item, index) in buttons" :key="index" :data-index="index" @tap.stop="buttonTap">
|
||||||
|
{{ item }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import thorui from '@/components/common/tui-clipboard/tui-clipboard.js'
|
||||||
|
export default {
|
||||||
|
name: 'tuiCopyText',
|
||||||
|
emits: ['click', 'copy'],
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: 'copy text'
|
||||||
|
},
|
||||||
|
//需要复制的文本,默认空,则默认复制的值为value
|
||||||
|
copyValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//字体大小
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 28
|
||||||
|
},
|
||||||
|
//是否加粗
|
||||||
|
bold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//选中后背景色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//是否显示复制tooltip,为false时,长按直接复制(无扩展buttons时生效)
|
||||||
|
showCopyBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//复制按钮显示方向:top,left,right,bottom
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'top'
|
||||||
|
},
|
||||||
|
//复制按钮z-index
|
||||||
|
zIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 997
|
||||||
|
},
|
||||||
|
//使用系统长按操作,扩展按钮失效
|
||||||
|
systemCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否移除复制按钮(当只需要扩展按钮时使用且有扩展按钮时生效)
|
||||||
|
removeCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//扩展按钮(1~3个左右)
|
||||||
|
buttons: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showToolTip: false,
|
||||||
|
isCopy: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handlePC(e) {
|
||||||
|
this.isCopy = false;
|
||||||
|
},
|
||||||
|
handleCopyByPC(e) {
|
||||||
|
if (this.isCopy) {
|
||||||
|
this.handleCopy(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCopy(e) {
|
||||||
|
if (this.systemCopy) return;
|
||||||
|
if (this.showCopyBtn || this.buttons.length > 0) {
|
||||||
|
this.showToolTip = true;
|
||||||
|
} else {
|
||||||
|
this.copy(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copy(e) {
|
||||||
|
thorui.getClipboardData(
|
||||||
|
this.copyValue || this.value,
|
||||||
|
res => {
|
||||||
|
if (res) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '复制成功',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
this.$emit('copy', {
|
||||||
|
value: this.value,
|
||||||
|
copyValue: this.copyValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
e
|
||||||
|
);
|
||||||
|
this.cancel();
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.showToolTip = false;
|
||||||
|
},
|
||||||
|
buttonTap(e) {
|
||||||
|
let index = Number(e.currentTarget.dataset.index);
|
||||||
|
this.$emit('click', {
|
||||||
|
index: index,
|
||||||
|
value: this.value,
|
||||||
|
copyValue: this.copyValue
|
||||||
|
});
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-copy__box {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
font-weight: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-ccpy__text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__list {
|
||||||
|
font-size: 14px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #060607;
|
||||||
|
padding: 5px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__column {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__cell {
|
||||||
|
min-width: 60px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__extend {
|
||||||
|
border-left: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__cell-column {
|
||||||
|
padding: 5px !important;
|
||||||
|
border-left: 0 !important;
|
||||||
|
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__cell:last-child {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__top {
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__top::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #060607 transparent transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__bottom {
|
||||||
|
left: 50%;
|
||||||
|
top: 100%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__bottom::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent transparent #060607 transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__left {
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-100%, -50%);
|
||||||
|
margin-left: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__left::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 100%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent transparent transparent #060607;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__right {
|
||||||
|
left: 100%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-tooltip__right::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(-100%, -50%);
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent #060607 transparent transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,238 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-countdown__verify" :class="{ 'tui-verify__opacity': status > 1 && isOpacity }"
|
||||||
|
:style="{ width: width, height: height, padding: padding, margin: margin, borderRadius: radius, fontSize: size + 'rpx', color: getColor, background: background }"
|
||||||
|
:hover-class="hover && status == 1 ? 'tui-verify__opacity' : ''" :hover-stay-time="150" @tap.stop="sendCode">
|
||||||
|
{{ showText }}
|
||||||
|
<view class="tui-verify__line"
|
||||||
|
:style="{ borderWidth: borderWidth, borderColor: getBorderColor, borderRadius: radius }"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiCountdownVerify',
|
||||||
|
emits: ['send', 'countdown', 'end'],
|
||||||
|
props: {
|
||||||
|
//发送前显示文本
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: '发送验证码'
|
||||||
|
},
|
||||||
|
//发送中显示文本
|
||||||
|
sendText: {
|
||||||
|
type: String,
|
||||||
|
default: '请稍候...'
|
||||||
|
},
|
||||||
|
//发送后显示文本(前面会自动加上seconds)
|
||||||
|
countdownText: {
|
||||||
|
type: String,
|
||||||
|
default: 's后获取'
|
||||||
|
},
|
||||||
|
//倒计时秒数
|
||||||
|
seconds: {
|
||||||
|
type: Number,
|
||||||
|
default: 60
|
||||||
|
},
|
||||||
|
//宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '182rpx'
|
||||||
|
},
|
||||||
|
//高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '56rpx'
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//圆角
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '6rpx'
|
||||||
|
},
|
||||||
|
//字体大小 rpx
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
//字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//背景色
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//边框宽度
|
||||||
|
borderWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '1px'
|
||||||
|
},
|
||||||
|
//边框颜色
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//倒计时的时候是否改变opacity值
|
||||||
|
isOpacity: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否需要点击效果
|
||||||
|
hover: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//短信发送成功(改变数值且数值大于0表示发送成功,多次发送数值递增即可)
|
||||||
|
successVal: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//重置组件状态(改变数值且数值大于0,多次重置数值递增即可)
|
||||||
|
resetVal: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//是否默认为倒计时状态
|
||||||
|
start: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//自定义参数
|
||||||
|
params: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getColor() {
|
||||||
|
return this.color || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
},
|
||||||
|
getBorderColor() {
|
||||||
|
return this.borderColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showText: '',
|
||||||
|
//1-发送前,2-发送中 3-发送成功,倒计时
|
||||||
|
status: 1,
|
||||||
|
countdownTimer: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.start) {
|
||||||
|
this.doLoop();
|
||||||
|
} else {
|
||||||
|
this.showText = this.text;
|
||||||
|
this.clearTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer();
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer();
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
watch: {
|
||||||
|
successVal(val) {
|
||||||
|
if (val && val > 0) {
|
||||||
|
this.doLoop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetVal(val) {
|
||||||
|
if (val && val > 0) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sendCode() {
|
||||||
|
if (this.status > 1) return;
|
||||||
|
this.clearTimer();
|
||||||
|
this.status = 2;
|
||||||
|
this.showText = this.sendText;
|
||||||
|
this.$emit('send', {
|
||||||
|
params: this.params
|
||||||
|
});
|
||||||
|
},
|
||||||
|
doLoop: function() {
|
||||||
|
this.clearTimer();
|
||||||
|
this.status = 3;
|
||||||
|
let seconds = this.seconds || 60;
|
||||||
|
this.showText = seconds + this.countdownText;
|
||||||
|
this.countdownTimer = setInterval(() => {
|
||||||
|
if (seconds > 1) {
|
||||||
|
--seconds;
|
||||||
|
this.showText = seconds + this.countdownText;
|
||||||
|
//倒计时
|
||||||
|
this.$emit('countdown', {
|
||||||
|
seconds: seconds,
|
||||||
|
params: this.params
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.reset();
|
||||||
|
//倒计时结束
|
||||||
|
this.$emit('end', {
|
||||||
|
params: this.params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
//验证码发送成功
|
||||||
|
success() {
|
||||||
|
this.doLoop();
|
||||||
|
},
|
||||||
|
//重置发送组件
|
||||||
|
reset() {
|
||||||
|
this.clearTimer();
|
||||||
|
this.showText = this.text;
|
||||||
|
this.status = 1;
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearInterval(this.countdownTimer);
|
||||||
|
this.countdownTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-countdown__verify {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-verify__opacity {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-verify__line {
|
||||||
|
position: absolute;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scale(0.5, 0.5) translateZ(0);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-style: solid;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
343
src/components/thorui/tui-countdown/tui-countdown.vue
Normal file
343
src/components/thorui/tui-countdown/tui-countdown.vue
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-countdown-box">
|
||||||
|
<view class="tui-countdown-item"
|
||||||
|
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(d, width) + 'rpx', height: height + 'rpx' }"
|
||||||
|
v-if="days">
|
||||||
|
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||||
|
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||||
|
{{ d }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||||
|
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }" v-if="days">
|
||||||
|
{{ isColon ? ':' : '天' }}
|
||||||
|
</view>
|
||||||
|
<view class="tui-countdown-item"
|
||||||
|
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(h, width) + 'rpx', height: height + 'rpx' }"
|
||||||
|
v-if="hours">
|
||||||
|
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||||
|
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||||
|
{{ h }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||||
|
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }" v-if="hours">
|
||||||
|
{{ isColon ? ':' : '时' }}
|
||||||
|
</view>
|
||||||
|
<view class="tui-countdown-item"
|
||||||
|
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(i, width) + 'rpx', height: height + 'rpx' }"
|
||||||
|
v-if="minutes">
|
||||||
|
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||||
|
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||||
|
{{ i }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||||
|
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }" v-if="minutes">
|
||||||
|
{{ isColon ? ':' : '分' }}
|
||||||
|
</view>
|
||||||
|
<view class="tui-countdown-item"
|
||||||
|
:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(s, width) + 'rpx', height: height + 'rpx' }"
|
||||||
|
v-if="seconds">
|
||||||
|
<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']"
|
||||||
|
:style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
|
||||||
|
{{ s }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-countdown-colon" :class="{ 'tui-colon-pad': borderColor == 'transparent' }"
|
||||||
|
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
|
||||||
|
v-if="seconds && !isColon">
|
||||||
|
{{ unitEn ? 's' : '秒' }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tui-countdown-colon"
|
||||||
|
:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
|
||||||
|
v-if="seconds && isMs && isColon">.</view>
|
||||||
|
<view class="tui-countdown__ms" :style="{
|
||||||
|
background: backgroundColor,
|
||||||
|
borderColor: borderColor,
|
||||||
|
fontSize: msSize + 'rpx',
|
||||||
|
color: msColor,
|
||||||
|
height: height + 'rpx',
|
||||||
|
width: msWidth > 0 ? msWidth + 'rpx' : 'auto'
|
||||||
|
}" v-if="seconds && isMs">
|
||||||
|
<view :class="{ 'tui-ms__list': ani }">
|
||||||
|
<view class="tui-ms__item" :style="{ height: height + 'rpx' }" v-for="(item, index) in ms" :key="index">
|
||||||
|
<view :class="[scale ? 'tui-countdown-scale' : '']">{{item}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiCountdown',
|
||||||
|
emits: ['end', 'time'],
|
||||||
|
props: {
|
||||||
|
//数字框宽度
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
//数字框高度
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
//数字框border颜色
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//数字框背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//数字框字体大小
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
//数字框字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//是否缩放 0.9
|
||||||
|
scale: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//冒号大小
|
||||||
|
colonSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 28
|
||||||
|
},
|
||||||
|
//冒号颜色
|
||||||
|
colonColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//剩余时间 (单位:秒)
|
||||||
|
time: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//是否包含天
|
||||||
|
days: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否包含小时
|
||||||
|
hours: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否包含分钟
|
||||||
|
minutes: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否包含秒
|
||||||
|
seconds: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//单位用英文缩写表示 仅seconds秒数有效
|
||||||
|
unitEn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否展示为冒号,false为文字
|
||||||
|
isColon: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否返回剩余时间
|
||||||
|
returnTime: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否显示毫秒
|
||||||
|
isMs: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
msWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
msSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
msColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
time(val) {
|
||||||
|
this.clearTimer();
|
||||||
|
this.doLoop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
countdown: null,
|
||||||
|
d: '0',
|
||||||
|
h: '00',
|
||||||
|
i: '00',
|
||||||
|
s: '00',
|
||||||
|
//此处若从9到1,结束需要特殊处理
|
||||||
|
ms: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
ani: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.clearTimer();
|
||||||
|
let seconds = Number(this.time || 0);
|
||||||
|
if (seconds > 0) {
|
||||||
|
this.doLoop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer();
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer();
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
getWidth: function(num, width) {
|
||||||
|
return num > 99 ? (width / 2) * num.toString().length : width;
|
||||||
|
},
|
||||||
|
clearTimer() {
|
||||||
|
clearInterval(this.countdown);
|
||||||
|
this.countdown = null;
|
||||||
|
},
|
||||||
|
endOfTime(isStop = false) {
|
||||||
|
this.ani = false;
|
||||||
|
this.clearTimer();
|
||||||
|
if (!isStop) {
|
||||||
|
this.$emit('end', {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doLoop(time = 0) {
|
||||||
|
let seconds = time || Number(this.time || 0);
|
||||||
|
this.ani = true;
|
||||||
|
this.countDown(seconds);
|
||||||
|
this.countdown = setInterval(() => {
|
||||||
|
seconds--;
|
||||||
|
this.countDown(seconds);
|
||||||
|
if (this.returnTime) {
|
||||||
|
this.$emit('time', {
|
||||||
|
seconds: seconds
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (seconds <= 0) {
|
||||||
|
this.endOfTime();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
countDown(seconds) {
|
||||||
|
let [day, hour, minute, second] = [0, 0, 0, 0];
|
||||||
|
if (seconds > 0) {
|
||||||
|
day = this.days ? Math.floor(seconds / (60 * 60 * 24)) : 0;
|
||||||
|
hour = this.hours ? Math.floor(seconds / (60 * 60)) - day * 24 : 0;
|
||||||
|
minute = this.minutes ? Math.floor(seconds / 60) - hour * 60 - day * 24 * 60 : 0;
|
||||||
|
second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
|
||||||
|
}
|
||||||
|
hour = hour < 10 ? '0' + hour : hour;
|
||||||
|
minute = minute < 10 ? '0' + minute : minute;
|
||||||
|
second = second < 10 ? '0' + second : second;
|
||||||
|
this.d = day;
|
||||||
|
this.h = hour;
|
||||||
|
this.i = minute;
|
||||||
|
this.s = second;
|
||||||
|
},
|
||||||
|
reset(seconds = 0) {
|
||||||
|
let time = seconds || Number(this.time);
|
||||||
|
this.clearTimer();
|
||||||
|
if (time > 0) {
|
||||||
|
this.doLoop(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-countdown-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-countdown-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-countdown-item {
|
||||||
|
border: 1rpx solid;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-countdown-time {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-countdown-colon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 5rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-colon-pad {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-countdown-scale {
|
||||||
|
transform: scale(0.9);
|
||||||
|
transform-origin: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-countdown__ms {
|
||||||
|
border: 1rpx solid;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*ms使用css3代替js频繁更新操作,性能优化*/
|
||||||
|
.tui-ms__list {
|
||||||
|
animation: loop 1s steps(10) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loop {
|
||||||
|
from {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-ms__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
763
src/components/thorui/tui-cropper-app/tui-cropper-app.vue
Normal file
763
src/components/thorui/tui-cropper-app/tui-cropper-app.vue
Normal file
@ -0,0 +1,763 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-container" @touchmove.stop.prevent="stop">
|
||||||
|
<view class="tui-image-cropper" :change:prop="parse.propsChange" :prop="props" :data-lockRatio="lockRatio"
|
||||||
|
:data-lockWidth="lockWidth" :data-lockHeight="lockHeight" :data-maxWidth="maxWidth"
|
||||||
|
:data-minWidth="minWidth" :data-maxHeight="maxHeight" :data-minHeight="minHeight" :data-width="width"
|
||||||
|
:data-height="height" :data-limitMove="limitMove" :data-windowHeight="sysInfo.windowHeight || 600"
|
||||||
|
:data-windowWidth="sysInfo.windowWidth || 400" :data-imgTop="imgTop" :data-imgLeft="imgLeft"
|
||||||
|
:data-imgWidth="imgWidth" :data-imgHeight="imgHeight" :data-angle="angle" @touchend="parse.cutTouchEnd"
|
||||||
|
@touchstart="parse.cutTouchStart" @touchmove="parse.cutTouchMove">
|
||||||
|
<view class="tui-content">
|
||||||
|
<view class="tui-content-top tui-bg-transparent"
|
||||||
|
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||||
|
<view class="tui-content-middle">
|
||||||
|
<view class="tui-bg-transparent tui-wxs-bg"
|
||||||
|
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||||
|
<view class="tui-cropper-box"
|
||||||
|
:style="{ borderColor: borderColor, transitionProperty: cutAnimation ? '' : 'background' }">
|
||||||
|
<view v-for="(item, index) in 4" :key="index" class="tui-edge"
|
||||||
|
:class="[`tui-${index < 2 ? 'top' : 'bottom'}-${index === 0 || index === 2 ? 'left' : 'right'}`]"
|
||||||
|
:style="{
|
||||||
|
width: edgeWidth,
|
||||||
|
height: edgeWidth,
|
||||||
|
borderColor: edgeColor,
|
||||||
|
borderWidth: edgeBorderWidth,
|
||||||
|
left: index === 0 || index === 2 ? `-${edgeOffsets}` : 'auto',
|
||||||
|
right: index === 1 || index === 3 ? `-${edgeOffsets}` : 'auto',
|
||||||
|
top: index < 2 ? `-${edgeOffsets}` : 'auto',
|
||||||
|
bottom: index > 1 ? `-${edgeOffsets}` : 'auto'
|
||||||
|
}"></view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-flex-auto tui-bg-transparent"
|
||||||
|
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-flex-auto tui-bg-transparent"
|
||||||
|
:style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
|
||||||
|
</view>
|
||||||
|
<image @load="imageLoad" @error="imageLoad" @touchstart="parse.touchstart" @touchmove="parse.touchmove"
|
||||||
|
@touchend="parse.touchend" :data-minScale="minScale" :data-maxScale="maxScale"
|
||||||
|
:data-disableRotate="disableRotate" :style="{
|
||||||
|
width: imgWidth ? imgWidth + 'px' : 'auto',
|
||||||
|
height: imgHeight ? imgHeight + 'px' : 'auto',
|
||||||
|
transitionDuration: (cutAnimation ? 0.3 : 0) + 's'
|
||||||
|
}" class="tui-cropper-image" :class="{'tui-cropper__image-hidden':!picturePath}" :src="picturePath"
|
||||||
|
mode="widthFix"></image>
|
||||||
|
</view>
|
||||||
|
<view class="tui-cropper-tabbar" v-if="!custom">
|
||||||
|
<view class="tui-op-btn" @tap.stop="back">取消</view>
|
||||||
|
<image :src="rotateImg" class="tui-rotate-img" @tap="setAngle"></image>
|
||||||
|
<view class="tui-op-btn" @tap.stop="getImage">完成</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script src="./tui-cropper-app.wxs" module="parse" lang="wxs"></script>
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 注意:组件中使用的图片地址,将文件复制到自己项目中
|
||||||
|
* 如果图片位置与组件同级,编译成小程序时图片会丢失
|
||||||
|
* 拷贝static下整个components文件夹
|
||||||
|
*也可直接转成base64(不建议)
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
export default {
|
||||||
|
name: 'tuiCropperApp',
|
||||||
|
emits: ['ready', 'cropper', 'imageLoad', 'initAngle'],
|
||||||
|
props: {
|
||||||
|
//图片路径
|
||||||
|
imageUrl: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
默认正方形,可修改大小控制比例
|
||||||
|
裁剪框高度 px
|
||||||
|
*/
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 280
|
||||||
|
},
|
||||||
|
//裁剪框宽度 px
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 280
|
||||||
|
},
|
||||||
|
//裁剪框最小宽度 px
|
||||||
|
minWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//裁剪框最小高度 px
|
||||||
|
minHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//裁剪框最大宽度 px
|
||||||
|
maxWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 360
|
||||||
|
},
|
||||||
|
//裁剪框最大高度 px
|
||||||
|
maxHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 360
|
||||||
|
},
|
||||||
|
//裁剪框border颜色
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(255,255,255,0.1)'
|
||||||
|
},
|
||||||
|
//裁剪框边缘线颜色
|
||||||
|
edgeColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#FFFFFF'
|
||||||
|
},
|
||||||
|
//裁剪框边缘线宽度 w=h
|
||||||
|
edgeWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '34rpx'
|
||||||
|
},
|
||||||
|
//裁剪框边缘线border宽度
|
||||||
|
edgeBorderWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '6rpx'
|
||||||
|
},
|
||||||
|
//偏移距离,根据edgeBorderWidth进行调整
|
||||||
|
edgeOffsets: {
|
||||||
|
type: String,
|
||||||
|
default: '6rpx'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 如果宽度和高度都为true则裁剪框禁止拖动
|
||||||
|
* 裁剪框宽度锁定
|
||||||
|
*/
|
||||||
|
lockWidth: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//裁剪框高度锁定
|
||||||
|
lockHeight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//锁定裁剪框比例(放大或缩小)
|
||||||
|
lockRatio: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//生成的图片尺寸相对剪裁框的比例
|
||||||
|
scaleRatio: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
//图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理
|
||||||
|
quality: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.8
|
||||||
|
},
|
||||||
|
//图片旋转角度[当前版本只能传90的倍数,否则可能导致裁剪失败或不正确]
|
||||||
|
rotateAngle: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//图片最小缩放比
|
||||||
|
minScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.5
|
||||||
|
},
|
||||||
|
//图片最大缩放比
|
||||||
|
maxScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
//是否禁用触摸旋转(为false则可以触摸转动图片,limitMove为false生效)
|
||||||
|
//此属性App端当前版本不可用,否则可能导致裁剪失败或不正确
|
||||||
|
disableRotate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否限制移动范围(剪裁框只能在图片内,为true不可触摸转动图片)
|
||||||
|
//注意:此属性当前版本不可传false,由于api不支持超出裁剪区域裁剪,所以只能限制在裁剪框内
|
||||||
|
limitMove: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//自定义操作栏(为true时隐藏底部操作栏)
|
||||||
|
custom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//值发生改变开始裁剪(custom为true时生效)
|
||||||
|
startCutting: {
|
||||||
|
type: [Number, Boolean],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 是否返回base64(H5端默认base64)
|
||||||
|
* 支持平台:App,微信小程序,支付宝小程序,H5(默认url就是base64)
|
||||||
|
**/
|
||||||
|
isBase64: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//裁剪时是否显示loadding
|
||||||
|
loadding: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//旋转icon
|
||||||
|
rotateImg: {
|
||||||
|
type: String,
|
||||||
|
default: '/static/components/cropper/img_rotate.png'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
TIME_CUT_CENTER: null,
|
||||||
|
cutX: 0, //画布x轴起点
|
||||||
|
cutY: 0, //画布y轴起点0
|
||||||
|
canvasWidth: 0,
|
||||||
|
canvasHeight: 0,
|
||||||
|
naturalWidth: 0,
|
||||||
|
naturalHeight: 0,
|
||||||
|
imgWidth: 0, //图片宽度
|
||||||
|
imgHeight: 0, //图片高度
|
||||||
|
scale: 1, //图片缩放比
|
||||||
|
angle: 0, //图片旋转角度
|
||||||
|
cutAnimation: false, //是否开启图片和裁剪框过渡
|
||||||
|
cutAnimationTime: null,
|
||||||
|
imgTop: 0, //图片上边距
|
||||||
|
imgLeft: 0, //图片左边距
|
||||||
|
sysInfo: {},
|
||||||
|
props: '',
|
||||||
|
sizeChange: 0, //2
|
||||||
|
angleChange: 0, //3
|
||||||
|
resetChange: 0, //4
|
||||||
|
centerChange: 0, //5
|
||||||
|
orientation: '',
|
||||||
|
picturePath: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
//定义变量然后利用change触发
|
||||||
|
imageUrl(val, oldVal) {
|
||||||
|
this.imageReset();
|
||||||
|
this.showLoading();
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: val,
|
||||||
|
success: res => {
|
||||||
|
//计算图片尺寸
|
||||||
|
this.naturalWidth = res.width;
|
||||||
|
this.naturalHeight = res.height;
|
||||||
|
this.orientation = res.orientation;
|
||||||
|
if (this.orientation != 'up') {
|
||||||
|
//宽高传值颠倒
|
||||||
|
let width = this.orientation == 'down' ? res.width : res.height;
|
||||||
|
let height = this.orientation == 'down' ? res.height : res.width;
|
||||||
|
this.compressImage(val, width, height);
|
||||||
|
} else {
|
||||||
|
this.picturePath = val;
|
||||||
|
this.imgComputeSize(res.width, res.height);
|
||||||
|
if (this.limitMove) {
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: err => {
|
||||||
|
this.imgComputeSize();
|
||||||
|
if (this.limitMove) {
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rotateAngle(val) {
|
||||||
|
this.cutAnimation = true;
|
||||||
|
this.angle = val;
|
||||||
|
this.angleChanged(val);
|
||||||
|
},
|
||||||
|
cutAnimation(val) {
|
||||||
|
//开启过渡260毫秒之后自动关闭
|
||||||
|
clearTimeout(this.cutAnimationTime);
|
||||||
|
if (val) {
|
||||||
|
this.cutAnimationTime = setTimeout(() => {
|
||||||
|
this.cutAnimation = false;
|
||||||
|
}, 260);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limitMove(val) {
|
||||||
|
if (val) {
|
||||||
|
this.angleChanged(this.angle);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startCutting(val) {
|
||||||
|
if (this.custom && val) {
|
||||||
|
this.getImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.picturePath = this.imageUrl;
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.sysInfo = uni.getSystemInfoSync();
|
||||||
|
this.imgTop = this.sysInfo.windowHeight / 2;
|
||||||
|
this.imgLeft = this.sysInfo.windowWidth / 2;
|
||||||
|
this.canvasHeight = this.height;
|
||||||
|
this.canvasWidth = this.width;
|
||||||
|
//初始化
|
||||||
|
setTimeout(() => {
|
||||||
|
this.props = '1,1';
|
||||||
|
}, 0);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$emit('ready', {});
|
||||||
|
}, 200);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toast(title) {
|
||||||
|
uni.showToast({
|
||||||
|
title: title || '出错啦~',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async compressImage(url, width, height) {
|
||||||
|
let imgUrl = url;
|
||||||
|
if (~imgUrl.indexOf('https:')) {
|
||||||
|
imgUrl = await this.getLocalImage(url);
|
||||||
|
if (!imgUrl) {
|
||||||
|
this.toast('网络图片处理失败~');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let defaultAngle = {
|
||||||
|
up: 0,
|
||||||
|
down: 180,
|
||||||
|
left: 270,
|
||||||
|
right: 90
|
||||||
|
} [this.orientation] || 0;
|
||||||
|
let f_dst = `_documents/${this.unique()}.jpg`;
|
||||||
|
plus.zip.compressImage({
|
||||||
|
src: imgUrl,
|
||||||
|
dst: f_dst,
|
||||||
|
overwrite: true,
|
||||||
|
rotate: defaultAngle,
|
||||||
|
format: 'jpg'
|
||||||
|
},
|
||||||
|
i => {
|
||||||
|
this.picturePath = i.target;
|
||||||
|
this.imgComputeSize(width, height);
|
||||||
|
if (this.limitMove) {
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
e => {
|
||||||
|
this.picturePath = imgUrl;
|
||||||
|
this.imgComputeSize(width, height);
|
||||||
|
if (this.limitMove) {
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
//网络图片转成本地文件[同步执行]
|
||||||
|
async getLocalImage(url) {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
uni.downloadFile({
|
||||||
|
url: url,
|
||||||
|
success: res => {
|
||||||
|
resolve(res.tempFilePath);
|
||||||
|
},
|
||||||
|
fail: res => {
|
||||||
|
reject(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//返回裁剪后图片信息
|
||||||
|
getImage() {
|
||||||
|
if (!this.picturePath) {
|
||||||
|
this.toast('请选择图片');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadding && this.showLoading();
|
||||||
|
let cutting = async () => {
|
||||||
|
//图片实际大小
|
||||||
|
let imgUrl = this.picturePath;
|
||||||
|
if (~this.picturePath.indexOf('https:')) {
|
||||||
|
imgUrl = await this.getLocalImage(this.picturePath);
|
||||||
|
if (!imgUrl) {
|
||||||
|
this.toast('网络图片处理失败~');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let data = {
|
||||||
|
url: '',
|
||||||
|
base64: ''
|
||||||
|
};
|
||||||
|
let f_dst = `_documents/${this.unique()}.jpg`;
|
||||||
|
let multiple = Math.round(this.angle / 90);
|
||||||
|
let angle = 0,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
left = 0,
|
||||||
|
top = 0,
|
||||||
|
clipHeight = 0,
|
||||||
|
clipWidth = 0;
|
||||||
|
|
||||||
|
let isAndroid = this.sysInfo.platform.toLocaleLowerCase() == 'android';
|
||||||
|
|
||||||
|
if (multiple % 2 == 0) {
|
||||||
|
angle = this.angle % 360 == 0 || this.angle == 0 ? 0 : 180;
|
||||||
|
if (!isAndroid || (isAndroid && angle == 0)) {
|
||||||
|
x = this.imgLeft - (this.imgWidth * this.scale) / 2;
|
||||||
|
y = this.imgTop - (this.imgHeight * this.scale) / 2;
|
||||||
|
left = (((this.cutX - x) / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
top = (((this.cutY - y) / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
clipWidth = ((this.canvasWidth / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
clipHeight = ((this.canvasHeight / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
} else {
|
||||||
|
x = this.imgLeft - (this.imgWidth * this.scale) / 2;
|
||||||
|
y = this.imgTop - (this.imgHeight * this.scale) / 2;
|
||||||
|
left = (((this.imgWidth * this.scale - (this.cutX - x) - this.canvasWidth) / this
|
||||||
|
.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
top = (((this.imgHeight * this.scale - (this.cutY - y) - this.canvasHeight) / this
|
||||||
|
.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
clipWidth = ((this.canvasWidth / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
clipHeight = ((this.canvasHeight / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
angle = this.angle % 270 == 0 ? 270 : 90;
|
||||||
|
if (isAndroid) {
|
||||||
|
if (angle == 90) {
|
||||||
|
x = this.imgLeft - (this.imgHeight * this.scale) / 2;
|
||||||
|
y = this.imgTop - (this.imgWidth * this.scale) / 2;
|
||||||
|
top = (((this.imgHeight * this.scale - (this.cutX - x) - this.canvasHeight) / this
|
||||||
|
.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
left = (((this.imgWidth * this.scale - (this.cutY - y) - this.canvasWidth) / this
|
||||||
|
.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
clipHeight = ((this.canvasWidth / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
clipWidth = ((this.canvasHeight / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
} else {
|
||||||
|
x = this.imgLeft - (this.imgHeight * this.scale) / 2;
|
||||||
|
y = this.imgTop - (this.imgWidth * this.scale) / 2;
|
||||||
|
top = (((this.cutX - x) / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
left = (((this.cutY - y) / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
clipHeight = ((this.canvasWidth / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
clipWidth = ((this.canvasHeight / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
x = this.imgLeft - (this.imgHeight * this.scale) / 2;
|
||||||
|
y = this.imgTop - (this.imgWidth * this.scale) / 2;
|
||||||
|
left = (((this.cutX - x) / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
top = (((this.cutY - y) / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
clipWidth = ((this.canvasWidth / this.imgHeight / this.scale) * 100).toFixed(2);
|
||||||
|
clipHeight = ((this.canvasHeight / this.imgWidth / this.scale) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let width = (this.imgWidth < this.naturalWidth ? this.naturalWidth : this.imgWidth) * this
|
||||||
|
.scale;
|
||||||
|
let height = (this.imgHeight < this.naturalHeight ? this.naturalHeight : this.imgHeight) * this
|
||||||
|
.scale;
|
||||||
|
// if (isAndroid && width > 800) {
|
||||||
|
// width = '800px';
|
||||||
|
// height = 'auto'
|
||||||
|
// } else {
|
||||||
|
// width = `${width}px`;
|
||||||
|
// height = `${height}px`
|
||||||
|
// }
|
||||||
|
width = `${width}px`;
|
||||||
|
height = `${height}px`;
|
||||||
|
left = Number(left) <= 0 ? 0 : left;
|
||||||
|
top = Number(top) <= 0 ? 0 : top;
|
||||||
|
plus.zip.compressImage({
|
||||||
|
src: imgUrl,
|
||||||
|
dst: f_dst,
|
||||||
|
quality: this.quality * 100,
|
||||||
|
overwrite: true,
|
||||||
|
format: 'jpg',
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
rotate: angle,
|
||||||
|
clip: {
|
||||||
|
top: `${top}%`,
|
||||||
|
left: `${left}%`,
|
||||||
|
width: `${clipWidth}%`,
|
||||||
|
height: `${clipHeight}%`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
i => {
|
||||||
|
this.loadding && uni.hideLoading();
|
||||||
|
data.url = i.target;
|
||||||
|
if (this.isBase64) {
|
||||||
|
plus.io.resolveLocalFileSystemURL(f_dst, entry => {
|
||||||
|
entry.file(
|
||||||
|
file => {
|
||||||
|
let reader = new plus.io.FileReader();
|
||||||
|
reader.onloadend = e => {
|
||||||
|
data.base64 = e.target.result;
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
},
|
||||||
|
e => {
|
||||||
|
//转换base64失败,传回图片url
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
e => {
|
||||||
|
console.log(e);
|
||||||
|
this.toast('图片裁剪失败,请稍候再试~');
|
||||||
|
//原图输出
|
||||||
|
// data.url = imgUrl;
|
||||||
|
// this.$emit('cropper', data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
setTimeout(() => {
|
||||||
|
cutting();
|
||||||
|
}, 20);
|
||||||
|
},
|
||||||
|
unique(n = 6) {
|
||||||
|
let rnd = '';
|
||||||
|
for (let i = 0; i < n; i++) rnd += Math.floor(Math.random() * 10);
|
||||||
|
return 'thorui_' + new Date().getTime() + rnd;
|
||||||
|
},
|
||||||
|
change(e) {
|
||||||
|
this.cutX = e.cutX || 0;
|
||||||
|
this.cutY = e.cutY || 0;
|
||||||
|
this.canvasWidth = e.canvasWidth || this.width;
|
||||||
|
this.canvasHeight = e.canvasHeight || this.height;
|
||||||
|
this.imgWidth = e.imgWidth || this.imgWidth;
|
||||||
|
this.imgHeight = e.imgHeight || this.imgHeight;
|
||||||
|
this.scale = e.scale || 1;
|
||||||
|
this.angle = e.angle || 0;
|
||||||
|
this.imgTop = e.imgTop || 0;
|
||||||
|
this.imgLeft = e.imgLeft || 0;
|
||||||
|
},
|
||||||
|
imageReset() {
|
||||||
|
this.scale = 1;
|
||||||
|
this.angle = 0;
|
||||||
|
let sys = this.sysInfo.windowHeight ? this.sysInfo : uni.getSystemInfoSync();
|
||||||
|
this.imgTop = sys.windowHeight / 2;
|
||||||
|
this.imgLeft = sys.windowWidth / 2;
|
||||||
|
this.resetChange++;
|
||||||
|
this.props = `4,${this.resetChange}`;
|
||||||
|
//初始化旋转角度 0deg
|
||||||
|
this.$emit('initAngle', {});
|
||||||
|
},
|
||||||
|
imageLoad(e) {
|
||||||
|
this.imageReset();
|
||||||
|
uni.hideLoading();
|
||||||
|
this.$emit('imageLoad', {});
|
||||||
|
},
|
||||||
|
|
||||||
|
imgComputeSize(width, height) {
|
||||||
|
//默认按图片最小边 = 对应裁剪框尺寸
|
||||||
|
let imgWidth = width,
|
||||||
|
imgHeight = height;
|
||||||
|
if (imgWidth && imgHeight) {
|
||||||
|
if (imgWidth / imgHeight > this.width / this.height) {
|
||||||
|
imgHeight = this.height;
|
||||||
|
imgWidth = (width / height) * imgHeight;
|
||||||
|
} else {
|
||||||
|
imgWidth = this.width;
|
||||||
|
imgHeight = (height / width) * imgWidth;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let sys = this.sysInfo || uni.getSystemInfoSync();
|
||||||
|
imgWidth = sys.windowWidth;
|
||||||
|
imgHeight = 0;
|
||||||
|
}
|
||||||
|
this.imgWidth = imgWidth;
|
||||||
|
this.imgHeight = imgHeight;
|
||||||
|
this.sizeChange++;
|
||||||
|
this.props = `2,${this.sizeChange}`;
|
||||||
|
},
|
||||||
|
moveStop() {
|
||||||
|
clearTimeout(this.TIME_CUT_CENTER);
|
||||||
|
this.TIME_CUT_CENTER = setTimeout(() => {
|
||||||
|
if (!this.cutAnimation) {
|
||||||
|
this.cutAnimation = true;
|
||||||
|
}
|
||||||
|
this.centerChange++;
|
||||||
|
this.props = `5,${this.centerChange}`;
|
||||||
|
}, 666);
|
||||||
|
},
|
||||||
|
moveDuring() {
|
||||||
|
clearTimeout(this.TIME_CUT_CENTER);
|
||||||
|
},
|
||||||
|
showLoading() {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '请稍候...',
|
||||||
|
mask: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop() {},
|
||||||
|
back() {
|
||||||
|
uni.navigateBack();
|
||||||
|
},
|
||||||
|
angleChanged(val) {
|
||||||
|
this.moveStop();
|
||||||
|
if (this.limitMove && val % 90) {
|
||||||
|
this.angle = Math.round(val / 90) * 90;
|
||||||
|
}
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
},
|
||||||
|
setAngle() {
|
||||||
|
this.cutAnimation = true;
|
||||||
|
this.angle = this.angle + 90;
|
||||||
|
this.angleChanged(this.angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-container {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-image-cropper {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper__image-hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-content {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bg-transparent {
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-content-top {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-content-middle {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper-box {
|
||||||
|
position: relative;
|
||||||
|
/* transition-duration: 0.2s; */
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-flex-auto {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper-image {
|
||||||
|
width: 100%;
|
||||||
|
border-style: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-edge {
|
||||||
|
border-style: solid;
|
||||||
|
pointer-events: auto;
|
||||||
|
position: absolute;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-top-left {
|
||||||
|
border-bottom-width: 0 !important;
|
||||||
|
border-right-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-top-right {
|
||||||
|
border-bottom-width: 0 !important;
|
||||||
|
border-left-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bottom-left {
|
||||||
|
border-top-width: 0 !important;
|
||||||
|
border-right-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bottom-right {
|
||||||
|
border-top-width: 0 !important;
|
||||||
|
border-left-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper-tabbar {
|
||||||
|
width: 100%;
|
||||||
|
height: 120rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper-tabbar::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||||
|
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-op-btn {
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-rotate-img {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
571
src/components/thorui/tui-cropper-app/tui-cropper-app.wxs
Normal file
571
src/components/thorui/tui-cropper-app/tui-cropper-app.wxs
Normal file
@ -0,0 +1,571 @@
|
|||||||
|
var cropper = {
|
||||||
|
CUT_START: null,
|
||||||
|
cutX: 0, //画布x轴起点
|
||||||
|
cutY: 0, //画布y轴起点0
|
||||||
|
touchRelative: [{
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}], //手指或鼠标和图片中心的相对位置
|
||||||
|
flagCutTouch: false, //是否是拖动裁剪框
|
||||||
|
hypotenuseLength: 0, //双指触摸时斜边长度
|
||||||
|
flagEndTouch: false, //是否结束触摸
|
||||||
|
canvasWidth: 0,
|
||||||
|
canvasHeight: 0,
|
||||||
|
imgWidth: 0, //图片宽度
|
||||||
|
imgHeight: 0, //图片高度
|
||||||
|
scale: 1, //图片缩放比
|
||||||
|
angle: 0, //图片旋转角度
|
||||||
|
imgTop: 0, //图片上边距
|
||||||
|
imgLeft: 0, //图片左边距
|
||||||
|
//是否限制移动范围(剪裁框只能在图片内,为true不可触摸转动图片)
|
||||||
|
limitMove: true,
|
||||||
|
minHeight: 0,
|
||||||
|
maxHeight: 0,
|
||||||
|
minWidth: 0,
|
||||||
|
maxWidth: 0,
|
||||||
|
windowHeight: 0,
|
||||||
|
windowWidth: 0,
|
||||||
|
init: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function bool(str) {
|
||||||
|
return str === 'true' || str == true ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchstart(e, ins) {
|
||||||
|
//var instance = e.instance;
|
||||||
|
// var state = instance.getState();
|
||||||
|
var touch = e.touches || e.changedTouches;
|
||||||
|
cropper.flagEndTouch = false;
|
||||||
|
if (touch.length == 1) {
|
||||||
|
cropper.touchRelative[0] = {
|
||||||
|
x: touch[0].pageX - cropper.imgLeft,
|
||||||
|
y: touch[0].pageY - cropper.imgTop
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var width = Math.abs(touch[0].pageX - touch[1].pageX);
|
||||||
|
var height = Math.abs(touch[0].pageY - touch[1].pageY);
|
||||||
|
cropper.touchRelative = [{
|
||||||
|
x: touch[0].pageX - cropper.imgLeft,
|
||||||
|
y: touch[0].pageY - cropper.imgTop
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: touch[1].pageX - cropper.imgLeft,
|
||||||
|
y: touch[1].pageY - cropper.imgTop
|
||||||
|
}
|
||||||
|
];
|
||||||
|
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDuring(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
ins.callMethod('moveDuring')
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveStop(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
ins.callMethod('moveStop')
|
||||||
|
};
|
||||||
|
|
||||||
|
function setCutCenter(ins) {
|
||||||
|
var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5;
|
||||||
|
var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5;
|
||||||
|
//顺序不能变
|
||||||
|
cropper.imgTop = cropper.imgTop - cropper.cutY + cutY;
|
||||||
|
cropper.cutY = cutY; //截取的框上边距
|
||||||
|
cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX;
|
||||||
|
cropper.cutX = cutX; //截取的框左边距
|
||||||
|
styleUpdate(ins)
|
||||||
|
cutDetectionPosition(ins)
|
||||||
|
imgTransform(ins)
|
||||||
|
updateData(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchmove(e, ins) {
|
||||||
|
var touch = e.touches || e.changedTouches;
|
||||||
|
if (cropper.flagEndTouch) return;
|
||||||
|
moveDuring(ins);
|
||||||
|
if (e.touches.length == 1) {
|
||||||
|
var left = touch[0].pageX - cropper.touchRelative[0].x,
|
||||||
|
top = touch[0].pageY - cropper.touchRelative[0].y;
|
||||||
|
cropper.imgLeft = left;
|
||||||
|
cropper.imgTop = top;
|
||||||
|
imgTransform(ins);
|
||||||
|
imgMarginDetectionPosition(ins);
|
||||||
|
} else {
|
||||||
|
var res = e.instance.getDataset();
|
||||||
|
var minScale = +res.minscale;
|
||||||
|
var maxScale = +res.maxscale;
|
||||||
|
var disableRotate = bool(res.disablerotate)
|
||||||
|
var width = Math.abs(touch[0].pageX - touch[1].pageX),
|
||||||
|
height = Math.abs(touch[0].pageY - touch[1].pageY),
|
||||||
|
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
|
||||||
|
scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
|
||||||
|
current_deg = 0;
|
||||||
|
scale = scale <= minScale ? minScale : scale;
|
||||||
|
scale = scale >= maxScale ? maxScale : scale;
|
||||||
|
cropper.scale = scale;
|
||||||
|
imgMarginDetectionScale(ins, true);
|
||||||
|
var touchRelative = [{
|
||||||
|
x: touch[0].pageX - cropper.imgLeft,
|
||||||
|
y: touch[0].pageY - cropper.imgTop
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: touch[1].pageX - cropper.imgLeft,
|
||||||
|
y: touch[1].pageY - cropper.imgTop
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (!disableRotate) {
|
||||||
|
var first_atan = (180 / Math.PI) * Math.atan2(touchRelative[0].y, touchRelative[0].x);
|
||||||
|
var first_atan_old = (180 / Math.PI) * Math.atan2(cropper.touchRelative[0].y, cropper.touchRelative[0].x);
|
||||||
|
var second_atan = (180 / Math.PI) * Math.atan2(touchRelative[1].y, touchRelative[1].x);
|
||||||
|
var second_atan_old = (180 / Math.PI) * Math.atan2(cropper.touchRelative[1].y, cropper.touchRelative[1].x);
|
||||||
|
var first_deg = first_atan - first_atan_old,
|
||||||
|
second_deg = second_atan - second_atan_old;
|
||||||
|
if (first_deg != 0) {
|
||||||
|
current_deg = first_deg;
|
||||||
|
} else if (second_deg != 0) {
|
||||||
|
current_deg = second_deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cropper.touchRelative = touchRelative;
|
||||||
|
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||||
|
//更新视图
|
||||||
|
cropper.angle = cropper.angle + current_deg;
|
||||||
|
imgTransform(ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchend(e, ins) {
|
||||||
|
cropper.flagEndTouch = true;
|
||||||
|
moveStop(ins);
|
||||||
|
updateData(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cutTouchStart(e, ins) {
|
||||||
|
var touch = e.touches || e.changedTouches;
|
||||||
|
var currentX = touch[0].pageX;
|
||||||
|
var currentY = touch[0].pageY;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (右下-1 右上-2 左上-3 左下-4)
|
||||||
|
* left_x [3,4]
|
||||||
|
* top_y [2,3]
|
||||||
|
* right_x [1,2]
|
||||||
|
* bottom_y [1,4]
|
||||||
|
*/
|
||||||
|
var left_x1 = cropper.cutX - 30;
|
||||||
|
var left_x2 = cropper.cutX + 30;
|
||||||
|
|
||||||
|
var top_y1 = cropper.cutY - 30;
|
||||||
|
var top_y2 = cropper.cutY + 30;
|
||||||
|
|
||||||
|
var right_x1 = cropper.cutX + cropper.canvasWidth - 30;
|
||||||
|
var right_x2 = cropper.cutX + cropper.canvasWidth + 30;
|
||||||
|
|
||||||
|
var bottom_y1 = cropper.cutY + cropper.canvasHeight - 30;
|
||||||
|
var bottom_y2 = cropper.cutY + cropper.canvasHeight + 30;
|
||||||
|
|
||||||
|
if (currentX > right_x1 && currentX < right_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
|
||||||
|
moveDuring();
|
||||||
|
cropper.flagCutTouch = true;
|
||||||
|
cropper.flagEndTouch = true;
|
||||||
|
cropper.CUT_START = {
|
||||||
|
width: cropper.canvasWidth,
|
||||||
|
height: cropper.canvasHeight,
|
||||||
|
x: currentX,
|
||||||
|
y: currentY,
|
||||||
|
corner: 1
|
||||||
|
};
|
||||||
|
} else if (currentX > right_x1 && currentX < right_x2 && currentY > top_y1 && currentY < top_y2) {
|
||||||
|
moveDuring();
|
||||||
|
cropper.flagCutTouch = true;
|
||||||
|
cropper.flagEndTouch = true;
|
||||||
|
cropper.CUT_START = {
|
||||||
|
width: cropper.canvasWidth,
|
||||||
|
height: cropper.canvasHeight,
|
||||||
|
x: currentX,
|
||||||
|
y: currentY,
|
||||||
|
cutY: cropper.cutY,
|
||||||
|
cutX: cropper.cutX,
|
||||||
|
corner: 2
|
||||||
|
};
|
||||||
|
} else if (currentX > left_x1 && currentX < left_x2 && currentY > top_y1 && currentY < top_y2) {
|
||||||
|
moveDuring();
|
||||||
|
cropper.flagCutTouch = true;
|
||||||
|
cropper.flagEndTouch = true;
|
||||||
|
cropper.CUT_START = {
|
||||||
|
width: cropper.canvasWidth,
|
||||||
|
height: cropper.canvasHeight,
|
||||||
|
cutY: cropper.cutY,
|
||||||
|
cutX: cropper.cutX,
|
||||||
|
x: currentX,
|
||||||
|
y: currentY,
|
||||||
|
corner: 3
|
||||||
|
};
|
||||||
|
} else if (currentX > left_x1 && currentX < left_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
|
||||||
|
moveDuring();
|
||||||
|
cropper.flagCutTouch = true;
|
||||||
|
cropper.flagEndTouch = true;
|
||||||
|
cropper.CUT_START = {
|
||||||
|
width: cropper.canvasWidth,
|
||||||
|
height: cropper.canvasHeight,
|
||||||
|
cutY: cropper.cutY,
|
||||||
|
cutX: cropper.cutX,
|
||||||
|
x: currentX,
|
||||||
|
y: currentY,
|
||||||
|
corner: 4
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cutTouchMove(e, ins) {
|
||||||
|
if (!cropper.CUT_START || cropper.CUT_START === 'null') return;
|
||||||
|
if (cropper.flagCutTouch) {
|
||||||
|
var touch = e.touches || e.changedTouches;
|
||||||
|
var res = e.instance.getDataset();
|
||||||
|
var lockRatio = bool(res.lockratio);
|
||||||
|
var lockWidth = bool(res.lockwidth);
|
||||||
|
var lockHeight = bool(res.lockheight);
|
||||||
|
if (lockRatio && (lockWidth || lockHeight)) return;
|
||||||
|
var width = cropper.canvasWidth,
|
||||||
|
height = cropper.canvasHeight,
|
||||||
|
cutY = cropper.cutY,
|
||||||
|
cutX = cropper.cutX;
|
||||||
|
|
||||||
|
var size_correct = function() {
|
||||||
|
width = width <= cropper.maxWidth ? (width >= cropper.minWidth ? width : cropper.minWidth) : cropper
|
||||||
|
.maxWidth;
|
||||||
|
height = height <= cropper.maxHeight ? (height >= cropper.minHeight ? height : cropper.minHeight) :
|
||||||
|
cropper.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size_inspect = function() {
|
||||||
|
if ((width > cropper.maxWidth || width < cropper.minWidth || height > cropper.maxHeight || height <
|
||||||
|
cropper.minHeight) &&
|
||||||
|
lockRatio) {
|
||||||
|
size_correct();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
size_correct();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
height = cropper.CUT_START.height + (cropper.CUT_START.corner > 1 && cropper.CUT_START.corner < 4 ? 1 : -1) * (
|
||||||
|
cropper.CUT_START.y - touch[0].pageY);
|
||||||
|
switch (cropper.CUT_START.corner) {
|
||||||
|
case 1:
|
||||||
|
width = cropper.CUT_START.width - cropper.CUT_START.x + touch[0].pageX;
|
||||||
|
if (lockRatio) {
|
||||||
|
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||||
|
}
|
||||||
|
if (!size_inspect()) return;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
width = cropper.CUT_START.width - cropper.CUT_START.x + touch[0].pageX;
|
||||||
|
if (lockRatio) {
|
||||||
|
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||||
|
}
|
||||||
|
if (!size_inspect()) return;
|
||||||
|
cutY = cropper.CUT_START.cutY - (height - cropper.CUT_START.height);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
width = cropper.CUT_START.width + cropper.CUT_START.x - touch[0].pageX;
|
||||||
|
if (lockRatio) {
|
||||||
|
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||||
|
}
|
||||||
|
if (!size_inspect()) return;
|
||||||
|
cutY = cropper.CUT_START.cutY - (height - cropper.CUT_START.height);
|
||||||
|
cutX = cropper.CUT_START.cutX - (width - cropper.CUT_START.width);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
width = cropper.CUT_START.width + cropper.CUT_START.x - touch[0].pageX;
|
||||||
|
if (lockRatio) {
|
||||||
|
height = width / (cropper.canvasWidth / cropper.canvasHeight);
|
||||||
|
}
|
||||||
|
if (!size_inspect()) return;
|
||||||
|
cutX = cropper.CUT_START.cutX - (width - cropper.CUT_START.width);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!lockWidth && !lockHeight) {
|
||||||
|
cropper.canvasWidth = width;
|
||||||
|
cropper.cutX = cutX;
|
||||||
|
cropper.canvasHeight = height;
|
||||||
|
cropper.cutY = cutY;
|
||||||
|
canvasHeight(ins);
|
||||||
|
canvasWidth(ins);
|
||||||
|
} else if (!lockWidth) {
|
||||||
|
cropper.canvasWidth = width;
|
||||||
|
cropper.cutX = cutX;
|
||||||
|
canvasWidth(ins);
|
||||||
|
} else if (!lockHeight) {
|
||||||
|
cropper.canvasHeight = height;
|
||||||
|
cropper.cutY = cutY;
|
||||||
|
canvasHeight(ins);
|
||||||
|
}
|
||||||
|
styleUpdate(ins)
|
||||||
|
imgMarginDetectionScale(ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//检测剪裁框位置是否在允许的范围内(屏幕内)
|
||||||
|
function cutDetectionPosition(ins) {
|
||||||
|
var windowHeight = cropper.windowHeight,
|
||||||
|
windowWidth = cropper.windowWidth;
|
||||||
|
|
||||||
|
var cutDetectionPositionTop = function() {
|
||||||
|
//检测上边距是否在范围内
|
||||||
|
if (cropper.cutY < 0) {
|
||||||
|
cropper.cutY = 0;
|
||||||
|
}
|
||||||
|
if (cropper.cutY > windowHeight - cropper.canvasHeight) {
|
||||||
|
cropper.cutY = windowHeight - cropper.canvasHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cutDetectionPositionLeft = function() {
|
||||||
|
//检测左边距是否在范围内
|
||||||
|
if (cropper.cutX < 0) {
|
||||||
|
cropper.cutX = 0;
|
||||||
|
}
|
||||||
|
if (cropper.cutX > windowWidth - cropper.canvasWidth) {
|
||||||
|
cropper.cutX = windowWidth - cropper.canvasWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
|
||||||
|
if (cropper.cutY == null && cropper.cutX == null) {
|
||||||
|
var cutY = (windowHeight - cropper.canvasHeight) * 0.5;
|
||||||
|
var cutX = (windowWidth - cropper.canvasWidth) * 0.5;
|
||||||
|
cropper.cutY = cutY; //截取的框上边距
|
||||||
|
cropper.cutX = cutX; //截取的框左边距
|
||||||
|
} else if (cropper.cutY != null && cropper.cutX != null) {
|
||||||
|
cutDetectionPositionTop();
|
||||||
|
cutDetectionPositionLeft();
|
||||||
|
} else if (cropper.cutY != null && cropper.cutX == null) {
|
||||||
|
cutDetectionPositionTop();
|
||||||
|
cropper.cutX = (windowWidth - cropper.canvasWidth) / 2;
|
||||||
|
} else if (cropper.cutY == null && cropper.cutX != null) {
|
||||||
|
cutDetectionPositionLeft();
|
||||||
|
cropper.cutY = (windowHeight - cropper.canvasHeight) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
styleUpdate(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片边缘检测-缩放
|
||||||
|
*/
|
||||||
|
function imgMarginDetectionScale(ins, delay) {
|
||||||
|
if (!cropper.limitMove) return;
|
||||||
|
var scale = cropper.scale;
|
||||||
|
var imgWidth = cropper.imgWidth;
|
||||||
|
var imgHeight = cropper.imgHeight;
|
||||||
|
if ((cropper.angle / 90) % 2) {
|
||||||
|
imgWidth = cropper.imgHeight;
|
||||||
|
imgHeight = cropper.imgWidth;
|
||||||
|
}
|
||||||
|
if (imgWidth * scale < cropper.canvasWidth) {
|
||||||
|
scale = cropper.canvasWidth / imgWidth;
|
||||||
|
}
|
||||||
|
if (imgHeight * scale < cropper.canvasHeight) {
|
||||||
|
scale = Math.max(scale, cropper.canvasHeight / imgHeight);
|
||||||
|
}
|
||||||
|
imgMarginDetectionPosition(ins, scale, delay);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 图片边缘检测-位置
|
||||||
|
*/
|
||||||
|
function imgMarginDetectionPosition(ins, scale, delay) {
|
||||||
|
if (!cropper.limitMove) return;
|
||||||
|
var left = cropper.imgLeft;
|
||||||
|
var top = cropper.imgTop;
|
||||||
|
scale = scale || cropper.scale;
|
||||||
|
var imgWidth = cropper.imgWidth;
|
||||||
|
var imgHeight = cropper.imgHeight;
|
||||||
|
if ((cropper.angle / 90) % 2) {
|
||||||
|
imgWidth = cropper.imgHeight;
|
||||||
|
imgHeight = cropper.imgWidth;
|
||||||
|
}
|
||||||
|
left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2;
|
||||||
|
left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper
|
||||||
|
.canvasWidth -
|
||||||
|
(imgWidth * scale) / 2;
|
||||||
|
top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2;
|
||||||
|
top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper
|
||||||
|
.canvasHeight -
|
||||||
|
(imgHeight * scale) / 2;
|
||||||
|
|
||||||
|
cropper.imgLeft = left;
|
||||||
|
cropper.imgTop = top;
|
||||||
|
cropper.scale = scale;
|
||||||
|
styleUpdate(ins)
|
||||||
|
if (!delay || delay === 'null') {
|
||||||
|
imgTransform(ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cutTouchEnd(e, ins) {
|
||||||
|
moveStop(ins);
|
||||||
|
cropper.flagCutTouch = false;
|
||||||
|
updateData(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//改变截取框大小
|
||||||
|
function computeCutSize(ins) {
|
||||||
|
if (cropper.canvasWidth > cropper.windowWidth) {
|
||||||
|
cropper.canvasWidth = cropper.windowWidth;
|
||||||
|
// canvasWidth(ins)
|
||||||
|
} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
|
||||||
|
cropper.cutX = cropper.windowWidth - cropper.cutX;
|
||||||
|
}
|
||||||
|
if (cropper.canvasHeight > cropper.windowHeight) {
|
||||||
|
cropper.canvasHeight = cropper.windowHeight;
|
||||||
|
// canvasHeight(ins)
|
||||||
|
} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
|
||||||
|
cropper.cutY = cropper.windowHeight - cropper.cutY;
|
||||||
|
}
|
||||||
|
// styleUpdate(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
function styleUpdate(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
var tcb = ins.selectComponent('.tui-cropper-box')
|
||||||
|
var tcm = ins.selectComponent('.tui-content-middle')
|
||||||
|
var tct = ins.selectComponent('.tui-content-top')
|
||||||
|
var twb = ins.selectComponent('.tui-wxs-bg')
|
||||||
|
if (!tcb || !tcm || !tct || !twb) return;
|
||||||
|
tcb.setStyle({
|
||||||
|
'width': cropper.canvasWidth + 'px',
|
||||||
|
'height': cropper.canvasHeight + 'px'
|
||||||
|
})
|
||||||
|
tcm.setStyle({
|
||||||
|
'height': cropper.canvasHeight + 'px'
|
||||||
|
})
|
||||||
|
tct.setStyle({
|
||||||
|
'height': cropper.cutY + 'px'
|
||||||
|
})
|
||||||
|
twb.setStyle({
|
||||||
|
'width': cropper.cutX + 'px'
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function imgTransform(ins) {
|
||||||
|
var owner = ins.selectComponent('.tui-cropper-image')
|
||||||
|
if (!owner) return
|
||||||
|
var x = cropper.imgLeft - cropper.imgWidth / 2;
|
||||||
|
var y = cropper.imgTop - cropper.imgHeight / 2;
|
||||||
|
owner.setStyle({
|
||||||
|
'transform': 'translate3d(' + x + 'px,' + y + 'px,0) scale(' + cropper.scale + ') rotate(' + cropper
|
||||||
|
.angle + 'deg)'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageReset(ins) {
|
||||||
|
cropper.scale = 1;
|
||||||
|
cropper.angle = 0;
|
||||||
|
imgTransform(ins);
|
||||||
|
}
|
||||||
|
//监听截取框宽高变化
|
||||||
|
function canvasWidth(ins) {
|
||||||
|
if (cropper.canvasWidth < cropper.minWidth) {
|
||||||
|
cropper.canvasWidth = cropper.minWidth;
|
||||||
|
}
|
||||||
|
if (!ins) return;
|
||||||
|
computeCutSize(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasHeight(ins) {
|
||||||
|
if (cropper.canvasHeight < cropper.minHeight) {
|
||||||
|
cropper.canvasHeight = cropper.minHeight;
|
||||||
|
}
|
||||||
|
if (!ins) return;
|
||||||
|
computeCutSize(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateData(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
ins.callMethod('change', {
|
||||||
|
cutX: cropper.cutX,
|
||||||
|
cutY: cropper.cutY,
|
||||||
|
canvasWidth: cropper.canvasWidth,
|
||||||
|
canvasHeight: cropper.canvasHeight,
|
||||||
|
imgWidth: cropper.imgWidth,
|
||||||
|
imgHeight: cropper.imgHeight,
|
||||||
|
scale: cropper.scale,
|
||||||
|
angle: cropper.angle,
|
||||||
|
imgTop: cropper.imgTop,
|
||||||
|
imgLeft: cropper.imgLeft
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function propsChange(prop, oldProp, ownerInstance, ins) {
|
||||||
|
if (prop && prop !== 'null') {
|
||||||
|
var params = prop.split(',')
|
||||||
|
var type = +params[0]
|
||||||
|
var dataset = ins.getDataset();
|
||||||
|
if (cropper.init || type == 4) {
|
||||||
|
cropper.maxHeight = +dataset.maxheight;
|
||||||
|
cropper.minHeight = +dataset.minheight;
|
||||||
|
cropper.maxWidth = +dataset.maxwidth;
|
||||||
|
cropper.minWidth = +dataset.minwidth;
|
||||||
|
cropper.canvasWidth = +dataset.width;
|
||||||
|
cropper.canvasHeight = +dataset.height;
|
||||||
|
cropper.imgTop = dataset.windowheight / 2;
|
||||||
|
cropper.imgLeft = dataset.windowwidth / 2;
|
||||||
|
cropper.imgWidth = +dataset.imgwidth;
|
||||||
|
cropper.imgHeight = +dataset.imgheight;
|
||||||
|
cropper.windowHeight = +dataset.windowheight;
|
||||||
|
cropper.windowWidth = +dataset.windowwidth;
|
||||||
|
cropper.init = false
|
||||||
|
} else if (type == 2 || type == 3) {
|
||||||
|
cropper.imgWidth = +dataset.imgwidth;
|
||||||
|
cropper.imgHeight = +dataset.imgheight;
|
||||||
|
}
|
||||||
|
cropper.limitMove = bool(dataset.limitmove);
|
||||||
|
cropper.angle = +dataset.angle;
|
||||||
|
if (type == 3) {
|
||||||
|
imgTransform(ownerInstance);
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
setCutCenter(ownerInstance);
|
||||||
|
//设置裁剪框大小>设置图片尺寸>绘制canvas
|
||||||
|
computeCutSize(ownerInstance);
|
||||||
|
//检查裁剪框是否在范围内
|
||||||
|
cutDetectionPosition(ownerInstance);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setCutCenter(ownerInstance);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
imgMarginDetectionScale(ownerInstance)
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
imageReset(ownerInstance);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
setCutCenter(ownerInstance);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
touchstart: touchstart,
|
||||||
|
touchmove: touchmove,
|
||||||
|
touchend: touchend,
|
||||||
|
cutTouchStart: cutTouchStart,
|
||||||
|
cutTouchMove: cutTouchMove,
|
||||||
|
cutTouchEnd: cutTouchEnd,
|
||||||
|
propsChange: propsChange
|
||||||
|
}
|
||||||
@ -0,0 +1,147 @@
|
|||||||
|
function isObject(value) {
|
||||||
|
let type = typeof value
|
||||||
|
return !!value && (type == 'object' || type == 'function')
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounce(func, wait, options) {
|
||||||
|
let lastArgs,
|
||||||
|
lastThis,
|
||||||
|
maxWait,
|
||||||
|
result,
|
||||||
|
timerId,
|
||||||
|
lastCallTime
|
||||||
|
|
||||||
|
let lastInvokeTime = 0
|
||||||
|
let leading = false
|
||||||
|
let maxing = false
|
||||||
|
let trailing = true
|
||||||
|
|
||||||
|
// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
|
||||||
|
const useRAF = false
|
||||||
|
|
||||||
|
if (typeof func !== 'function') {
|
||||||
|
throw new TypeError('Expected a function')
|
||||||
|
}
|
||||||
|
wait = +wait || 0
|
||||||
|
if (isObject(options)) {
|
||||||
|
leading = !!options.leading
|
||||||
|
maxing = 'maxWait' in options
|
||||||
|
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
|
||||||
|
trailing = 'trailing' in options ? !!options.trailing : trailing
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeFunc(time) {
|
||||||
|
const args = lastArgs
|
||||||
|
const thisArg = lastThis
|
||||||
|
|
||||||
|
lastArgs = lastThis = undefined
|
||||||
|
lastInvokeTime = time
|
||||||
|
result = func.apply(thisArg, args)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer(pendingFunc, wait) {
|
||||||
|
return setTimeout(pendingFunc, wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelTimer(id) {
|
||||||
|
clearTimeout(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function leadingEdge(time) {
|
||||||
|
// Reset any `maxWait` timer.
|
||||||
|
lastInvokeTime = time
|
||||||
|
// Start the timer for the trailing edge.
|
||||||
|
timerId = startTimer(timerExpired, wait)
|
||||||
|
// Invoke the leading edge.
|
||||||
|
return leading ? invokeFunc(time) : result
|
||||||
|
}
|
||||||
|
|
||||||
|
function remainingWait(time) {
|
||||||
|
const timeSinceLastCall = time - lastCallTime
|
||||||
|
const timeSinceLastInvoke = time - lastInvokeTime
|
||||||
|
const timeWaiting = wait - timeSinceLastCall
|
||||||
|
|
||||||
|
return maxing
|
||||||
|
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
|
||||||
|
: timeWaiting
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldInvoke(time) {
|
||||||
|
const timeSinceLastCall = time - lastCallTime
|
||||||
|
const timeSinceLastInvoke = time - lastInvokeTime
|
||||||
|
|
||||||
|
// Either this is the first call, activity has stopped and we're at the
|
||||||
|
// trailing edge, the system time has gone backwards and we're treating
|
||||||
|
// it as the trailing edge, or we've hit the `maxWait` limit.
|
||||||
|
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
|
||||||
|
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
|
||||||
|
}
|
||||||
|
|
||||||
|
function timerExpired() {
|
||||||
|
const time = Date.now()
|
||||||
|
if (shouldInvoke(time)) {
|
||||||
|
return trailingEdge(time)
|
||||||
|
}
|
||||||
|
// Restart the timer.
|
||||||
|
timerId = startTimer(timerExpired, remainingWait(time))
|
||||||
|
}
|
||||||
|
|
||||||
|
function trailingEdge(time) {
|
||||||
|
timerId = undefined
|
||||||
|
|
||||||
|
// Only invoke if we have `lastArgs` which means `func` has been
|
||||||
|
// debounced at least once.
|
||||||
|
if (trailing && lastArgs) {
|
||||||
|
return invokeFunc(time)
|
||||||
|
}
|
||||||
|
lastArgs = lastThis = undefined
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
if (timerId !== undefined) {
|
||||||
|
cancelTimer(timerId)
|
||||||
|
}
|
||||||
|
lastInvokeTime = 0
|
||||||
|
lastArgs = lastCallTime = lastThis = timerId = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function flush() {
|
||||||
|
return timerId === undefined ? result : trailingEdge(Date.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
function pending() {
|
||||||
|
return timerId !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounced(...args) {
|
||||||
|
const time = Date.now()
|
||||||
|
const isInvoking = shouldInvoke(time)
|
||||||
|
|
||||||
|
lastArgs = args
|
||||||
|
lastThis = this
|
||||||
|
lastCallTime = time
|
||||||
|
|
||||||
|
if (isInvoking) {
|
||||||
|
if (timerId === undefined) {
|
||||||
|
return leadingEdge(lastCallTime)
|
||||||
|
}
|
||||||
|
if (maxing) {
|
||||||
|
// Handle invocations in a tight loop.
|
||||||
|
timerId = startTimer(timerExpired, wait)
|
||||||
|
return invokeFunc(lastCallTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timerId === undefined) {
|
||||||
|
timerId = startTimer(timerExpired, wait)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
debounced.cancel = cancel
|
||||||
|
debounced.flush = flush
|
||||||
|
debounced.pending = pending
|
||||||
|
return debounced
|
||||||
|
}
|
||||||
|
|
||||||
|
export default debounce
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
export const mapVirtualToProps = ({
|
||||||
|
items,
|
||||||
|
itemHeight
|
||||||
|
}, {
|
||||||
|
startIndex,
|
||||||
|
endIndex
|
||||||
|
}) => {
|
||||||
|
const visibleItems = endIndex > -1 ? items.slice(startIndex, endIndex + 1) : []
|
||||||
|
|
||||||
|
// style
|
||||||
|
const height = items.length * itemHeight
|
||||||
|
const paddingTop = startIndex * itemHeight
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: visibleItems,
|
||||||
|
style: `height: ${height}px; padding-top: ${paddingTop}px; box-sizing: border-box;`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getVisibleItemBounds = (viewTop, viewHeight, itemCount, itemHeight, itemBuffer) => {
|
||||||
|
// visible list inside view
|
||||||
|
const listViewTop = Math.max(0, viewTop)
|
||||||
|
|
||||||
|
// visible item indexes
|
||||||
|
const startIndex = Math.max(0, Math.floor(listViewTop / itemHeight) - 12)
|
||||||
|
const endIndex = Math.min(startIndex + Math.ceil(viewHeight / itemHeight) + itemBuffer - 1, itemCount)
|
||||||
|
|
||||||
|
return {
|
||||||
|
startIndex,
|
||||||
|
endIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
517
src/components/thorui/tui-cropper/tui-cropper.vue
Normal file
517
src/components/thorui/tui-cropper/tui-cropper.vue
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-cropper__box" @touchmove.stop.prevent="stop">
|
||||||
|
<image @load="imageLoad" @error="imageLoad" @touchstart="parse.touchstart" @touchmove="parse.touchmove"
|
||||||
|
@touchend="parse.touchend" :data-minScale="minScale" :data-maxScale="maxScale" :style="{
|
||||||
|
width: (imgWidth ? imgWidth : width) + 'px',
|
||||||
|
height: imgHeight ? imgHeight + 'px' : 'auto',
|
||||||
|
transitionDuration: (animation ? 0.3 : 0) + 's'
|
||||||
|
}" class="tui-cropper__image" :class="{'tui-cropper__image-hidden':!imageUrl}" :src="imageUrl" mode="widthFix">
|
||||||
|
</image>
|
||||||
|
<view class="tui-backdrop__cropper"
|
||||||
|
:style="{ width: width + 'px', height: height + 'px', borderRadius: isRound ? '50%' : '0' }">
|
||||||
|
<view class="tui-cropper__border" :change:prop="parse.propsChange" :prop="props" :data-width="width"
|
||||||
|
:data-height="height" :data-windowHeight="sysInfo.windowHeight || 600"
|
||||||
|
:data-windowWidth="sysInfo.windowWidth || 400" :data-imgTop="imgTop" :data-imgLeft="imgLeft"
|
||||||
|
:data-imgWidth="imgWidth" :data-imgHeight="imgHeight" :data-angle="angle"
|
||||||
|
:style="{ borderRadius: isRound ? '50%' : '0', border: border }"></view>
|
||||||
|
</view>
|
||||||
|
<canvas canvas-id="tui-image__cropper" id="tui-image__cropper" :disable-scroll="true"
|
||||||
|
:style="{ width: width * scaleRatio + 'px', height: height * scaleRatio + 'px' }"
|
||||||
|
class="tui-cropper__canvas"></canvas>
|
||||||
|
<view class="tui-cropper__tabbar" v-if="!custom">
|
||||||
|
<view class="tui-op__btn" @tap.stop="back">取消</view>
|
||||||
|
<image :src="rotateImg" class="tui-rotate__img" @tap="setAngle"></image>
|
||||||
|
<view class="tui-op__btn" @tap.stop="getImage">完成</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script src="./tui-cropper.wxs" module="parse" lang="wxs"></script>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiCropper',
|
||||||
|
emits: ['ready', 'cropper', 'imageLoad', 'initAngle'],
|
||||||
|
props: {
|
||||||
|
//图片路径
|
||||||
|
imageUrl: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//裁剪框高度 px
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 280
|
||||||
|
},
|
||||||
|
//裁剪框宽度 px
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 280
|
||||||
|
},
|
||||||
|
//是否为圆形裁剪框
|
||||||
|
isRound: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//裁剪框边框
|
||||||
|
border: {
|
||||||
|
type: String,
|
||||||
|
default: '1px solid #fff'
|
||||||
|
},
|
||||||
|
//生成的图片尺寸相对剪裁框的比例
|
||||||
|
scaleRatio: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
//图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理
|
||||||
|
quality: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.8
|
||||||
|
},
|
||||||
|
//图片旋转角度
|
||||||
|
rotateAngle: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//图片最小缩放比
|
||||||
|
minScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.5
|
||||||
|
},
|
||||||
|
//图片最大缩放比
|
||||||
|
maxScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
//自定义操作栏(为true时隐藏底部操作栏)
|
||||||
|
custom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//值发生改变开始裁剪(custom为true时生效)
|
||||||
|
startCutting: {
|
||||||
|
type: [Number, Boolean],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 是否返回base64(H5端默认base64)
|
||||||
|
* 支持平台:App,微信小程序,支付宝小程序,H5(默认url就是base64)
|
||||||
|
**/
|
||||||
|
isBase64: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//裁剪时是否显示loadding
|
||||||
|
loadding: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//旋转icon
|
||||||
|
rotateImg: {
|
||||||
|
type: String,
|
||||||
|
default: '/static/components/cropper/img_rotate.png'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
TIME_CUT_CENTER: null,
|
||||||
|
cutX: 0, //画布x轴起点
|
||||||
|
cutY: 0, //画布y轴起点0
|
||||||
|
imgWidth: 0,
|
||||||
|
imgHeight: 0,
|
||||||
|
scale: 1, //图片缩放比
|
||||||
|
angle: 0, //图片旋转角度
|
||||||
|
animation: false, //是否开启图片过渡效果
|
||||||
|
animationTime: null,
|
||||||
|
imgTop: 0,
|
||||||
|
imgLeft: 0,
|
||||||
|
ctx: null,
|
||||||
|
sysInfo: {},
|
||||||
|
props: '',
|
||||||
|
sizeChange: 0, //2
|
||||||
|
angleChange: 0, //3
|
||||||
|
resetChange: 0, //4
|
||||||
|
centerChange: 0 //5
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
//定义变量然后利用change触发
|
||||||
|
imageUrl(val, oldVal) {
|
||||||
|
this.imageReset();
|
||||||
|
this.showLoading();
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: val,
|
||||||
|
success: res => {
|
||||||
|
//计算图片尺寸
|
||||||
|
this.imgComputeSize(res.width, res.height);
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
},
|
||||||
|
fail: err => {
|
||||||
|
this.imgComputeSize();
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rotateAngle(val) {
|
||||||
|
this.animation = true;
|
||||||
|
this.angle = val;
|
||||||
|
this.angleChanged(val);
|
||||||
|
},
|
||||||
|
animation(val) {
|
||||||
|
//开启过渡220毫秒之后自动关闭
|
||||||
|
clearTimeout(this.animationTime);
|
||||||
|
if (val) {
|
||||||
|
this.animationTime = setTimeout(() => {
|
||||||
|
this.animation = false;
|
||||||
|
}, 220);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startCutting(val) {
|
||||||
|
if (this.custom && val) {
|
||||||
|
this.getImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.sysInfo = uni.getSystemInfoSync();
|
||||||
|
this.imgTop = this.sysInfo.windowHeight / 2;
|
||||||
|
this.imgLeft = this.sysInfo.windowWidth / 2;
|
||||||
|
this.ctx = uni.createCanvasContext('tui-image__cropper', this);
|
||||||
|
//初始化
|
||||||
|
setTimeout(() => {
|
||||||
|
this.props = '1,1';
|
||||||
|
}, 0);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$emit('ready', {});
|
||||||
|
}, 200);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
//网络图片转成本地文件[同步执行]
|
||||||
|
async getLocalImage(url) {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
uni.downloadFile({
|
||||||
|
url: url,
|
||||||
|
success: res => {
|
||||||
|
resolve(res.tempFilePath);
|
||||||
|
},
|
||||||
|
fail: res => {
|
||||||
|
reject(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//返回裁剪后图片信息
|
||||||
|
getImage() {
|
||||||
|
if (!this.imageUrl) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请选择图片',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadding && this.showLoading();
|
||||||
|
let draw = async () => {
|
||||||
|
//图片实际大小
|
||||||
|
let imgWidth = this.imgWidth * this.scale * this.scaleRatio;
|
||||||
|
let imgHeight = this.imgHeight * this.scale * this.scaleRatio;
|
||||||
|
//canvas和图片的相对距离
|
||||||
|
let xpos = this.imgLeft - this.cutX;
|
||||||
|
let ypos = this.imgTop - this.cutY;
|
||||||
|
//旋转画布
|
||||||
|
this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio);
|
||||||
|
this.ctx.rotate((this.angle * Math.PI) / 180);
|
||||||
|
let imgUrl = this.imageUrl;
|
||||||
|
// #ifdef APP-PLUS || MP-WEIXIN
|
||||||
|
if (~this.imageUrl.indexOf('https:')) {
|
||||||
|
imgUrl = await this.getLocalImage(this.imageUrl);
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
this.ctx.drawImage(imgUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
|
||||||
|
this.ctx.draw(false, () => {
|
||||||
|
let params = {
|
||||||
|
width: this.width * this.scaleRatio,
|
||||||
|
height: Math.round(this.height * this.scaleRatio),
|
||||||
|
destWidth: this.width * this.scaleRatio,
|
||||||
|
destHeight: Math.round(this.height) * this.scaleRatio,
|
||||||
|
fileType: 'png',
|
||||||
|
quality: this.quality
|
||||||
|
};
|
||||||
|
let data = {
|
||||||
|
url: '',
|
||||||
|
base64: '',
|
||||||
|
width: this.width * this.scaleRatio,
|
||||||
|
height: this.height * this.scaleRatio
|
||||||
|
};
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
|
||||||
|
if (this.isBase64) {
|
||||||
|
this.ctx.toDataURL(params).then(dataURL => {
|
||||||
|
data.base64 = dataURL;
|
||||||
|
this.loadding && uni.hideLoading();
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.ctx.toTempFilePath({
|
||||||
|
...params,
|
||||||
|
success: res => {
|
||||||
|
data.url = res.apFilePath;
|
||||||
|
this.loadding && uni.hideLoading();
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-ALIPAY
|
||||||
|
let isBase64 = this.isBase64
|
||||||
|
// #ifdef MP-BAIDU || MP-TOUTIAO || H5
|
||||||
|
isBase64 = false;
|
||||||
|
// #endif
|
||||||
|
if (isBase64) {
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.canvasGetImageData({
|
||||||
|
canvasId: 'tui-image__cropper',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: this.width * this.scaleRatio,
|
||||||
|
height: Math.round(this.height * this.scaleRatio),
|
||||||
|
success: res => {
|
||||||
|
const arrayBuffer = new Uint8Array(res.data);
|
||||||
|
const base64 = uni.arrayBufferToBase64(arrayBuffer);
|
||||||
|
data.base64 = base64;
|
||||||
|
this.loadding && uni.hideLoading();
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
|
...params,
|
||||||
|
canvasId: 'tui-image__cropper',
|
||||||
|
success: res => {
|
||||||
|
data.url = res.tempFilePath;
|
||||||
|
const fs = wx.getFileSystemManager()
|
||||||
|
// 读取文件, base64 格式
|
||||||
|
const base64Str = fs.readFileSync(res.tempFilePath,
|
||||||
|
'base64')
|
||||||
|
const imgBase64 = `data:image/png;base64,${base64Str}`
|
||||||
|
data.base64 = imgBase64;
|
||||||
|
this.loadding && uni.hideLoading();
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
},
|
||||||
|
fail(res) {
|
||||||
|
console.log(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
// #endif
|
||||||
|
} else {
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
|
...params,
|
||||||
|
canvasId: 'tui-image__cropper',
|
||||||
|
success: res => {
|
||||||
|
data.url = res.tempFilePath;
|
||||||
|
// #ifdef H5
|
||||||
|
data.base64 = res.tempFilePath;
|
||||||
|
// #endif
|
||||||
|
this.loadding && uni.hideLoading();
|
||||||
|
this.$emit('cropper', data);
|
||||||
|
},
|
||||||
|
fail(res) {
|
||||||
|
console.log(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
});
|
||||||
|
};
|
||||||
|
draw();
|
||||||
|
},
|
||||||
|
change(e) {
|
||||||
|
this.cutX = e.cutX || 0;
|
||||||
|
this.cutY = e.cutY || 0;
|
||||||
|
this.imgWidth = e.imgWidth || this.imgWidth;
|
||||||
|
this.imgHeight = e.imgHeight || this.imgHeight;
|
||||||
|
this.scale = e.scale || 1;
|
||||||
|
this.angle = e.angle || 0;
|
||||||
|
this.imgTop = e.imgTop || 0;
|
||||||
|
this.imgLeft = e.imgLeft || 0;
|
||||||
|
},
|
||||||
|
imageReset() {
|
||||||
|
this.scale = 1;
|
||||||
|
this.angle = 0;
|
||||||
|
let sys = this.sysInfo.windowHeight ? this.sysInfo : uni.getSystemInfoSync();
|
||||||
|
this.imgTop = sys.windowHeight / 2;
|
||||||
|
this.imgLeft = sys.windowWidth / 2;
|
||||||
|
this.resetChange++;
|
||||||
|
this.props = `4,${this.resetChange}`;
|
||||||
|
//初始化旋转角度 0deg
|
||||||
|
this.$emit('initAngle', {});
|
||||||
|
},
|
||||||
|
imgComputeSize(width, height) {
|
||||||
|
//默认按图片最小边 = 对应裁剪框尺寸
|
||||||
|
let imgWidth = width,
|
||||||
|
imgHeight = height;
|
||||||
|
if (imgWidth && imgHeight) {
|
||||||
|
if (imgWidth / imgHeight > this.width / this.height) {
|
||||||
|
imgHeight = this.height;
|
||||||
|
imgWidth = (width / height) * imgHeight;
|
||||||
|
} else {
|
||||||
|
imgWidth = this.width;
|
||||||
|
imgHeight = (height / width) * imgWidth;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let sys = this.sysInfo.windowHeight ? this.sysInfo : uni.getSystemInfoSync();
|
||||||
|
imgWidth = sys.windowWidth;
|
||||||
|
imgHeight = 0;
|
||||||
|
}
|
||||||
|
this.imgWidth = imgWidth;
|
||||||
|
this.imgHeight = imgHeight;
|
||||||
|
this.sizeChange++;
|
||||||
|
this.props = `2,${this.sizeChange}`;
|
||||||
|
},
|
||||||
|
imageLoad(e) {
|
||||||
|
this.imageReset();
|
||||||
|
uni.hideLoading();
|
||||||
|
this.$emit('imageLoad', {});
|
||||||
|
},
|
||||||
|
moveStop() {
|
||||||
|
clearTimeout(this.TIME_CUT_CENTER);
|
||||||
|
this.TIME_CUT_CENTER = setTimeout(() => {
|
||||||
|
this.centerChange++;
|
||||||
|
this.props = `5,${this.centerChange}`;
|
||||||
|
}, 666);
|
||||||
|
},
|
||||||
|
moveDuring() {
|
||||||
|
clearTimeout(this.TIME_CUT_CENTER);
|
||||||
|
},
|
||||||
|
showLoading() {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '请稍候...',
|
||||||
|
mask: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop() {},
|
||||||
|
back() {
|
||||||
|
uni.navigateBack();
|
||||||
|
},
|
||||||
|
angleChanged(val) {
|
||||||
|
this.moveStop();
|
||||||
|
if (val % 90) {
|
||||||
|
this.angle = Math.round(val / 90) * 90;
|
||||||
|
}
|
||||||
|
this.angleChange++;
|
||||||
|
this.props = `3,${this.angleChange}`;
|
||||||
|
},
|
||||||
|
setAngle() {
|
||||||
|
this.animation = true;
|
||||||
|
this.angle = this.angle + 90;
|
||||||
|
this.angleChanged(this.angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-cropper__box {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper__image {
|
||||||
|
width: 100%;
|
||||||
|
border-style: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper__image-hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper__canvas {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10;
|
||||||
|
left: -2000px;
|
||||||
|
top: -2000px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-backdrop__cropper {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 4;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border: 3000px solid rgba(0, 0, 0, 0.55);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper__border {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper__tabbar {
|
||||||
|
width: 100%;
|
||||||
|
height: 120rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cropper__tabbar::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||||
|
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-op__btn {
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-rotate__img {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
321
src/components/thorui/tui-cropper/tui-cropper.wxs
Normal file
321
src/components/thorui/tui-cropper/tui-cropper.wxs
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
var cropper = {
|
||||||
|
cutX: 0, //画布x轴起点
|
||||||
|
cutY: 0, //画布y轴起点0
|
||||||
|
touchRelative: [{
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}], //手指或鼠标和图片中心的相对位置
|
||||||
|
hypotenuseLength: 0, //双指触摸时斜边长度
|
||||||
|
flagEndTouch: false, //是否结束触摸
|
||||||
|
canvasWidth: 0,
|
||||||
|
canvasHeight: 0,
|
||||||
|
imgWidth: 0, //图片宽度
|
||||||
|
imgHeight: 0, //图片高度
|
||||||
|
scale: 1, //图片缩放比
|
||||||
|
angle: 0, //图片旋转角度
|
||||||
|
imgTop: 0, //图片上边距
|
||||||
|
imgLeft: 0, //图片左边距
|
||||||
|
windowHeight: 0,
|
||||||
|
windowWidth: 0,
|
||||||
|
init: true
|
||||||
|
}
|
||||||
|
|
||||||
|
function bool(str) {
|
||||||
|
return str === 'true' || str == true ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchstart(e, ins) {
|
||||||
|
var touch = e.touches || e.changedTouches;
|
||||||
|
cropper.flagEndTouch = false;
|
||||||
|
if (touch.length == 1) {
|
||||||
|
cropper.touchRelative[0] = {
|
||||||
|
x: touch[0].pageX - cropper.imgLeft,
|
||||||
|
y: touch[0].pageY - cropper.imgTop
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var width = Math.abs(touch[0].pageX - touch[1].pageX);
|
||||||
|
var height = Math.abs(touch[0].pageY - touch[1].pageY);
|
||||||
|
cropper.touchRelative = [{
|
||||||
|
x: touch[0].pageX - cropper.imgLeft,
|
||||||
|
y: touch[0].pageY - cropper.imgTop
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: touch[1].pageX - cropper.imgLeft,
|
||||||
|
y: touch[1].pageY - cropper.imgTop
|
||||||
|
}
|
||||||
|
];
|
||||||
|
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDuring(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
ins.callMethod('moveDuring')
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveStop(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
ins.callMethod('moveStop')
|
||||||
|
};
|
||||||
|
|
||||||
|
function setCutCenter(ins) {
|
||||||
|
var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5;
|
||||||
|
var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5;
|
||||||
|
//顺序不能变
|
||||||
|
cropper.imgTop = cropper.imgTop - cropper.cutY + cutY;
|
||||||
|
cropper.cutY = cutY; //截取的框上边距
|
||||||
|
cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX;
|
||||||
|
cropper.cutX = cutX; //截取的框左边距
|
||||||
|
cutDetectionPosition(ins)
|
||||||
|
imgTransform(ins)
|
||||||
|
updateData(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchmove(e, ins) {
|
||||||
|
var touch = e.touches || e.changedTouches;
|
||||||
|
if (cropper.flagEndTouch) return;
|
||||||
|
moveDuring(ins);
|
||||||
|
if (e.touches.length == 1) {
|
||||||
|
var left = touch[0].pageX - cropper.touchRelative[0].x,
|
||||||
|
top = touch[0].pageY - cropper.touchRelative[0].y;
|
||||||
|
cropper.imgLeft = left;
|
||||||
|
cropper.imgTop = top;
|
||||||
|
imgTransform(ins);
|
||||||
|
imgMarginDetectionPosition(ins);
|
||||||
|
} else {
|
||||||
|
var res = e.instance.getDataset();
|
||||||
|
var minScale = +res.minscale;
|
||||||
|
var maxScale = +res.maxscale;
|
||||||
|
var width = Math.abs(touch[0].pageX - touch[1].pageX),
|
||||||
|
height = Math.abs(touch[0].pageY - touch[1].pageY),
|
||||||
|
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
|
||||||
|
scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
|
||||||
|
current_deg = 0;
|
||||||
|
scale = scale <= minScale ? minScale : scale;
|
||||||
|
scale = scale >= maxScale ? maxScale : scale;
|
||||||
|
cropper.scale = scale;
|
||||||
|
imgMarginDetectionScale(ins, true);
|
||||||
|
var touchRelative = [{
|
||||||
|
x: touch[0].pageX - cropper.imgLeft,
|
||||||
|
y: touch[0].pageY - cropper.imgTop
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: touch[1].pageX - cropper.imgLeft,
|
||||||
|
y: touch[1].pageY - cropper.imgTop
|
||||||
|
}
|
||||||
|
];
|
||||||
|
cropper.touchRelative = touchRelative;
|
||||||
|
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
|
||||||
|
//更新视图
|
||||||
|
cropper.angle = cropper.angle + current_deg;
|
||||||
|
imgTransform(ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchend(e, ins) {
|
||||||
|
cropper.flagEndTouch = true;
|
||||||
|
moveStop(ins);
|
||||||
|
updateData(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//检测剪裁框位置是否在允许的范围内(屏幕内)
|
||||||
|
function cutDetectionPosition(ins) {
|
||||||
|
var windowHeight = cropper.windowHeight,
|
||||||
|
windowWidth = cropper.windowWidth;
|
||||||
|
|
||||||
|
var cutDetectionPositionTop = function() {
|
||||||
|
//检测上边距是否在范围内
|
||||||
|
if (cropper.cutY < 0) {
|
||||||
|
cropper.cutY = 0;
|
||||||
|
}
|
||||||
|
if (cropper.cutY > windowHeight - cropper.canvasHeight) {
|
||||||
|
cropper.cutY = windowHeight - cropper.canvasHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cutDetectionPositionLeft = function() {
|
||||||
|
//检测左边距是否在范围内
|
||||||
|
if (cropper.cutX < 0) {
|
||||||
|
cropper.cutX = 0;
|
||||||
|
}
|
||||||
|
if (cropper.cutX > windowWidth - cropper.canvasWidth) {
|
||||||
|
cropper.cutX = windowWidth - cropper.canvasWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
|
||||||
|
if (cropper.cutY == null && cropper.cutX == null) {
|
||||||
|
var cutY = (windowHeight - cropper.canvasHeight) * 0.5;
|
||||||
|
var cutX = (windowWidth - cropper.canvasWidth) * 0.5;
|
||||||
|
cropper.cutY = cutY; //截取的框上边距
|
||||||
|
cropper.cutX = cutX; //截取的框左边距
|
||||||
|
} else if (cropper.cutY != null && cropper.cutX != null) {
|
||||||
|
cutDetectionPositionTop();
|
||||||
|
cutDetectionPositionLeft();
|
||||||
|
} else if (cropper.cutY != null && cropper.cutX == null) {
|
||||||
|
cutDetectionPositionTop();
|
||||||
|
cropper.cutX = (windowWidth - cropper.canvasWidth) / 2;
|
||||||
|
} else if (cropper.cutY == null && cropper.cutX != null) {
|
||||||
|
cutDetectionPositionLeft();
|
||||||
|
cropper.cutY = (windowHeight - cropper.canvasHeight) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片边缘检测-缩放
|
||||||
|
*/
|
||||||
|
function imgMarginDetectionScale(ins, delay) {
|
||||||
|
var scale = cropper.scale;
|
||||||
|
var imgWidth = cropper.imgWidth;
|
||||||
|
var imgHeight = cropper.imgHeight;
|
||||||
|
if ((cropper.angle / 90) % 2) {
|
||||||
|
imgWidth = cropper.imgHeight;
|
||||||
|
imgHeight = cropper.imgWidth;
|
||||||
|
}
|
||||||
|
if (imgWidth * scale < cropper.canvasWidth) {
|
||||||
|
scale = cropper.canvasWidth / imgWidth;
|
||||||
|
}
|
||||||
|
if (imgHeight * scale < cropper.canvasHeight) {
|
||||||
|
scale = Math.max(scale, cropper.canvasHeight / imgHeight);
|
||||||
|
}
|
||||||
|
imgMarginDetectionPosition(ins, scale, delay);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 图片边缘检测-位置
|
||||||
|
*/
|
||||||
|
function imgMarginDetectionPosition(ins, scale, delay) {
|
||||||
|
var left = cropper.imgLeft;
|
||||||
|
var top = cropper.imgTop;
|
||||||
|
scale = scale || cropper.scale;
|
||||||
|
var imgWidth = cropper.imgWidth;
|
||||||
|
var imgHeight = cropper.imgHeight;
|
||||||
|
if ((cropper.angle / 90) % 2) {
|
||||||
|
imgWidth = cropper.imgHeight;
|
||||||
|
imgHeight = cropper.imgWidth;
|
||||||
|
}
|
||||||
|
left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2;
|
||||||
|
left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper.canvasWidth -
|
||||||
|
(imgWidth * scale) / 2;
|
||||||
|
top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2;
|
||||||
|
top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper.canvasHeight -
|
||||||
|
(imgHeight * scale) / 2;
|
||||||
|
|
||||||
|
cropper.imgLeft = left;
|
||||||
|
cropper.imgTop = top;
|
||||||
|
cropper.scale = scale;
|
||||||
|
if (!delay || delay === 'null') {
|
||||||
|
imgTransform(ins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//改变截取框大小
|
||||||
|
function computeCutSize(ins) {
|
||||||
|
if (cropper.canvasWidth > cropper.windowWidth) {
|
||||||
|
cropper.canvasWidth = cropper.windowWidth;
|
||||||
|
} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
|
||||||
|
cropper.cutX = cropper.windowWidth - cropper.cutX;
|
||||||
|
}
|
||||||
|
if (cropper.canvasHeight > cropper.windowHeight) {
|
||||||
|
cropper.canvasHeight = cropper.windowHeight;
|
||||||
|
} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
|
||||||
|
cropper.cutY = cropper.windowHeight - cropper.cutY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function imgTransform(ins) {
|
||||||
|
var owner = ins.selectComponent('.tui-cropper__image')
|
||||||
|
if (!owner) return
|
||||||
|
var x = cropper.imgLeft - cropper.imgWidth / 2;
|
||||||
|
var y = cropper.imgTop - cropper.imgHeight / 2;
|
||||||
|
owner.setStyle({
|
||||||
|
'transform': 'translate3d(' + x + 'px,' + y + 'px,0) scale(' + cropper.scale + ') rotate(' + cropper.angle + 'deg)'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageReset(ins) {
|
||||||
|
cropper.scale = 1;
|
||||||
|
cropper.angle = 0;
|
||||||
|
imgTransform(ins);
|
||||||
|
}
|
||||||
|
//监听截取框宽高变化
|
||||||
|
function canvasWidth(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
computeCutSize(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasHeight(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
computeCutSize(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateData(ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
ins.callMethod('change', {
|
||||||
|
cutX: cropper.cutX,
|
||||||
|
cutY: cropper.cutY,
|
||||||
|
imgWidth: cropper.imgWidth,
|
||||||
|
imgHeight: cropper.imgHeight,
|
||||||
|
scale: cropper.scale,
|
||||||
|
angle: cropper.angle,
|
||||||
|
imgTop: cropper.imgTop,
|
||||||
|
imgLeft: cropper.imgLeft
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function propsChange(prop, oldProp, ownerInstance, ins) {
|
||||||
|
if (prop && prop !== 'null') {
|
||||||
|
var params = prop.split(',')
|
||||||
|
var type = +params[0]
|
||||||
|
var dataset = ins.getDataset();
|
||||||
|
if (cropper.init || type == 4) {
|
||||||
|
cropper.canvasWidth = +dataset.width;
|
||||||
|
cropper.canvasHeight = +dataset.height;
|
||||||
|
cropper.imgTop = dataset.windowheight / 2;
|
||||||
|
cropper.imgLeft = dataset.windowwidth / 2;
|
||||||
|
cropper.imgWidth = +dataset.imgwidth;
|
||||||
|
cropper.imgHeight = +dataset.imgheight;
|
||||||
|
cropper.windowHeight = +dataset.windowheight;
|
||||||
|
cropper.windowWidth = +dataset.windowwidth;
|
||||||
|
cropper.init = false
|
||||||
|
} else if (type == 2 || type == 3) {
|
||||||
|
cropper.imgWidth = +dataset.imgwidth;
|
||||||
|
cropper.imgHeight = +dataset.imgheight;
|
||||||
|
}
|
||||||
|
cropper.angle = +dataset.angle;
|
||||||
|
if (type == 3) {
|
||||||
|
imgTransform(ownerInstance);
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
setCutCenter(ownerInstance);
|
||||||
|
//设置裁剪框大小>设置图片尺寸>绘制canvas
|
||||||
|
computeCutSize(ownerInstance);
|
||||||
|
//检查裁剪框是否在范围内
|
||||||
|
cutDetectionPosition(ownerInstance);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setCutCenter(ownerInstance);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
imgMarginDetectionScale(ownerInstance)
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
imageReset(ownerInstance);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
setCutCenter(ownerInstance);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
touchstart: touchstart,
|
||||||
|
touchmove: touchmove,
|
||||||
|
touchend: touchend,
|
||||||
|
propsChange: propsChange
|
||||||
|
}
|
||||||
196
src/components/thorui/tui-cubic-bezier/tui-cubic-bezier.vue
Normal file
196
src/components/thorui/tui-cubic-bezier/tui-cubic-bezier.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-cubic__bezier" :class="{ 'tui-animate__bezier': animate }" :style="{ left: left, top: top, transform: transform_x }" @tap.stop="handleClick">
|
||||||
|
<view
|
||||||
|
class="tui-badge__bezier"
|
||||||
|
:class="{ 'tui-animate__bezier_y': animate }"
|
||||||
|
:style="{ width: width, height: height, borderRadius: height, backgroundColor: getBgColor, transform: transform_y }"
|
||||||
|
>
|
||||||
|
<text v-if="!custom">1</text>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiCubicBezier',
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//屏幕宽度[总宽度] px
|
||||||
|
windowWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 375
|
||||||
|
},
|
||||||
|
//屏幕高度[总高度] px
|
||||||
|
windowHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 667
|
||||||
|
},
|
||||||
|
//badge宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '40rpx'
|
||||||
|
},
|
||||||
|
//badge高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '40rpx'
|
||||||
|
},
|
||||||
|
//动画badge背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//badge left
|
||||||
|
left: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//badge top
|
||||||
|
top: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//是否自定义动画显示内容
|
||||||
|
custom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//动画方向: up-右上(top,right),down-右下(right,bottom), bottom-左下(left,bottom),top-左上(left,top)
|
||||||
|
//括号中为position必传值
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'down'
|
||||||
|
},
|
||||||
|
//根据方向传值,终点的坐标点,一般为元素中心位置
|
||||||
|
position: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
top: 50,
|
||||||
|
bottom: 80,
|
||||||
|
left: 30,
|
||||||
|
right: 40
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//列表中索引 index
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//自定义参数
|
||||||
|
params: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
getBgColor(){
|
||||||
|
return this.backgroundColor || (uni && uni.$tui && uni.$tui.color.danger) || '#EB0909'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
time: 0,
|
||||||
|
animate: false,
|
||||||
|
transform_x: '',
|
||||||
|
transform_y: '',
|
||||||
|
sys: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick(e) {
|
||||||
|
//550ms内防止重复点击
|
||||||
|
if (new Date().getTime() - this.time <= 550) return;
|
||||||
|
this.time = new Date().getTime();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.time = 0;
|
||||||
|
}, 550);
|
||||||
|
this.bezierEffect(e);
|
||||||
|
this.$emit('click', {
|
||||||
|
index: this.index,
|
||||||
|
params: this.params
|
||||||
|
});
|
||||||
|
},
|
||||||
|
bezierEffect(e) {
|
||||||
|
let touch = null;
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
touch = e.detail;
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-BAIDU
|
||||||
|
touch = e.changedTouches[0]
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef MP-ALIPAY || MP-BAIDU
|
||||||
|
touch = e.touches[0];
|
||||||
|
// #endif
|
||||||
|
let diff = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
//up-右上,down-右下, bottom-左下,top-左上
|
||||||
|
switch (this.direction) {
|
||||||
|
case 'up':
|
||||||
|
diff.x = this.windowWidth - touch.clientX - this.position.right;
|
||||||
|
diff.y = this.position.top - touch.clientY;
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
diff.x = this.windowWidth - touch.clientX - this.position.right;
|
||||||
|
diff.y = this.windowHeight - touch.clientY - this.position.bottom;
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
diff.x = this.position.left - touch.clientX;
|
||||||
|
diff.y = this.windowHeight - touch.clientY - this.position.bottom;
|
||||||
|
break;
|
||||||
|
case 'top':
|
||||||
|
diff.x = this.position.left - touch.clientX;
|
||||||
|
diff.y = this.position.top - touch.clientY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//移动距离
|
||||||
|
this.animate = true;
|
||||||
|
this.transform_x = `translate3d(${diff.x}px,0,0)`;
|
||||||
|
this.transform_y = `translate3d(0,${diff.y}px,0) rotate(350deg) scale(0.8)`;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.animate = false;
|
||||||
|
//恢复到最初状态
|
||||||
|
this.transform_x = 'none';
|
||||||
|
this.transform_y = 'none';
|
||||||
|
}, 550);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-cubic__bezier {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-animate__bezier {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 990;
|
||||||
|
transition: opacity 0.1s, transform cubic-bezier(0, 0, 0, 0) 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-animate__bezier_y {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 990;
|
||||||
|
transition: opacity 0.1s, transform cubic-bezier(0.3, -0.2, 1, 0) 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-badge__bezier {
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
321
src/components/thorui/tui-data-checkbox/tui-data-checkbox.vue
Normal file
321
src/components/thorui/tui-data-checkbox/tui-data-checkbox.vue
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-data-checkbox" :style="{marginBottom:'-'+gap+'rpx'}">
|
||||||
|
<view class="tui-data__cb-item" :class="{'tui-data__cb-disabled':item.disable}"
|
||||||
|
:style="{width:width?width+'rpx':'auto',padding:padding,borderRadius:radius,marginRight:gap+'rpx',marginBottom:gap+'rpx',background:item.checked?activeBgColor:background,borderColor:item.checked?getBorderColor:(defaultBorderColor || background)}"
|
||||||
|
v-for="(item,index) in itemList" :key="index" @tap.stop="itemTap(index)">
|
||||||
|
<text class="tui-cb__text"
|
||||||
|
:style="{fontSize:size+'rpx',lineHeight:size+'rpx',color:item.checked?getActiveColor:color}">{{item[textField]}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-data-checkbox",
|
||||||
|
emits: ['change', 'input', 'update:modelValue'],
|
||||||
|
props: {
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value'
|
||||||
|
},
|
||||||
|
disableField: {
|
||||||
|
type: String,
|
||||||
|
default: 'disable'
|
||||||
|
},
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue: {
|
||||||
|
type: [Array, String, Number],
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
value: {
|
||||||
|
type: [Array, String, Number],
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//最小选择数,仅单选时有效,可选值0、1
|
||||||
|
min: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//最大选择数
|
||||||
|
max: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '20rpx 32rpx'
|
||||||
|
},
|
||||||
|
gap: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '8rpx'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
activeColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
activeBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(86, 119, 252, 0.1)'
|
||||||
|
},
|
||||||
|
defaultBorderColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getActiveColor() {
|
||||||
|
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
},
|
||||||
|
getBorderColor() {
|
||||||
|
return this.borderColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
options(vals) {
|
||||||
|
//value,text,disable
|
||||||
|
this.initData(vals)
|
||||||
|
},
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue(vals) {
|
||||||
|
this.modelChange(vals)
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
value(vals) {
|
||||||
|
this.modelChange(vals)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initData(this.options)
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
itemList: [],
|
||||||
|
singleVal: '',
|
||||||
|
vals: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData(vals) {
|
||||||
|
vals = JSON.parse(JSON.stringify(vals))
|
||||||
|
if (vals && vals.length > 0) {
|
||||||
|
if (typeof vals[0] !== 'object') {
|
||||||
|
vals = vals.map((item, index) => {
|
||||||
|
return {
|
||||||
|
[this.textField]: item,
|
||||||
|
[this.valueField]: item,
|
||||||
|
checked: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
vals.map((item, index) => {
|
||||||
|
item.checked = item.checked || false
|
||||||
|
if (item[this.valueField] === undefined) {
|
||||||
|
item[this.valueField] = item[this.textField]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.itemList = vals;
|
||||||
|
// #ifdef VUE3
|
||||||
|
this.modelChange(this.modelValue)
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef VUE3
|
||||||
|
this.modelChange(this.value)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emitsChange(e) {
|
||||||
|
this.$emit('change', e)
|
||||||
|
this.$emit('input', e.detail.value)
|
||||||
|
// #ifdef VUE3
|
||||||
|
this.$emit("update:modelValue", e.detail.value);
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
radioChange(index, model) {
|
||||||
|
let min = Number(this.min)
|
||||||
|
if (this.singleVal === model[this.valueField] && min > 0) return;
|
||||||
|
let val = '';
|
||||||
|
let i = index
|
||||||
|
this.itemList.forEach((item, idx) => {
|
||||||
|
if (idx === index) {
|
||||||
|
const bool = this.singleVal === item[this.valueField] && min <= 0
|
||||||
|
val = bool ? '' : item[this.valueField]
|
||||||
|
i = bool ? -1 : index
|
||||||
|
item.checked = bool ? false : true
|
||||||
|
} else {
|
||||||
|
item.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.singleVal = val
|
||||||
|
let e = {
|
||||||
|
detail: {
|
||||||
|
index: i,
|
||||||
|
value: val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emitsChange(e)
|
||||||
|
},
|
||||||
|
checkboxChange(index, model) {
|
||||||
|
let max = Number(this.max)
|
||||||
|
let vals = this.vals
|
||||||
|
let i = vals.indexOf(model[this.valueField])
|
||||||
|
if (vals.length >= max && max !== -1 && i === -1) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '已超出最大选择数!',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.itemList.forEach((item, idx) => {
|
||||||
|
if (idx === index) {
|
||||||
|
item.checked = i !== -1 ? false : true
|
||||||
|
if (item.checked) {
|
||||||
|
vals.push(item[this.valueField])
|
||||||
|
} else {
|
||||||
|
vals.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.vals = vals
|
||||||
|
let e = {
|
||||||
|
detail: {
|
||||||
|
value: vals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emitsChange(e)
|
||||||
|
},
|
||||||
|
itemTap(index) {
|
||||||
|
let item = this.itemList[index]
|
||||||
|
if (item[this.disableField]) return;
|
||||||
|
if (this.multiple) {
|
||||||
|
this.checkboxChange(index, item)
|
||||||
|
} else {
|
||||||
|
this.radioChange(index, item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modelChange(vals) {
|
||||||
|
if (this.multiple) {
|
||||||
|
this.itemList.forEach(item => {
|
||||||
|
if (vals.includes(item[this.valueField])) {
|
||||||
|
item.checked = true;
|
||||||
|
} else {
|
||||||
|
item.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.vals = vals
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.itemList.forEach(item => {
|
||||||
|
if (vals == item[this.valueField]) {
|
||||||
|
item.checked = true;
|
||||||
|
} else {
|
||||||
|
item.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.singleVal = vals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-data-checkbox {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-data__cb-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cb__text {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-data__cb-disabled {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-cb-checkmark {
|
||||||
|
width: 20rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-width: 3px;
|
||||||
|
border-bottom-color: #FFFFFF;
|
||||||
|
border-right-style: solid;
|
||||||
|
border-right-width: 3px;
|
||||||
|
border-right-color: #FFFFFF;
|
||||||
|
transform: rotate(45deg) scale(0.5);
|
||||||
|
transform-origin: 54% 48%;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
775
src/components/thorui/tui-datetime/tui-datetime.vue
Normal file
775
src/components/thorui/tui-datetime/tui-datetime.vue
Normal file
@ -0,0 +1,775 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-datetime-picker" :style="{zIndex}">
|
||||||
|
<view class="tui-datetime__mask" :class="{ 'tui-datetime__mask-show': isShow }" :style="{zIndex:getMaskZIndex}"
|
||||||
|
@touchmove.stop.prevent="stop" catchtouchmove="stop" @tap="maskClick"></view>
|
||||||
|
<view class="tui-datetime__header" :class="{ 'tui-show': isShow }" :style="{zIndex:getPickerZIndex}">
|
||||||
|
<view class="tui-picker-header" :class="{ 'tui-date-radius': radius }"
|
||||||
|
:style="{ backgroundColor: headerBackground }" @touchmove.stop.prevent="stop" catchtouchmove="stop">
|
||||||
|
<view class="tui-btn-picker" :style="{ color: cancelColor }" hover-class="tui-opacity"
|
||||||
|
:hover-stay-time="150" @tap="hide">取消</view>
|
||||||
|
<view class="tui-pickerdate__title" :style="{fontSize:titleSize+'rpx',color:titleColor}">{{title}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-btn-picker" :style="{ color: getColor }" hover-class="tui-opacity"
|
||||||
|
:hover-stay-time="150" @tap="btnFix">确定</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-date-header" :style="{ backgroundColor: unitBackground }" v-if="unitTop">
|
||||||
|
<view class="tui-date-unit" v-if="type < 4 || type == 7 || type==8">年</view>
|
||||||
|
<view class="tui-date-unit" v-if="(type < 4 && type>0) || type == 7 || type==8">月</view>
|
||||||
|
<view class="tui-date-unit" v-if="type == 1 || type == 2 || type == 7 || type==8">日</view>
|
||||||
|
<view class="tui-date-unit" v-if="type == 1 || type == 4 || type == 5 || type == 7 || type==8">时</view>
|
||||||
|
<view class="tui-date-unit" v-if="(type == 1 || type > 3) && type!=8">分</view>
|
||||||
|
<view class="tui-date-unit" v-if="type > 4 && type !=8">秒</view>
|
||||||
|
</view>
|
||||||
|
<view @touchstart.stop="pickerstart" class="tui-date__picker-body"
|
||||||
|
:style="{ backgroundColor: bodyBackground,height:height+'rpx' }">
|
||||||
|
<picker-view :key="type" :immediate-change="immediate" :value="value" @change="change"
|
||||||
|
class="tui-datetime__picker-view">
|
||||||
|
<picker-view-column v-if="type < 4 || type == 7 || type==8">
|
||||||
|
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||||
|
v-for="(item, index) in years" :key="index">
|
||||||
|
{{ item }}
|
||||||
|
<text class="tui-date__unit-text" v-if="!unitTop">年</text>
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column v-if="(type < 4 && type>0) || type == 7 || type==8">
|
||||||
|
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||||
|
v-for="(item, index) in months" :key="index">
|
||||||
|
{{ formatNum(item) }}
|
||||||
|
<text class="tui-date__unit-text" v-if="!unitTop">月</text>
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column v-if="type == 1 || type == 2 || type == 7 || type==8">
|
||||||
|
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||||
|
v-for="(item, index) in days" :key="index">
|
||||||
|
{{ formatNum(item) }}
|
||||||
|
<text class="tui-date__unit-text" v-if="!unitTop">日</text>
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column v-if="type == 1 || type == 4 || type == 5 || type == 7 || type==8">
|
||||||
|
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||||
|
v-for="(item, index) in hours" :key="index">
|
||||||
|
{{ formatNum(item) }}
|
||||||
|
<text class="tui-date__unit-text" v-if="!unitTop">时</text>
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column v-if="(type == 1 || type > 3) && type!=8">
|
||||||
|
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||||
|
v-for="(item, index) in minutes" :key="index">
|
||||||
|
{{ formatNum(item) }}
|
||||||
|
<text class="tui-date__unit-text" v-if="!unitTop">分</text>
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
<picker-view-column v-if="type > 4 && type!=8">
|
||||||
|
<view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
|
||||||
|
v-for="(item, index) in seconds" :key="index">
|
||||||
|
{{ formatNum(item) }}
|
||||||
|
<text class="tui-date__unit-text" v-if="!unitTop">秒</text>
|
||||||
|
</view>
|
||||||
|
</picker-view-column>
|
||||||
|
</picker-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiDatetime',
|
||||||
|
emits: ['cancel', 'confirm'],
|
||||||
|
props: {
|
||||||
|
//0-年 1-日期+时间(年月日+时分) 2-日期(年月日) 3-日期(年月) 4-时间(时分) 5-时分秒 6-分秒 7-年月日 时分秒 8-年月日+小时
|
||||||
|
type: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//年份区间
|
||||||
|
startYear: {
|
||||||
|
type: Number,
|
||||||
|
default: 1980
|
||||||
|
},
|
||||||
|
//年份区间
|
||||||
|
endYear: {
|
||||||
|
type: Number,
|
||||||
|
default: 2050
|
||||||
|
},
|
||||||
|
hoursData: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minutesData: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
secondsData: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//显示标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//标题字体大小
|
||||||
|
titleSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 34
|
||||||
|
},
|
||||||
|
//标题字体颜色
|
||||||
|
titleColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//"取消"字体颜色
|
||||||
|
cancelColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#888'
|
||||||
|
},
|
||||||
|
//"确定"字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//设置默认显示日期 2019-08-01 || 2019-08-01 17:01 || 2019/08/01
|
||||||
|
setDateTime: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//单位置顶
|
||||||
|
unitTop: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//圆角设置
|
||||||
|
radius: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//头部背景色
|
||||||
|
headerBackground: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//根据实际调整,不建议使用深颜色
|
||||||
|
bodyBackground: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//单位置顶时,单位条背景色
|
||||||
|
unitBackground: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 520
|
||||||
|
},
|
||||||
|
//点击遮罩 是否可关闭
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 998
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let immediate = true;
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
immediate = false
|
||||||
|
// #endif
|
||||||
|
return {
|
||||||
|
immediate,
|
||||||
|
isShow: false,
|
||||||
|
years: [],
|
||||||
|
months: [],
|
||||||
|
days: [],
|
||||||
|
hours: [],
|
||||||
|
minutes: [],
|
||||||
|
seconds: [],
|
||||||
|
year: 0,
|
||||||
|
month: 0,
|
||||||
|
day: 0,
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
second: 0,
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
value: [],
|
||||||
|
isEnd: true,
|
||||||
|
isChange: false,
|
||||||
|
isSelect: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initData();
|
||||||
|
}, 20)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
yearOrMonth() {
|
||||||
|
return `${this.year}-${this.month}`;
|
||||||
|
},
|
||||||
|
propsChange() {
|
||||||
|
return `${this.type}-${this.startYear}-${this.endYear}-${this.hoursData}-${this.minutesData}-${this.secondsData}`;
|
||||||
|
},
|
||||||
|
getColor() {
|
||||||
|
return this.color || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
},
|
||||||
|
getMaskZIndex() {
|
||||||
|
return Number(this.zIndex) + 1
|
||||||
|
},
|
||||||
|
getPickerZIndex() {
|
||||||
|
return Number(this.zIndex) + 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
yearOrMonth() {
|
||||||
|
this.setDays();
|
||||||
|
},
|
||||||
|
propsChange() {
|
||||||
|
if (this.isChange) return;
|
||||||
|
this.isChange = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isChange = false;
|
||||||
|
this.initData();
|
||||||
|
}, 50);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setDateTime(val) {
|
||||||
|
if (val && val !== true) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initData();
|
||||||
|
}, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
stop() {},
|
||||||
|
formatNum: function(num) {
|
||||||
|
return num < 10 ? '0' + num : num + '';
|
||||||
|
},
|
||||||
|
generateArray: function(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
getIndex: function(arr, val) {
|
||||||
|
if (!arr || arr.length === 0 || val === undefined) return 0;
|
||||||
|
let index = arr.indexOf(val);
|
||||||
|
return index == -1 ? 0 : index;
|
||||||
|
},
|
||||||
|
getCharCount(str) {
|
||||||
|
let regex = new RegExp('/', 'g');
|
||||||
|
let result = str.match(regex);
|
||||||
|
return !result ? 0 : result.length;
|
||||||
|
},
|
||||||
|
//日期时间处理
|
||||||
|
initSelectValue() {
|
||||||
|
let fdate = ''
|
||||||
|
if (this.setDateTime && this.setDateTime !== true) {
|
||||||
|
fdate = this.setDateTime.replace(/\-/g, '/');
|
||||||
|
if (this.type == 3 && this.getCharCount(fdate) === 1) {
|
||||||
|
fdate += '/01'
|
||||||
|
} else if (this.type == 0) {
|
||||||
|
fdate += '/01/01'
|
||||||
|
}
|
||||||
|
fdate = fdate && fdate.indexOf('/') == -1 ? `2023/01/01 ${fdate}` : fdate;
|
||||||
|
}
|
||||||
|
let time = null;
|
||||||
|
if (fdate) time = new Date(fdate);
|
||||||
|
else time = new Date();
|
||||||
|
let year = time.getFullYear();
|
||||||
|
if (year > this.endYear) {
|
||||||
|
year = this.endYear
|
||||||
|
} else if (year < this.startYear) {
|
||||||
|
year = this.startYear
|
||||||
|
}
|
||||||
|
const month = time.getMonth() + 1;
|
||||||
|
const day = time.getDate();
|
||||||
|
const hour = time.getHours();
|
||||||
|
const minute = time.getMinutes();
|
||||||
|
const second = time.getSeconds();
|
||||||
|
this.year = year;
|
||||||
|
this.month = month;
|
||||||
|
this.day = day;
|
||||||
|
this.hour = hour;
|
||||||
|
this.minute = minute;
|
||||||
|
this.second = second;
|
||||||
|
|
||||||
|
return [year, month, day, hour, minute, second]
|
||||||
|
},
|
||||||
|
initData() {
|
||||||
|
const def = this.initSelectValue();
|
||||||
|
const type = Number(this.type)
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
this.setYears();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this.setYears();
|
||||||
|
this.setMonths();
|
||||||
|
this.setDays();
|
||||||
|
this.setHours();
|
||||||
|
this.setMinutes();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.setYears();
|
||||||
|
this.setMonths();
|
||||||
|
this.setDays();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this.setYears();
|
||||||
|
this.setMonths();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.setHours();
|
||||||
|
this.setMinutes();
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this.setHours();
|
||||||
|
this.setMinutes();
|
||||||
|
this.setSeconds();
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
this.setMinutes();
|
||||||
|
this.setSeconds();
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
this.setYears();
|
||||||
|
this.setMonths();
|
||||||
|
this.setDays();
|
||||||
|
this.setHours();
|
||||||
|
this.setMinutes();
|
||||||
|
this.setSeconds();
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
this.setYears();
|
||||||
|
this.setMonths();
|
||||||
|
this.setDays();
|
||||||
|
this.setHours();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setDefaultValues(def);
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setDefaultValues(def) {
|
||||||
|
let vals = []
|
||||||
|
// 1-年月日+时分 2-年月日 3-年月 4-时分 5-时分秒 6-分秒 7-年月日 时分秒 8-年月日+小时
|
||||||
|
const year = this.getIndex(this.years, def[0]);
|
||||||
|
|
||||||
|
const month = this.getIndex(this.months, def[1])
|
||||||
|
const day = this.getIndex(this.days, def[2])
|
||||||
|
const hour = this.getIndex(this.hours, def[3])
|
||||||
|
const minute = this.getIndex(this.minutes, def[4])
|
||||||
|
const second = this.getIndex(this.seconds, def[5])
|
||||||
|
|
||||||
|
// console.log(year, month, day, hour, minute, second)
|
||||||
|
const type = Number(this.type)
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
vals = [year]
|
||||||
|
case 1:
|
||||||
|
vals = [year, month, day, hour, minute]
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
vals = [year, month, day]
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
vals = [year, month]
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
vals = [hour, minute]
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
vals = [hour, minute, second]
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
vals = [minute, second]
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
vals = [year, month, day, hour, minute, second]
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
vals = [year, month, day, hour]
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (this.value.join(',') === vals.join(',')) return;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.value = vals;
|
||||||
|
}, 150);
|
||||||
|
|
||||||
|
},
|
||||||
|
setYears() {
|
||||||
|
this.years = this.generateArray(this.startYear, this.endYear);
|
||||||
|
},
|
||||||
|
setMonths() {
|
||||||
|
this.months = this.generateArray(1, 12);
|
||||||
|
|
||||||
|
},
|
||||||
|
setDays() {
|
||||||
|
if (this.type == 3 || this.type == 4) return;
|
||||||
|
let totalDays = new Date(this.year, this.month, 0).getDate();
|
||||||
|
totalDays = !totalDays || totalDays < 1 ? 1 : totalDays
|
||||||
|
this.days = this.generateArray(1, totalDays);
|
||||||
|
},
|
||||||
|
setHours() {
|
||||||
|
if (this.hoursData && this.hoursData.length > 0) {
|
||||||
|
this.hours = this.hoursData;
|
||||||
|
} else {
|
||||||
|
this.hours = this.generateArray(0, 23);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setMinutes() {
|
||||||
|
if (this.minutesData && this.minutesData.length > 0) {
|
||||||
|
this.minutes = this.minutesData
|
||||||
|
} else {
|
||||||
|
this.minutes = this.generateArray(0, 59);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSeconds() {
|
||||||
|
if (this.secondsData && this.secondsData.length > 0) {
|
||||||
|
this.seconds = this.secondsData;
|
||||||
|
} else {
|
||||||
|
this.seconds = this.generateArray(0, 59);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isShow = true;
|
||||||
|
}, 250);
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.isShow = false;
|
||||||
|
this.$emit('cancel', {});
|
||||||
|
},
|
||||||
|
maskClick() {
|
||||||
|
if (!this.maskClosable) return;
|
||||||
|
this.hide()
|
||||||
|
},
|
||||||
|
change(e) {
|
||||||
|
this.value = e.detail.value;
|
||||||
|
const type = Number(this.type)
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
this.year = this.years[this.value[0]];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this.year = this.years[this.value[0]];
|
||||||
|
this.month = this.months[this.value[1]];
|
||||||
|
this.day = this.days[this.value[2]];
|
||||||
|
this.hour = this.hours[this.value[3]];
|
||||||
|
this.minute = this.minutes[this.value[4]];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.year = this.years[this.value[0]];
|
||||||
|
this.month = this.months[this.value[1]];
|
||||||
|
this.day = this.days[this.value[2]];
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this.year = this.years[this.value[0]];
|
||||||
|
this.month = this.months[this.value[1]];
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.hour = this.hours[this.value[0]];
|
||||||
|
this.minute = this.minutes[this.value[1]];
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this.hour = this.hours[this.value[0]];
|
||||||
|
this.minute = this.minutes[this.value[1]];
|
||||||
|
this.second = this.seconds[this.value[2]];
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
this.minute = this.minutes[this.value[0]];
|
||||||
|
this.second = this.seconds[this.value[1]];
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
this.year = this.years[this.value[0]];
|
||||||
|
this.month = this.months[this.value[1]];
|
||||||
|
this.day = this.days[this.value[2]];
|
||||||
|
this.hour = this.hours[this.value[3]];
|
||||||
|
this.minute = this.minutes[this.value[4]];
|
||||||
|
this.second = this.seconds[this.value[5]];
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
this.year = this.years[this.value[0]];
|
||||||
|
this.month = this.months[this.value[1]];
|
||||||
|
this.day = this.days[this.value[2]];
|
||||||
|
this.hour = this.hours[this.value[3]];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.isEnd = true
|
||||||
|
},
|
||||||
|
selectResult() {
|
||||||
|
let result = {};
|
||||||
|
let year = this.year;
|
||||||
|
let month = this.formatNum(this.month || 0);
|
||||||
|
let day = this.formatNum(this.day || 0);
|
||||||
|
let hour = this.formatNum(this.hour || 0);
|
||||||
|
let minute = this.formatNum(this.minute || 0);
|
||||||
|
let second = this.formatNum(this.second || 0);
|
||||||
|
const type = Number(this.type)
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
result = {
|
||||||
|
year: year,
|
||||||
|
result: year
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
result = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
result: `${year}-${month}-${day} ${hour}:${minute}`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
result = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
result: `${year}-${month}-${day}`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
result = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
result: `${year}-${month}`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
result = {
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
result: `${hour}:${minute}`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
result = {
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
second: second,
|
||||||
|
result: `${hour}:${minute}:${second}`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
result = {
|
||||||
|
minute: minute,
|
||||||
|
second: second,
|
||||||
|
result: `${minute}:${second}`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
result = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
hour: hour,
|
||||||
|
minute: minute,
|
||||||
|
second: second,
|
||||||
|
result: `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
result = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
hour: hour,
|
||||||
|
result: `${year}-${month}-${day} ${hour}:00`
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.$emit('confirm', result);
|
||||||
|
},
|
||||||
|
waitFix(index = 0) {
|
||||||
|
if (this.isEnd) {
|
||||||
|
this.selectResult()
|
||||||
|
} else {
|
||||||
|
index++;
|
||||||
|
if (index >= 20) {
|
||||||
|
this.isEnd = true
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.waitFix(index)
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
btnFix() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.waitFix()
|
||||||
|
this.hide();
|
||||||
|
}, 80);
|
||||||
|
},
|
||||||
|
pickerstart() {
|
||||||
|
this.isEnd = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-datetime-picker {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-datetime__picker-view {
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-datetime__mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-datetime__mask-show {
|
||||||
|
visibility: visible !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-datetime__header {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 52rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 26rpx;
|
||||||
|
/* #ifdef MP */
|
||||||
|
box-shadow: 0 15rpx 10rpx -15rpx #efefef;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef MP */
|
||||||
|
box-shadow: 0 15rpx 10rpx -15rpx #888;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-unit {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-show {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-picker-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 90rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 32rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date-radius {
|
||||||
|
border-top-left-radius: 20rpx;
|
||||||
|
border-top-right-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-picker-header::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1rpx solid #eaeef1;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date__picker-body {
|
||||||
|
width: 100%;
|
||||||
|
/* height: 520rpx; */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date__column-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-font-size_32 {
|
||||||
|
font-size: 32rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-date__unit-text {
|
||||||
|
font-size: 24rpx !important;
|
||||||
|
padding-left: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-btn-picker {
|
||||||
|
padding: 16rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: pointer;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-opacity {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-pickerdate__title {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
193
src/components/thorui/tui-dialog/tui-dialog.vue
Normal file
193
src/components/thorui/tui-dialog/tui-dialog.vue
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view v-if="show" class="tui-dialog" :style="{background:backgroundColor,borderRadius:radius}" @tap.stop="stopEvent">
|
||||||
|
<view class="tui-dialog__hd">
|
||||||
|
<view class="tui-dialog__title" :style="{color:titleColor}">{{title}}
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-dialog__bd" :style="{color:contentColor}">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</view>
|
||||||
|
<view class="tui-dialog__ft">
|
||||||
|
<block v-if="buttons && buttons.length">
|
||||||
|
<view v-for="(item,index) in buttons" :key="index" :style="{color:item.color || '#333'}" class="tui-dialog__btn" :data-index="index" @tap="buttonTap">{{item.text}}</view>
|
||||||
|
</block>
|
||||||
|
<slot name="footer" v-else></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view @tap="close" class="tui-dialog__mask" :class="{'tui-mask_hidden':!show}" v-if="mask"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name:'tuiDialog',
|
||||||
|
emits: ['click','close'],
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
mask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor:{
|
||||||
|
type:String,
|
||||||
|
default:'#fff'
|
||||||
|
},
|
||||||
|
radius:{
|
||||||
|
type:String,
|
||||||
|
default:'12px'
|
||||||
|
},
|
||||||
|
titleColor:{
|
||||||
|
type:String,
|
||||||
|
default:'#333'
|
||||||
|
},
|
||||||
|
contentColor:{
|
||||||
|
type:String,
|
||||||
|
default:'#999'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
buttonTap(e) {
|
||||||
|
const {
|
||||||
|
index
|
||||||
|
} = e.currentTarget.dataset;
|
||||||
|
this.$emit('click', {
|
||||||
|
index,
|
||||||
|
item: this.buttons[index]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (!this.maskClosable) return;
|
||||||
|
this.$emit('close', {});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopEvent() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tui-dialog {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 5000;
|
||||||
|
top: 50%;
|
||||||
|
left: 16px;
|
||||||
|
right: 16px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__hd {
|
||||||
|
padding: 32px 24px 16px
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.4
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__bd {
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding: 0 24px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-wrap: break-word;
|
||||||
|
-webkit-hyphens: auto;
|
||||||
|
hyphens: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-dialog__ft {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
line-height: 56px;
|
||||||
|
min-height: 56px;
|
||||||
|
font-size: 17px
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__ft:after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, .1);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scaleY(.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__btn {
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__btn:active {
|
||||||
|
background-color: #ECECEC
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__btn:first-child::after {
|
||||||
|
width: 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__btn::after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
bottom: 0;
|
||||||
|
border-left: 1px solid rgba(0,0,0,.1);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scaleX(.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__mask.tui-mask_hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale3d(1, 1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dialog__mask {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, .6);
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
transition: all .2s ease-in
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,276 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-digital-keyboard" :class="{'tui-keyboard__action':show}"
|
||||||
|
:style="{backgroundColor:darkMode?'#1E1E1E':backgroundColor,zIndex:zIndex}">
|
||||||
|
<!--自定义内容,如关闭键盘按钮-->
|
||||||
|
<slot></slot>
|
||||||
|
<view class="tui-keyboard__grids">
|
||||||
|
<view class="tui-keyboard__left">
|
||||||
|
<view class="tui-keyboard__grid" :class="{'tui-flex__2':item=='0'}" v-for="(item,index) in keyArr"
|
||||||
|
:key="index">
|
||||||
|
<view class="tui-key__item"
|
||||||
|
:style="{color:darkMode?'#D1D1D1':color,backgroundColor:darkMode?'#2C2C2C':keyBackground}"
|
||||||
|
:hover-class="!isDecimal && index==10?'':'tui-key__item-active'" :hover-stay-time="150"
|
||||||
|
@tap.stop="handleClick" :data-value="item">{{item}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-keyboard__right">
|
||||||
|
<view class="tui-keyboard__grid">
|
||||||
|
<view class="tui-key__item" @tap.stop="backspace" hover-class="tui-key__item-active"
|
||||||
|
:hover-stay-time="150" :style="{backgroundColor:darkMode?'#2C2C2C':keyBackground}">
|
||||||
|
<text class="tui-dk__icon tui-icon__backspace" :style="{color:darkMode?'#D1D1D1':color}"></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-keyboard__grid tui-dk__btn">
|
||||||
|
<view class="tui-key__item" :class="{'tui-dkBtn__disabled':disabled}" @tap.stop="confirm"
|
||||||
|
:style="{color:buttonColor,background:getButtonBackground,fontSize:buttonSize+'rpx',fontWeight:buttonFontBold?'bold':'normal'}"
|
||||||
|
:hover-class="disabled?'':'tui-key__item-active'" :hover-stay-time="150">{{buttonText}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiDigitalKeyboard',
|
||||||
|
emits: ['click', 'backspace', 'confirm'],
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 996
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#F7F7F7'
|
||||||
|
},
|
||||||
|
//数字以及删除键字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#1a1a1a'
|
||||||
|
},
|
||||||
|
keyBackground: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
type: String,
|
||||||
|
default: '确定'
|
||||||
|
},
|
||||||
|
buttonBackground: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
buttonColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//rpx
|
||||||
|
buttonSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
buttonFontBold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否需要小数点
|
||||||
|
isDecimal: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//可自定义颜色,设置为true时,自定义设置失效
|
||||||
|
darkMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isDecimal(val) {
|
||||||
|
this.initData(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getButtonBackground() {
|
||||||
|
return this.buttonBackground || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keyArr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initData(this.isDecimal)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData(val) {
|
||||||
|
let keyArr = this.keyArr;
|
||||||
|
if (val) {
|
||||||
|
keyArr = keyArr.splice(10, 1, '.')
|
||||||
|
} else {
|
||||||
|
keyArr = keyArr.splice(10, 1, '')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick(e) {
|
||||||
|
if (!this.show) return;
|
||||||
|
const keyVal = e.currentTarget.dataset.value;
|
||||||
|
if (keyVal) {
|
||||||
|
this.$emit('click', {
|
||||||
|
value: keyVal
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backspace() {
|
||||||
|
this.$emit('backspace', {})
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('confirm', {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'digitalKeyboard';
|
||||||
|
src: url('data:font/truetype;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTY0VTDkAAAacAAAAHEdERUYAKQAKAAAGfAAAAB5PUy8yPH5IAgAAAVgAAABWY21hcAAP6bsAAAHAAAABQmdhc3D//wADAAAGdAAAAAhnbHlmkTxXZQAAAxAAAACgaGVhZBpqaSwAAADcAAAANmhoZWEHfAOFAAABFAAAACRobXR4DAAAPQAAAbAAAAAQbG9jYQBQAAAAAAMEAAAACm1heHABEAA9AAABOAAAACBuYW1lKeYRVQAAA7AAAAKIcG9zdGw+RqkAAAY4AAAAOQABAAAAAQAAb8Q5j18PPPUACwQAAAAAANu4kpgAAAAA27iSmAA9AFADngKvAAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAOeAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEADEAAgAAAAAAAgAAAAoACgAAAP8AAAAAAAAAAQQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5hDmEAOA/4AAXAOAAIAAAAABAAAAAAAABAAAAAAAAAAEAAAABAAAPQAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAOYQ//8AAOYQ//8Z8wABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAACAD0AUAOeAq8AEwAwAAABISIGDwEGFB8BHgEXIT4BNxEuAQMGIi8BBwYiLgE/AScmNDYyHwE3NjIWFA8BFxYUA03+AhMhDMcLDcUMIRMB/iMtAQEtxAkYCURFCRkQAQlFRggSFwlFRQkYEQhERAkCrxAO8A4nEO4ODwEBLiIBvSIu/mMJCURECREZCURGCRgRCEVFCBIYCURFCRgAAAAAAAASAN4AAQAAAAAAAAAVACwAAQAAAAAAAQAIAFQAAQAAAAAAAgAHAG0AAQAAAAAAAwAIAIcAAQAAAAAABAAIAKIAAQAAAAAABQALAMMAAQAAAAAABgAIAOEAAQAAAAAACgArAUIAAQAAAAAACwATAZYAAwABBAkAAAAqAAAAAwABBAkAAQAQAEIAAwABBAkAAgAOAF0AAwABBAkAAwAQAHUAAwABBAkABAAQAJAAAwABBAkABQAWAKsAAwABBAkABgAQAM8AAwABBAkACgBWAOoAAwABBAkACwAmAW4ACgBDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AAoAAApDcmVhdGVkIGJ5IGljb25mb250CgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAUgBlAGcAdQBsAGEAcgAAUmVndWxhcgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAVgBlAHIAcwBpAG8AbgAgADEALgAwAABWZXJzaW9uIDEuMAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgAAR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0LgAAaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAaHR0cDovL2ZvbnRlbGxvLmNvbQAAAgAAAAAAAAAKAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACAQIOYmFja3NwYWNlLWZpbGwAAAAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAwADAAEABAAAAAIAAAAAAAAAAQAAAADVpCcIAAAAANu4kpgAAAAA27iSmA==') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dk__icon {
|
||||||
|
font-family: 'digitalKeyboard' !important;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-size: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon__backspace:before {
|
||||||
|
content: '\e610';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-digital-keyboard {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translate3d(0, 100%, 0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.25s ease-in-out;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-digital-keyboard::before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
border-top: 1px solid rgba(51, 51, 51, .1);
|
||||||
|
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard__action {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard__grids {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard__left {
|
||||||
|
width: 75%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-right: 8rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard__right {
|
||||||
|
width: 25%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 8rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-keyboard__grid {
|
||||||
|
width: 33.3333%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-left: 16rpx;
|
||||||
|
padding-bottom: 16rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard__right .tui-keyboard__grid {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-dk__btn {
|
||||||
|
padding-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dkBtn__disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-key__item {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
height: 88rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-key__item-active::after {
|
||||||
|
content: ' ';
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dk__btn .tui-key__item {
|
||||||
|
height: 296rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-flex__2 {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
147
src/components/thorui/tui-digital-roller/tui-digital-roller.vue
Normal file
147
src/components/thorui/tui-digital-roller/tui-digital-roller.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-digital__roller">
|
||||||
|
<view class="tui-digital__box" v-for="(item,index) in columns" :key="index"
|
||||||
|
:style="{width:cellWidth,height:height+'px'}">
|
||||||
|
<view class="tui-digital__column"
|
||||||
|
:style="{transform:`translate3d(0, -${keys[index] * height}px, 0)`,transitionDuration:`${duration}s`,transitionTimingFunction:animation}">
|
||||||
|
<view class="tui-digital__item" v-for="(val,idx) in item" :key="idx"
|
||||||
|
:style="{color:getColor,fontSize:size+'rpx',fontWeight:bold?'bold':'normal',height:height+'px',lineHeight:size+'rpx'}">
|
||||||
|
{{val}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var numArr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
export default {
|
||||||
|
name: 'tuiDigitalRoller',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//rpx
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
bold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//滚动行高度 rpx
|
||||||
|
cellHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
//单个数字宽度
|
||||||
|
cellWidth: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
// 动画过渡效果
|
||||||
|
animation: {
|
||||||
|
type: String,
|
||||||
|
default: 'cubic-bezier(0, 1, 0, 1)'
|
||||||
|
},
|
||||||
|
//动画时间
|
||||||
|
duration: {
|
||||||
|
type: Number,
|
||||||
|
default: 1.2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
this.initColumn(this.value)
|
||||||
|
},
|
||||||
|
cellHeight(val) {
|
||||||
|
this.handleHeight(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
getColor(){
|
||||||
|
return this.color || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
columns: [],
|
||||||
|
keys: [],
|
||||||
|
height: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.handleHeight(this.cellHeight)
|
||||||
|
this.initColumn(this.value)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleHeight(h) {
|
||||||
|
this.height = uni.upx2px(h || 0)
|
||||||
|
},
|
||||||
|
initColumn(val) {
|
||||||
|
val = val + '';
|
||||||
|
let vals = val.split('');
|
||||||
|
let digit = vals.length,
|
||||||
|
arr = [];
|
||||||
|
for (let i = 0; i < digit; i++) {
|
||||||
|
if (~numArr.indexOf(vals[i])) {
|
||||||
|
arr.push(numArr)
|
||||||
|
} else {
|
||||||
|
arr.push([vals[i]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.columns = arr;
|
||||||
|
this.roll(vals)
|
||||||
|
|
||||||
|
},
|
||||||
|
roll(vals) {
|
||||||
|
let lengths = this.columns.length,
|
||||||
|
indexs = [];
|
||||||
|
|
||||||
|
while (vals.length) {
|
||||||
|
let num = vals.pop();
|
||||||
|
if (~numArr.indexOf(num)) {
|
||||||
|
indexs.unshift(Number(num))
|
||||||
|
} else {
|
||||||
|
indexs.unshift(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (indexs.length < lengths) {
|
||||||
|
indexs.unshift(0)
|
||||||
|
}
|
||||||
|
this.keys = indexs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-digital__roller {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-digital__box {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-digital__column {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-digital__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
198
src/components/thorui/tui-divide-list/tui-divide-list.vue
Normal file
198
src/components/thorui/tui-divide-list/tui-divide-list.vue
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-divide-list" :style="{background:background}">
|
||||||
|
<view class="tui-divide--item" @tap="itemTap(index)"
|
||||||
|
:style="{paddingTop:padding+'rpx',paddingBottom:padding+'rpx'}" v-for="(item,index) in list" :key="index">
|
||||||
|
<view class="tui-divide--value">
|
||||||
|
<view class="tui-divide--val" v-if="item[valueField] && !item[srcField]"
|
||||||
|
:style="{color:item.valueColor || valueColor,fontSize:(item.valueSize || valueSize)+'rpx',fontWeight:item.valueFontWeight || valueFontWeight}">
|
||||||
|
{{item[valueField]}}
|
||||||
|
</view>
|
||||||
|
<image :src="item[srcField]"
|
||||||
|
:style="{width:(item.width || width) +'rpx',height:(item.height || height) +'rpx'}"
|
||||||
|
v-if="item[srcField]">
|
||||||
|
</image>
|
||||||
|
<view :class="[item.isDot ? 'tui-badge-dot' : 'tui-badge']"
|
||||||
|
:style="{ color: badgeColor, background: getBadgeBgColor }" v-if="item[numField] || item.isDot">
|
||||||
|
{{ item.isDot ? '' : item[numField] }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-divide--text" v-if="item[textField]"
|
||||||
|
:style="{color:item.textColor || textColor,fontSize:(item.textSize || textSize)+'rpx',fontWeight:item.textFontWeight || textFontWeight}">
|
||||||
|
{{item[textField]}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-divide--line" :style="{background:dividerColor,height:dividerHeight+'%',top:getTop}"
|
||||||
|
v-if="divider && index!==list.length-1"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-divide-list",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value'
|
||||||
|
},
|
||||||
|
srcField: {
|
||||||
|
type: String,
|
||||||
|
default: 'src'
|
||||||
|
},
|
||||||
|
numField: {
|
||||||
|
type: String,
|
||||||
|
default: 'num'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
textSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
textColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#999'
|
||||||
|
},
|
||||||
|
textFontWeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
valueSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
valueColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
valueFontWeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 400
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 80
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 80
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
dividerColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#eaeef1'
|
||||||
|
},
|
||||||
|
//百分比:1~100
|
||||||
|
dividerHeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 60
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
//角标字体颜色
|
||||||
|
badgeColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//角标背景颜色
|
||||||
|
badgeBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getTop() {
|
||||||
|
let height = Number(this.dividerHeight) || 60
|
||||||
|
return (100 - height) / 2 + '%'
|
||||||
|
},
|
||||||
|
getBadgeBgColor() {
|
||||||
|
return this.badgeBgColor || (uni && uni.$tui && uni.$tui.color.pink) || '#f74d54'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
itemTap(index) {
|
||||||
|
this.$emit('click', {
|
||||||
|
index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-divide-list {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-divide--item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-divide--line {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 1px;
|
||||||
|
transform: scaleX(0.5);
|
||||||
|
transform-origin: 100% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-divide--text {
|
||||||
|
padding-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-divide--value {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-badge {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
min-width: 20rpx;
|
||||||
|
padding: 0 6rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
right: 0;
|
||||||
|
top: -8rpx;
|
||||||
|
transform: translateX(88%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-badge-dot {
|
||||||
|
position: absolute;
|
||||||
|
height: 16rpx;
|
||||||
|
width: 16rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
right: -8rpx;
|
||||||
|
top: -4rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
103
src/components/thorui/tui-divider/tui-divider.vue
Normal file
103
src/components/thorui/tui-divider/tui-divider.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-divider" :style="{ height: height + 'rpx' }">
|
||||||
|
<view class="tui-divider-line"
|
||||||
|
:style="{ width: width, background: getBgColor(gradual, gradualColor, dividerColor) }"></view>
|
||||||
|
<view class="tui-divider-text"
|
||||||
|
:style="{ color: color, fontSize: size + 'rpx', lineHeight: size + 'rpx', backgroundColor: backgroundColor, fontWeight: bold ? 'bold' : 'normal' }">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiDivider',
|
||||||
|
props: {
|
||||||
|
//divider占据高度
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//divider宽度,可填写具体长度,如400rpx
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
//divider颜色,如果为渐变线条,此属性失效
|
||||||
|
dividerColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#e5e5e5'
|
||||||
|
},
|
||||||
|
//文字颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#999'
|
||||||
|
},
|
||||||
|
//文字大小 rpx
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
bold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//背景颜色,和当前页面背景色保持一致
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fafafa'
|
||||||
|
},
|
||||||
|
//是否为渐变线条,为true,divideColor失效
|
||||||
|
gradual: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//渐变色值,to right ,提供两个色值即可,由浅至深
|
||||||
|
gradualColor: {
|
||||||
|
type: Array,
|
||||||
|
default: function() {
|
||||||
|
return ['#eee', '#ccc'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getBgColor: function(gradual, gradualColor, dividerColor) {
|
||||||
|
let bgColor = dividerColor;
|
||||||
|
if (gradual) {
|
||||||
|
bgColor = 'linear-gradient(to right,' + gradualColor[0] + ',' + gradualColor[1] + ',' +
|
||||||
|
gradualColor[1] + ',' + gradualColor[0] + ')';
|
||||||
|
}
|
||||||
|
return bgColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-divider {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-divider-line {
|
||||||
|
position: absolute;
|
||||||
|
height: 1px;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
-webkit-transform: scaleY(0.5) translateX(-50%) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateX(-50%) translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-divider-text {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 18rpx;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
158
src/components/thorui/tui-drag/tui-drag.js
Normal file
158
src/components/thorui/tui-drag/tui-drag.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// #ifndef APP-PLUS || MP-WEIXIN || H5
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
current: -1,
|
||||||
|
transX: '0px',
|
||||||
|
transY: '0px',
|
||||||
|
startTouch: null,
|
||||||
|
tDragging: false,
|
||||||
|
sId: null,
|
||||||
|
preStartKey: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isOutRange(x1, y1, x2, y2, x3, y3) {
|
||||||
|
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
|
||||||
|
},
|
||||||
|
sortCore(sKey, eKey) {
|
||||||
|
var excludeFix = (cKey, type) => {
|
||||||
|
// fixed 元素位置不会变化, 这里直接用 cKey(sortKey) 获取, 更加快捷
|
||||||
|
if (this.list[cKey].fixed) {
|
||||||
|
var _cKey = type ? --cKey : ++cKey;
|
||||||
|
return excludeFix(cKey, type);
|
||||||
|
}
|
||||||
|
return cKey;
|
||||||
|
}
|
||||||
|
let endRealKey = -1;
|
||||||
|
this.list.forEach((item) => {
|
||||||
|
if (item.sortKey === eKey) endRealKey = item.realKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.list.map((item) => {
|
||||||
|
let cKey = item.sortKey;
|
||||||
|
let rKey = item.realKey;
|
||||||
|
if (sKey < eKey) {
|
||||||
|
if (cKey > sKey && cKey <= eKey) {
|
||||||
|
--rKey;
|
||||||
|
cKey = excludeFix(--cKey, true);
|
||||||
|
} else if (cKey === sKey) {
|
||||||
|
rKey = endRealKey;
|
||||||
|
cKey = eKey;
|
||||||
|
}
|
||||||
|
} else if (sKey > eKey) {
|
||||||
|
if (cKey >= eKey && cKey < sKey) {
|
||||||
|
++rKey;
|
||||||
|
cKey = excludeFix(++cKey, false);
|
||||||
|
} else if (cKey === sKey) {
|
||||||
|
rKey = endRealKey;
|
||||||
|
cKey = eKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.sortKey !== cKey && !item.fixed) {
|
||||||
|
let columns = Number(this.columns)
|
||||||
|
item.transX = (cKey % columns) * this.baseData.itemWidth + "px";
|
||||||
|
item.transY = Math.floor(cKey / columns) * this.baseData.itemHeight + "px";
|
||||||
|
item.sortKey = cKey;
|
||||||
|
item.realKey = rKey;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
emitsEvent(list, type) {
|
||||||
|
let changeList = [],
|
||||||
|
itemList = [];
|
||||||
|
|
||||||
|
list.forEach((item) => {
|
||||||
|
changeList[item.sortKey] = item;
|
||||||
|
});
|
||||||
|
|
||||||
|
changeList.forEach((item) => {
|
||||||
|
itemList.push(item.data);
|
||||||
|
});
|
||||||
|
if (type == "change") {
|
||||||
|
this.change({
|
||||||
|
listData: itemList
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.sort_end({
|
||||||
|
listData: itemList
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
longPress(e) {
|
||||||
|
if (!this.isEdit) return;
|
||||||
|
let touch = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0] : this.startTouch;
|
||||||
|
if (!touch) return;
|
||||||
|
this.current = Number(e.currentTarget.dataset.index);
|
||||||
|
// 初始项是固定项则返回
|
||||||
|
let item = this.list[this.current];
|
||||||
|
if (item && item.fixed) return;
|
||||||
|
|
||||||
|
if (this.tDragging) return;
|
||||||
|
//pageX
|
||||||
|
this.transX = this.columns == 1 ? 0 : touch.clientX - (this.baseData.itemWidth / 2 + this.baseData.wrapLeft) + 'px';
|
||||||
|
this.transY = touch.clientY - (this.baseData.itemHeight / 2 + this.baseData.wrapTop) + 'px';
|
||||||
|
this.sId = touch.identifier;
|
||||||
|
this.tDragging = true;
|
||||||
|
},
|
||||||
|
touchstart(e) {
|
||||||
|
if (!this.isEdit) return;
|
||||||
|
this.startTouch = e.changedTouches[0] || e.touches[0];
|
||||||
|
},
|
||||||
|
touchmove(e) {
|
||||||
|
if (!this.isEdit || !this.tDragging) return;
|
||||||
|
let touch = e.changedTouches[0] || e.touches[0];
|
||||||
|
if (!touch || this.sId !== touch.identifier) return;
|
||||||
|
let transX = this.columns == 1 ? 0 : touch.clientX - (this.baseData.itemWidth / 2 + this.baseData.wrapLeft);
|
||||||
|
let transY = touch.clientY - (this.baseData.itemHeight / 2 + this.baseData.wrapTop);
|
||||||
|
|
||||||
|
if (touch.clientY > this.baseData.windowHeight - this.baseData.itemHeight) {
|
||||||
|
this.pageScroll({
|
||||||
|
scrollTop: touch.pageY + this.baseData.itemHeight - this.baseData.windowHeight
|
||||||
|
})
|
||||||
|
} else if (touch.clientY < this.baseData.itemHeight) {
|
||||||
|
this.pageScroll({
|
||||||
|
scrollTop: touch.pageY - this.baseData.itemHeight
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.transX = transX + 'px'
|
||||||
|
this.transY = transY + 'px'
|
||||||
|
|
||||||
|
let startKey = this.list[this.current].sortKey;
|
||||||
|
let curX = Math.round(transX / this.baseData.itemWidth);
|
||||||
|
let curY = Math.round(transY / this.baseData.itemHeight);
|
||||||
|
let endKey = curX + Number(this.columns) * curY;
|
||||||
|
// 目标项是固定项则返回
|
||||||
|
var item = this.list[endKey];
|
||||||
|
if (item && item.fixed) return;
|
||||||
|
|
||||||
|
if (this.isOutRange(curX, Number(this.columns), curY, this.rows, endKey, this.list.length)) return;
|
||||||
|
|
||||||
|
if (startKey === endKey || startKey === this.preStartKey) return;
|
||||||
|
this.preStartKey = startKey;
|
||||||
|
|
||||||
|
let list = this.sortCore(startKey, endKey);
|
||||||
|
this.listChange({
|
||||||
|
list: list
|
||||||
|
})
|
||||||
|
this.emitsEvent(list, "change");
|
||||||
|
},
|
||||||
|
touchend(e) {
|
||||||
|
if (!this.isEdit || !this.tDragging) return;
|
||||||
|
this.emitsEvent(this.list, "sortend");
|
||||||
|
this.preStartKey = -1;
|
||||||
|
this.tDragging = false;
|
||||||
|
this.current = -1;
|
||||||
|
this.transX = '0px';
|
||||||
|
this.transY = '0px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-PLUS|| MP-WEIXIN || H5
|
||||||
|
export default {}
|
||||||
|
// #endif
|
||||||
310
src/components/thorui/tui-drag/tui-drag.vue
Normal file
310
src/components/thorui/tui-drag/tui-drag.vue
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifdef APP-PLUS || MP-WEIXIN || H5 -->
|
||||||
|
<view class="tui-drag__wrap" :list="list" :style="{ height: getHeight + 'rpx' }" :basedata="baseData"
|
||||||
|
:change:list="handler.listObserver" :change:basedata="handler.baseDataObserver"
|
||||||
|
:catch:touchmove="wxDrag?true:''">
|
||||||
|
<view class="tui-drag__item" v-for="(item, index) in list" :key="item.id" :data-index="index"
|
||||||
|
:style="{ width: 100 / columns + '%', height: itemHeight + 'rpx' }" @longpress="handler.longPress"
|
||||||
|
:data-basedata="baseData" :data-edit="isEdit && canMove?true:false" :data-app="isApp"
|
||||||
|
@touchstart="handler.touchStart" @touchmove="handler.touchMove" @touchend="handler.touchEnd"
|
||||||
|
@mousedown="handler.mousedown">
|
||||||
|
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="isEdit">
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef APP-PLUS || MP-WEIXIN || H5 -->
|
||||||
|
<view class="tui-drag__wrap" :style="{ height: getHeight + 'rpx' }">
|
||||||
|
<view class="tui-drag__item"
|
||||||
|
:class="{'tui-drag__current':current===index,'tui-drag__transition':current!==index && !isInit,'tui-drag__fixed':item.fixed,'tui-drag__hidden':isInit}"
|
||||||
|
v-for="(item, index) in list" :key="item.id" :data-index="index"
|
||||||
|
:style="{ width: 100 / columns + '%', height: itemHeight + 'rpx',transform:`translate3d(${index === current && !item.fixed ? transX : item.transX}, ${index === current && !item.fixed ? transY: item.transY}, 0px)`}"
|
||||||
|
@longpress="longPress" @touchstart="touchstart" @touchmove.stop.prevent="touchmove" @touchend="touchend">
|
||||||
|
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="isEdit">
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
|
<!-- #ifdef APP-PLUS || H5 || MP-WEIXIN -->
|
||||||
|
<script src="./tui-drag.wxs" lang="wxs" module="handler"></script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<script>
|
||||||
|
import mpdrag from './tui-drag.js'
|
||||||
|
export default {
|
||||||
|
name: 'tuiDrag',
|
||||||
|
mixins: [mpdrag],
|
||||||
|
emits: ['click', 'sortend', 'change'],
|
||||||
|
props: {
|
||||||
|
|
||||||
|
/*
|
||||||
|
数据源
|
||||||
|
约定属性 fixed(是否固定,表示此内容不可拖拽,也不可换位置)
|
||||||
|
*/
|
||||||
|
listData: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 列数
|
||||||
|
columns: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
// 顶部固定高度
|
||||||
|
topSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 底部固定高度
|
||||||
|
bottomSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 每个 item 高度, 用于计算 item-wrap 高度 rpx
|
||||||
|
itemHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 页面滚动高度
|
||||||
|
scrollTop: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//编辑状态:为true时才可拖拽
|
||||||
|
isEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let isApp = 0;
|
||||||
|
// #ifdef APP
|
||||||
|
isApp = 1;
|
||||||
|
// #endif
|
||||||
|
return {
|
||||||
|
isApp,
|
||||||
|
/* 未渲染数据 */
|
||||||
|
baseData: {},
|
||||||
|
platform: '', // 平台信息
|
||||||
|
listWxs: [], // wxs 传回的最新 list 数据
|
||||||
|
rows: 4, // 行数
|
||||||
|
|
||||||
|
list: [], // 渲染数据列
|
||||||
|
dragging: true,
|
||||||
|
isInit: true,
|
||||||
|
wxDrag: true,
|
||||||
|
canMove: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
listData(val) {
|
||||||
|
this.list = []
|
||||||
|
setTimeout(() => {
|
||||||
|
this.init()
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
columns(val) {
|
||||||
|
this.list = []
|
||||||
|
setTimeout(() => {
|
||||||
|
this.init()
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getHeight() {
|
||||||
|
return this.rows * this.itemHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.init();
|
||||||
|
}, 50);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
vibrate() {
|
||||||
|
// #ifndef H5
|
||||||
|
if (this.platform !== 'devtools') uni.vibrateShort();
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
pageScroll(e) {
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: e.scrollTop,
|
||||||
|
duration: 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
drag(e) {
|
||||||
|
this.wxDrag = e.wxdrag;
|
||||||
|
// this.dragging = e.dragging;
|
||||||
|
},
|
||||||
|
listChange(e) {
|
||||||
|
this.listWxs = e.list;
|
||||||
|
},
|
||||||
|
itemClick(e) {
|
||||||
|
let index = e.currentTarget.dataset.index;
|
||||||
|
let item = this.listWxs[index];
|
||||||
|
|
||||||
|
this.$emit('click', {
|
||||||
|
key: item.realKey,
|
||||||
|
data: item.data,
|
||||||
|
extra: e.detail
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unique(n = 6) {
|
||||||
|
let rnd = '';
|
||||||
|
for (let i = 0; i < n; i++) rnd += Math.floor(Math.random() * 10);
|
||||||
|
return 'tui_' + new Date().getTime() + rnd;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化获取 dom 信息
|
||||||
|
*/
|
||||||
|
initDom(callback) {
|
||||||
|
let {
|
||||||
|
windowWidth,
|
||||||
|
windowHeight,
|
||||||
|
platform
|
||||||
|
} = uni.getSystemInfoSync();
|
||||||
|
let remScale = (windowWidth || 375) / 375;
|
||||||
|
|
||||||
|
this.platform = platform;
|
||||||
|
|
||||||
|
let baseData = {};
|
||||||
|
baseData.windowHeight = windowHeight;
|
||||||
|
baseData.realTopSize = (this.topSize * remScale) / 2;
|
||||||
|
baseData.realBottomSize = (this.bottomSize * remScale) / 2;
|
||||||
|
baseData.columns = this.columns;
|
||||||
|
baseData.rows = this.rows;
|
||||||
|
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select('.tui-drag__item').boundingClientRect();
|
||||||
|
query.select('.tui-drag__wrap').boundingClientRect();
|
||||||
|
query.exec(res => {
|
||||||
|
if (res && res.length > 0 && res[0] && res[0].width) {
|
||||||
|
baseData.itemWidth = res[0].width;
|
||||||
|
baseData.itemHeight = res[0].height;
|
||||||
|
baseData.wrapLeft = res[1].left;
|
||||||
|
baseData.wrapTop = res[1].top + this.scrollTop;
|
||||||
|
this.dragging = false;
|
||||||
|
this.baseData = baseData;
|
||||||
|
callback && callback()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化函数
|
||||||
|
* {listData, topSize, bottomSize, itemHeight} 参数改变需要手动调用初始化方法
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
// 初始必须为true以绑定wxs中的函数,
|
||||||
|
this.wxDrag = true
|
||||||
|
this.isInit = true
|
||||||
|
this.dragging = false
|
||||||
|
this.canMove = false
|
||||||
|
let delItem = item => {
|
||||||
|
let obj = {
|
||||||
|
...item
|
||||||
|
}
|
||||||
|
let fixed = obj.fixed || false;
|
||||||
|
delete obj["fixed"];
|
||||||
|
return {
|
||||||
|
id: this.unique(),
|
||||||
|
fixed: fixed,
|
||||||
|
data: {
|
||||||
|
...obj
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let listData = this.listData,
|
||||||
|
_list = [];
|
||||||
|
// 遍历数据源增加扩展项, 以用作排序使用
|
||||||
|
listData.forEach((item, index) => {
|
||||||
|
_list.push(delItem(item));
|
||||||
|
});
|
||||||
|
|
||||||
|
let i = 0,
|
||||||
|
columns = this.columns;
|
||||||
|
let list = (_list || []).map((item, index) => {
|
||||||
|
item.realKey = i++; // 真实顺序
|
||||||
|
item.sortKey = index; // 整体顺序
|
||||||
|
|
||||||
|
item.tranX = `${(item.sortKey % columns) * 100}%`;
|
||||||
|
item.tranY = `${Math.floor(item.sortKey / columns) * 100}%`;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.rows = Math.ceil(list.length / columns);
|
||||||
|
this.list = list;
|
||||||
|
this.listWxs = list;
|
||||||
|
if (list.length === 0) return;
|
||||||
|
let delay_time = 1000
|
||||||
|
// 异步加载数据时候, 延迟执行 initDom 方法, 防止无法正确获取 dom 信息
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initDom(() => {
|
||||||
|
// #ifndef APP-PLUS || MP-WEIXIN || H5
|
||||||
|
list.map((item, index) => {
|
||||||
|
item.transX =
|
||||||
|
`${index%columns * this.baseData.itemWidth}px`;
|
||||||
|
item.transY =
|
||||||
|
`${Math.floor(index/columns) * this.baseData.itemHeight}px`;
|
||||||
|
})
|
||||||
|
this.list = list;
|
||||||
|
this.listWxs = list;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isInit = false
|
||||||
|
this.dragging = true;
|
||||||
|
}, 500)
|
||||||
|
// #endif
|
||||||
|
this.canMove = true
|
||||||
|
})
|
||||||
|
}, delay_time);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sort_end(e) {
|
||||||
|
this.$emit('sortend', {
|
||||||
|
listData: e.listData
|
||||||
|
});
|
||||||
|
},
|
||||||
|
change(e) {
|
||||||
|
this.$emit('change', {
|
||||||
|
listData: e.listData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-drag__wrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drag__wrap .tui-drag__item {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
/* #ifndef APP-PLUS || MP-WEIXIN || H5 */
|
||||||
|
transition: transform 0s;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drag__transition {
|
||||||
|
transition: transform 0.35s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drag__wrap .tui-drag__current {
|
||||||
|
z-index: 10 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drag__wrap .tui-drag__fixed {
|
||||||
|
z-index: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drag__hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
343
src/components/thorui/tui-drag/tui-drag.wxs
Normal file
343
src/components/thorui/tui-drag/tui-drag.wxs
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
var _st = {}
|
||||||
|
|
||||||
|
function isPC() {
|
||||||
|
if (typeof navigator !== 'object') return false;
|
||||||
|
var userAgentInfo = navigator.userAgent;
|
||||||
|
var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
|
||||||
|
var flag = true;
|
||||||
|
for (var v = 0; v < Agents.length - 1; v++) {
|
||||||
|
if (userAgentInfo.indexOf(Agents[v]) > 0) {
|
||||||
|
flag = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
var isH5 = false
|
||||||
|
if (typeof window === 'object') isH5 = true
|
||||||
|
|
||||||
|
var isOutRange = function(x1, y1, x2, y2, x3, y3) {
|
||||||
|
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
|
||||||
|
};
|
||||||
|
var isEdit = false;
|
||||||
|
|
||||||
|
function bool(str) {
|
||||||
|
return str === 'true' || str == true ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortCore = function(sKey, eKey, st) {
|
||||||
|
var _ = st.basedata;
|
||||||
|
var excludeFix = function(cKey, type) {
|
||||||
|
// fixed 元素位置不会变化, 这里直接用 cKey(sortKey) 获取, 更加快捷
|
||||||
|
if (st.list[cKey].fixed) {
|
||||||
|
var _cKey = type ? --cKey : ++cKey;
|
||||||
|
return excludeFix(cKey, type);
|
||||||
|
}
|
||||||
|
return cKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先获取到 endKey 对应的 realKey, 防止下面排序过程中该 realKey 被修改
|
||||||
|
var endRealKey = -1;
|
||||||
|
st.list.forEach(function(item) {
|
||||||
|
if (item.sortKey === eKey) endRealKey = item.realKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
return st.list.map(function(item) {
|
||||||
|
if (item.fixed) return item;
|
||||||
|
var cKey = item.sortKey;
|
||||||
|
var rKey = item.realKey;
|
||||||
|
|
||||||
|
if (sKey < eKey) {
|
||||||
|
// 正序拖动
|
||||||
|
if (cKey > sKey && cKey <= eKey) {
|
||||||
|
--rKey;
|
||||||
|
cKey = excludeFix(--cKey, true);
|
||||||
|
} else if (cKey === sKey) {
|
||||||
|
rKey = endRealKey;
|
||||||
|
cKey = eKey;
|
||||||
|
}
|
||||||
|
} else if (sKey > eKey) {
|
||||||
|
// 倒序拖动
|
||||||
|
if (cKey >= eKey && cKey < sKey) {
|
||||||
|
++rKey
|
||||||
|
cKey = excludeFix(++cKey, false);
|
||||||
|
} else if (cKey === sKey) {
|
||||||
|
rKey = endRealKey;
|
||||||
|
cKey = eKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.sortKey !== cKey) {
|
||||||
|
item.tranX = (cKey % _.columns) * 100 + "%";
|
||||||
|
item.tranY = Math.floor(cKey / _.columns) * 100 + "%";
|
||||||
|
item.sortKey = cKey;
|
||||||
|
item.realKey = rKey;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var triggerCustomEvent = function(list, type, ins) {
|
||||||
|
if (!ins) return;
|
||||||
|
var _list = [],
|
||||||
|
listData = [];
|
||||||
|
|
||||||
|
list.forEach(function(item) {
|
||||||
|
_list[item.sortKey] = item;
|
||||||
|
});
|
||||||
|
|
||||||
|
_list.forEach(function(item) {
|
||||||
|
listData.push(item.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
//编译到小程序 funcName作为参数传递导致事件不执行
|
||||||
|
if (type == "change") {
|
||||||
|
ins.callMethod("change", {
|
||||||
|
listData: listData
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ins.callMethod("sort_end", {
|
||||||
|
listData: listData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var longPress = function(event, ownerInstance) {
|
||||||
|
var ins = event.instance;
|
||||||
|
var dataset = ins.getDataset()
|
||||||
|
isEdit = bool(dataset.edit)
|
||||||
|
if (!isEdit) return;
|
||||||
|
var st = ins.getState();
|
||||||
|
if (!st.basedata || st.basedata == 'undefined') {
|
||||||
|
st.basedata = JSON.parse(dataset.basedata)
|
||||||
|
}
|
||||||
|
var _ = st.basedata;
|
||||||
|
var sTouch = null;
|
||||||
|
if (isH5 && isPC()) {
|
||||||
|
sTouch = event;
|
||||||
|
} else {
|
||||||
|
sTouch = event.changedTouches ? event.changedTouches[0] : st.sTouch;
|
||||||
|
}
|
||||||
|
if (!sTouch) return;
|
||||||
|
|
||||||
|
st.cur = +dataset.index;
|
||||||
|
|
||||||
|
st.app = +dataset.app == 1 ? true : false;
|
||||||
|
|
||||||
|
// 初始项是固定项则返回
|
||||||
|
var item = st.list[st.cur];
|
||||||
|
if (item && item.fixed) return;
|
||||||
|
|
||||||
|
// 如果已经在 drag 中则返回, 防止多指触发 drag 动作, touchstart 事件中有效果
|
||||||
|
if (st.dragging) return;
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
ownerInstance.callMethod("drag", {
|
||||||
|
wxdrag: true
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 计算X,Y轴初始位移, 使 item 中心移动到点击处, 单列时候X轴初始不做位移
|
||||||
|
st.tranX = _.columns === 1 ? 0 : sTouch.pageX - (_.itemWidth / 2 + _.wrapLeft);
|
||||||
|
st.tranY = sTouch.pageY - (_.itemHeight / 2 + _.wrapTop);
|
||||||
|
st.sId = sTouch.identifier;
|
||||||
|
|
||||||
|
ins.setStyle({
|
||||||
|
'transform': 'translate3d(' + st.tranX + 'px, ' + st.tranY + 'px, 0)'
|
||||||
|
});
|
||||||
|
st.itemsInstance.forEach(function(item, index) {
|
||||||
|
item.removeClass("tui-drag__transition").removeClass("tui-drag__current");
|
||||||
|
item.addClass(index == st.cur ? "tui-drag__current" : "tui-drag__transition");
|
||||||
|
})
|
||||||
|
|
||||||
|
ownerInstance.callMethod("vibrate");
|
||||||
|
st.dragging = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var touchStart = function(event, ownerInstance) {
|
||||||
|
var ins = event.instance;
|
||||||
|
var state = ins.getState()
|
||||||
|
state.list = _st.list
|
||||||
|
state.itemsInstance = _st.itemsInstance
|
||||||
|
state.basedata = _st.basedata
|
||||||
|
if (isH5 && isPC()) {
|
||||||
|
state.sTouch = event;
|
||||||
|
} else {
|
||||||
|
state.sTouch = event.changedTouches[0] || event.touches[0]
|
||||||
|
}
|
||||||
|
var dataset = ins.getDataset()
|
||||||
|
isEdit = bool(dataset.edit)
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
ownerInstance.callMethod("drag", {
|
||||||
|
wxdrag: false
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
var touchMove = function(e, ownerInstance, pc_e) {
|
||||||
|
var ins = null
|
||||||
|
var st = {}
|
||||||
|
var mTouch = null;
|
||||||
|
if (isH5 && isPC()) {
|
||||||
|
mTouch = e;
|
||||||
|
st = pc_e.instance.getState()
|
||||||
|
ins = pc_e.instance;
|
||||||
|
} else {
|
||||||
|
mTouch = e.changedTouches[0] || e.touches[0]
|
||||||
|
ins = e.instance
|
||||||
|
st = ins.getState()
|
||||||
|
}
|
||||||
|
if (e.preventDefault && st.dragging) {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
if (st.app && st.dragging && event && event.preventDefault) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
var _ = st.basedata;
|
||||||
|
|
||||||
|
if (!st.dragging || !isEdit || !mTouch) return;
|
||||||
|
|
||||||
|
// 如果不是同一个触发点则返回
|
||||||
|
if (st.sId !== mTouch.identifier) return;
|
||||||
|
|
||||||
|
// 计算X,Y轴位移, 单列时候X轴初始不做位移
|
||||||
|
var tranX = _.columns === 1 ? 0 : mTouch.pageX - (_.itemWidth / 2 + _.wrapLeft);
|
||||||
|
var tranY = mTouch.pageY - (_.itemHeight / 2 + _.wrapTop);
|
||||||
|
|
||||||
|
// 到顶到底自动滑动
|
||||||
|
if (mTouch.clientY > _.windowHeight - _.itemHeight - _.realBottomSize) {
|
||||||
|
// 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
|
||||||
|
ownerInstance.callMethod("pageScroll", {
|
||||||
|
scrollTop: mTouch.pageY + _.itemHeight - (_.windowHeight - _.realBottomSize)
|
||||||
|
});
|
||||||
|
} else if (mTouch.clientY < _.itemHeight + _.realTopSize) {
|
||||||
|
// 当前触摸点pageY - item高度 - 顶部固定区域高度
|
||||||
|
ownerInstance.callMethod("pageScroll", {
|
||||||
|
scrollTop: mTouch.pageY - _.itemHeight - _.realTopSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置当前激活元素偏移量
|
||||||
|
ins.setStyle({
|
||||||
|
'transform': 'translate3d(' + tranX + 'px, ' + tranY + 'px, 0)'
|
||||||
|
})
|
||||||
|
|
||||||
|
var startKey = st.list[st.cur].sortKey;
|
||||||
|
var curX = Math.round(tranX / _.itemWidth);
|
||||||
|
var curY = Math.round(tranY / _.itemHeight);
|
||||||
|
var endKey = curX + _.columns * curY;
|
||||||
|
|
||||||
|
// 目标项是固定项则返回
|
||||||
|
var item = st.list[endKey];
|
||||||
|
if (item && item.fixed) return;
|
||||||
|
|
||||||
|
// X轴或Y轴超出范围则返回
|
||||||
|
if (isOutRange(curX, _.columns, curY, _.rows, endKey, st.list.length)) return;
|
||||||
|
|
||||||
|
// 防止拖拽过程中发生乱序问题
|
||||||
|
if (startKey === endKey || startKey === st.preStartKey) return;
|
||||||
|
st.preStartKey = startKey;
|
||||||
|
|
||||||
|
var list = sortCore(startKey, endKey, st);
|
||||||
|
st.itemsInstance.forEach(function(itemIns, index) {
|
||||||
|
var item = list[index];
|
||||||
|
if (index !== st.cur) {
|
||||||
|
itemIns.setStyle({
|
||||||
|
'transform': 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ownerInstance.callMethod("vibrate");
|
||||||
|
ownerInstance.callMethod("listChange", {
|
||||||
|
list: list
|
||||||
|
});
|
||||||
|
triggerCustomEvent(list, "change", ownerInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
var touchEnd = function(event, ownerInstance, pc_e) {
|
||||||
|
var ins = null
|
||||||
|
var st = {}
|
||||||
|
if (isH5 && isPC()) {
|
||||||
|
ins = pc_e.instance
|
||||||
|
st = pc_e.instance.getState()
|
||||||
|
} else {
|
||||||
|
st = event.instance.getState()
|
||||||
|
ins = event.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!st.dragging || !isEdit) return;
|
||||||
|
triggerCustomEvent(st.list, "sort_end", ownerInstance);
|
||||||
|
|
||||||
|
ins.addClass("tui-drag__transition");
|
||||||
|
ins.setStyle({
|
||||||
|
'transform': 'translate3d(' + st.list[st.cur].tranX + ',' + st.list[st.cur].tranY + ', 0)'
|
||||||
|
});
|
||||||
|
st.itemsInstance.forEach(function(item, index) {
|
||||||
|
item.removeClass("tui-drag__transition");
|
||||||
|
})
|
||||||
|
st.preStartKey = -1;
|
||||||
|
st.dragging = false;
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
ownerInstance.callMethod("drag", {
|
||||||
|
wxdrag: false
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
st.cur = -1;
|
||||||
|
st.tranX = 0;
|
||||||
|
st.tranY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseDataObserver = function(newVal, oldVal, ownerInstance, ins) {
|
||||||
|
// var st = ownerInstance.getState();
|
||||||
|
_st.basedata = newVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
var listObserver = function(newVal, oldVal, ownerInstance, ins) {
|
||||||
|
// var st = ownerInstance.getState();
|
||||||
|
_st.itemsInstance = ownerInstance.selectAllComponents('.tui-drag__item');
|
||||||
|
|
||||||
|
_st.list = newVal || [];
|
||||||
|
if (!_st.itemsInstance || _st.itemsInstance.length === 0) return;
|
||||||
|
_st.list.forEach(function(item, index) {
|
||||||
|
var itemIns = _st.itemsInstance[index];
|
||||||
|
if (item && itemIns) {
|
||||||
|
itemIns.setStyle({
|
||||||
|
'transform': 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)'
|
||||||
|
});
|
||||||
|
if (item.fixed) itemIns.addClass("tui-drag__fixed");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var movable = false;
|
||||||
|
|
||||||
|
function mousedown(e, ins) {
|
||||||
|
if (!isH5 || !isPC()) return
|
||||||
|
touchStart(e, ins)
|
||||||
|
longPress(e, ins)
|
||||||
|
movable = true
|
||||||
|
window.onmousemove = function(event) {
|
||||||
|
if (!isH5 || !isPC() || !movable) return
|
||||||
|
touchMove(event, ins, e)
|
||||||
|
}
|
||||||
|
window.onmouseup = function(event) {
|
||||||
|
if (!isH5 || !isPC() || !movable) return
|
||||||
|
touchEnd(event, ins, e)
|
||||||
|
movable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopMove() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
longPress: longPress,
|
||||||
|
touchStart: touchStart,
|
||||||
|
touchMove: touchMove,
|
||||||
|
touchEnd: touchEnd,
|
||||||
|
mousedown: mousedown,
|
||||||
|
baseDataObserver: baseDataObserver,
|
||||||
|
listObserver: listObserver,
|
||||||
|
stopMove: stopMove
|
||||||
|
}
|
||||||
140
src/components/thorui/tui-drawer/tui-drawer.vue
Normal file
140
src/components/thorui/tui-drawer/tui-drawer.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<!-- @touchmove.stop.prevent -->
|
||||||
|
<view>
|
||||||
|
<view v-if="mask" class="tui-drawer-mask" :class="{ 'tui-drawer-mask_show': visible }" :style="{ zIndex: maskZIndex }" @tap="handleMaskClick"></view>
|
||||||
|
<view
|
||||||
|
class="tui-drawer-container"
|
||||||
|
:class="[`tui-drawer-container_${mode}`, visible ? `tui-drawer-${mode}__show` : '']"
|
||||||
|
:style="{ zIndex: zIndex, backgroundColor: backgroundColor }"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 超过一屏时插槽使用scroll-view
|
||||||
|
**/
|
||||||
|
export default {
|
||||||
|
name: 'tuiDrawer',
|
||||||
|
emits: ['close'],
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
mask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// left right bottom top
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'right'
|
||||||
|
},
|
||||||
|
//drawer z-index
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 990
|
||||||
|
},
|
||||||
|
//mask z-index
|
||||||
|
maskZIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 980
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleMaskClick() {
|
||||||
|
if (!this.maskClosable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('close', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-drawer-mask {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.tui-drawer-mask_show {
|
||||||
|
display: block;
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drawer-container {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
height: 100.2%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
overflow-y: scroll;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
-ms-touch-action: pan-y cross-slide-y;
|
||||||
|
-ms-scroll-chaining: none;
|
||||||
|
-ms-scroll-limit: 0 50 0 50;
|
||||||
|
}
|
||||||
|
.tui-drawer-container_left {
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate3d(-100%, -50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drawer-container_right {
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
left: auto;
|
||||||
|
transform: translate3d(100%, -50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-drawer-container_bottom,
|
||||||
|
.tui-drawer-container_top {
|
||||||
|
width: 100%;
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 20rpx;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.tui-drawer-container_bottom {
|
||||||
|
bottom: 0;
|
||||||
|
top: auto;
|
||||||
|
transform: translate3d(0, 100%, 0);
|
||||||
|
}
|
||||||
|
.tui-drawer-container_top {
|
||||||
|
transform: translate3d(0, -100%, 0);
|
||||||
|
}
|
||||||
|
.tui-drawer-left__show,
|
||||||
|
.tui-drawer-right__show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate3d(0, -50%, 0);
|
||||||
|
}
|
||||||
|
.tui-drawer-top__show,
|
||||||
|
.tui-drawer-bottom__show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="tui-selected-class tui-dropdown-list"
|
||||||
|
:style="{ height: selectHeight ? selectHeight + 'rpx' : 'auto' }">
|
||||||
|
<slot name="selectionbox"></slot>
|
||||||
|
<view class="tui-dropdown-view" :class="[show ? 'tui-dropdownlist-show' : '']"
|
||||||
|
:style="{ backgroundColor: backgroundColor, height: show ? height + 'rpx' : 0, top: top + 'rpx' }">
|
||||||
|
<slot name="dropdownbox"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-dropdown__mask" :style="{backgroundColor:maskBackground}" v-if="isMask && show"
|
||||||
|
@tap.stop="maskClick">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiDropdownList',
|
||||||
|
emits: ['close'],
|
||||||
|
props: {
|
||||||
|
//控制显示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//top rpx
|
||||||
|
top: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//下拉框高度 rpx
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//选择框高度 单位rpx
|
||||||
|
selectHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
isMask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
maskBackground: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
maskClick() {
|
||||||
|
this.$emit('close', {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-dropdown-list {
|
||||||
|
position: relative;
|
||||||
|
z-index: 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dropdown-view {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -99;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
/* visibility: hidden; */
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dropdownlist-show {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 996;
|
||||||
|
/* visibility: visible; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-dropdown__mask {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, .1);
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
308
src/components/thorui/tui-fab/tui-fab.vue
Normal file
308
src/components/thorui/tui-fab/tui-fab.vue
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="tui-fab-box"
|
||||||
|
:class="{'tui-fab-right':left==0 || (left && right),'tui-fab__box-bottom':bottom && top == 0,'tui-fab__box-top':!(bottom && top == 0)}"
|
||||||
|
:style="getStyles">
|
||||||
|
<view class="tui-fab-btn" :class="{'tui-visible':isOpen,'tui-fab-hidden':isHidden}">
|
||||||
|
<view class="tui-fab-item-box"
|
||||||
|
:class="{'tui-fab-item-left':left && right==0 && item[imgField],'tui-fab__pb40':bottom && top == 0,'tui-fab__pt40':!(bottom && top == 0)}"
|
||||||
|
v-for="(item,index) in btnList" :key="index" @tap.stop="handleClick(index)">
|
||||||
|
<view :class="[left && right==0?'tui-text-left':'tui-text-right']" v-if="item[imgField]"
|
||||||
|
:style="{fontSize:(item.fontSize || size)+'rpx',color:item.color}">{{item[textField] || ""}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-fab-item"
|
||||||
|
:style="{width:width+'rpx',height:height+'rpx',background:item.bgColor || getBgColor,borderRadius:radius}">
|
||||||
|
<view class="tui-fab-title" v-if="!item[imgField]"
|
||||||
|
:style="{fontSize:(item.fontSize || size)+'rpx',color:item.color}">{{item[textField] || ""}}
|
||||||
|
</view>
|
||||||
|
<image :src="item[imgField]" class="tui-fab-img" v-else
|
||||||
|
:style="{width:(item.imgWidth || 64)+'rpx',height:(item.imgHeight || 64)+'rpx'}"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-fab-item" :class="{'tui-active':isOpen}"
|
||||||
|
:style="{width:width+'rpx',height:height+'rpx',borderRadius:radius,background:getBgColor,color:color}"
|
||||||
|
@tap.stop="handleClick(-1)">
|
||||||
|
<text class="tui-fab-icon tui-icon-plus" v-if="!custom"></text>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tui-fab-mask" :style="getZIndex" :class="{'tui-visible':isOpen}" @tap="handleClickCancel"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//拓展出来的按钮不应多于6个,否则违反了作为悬浮按钮的快速、高效的原则
|
||||||
|
export default {
|
||||||
|
name: "tuiFab",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
//rpx 为0时值为auto
|
||||||
|
left: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//rpx 当为0时且left不为0,值为auto
|
||||||
|
right: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 80
|
||||||
|
},
|
||||||
|
//rpx bottom值
|
||||||
|
bottom: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//rpx top值
|
||||||
|
top: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 997
|
||||||
|
},
|
||||||
|
//默认按钮 宽度 rpx
|
||||||
|
width: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 108
|
||||||
|
},
|
||||||
|
//默认按钮 高度 rpx
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 108
|
||||||
|
},
|
||||||
|
//圆角值
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: "50%"
|
||||||
|
},
|
||||||
|
//默认按钮自定义内容[替换加号]
|
||||||
|
custom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//默认按钮背景颜色
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
//字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "#fff"
|
||||||
|
},
|
||||||
|
btnList: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
type: String,
|
||||||
|
default: "text"
|
||||||
|
},
|
||||||
|
imgField: {
|
||||||
|
type: String,
|
||||||
|
default: "imgUrl"
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 28
|
||||||
|
},
|
||||||
|
//点击遮罩 是否可关闭
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getBgColor() {
|
||||||
|
return this.bgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||||
|
},
|
||||||
|
getStyles() {
|
||||||
|
let style = `z-index:${this.zIndex};`;
|
||||||
|
if (this.left && this.right == 0) {
|
||||||
|
style += `left:${this.left}rpx;`
|
||||||
|
} else {
|
||||||
|
style += `right:${this.right}rpx;`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bottom && this.top == 0) {
|
||||||
|
style += `bottom:${this.bottom}rpx;`
|
||||||
|
} else {
|
||||||
|
style += `top:${this.top}rpx;`
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
getZIndex() {
|
||||||
|
return Number(this.zIndex) - 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isOpen: false,
|
||||||
|
isHidden: true,
|
||||||
|
timer: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
handleClick: function(index) {
|
||||||
|
this.isHidden = false
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
if (index == -1 && this.btnList.length) {
|
||||||
|
this.isOpen = !this.isOpen
|
||||||
|
} else {
|
||||||
|
this.$emit("click", {
|
||||||
|
index: index
|
||||||
|
})
|
||||||
|
this.isOpen = false
|
||||||
|
}
|
||||||
|
if (!this.isOpen) {
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.isHidden = true
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClickCancel: function() {
|
||||||
|
if (!this.maskClosable) return;
|
||||||
|
this.isOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'tuifab';
|
||||||
|
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAREAA0AAAAABnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEKAAAABoAAAAciPExJUdERUYAAAQIAAAAHgAAAB4AKQAKT1MvMgAAAaAAAABCAAAAVjyBSAVjbWFwAAAB9AAAAD4AAAFCAA/pvmdhc3AAAAQAAAAACAAAAAj//wADZ2x5ZgAAAkAAAABRAAAAYFkYQQNoZWFkAAABMAAAADAAAAA2Fm5OF2hoZWEAAAFgAAAAHQAAACQH3QOFaG10eAAAAeQAAAAPAAAAEAwAAANsb2NhAAACNAAAAAoAAAAKADAAAG1heHAAAAGAAAAAHwAAACABDwAobmFtZQAAApQAAAFJAAACiCnmEVVwb3N0AAAD4AAAAB8AAAAx2XRuznjaY2BkYGAAYtGolt54fpuvDNwsDCBwc1krH5xm/t/I/J+5FsjlYGACiQIAGAEKZHjaY2BkYGBu+N/AEMPCAALM/xkYGVABCwBZ4wNrAAAAeNpjYGRgYGBhkGEA0QwMTEDMBYQMDP/BfAYAC4kBOAB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PhJ8JMzf8b2CIYW5gaAAKM4LkAN21DAEAAHjaY2GAABYIZgYAAIMAEAB42mNgYGBmgGAZBkYGELAB8hjBfBYGBSDNAoRA/jPh//8hpOQHqEoGRjYGGJOBkQlIMDGgAkaGYQ8AUSIHswAAAAAAAAAAAAAAMAAAeNpjYGRg/t/I/J+5lkGagYFRUVCPUYmNXVCRj1FETFxRUI7RyMxcUNGO0USN+fS/HEY5XTnGfznicnLijFPAHMYpYnJyjFvBlBgWBQBNJxKpAAAAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMCiTIxMbFmZiRmJ+QALXAKKAAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAMAAQAEAAAAAgAAAAB42mNgYGBkAIKrS9Q5QPTNZa18MBoAPbcFzgAA) format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-icon {
|
||||||
|
font-family: "tuifab" !important;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon-plus:before {
|
||||||
|
content: "\e613";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab__box-bottom {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab__box-top {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-right {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-btn {
|
||||||
|
transform: scale(0);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-hidden {
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-fab-item-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab__pb40 {
|
||||||
|
padding-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab__pt40 {
|
||||||
|
padding-top: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-item-left {
|
||||||
|
flex-flow: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-title {
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-text-left {
|
||||||
|
padding-left: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-text-right {
|
||||||
|
padding-right: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.2s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-radius {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-active {
|
||||||
|
transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fab-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-visible {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
137
src/components/thorui/tui-footer/tui-footer.vue
Normal file
137
src/components/thorui/tui-footer/tui-footer.vue
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-footer-class tui-footer" :class="[fixed?'tui-fixed':'']"
|
||||||
|
:style='{backgroundColor:backgroundColor}'>
|
||||||
|
<view class="tui-footer-link" v-if="navigate.length>0" :style="{color:getLinkColor}">
|
||||||
|
<block v-for="(item,index) in navigate" :key="index">
|
||||||
|
<navigator class="tui-link" hover-class="tui-link-hover" :hover-stop-propagation="true"
|
||||||
|
:style="{color:(item.color || getLinkColor),fontSize:(item.size || 28)+'rpx'}"
|
||||||
|
:open-type="item.type" :url="item[urlField]" :target="item.target" :delta="item.delta"
|
||||||
|
:app-id="item.appid" :path="item.path" :extra-data="item.extradata" :bindsuccess="item.bindsuccess"
|
||||||
|
:bindfail="item.bindfail">{{item[textField]}}</navigator>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
<view class="tui-footer-copyright" :style="{color:color,fontSize:size+'rpx'}">
|
||||||
|
{{copyright}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiFooter",
|
||||||
|
props: {
|
||||||
|
//type target url delta appid path extradata bindsuccess bindfail text color size
|
||||||
|
//链接设置 数据格式对应上面注释的属性值
|
||||||
|
navigate: {
|
||||||
|
type: Array,
|
||||||
|
default: function() {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
urlField: {
|
||||||
|
type: String,
|
||||||
|
default: "url"
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
type: String,
|
||||||
|
default: "text"
|
||||||
|
},
|
||||||
|
//底部文本
|
||||||
|
copyright: {
|
||||||
|
type: String,
|
||||||
|
default: "All Rights Reserved."
|
||||||
|
},
|
||||||
|
//copyright 字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "#A7A7A7"
|
||||||
|
},
|
||||||
|
//copyright 字体大小
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
},
|
||||||
|
//footer背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: "transparent"
|
||||||
|
},
|
||||||
|
//V2.8.0+
|
||||||
|
linkColor: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
//是否固定在底部
|
||||||
|
fixed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getLinkColor() {
|
||||||
|
return this.linkColor || (uni && uni.$tui && uni.$tui.color.link) || '#586c94'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-footer {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 30rpx 24rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fixed {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-footer-link {
|
||||||
|
/* color: #586c94; */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-link {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 18rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-link::before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
bottom: 0;
|
||||||
|
border-right: 1px solid #d3d3d3;
|
||||||
|
-webkit-transform-origin: 100% 0;
|
||||||
|
transform-origin: 100% 0;
|
||||||
|
-webkit-transform: scaleX(0.5);
|
||||||
|
transform: scaleX(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-link:last-child::before {
|
||||||
|
border-right: 0 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-link-hover {
|
||||||
|
opacity: 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-footer-copyright {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #A7A7A7;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
349
src/components/thorui/tui-form-button/tui-form-button.vue
Normal file
349
src/components/thorui/tui-form-button/tui-form-button.vue
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-button__container" :style="{width: getWidth,height: height,margin:margin,borderRadius: getRadius}"
|
||||||
|
@touchstart="handleStart" @touchend="handleClick" @touchcancel="handleEnd">
|
||||||
|
<button class="tui-button" :class="[
|
||||||
|
bold ? 'tui-text__bold' : '',
|
||||||
|
time && (plain || link) ? 'tui-button__opacity' : '',
|
||||||
|
disabled && !disabledBackground ? 'tui-button__opacity' : '',
|
||||||
|
(!width || width==='100%' || width===true) && (!btnSize || btnSize===true)?'tui-button__flex-1':'',
|
||||||
|
time && !plain && !link ? 'tui-button__active' : ''
|
||||||
|
]" :style="{
|
||||||
|
width: getWidth,
|
||||||
|
height: getHeight,
|
||||||
|
lineHeight: getHeight,
|
||||||
|
background: disabled && disabledBackground ? disabledBackground : (plain ? 'transparent' : getBackground),
|
||||||
|
borderWidth:borderWidth,
|
||||||
|
borderColor: borderColor ? borderColor : disabled && disabledBackground ? disabledBackground : (link?'transparent':getBackground),
|
||||||
|
borderRadius: getRadius,
|
||||||
|
fontSize: getSize + 'rpx',
|
||||||
|
color: disabled && disabledBackground ? disabledColor : getColor
|
||||||
|
}" :loading="loading" :form-type="formType" :open-type="openType" :app-parameter="appParameter"
|
||||||
|
@getuserinfo="bindgetuserinfo" @getphonenumber="bindgetphonenumber" @contact="bindcontact"
|
||||||
|
@error="binderror" @opensetting="bindopensetting" @chooseavatar="bindchooseavatar"
|
||||||
|
@launchapp="bindlaunchapp" :disabled="disabled" :scope="scope" @tap.stop="handleTap">
|
||||||
|
<text class="tui-button__text" :class="{'tui-text__bold':bold}" v-if="text"
|
||||||
|
:style="{fontSize: getSize + 'rpx',lineHeight:getSize + 'rpx',color: disabled && disabledBackground ? disabledColor : getColor}">{{text}}</text>
|
||||||
|
<slot></slot>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tui-form-button',
|
||||||
|
emits: ['click', 'getuserinfo', 'contact', 'getphonenumber', 'error', 'opensetting', 'chooseavatar', 'launchapp'],
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
behaviors: ['wx://form-field-button'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-BAIDU
|
||||||
|
behaviors: ['swan://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-QQ
|
||||||
|
behaviors: ['qq://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
behaviors: ['uni://form-field'],
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
//按钮背景色
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//按钮显示文本
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//按钮字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//按钮禁用背景色
|
||||||
|
disabledBackground: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//按钮禁用字体颜色
|
||||||
|
disabledColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
borderWidth: {
|
||||||
|
type: String,
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
default: '0.5px'
|
||||||
|
// #endif
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
default: '1px'
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
//高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//medium 368*80 / small 240*80/ mini 116*64
|
||||||
|
btnSize: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//字体大小,单位rpx
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
bold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//圆角
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
plain: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
formType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
openType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//支付宝小程序
|
||||||
|
//当 open-type 为 getAuthorize 时,可以设置 scope 为:phoneNumber、userInfo
|
||||||
|
scope: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
appParameter: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getWidth() {
|
||||||
|
//medium 184*40 / small 120 40/ mini 58*32
|
||||||
|
let width = this.width;
|
||||||
|
if (this.btnSize && this.btnSize !== true) {
|
||||||
|
width = {
|
||||||
|
'medium': '368rpx',
|
||||||
|
'small': '240rpx',
|
||||||
|
'mini': '116rpx'
|
||||||
|
} [this.btnSize] || this.width
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
},
|
||||||
|
getHeight() {
|
||||||
|
//medium 184*40 / small 120 40/ mini 58*32
|
||||||
|
let height = this.height || (uni && uni.$tui && uni.$tui.tuiFormButton.height) || '96rpx';
|
||||||
|
if (this.btnSize && this.btnSize !== true) {
|
||||||
|
height = {
|
||||||
|
'medium': '80rpx',
|
||||||
|
'small': '80rpx',
|
||||||
|
'mini': '64rpx'
|
||||||
|
} [this.btnSize] || height
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
},
|
||||||
|
getBackground() {
|
||||||
|
return this.background || (uni && uni.$tui && uni.$tui.tuiFormButton.background) || '#5677fc';
|
||||||
|
},
|
||||||
|
getColor() {
|
||||||
|
return this.color || (uni && uni.$tui && uni.$tui.tuiFormButton.color) || '#fff';
|
||||||
|
},
|
||||||
|
getRadius() {
|
||||||
|
return this.radius || (uni && uni.$tui && uni.$tui.tuiFormButton.radius) || '6rpx';
|
||||||
|
},
|
||||||
|
getSize() {
|
||||||
|
return this.size || (uni && uni.$tui && uni.$tui.tuiFormButton.size) || 32;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
time: 0,
|
||||||
|
trigger: false,
|
||||||
|
tap: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleStart() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.trigger = false;
|
||||||
|
this.tap = true;
|
||||||
|
if (new Date().getTime() - this.time <= 150) return;
|
||||||
|
this.trigger = true;
|
||||||
|
this.time = new Date().getTime();
|
||||||
|
},
|
||||||
|
handleClick() {
|
||||||
|
if (this.disabled || !this.trigger) return;
|
||||||
|
this.time = 0;
|
||||||
|
},
|
||||||
|
handleTap() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('click', {
|
||||||
|
index: Number(this.index)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleEnd() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.time = 0;
|
||||||
|
}, 150);
|
||||||
|
},
|
||||||
|
bindgetuserinfo({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('getuserinfo', detail);
|
||||||
|
},
|
||||||
|
bindcontact({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('contact', detail);
|
||||||
|
},
|
||||||
|
bindgetphonenumber({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('getphonenumber', detail);
|
||||||
|
},
|
||||||
|
binderror({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('error', detail);
|
||||||
|
},
|
||||||
|
bindopensetting({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('opensetting', detail);
|
||||||
|
},
|
||||||
|
bindchooseavatar({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('chooseavatar', detail);
|
||||||
|
},
|
||||||
|
bindlaunchapp({
|
||||||
|
detail = {}
|
||||||
|
} = {}) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.$emit('launchapp', detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-button__container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-button {
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
border-width: 0.5px;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
border-width: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* #endif */
|
||||||
|
border-style: solid;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
transform: translateZ(0);
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-button__flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-button::after {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
.tui-button__active {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-button__active::after {
|
||||||
|
content: ' ';
|
||||||
|
background: var(--tui-button-active, rgba(0, 0, 0, .15));
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: none;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
.tui-button__text {
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding-left: 0 !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-button__opacity {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-text__bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
47
src/components/thorui/tui-form-field/tui-form-field.vue
Normal file
47
src/components/thorui/tui-form-field/tui-form-field.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="{'tui-form__field':hidden}">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//此组件只为form表单提交传递数据使用,暂时用于微信/百度小程序
|
||||||
|
export default {
|
||||||
|
emits: ['input', 'update:modelValue'],
|
||||||
|
name: "tui-form-field",
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
behaviors: ['wx://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-BAIDU
|
||||||
|
behaviors: ['swan://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-QQ
|
||||||
|
behaviors: ['qq://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
behaviors: ['uni://form-field'],
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
//是否为隐藏域
|
||||||
|
hidden: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [Number, String, Array],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, String, Array],
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-form__field {
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
482
src/components/thorui/tui-form-item/tui-form-item.vue
Normal file
482
src/components/thorui/tui-form-item/tui-form-item.vue
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-form__item-outer"
|
||||||
|
:style="{marginTop:marginTop+'rpx',marginBottom:marginBottom+'rpx',borderRadius:getRadius,background:getBgColor}">
|
||||||
|
<view class="tui-form__item-wrap" :class="{'tui-form__highlight':highlight}"
|
||||||
|
:style="{padding:getPadding,background:getBgColor,borderRadius:getRadius}" @tap="handleClick">
|
||||||
|
<!-- #ifdef APP-NVUE -->
|
||||||
|
<view class="tui-form__asterisk" v-if="asterisk">
|
||||||
|
<text :style="{color:getAsteriskColor}">*</text>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<view class="tui-form__asterisk" v-if="asterisk" :style="{color:getAsteriskColor}">*</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<view class="tui-form__label" :style="getLabelStyl" v-if="label">{{label}}</view>
|
||||||
|
<view class="tui-form__item-content">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
<view v-if="bottomBorder" :style="{background:getBorderColor,left:left+'rpx',right:right+'rpx'}"
|
||||||
|
class="tui-form__item-bottom"></view>
|
||||||
|
<view class="tui-form__item-arrow" v-if="arrow" :style="{'border-color':getArrowColor}">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<slot name="row"></slot>
|
||||||
|
<view class="tui-form__item-error"
|
||||||
|
:class="[absolute?'tui-form__error-absolute':'tui-form__error-relative',errorPosition==3?'tui-form__error-right':'',errorMsg && errorMsg!==true?'tui-form__error-active':'']"
|
||||||
|
v-if="((!absolute && errorMsg && errorMsg!==true) || absolute) && prop"
|
||||||
|
:style="{paddingLeft:getErrorLeft,paddingRight:getErrorRight}">
|
||||||
|
<text class="tui-form__error-text" :class="{'tui-form__error-right':errorPosition==3}"
|
||||||
|
:style="{color:getAsteriskColor}">{{errorMsg}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tui-form-item',
|
||||||
|
emits: ['click'],
|
||||||
|
inject: {
|
||||||
|
form: {
|
||||||
|
value: "form",
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
marginTop: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
marginBottom: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
labelSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
labelColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//2.3.0+
|
||||||
|
labelFontWeight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//v2.9.2+
|
||||||
|
labelWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 160
|
||||||
|
},
|
||||||
|
labelRight: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 16
|
||||||
|
},
|
||||||
|
asterisk: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
asteriskColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
arrowColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
bottomBorder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 30
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//表单域 model 字段,在使用校验时该属性是必填的
|
||||||
|
prop: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//错误提示信息布局方式是否使用绝对定位
|
||||||
|
absolute: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//错误提示位置:1-标题左对齐 2-内容左对齐 3-居右对齐
|
||||||
|
position: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//表单验证规则,如果平台不支持传入函数,则使用setRules方法传入
|
||||||
|
rules: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getPadding() {
|
||||||
|
return this.padding || (uni && uni.$tui && uni.$tui.tuiFormItem.padding) || '28rpx 30rpx';
|
||||||
|
},
|
||||||
|
getBgColor() {
|
||||||
|
return this.background || (uni && uni.$tui && uni.$tui.tuiFormItem.background) || '#fff';
|
||||||
|
},
|
||||||
|
getRadius() {
|
||||||
|
return this.radius || (uni && uni.$tui && uni.$tui.tuiFormItem.radius) || '0';
|
||||||
|
},
|
||||||
|
getAsteriskColor() {
|
||||||
|
return this.asteriskColor || (uni && uni.$tui && uni.$tui.tuiFormItem.asteriskColor) || '#EB0909';
|
||||||
|
},
|
||||||
|
getLabelStyl() {
|
||||||
|
const labelSize = this.labelSize || (uni && uni.$tui && uni.$tui.tuiFormItem.labelSize) || 32;
|
||||||
|
const labelColor = this.labelColor || (uni && uni.$tui && uni.$tui.tuiFormItem.labelColor) || '#333';
|
||||||
|
const weight = this.labelFontWeight || (uni && uni.$tui && uni.$tui.tuiFormItem.labelFontWeight) || 400;
|
||||||
|
return `width:${this.labelWidth}rpx;font-size:${labelSize}rpx;color:${labelColor};padding-right:${this.labelRight}rpx;font-weight:${weight};`
|
||||||
|
},
|
||||||
|
getArrowColor() {
|
||||||
|
return this.arrowColor || (uni && uni.$tui && uni.$tui.tuiFormItem.arrowColor) || '#c0c0c0';
|
||||||
|
},
|
||||||
|
getBorderColor() {
|
||||||
|
return this.borderColor || (uni && uni.$tui && uni.$tui.tuiFormItem.borderColor) || '#eaeef1';
|
||||||
|
},
|
||||||
|
errorPosition() {
|
||||||
|
return this.position || (uni && uni.$tui && uni.$tui.tuiFormItem.position) || 2
|
||||||
|
},
|
||||||
|
getErrorRight() {
|
||||||
|
//padding值不可为空,因为需要放置错误信息
|
||||||
|
const padding = this.getPadding || '28rpx 30rpx'
|
||||||
|
const arr = padding.split(' ')
|
||||||
|
return arr[1] || arr[0] || '30rpx'
|
||||||
|
},
|
||||||
|
getErrorLeft() {
|
||||||
|
const position = this.errorPosition
|
||||||
|
let left = '30rpx'
|
||||||
|
if (position == 2) {
|
||||||
|
const pr = this.getErrorRight
|
||||||
|
const pdr = pr ? pr.replace('rpx', '').replace('px', '') : 0;
|
||||||
|
left = (Number(this.labelWidth) + Number(pdr)) + 'rpx'
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
errorMsg: '',
|
||||||
|
showError: false,
|
||||||
|
itemValue: '',
|
||||||
|
watchKey: '',
|
||||||
|
//是否即时校验
|
||||||
|
isImmediate: false,
|
||||||
|
//item项自己的rules
|
||||||
|
formItemRules: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
prop: {
|
||||||
|
handler(val) {
|
||||||
|
const key = `form.model.${val || 'prop_key_empty'}`
|
||||||
|
if (val && val !== true && this.form && key != this.watchKey) {
|
||||||
|
this.watchKey = key
|
||||||
|
this.$watch(key, (val) => {
|
||||||
|
if (this.isImmediate && this.prop && this.form) {
|
||||||
|
this.form.immediateValidator(this.prop).then(res => {
|
||||||
|
if (res.isPass) {
|
||||||
|
this.errorMsg = ''
|
||||||
|
} else {
|
||||||
|
this.errorMsg = res.errorMsg
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err.errorMsg)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (this.showError && val != this.itemValue) {
|
||||||
|
this.errorMsg = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.uninstall()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.uninstall()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
created() {
|
||||||
|
if (this.form) {
|
||||||
|
this.form.children.push(this)
|
||||||
|
//主要用于动态表单初始化值
|
||||||
|
this.isImmediate = this.form.isImmediate;
|
||||||
|
this.showError = this.form.showMessage ? false : true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick() {
|
||||||
|
this.$emit('click', {
|
||||||
|
index: this.index
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//设置校验规则
|
||||||
|
setRules(rules) {
|
||||||
|
this.formItemRules = rules
|
||||||
|
},
|
||||||
|
//设置校验规则,并合并或替换Form组件中该prop对应的rules【当页面调用Form组件校验方法传入rules时进行合并操作】
|
||||||
|
//该方法作为备用方法,如果有需要再进行放开【仅当动态formItem组件,并且当前rules未设置在总rules数据里时使用】
|
||||||
|
setRulesMerge(rules) {
|
||||||
|
this.formItemRules = rules || this.rules
|
||||||
|
if (this.form) {
|
||||||
|
const index = this.form.concatRules.findIndex(e => e.name === rules.name || e.name === this.prop)
|
||||||
|
const rule = this.getRules()
|
||||||
|
if (!rule) return;
|
||||||
|
if (index === -1) {
|
||||||
|
this.form.concatRules.push(rule)
|
||||||
|
} else {
|
||||||
|
this.form.concatRules[index] = rule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//是否开启即时校验
|
||||||
|
immediateValidate(isOpen) {
|
||||||
|
this.isImmediate = isOpen;
|
||||||
|
},
|
||||||
|
// Form组件获取当前FormItem 项 rules数据
|
||||||
|
getRules() {
|
||||||
|
//优先使用setRules 方法传入的rules值
|
||||||
|
const rules = this.formItemRules || this.rules
|
||||||
|
if (!rules.name && (rules.rule || rules.validator)) {
|
||||||
|
rules['name'] = this.prop
|
||||||
|
}
|
||||||
|
//当未传入prop则不进行校验
|
||||||
|
return !rules.name ? null : rules
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 验证方法
|
||||||
|
* @param {any} value 值,不传则使用Form组件model中值
|
||||||
|
*/
|
||||||
|
validate(value) {
|
||||||
|
const rules = this.getRules()
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (this.form && rules) {
|
||||||
|
const model = {}
|
||||||
|
let val = value;
|
||||||
|
if (val === undefined || val === null) {
|
||||||
|
val = this.form.model[rules.name] || null
|
||||||
|
}
|
||||||
|
model[rules.name] = val;
|
||||||
|
|
||||||
|
this.form.immediateValidator(rules.name, model, [rules]).then(res => {
|
||||||
|
if (res.isPass) {
|
||||||
|
this.errorMsg = ''
|
||||||
|
} else {
|
||||||
|
this.errorMsg = res.errorMsg
|
||||||
|
}
|
||||||
|
resolve(res)
|
||||||
|
}).catch(err => {
|
||||||
|
reject(err)
|
||||||
|
console.log(err.errorMsg)
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
reject({
|
||||||
|
isPass: false,
|
||||||
|
errorMsg: '未检测到Form组件或rules校验规则数据!'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clearValidate() {
|
||||||
|
this.errorMsg = ''
|
||||||
|
},
|
||||||
|
uninstall() {
|
||||||
|
this.form && this.form.uninstall(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-form__item-outer {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__item-wrap {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__highlight:active {
|
||||||
|
background-color: #f1f1f1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__asterisk {
|
||||||
|
position: absolute;
|
||||||
|
left: 12rpx;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
height: 30rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
line-height: 1.15;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__item-label {
|
||||||
|
padding-right: 12rpx;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: inline-block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__item-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__item-bottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
height: 0.5px;
|
||||||
|
z-index: -1;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
height: 1px;
|
||||||
|
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
z-index: 1;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__item-arrow {
|
||||||
|
height: 40rpx;
|
||||||
|
width: 40rpx;
|
||||||
|
border-width: 3px 3px 0 0;
|
||||||
|
border-style: solid;
|
||||||
|
transform: rotate(45deg) scale(0.5);
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
border-radius: 4rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
border-top-right-radius: 3rpx;
|
||||||
|
/* #endif */
|
||||||
|
transform-origin: center center;
|
||||||
|
margin-right: -5.8579rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__item-error {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__error-relative {
|
||||||
|
position: relative;
|
||||||
|
padding-top: 4rpx;
|
||||||
|
padding-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__error-absolute {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
transform: translateY(-100%);
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
transform: translateY(-24rpx);
|
||||||
|
/* #endif */
|
||||||
|
opacity: 0;
|
||||||
|
transition-property: transform, opacity;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__error-active {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__error-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__error-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__label {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
370
src/components/thorui/tui-form/tui-form.vue
Normal file
370
src/components/thorui/tui-form/tui-form.vue
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-form__box" :style="{backgroundColor:backgroundColor,padding:padding,borderRadius:radius}">
|
||||||
|
<slot></slot>
|
||||||
|
<view class="tui-form__errmsg"
|
||||||
|
:style="{top:tipTop+'px',padding:tipPadding,backgroundColor:getTipBgColor,borderRadius:tipRidus}"
|
||||||
|
v-if="showMessage" :class="{'tui-message__show':errorMsg}"><text class="tui-form__text"
|
||||||
|
:style="{fontSize:tipSize+'rpx',color:tipColor}">{{errorMsg}}</text></view>
|
||||||
|
<view class="tui-form__mask" v-if="disabled"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import form from "./tui-validation.js"
|
||||||
|
export default {
|
||||||
|
name: "tui-form",
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
form: this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
//表单数据对象
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//表单验证规则,即将废弃
|
||||||
|
rules: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//表单背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'transparent'
|
||||||
|
},
|
||||||
|
//表单padding值
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//是否顶部弹出方式显示校验错误信息【为false时则可关联FormItem组件显示校验错误信息】
|
||||||
|
showMessage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//表单圆角值
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//是否禁用该表单内的所有组件,透明遮罩层
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//提示框top值 px
|
||||||
|
tipTop: {
|
||||||
|
type: [Number, String],
|
||||||
|
// #ifdef H5
|
||||||
|
default: 44,
|
||||||
|
// #endif
|
||||||
|
// #ifndef H5
|
||||||
|
default: 0
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
//错误提示框padding值
|
||||||
|
tipPadding: {
|
||||||
|
type: String,
|
||||||
|
default: '20rpx'
|
||||||
|
},
|
||||||
|
//错误提示框背景色
|
||||||
|
tipBackgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//错误提示字体大小
|
||||||
|
tipSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 28
|
||||||
|
},
|
||||||
|
//错误提示字体颜色
|
||||||
|
tipColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//错误提示框圆角值
|
||||||
|
tipRidus: {
|
||||||
|
type: String,
|
||||||
|
default: '12rpx'
|
||||||
|
},
|
||||||
|
//错误消息显示时间 ms
|
||||||
|
duration: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getTipBgColor() {
|
||||||
|
return this.tipBackgroundColor || (uni && uni.$tui && uni.$tui.tuiForm.tipBackgroundColor) ||
|
||||||
|
'#f74d54';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
showMessage(val) {
|
||||||
|
if (this.children && this.children.length > 0) {
|
||||||
|
this.children.forEach(item => {
|
||||||
|
item.showError = val ? false : true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
errorMsg: '',
|
||||||
|
timer: null,
|
||||||
|
formRules: [],
|
||||||
|
isImmediate: false,
|
||||||
|
concatRules: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.children = []
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.clearTimer()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
clearTimer() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null;
|
||||||
|
this.children = null
|
||||||
|
},
|
||||||
|
getFormItemRules() {
|
||||||
|
let rules = []
|
||||||
|
if (this.children && this.children.length > 0) {
|
||||||
|
this.children.forEach(child => {
|
||||||
|
let rule = child.getRules()
|
||||||
|
rule && rules.push(rule)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
},
|
||||||
|
getMergeRules(rules) {
|
||||||
|
if (this.concatRules.length === 0) return rules;
|
||||||
|
let formRules = [...rules]
|
||||||
|
//合并并替换当前rules数据
|
||||||
|
this.concatRules.forEach(item => {
|
||||||
|
const index = rules.findIndex(e => e.name === item.name)
|
||||||
|
if (index === -1) {
|
||||||
|
formRules.push(item)
|
||||||
|
} else {
|
||||||
|
formRules[index] = item;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return formRules;
|
||||||
|
},
|
||||||
|
//{Object} model 表单数据对象,传null则使用属性中model值
|
||||||
|
//{Array} rules 表单验证规则,传null则使用FormItem项内传入的rules值
|
||||||
|
//{Boolean} checkAll 是否返回所有错误信息
|
||||||
|
validate(model, rules, checkAll = false) {
|
||||||
|
model = model || this.model
|
||||||
|
rules = rules || this.rules || []
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (rules.length === 0) {
|
||||||
|
rules = this.getFormItemRules()
|
||||||
|
} else {
|
||||||
|
rules = this.getMergeRules(rules)
|
||||||
|
}
|
||||||
|
let res = form.validation(model, rules, checkAll);
|
||||||
|
if (!res.isPass) {
|
||||||
|
let errors = res.errorMsg;
|
||||||
|
if (this.showMessage) {
|
||||||
|
this.clearTimer()
|
||||||
|
if (checkAll) {
|
||||||
|
errors = errors[0].msg
|
||||||
|
}
|
||||||
|
this.errorMsg = errors;
|
||||||
|
const duration = this.duration || (uni && uni.$tui && uni.$tui.tuiForm.duration) ||
|
||||||
|
2000
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.errorMsg = ''
|
||||||
|
}, Number(duration))
|
||||||
|
} else {
|
||||||
|
if (checkAll && this.children && this.children.length > 0) {
|
||||||
|
//FormItem组件 显示提示
|
||||||
|
this.children.forEach(item => {
|
||||||
|
const index = errors.findIndex(err => err.name === item.prop)
|
||||||
|
if (item.prop && item.prop !== true && ~index) {
|
||||||
|
item.errorMsg = errors[index].msg
|
||||||
|
item.itemValue = model[item.prop]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(res)
|
||||||
|
} catch (e) {
|
||||||
|
//TODO handle the exception
|
||||||
|
reject({
|
||||||
|
isPass: false,
|
||||||
|
errorMsg: '校验出错,请检查数据格式是否有误!'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
//结合FormItem组件进行校验时 是否开启即时校验
|
||||||
|
immediateValidate(isOpen, rules = []) {
|
||||||
|
this.isImmediate = isOpen;
|
||||||
|
if (isOpen) {
|
||||||
|
if (!rules || rules.length === 0) {
|
||||||
|
rules = this.getFormItemRules()
|
||||||
|
} else {
|
||||||
|
rules = this.getMergeRules(rules)
|
||||||
|
}
|
||||||
|
this.formRules = rules || []
|
||||||
|
}
|
||||||
|
if (this.children && this.children.length > 0) {
|
||||||
|
this.children.forEach(item => {
|
||||||
|
item.immediateValidate(isOpen)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//校验
|
||||||
|
immediateValidator(prop, model, rules) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let res = form.validation(model || this.model, rules || this.formRules, true);
|
||||||
|
if (!res.isPass) {
|
||||||
|
//显示提示
|
||||||
|
let errors = res.errorMsg;
|
||||||
|
const index = errors.findIndex(err => err.name === prop)
|
||||||
|
if (~index) {
|
||||||
|
res.errorMsg = errors[index].msg
|
||||||
|
} else {
|
||||||
|
res.isPass = true
|
||||||
|
res.errorMsg = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(res)
|
||||||
|
} catch (e) {
|
||||||
|
reject({
|
||||||
|
isPass: false,
|
||||||
|
errorMsg: '校验出错,请检查数据格式是否有误!'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clearValidate(props = []) {
|
||||||
|
let arr = props;
|
||||||
|
arr = !arr ? [] : arr
|
||||||
|
if (typeof props === 'string') {
|
||||||
|
arr = [props]
|
||||||
|
}
|
||||||
|
if (this.children && this.children.length > 0) {
|
||||||
|
//清除指定字段的表单验证信息
|
||||||
|
if (arr && arr.length > 0) {
|
||||||
|
this.children.forEach(item => {
|
||||||
|
if (item.prop && ~arr.indexOf(item.prop)) {
|
||||||
|
item.errorMsg = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
//清除所有字段的表单验证信息
|
||||||
|
this.children.forEach(item => {
|
||||||
|
item.errorMsg = ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 验证具体的某个字段
|
||||||
|
* @param {Array<string> | String} props 字段key
|
||||||
|
* @param {Array} rules 表单验证规则,当传null 或空数组时使用FormItem组件内rules
|
||||||
|
* @param {Object} model 表单数据对象,不传则使用属性中model值
|
||||||
|
*/
|
||||||
|
validateField(props, rules, model) {
|
||||||
|
if (!rules || rules.length === 0) {
|
||||||
|
rules = this.getFormItemRules()
|
||||||
|
} else {
|
||||||
|
rules = this.getMergeRules(rules)
|
||||||
|
}
|
||||||
|
const isString = typeof props === 'string';
|
||||||
|
const formRules = rules.filter(item => props === item.name || (!isString && props
|
||||||
|
.indexOf(item.name) !== -1));
|
||||||
|
model = model || this.model
|
||||||
|
return this.validate(model, formRules, true)
|
||||||
|
},
|
||||||
|
// 移除表单项
|
||||||
|
uninstall(instance) {
|
||||||
|
if (this.children && this.children.length > 0) {
|
||||||
|
const index = this.children.findIndex(item => item === instance)
|
||||||
|
if (index !== -1) {
|
||||||
|
this.children.splice(index, 1)
|
||||||
|
}
|
||||||
|
const rules = instance.getRules() || {}
|
||||||
|
const prop = instance.prop || rules.name || ''
|
||||||
|
const idx = this.concatRules.findIndex(ru => ru.name === prop)
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.concatRules.splice(idx, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-form__box {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__errmsg {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 900;
|
||||||
|
text-align: center;
|
||||||
|
left: 20rpx;
|
||||||
|
right: 20rpx;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
word-break: break-all;
|
||||||
|
/* #endif */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24rpx;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateZ(0) translateY(-100%);
|
||||||
|
transition-property: transform, opacity;
|
||||||
|
transition-duration: 0.25s;
|
||||||
|
transition-delay: 0s;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__text {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-message__show {
|
||||||
|
transform: translateY(0) translateZ(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-form__mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
346
src/components/thorui/tui-form/tui-validation.js
Normal file
346
src/components/thorui/tui-form/tui-validation.js
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
/**
|
||||||
|
* 表单验证
|
||||||
|
* @author echo.
|
||||||
|
* @version 2.9.2+
|
||||||
|
**/
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
//非必填情况下,如果值为空,则不进行校验
|
||||||
|
//当出现错误时返回错误消息,否则返回空即为验证通过
|
||||||
|
/*
|
||||||
|
formData:Object 表单对象。{key:value,key:value},key==rules.name
|
||||||
|
rules: Array [{name:name,rule:[],msg:[],validator:[],{name:name,rule:[],msg:[],validator:[]}]
|
||||||
|
name:name 属性=> 元素的名称
|
||||||
|
rule:字符串数组 ["required","isMobile","isEmail","isCarNo","isIdCard","isAmount","isNum","isChinese","isNotChinese","isEnglish",isEnAndNo","isSpecial","isEmoji",""isDate","isUrl","isSame:key","range:[1,9]","minLength:9","maxLength:Number","isKeyword:key1,key2,key3..."]
|
||||||
|
msg:数组 []。 与数组 rule 长度相同,对应的错误提示信息
|
||||||
|
validator:[{msg:'错误消息',method:Function}],自定义验证方法组,函数约定:(value)=>{ return true or false}
|
||||||
|
checkAll:是否返回所有错误信息
|
||||||
|
*/
|
||||||
|
validation: function(formData, rules, checkAll = false) {
|
||||||
|
let result = {
|
||||||
|
isPass: true,
|
||||||
|
errorMsg: checkAll ? [] : ''
|
||||||
|
};
|
||||||
|
for (let item of rules) {
|
||||||
|
let key = item.name;
|
||||||
|
let rule = item.rule || [];
|
||||||
|
let validator = item.validator || [];
|
||||||
|
let msgArr = item.msg;
|
||||||
|
const ruleLen = rule.length;
|
||||||
|
const validatorLen = validator.length;
|
||||||
|
let itemVal = formData[key];
|
||||||
|
itemVal = (itemVal === null || itemVal === undefined ? '' : itemVal).toString();
|
||||||
|
if (!key || (ruleLen === 0 && validatorLen === 0) || (!~rule.indexOf("required") &&
|
||||||
|
itemVal.length === 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let i = 0, length = rule.length; i < length; i++) {
|
||||||
|
let ruleItem = rule[i];
|
||||||
|
let msg = msgArr[i];
|
||||||
|
if (!msg || !ruleItem) continue;
|
||||||
|
//数据处理
|
||||||
|
let value = null;
|
||||||
|
if (~ruleItem.indexOf(":")) {
|
||||||
|
let temp = ruleItem.split(":");
|
||||||
|
ruleItem = temp[0];
|
||||||
|
value = temp[1];
|
||||||
|
}
|
||||||
|
let isError = false;
|
||||||
|
switch (ruleItem) {
|
||||||
|
case "required":
|
||||||
|
isError = form._isNullOrEmpty(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isMobile":
|
||||||
|
isError = !form._isMobile(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isEmail":
|
||||||
|
isError = !form._isEmail(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isCarNo":
|
||||||
|
isError = !form._isCarNo(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isIdCard":
|
||||||
|
isError = !form._isIdCard(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isAmount":
|
||||||
|
isError = !form._isAmount(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isNum":
|
||||||
|
isError = !form._isNum(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isChinese":
|
||||||
|
isError = !form._isChinese(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isNotChinese":
|
||||||
|
isError = !form._isNotChinese(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isEnglish":
|
||||||
|
isError = !form._isEnglish(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isEnAndNo":
|
||||||
|
isError = !form._isEnAndNo(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isEnOrNo":
|
||||||
|
isError = !form._isEnOrNo(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isSpecial":
|
||||||
|
isError = form._isSpecial(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isEmoji":
|
||||||
|
isError = form._isEmoji(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isDate":
|
||||||
|
isError = !form._isDate(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isUrl":
|
||||||
|
isError = !form._isUrl(formData[key]);
|
||||||
|
break;
|
||||||
|
case "isSame":
|
||||||
|
isError = !form._isSame(formData[key], formData[value]);
|
||||||
|
break;
|
||||||
|
case "range":
|
||||||
|
let range = null;
|
||||||
|
try {
|
||||||
|
range = JSON.parse(value);
|
||||||
|
if (range.length <= 1) {
|
||||||
|
throw new Error("range值传入有误!")
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return "range值传入有误!"
|
||||||
|
}
|
||||||
|
isError = !form._isRange(formData[key], range[0], range[1])
|
||||||
|
break;
|
||||||
|
case "minLength":
|
||||||
|
isError = !form._minLength(formData[key], value)
|
||||||
|
break;
|
||||||
|
case "maxLength":
|
||||||
|
isError = !form._maxLength(formData[key], value)
|
||||||
|
break;
|
||||||
|
case "isKeyword":
|
||||||
|
isError = !form._isKeyword(formData[key], value)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
result.isPass = false;
|
||||||
|
if (checkAll) {
|
||||||
|
result.errorMsg.push({
|
||||||
|
name: key,
|
||||||
|
msg: msg
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
result.errorMsg = msg;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (validator && validator.length > 0) {
|
||||||
|
for (let model of validator) {
|
||||||
|
let func = model.method;
|
||||||
|
if (func && !func(formData[key])) {
|
||||||
|
result.isPass = false;
|
||||||
|
if (checkAll) {
|
||||||
|
const index = result.errorMsg.findIndex(item => item.name === key)
|
||||||
|
if (index === -1) {
|
||||||
|
result.errorMsg.push({
|
||||||
|
name: key,
|
||||||
|
msg: model.msg || `${key} error !`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
result.errorMsg = model.msg || `${key} error !`;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
//允许填写字符串null或者undefined
|
||||||
|
_isNullOrEmpty: function(value) {
|
||||||
|
return (value === null || value === '' || value === undefined) ? true : false;
|
||||||
|
},
|
||||||
|
_isMobile: function(value) {
|
||||||
|
return /^(?:13\d|14\d|15\d|16\d|17\d|18\d|19\d)\d{5}(\d{3}|\*{3})$/.test(value);
|
||||||
|
},
|
||||||
|
_isEmail: function(value) {
|
||||||
|
return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(value);
|
||||||
|
},
|
||||||
|
_isCarNo: function(value) {
|
||||||
|
// 新能源车牌
|
||||||
|
const xreg =
|
||||||
|
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
|
||||||
|
// 旧车牌
|
||||||
|
const creg =
|
||||||
|
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
|
||||||
|
if (value.length === 7) {
|
||||||
|
return creg.test(value);
|
||||||
|
} else if (value.length === 8) {
|
||||||
|
return xreg.test(value);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_isIdCard: function(value) {
|
||||||
|
let idCard = value;
|
||||||
|
if (idCard.length == 15) {
|
||||||
|
return this.__isValidityBrithBy15IdCard;
|
||||||
|
} else if (idCard.length == 18) {
|
||||||
|
let arrIdCard = idCard.split("");
|
||||||
|
if (this.__isValidityBrithBy18IdCard(idCard) && this.__isTrueValidateCodeBy18IdCard(arrIdCard)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
__isTrueValidateCodeBy18IdCard: function(arrIdCard) {
|
||||||
|
let sum = 0;
|
||||||
|
let Wi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
|
||||||
|
let ValideCode = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2];
|
||||||
|
if (arrIdCard[17].toLowerCase() == 'x') {
|
||||||
|
arrIdCard[17] = 10;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 17; i++) {
|
||||||
|
sum += Wi[i] * arrIdCard[i];
|
||||||
|
}
|
||||||
|
let valCodePosition = sum % 11;
|
||||||
|
if (arrIdCard[17] == ValideCode[valCodePosition]) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
__isValidityBrithBy18IdCard: function(idCard18) {
|
||||||
|
let year = idCard18.substring(6, 10);
|
||||||
|
let month = idCard18.substring(10, 12);
|
||||||
|
let day = idCard18.substring(12, 14);
|
||||||
|
let temp_date = new Date(year, parseFloat(month) - 1, parseFloat(day));
|
||||||
|
if (temp_date.getFullYear() != parseFloat(year) || temp_date.getMonth() != parseFloat(month) - 1 ||
|
||||||
|
temp_date.getDate() !=
|
||||||
|
parseFloat(day)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
__isValidityBrithBy15IdCard: function(idCard15) {
|
||||||
|
let year = idCard15.substring(6, 8);
|
||||||
|
let month = idCard15.substring(8, 10);
|
||||||
|
let day = idCard15.substring(10, 12);
|
||||||
|
let temp_date = new Date(year, parseFloat(month) - 1, parseFloat(day));
|
||||||
|
|
||||||
|
if (temp_date.getFullYear() != parseFloat(year) || temp_date.getMonth() != parseFloat(month) - 1 ||
|
||||||
|
temp_date.getDate() !=
|
||||||
|
parseFloat(day)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_isAmount: function(value) {
|
||||||
|
//金额,只允许保留两位小数
|
||||||
|
return /^([0-9]*[.]?[0-9])[0-9]{0,1}$/.test(value);
|
||||||
|
},
|
||||||
|
_isNum: function(value) {
|
||||||
|
//只能为数字
|
||||||
|
return /^[0-9]+$/.test(value);
|
||||||
|
},
|
||||||
|
//是否全部为中文
|
||||||
|
_isChinese: function(value) {
|
||||||
|
let reg = /^[\u4e00-\u9fa5]+$/;
|
||||||
|
return value !== "" && reg.test(value) && !form._isSpecial(value) && !form._isEmoji(value)
|
||||||
|
},
|
||||||
|
//是否不包含中文,可以有特殊字符
|
||||||
|
_isNotChinese: function(value) {
|
||||||
|
let reg = /.*[\u4e00-\u9fa5]+.*$/;
|
||||||
|
let result = true;
|
||||||
|
if (reg.test(value)) {
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
_isEnglish: function(value) {
|
||||||
|
return /^[a-zA-Z]*$/.test(value)
|
||||||
|
},
|
||||||
|
_isEnAndNo: function(value) {
|
||||||
|
//8~20位数字和字母组合
|
||||||
|
return /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,20}$/.test(value);
|
||||||
|
},
|
||||||
|
_isEnOrNo: function(value) {
|
||||||
|
//英文或者数字
|
||||||
|
let reg = /.*[\u4e00-\u9fa5]+.*$/;
|
||||||
|
let result = true;
|
||||||
|
if (reg.test(value) || form._isSpecial(value) || form._isEmoji(value)) {
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
_isSpecial: function(value) {
|
||||||
|
//是否包含特殊字符
|
||||||
|
let regEn = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im,
|
||||||
|
regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]]/im;
|
||||||
|
if (regEn.test(value) || regCn.test(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
_isEmoji: function(value) {
|
||||||
|
//是否包含表情
|
||||||
|
return /\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g.test(value);
|
||||||
|
},
|
||||||
|
_isDate: function(value) {
|
||||||
|
//2019-10-12
|
||||||
|
const reg =
|
||||||
|
/^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;
|
||||||
|
return reg.test(value);
|
||||||
|
},
|
||||||
|
_isUrl: function(value) {
|
||||||
|
return /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})(:[0-9]{1,5})?((\/?)|(\/[\\\w_!~*\\'()\\\.;?:@&=+$,%#-]+)+\/?)$/
|
||||||
|
.test(value);
|
||||||
|
},
|
||||||
|
_isSame: function(value1, value2) {
|
||||||
|
return value1 === value2
|
||||||
|
},
|
||||||
|
_isRange: function(value, range1, range2) {
|
||||||
|
if ((!range1 && range1 != 0) && (!range2 && range2 != 0)) {
|
||||||
|
return true;
|
||||||
|
} else if (!range1 && range1 != 0) {
|
||||||
|
return value <= range2
|
||||||
|
} else if (!range2 && range2 != 0) {
|
||||||
|
return value >= range1
|
||||||
|
} else {
|
||||||
|
return value >= range1 && value <= range2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_minLength: function(value, min) {
|
||||||
|
return value.length >= Number(min)
|
||||||
|
},
|
||||||
|
_maxLength: function(value, max) {
|
||||||
|
return value.length <= Number(max)
|
||||||
|
},
|
||||||
|
_isKeyword: function(value, keywords) {
|
||||||
|
//是否包含关键词,敏感词,多个以英文逗号分隔,包含则为false,弹出提示语!
|
||||||
|
let result = true;
|
||||||
|
if (!keywords) return result;
|
||||||
|
let key = keywords.split(',');
|
||||||
|
for (let i = 0, len = key.length; i < len; i++) {
|
||||||
|
if (~value.indexOf(key[i])) {
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
validation: form.validation
|
||||||
|
};
|
||||||
212
src/components/thorui/tui-gallery/tui-gallery.vue
Normal file
212
src/components/thorui/tui-gallery/tui-gallery.vue
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-gallery" :class="{'tui-gallery_show':show}" @tap="hideGallery">
|
||||||
|
<view class="tui-gallery__info">{{currentIndex+1}}/{{getLen}}</view>
|
||||||
|
<swiper class="tui-gallery__img__wrap" :indicator-dots="false" @change="change" :current="defCurIndex"
|
||||||
|
:autoplay="false" :duration="500">
|
||||||
|
<swiper-item v-for="(item,index) in imgUrls" :key="index">
|
||||||
|
<image mode="aspectFit" class="tui-gallery__img" :src="item[srcField]"></image>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<view class="tui-gallery__desc" v-if="!showDelete">
|
||||||
|
{{getDesc(currentIndex,imgUrls)}}
|
||||||
|
</view>
|
||||||
|
<view class="tui-gallery__operate" hover-class="tui-opacity__del" :hover-start-time="150" @tap.stop="deleteImg"
|
||||||
|
v-if="showDelete">
|
||||||
|
删除
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiGallery',
|
||||||
|
emits: ['change', 'delete', 'hide'],
|
||||||
|
props: {
|
||||||
|
urls: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
srcField: {
|
||||||
|
type: String,
|
||||||
|
default: 'src'
|
||||||
|
},
|
||||||
|
descField: {
|
||||||
|
type: String,
|
||||||
|
default: 'desc'
|
||||||
|
},
|
||||||
|
showDelete: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
hideOnClick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getLen() {
|
||||||
|
return this.imgUrls.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
urls(newVal, oldVal) {
|
||||||
|
this.imgUrls = newVal
|
||||||
|
},
|
||||||
|
current(newVal) {
|
||||||
|
this.defCurIndex = this.currentIndex;
|
||||||
|
let val = Number(newVal)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.defCurIndex = val;
|
||||||
|
this.currentIndex = val;
|
||||||
|
}, 20)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.defCurIndex = Number(this.current);
|
||||||
|
this.currentIndex = this.defCurIndex;
|
||||||
|
this.imgUrls = this.urls;
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imgUrls: [],
|
||||||
|
currentIndex: 0,
|
||||||
|
defCurIndex: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDesc(index, imgUrls) {
|
||||||
|
let desc = ''
|
||||||
|
let item = imgUrls[index]
|
||||||
|
if (item) {
|
||||||
|
desc = item[this.descField]
|
||||||
|
}
|
||||||
|
return desc
|
||||||
|
},
|
||||||
|
change(e) {
|
||||||
|
this.currentIndex = e.detail.current
|
||||||
|
this.$emit('change', {
|
||||||
|
current: e.detail.current
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteImg() {
|
||||||
|
const imgs = this.imgUrls;
|
||||||
|
const url = imgs.splice(this.current, 1);
|
||||||
|
this.$emit('delete', {
|
||||||
|
url: url[0],
|
||||||
|
index: this.current
|
||||||
|
});
|
||||||
|
|
||||||
|
if (imgs.length === 0) {
|
||||||
|
this.hideGallery();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentIndex = 0;
|
||||||
|
this.imgUrls = imgs
|
||||||
|
},
|
||||||
|
hideGallery() {
|
||||||
|
if (this.hideOnClick) {
|
||||||
|
this.$emit('hide', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-gallery {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #000;
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-gallery__img,
|
||||||
|
.tui-gallery__operate,
|
||||||
|
.tui-gallery__desc {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
left: constant(safe-area-inset-left);
|
||||||
|
left: env(safe-area-inset-left);
|
||||||
|
right: 0;
|
||||||
|
right: constant(safe-area-inset-right);
|
||||||
|
right: env(safe-area-inset-right)
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-gallery__img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
top: constant(safe-area-inset-top);
|
||||||
|
top: env(safe-area-inset-top);
|
||||||
|
bottom: 60px;
|
||||||
|
bottom: calc(60px + constant(safe-area-inset-bottom));
|
||||||
|
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||||
|
background: 50% no-repeat;
|
||||||
|
background-size: contain
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-gallery__operate,
|
||||||
|
.tui-gallery__desc {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
background-color: #0d0d0d;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 60px;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tui-gallery__info {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 60px;
|
||||||
|
min-height: 60px;
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-gallery__img__wrap {
|
||||||
|
-webkit-box-flex: 1;
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
font-size: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-gallery__operate {
|
||||||
|
position: static
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-gallery_show {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-opacity__del {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
215
src/components/thorui/tui-grade/tui-grade.vue
Normal file
215
src/components/thorui/tui-grade/tui-grade.vue
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-grade__box" :class="[classBaidu]" @touchstart="touchStart" @touchmove="touchMove">
|
||||||
|
<block v-for="(item, index) in quantityArr" :key="index">
|
||||||
|
<view class="tui-icon__grade"
|
||||||
|
:class="['tui-icon__star' + (index < intScore ? 'full' : '') + (decimalScore > 0 && index == intScore ? 'half' : '')]"
|
||||||
|
@tap="touchMoveTo" :style="{
|
||||||
|
width: (width < size ? size : width) + 'px',
|
||||||
|
fontSize: size + 'px',
|
||||||
|
color: index < intScore || (decimalScore > 0 && index == intScore) ? getActiveColor : normal
|
||||||
|
}"></view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiGrade',
|
||||||
|
emits: ['change'],
|
||||||
|
props: {
|
||||||
|
//数量
|
||||||
|
quantity: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
//当前评分,小数只支持0.5
|
||||||
|
score: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//禁用点击
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//星星大小 px
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
//星星宽度 px,大于等于size值,设置间距使用
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
//未选中颜色
|
||||||
|
normal: {
|
||||||
|
type: String,
|
||||||
|
default: '#b2b2b2'
|
||||||
|
},
|
||||||
|
//选中颜色
|
||||||
|
active: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//是否支持半星选择/展示
|
||||||
|
isHalf: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//超过多少比例选中半星(取值区间0~0.5)
|
||||||
|
halfRate: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.25
|
||||||
|
},
|
||||||
|
//自定义参数
|
||||||
|
params: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
getActiveColor(){
|
||||||
|
return this.active || (uni && uni.$tui && uni.$tui.color.danger) || '#EB0909'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let classBaidu = `tui_11_${Math.ceil(Math.random() * 10e5).toString(36)}`;
|
||||||
|
return {
|
||||||
|
classBaidu,
|
||||||
|
pageX: 0,
|
||||||
|
intScore: 0,
|
||||||
|
decimalScore: 0,
|
||||||
|
quantityArr: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.quantityArr = this.generateArray(1, Number(this.quantity))
|
||||||
|
this.starActive(this.score);
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
quantity(val) {
|
||||||
|
this.quantityArr = this.generateArray(1, Number(val))
|
||||||
|
},
|
||||||
|
score(val) {
|
||||||
|
this.starActive(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateArray: function(start, end) {
|
||||||
|
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||||
|
},
|
||||||
|
starActive(val) {
|
||||||
|
val = Number(val);
|
||||||
|
let intVal = parseInt(val);
|
||||||
|
let decimalVal = val % 1;
|
||||||
|
if (!this.isHalf) {
|
||||||
|
intVal = decimalVal > 0 ? intVal + 1 : intVal;
|
||||||
|
decimalVal = 0;
|
||||||
|
}
|
||||||
|
this.intScore = intVal;
|
||||||
|
this.decimalScore = decimalVal;
|
||||||
|
},
|
||||||
|
touchMoveTo(e) {
|
||||||
|
// #ifdef H5
|
||||||
|
this.touchMove(e)
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
touchStart(e) {
|
||||||
|
// #ifndef H5
|
||||||
|
this.touchMove(e)
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
touchMove(e) {
|
||||||
|
if (this.disabled || !e.changedTouches[0]) return;
|
||||||
|
const movePageX = e.changedTouches[0].pageX;
|
||||||
|
const distance = movePageX - this.pageX;
|
||||||
|
let score = 0;
|
||||||
|
if (distance > 0) {
|
||||||
|
score = distance / this.width;
|
||||||
|
let decimalScore = score % 1;
|
||||||
|
if (!this.isHalf) {
|
||||||
|
decimalScore = decimalScore > 0 ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
if (decimalScore > this.halfRate) {
|
||||||
|
decimalScore = decimalScore <= 0.5 ? 0.5 : 1;
|
||||||
|
} else {
|
||||||
|
decimalScore = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
score = parseInt(score) + decimalScore;
|
||||||
|
score = score > Number(this.quantity) ? Number(this.quantity) : score;
|
||||||
|
}
|
||||||
|
this.$emit('change', {
|
||||||
|
score: score,
|
||||||
|
params: this.params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// #ifndef MP-BAIDU
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.select('.tui-grade__box')
|
||||||
|
.boundingClientRect(res => {
|
||||||
|
this.pageX = res.left || 0;
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-BAIDU
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.select('.' + this.classBaidu)
|
||||||
|
.boundingClientRect(res => {
|
||||||
|
this.pageX = res.left || 0;
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'tuiGradeFont';
|
||||||
|
src: url('data:font/truetype;charset=utf-8;base64,AAEAAAAOAIAAAwBgRkZUTY3VyXMAAAiUAAAAHEdERUYAKQAMAAAIdAAAAB5PUy8yQA9LlgAAAWgAAABWY21hcAAP7U4AAAHUAAABQmN2dCAAIgKIAAADGAAAAARnYXNw//8AAwAACGwAAAAIZ2x5Zgdi75IAAAMsAAACcGhlYWQbUeZgAAAA7AAAADZoaGVhB98DhgAAASQAAAAkaG10eAwDAB4AAAHAAAAAFGxvY2EB0gEyAAADHAAAAA5tYXhwARIAfAAAAUgAAAAgbmFtZSnmEVUAAAWcAAACiHBvc3QWS2RPAAAIJAAAAEcAAQAAAAEAAL1bP3tfDzz1AAsEAAAAAADcGNE1AAAAANwY0TX//v+UBAMDZQAAAAgAAgAAAAAAAAABAAADgP+AAFwEAf/+AAAEAwABAAAAAAAAAAAAAAAAAAAABAABAAAABgBCAAIAAAAAAAIAAAAKAAoAAAD/AC4AAAAAAAEEAQGQAAUAAAKJAswAAACPAokCzAAAAesAMgEIAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOmh6aMDgP+AAFwDgACAAAAAAQAAAAAAAAQBACIAAAAABAEAAAQB//7//v/+AAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAA6aP//wAA6aH//xZiAAEAAAAAAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACICiAAAACoAKgAqAHAA3gE4AAAAAgAiAAABMgKqAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIgEQ7szMAqr9ViICZgAAAf/+/5QEAwNlACcAACUGFxMWBgciJyUmIgcFBiMuATcTNi8BJjY3JTY3EzYyFxMWFwUeAQcDEAYCNwEJBwQE/twDCAT+3AQDCAkBOAEG7AcGCgFGCASSBRMFkgQIAUcKBQb6Bgj+uwgKAQKaAgKaAgEKCAFFCAbnBxICMAEHASgJCf7YBwEwAhIHAAAC//7/lAQDA2UAGABBAAABFx4BHwEHDgEfAScmIg8BNzYmLwE3PgE3EyIHAwYHBQ4BHwEWBwMGFhcyNyU2MhcFFjM+AScDJj8BNiYnJSYnAyYCAWcJHxTmpg8MBCfOEicRzicDDA6n5xMgCWcKBZIDCf66CgYH7AYBOAEKBwQDASQECAMBJAQEBwkBNwIG7QYFCv65CASSBQLp0RIXAyGjDiUU5WwJCWzlFCUOoyEDFxIBTQn+2AcCLwISB+cGCP67CAoBApoCApoCAQoIAUUIBucHEgIvAgcBKAkAAv/+/5QEAwNlACcAMwAAATYmJyUmJwMmIgcDBgcFDgEfARYHAwYWFzI3JTYyFwUWMz4BJwMmNwcXJxEXHgEfAQcOAQP9BgUK/rkIBJIFEgWSBAj+uQoGB+wGATgBCgcEAwEkBAgDASUDBAcKATgBBUMn00cJHxTmpg8MAeEHEgIwAQcBKAkJ/tgHATACEgfnBgj+uwgKAQKaAgKaAgEKCAFFCAYZ5W8CPZASFwMhow4lAAAAAAASAN4AAQAAAAAAAAAVACwAAQAAAAAAAQAIAFQAAQAAAAAAAgAHAG0AAQAAAAAAAwAIAIcAAQAAAAAABAAIAKIAAQAAAAAABQALAMMAAQAAAAAABgAIAOEAAQAAAAAACgArAUIAAQAAAAAACwATAZYAAwABBAkAAAAqAAAAAwABBAkAAQAQAEIAAwABBAkAAgAOAF0AAwABBAkAAwAQAHUAAwABBAkABAAQAJAAAwABBAkABQAWAKsAAwABBAkABgAQAM8AAwABBAkACgBWAOoAAwABBAkACwAmAW4ACgBDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AAoAAApDcmVhdGVkIGJ5IGljb25mb250CgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAUgBlAGcAdQBsAGEAcgAAUmVndWxhcgAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAAVgBlAHIAcwBpAG8AbgAgADEALgAwAABWZXJzaW9uIDEuMAAAaQBjAG8AbgBmAG8AbgB0AABpY29uZm9udAAARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgAAR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0LgAAaAB0AHQAcAA6AC8ALwBmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQAAaHR0cDovL2ZvbnRlbGxvLmNvbQAAAgAAAAAAAAAKAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAGAAAAAQACAQIBAwEECXN0YXItZnVsbARzdGFyCXN0YXItaGFsZgAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAwAFAAEABAAAAAIAAAAAAAAAAQAAAADVpCcIAAAAANwY0TUAAAAA3BjRNQ==') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon__grade {
|
||||||
|
font-family: 'tuiGradeFont' !important;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon__starfull:before {
|
||||||
|
content: '\e9a1';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon__star:before {
|
||||||
|
content: '\e9a2';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon__starhalf:before {
|
||||||
|
content: '\e9a3';
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grade__box {
|
||||||
|
display: -webkit-inline-flex;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
153
src/components/thorui/tui-grid-item/tui-grid-item.vue
Normal file
153
src/components/thorui/tui-grid-item/tui-grid-item.vue
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-grid" :class="[bottomLine?'':'tui-grid-bottom',border?'':'tui-grid__unlined','tui-grid-'+(cell<2?3:cell)]" :hover-class="hover?'tui-item-hover':''"
|
||||||
|
:hover-stay-time="150" :style="{backgroundColor:backgroundColor}" @tap="handleClick">
|
||||||
|
<view class='tui-grid-bg'>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiGridItem",
|
||||||
|
emits: ['click'],
|
||||||
|
props: {
|
||||||
|
cell: {
|
||||||
|
type: [Number,String],
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: "#fff"
|
||||||
|
},
|
||||||
|
//是否有点击效果
|
||||||
|
hover: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否需要底部线条
|
||||||
|
bottomLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否需要纵向边框线条
|
||||||
|
border:{
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick() {
|
||||||
|
this.$emit('click', {
|
||||||
|
index: this.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-grid {
|
||||||
|
position: relative;
|
||||||
|
padding: 40rpx 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #fff;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
/* #ifdef MP-BAIDU */
|
||||||
|
.tui-grid:active{
|
||||||
|
background-color: #f7f7f9;
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
.tui-grid-2 {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-3 {
|
||||||
|
width: 33.333333333%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-4 {
|
||||||
|
width: 25%;
|
||||||
|
padding: 30rpx 20rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-5 {
|
||||||
|
width: 20%;
|
||||||
|
padding: 20rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-2:nth-of-type(2n)::before {
|
||||||
|
width: 0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-3:nth-of-type(3n)::before {
|
||||||
|
width: 0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-4:nth-of-type(4n)::before {
|
||||||
|
width: 0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-5:nth-of-type(5n)::before {
|
||||||
|
width: 0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid::before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
bottom: 0;
|
||||||
|
border-right: 1px solid #eaeef1;
|
||||||
|
-webkit-transform-origin: 100% 0;
|
||||||
|
transform-origin: 100% 0;
|
||||||
|
-webkit-transform: scaleX(0.5);
|
||||||
|
transform: scaleX(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid__unlined::before{
|
||||||
|
width: 0 !important;
|
||||||
|
border-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid::after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
border-bottom: 1px solid #eaeef1;
|
||||||
|
-webkit-transform-origin: 0 100%;
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-bottom::after {
|
||||||
|
height: 0 !important;
|
||||||
|
border-bottom: 0 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-bg {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-item-hover {
|
||||||
|
background-color: #f7f7f9 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
src/components/thorui/tui-grid/tui-grid.vue
Normal file
44
src/components/thorui/tui-grid/tui-grid.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-grids" :class="{'tui-border-top':unlined}">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name:"tuiGrid",
|
||||||
|
props: {
|
||||||
|
//是否去掉上线条
|
||||||
|
unlined: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-grids {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grids::after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
border-top: 1px solid #eaeef1;
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-border-top::after {
|
||||||
|
border-top: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
190
src/components/thorui/tui-icon/tui-icon.js
Normal file
190
src/components/thorui/tui-icon/tui-icon.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
export default {
|
||||||
|
"about": "\ue772",
|
||||||
|
"about-fill": "\ue771",
|
||||||
|
"add": "\ue770",
|
||||||
|
"add-fill": "\ue76f",
|
||||||
|
"addmessage": "\ue76e",
|
||||||
|
"addressbook": "\ue76d",
|
||||||
|
"agree": "\ue76c",
|
||||||
|
"agree-fill": "\ue76b",
|
||||||
|
"alarm": "\ue76a",
|
||||||
|
"alarm-fill": "\ue769",
|
||||||
|
"alipay": "\ue768",
|
||||||
|
"android": "\ue767",
|
||||||
|
"applets": "\ue766",
|
||||||
|
"arrowdown": "\ue765",
|
||||||
|
"arrowleft": "\ue764",
|
||||||
|
"arrowright": "\ue763",
|
||||||
|
"arrowup": "\ue762",
|
||||||
|
"attestation": "\ue761",
|
||||||
|
"back": "\ue760",
|
||||||
|
"bag": "\ue75f",
|
||||||
|
"bag-fill": "\ue75e",
|
||||||
|
"balloon": "\ue75d",
|
||||||
|
"bankcard": "\ue75c",
|
||||||
|
"bankcard-fill": "\ue75b",
|
||||||
|
"bottom": "\ue75a",
|
||||||
|
"calendar": "\ue759",
|
||||||
|
"camera": "\ue758",
|
||||||
|
"camera-fill": "\ue757",
|
||||||
|
"camera-add": "\ue756",
|
||||||
|
"card": "\ue755",
|
||||||
|
"card-fill": "\ue754",
|
||||||
|
"cart": "\ue753",
|
||||||
|
"cart-fill": "\ue752",
|
||||||
|
"category": "\ue751",
|
||||||
|
"category-fill": "\ue750",
|
||||||
|
"check": "\ue74f",
|
||||||
|
"circle": "\ue74e",
|
||||||
|
"circle-fill": "\ue74d",
|
||||||
|
"circle-selected": "\ue74c",
|
||||||
|
"clock": "\ue74b",
|
||||||
|
"clock-fill": "\ue74a",
|
||||||
|
"close": "\ue749",
|
||||||
|
"close-fill": "\ue748",
|
||||||
|
"community": "\ue747",
|
||||||
|
"community-fill": "\ue746",
|
||||||
|
"computer": "\ue745",
|
||||||
|
"computer-fill": "\ue744",
|
||||||
|
"coupon": "\ue743",
|
||||||
|
"delete": "\ue742",
|
||||||
|
"deletekey": "\ue741",
|
||||||
|
"dingtalk": "\ue740",
|
||||||
|
"dissatisfied": "\ue73f",
|
||||||
|
"down": "\ue73e",
|
||||||
|
"download": "\ue73d",
|
||||||
|
"edit": "\ue73c",
|
||||||
|
"ellipsis": "\ue73b",
|
||||||
|
"enlarge": "\ue73a",
|
||||||
|
"evaluate": "\ue739",
|
||||||
|
"exchange": "\ue738",
|
||||||
|
"explain": "\ue737",
|
||||||
|
"explain-fill": "\ue736",
|
||||||
|
"explore": "\ue735",
|
||||||
|
"explore-fill": "\ue734",
|
||||||
|
"eye": "\ue733",
|
||||||
|
"feedback": "\ue732",
|
||||||
|
"fingerprint": "\ue730",
|
||||||
|
"friendadd": "\ue72f",
|
||||||
|
"friendadd-fill": "\ue72e",
|
||||||
|
"gps": "\ue72d",
|
||||||
|
"histogram": "\ue72c",
|
||||||
|
"home": "\ue72b",
|
||||||
|
"home-fill": "\ue72a",
|
||||||
|
"house": "\ue729",
|
||||||
|
"imface": "\ue728",
|
||||||
|
"imkeyboard": "\ue727",
|
||||||
|
"immore": "\ue726",
|
||||||
|
"imvoice": "\ue725",
|
||||||
|
"ios": "\ue724",
|
||||||
|
"kefu": "\ue723",
|
||||||
|
"label": "\ue722",
|
||||||
|
"label-fill": "\ue721",
|
||||||
|
"like": "\ue720",
|
||||||
|
"like-fill": "\ue71f",
|
||||||
|
"link": "\ue71e",
|
||||||
|
"listview": "\ue71d",
|
||||||
|
"loading": "\ue71c",
|
||||||
|
"location": "\ue71b",
|
||||||
|
"mail": "\ue71a",
|
||||||
|
"mail-fill": "\ue719",
|
||||||
|
"manage": "\ue718",
|
||||||
|
"manage-fill": "\ue717",
|
||||||
|
"member": "\ue716",
|
||||||
|
"member-fill": "\ue715",
|
||||||
|
"message": "\ue714",
|
||||||
|
"message-fill": "\ue713",
|
||||||
|
"mobile": "\ue712",
|
||||||
|
"moments": "\ue711",
|
||||||
|
"more": "\ue710",
|
||||||
|
"more-fill": "\ue70f",
|
||||||
|
"narrow": "\ue70e",
|
||||||
|
"news": "\ue70d",
|
||||||
|
"news-fill": "\ue70c",
|
||||||
|
"nodata": "\ue70b",
|
||||||
|
"notice": "\ue699",
|
||||||
|
"notice-fill": "\ue698",
|
||||||
|
"offline": "\ue697",
|
||||||
|
"offline-fill": "\ue696",
|
||||||
|
"oppose": "\ue695",
|
||||||
|
"oppose-fill": "\ue694",
|
||||||
|
"order": "\ue693",
|
||||||
|
"partake": "\ue692",
|
||||||
|
"people": "\ue691",
|
||||||
|
"people-fill": "\ue690",
|
||||||
|
"pic": "\ue68f",
|
||||||
|
"pic-fill": "\ue68e",
|
||||||
|
"picture": "\ue68d",
|
||||||
|
"pie": "\ue68c",
|
||||||
|
"plus": "\ue689",
|
||||||
|
"polygonal": "\ue688",
|
||||||
|
"position": "\ue686",
|
||||||
|
"pwd": "\ue685",
|
||||||
|
"qq": "\ue684",
|
||||||
|
"qrcode": "\ue682",
|
||||||
|
"redpacket": "\ue681",
|
||||||
|
"redpacket-fill": "\ue680",
|
||||||
|
"reduce": "\ue67f",
|
||||||
|
"refresh": "\ue67e",
|
||||||
|
"revoke": "\ue67d",
|
||||||
|
"satisfied": "\ue67c",
|
||||||
|
"screen": "\ue67b",
|
||||||
|
"search": "\ue67a",
|
||||||
|
"search-2": "\ue679",
|
||||||
|
"send": "\ue678",
|
||||||
|
"service": "\ue677",
|
||||||
|
"service-fill": "\ue676",
|
||||||
|
"setup": "\ue675",
|
||||||
|
"setup-fill": "\ue674",
|
||||||
|
"share": "\ue673",
|
||||||
|
"share-fill": "\ue672",
|
||||||
|
"shield": "\ue671",
|
||||||
|
"shop": "\ue670",
|
||||||
|
"shop-fill": "\ue66f",
|
||||||
|
"shut": "\ue66e",
|
||||||
|
"signin": "\ue66d",
|
||||||
|
"sina": "\ue66c",
|
||||||
|
"skin": "\ue66b",
|
||||||
|
"soso": "\ue669",
|
||||||
|
"square": "\ue668",
|
||||||
|
"square-fill": "\ue667",
|
||||||
|
"square-selected": "\ue666",
|
||||||
|
"star": "\ue665",
|
||||||
|
"star-fill": "\ue664",
|
||||||
|
"strategy": "\ue663",
|
||||||
|
"sweep": "\ue662",
|
||||||
|
"time": "\ue661",
|
||||||
|
"time-fill": "\ue660",
|
||||||
|
"todown": "\ue65f",
|
||||||
|
"toleft": "\ue65e",
|
||||||
|
"tool": "\ue65d",
|
||||||
|
"top": "\ue65c",
|
||||||
|
"toright": "\ue65b",
|
||||||
|
"towardsleft": "\ue65a",
|
||||||
|
"towardsright": "\ue659",
|
||||||
|
"towardsright-fill": "\ue658",
|
||||||
|
"transport": "\ue657",
|
||||||
|
"transport-fill": "\ue656",
|
||||||
|
"turningdown": "\ue654",
|
||||||
|
"turningleft": "\ue653",
|
||||||
|
"turningright": "\ue652",
|
||||||
|
"turningup": "\ue651",
|
||||||
|
"unreceive": "\ue650",
|
||||||
|
"seen": "\ue7d2",
|
||||||
|
"unseen": "\ue7d1",
|
||||||
|
"up": "\ue64e",
|
||||||
|
"upload": "\ue64c",
|
||||||
|
"video": "\ue64b",
|
||||||
|
"voice": "\ue649",
|
||||||
|
"voice-fill": "\ue648",
|
||||||
|
"voipphone": "\ue647",
|
||||||
|
"wallet": "\ue646",
|
||||||
|
"warning": "\ue645",
|
||||||
|
"wealth": "\ue644",
|
||||||
|
"wealth-fill": "\ue643",
|
||||||
|
"weather": "\ue642",
|
||||||
|
"wechat": "\ue641",
|
||||||
|
"wifi": "\ue640",
|
||||||
|
"play": "\ue7d5",
|
||||||
|
"suspend": "\ue7d4"
|
||||||
|
}
|
||||||
95
src/components/thorui/tui-icon/tui-icon.vue
Normal file
95
src/components/thorui/tui-icon/tui-icon.vue
Normal file
File diff suppressed because one or more lines are too long
1116
src/components/thorui/tui-image-cropper/tui-image-cropper.vue
Normal file
1116
src/components/thorui/tui-image-cropper/tui-image-cropper.vue
Normal file
File diff suppressed because it is too large
Load Diff
164
src/components/thorui/tui-image-group/tui-image-group.vue
Normal file
164
src/components/thorui/tui-image-group/tui-image-group.vue
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="tui-image-container"
|
||||||
|
:style="{ marginBottom: multiLine ? `-${distance}rpx` : 0 }"
|
||||||
|
:class="{ 'tui-image-direction': direction == 'column', 'tui-image__warp': multiLine }"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in imageList"
|
||||||
|
:key="index"
|
||||||
|
class="tui-image__itembox"
|
||||||
|
:style="{
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
borderRadius: radius,
|
||||||
|
marginLeft: direction == 'column' || multiLine ? 0 : (index && distance) + 'rpx',
|
||||||
|
marginRight: multiLine ? distance + 'rpx' : 0,
|
||||||
|
marginBottom: multiLine ? distance + 'rpx' : 0,
|
||||||
|
marginTop: direction == 'row' ? 0 : (index && distance) + 'rpx'
|
||||||
|
}"
|
||||||
|
@tap="bindClick(index, item.id)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="tui-image-item"
|
||||||
|
:mode="mode"
|
||||||
|
:lazy-load="lazyLoad"
|
||||||
|
fade-show="fadeShow"
|
||||||
|
:webp="webp"
|
||||||
|
:show-menu-by-longpress="longpress"
|
||||||
|
@error="error"
|
||||||
|
@load="load"
|
||||||
|
:style="{ width: width, height: height, borderRadius: radius, borderWidth: borderWidth, borderColor: borderColor }"
|
||||||
|
:src="item.src"
|
||||||
|
></image>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiImageGroup',
|
||||||
|
emits: ['errorEvent','loaded','click'],
|
||||||
|
props: {
|
||||||
|
//图片集合
|
||||||
|
/*
|
||||||
|
[{id:1,src:"1.png"}]
|
||||||
|
*/
|
||||||
|
imageList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//图片宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '120rpx'
|
||||||
|
},
|
||||||
|
//图片高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '120rpx'
|
||||||
|
},
|
||||||
|
//图片边框宽度 rpx
|
||||||
|
borderWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//图片边框颜色 可传rgba
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//图片圆角
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '50%'
|
||||||
|
},
|
||||||
|
//图片裁剪、缩放的模式
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'scaleToFill'
|
||||||
|
},
|
||||||
|
//图片懒加载。只针对page与scroll-view下的image有效
|
||||||
|
lazyLoad: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//图片显示动画效果 | 仅App-nvue 2.3.4+ Android有效
|
||||||
|
fadeShow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//默认不解析 webP 格式,只支持网络资源 | 微信小程序2.9.0
|
||||||
|
webp: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//开启长按图片显示识别小程序码菜单 | 微信小程序2.7.0
|
||||||
|
longpress: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否组合排列
|
||||||
|
isGroup: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//排列方向 row ,column
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'row'
|
||||||
|
},
|
||||||
|
//偏移距离 rpx
|
||||||
|
distance: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: -16
|
||||||
|
},
|
||||||
|
//是否可多行展示,排列方向 row时生效,distance需设置为大于0的数
|
||||||
|
multiLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
error(e) {
|
||||||
|
this.$emit('errorEvent', e);
|
||||||
|
},
|
||||||
|
load(e) {
|
||||||
|
this.$emit('loaded', e);
|
||||||
|
},
|
||||||
|
bindClick(index, id) {
|
||||||
|
this.$emit('click', {
|
||||||
|
index: index,
|
||||||
|
id: id || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-image-container {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.tui-image-direction {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.tui-image__warp {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.tui-image__itembox {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tui-image-item {
|
||||||
|
border-style: solid;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
629
src/components/thorui/tui-index-list/tui-index-list.vue
Normal file
629
src/components/thorui/tui-index-list/tui-index-list.vue
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-index-list">
|
||||||
|
<scroll-view class="tui-scroll__view" :style="{ height: getHeight }" scroll-y :scroll-top="scrollTop"
|
||||||
|
@scroll="scroll">
|
||||||
|
<slot name="header"></slot>
|
||||||
|
<view class="tui-content__box">
|
||||||
|
<view class="tui-item__select" v-for="(item, index) in listData" :key="index">
|
||||||
|
<view v-if="index == listItemCur" class="tui-content__title"
|
||||||
|
:class="{ 'tui-line__top': topLine, 'tui-line__bottom': bottomLine }">
|
||||||
|
<view class="tui-title__item"
|
||||||
|
:style="{ background: background_cur, color: color_cur, fontSize: size, height: height, padding: padding }">
|
||||||
|
{{ item.letter }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="index == listItemCur + 1" class="tui-content__title"
|
||||||
|
:class="{ 'tui-line__top': topLine, 'tui-line__bottom': bottomLine }">
|
||||||
|
<view class="tui-title__item"
|
||||||
|
:style="{ background: background_next, color: color_next, fontSize: size, height: height, padding: padding }">
|
||||||
|
{{ item.letter }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="tui-content__title"
|
||||||
|
:class="{ 'tui-line__top': topLine, 'tui-line__bottom': bottomLine }">
|
||||||
|
<view class="tui-title__item"
|
||||||
|
:style="{ background: background, color: color, fontSize: size, height: height, padding: padding }">
|
||||||
|
{{ item.letter }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<slot name="item" :entity="item.data" :index="index"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="tui-index__indicator"
|
||||||
|
:class="[touching && indicatorTop != -1 ? 'tui-indicator__show' : '', treeKeyTran ? 'tui-indicator__tran' : '']"
|
||||||
|
:style="{ top: indicatorTop + 'px' }">
|
||||||
|
{{ listData[treeItemCur] && listData[treeItemCur].letter }}
|
||||||
|
</view>
|
||||||
|
<view id="tui_index__letter" class="tui-index__letter" @touchstart.stop="touchStart"
|
||||||
|
@touchmove.stop.prevent="touchMove" @touchend.stop="touchEnd" @touchcancel.stop="touchEnd">
|
||||||
|
<view class="tui-letter__item" :class="[index === treeItemCur ? 'tui-letter__cur' : '']"
|
||||||
|
v-for="(item, index) in listData" :key="index" @tap="letterClick(index,item.letter)">
|
||||||
|
<view class="tui-letter__key"
|
||||||
|
:style="{ background: index === treeItemCur ? getActiveKeyBgColor : '', color: index === treeItemCur ? activeKeyColor : keyColor }">
|
||||||
|
{{ item.letter }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let ColorUtil = {
|
||||||
|
rgbToHex(r, g, b) {
|
||||||
|
let hex = ((r << 16) | (g << 8) | b).toString(16);
|
||||||
|
return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex;
|
||||||
|
},
|
||||||
|
hexToRgb(hex) {
|
||||||
|
let rgb = [];
|
||||||
|
if (hex.length === 4) {
|
||||||
|
let text = hex.substring(1, 4);
|
||||||
|
hex = '#' + text + text;
|
||||||
|
}
|
||||||
|
for (let i = 1; i < 7; i += 2) {
|
||||||
|
rgb.push(parseInt('0x' + hex.slice(i, i + 2)));
|
||||||
|
}
|
||||||
|
return rgb;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 生成渐变过渡色数组 {startColor: 开始颜色值, endColor: 结束颜色值, step: 生成色值数组长度}
|
||||||
|
*/
|
||||||
|
gradient(startColor, endColor, step) {
|
||||||
|
// 将hex转换为rgb
|
||||||
|
let sColor = this.hexToRgb(startColor),
|
||||||
|
eColor = this.hexToRgb(endColor);
|
||||||
|
|
||||||
|
// 计算R\G\B每一步的差值
|
||||||
|
let rStep = (eColor[0] - sColor[0]) / step,
|
||||||
|
gStep = (eColor[1] - sColor[1]) / step,
|
||||||
|
bStep = (eColor[2] - sColor[2]) / step;
|
||||||
|
|
||||||
|
let gradientColorArr = [];
|
||||||
|
for (let i = 0; i < step; i++) {
|
||||||
|
// 计算每一步的hex值
|
||||||
|
gradientColorArr.push(this.rgbToHex(parseInt(rStep * i + sColor[0]), parseInt(gStep * i + sColor[1]),
|
||||||
|
parseInt(bStep * i + sColor[2])));
|
||||||
|
}
|
||||||
|
return gradientColorArr;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 生成随机颜色值
|
||||||
|
*/
|
||||||
|
generateColor() {
|
||||||
|
let color = '#';
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
color += ((Math.random() * 16) | 0).toString(16);
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
name: 'tuiIndexList',
|
||||||
|
emits: ['letterClick'],
|
||||||
|
props: {
|
||||||
|
// 数据源
|
||||||
|
listData: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 顶部高度
|
||||||
|
top: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 底部高度
|
||||||
|
bottom: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//top和bottom单位,可传rpx 或 px
|
||||||
|
unit: {
|
||||||
|
type: String,
|
||||||
|
default: 'px'
|
||||||
|
},
|
||||||
|
//sticky letter 是否显示上边线条
|
||||||
|
topLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//sticky letter 是否显示下边线条
|
||||||
|
bottomLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '60rpx'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: '#666'
|
||||||
|
},
|
||||||
|
activeColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '26rpx'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: String,
|
||||||
|
default: '#ededed'
|
||||||
|
},
|
||||||
|
activeBackground: {
|
||||||
|
type: String,
|
||||||
|
default: '#FFFFFF'
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '0 20rpx'
|
||||||
|
},
|
||||||
|
keyColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#666'
|
||||||
|
},
|
||||||
|
activeKeyColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#FFFFFF'
|
||||||
|
},
|
||||||
|
activeKeyBackground: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//重新初始化[可异步加载时使用,设置大于0的数]
|
||||||
|
reinit: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getHeight() {
|
||||||
|
return `calc(100vh - ${this.top + this.bottom + this.unit})`;
|
||||||
|
},
|
||||||
|
getChange() {
|
||||||
|
return `${this.top}-${this.bottom}-${this.reinit}`;
|
||||||
|
},
|
||||||
|
getActiveColor(){
|
||||||
|
return this.activeColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
},
|
||||||
|
getActiveKeyBgColor(){
|
||||||
|
return this.activeKeyBackground || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
listData(val) {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
getChange(val) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
remScale: 1, // 缩放比例
|
||||||
|
realTop: 0, // 计算后顶部高度实际值
|
||||||
|
realBottom: 0, // 计算后底部高度实际值
|
||||||
|
treeInfo: {
|
||||||
|
// 索引树节点信息
|
||||||
|
treeTop: 0,
|
||||||
|
treeBottom: 0,
|
||||||
|
itemHeight: 0,
|
||||||
|
itemMount: 0
|
||||||
|
},
|
||||||
|
indicatorTopList: [], // 指示器节点信息列表
|
||||||
|
maxScrollTop: 0, // 最大滚动高度
|
||||||
|
blocks: [], // 节点组信息
|
||||||
|
|
||||||
|
/* 渲染数据 */
|
||||||
|
treeItemCur: -1, // 索引树的聚焦项
|
||||||
|
listItemCur: -1, // 节点树的聚焦项
|
||||||
|
touching: false, // 是否在触摸索引树中
|
||||||
|
scrollTop: 0, // 节点树滚动高度
|
||||||
|
indicatorTop: -1, // 指示器顶部距离
|
||||||
|
treeKeyTran: false,
|
||||||
|
background_cur: '',
|
||||||
|
color_cur: '',
|
||||||
|
background_next: '',
|
||||||
|
color_next: '',
|
||||||
|
colors: [],
|
||||||
|
backgroundColors: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
scroll(e) {
|
||||||
|
if (this.touching) return;
|
||||||
|
|
||||||
|
let scrollTop = e.detail.scrollTop;
|
||||||
|
if (scrollTop > this.maxScrollTop) return;
|
||||||
|
|
||||||
|
let blocks = this.blocks,
|
||||||
|
stickyTitleHeight = this.remScale * 30;
|
||||||
|
let len = blocks.length - 1;
|
||||||
|
this.background_cur = this.background;
|
||||||
|
this.color_cur = this.color;
|
||||||
|
for (let i = len; i >= 0; i--) {
|
||||||
|
let block = blocks[i];
|
||||||
|
// 判断当前滚动值 scrollTop 所在区间, 以得到当前聚焦项
|
||||||
|
if (scrollTop >= block.itemTop && scrollTop < block.itemBottom) {
|
||||||
|
// 判断当前滚动值 scrollTop 是否在当前聚焦项底一个 .block__title 高度范围内, 如果是则开启过度色值计算
|
||||||
|
if (scrollTop > block.itemBottom - stickyTitleHeight) {
|
||||||
|
let percent = Math.floor(((scrollTop - (block.itemBottom - stickyTitleHeight)) /
|
||||||
|
stickyTitleHeight) * 100);
|
||||||
|
this.background_cur = this.backgroundColors[percent];
|
||||||
|
this.color_cur = this.colors[percent];
|
||||||
|
this.background_next = this.backgroundColors[100 - percent];
|
||||||
|
this.color_next = this.colors[100 - percent];
|
||||||
|
this.treeItemCur = i;
|
||||||
|
this.listItemCur = i;
|
||||||
|
} else if (scrollTop <= block.itemBottom - stickyTitleHeight) {
|
||||||
|
this.background_cur = this.activeBackground;
|
||||||
|
this.color_cur = this.getActiveColor;
|
||||||
|
this.background_next = this.background;
|
||||||
|
this.color_next = this.color;
|
||||||
|
this.treeItemCur = i;
|
||||||
|
this.listItemCur = i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* tree 触摸开始
|
||||||
|
*/
|
||||||
|
touchStart(e) {
|
||||||
|
// 获取触摸点信息
|
||||||
|
let startTouch = e.changedTouches[0];
|
||||||
|
if (!startTouch) return;
|
||||||
|
this.touching = true;
|
||||||
|
let treeItemCur = this.getCurrentTreeItem(startTouch.pageY);
|
||||||
|
this.setValue(treeItemCur);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* tree 触摸移动
|
||||||
|
*/
|
||||||
|
touchMove(e) {
|
||||||
|
// 获取触摸点信息
|
||||||
|
let currentTouch = e.changedTouches[0];
|
||||||
|
if (!currentTouch) return;
|
||||||
|
|
||||||
|
// 滑动结束后迅速开始第二次滑动时候 touching 为 false 造成不显示 indicator 问题
|
||||||
|
if (!this.touching) {
|
||||||
|
this.touching = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let treeItemCur = this.getCurrentTreeItem(currentTouch.pageY);
|
||||||
|
this.setValue(treeItemCur);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* tree 触摸结束
|
||||||
|
*/
|
||||||
|
touchEnd(e) {
|
||||||
|
let treeItemCur = this.treeItemCur;
|
||||||
|
let listItemCur = this.listItemCur;
|
||||||
|
if (treeItemCur !== listItemCur) {
|
||||||
|
this.treeItemCur = listItemCur;
|
||||||
|
this.indicatorTop = this.indicatorTopList[treeItemCur];
|
||||||
|
}
|
||||||
|
this.treeKeyTran = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.touching = false;
|
||||||
|
this.treeKeyTran = false;
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
letterClick(index, letter) {
|
||||||
|
// #ifdef H5
|
||||||
|
this.setValue(index);
|
||||||
|
this.touchEnd()
|
||||||
|
// #endif
|
||||||
|
this.$emit('letterClick', {
|
||||||
|
index: index,
|
||||||
|
letter: letter
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取当前触摸的 tree-item
|
||||||
|
* @param pageY: 当前触摸点pageY
|
||||||
|
*/
|
||||||
|
getCurrentTreeItem(pageY) {
|
||||||
|
let {
|
||||||
|
treeTop,
|
||||||
|
treeBottom,
|
||||||
|
itemHeight,
|
||||||
|
itemMount
|
||||||
|
} = this.treeInfo;
|
||||||
|
|
||||||
|
if (pageY < treeTop) {
|
||||||
|
return 0;
|
||||||
|
} else if (pageY >= treeBottom) {
|
||||||
|
return itemMount - 1;
|
||||||
|
} else {
|
||||||
|
return Math.floor((pageY - treeTop) / itemHeight);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸之后后设置对应value
|
||||||
|
*/
|
||||||
|
setValue(treeItemCur) {
|
||||||
|
if (treeItemCur === this.treeItemCur) return;
|
||||||
|
|
||||||
|
let block = this.blocks[treeItemCur];
|
||||||
|
if (!block) return;
|
||||||
|
|
||||||
|
let {
|
||||||
|
scrollTop,
|
||||||
|
scrollIndex
|
||||||
|
} = block,
|
||||||
|
indicatorTop = this.indicatorTopList[treeItemCur];
|
||||||
|
|
||||||
|
this.background_cur = this.activeBackground;
|
||||||
|
this.color_cur = this.getActiveColor;
|
||||||
|
this.background_next = this.background;
|
||||||
|
this.color_next = this.color;
|
||||||
|
this.treeItemCur = treeItemCur;
|
||||||
|
this.scrollTop = scrollTop;
|
||||||
|
this.listItemCur = scrollIndex;
|
||||||
|
this.indicatorTop = indicatorTop;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 清除参数
|
||||||
|
*/
|
||||||
|
clearData() {
|
||||||
|
this.treeItemCur = 0; // 索引树的聚焦项
|
||||||
|
this.listItemCur = 0; // 节点树的聚焦项
|
||||||
|
this.touching = false; // 是否在触摸索引树中
|
||||||
|
this.scrollTop = 0; // 节点树滚动高度
|
||||||
|
this.indicatorTop = -1; // 指示器顶部距离
|
||||||
|
this.treeKeyTran = false;
|
||||||
|
this.background_cur = this.background;
|
||||||
|
this.color_cur = this.color;
|
||||||
|
this.background_next = this.background;
|
||||||
|
this.color_next = this.color;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化获取 dom 信息
|
||||||
|
*/
|
||||||
|
initDom() {
|
||||||
|
let {
|
||||||
|
windowHeight,
|
||||||
|
windowWidth
|
||||||
|
} = uni.getSystemInfoSync();
|
||||||
|
let remScale = (windowWidth || 375) / 375,
|
||||||
|
realTop = (this.top * remScale) / 2,
|
||||||
|
realBottom = (this.bottom * remScale) / 2,
|
||||||
|
colors = ColorUtil.gradient(this.getActiveColor, this.color, 100),
|
||||||
|
backgroundColors = ColorUtil.gradient(this.activeBackground, this.background, 100);
|
||||||
|
|
||||||
|
this.remScale = remScale;
|
||||||
|
this.realTop = realTop;
|
||||||
|
this.realBottom = realBottom;
|
||||||
|
this.colors = colors;
|
||||||
|
this.backgroundColors = backgroundColors;
|
||||||
|
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.select('#tui_index__letter')
|
||||||
|
.boundingClientRect(res => {
|
||||||
|
let treeTop = res.top,
|
||||||
|
treeBottom = res.top + res.height,
|
||||||
|
itemHeight = res.height / this.listData.length,
|
||||||
|
itemMount = this.listData.length;
|
||||||
|
|
||||||
|
let indicatorTopList = this.listData.map((item, index) => {
|
||||||
|
return itemHeight / 2 + index * itemHeight + treeTop - remScale * 25;
|
||||||
|
});
|
||||||
|
this.treeInfo = {
|
||||||
|
treeTop: treeTop,
|
||||||
|
treeBottom: treeBottom,
|
||||||
|
itemHeight: itemHeight,
|
||||||
|
itemMount: itemMount
|
||||||
|
};
|
||||||
|
this.indicatorTopList = indicatorTopList;
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.select('.tui-content__box')
|
||||||
|
.boundingClientRect(res => {
|
||||||
|
let maxScrollTop = res.height - (windowHeight - realTop - realBottom);
|
||||||
|
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(this)
|
||||||
|
.selectAll('.tui-item__select')
|
||||||
|
.boundingClientRect(res => {
|
||||||
|
let maxScrollIndex = -1;
|
||||||
|
|
||||||
|
let blocks = res.map((item, index) => {
|
||||||
|
// Math.ceil 向上取整, 防止索引树切换列表时候造成真机固定头部上边线显示过粗问题
|
||||||
|
let itemTop = Math.ceil(item.top - realTop),
|
||||||
|
itemBottom = Math.ceil(itemTop + item.height);
|
||||||
|
|
||||||
|
if (maxScrollTop >= itemTop && maxScrollTop < itemBottom)
|
||||||
|
maxScrollIndex = index;
|
||||||
|
|
||||||
|
return {
|
||||||
|
itemTop: itemTop,
|
||||||
|
itemBottom: itemBottom,
|
||||||
|
scrollTop: itemTop >= maxScrollTop ? maxScrollTop : itemTop,
|
||||||
|
scrollIndex: maxScrollIndex === -1 ? index : maxScrollIndex
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.maxScrollTop = maxScrollTop;
|
||||||
|
this.blocks = blocks;
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
this.clearData();
|
||||||
|
// 避免获取不到节点信息报错问题
|
||||||
|
if (this.listData.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 异步加载数据时候, 延迟执行 initDom 方法
|
||||||
|
setTimeout(() => this.initDom(), 1200);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(()=>{
|
||||||
|
this.init();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-index-list {
|
||||||
|
width: 100vw;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-scroll__view {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-content__box {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-content__title {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-content__title .tui-title__item {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__top::before {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
border-top: 1px solid #ebedf0;
|
||||||
|
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__bottom::after {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1px solid #ebedf0;
|
||||||
|
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform: scaleY(0.5) translateZ(0);
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-index__indicator {
|
||||||
|
position: fixed;
|
||||||
|
right: 100rpx;
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
line-height: 100rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 60rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
display: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-index__indicator:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
border-radius: 100% 0% 100% 100%;
|
||||||
|
background: #c9c9c9;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-indicator__show {
|
||||||
|
display: block;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-indicator__tran {
|
||||||
|
display: block;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-index__letter {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-letter__item {
|
||||||
|
padding: 0 8rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-letter__key {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 26rpx;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transform-origin: center center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-list__item {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-list__item .tui-avatar {
|
||||||
|
width: 68rpx;
|
||||||
|
height: 68rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-list__item view {
|
||||||
|
width: 90%;
|
||||||
|
font-size: 32rpx;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
padding-right: 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
578
src/components/thorui/tui-input/tui-input.vue
Normal file
578
src/components/thorui/tui-input/tui-input.vue
Normal file
@ -0,0 +1,578 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-input__wrap"
|
||||||
|
:class="{'tui-border__top':borderTop && !inputBorder,'tui-border__bottom':borderBottom && !inputBorder,'tui-radius__fillet':isFillet && !getRadius,'tui-input__border-nvue':inputBorder}"
|
||||||
|
:style="getStyles" @tap="fieldClick">
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<view class="tui-input__border-top" v-if="borderTop && !inputBorder" :style="{borderTopColor:borderColor}">
|
||||||
|
</view>
|
||||||
|
<view class="tui-input__border-bottom" :class="{'tui-line__left':lineLeft}" v-if="borderBottom && !inputBorder"
|
||||||
|
:style="{borderBottomColor:borderColor}"></view>
|
||||||
|
<view class="tui-input__border" :class="{'tui-radius__fillet':isFillet && !getRadius}" v-if="inputBorder"
|
||||||
|
:style="{borderColor:borderColor,borderRadius:(getRadius*2)+'rpx'}"></view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef APP-NVUE -->
|
||||||
|
<view class="tui-input__required" v-if="required">
|
||||||
|
<text :style="{color:getRequiredColor}">*</text>
|
||||||
|
</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<view class="tui-input__required" :style="{color:getRequiredColor}" v-if="required">*</view>
|
||||||
|
<!-- #endif -->
|
||||||
|
<view class="tui-input__label"
|
||||||
|
:style="{fontSize:getLabelSize+'rpx',color:getLabelColor,minWidth:labelWidth+'rpx'}" v-if="label">
|
||||||
|
<text :style="{fontSize:getLabelSize+'rpx',color:getLabelColor}">{{label}}</text>
|
||||||
|
</view>
|
||||||
|
<slot name="left"></slot>
|
||||||
|
<input class="tui-input__self" :class="{'tui-text__right':textRight,'tui-input__disabled':disabled}"
|
||||||
|
:style="{fontSize:getSize+'rpx',color:color}" placeholder-class="tui-input__placeholder" :type="type"
|
||||||
|
:name="name" :value="inputVal" :password="password" :placeholder="inputVal?'':placeholder"
|
||||||
|
:placeholder-style="placeholderStyl" :disabled="disabled" :cursor-spacing="cursorSpacing"
|
||||||
|
:maxlength="maxlength" :focus="focused" :confirm-type="confirmType" :confirm-hold="confirmHold"
|
||||||
|
:cursor="cursor" :selection-start="selectionStart" :selection-end="selectionEnd"
|
||||||
|
:adjust-position="adjustPosition" :hold-keyboard="holdKeyboard" :auto-blur="autoBlur" @focus="onFocus"
|
||||||
|
@blur="onBlur" @input="onInput" @confirm="onConfirm" @keyboardheightchange="onKeyboardheightchange" />
|
||||||
|
<icon type="clear" :size="clearSize" :color="clearColor" v-if="clearable && inputVal != ''" @tap.stop="onClear">
|
||||||
|
</icon>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-input",
|
||||||
|
emits: ['input', 'update:modelValue', 'focus', 'blur', 'confirm', 'click', 'keyboardheightchange'],
|
||||||
|
//这里加group是为了避免在表单中使用时给组件加value属性
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
behaviors: ['wx://form-field-group'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-BAIDU
|
||||||
|
behaviors: ['swan://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-QQ
|
||||||
|
behaviors: ['qq://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
behaviors: ['uni://form-field'],
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
//是否为必填项
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
requiredColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//左侧标题
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//标题字体大小
|
||||||
|
labelSize: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
labelColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//label 最小宽度 rpx
|
||||||
|
labelWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 140
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//px
|
||||||
|
clearSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 15
|
||||||
|
},
|
||||||
|
clearColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#bfbfbf'
|
||||||
|
},
|
||||||
|
//获取焦点
|
||||||
|
focus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
placeholderStyle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//输入框名称
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//输入框值
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// #ifdef VUE3
|
||||||
|
//输入框值
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
modelModifiers: {
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
//与官方input type属性一致
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
maxlength: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 140
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 'NaN'
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 'NaN'
|
||||||
|
},
|
||||||
|
cursorSpacing: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
confirmType: {
|
||||||
|
type: String,
|
||||||
|
default: 'done'
|
||||||
|
},
|
||||||
|
confirmHold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
selectionStart: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
selectionEnd: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
adjustPosition: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
holdKeyboard: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
autoBlur: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//输入框字体大小 rpx
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//输入框字体颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 是否显示 input 边框
|
||||||
|
inputBorder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
borderColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(0, 0, 0, 0.1)'
|
||||||
|
},
|
||||||
|
//input是否显示为圆角
|
||||||
|
isFillet: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否显示上边框
|
||||||
|
borderTop: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否显示下边框
|
||||||
|
borderBottom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//下边框线条是否有左偏移距离
|
||||||
|
lineLeft: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 是否自动去除两端的空格
|
||||||
|
trim: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
textRight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//输入框padding值
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//输入框背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
//输入框margin-top值 rpx
|
||||||
|
marginTop: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getLabelSize() {
|
||||||
|
return this.labelSize || (uni && uni.$tui && uni.$tui.tuiInput.labelSize) || 32
|
||||||
|
},
|
||||||
|
getLabelColor() {
|
||||||
|
return this.labelColor || (uni && uni.$tui && uni.$tui.tuiInput.labelColor) || '#333'
|
||||||
|
},
|
||||||
|
getSize() {
|
||||||
|
return this.size || (uni && uni.$tui && uni.$tui.tuiInput.size) || 32
|
||||||
|
},
|
||||||
|
getColor() {
|
||||||
|
return this.color || (uni && uni.$tui && uni.$tui.tuiInput.color) || '#333'
|
||||||
|
},
|
||||||
|
getRadius() {
|
||||||
|
let radius = this.radius
|
||||||
|
if (radius === -1 || radius === true) {
|
||||||
|
radius = uni && uni.$tui && uni.$tui.tuiInput.radius
|
||||||
|
}
|
||||||
|
return Number(radius || 0)
|
||||||
|
},
|
||||||
|
getStyles() {
|
||||||
|
const padding = this.padding || (uni && uni.$tui && uni.$tui.tuiInput.padding) || '26rpx 30rpx';
|
||||||
|
const bgColor = this.backgroundColor || (uni && uni.$tui && uni.$tui.tuiInput.backgroundColor) ||
|
||||||
|
'#FFFFFF';
|
||||||
|
let radius = this.getRadius;
|
||||||
|
let styles = `padding:${padding};background:${bgColor};margin-top:${this.marginTop}rpx;`
|
||||||
|
if (radius && radius !== true && radius !== -1) {
|
||||||
|
styles += `border-radius:${radius}rpx;`
|
||||||
|
}
|
||||||
|
if (this.borderTop || this.borderBottom || this.inputBorder) {
|
||||||
|
styles += `border-color:${this.borderColor};`
|
||||||
|
}
|
||||||
|
return styles
|
||||||
|
},
|
||||||
|
getRequiredColor() {
|
||||||
|
return this.requiredColor || (uni && uni.$tui && uni.$tui.tuiInput.requiredColor) || '#EB0909'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
placeholderStyl: '',
|
||||||
|
focused: false,
|
||||||
|
inputVal: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
focus(val) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.focused = val
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
placeholderStyle() {
|
||||||
|
this.fieldPlaceholderStyle()
|
||||||
|
},
|
||||||
|
// #ifdef VUE3
|
||||||
|
modelValue(newVal) {
|
||||||
|
this.inputVal = newVal
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
value(newVal) {
|
||||||
|
this.inputVal = newVal
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fieldPlaceholderStyle()
|
||||||
|
setTimeout(() => {
|
||||||
|
// #ifndef VUE3
|
||||||
|
this.inputVal = this.value
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef VUE3
|
||||||
|
if (this.value && !this.modelValue) {
|
||||||
|
this.inputVal = this.value
|
||||||
|
} else {
|
||||||
|
this.inputVal = this.modelValue
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}, 50)
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.focused = this.focus
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fieldPlaceholderStyle() {
|
||||||
|
if (this.placeholderStyle) {
|
||||||
|
this.placeholderStyl = this.placeholderStyle
|
||||||
|
} else {
|
||||||
|
const size = uni.upx2px(this.size || (uni && uni.$tui && uni.$tui.tuiInput.size) || 32)
|
||||||
|
this.placeholderStyl = `font-size:${size}px`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onInput(event) {
|
||||||
|
let value = event.detail.value;
|
||||||
|
if (this.trim) value = this.trimStr(value);
|
||||||
|
this.inputVal = value
|
||||||
|
//数字类型 数值不能超过最大整数安全范围,一但超过则返回字符串
|
||||||
|
const cVal = Number(value)
|
||||||
|
if ((this.modelModifiers.number || this.type === 'digit' || this.type === 'number') && !isNaN(cVal) &&
|
||||||
|
Number.isSafeInteger(cVal)) {
|
||||||
|
let eVal = this.type === 'digit' ? value : cVal
|
||||||
|
if (typeof cVal === 'number') {
|
||||||
|
const min = Number(this.min)
|
||||||
|
const max = Number(this.max)
|
||||||
|
if (typeof min === 'number' && cVal < min) {
|
||||||
|
eVal = min
|
||||||
|
} else if (typeof max === 'number' && max < cVal) {
|
||||||
|
eVal = max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = isNaN(eVal) ? value : eVal
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
event.detail.value !== '' && (this.inputVal = value);
|
||||||
|
})
|
||||||
|
const inputValue = event.detail.value !== '' ? value : ''
|
||||||
|
this.$emit('input', inputValue);
|
||||||
|
// #ifdef VUE3
|
||||||
|
this.$emit('update:modelValue', inputValue)
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
onFocus(event) {
|
||||||
|
this.$emit('focus', event);
|
||||||
|
},
|
||||||
|
onBlur(event) {
|
||||||
|
this.$emit('blur', event);
|
||||||
|
},
|
||||||
|
onConfirm(e) {
|
||||||
|
this.$emit('confirm', e);
|
||||||
|
},
|
||||||
|
onClear(event) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
uni.hideKeyboard()
|
||||||
|
this.inputVal = '';
|
||||||
|
this.$emit('input', '');
|
||||||
|
// #ifdef VUE3
|
||||||
|
this.$emit('update:modelValue', '')
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
fieldClick() {
|
||||||
|
this.$emit('click', {
|
||||||
|
name: this.name
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onKeyboardheightchange(e) {
|
||||||
|
this.$emit('keyboardheightchange', e.detail)
|
||||||
|
},
|
||||||
|
trimStr(str) {
|
||||||
|
return str.replace(/^\s+|\s+$/g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-input__wrap {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
padding: 26rpx 30rpx;
|
||||||
|
/* #endif */
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
|
||||||
|
.tui-input__border-top {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
border-top: 1px solid var(--thorui-line-color, rgba(0, 0, 0, 0.1));
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-input__border-bottom {
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1px solid var(--thorui-line-color, rgba(0, 0, 0, 0.1));
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-line__left {
|
||||||
|
left: 30rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
.tui-border__top {
|
||||||
|
border-top-width: 0.5px;
|
||||||
|
border-top-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-border__bottom {
|
||||||
|
border-top-width: 0.5px;
|
||||||
|
border-top-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
.tui-input__required {
|
||||||
|
position: absolute;
|
||||||
|
left: 12rpx;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
height: 30rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
line-height: 1.15;
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-input__label {
|
||||||
|
padding-right: 12rpx;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-input__self {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 12rpx;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: visible;
|
||||||
|
/* #endif */
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-input__placeholder {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
color: var(--thorui-text-color-placeholder, #ccc);
|
||||||
|
overflow: visible;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 32rpx;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #ifdef MP */
|
||||||
|
::v-deep .tui-input__placeholder {
|
||||||
|
color: var(--thorui-text-color-placeholder, #ccc);
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
.tui-input__border-nvue {
|
||||||
|
border-width: 0.5px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
.tui-input__disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-input__border {
|
||||||
|
position: absolute;
|
||||||
|
height: 200%;
|
||||||
|
width: 200%;
|
||||||
|
border: 1px solid var(--thorui-border-color, #d1d1d1);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scale(0.5);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
.tui-radius__fillet {
|
||||||
|
border-radius: 100px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-text__right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-keyboard-input tui-pwd-box" :style="{backgroundColor:backgroundColor}">
|
||||||
|
<view class="tui-inner-box">
|
||||||
|
<view class="tui-input" :class="[inputvalue.length===4?'tui-margin-right':'']" :style="{fontSize:size+'rpx',color:color,width:(inputvalue.length===4?90:70)+'rpx' }"
|
||||||
|
v-for="(item,index) in inputvalue" :key="index">{{item}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiKeyboardInput",
|
||||||
|
props: {
|
||||||
|
//背景颜色
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: "#fff"
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "#333"
|
||||||
|
},
|
||||||
|
//输入框的值:数组格式,长度即为输入框个数
|
||||||
|
inputvalue: {
|
||||||
|
type: Array,
|
||||||
|
default: ["", "", "", "", "", ""] //密码圆点 ●
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-pwd-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-inner-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-input {
|
||||||
|
height: 80rpx;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
border-bottom: 2px solid #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-margin-right {
|
||||||
|
margin-right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-input:last-child {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
241
src/components/thorui/tui-keyboard/tui-keyboard.vue
Normal file
241
src/components/thorui/tui-keyboard/tui-keyboard.vue
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="tui-keyboard-mask" :class="[show?'tui-mask-show':'']" v-if="mask" @tap="handleClose"></view>
|
||||||
|
<view class="tui-keyboard" :class="{'tui-keyboard-radius':radius,'tui-keyboard-action':action,'tui-keyboard-show':show}">
|
||||||
|
<slot></slot>
|
||||||
|
<view class="tui-keyboard-grids">
|
||||||
|
<!--{{(index==9 || index==10 || index==11)?'tui-grid-bottom':''}}-->
|
||||||
|
<view class="tui-keyboard-grid" :class="{'tui-bg-gray':index==9 || index==11}" v-for="(item,index) in itemList"
|
||||||
|
:key="index" hover-class="tui-keyboard-hover" :hover-stay-time="150" @tap="handleClick" :data-index="index">
|
||||||
|
<view v-if="index<11" class="tui-keyboard-item" :class="{'tui-fontsize-32':index==9}">{{getKeyBoard(index,action)}}</view>
|
||||||
|
<view v-else class="tui-keyboard-item">
|
||||||
|
<view class="tui-icon tui-keyboard-delete"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tuiKeyboard",
|
||||||
|
emits: ['click','close'],
|
||||||
|
props: {
|
||||||
|
//是否需要mask
|
||||||
|
mask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//控制键盘显示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//是否直接显示,不需要动画,一般使用在锁屏密码
|
||||||
|
action: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否带圆角
|
||||||
|
radius: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
itemList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getKeyBoard: function(index, action) {
|
||||||
|
var content = index + 1;
|
||||||
|
if (index == 9) {
|
||||||
|
content = action ? "取消" : "清除";
|
||||||
|
} else if (index == 10) {
|
||||||
|
content = 0;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
//关闭
|
||||||
|
handleClose() {
|
||||||
|
if (!this.show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('close', {});
|
||||||
|
},
|
||||||
|
handleClick(e) {
|
||||||
|
if (!this.show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataset = e.currentTarget.dataset;
|
||||||
|
this.$emit('click', {
|
||||||
|
index: Number(dataset.index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'keyboardFont';
|
||||||
|
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAASgAA0AAAAABugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEhAAAABoAAAAch/nJvUdERUYAAARkAAAAHgAAAB4AKQAKT1MvMgAAAZwAAABDAAAAVj4mSapjbWFwAAAB8AAAAD4AAAFCAA/rY2dhc3AAAARcAAAACAAAAAj//wADZ2x5ZgAAAjwAAACsAAAA0BLVU2FoZWFkAAABMAAAAC0AAAA2FXPmsWhoZWEAAAFgAAAAHAAAACQH3gOFaG10eAAAAeAAAAAOAAAAEAwAAABsb2NhAAACMAAAAAoAAAAKAGgAAG1heHAAAAF8AAAAHwAAACABEQBLbmFtZQAAAugAAAFJAAACiCnmEVVwb3N0AAAENAAAACgAAAA6nLlLs3jaY2BkYGAAYukqK754fpuvDNwsDCBwU+tiFBKtwMLA9ABIczAwgUQB4ccH+gAAAHjaY2BkYGBu+N/AEMPCAAJAkpEBFbAAAEcKAm142mNgZGBgYGGwZ2BmAAEmIOYCQgaG/2A+AwAPIgFdAHjaY2BkYWCcwMDKwMDUyXSGgYGhH0IzvmYwYuQAijKwMjNgBQFprikMDs93PN/B3PC/gSGGuYGhASjMCJIDAPenDU0AeNpjYYAAFigGAACAAA0AAHjaY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChED+8x3//0NICW+oSgZGNgYYk4GRCUgwMaACRoZhDwAItAhZAAAAAAAAAAAAAABoAAB42l3MTQqCUBSG4fNpqBxECS/+YFTXRGcFKteZjW0nuoqWVtOgPbgKZ1cqaBDN3snzkklE+xUZEwUkqSOCzGx4EGGEsJYd2vURgQdbomhayC0iu8h8lEVmiR1sS4TVGVFYqeaEVjXmVT8TsWjf83yYIjFq1QM9I0/1c9HMMI06zfHgmMeRY8HDwOKnjSlYZvdQ5u4yB+gVbqrX97cAOxsHn9GF/9G3iV4WbSWBeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMCiTIxM/FmZiXkFiXnxxRmJeckZpQA1nQZRAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMAAwABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9E2ti1EwGgA9dwYGAAA=) format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon {
|
||||||
|
font-family: "keyboardFont" !important;
|
||||||
|
font-size: 22px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
line-height: 1;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-delete:before {
|
||||||
|
content: "\e7b8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 998;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-mask-show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard {
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-radius {
|
||||||
|
border-top-left-radius: 16rpx;
|
||||||
|
border-top-right-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-action {
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translate3d(0, 100%, 0);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-show {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-bg-gray {
|
||||||
|
background-color: #e7e6eb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-grids {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
display: -webkit-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-grids::after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
border-top: 1px solid #eaeef1;
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-grid {
|
||||||
|
position: relative;
|
||||||
|
padding: 24rpx 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #fff;
|
||||||
|
width: 33.33333333%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-grid:nth-of-type(3n)::before {
|
||||||
|
width: 0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-grid::before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 1px;
|
||||||
|
bottom: 0;
|
||||||
|
border-right: 1px solid #eaeef1;
|
||||||
|
-webkit-transform-origin: 100% 0;
|
||||||
|
transform-origin: 100% 0;
|
||||||
|
-webkit-transform: scaleX(0.5);
|
||||||
|
transform: scaleX(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-grid::after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 1px;
|
||||||
|
border-bottom: 1px solid #eaeef1;
|
||||||
|
-webkit-transform-origin: 0 100%;
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
-webkit-transform: scaleY(0.5);
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-grid-bottom::after {
|
||||||
|
height: 0 !important;
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-hover {
|
||||||
|
background-color: #f7f7f9 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-keyboard-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 48rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-fontsize-32 {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
54
src/components/thorui/tui-label/tui-label.vue
Normal file
54
src/components/thorui/tui-label/tui-label.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-label__box" :class="{'tui-label__full':isFull}" :style="{padding:padding,margin:margin}"
|
||||||
|
@tap.stop="onClick">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//该组件主要用于tui-radio,tui-checkbox,tui-switch组件外层,类似label功能
|
||||||
|
export default {
|
||||||
|
name: "tui-label",
|
||||||
|
props: {
|
||||||
|
padding: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
isFull: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.childrens = [];
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
if (this.childrens && this.childrens.length > 0) {
|
||||||
|
for (let child of this.childrens) {
|
||||||
|
child.labelClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-label__box {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-label__full {
|
||||||
|
flex: 1;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
width: 100%;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
158
src/components/thorui/tui-landscape/tui-landscape.vue
Normal file
158
src/components/thorui/tui-landscape/tui-landscape.vue
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-landscape__box">
|
||||||
|
<view class="tui-landscape__inner" :style="{zIndex:zIndex}" v-if="show">
|
||||||
|
<slot></slot>
|
||||||
|
<view class="tui-icon__close"
|
||||||
|
:style="{top:position!=1?iconTop:'auto',bottom:position==1?iconBottom:'auto',left:position==3?iconLeft:(position==1?'50%':'auto'),right:position==2?iconRight:'auto'}"
|
||||||
|
:class="{'tui-icon__bottom':position==1}" v-if="closeIcon" @tap.stop="close">
|
||||||
|
<icon type="clear" :color="iconColor" :size="iconSize"></icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view :style="{backgroundColor:maskBgColor,zIndex:maskZIndex}" @tap.stop="close(1)" class="tui-landscape__mask"
|
||||||
|
:class="{'tui-mask_hidden':!show}" v-if="mask"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-landscape",
|
||||||
|
emits: ['close'],
|
||||||
|
props: {
|
||||||
|
//是否显示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//显示内容区z-index
|
||||||
|
zIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 1001
|
||||||
|
},
|
||||||
|
//是否需要关闭图标
|
||||||
|
closeIcon: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//关闭图标颜色
|
||||||
|
iconColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
//关闭图标大小 px
|
||||||
|
iconSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 25
|
||||||
|
},
|
||||||
|
//icon位置:1-底部 2-右上角 3-左上角
|
||||||
|
position: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
//关闭图标top值,position为2或3的时候生效
|
||||||
|
iconTop: {
|
||||||
|
type: String,
|
||||||
|
default: '-120rpx'
|
||||||
|
},
|
||||||
|
//关闭图标bottom值,position为1的时候生效
|
||||||
|
iconBottom: {
|
||||||
|
type: String,
|
||||||
|
default: '-120rpx'
|
||||||
|
},
|
||||||
|
//关闭图标left值,position为3的时候生效
|
||||||
|
iconLeft: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//关闭图标right值,position为2的时候生效
|
||||||
|
iconRight: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//点击遮罩是否可以关闭
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//是否需要遮罩
|
||||||
|
mask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//遮罩背景色
|
||||||
|
maskBgColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(0,0,0,.6)'
|
||||||
|
},
|
||||||
|
//遮罩z-index值
|
||||||
|
maskZIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
},
|
||||||
|
//自定义参数
|
||||||
|
params: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close(isMask) {
|
||||||
|
if (isMask == 1 && !this.maskClosable) return;
|
||||||
|
this.$emit('close', {
|
||||||
|
params: this.params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-landscape__box {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-landscape__inner {
|
||||||
|
max-width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon__close {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-icon__bottom {
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-landscape__mask {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
transition: all .2s ease-in
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-mask_hidden {
|
||||||
|
opacity: 0 !important;
|
||||||
|
transform: scale3d(1, 1, 0) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
225
src/components/thorui/tui-lazyload-img/tui-lazyload-img.vue
Normal file
225
src/components/thorui/tui-lazyload-img/tui-lazyload-img.vue
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tui-lazyload__box"
|
||||||
|
:style="{backgroundColor:placeholder?'transparent':backgroundColor,width:width,height:height?height:'auto',borderRadius:radius}"
|
||||||
|
@tap="handleClick">
|
||||||
|
<image class="tui-lazyload__img"
|
||||||
|
:class="{'tui-img__hidden':!placeholder && fadeShow && !show,'tui-img__appear':show && !placeholder && fadeShow}"
|
||||||
|
:style="{height:height,borderRadius:radius}" :src="show?src:placeholder"
|
||||||
|
:mode="imgMode" :webp="webp" :show-menu-by-longpress="showMenuByLongpress" :draggable="draggable"
|
||||||
|
@load="load" @error="error" :id="elId">
|
||||||
|
</image>
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tui-lazyload-img",
|
||||||
|
emits: ['error', 'load', 'click'],
|
||||||
|
options: {
|
||||||
|
virtualHost: true
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
//图片路径
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//占位图路径
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
//占位背景色,placeholder有值时失效
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#E7E7E7'
|
||||||
|
},
|
||||||
|
//图片的裁剪模式,参考image组件mode属性
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: 'widthFix'
|
||||||
|
},
|
||||||
|
//图片显示动画效果,无占位图时有效
|
||||||
|
fadeShow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//默认不解析 webP 格式,只支持网络资源 微信小程序2.9.0
|
||||||
|
webp: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//开启长按图片显示识别小程序码菜单 微信小程序2.7.0
|
||||||
|
showMenuByLongpress: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//鼠标长按是否能拖动图片 仅H5平台 3.1.1+ 有效
|
||||||
|
draggable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
//图片宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '340rpx'
|
||||||
|
},
|
||||||
|
//图片高度,如果高度设置为auto,mode值需要设置为widthFix
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '340rpx'
|
||||||
|
},
|
||||||
|
//图片圆角值,如:10rpx
|
||||||
|
radius: {
|
||||||
|
type: String,
|
||||||
|
default: '0'
|
||||||
|
},
|
||||||
|
//节点布局区域的下边界,目标节点区域以下 bottom(px) 时,就会触发回调函数
|
||||||
|
bottom: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 50
|
||||||
|
},
|
||||||
|
//是否停止监听,设置为true时回调函数将不再触发
|
||||||
|
disconnect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
//图片在列表中的索引值
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let elId = this.unique() + this.index
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
elId: elId,
|
||||||
|
imgMode: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
disconnect(val) {
|
||||||
|
if (val) {
|
||||||
|
this.removeObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.observer = null;
|
||||||
|
// this.elId = this.unique() + this.index;
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// #ifndef H5
|
||||||
|
if (!this.disconnect) {
|
||||||
|
this.initObserver()
|
||||||
|
} else {
|
||||||
|
this.show = true;
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
if (!this.disconnect && window.self === window.top) {
|
||||||
|
this.initObserver()
|
||||||
|
} else {
|
||||||
|
this.show = true;
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
this.imgMode = this.mode;
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// #ifndef VUE3
|
||||||
|
beforeDestroy() {
|
||||||
|
this.removeObserver()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
beforeUnmount() {
|
||||||
|
this.removeObserver()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
methods: {
|
||||||
|
unique: function(n) {
|
||||||
|
n = n || 6;
|
||||||
|
let rnd = '';
|
||||||
|
for (let i = 0; i < n; i++)
|
||||||
|
rnd += Math.floor(Math.random() * 10);
|
||||||
|
return 'tui_' + new Date().getTime() + rnd;
|
||||||
|
},
|
||||||
|
removeObserver() {
|
||||||
|
if (this.observer) {
|
||||||
|
this.observer.disconnect()
|
||||||
|
this.observer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initObserver() {
|
||||||
|
if (this.observer || this.show) return;
|
||||||
|
try {
|
||||||
|
let element = this.elId ? `#${this.elId}` : '.tui-lazyload__img';
|
||||||
|
const observer = uni.createIntersectionObserver(this)
|
||||||
|
observer.relativeToViewport({
|
||||||
|
bottom: Number(this.bottom) || 50
|
||||||
|
}).observe(element, (res) => {
|
||||||
|
if (res.intersectionRatio > 0 && !this.show) {
|
||||||
|
this.show = true;
|
||||||
|
this.removeObserver()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.observer = observer
|
||||||
|
} catch (e) {
|
||||||
|
//TODO handle the exception
|
||||||
|
this.show = true;
|
||||||
|
this.removeObserver()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error(e) {
|
||||||
|
if (!this.show) return;
|
||||||
|
this.$emit('error', {
|
||||||
|
detail: e.detail,
|
||||||
|
index: this.index
|
||||||
|
})
|
||||||
|
},
|
||||||
|
load(e) {
|
||||||
|
if (!this.show) return;
|
||||||
|
this.$emit('load', {
|
||||||
|
detail: e.detail,
|
||||||
|
index: this.index
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleClick(e) {
|
||||||
|
this.$emit('click', {
|
||||||
|
index: this.index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tui-lazyload__box {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-lazyload__img {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: opacity .3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-img__hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-img__appear {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
119
src/components/thorui/tui-link/tui-link.vue
Normal file
119
src/components/thorui/tui-link/tui-link.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<template>
|
||||||
|
<a v-if="isShowA" class="tui-link__text" :href="href" :class="{'tui-link__underline':underline}"
|
||||||
|
:style="{color:getColor,fontSize:size+'rpx'}" :download="download">
|
||||||
|
<slot>{{text || href}}</slot>
|
||||||
|
</a>
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<text v-else class="tui-link__text" :class="{'tui-link__underline':underline}"
|
||||||
|
:style="{color:getColor,fontSize:size+'rpx'}" @tap="openURL">
|
||||||
|
<slot>{{text || href}}</slot>
|
||||||
|
</text>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef APP-NVUE -->
|
||||||
|
<text v-else class="tui-link__text" :class="{'tui-link__underline':underline}"
|
||||||
|
:style="{color:getColor,fontSize:size+'rpx'}" @tap="openURL">{{text || href}}</text>
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'tuiLink',
|
||||||
|
options: {
|
||||||
|
virtualHost: true
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
href: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
underline: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
copyTips: {
|
||||||
|
type: String,
|
||||||
|
default: '链接已复制'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 28
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isShowA() {
|
||||||
|
let h5 = false
|
||||||
|
// #ifdef H5
|
||||||
|
h5 = true;
|
||||||
|
// #endif
|
||||||
|
if ((this.isMail() || this.isTel()) && h5) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getColor() {
|
||||||
|
return this.color || (uni && uni.$tui && uni.$tui.color.link) || '#586c94'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isMail() {
|
||||||
|
return this.href.startsWith('mailto:');
|
||||||
|
},
|
||||||
|
isTel() {
|
||||||
|
return this.href.startsWith('tel:');
|
||||||
|
},
|
||||||
|
openURL() {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if (this.isTel()) {
|
||||||
|
this.makePhoneCall(this.href.replace('tel:', ''));
|
||||||
|
} else {
|
||||||
|
plus.runtime.openURL(this.href);
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
window.open(this.href)
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: this.href,
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: this.copyTips,
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
makePhoneCall(phoneNumber) {
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* #ifdef H5 */
|
||||||
|
.tui-link__text {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #endif */
|
||||||
|
.tui-link__underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user