first commit
This commit is contained in:
commit
3679c9b8ef
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
16
.hbuilderx/launch.json
Normal file
16
.hbuilderx/launch.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||||
|
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||||
|
"version": "0.0",
|
||||||
|
"configurations": [{
|
||||||
|
"default" :
|
||||||
|
{
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"mp-weixin" :
|
||||||
|
{
|
||||||
|
"launchtype" : "local"
|
||||||
|
},
|
||||||
|
"type" : "uniCloud"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
38
.prettierrc.js
Normal file
38
.prettierrc.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
module.exports = {
|
||||||
|
// 一行最多 80 字符
|
||||||
|
printWidth: 80,
|
||||||
|
// 使用 4 个空格缩进
|
||||||
|
tabWidth: 2,
|
||||||
|
// 不使用 tab 缩进,而使用空格
|
||||||
|
useTabs: false,
|
||||||
|
// 行尾需要有分号
|
||||||
|
semi: false,
|
||||||
|
// 使用单引号代替双引号
|
||||||
|
singleQuote: true,
|
||||||
|
// 对象的 key 仅在必要时用引号
|
||||||
|
quoteProps: 'as-needed',
|
||||||
|
// jsx 不使用单引号,而使用双引号
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
// 末尾使用逗号
|
||||||
|
trailingComma: 'all',
|
||||||
|
// 大括号内的首尾需要空格 { foo: bar }
|
||||||
|
bracketSpacing: true,
|
||||||
|
// jsx 标签的反尖括号需要换行
|
||||||
|
jsxBracketSameLine: false,
|
||||||
|
// 箭头函数,只有一个参数的时候,也需要括号
|
||||||
|
arrowParens: 'always',
|
||||||
|
// 每个文件格式化的范围是文件的全部内容
|
||||||
|
rangeStart: 0,
|
||||||
|
rangeEnd: Infinity,
|
||||||
|
// 不需要写文件开头的 @prettier
|
||||||
|
requirePragma: false,
|
||||||
|
// 不需要自动在文件开头插入 @prettier
|
||||||
|
insertPragma: false,
|
||||||
|
// 使用默认的折行标准
|
||||||
|
proseWrap: 'preserve',
|
||||||
|
// 根据显示样式决定 html 要不要折行
|
||||||
|
htmlWhitespaceSensitivity: 'css',
|
||||||
|
// 换行符使用 lf
|
||||||
|
endOfLine: 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 恋轩
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
20
index.html
Normal file
20
index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<script>
|
||||||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||||
|
CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||||
|
</script>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7967
package-lock.json
generated
Normal file
7967
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
package.json
Normal file
70
package.json
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"name": "uni-preset-vue",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "此项目为uniapp的vue3 + ts + vite + pinia的模板",
|
||||||
|
"scripts": {
|
||||||
|
"dev:app": "uni -p app",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev:h5": "uni",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build:app": "uni build -p app",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dcloudio/uni-app": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@types/uni-app": "^1.4.8",
|
||||||
|
"js-calendar-converter": "^0.0.7",
|
||||||
|
"lunisolar": "^2.6.0",
|
||||||
|
"miniprogram-api-typings": "^4.1.0",
|
||||||
|
"pinia": "^2.0.36",
|
||||||
|
"pinia-plugin-persistedstate": "^2.3.0",
|
||||||
|
"text-encoding-shim": "^1.0.5",
|
||||||
|
"vue": "^3.5.1",
|
||||||
|
"wot-design-uni": "^1.13.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@dcloudio/types": "^3.0.13",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3050320220729001",
|
||||||
|
"@types/node": "^18.11.9",
|
||||||
|
"sass": "^1.54.9",
|
||||||
|
"typescript": "^4.7.4",
|
||||||
|
"unplugin-auto-import": "^0.11.2",
|
||||||
|
"vite": "^2.9.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/App.vue
Normal file
56
src/App.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
||||||
|
onLaunch(() => {
|
||||||
|
console.log("App Launch");
|
||||||
|
});
|
||||||
|
onShow(() => {
|
||||||
|
console.log("App Show");
|
||||||
|
});
|
||||||
|
onHide(() => {
|
||||||
|
console.log("App Hide");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
:root,
|
||||||
|
page {
|
||||||
|
--wot-color-theme: #217891 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-toast {
|
||||||
|
background-color: #fff !important;
|
||||||
|
color: #333 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100dvh;
|
||||||
|
max-width: auto;
|
||||||
|
min-height: calc(100vh - env(safe-area-inset-bottom) - 50px);
|
||||||
|
min-height: calc(100vh - constant(safe-area-inset-bottom) - 50px);
|
||||||
|
overflow-y: hidden;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-2 {
|
||||||
|
gap: 16rpx
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-4 {
|
||||||
|
gap: 32rpx
|
||||||
|
}
|
||||||
|
|
||||||
|
// .wd-message-box__content {
|
||||||
|
// text-align: left !important;
|
||||||
|
// }
|
||||||
|
</style>
|
||||||
4
src/api/index.ts
Normal file
4
src/api/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './modules/AI'
|
||||||
|
export * from './modules/ZhouYi'
|
||||||
|
export * from './modules/question'
|
||||||
|
export * from './modules/suggestion'
|
||||||
147
src/api/modules/AI.ts
Normal file
147
src/api/modules/AI.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
import { getPageConfig } from './config'
|
||||||
|
import { 动爻数量 } from '@/utils/gua_64'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useAIReponseStore, useTabStore } from '@/stores'
|
||||||
|
import * as TextEncoding from "text-encoding-shim";
|
||||||
|
import { watch } from 'vue'
|
||||||
|
|
||||||
|
const {
|
||||||
|
responseText,
|
||||||
|
showResponseText,
|
||||||
|
isLoading,
|
||||||
|
isDone
|
||||||
|
} = storeToRefs(useAIReponseStore())
|
||||||
|
|
||||||
|
const {
|
||||||
|
tabIndex
|
||||||
|
} = storeToRefs(useTabStore())
|
||||||
|
|
||||||
|
|
||||||
|
// const API_KEY = 'sk-Go5P3ztUx30inaZCsWHQje7SjUxbGjD6znDO6xUJNCDB5jNu' // 美团团
|
||||||
|
// const API_KEY = 'sk-OPl2umaMSEp7QrwK5e13Fd5f9d86471788A3E2590f439f18' // aihubmix
|
||||||
|
// const API_KEY = 'sk-54463246c08f40ecb3baec28bc9a78b4' // 千问
|
||||||
|
const API_KEY = 'sk-b65f99c1b2ab416aaf340891cf4ca308' // ds
|
||||||
|
|
||||||
|
const AI_URL = 'https://api.deepseek.com/chat/completions'
|
||||||
|
// const AI_URL = 'https://max.openai365.top/v1/chat/completions'
|
||||||
|
// const AI_URL = 'https://aihubmix.com/v1/chat/completions'
|
||||||
|
// const AI_MODEL = 'gemini-3-pro-preview'
|
||||||
|
// const AI_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions'
|
||||||
|
const AI_MODEL = 'deepseek-chat'
|
||||||
|
|
||||||
|
const errMessage = {
|
||||||
|
401: 'API key 无效',
|
||||||
|
403: 'API key 余额不足',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AIChat = async (
|
||||||
|
q: string, // 用户所问之事
|
||||||
|
symbol_1: string, // 本卦结果
|
||||||
|
symbol_2: string, // 变卦结果
|
||||||
|
symbol_3: string, // 动爻结果
|
||||||
|
// count: number, // 动爻数量
|
||||||
|
) => {
|
||||||
|
const pageCfg = await getPageConfig()
|
||||||
|
|
||||||
|
let callWord = pageCfg.call_word
|
||||||
|
callWord = callWord
|
||||||
|
.replace('[q]', q)
|
||||||
|
.replace('[symbol_1]', symbol_1)
|
||||||
|
.replace('[symbol_2]', symbol_2)
|
||||||
|
.replace('[symbol_3]', symbol_3)
|
||||||
|
|
||||||
|
console.log(callWord)
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const requestTask = await StreamRequest({
|
||||||
|
// model: AI_MODEL,
|
||||||
|
// messages: [
|
||||||
|
// { role: "system", content: "You are a helpful assistant." },
|
||||||
|
// { role: "user", content: callWord }
|
||||||
|
// ],
|
||||||
|
// stream: true
|
||||||
|
// })
|
||||||
|
// requestTask.onHeadersReceived((e) => {
|
||||||
|
// console.log(e);
|
||||||
|
// })
|
||||||
|
// requestTask.onChunkReceived((e) => {
|
||||||
|
// console.log(e);
|
||||||
|
// const decoder = new TextDecoder('utf-8');
|
||||||
|
// const txt = decoder.decode(e.data);
|
||||||
|
// responseText.value = txt
|
||||||
|
// })
|
||||||
|
// } catch (err) {
|
||||||
|
// console.log(err);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return await new Promise<any>((resolve, reject) => {
|
||||||
|
const requestTask: any = uni.request({
|
||||||
|
url: AI_URL,
|
||||||
|
method: 'POST',
|
||||||
|
header: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${API_KEY}`
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
model: AI_MODEL,
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: "You are a helpful assistant." },
|
||||||
|
{ role: "user", content: callWord }
|
||||||
|
],
|
||||||
|
stream: true
|
||||||
|
},
|
||||||
|
enableChunked: true,
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
resolve({
|
||||||
|
code: res.statusCode,
|
||||||
|
message: errMessage[res.statusCode]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDone.value = true
|
||||||
|
// console.log('Data received 数据接受完毕:', res.data)
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
// console.log('打印***error 错误处理', error)
|
||||||
|
},
|
||||||
|
complete: (complete) => {
|
||||||
|
// console.log('打印***complete 完成接收', complete)
|
||||||
|
}
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
requestTask.onChunkReceived(res => {
|
||||||
|
const uint8Array = new Uint8Array(res.data);
|
||||||
|
const decoder = new TextEncoding.TextDecoder("utf-8");
|
||||||
|
const chunk = decoder.decode(uint8Array).toString().split('data: ')
|
||||||
|
|
||||||
|
for (let i = 1; i < chunk.length; i++) {
|
||||||
|
try {
|
||||||
|
const result = JSON.parse(chunk[i])
|
||||||
|
// console.log(result)
|
||||||
|
if (result.choices[0].delta.content) {
|
||||||
|
// showResponseText.value = true
|
||||||
|
isLoading.value = false
|
||||||
|
responseText.value += result.choices[0].delta?.content
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => tabIndex.value,
|
||||||
|
(newVal, oldVal) => {
|
||||||
|
if (newVal !== oldVal) {
|
||||||
|
requestTask.abort()
|
||||||
|
isLoading.value = false
|
||||||
|
responseText.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
41
src/api/modules/ZhouYi.ts
Normal file
41
src/api/modules/ZhouYi.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { request } from "@/utils/request"
|
||||||
|
|
||||||
|
export const getZhouList = () => {
|
||||||
|
return request('/api/zhou-yis?sort=index&pagination[pageSize]=64', {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getZhouDetail = ({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id?: string,
|
||||||
|
name?: string
|
||||||
|
}) => {
|
||||||
|
if (id) {
|
||||||
|
return request(`/api/zhou-yis/${id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
return request(`/api/zhou-yis?filters[name]=${name}`, {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const 获取易经爻辞 = (
|
||||||
|
name: string,
|
||||||
|
动爻名称: string
|
||||||
|
) => {
|
||||||
|
return request(`/api/zhou-yis?filters[name]=${name}`, {
|
||||||
|
method: 'GET',
|
||||||
|
}).then(
|
||||||
|
res => res.data[0]
|
||||||
|
).then(res => {
|
||||||
|
let str = res.desc.split('\n\n')
|
||||||
|
let index = str.findIndex(item => item.includes(动爻名称))
|
||||||
|
return str[index] + '\n' + str[index + 1]
|
||||||
|
})
|
||||||
|
}
|
||||||
7
src/api/modules/config.ts
Normal file
7
src/api/modules/config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { request } from '@/utils/request'
|
||||||
|
|
||||||
|
export const getPageConfig = async () => {
|
||||||
|
return request('/api/config?populate=avatar', {
|
||||||
|
method: 'GET'
|
||||||
|
}).then(res => res.data)
|
||||||
|
}
|
||||||
14
src/api/modules/question.ts
Normal file
14
src/api/modules/question.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { request } from "@/utils/request"
|
||||||
|
|
||||||
|
export const createQ = (
|
||||||
|
q: string
|
||||||
|
) => {
|
||||||
|
return request('/api/questions', {
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
14
src/api/modules/suggestion.ts
Normal file
14
src/api/modules/suggestion.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { request } from "@/utils/request"
|
||||||
|
|
||||||
|
export const createSuggestion = (
|
||||||
|
desc: string
|
||||||
|
) => {
|
||||||
|
return request('/api/suggestions', {
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
data: {
|
||||||
|
desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
39
src/components/Navbar.vue
Normal file
39
src/components/Navbar.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<wd-navbar
|
||||||
|
:title="title"
|
||||||
|
left-text="返回"
|
||||||
|
left-arrow
|
||||||
|
fixed
|
||||||
|
placeholder
|
||||||
|
safeAreaInsetTop
|
||||||
|
>
|
||||||
|
<template #capsule>
|
||||||
|
<wd-navbar-capsule @back="handleBack" @back-home="handleBackHome" />
|
||||||
|
</template>
|
||||||
|
</wd-navbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
const {
|
||||||
|
title
|
||||||
|
} = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
uni.navigateBack({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBackHome = () => {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
56
src/components/TabBar.vue
Normal file
56
src/components/TabBar.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tab-bar">
|
||||||
|
<wd-tabbar
|
||||||
|
v-model="tabIndex"
|
||||||
|
fixed
|
||||||
|
:zIndex="9"
|
||||||
|
safeAreaInsetBottom
|
||||||
|
placeholder
|
||||||
|
@change="onChange"
|
||||||
|
>
|
||||||
|
<wd-tabbar-item
|
||||||
|
v-for="(item, index) in tabBarList"
|
||||||
|
:key="index"
|
||||||
|
:title="item.title"
|
||||||
|
:icon="item.icon"
|
||||||
|
>
|
||||||
|
</wd-tabbar-item>
|
||||||
|
</wd-tabbar>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTabStore } from '@/stores/index'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
|
const {
|
||||||
|
tabIndex
|
||||||
|
} = storeToRefs(useTabStore())
|
||||||
|
|
||||||
|
const tabBarList = [
|
||||||
|
{
|
||||||
|
icon: 'home',
|
||||||
|
title: '学易'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'spool',
|
||||||
|
title: '问易'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'user',
|
||||||
|
title: '归处'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const onChange = ({ value }: { value: string }) => {
|
||||||
|
|
||||||
|
tabIndex.value = parseInt(value)
|
||||||
|
console.log(tabIndex.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tab-bar {
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
|
||||||
10
src/components/ua-markdown/lib/highlight/github-dark.min.css
vendored
Normal file
10
src/components/ua-markdown/lib/highlight/github-dark.min.css
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||||
|
Theme: GitHub Dark
|
||||||
|
Description: Dark theme as seen on github.com
|
||||||
|
Author: github.com
|
||||||
|
Maintainer: @Hirse
|
||||||
|
Updated: 2021-05-15
|
||||||
|
|
||||||
|
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||||
|
Current colors taken from GitHub's CSS
|
||||||
|
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
|
||||||
5254
src/components/ua-markdown/lib/highlight/uni-highlight.min.js
vendored
Normal file
5254
src/components/ua-markdown/lib/highlight/uni-highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
352
src/components/ua-markdown/lib/html-parser.js
Normal file
352
src/components/ua-markdown/lib/html-parser.js
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
/*
|
||||||
|
* HTML5 Parser By Sam Blowes
|
||||||
|
*
|
||||||
|
* Designed for HTML5 documents
|
||||||
|
*
|
||||||
|
* Original code by John Resig (ejohn.org)
|
||||||
|
* http://ejohn.org/blog/pure-javascript-html-parser/
|
||||||
|
* Original code by Erik Arvidsson, Mozilla Public License
|
||||||
|
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||||
|
*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* License
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* This code is triple licensed using Apache Software License 2.0,
|
||||||
|
* Mozilla Public License or GNU Public License
|
||||||
|
*
|
||||||
|
* ////////////////////////////////////////////////////////////////////////////
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy
|
||||||
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* ////////////////////////////////////////////////////////////////////////////
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License
|
||||||
|
* Version 1.1 (the "License"); you may not use this file except in
|
||||||
|
* compliance with the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS"
|
||||||
|
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing rights and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* The Original Code is Simple HTML Parser.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is Erik Arvidsson.
|
||||||
|
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
|
||||||
|
* Reserved.
|
||||||
|
*
|
||||||
|
* ////////////////////////////////////////////////////////////////////////////
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* Usage
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* // Use like so:
|
||||||
|
* HTMLParser(htmlString, {
|
||||||
|
* start: function(tag, attrs, unary) {},
|
||||||
|
* end: function(tag) {},
|
||||||
|
* chars: function(text) {},
|
||||||
|
* comment: function(text) {}
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // or to get an XML string:
|
||||||
|
* HTMLtoXML(htmlString);
|
||||||
|
*
|
||||||
|
* // or to get an XML DOM Document
|
||||||
|
* HTMLtoDOM(htmlString);
|
||||||
|
*
|
||||||
|
* // or to inject into an existing document/DOM node
|
||||||
|
* HTMLtoDOM(htmlString, document);
|
||||||
|
* HTMLtoDOM(htmlString, document.body);
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
// Regular Expressions for parsing tags and attributes
|
||||||
|
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
|
||||||
|
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
|
||||||
|
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
|
||||||
|
|
||||||
|
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
|
||||||
|
// fixed by xxx 将 ins 标签从块级名单中移除
|
||||||
|
|
||||||
|
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
|
||||||
|
|
||||||
|
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
|
||||||
|
// (and which close themselves)
|
||||||
|
|
||||||
|
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
|
||||||
|
|
||||||
|
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
|
||||||
|
|
||||||
|
var special = makeMap('script,style');
|
||||||
|
function HTMLParser(html, handler) {
|
||||||
|
var index;
|
||||||
|
var chars;
|
||||||
|
var match;
|
||||||
|
var stack = [];
|
||||||
|
var last = html;
|
||||||
|
|
||||||
|
stack.last = function () {
|
||||||
|
return this[this.length - 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
while (html) {
|
||||||
|
chars = true; // Make sure we're not in a script or style element
|
||||||
|
|
||||||
|
if (!stack.last() || !special[stack.last()]) {
|
||||||
|
// Comment
|
||||||
|
if (html.indexOf('<!--') == 0) {
|
||||||
|
index = html.indexOf('-->');
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
if (handler.comment) {
|
||||||
|
handler.comment(html.substring(4, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
html = html.substring(index + 3);
|
||||||
|
chars = false;
|
||||||
|
} // end tag
|
||||||
|
|
||||||
|
} else if (html.indexOf('</') == 0) {
|
||||||
|
match = html.match(endTag);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
html = html.substring(match[0].length);
|
||||||
|
match[0].replace(endTag, parseEndTag);
|
||||||
|
chars = false;
|
||||||
|
} // start tag
|
||||||
|
|
||||||
|
} else if (html.indexOf('<') == 0) {
|
||||||
|
match = html.match(startTag);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
html = html.substring(match[0].length);
|
||||||
|
match[0].replace(startTag, parseStartTag);
|
||||||
|
chars = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chars) {
|
||||||
|
index = html.indexOf('<');
|
||||||
|
var text = index < 0 ? html : html.substring(0, index);
|
||||||
|
html = index < 0 ? '' : html.substring(index);
|
||||||
|
|
||||||
|
if (handler.chars) {
|
||||||
|
handler.chars(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
|
||||||
|
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
|
||||||
|
|
||||||
|
if (handler.chars) {
|
||||||
|
handler.chars(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
parseEndTag('', stack.last());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (html == last) {
|
||||||
|
throw 'Parse Error: ' + html;
|
||||||
|
}
|
||||||
|
|
||||||
|
last = html;
|
||||||
|
} // Clean up any remaining tags
|
||||||
|
|
||||||
|
|
||||||
|
parseEndTag();
|
||||||
|
|
||||||
|
function parseStartTag(tag, tagName, rest, unary) {
|
||||||
|
tagName = tagName.toLowerCase();
|
||||||
|
|
||||||
|
if (block[tagName]) {
|
||||||
|
while (stack.last() && inline[stack.last()]) {
|
||||||
|
parseEndTag('', stack.last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeSelf[tagName] && stack.last() == tagName) {
|
||||||
|
parseEndTag('', tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
unary = empty[tagName] || !!unary;
|
||||||
|
|
||||||
|
if (!unary) {
|
||||||
|
stack.push(tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler.start) {
|
||||||
|
var attrs = [];
|
||||||
|
rest.replace(attr, function (match, name) {
|
||||||
|
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
|
||||||
|
attrs.push({
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (handler.start) {
|
||||||
|
handler.start(tagName, attrs, unary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseEndTag(tag, tagName) {
|
||||||
|
// If no tag name is provided, clean shop
|
||||||
|
if (!tagName) {
|
||||||
|
var pos = 0;
|
||||||
|
} // Find the closest opened tag of the same type
|
||||||
|
else {
|
||||||
|
for (var pos = stack.length - 1; pos >= 0; pos--) {
|
||||||
|
if (stack[pos] == tagName) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos >= 0) {
|
||||||
|
// Close all the open elements, up the stack
|
||||||
|
for (var i = stack.length - 1; i >= pos; i--) {
|
||||||
|
if (handler.end) {
|
||||||
|
handler.end(stack[i]);
|
||||||
|
}
|
||||||
|
} // Remove the open elements from the stack
|
||||||
|
|
||||||
|
|
||||||
|
stack.length = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMap(str) {
|
||||||
|
var obj = {};
|
||||||
|
var items = str.split(',');
|
||||||
|
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
obj[items[i]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDOCTYPE(html) {
|
||||||
|
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAttrs(attrs) {
|
||||||
|
return attrs.reduce(function (pre, attr) {
|
||||||
|
var value = attr.value;
|
||||||
|
var name = attr.name;
|
||||||
|
|
||||||
|
if (pre[name]) {
|
||||||
|
pre[name] = pre[name] + " " + value;
|
||||||
|
} else {
|
||||||
|
pre[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHtml(html) {
|
||||||
|
html = removeDOCTYPE(html);
|
||||||
|
var stacks = [];
|
||||||
|
var results = {
|
||||||
|
node: 'root',
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
HTMLParser(html, {
|
||||||
|
start: function start(tag, attrs, unary) {
|
||||||
|
var node = {
|
||||||
|
name: tag
|
||||||
|
};
|
||||||
|
|
||||||
|
if (attrs.length !== 0) {
|
||||||
|
node.attrs = parseAttrs(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unary) {
|
||||||
|
var parent = stacks[0] || results;
|
||||||
|
|
||||||
|
if (!parent.children) {
|
||||||
|
parent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.children.push(node);
|
||||||
|
} else {
|
||||||
|
stacks.unshift(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
end: function end(tag) {
|
||||||
|
var node = stacks.shift();
|
||||||
|
if (node.name !== tag) console.error('invalid state: mismatch end tag');
|
||||||
|
|
||||||
|
if (stacks.length === 0) {
|
||||||
|
results.children.push(node);
|
||||||
|
} else {
|
||||||
|
var parent = stacks[0];
|
||||||
|
|
||||||
|
if (!parent.children) {
|
||||||
|
parent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.children.push(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chars: function chars(text) {
|
||||||
|
var node = {
|
||||||
|
type: 'text',
|
||||||
|
text: text
|
||||||
|
};
|
||||||
|
|
||||||
|
if (stacks.length === 0) {
|
||||||
|
results.children.push(node);
|
||||||
|
} else {
|
||||||
|
var parent = stacks[0];
|
||||||
|
|
||||||
|
if (!parent.children) {
|
||||||
|
parent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.children.push(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
comment: function comment(text) {
|
||||||
|
var node = {
|
||||||
|
node: 'comment',
|
||||||
|
text: text
|
||||||
|
};
|
||||||
|
var parent = stacks[0];
|
||||||
|
|
||||||
|
if (!parent.children) {
|
||||||
|
parent.children = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.children.push(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseHtml;
|
||||||
2
src/components/ua-markdown/lib/markdown-it.min.js
vendored
Normal file
2
src/components/ua-markdown/lib/markdown-it.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
318
src/components/ua-markdown/ua-markdown.vue
Normal file
318
src/components/ua-markdown/ua-markdown.vue
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
<!-- uniapp vue3 markdown解析 -->
|
||||||
|
<template>
|
||||||
|
<view class="ua__markdown"><rich-text space="nbsp" :nodes="parseNodes(source)" @itemclick="handleItemClick"></rich-text></view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import MarkdownIt from './lib/markdown-it.min.js'
|
||||||
|
import hljs from './lib/highlight/uni-highlight.min.js'
|
||||||
|
import './lib/highlight/atom-one-dark.css'
|
||||||
|
import parseHtml from './lib/html-parser.js'
|
||||||
|
const props = defineProps({
|
||||||
|
// 解析内容
|
||||||
|
source: String,
|
||||||
|
showLine: { type: [Boolean, String], default: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
let copyCodeData = []
|
||||||
|
const markdown = MarkdownIt({
|
||||||
|
html: true,
|
||||||
|
highlight: function(str, lang) {
|
||||||
|
let preCode = ""
|
||||||
|
try {
|
||||||
|
preCode = hljs.highlightAuto(str).value
|
||||||
|
} catch (err) {
|
||||||
|
preCode = markdown.utils.escapeHtml(str);
|
||||||
|
}
|
||||||
|
const lines = preCode.split(/\n/).slice(0, -1)
|
||||||
|
// 添加自定义行号
|
||||||
|
let html = lines.map((item, index) => {
|
||||||
|
if( item == ''){
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return '<li><span class="line-num" data-line="' + (index + 1) + '"></span>' + item +'</li>'
|
||||||
|
}).join('')
|
||||||
|
if(props.showLine) {
|
||||||
|
html = '<ol style="padding: 0px 30px;">' + html + '</ol>'
|
||||||
|
}else {
|
||||||
|
html = '<ol style="padding: 0px 7px;list-style:none;">' + html + '</ol>'
|
||||||
|
}
|
||||||
|
copyCodeData.push(str)
|
||||||
|
let htmlCode = `<div class="markdown-wrap">`
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
htmlCode += `<div style="color: #aaa;text-align: right;font-size: 12px;padding:8px;">`
|
||||||
|
htmlCode += `${lang}<a class="copy-btn" code-data-index="${copyCodeData.length - 1}" style="margin-left: 8px;">复制代码</a>`
|
||||||
|
htmlCode += `</div>`
|
||||||
|
// #endif
|
||||||
|
htmlCode += `<pre class="hljs" style="padding:10px 8px 0;margin-bottom:5px;overflow: auto;display: block;border-radius: 5px;"><code>${html}</code></pre>`;
|
||||||
|
htmlCode += '</div>'
|
||||||
|
return htmlCode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const parseNodes = (value) => {
|
||||||
|
if(!value) return
|
||||||
|
// 解析<br />到\n
|
||||||
|
value = value.replace(/<br>|<br\/>|<br \/>/g, "\n")
|
||||||
|
value = value.replace(/ /g, " ")
|
||||||
|
let htmlString = ''
|
||||||
|
if (value.split("```").length % 2) {
|
||||||
|
let mdtext = value
|
||||||
|
if(mdtext[mdtext.length-1] != '\n'){
|
||||||
|
mdtext += '\n'
|
||||||
|
}
|
||||||
|
htmlString = markdown.render(mdtext)
|
||||||
|
} else {
|
||||||
|
htmlString = markdown.render(value)
|
||||||
|
}
|
||||||
|
// 解决小程序表格边框型失效问题
|
||||||
|
htmlString = htmlString.replace(/<table/g, `<table class="table"`)
|
||||||
|
htmlString = htmlString.replace(/<tr/g, `<tr class="tr"`)
|
||||||
|
htmlString = htmlString.replace(/<th>/g, `<th class="th">`)
|
||||||
|
htmlString = htmlString.replace(/<td/g, `<td class="td"`)
|
||||||
|
htmlString = htmlString.replace(/<hr>|<hr\/>|<hr \/>/g, `<hr class="hr">`)
|
||||||
|
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
return htmlString
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 将htmlString转成htmlArray,反之使用rich-text解析
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
return parseHtml(htmlString)
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制代码
|
||||||
|
const handleItemClick = (e) => {
|
||||||
|
let {attrs} = e.detail.node
|
||||||
|
let {"code-data-index":codeDataIndex,"class":className} = attrs
|
||||||
|
if(className == 'copy-btn'){
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: copyCodeData[codeDataIndex],showToast: false,
|
||||||
|
success() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '复制成功',icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ua__markdown {
|
||||||
|
font-size: 14px;line-height: 1.5; word-break: break-all;
|
||||||
|
h1,h2,h3,h4,h5,h6 {
|
||||||
|
font-family: inherit;font-weight: 500;line-height: 1.1;color: inherit;
|
||||||
|
}
|
||||||
|
h1,h2,h3 {margin-top: 20px;margin-bottom: 10px}
|
||||||
|
h4,h5,h6 {margin-top: 10px;margin-bottom: 10px}
|
||||||
|
.h1,h1 {font-size: 36px
|
||||||
|
}
|
||||||
|
.h2,h2 {font-size: 30px
|
||||||
|
}
|
||||||
|
.h3,h3 {font-size: 24px
|
||||||
|
}
|
||||||
|
.h4,h4 {font-size: 18px
|
||||||
|
}
|
||||||
|
.h5,h5 {font-size: 14px
|
||||||
|
}
|
||||||
|
.h6,h6 {font-size: 12px
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
background-color: transparent;color: #2196f3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
hr, ::v-deep .hr {
|
||||||
|
margin-top: 20px;margin-bottom: 20px; border: 0; border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
img { max-width: 35%;
|
||||||
|
}
|
||||||
|
p {margin: 0 0 10px}
|
||||||
|
em {
|
||||||
|
font-style: italic; font-weight: inherit;
|
||||||
|
}
|
||||||
|
ol,ul {
|
||||||
|
margin-top: 0; margin-bottom: 10px;padding-left: 40px;
|
||||||
|
}
|
||||||
|
ol ol,ol ul,ul ol,ul ul {margin-bottom: 0;
|
||||||
|
}
|
||||||
|
ol ol, ul ol {list-style-type: lower-roman;
|
||||||
|
}
|
||||||
|
ol ol ol, ul ul ol {list-style-type: lower-alpha;
|
||||||
|
}
|
||||||
|
dl {
|
||||||
|
margin-top: 0;margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
dt {font-weight: 600;
|
||||||
|
}
|
||||||
|
dt, dd {line-height: 1.4;
|
||||||
|
}
|
||||||
|
.task-list-item { list-style-type: none;
|
||||||
|
}
|
||||||
|
.task-list-item input {
|
||||||
|
margin: 0 .2em .25em -1.6em;vertical-align: middle;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
position: relative; z-index: 11;
|
||||||
|
}
|
||||||
|
code,kbd,pre,samp { font-family: Menlo,Monaco,Consolas,"Courier New",monospace;}
|
||||||
|
code:not(.hljs) {
|
||||||
|
padding: 2px 4px;font-size: 90%;color: #c7254e;background-color: #ffe7ee;border-radius: 4px;
|
||||||
|
}
|
||||||
|
code:empty {display: none;
|
||||||
|
}
|
||||||
|
pre code.hljs {
|
||||||
|
color: var(--vg__text-1); border-radius: 16px; background: var(--vg__bg-1);font-size: 12px;
|
||||||
|
}
|
||||||
|
.markdown-wrap {
|
||||||
|
font-size: 12px;margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
pre.code-block-wrapper {background: #2b2b2b;color: #f8f8f2;border-radius: 4px;overflow-x: auto;
|
||||||
|
padding: 1em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
pre.code-block-wrapper code {
|
||||||
|
padding: auto;
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
background-color: inherit;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.code-block-header__copy {
|
||||||
|
font-size: 16px;margin-left: 5px;
|
||||||
|
}
|
||||||
|
abbr[data-original-title],abbr[title] {
|
||||||
|
cursor: help;border-bottom: 1px dotted #777;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
padding: 10px 20px;margin: 0 0 20px;font-size: 17.5px;
|
||||||
|
border-left: 5px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child {
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
blockquote .small,blockquote footer,blockquote small {
|
||||||
|
display: block;font-size: 80%;line-height: 1.42857143;color: #777
|
||||||
|
}
|
||||||
|
blockquote .small:before,blockquote footer:before,blockquote small:before {
|
||||||
|
content: '\2014 \00A0'
|
||||||
|
}
|
||||||
|
.blockquote-reverse,blockquote.pull-right {
|
||||||
|
padding-right: 15px; padding-left: 0;
|
||||||
|
text-align: right;border-right: 5px solid #eee;border-left: 0
|
||||||
|
}
|
||||||
|
.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before {
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after {
|
||||||
|
content: '\00A0 \2014'
|
||||||
|
}
|
||||||
|
.footnotes {
|
||||||
|
-moz-column-count: 2;
|
||||||
|
-webkit-column-count: 2;
|
||||||
|
column-count: 2
|
||||||
|
}
|
||||||
|
.footnotes-list {padding-left: 2em}
|
||||||
|
table, ::v-deep .table {
|
||||||
|
border-spacing: 0;border-collapse: collapse; width: 100%;max-width: 65em; overflow: auto;margin-top: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
table tr, ::v-deep .table .tr {
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
table th, table td, ::v-deep .table .th, ::v-deep .table .td {
|
||||||
|
padding: 6px 13px;border: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
table th, ::v-deep .table .th {
|
||||||
|
font-weight: 600;background-color: #eee;
|
||||||
|
}
|
||||||
|
.hljs[class*=language-]:before {
|
||||||
|
position: absolute; z-index: 3;top: .8em; right: 1em; font-size: .8em; color: #999;
|
||||||
|
}
|
||||||
|
.hljs[class~=language-js]:before {
|
||||||
|
content: "js"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-ts]:before {
|
||||||
|
content: "ts"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-html]:before {
|
||||||
|
content: "html"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-md]:before {
|
||||||
|
content: "md"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-vue]:before {
|
||||||
|
content: "vue"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-css]:before {
|
||||||
|
content: "css"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-sass]:before {
|
||||||
|
content: "sass"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-scss]:before {
|
||||||
|
content: "scss"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-less]:before {
|
||||||
|
content: "less"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-stylus]:before {
|
||||||
|
content: "stylus"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-go]:before {
|
||||||
|
content: "go"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-java]:before {
|
||||||
|
content: "java"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-c]:before {
|
||||||
|
content: "c"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-sh]:before {
|
||||||
|
content: "sh"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-yaml]:before {
|
||||||
|
content: "yaml"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-py]:before {
|
||||||
|
content: "py"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-docker]:before {
|
||||||
|
content: "docker"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-dockerfile]:before {
|
||||||
|
content: "dockerfile"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-makefile]:before {
|
||||||
|
content: "makefile"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-javascript]:before {
|
||||||
|
content: "js"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-typescript]:before {
|
||||||
|
content: "ts"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-markup]:before {
|
||||||
|
content: "html"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-markdown]:before {
|
||||||
|
content: "md"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-json]:before {
|
||||||
|
content: "json"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-ruby]:before {
|
||||||
|
content: "rb"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-python]:before {
|
||||||
|
content: "py"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-bash]:before {
|
||||||
|
content: "sh"
|
||||||
|
}
|
||||||
|
.hljs[class~=language-php]:before {
|
||||||
|
content: "php"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/config/api.config.ts
Normal file
5
src/config/api.config.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const PORT = 1337
|
||||||
|
// export const HOST_NAME = 'http://localhost'
|
||||||
|
export const BASE_URL = 'https://ching.snhaenigseal.cn'
|
||||||
|
// export const BASE_SERVER_URL = `${HOST_NAME}:${PORT}`
|
||||||
|
// export const BASE_UPLOAD_URL = `${HOST_NAME}:${PORT}/api/upload`
|
||||||
12
src/main.ts
Normal file
12
src/main.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
|
||||||
|
import { setupStore } from './stores'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
setupStore(app);
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/manifest.json
Normal file
73
src/manifest.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"name" : "",
|
||||||
|
"appid" : "",
|
||||||
|
"description" : "",
|
||||||
|
"versionName" : "1.0.0",
|
||||||
|
"versionCode" : "100",
|
||||||
|
"transformPx" : false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus" : {
|
||||||
|
"usingComponents" : true,
|
||||||
|
"nvueStyleCompiler" : "uni-app",
|
||||||
|
"compilerVersion" : 3,
|
||||||
|
"splashscreen" : {
|
||||||
|
"alwaysShowBeforeRender" : true,
|
||||||
|
"waiting" : true,
|
||||||
|
"autoclose" : true,
|
||||||
|
"delay" : 0
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
"modules" : {},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
"distribute" : {
|
||||||
|
/* android打包配置 */
|
||||||
|
"android" : {
|
||||||
|
"permissions" : [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
"ios" : {},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs" : {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
"quickapp" : {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
"mp-weixin" : {
|
||||||
|
"appid" : "wxed54ab6227ca85b6",
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false
|
||||||
|
},
|
||||||
|
"usingComponents" : true,
|
||||||
|
"lazyCodeLoading": "requiredComponents"
|
||||||
|
},
|
||||||
|
"mp-alipay" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-baidu" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-toutiao" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"uniStatistics": {
|
||||||
|
"enable": false
|
||||||
|
},
|
||||||
|
"vueVersion" : "3"
|
||||||
|
}
|
||||||
18
src/package.json
Normal file
18
src/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"id": "ua-markdown",
|
||||||
|
"name": "uniapp markdown语法渲染及代码高亮",
|
||||||
|
"displayName": "uniapp markdown语法渲染及代码高亮",
|
||||||
|
"version": "1.2.4",
|
||||||
|
"description": "基于uniapp+vue3自定义解析markdown语法/高亮,适用于h5+小程序+App端。",
|
||||||
|
"keywords": [
|
||||||
|
"ua-markdown",
|
||||||
|
"uni-markdown",
|
||||||
|
"markdown"
|
||||||
|
],
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"前端组件",
|
||||||
|
"通用组件"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/pages.json
Normal file
34
src/pages.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
|
{
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle":"custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/history",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle":"custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/user/suggestion",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle":"custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/ZhouYi/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle":"custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "uni-app",
|
||||||
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/pages/ZhouYi/detail.vue
Normal file
67
src/pages/ZhouYi/detail.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<NavBar title="学习卦辞" />
|
||||||
|
|
||||||
|
<view class="content" v-if="info">
|
||||||
|
<view class="symbol">
|
||||||
|
<wd-text :text="info.symbol" size="96rpx" bold color="#333" />
|
||||||
|
<wd-text :text="info.name" size="48rpx" bold color="#333" />
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="desc">
|
||||||
|
<ua-markdown :source="info.desc" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="page">
|
||||||
|
<wd-status-tip image="https://asstes.snhaenigseal.cn/images/wot/content.png" tip="暂无内容" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
import { getZhouDetail } from '@/api/index'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
|
||||||
|
const info = ref(null)
|
||||||
|
|
||||||
|
onLoad((e) => {
|
||||||
|
if (e.id) {
|
||||||
|
getZhouDetail({
|
||||||
|
id: e.id
|
||||||
|
}).then(res => {
|
||||||
|
info.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (e.name) {
|
||||||
|
getZhouDetail({
|
||||||
|
name: e.name
|
||||||
|
}).then(res => {
|
||||||
|
info.value = res.data[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
align-items: center;
|
||||||
|
margin: 16rpx 0;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin: 32rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
75
src/pages/home/home.vue
Normal file
75
src/pages/home/home.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<NavBar title="学易" />
|
||||||
|
<view style="margin: 16rpx;">
|
||||||
|
<view class="title">
|
||||||
|
<wd-text text="上经" size="36rpx" bold color="#333" />
|
||||||
|
</view>
|
||||||
|
<wd-row >
|
||||||
|
<wd-col
|
||||||
|
:span="6"
|
||||||
|
v-if="list.length"
|
||||||
|
v-for="(item, index) in 上经"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<view class="item" @click="goTo('/pages/ZhouYi/detail?id=' + item.documentId)">
|
||||||
|
<wd-text :text="item.symbol" size="90rpx" color="#333" />
|
||||||
|
<wd-text :text="item.name" bold size="28rpx" color="#333" />
|
||||||
|
</view>
|
||||||
|
</wd-col>
|
||||||
|
</wd-row>
|
||||||
|
|
||||||
|
<view class="title">
|
||||||
|
<wd-text text="下经" size="36rpx" bold color="#333" />
|
||||||
|
</view>
|
||||||
|
<wd-row >
|
||||||
|
<wd-col
|
||||||
|
:span="6"
|
||||||
|
v-if="list.length"
|
||||||
|
v-for="(item, index) in 下经"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<view class="item" @click="goTo('/pages/ZhouYi/detail?id=' + item.documentId)">
|
||||||
|
<wd-text :text="item.symbol" size="90rpx" color="#333" />
|
||||||
|
<wd-text :text="item.name" bold size="28rpx" color="#333" />
|
||||||
|
</view>
|
||||||
|
</wd-col>
|
||||||
|
</wd-row>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
import { getZhouList } from '@/api/modules/ZhouYi';
|
||||||
|
import { goTo } from '@/utils/common'
|
||||||
|
|
||||||
|
const list = ref<any>([])
|
||||||
|
const 上经 = ref<any>([])
|
||||||
|
const 下经 = ref<any>([])
|
||||||
|
|
||||||
|
getZhouList().then(res => {
|
||||||
|
list.value = res.data
|
||||||
|
|
||||||
|
上经.value = list.value.filter(item => item.index >= 1 && item.index <= 30)
|
||||||
|
下经.value = list.value.filter(item => item.index >= 31 && item.index <= 64)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 36rpx 16rpx;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin: 16rpx;
|
||||||
|
padding: 32rpx 16rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
30
src/pages/index/index.vue
Normal file
30
src/pages/index/index.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<view class="content">
|
||||||
|
<Home v-if="tabIndex === 0" />
|
||||||
|
<SuanGua v-if="tabIndex === 1" />
|
||||||
|
<User v-if="tabIndex === 2" />
|
||||||
|
</view>
|
||||||
|
<TabBar />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SuanGua from '@/pages/suan-gua/suan-gua.vue'
|
||||||
|
import Home from '@/pages/home/home.vue'
|
||||||
|
import User from '@/pages/user/user.vue'
|
||||||
|
import TabBar from '@/components/TabBar.vue'
|
||||||
|
import { useTabStore } from "@/stores";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
|
const {
|
||||||
|
tabIndex
|
||||||
|
} = storeToRefs(useTabStore())
|
||||||
|
|
||||||
|
console.log(tabIndex.value);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.content {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
902
src/pages/suan-gua/suan-gua.vue
Normal file
902
src/pages/suan-gua/suan-gua.vue
Normal file
@ -0,0 +1,902 @@
|
|||||||
|
<template>
|
||||||
|
<NavBar title="问易" />
|
||||||
|
<view class="container">
|
||||||
|
|
||||||
|
<view class="header" style="position: relative;">
|
||||||
|
<view style="position: absolute; right: 32rpx; top: 32rpx;">
|
||||||
|
<wd-button size="small" type="icon" icon="refresh" @click="refreshTime"></wd-button>
|
||||||
|
</view>
|
||||||
|
<wd-text text="当前时间" bold color="#333" />
|
||||||
|
<view class="header-time flex-row">
|
||||||
|
<view class="flex-col gap-2">
|
||||||
|
<wd-text text="公历" color="#999" />
|
||||||
|
<wd-text :text="currentTime" bold color="#333" />
|
||||||
|
</view>
|
||||||
|
<view class="flex-col gap-2">
|
||||||
|
<wd-text text="农历" color="#999" />
|
||||||
|
<wd-text :text="lunarTime" bold color="#333" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="header flex-col gap-4" style="margin-top: 32rpx;">
|
||||||
|
<wd-text text="所问之事" bold color="#333" />
|
||||||
|
<wd-input v-model="inputTxt" placeholder="请输入" :disabled="lines.length !== 0"/>
|
||||||
|
|
||||||
|
<!-- 起卦按钮 & 进度提示 -->
|
||||||
|
<!-- <view class="action-area"> -->
|
||||||
|
<wd-button
|
||||||
|
type="warning"
|
||||||
|
size="large"
|
||||||
|
@click="handleCast"
|
||||||
|
:custom-style="{
|
||||||
|
backgroundColor: '#145060',
|
||||||
|
fontSize: '32rpx',
|
||||||
|
}"
|
||||||
|
:loading="coinLoading"
|
||||||
|
block
|
||||||
|
:disabled="showResponseText && !isDone"
|
||||||
|
>
|
||||||
|
{{ buttonText }}
|
||||||
|
</wd-button>
|
||||||
|
<!-- 铜钱区域:三个 view 标签显示正面/反面 -->
|
||||||
|
<view class="coins-area">
|
||||||
|
<view
|
||||||
|
v-for="(coin, index) in coins"
|
||||||
|
:key="index"
|
||||||
|
class="coin"
|
||||||
|
>
|
||||||
|
<wd-img
|
||||||
|
:src="coin === '正面' ? coinPositiveImg : coinNegativeImg"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部主爻与动爻区域 -->
|
||||||
|
<view class="result-content flex-col gap-4">
|
||||||
|
|
||||||
|
<view class="result-area">
|
||||||
|
<!-- 左侧:主爻(本卦六爻) -->
|
||||||
|
<view class="main-lines">
|
||||||
|
<view class="section-title">
|
||||||
|
<wd-text text="本卦" size="28rpx" bold color="#333" />
|
||||||
|
<wd-button
|
||||||
|
v-if="本卦"
|
||||||
|
@click="goTo(`/pages/ZhouYi/detail?name=${本卦}`)"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
:custom-style="{
|
||||||
|
border: '#4b3a2b 2px solid',
|
||||||
|
color: '#4b3a2b',
|
||||||
|
fontSize: '24rpx',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ 本卦 }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
<view v-if="lines.length === 0" class="empty-tip">尚未起卦</view>
|
||||||
|
<view v-else class="lines-list">
|
||||||
|
<view v-for="(line, idx) in displayLines" :key="idx" class="line-item" :style="{
|
||||||
|
backgroundImage: `url(${line.img})`
|
||||||
|
}">
|
||||||
|
<!-- <text class="line-position">{{ line.position }}</text> -->
|
||||||
|
<!-- <view class="line-symbol">{{ line.symbol }}</view>-->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧:变卦信息 -->
|
||||||
|
<view class="moving-lines">
|
||||||
|
<view class="section-title">
|
||||||
|
<wd-text text="变卦" size="28rpx" bold color="#333" />
|
||||||
|
<wd-button
|
||||||
|
v-if="变卦"
|
||||||
|
@click="goTo(`/pages/ZhouYi/detail?name=${变卦}`)"
|
||||||
|
size="small"
|
||||||
|
plain
|
||||||
|
:custom-style="{
|
||||||
|
border: '#4b3a2b 2px solid',
|
||||||
|
color: '#4b3a2b',
|
||||||
|
fontSize: '24rpx',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ 变卦 }}
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
<view v-if="movingInfo.length === 0" class="empty-tip">
|
||||||
|
{{ lines.length === 0 ? '暂无' : '无动爻' }}
|
||||||
|
</view>
|
||||||
|
<view v-else class="moving-list">
|
||||||
|
<view v-for="(info, idx) in movingInfo" :key="idx" class="moving-item" :style="{
|
||||||
|
backgroundImage: `url(${info.img})`
|
||||||
|
}">
|
||||||
|
<!-- {{ info }} -->
|
||||||
|
<!-- <view v-if="info.isMoving" class="moving-symbol">{{ info.symbol }}</view>
|
||||||
|
<view v-else >{{ info.symbol }}</view> -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<wd-text text="占断结果" bold color="#333" />
|
||||||
|
<wd-text v-if="!isComplete" text="尚未成象" color="#333" />
|
||||||
|
<wd-button
|
||||||
|
v-if="isComplete && showUserAgreeButton && !showCompleteLoading"
|
||||||
|
@click="userAgree"
|
||||||
|
block
|
||||||
|
size="large"
|
||||||
|
:custom-style="{
|
||||||
|
backgroundColor: '#145060',
|
||||||
|
fontSize: '32rpx',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</wd-button>
|
||||||
|
|
||||||
|
<view v-if="showResponse" class="result-desc flex-col gap-4">
|
||||||
|
<view class="flex-col gap-2">
|
||||||
|
<wd-text text="动爻爻辞" bold color="#333" />
|
||||||
|
<wd-text v-if="爻辞" :text="爻辞" color="#666" />
|
||||||
|
<view class="empty-tip" v-else>
|
||||||
|
<wd-text text="尚未成象" color="#666" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex-col gap-2">
|
||||||
|
<wd-text text="卦象说明" bold color="#333" />
|
||||||
|
<view class="empty-tip" v-if="!isComplete">
|
||||||
|
<wd-text text="尚未成象" color="#666" />
|
||||||
|
</view>
|
||||||
|
<wd-text
|
||||||
|
v-if="showResponseText && responseText"
|
||||||
|
:text="responseText"
|
||||||
|
color="#666"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<wd-button
|
||||||
|
v-if="isDone && showResponseText"
|
||||||
|
@click="save"
|
||||||
|
block
|
||||||
|
size="large"
|
||||||
|
:custom-style="{
|
||||||
|
backgroundColor: '#145060',
|
||||||
|
fontSize: '32rpx',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
保存此次结果
|
||||||
|
</wd-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<wd-message-box />
|
||||||
|
|
||||||
|
<wd-overlay :show="showCompleteLoading" lock-scroll>
|
||||||
|
<view class="overlay-content">
|
||||||
|
<view class="tips">
|
||||||
|
<view>
|
||||||
|
<wd-text text="提示" size="32rpx" color="#333" />
|
||||||
|
</view>
|
||||||
|
<wd-text text="金钱落定,六爻已成,正在为您推演卦象…… " size="28rpx" color="#333" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-overlay>
|
||||||
|
|
||||||
|
<wd-toast position="middle" />
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useMessage, useToast } from '@/uni_modules/wot-design-uni'
|
||||||
|
import { AIChat, 获取易经爻辞, createQ } from '@/api/index'
|
||||||
|
import { useUserStore, useAIReponseStore } from '@/stores'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { watch } from 'vue'
|
||||||
|
import { formatDate, goTo, rateLimit, sleep } from '@/utils/common'
|
||||||
|
import { 六十四卦 } from '@/utils/gua_64'
|
||||||
|
import lunisolar from 'lunisolar'
|
||||||
|
import { BASE_URL } from '@/config/api.config'
|
||||||
|
|
||||||
|
const {
|
||||||
|
responseText,
|
||||||
|
showResponseText,
|
||||||
|
isLoading,
|
||||||
|
isDone
|
||||||
|
} = storeToRefs(useAIReponseStore())
|
||||||
|
|
||||||
|
const {
|
||||||
|
yaoList
|
||||||
|
} = storeToRefs(useUserStore())
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const currentTime = ref(formatDate(new Date()))
|
||||||
|
const lunarTime = computed(() => {
|
||||||
|
const d = lunisolar(currentTime.value.substring(0, 16))
|
||||||
|
return d.lunar.toString()
|
||||||
|
})
|
||||||
|
const refreshTime = () => {
|
||||||
|
currentTime.value = formatDate(new Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
const YangImg = BASE_URL + '/uploads/mov_yang_d41e51fd83.png'
|
||||||
|
const YinImg = BASE_URL + '/uploads/yin_e32817a811.png'
|
||||||
|
|
||||||
|
const audio = uni.createInnerAudioContext()
|
||||||
|
audio.autoplay = false
|
||||||
|
audio.src = BASE_URL + '/uploads/01_b7107f6b3b.mp3'
|
||||||
|
|
||||||
|
const ding = uni.createInnerAudioContext()
|
||||||
|
ding.autoplay = false
|
||||||
|
ding.src = BASE_URL + '/uploads/ding_ae6e65af25.mp3'
|
||||||
|
|
||||||
|
const coinPositiveImg = BASE_URL + '/uploads/_52f5bd86b5.png'
|
||||||
|
const coinNegativeImg = BASE_URL + '/uploads/_5f46b99d48.png'
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
// 铜钱状态:三个独立的正/反面,初始均为“正面”
|
||||||
|
const coins = ref(['正面', '正面', '正面'])
|
||||||
|
const 本卦 = ref('')
|
||||||
|
const 变卦 = ref('')
|
||||||
|
|
||||||
|
// 存储六爻结果,每个元素为 { type, isMoving, symbol }
|
||||||
|
// type: '少阳'/'少阴'/'老阳'/'老阴'
|
||||||
|
// isMoving: Boolean
|
||||||
|
// symbol: '—' (阳爻) 或 '- -' (阴爻)
|
||||||
|
const lines = ref<any>([])
|
||||||
|
|
||||||
|
// 辅助函数:随机生成正面/反面
|
||||||
|
const randomSide = () => {
|
||||||
|
return Math.random() < 0.5 ? '正面' : '反面'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据三个铜钱结果计算爻的类型、是否动爻、符号
|
||||||
|
const analyzeYao = (coinResults) => {
|
||||||
|
console.log(coinResults)
|
||||||
|
const positiveCount = coinResults.filter(v => v === '正面').length
|
||||||
|
// 正面为阴,反面为阳
|
||||||
|
if (positiveCount === 3) {
|
||||||
|
// 三个正面 → 老阳(动爻)
|
||||||
|
return { type: '老阳', isMoving: true, symbol: '阳', img: YangImg, number: '9' }
|
||||||
|
} else if (positiveCount === 0) {
|
||||||
|
// 三个反面 → 老阴(动爻)
|
||||||
|
return { type: '老阴', isMoving: true, symbol: '阴', img: YinImg, number: '6' }
|
||||||
|
} else if (positiveCount === 2) {
|
||||||
|
// 两正一反 → 少阴(不动)
|
||||||
|
return { type: '少阴', isMoving: false, symbol: '阴', img: YinImg, number: '8' }
|
||||||
|
} else {
|
||||||
|
// 两反一正 → 少阳
|
||||||
|
return { type: '少阳', isMoving: false, symbol: '阳', img: YangImg, number: '7' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置所有状态(清空卦象,重置铜钱为默认正面)
|
||||||
|
const resetState = () => {
|
||||||
|
lines.value = []
|
||||||
|
coins.value = ['正面', '正面', '正面']
|
||||||
|
本卦.value = ''
|
||||||
|
变卦.value = ''
|
||||||
|
体卦.value = ''
|
||||||
|
用卦.value = ''
|
||||||
|
爻辞.value = ''
|
||||||
|
动爻列表.value = []
|
||||||
|
静爻列表.value = []
|
||||||
|
inputTxt.value = ''
|
||||||
|
showUserAgreeButton.value = true
|
||||||
|
showResponse.value = false
|
||||||
|
audio.pause()
|
||||||
|
useAIReponseStore().reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行一次摇卦(增加一爻)
|
||||||
|
const performCast = () => {
|
||||||
|
// 随机生成三个铜钱的正反面
|
||||||
|
const newCoins = [randomSide(), randomSide(), randomSide()]
|
||||||
|
coins.value = [...newCoins]
|
||||||
|
|
||||||
|
// 根据铜钱结果计算爻象
|
||||||
|
const yao = analyzeYao(newCoins)
|
||||||
|
// 存入 lines(从下往上,初爻索引0)
|
||||||
|
lines.value.unshift(yao)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const showDialog = ref(false)
|
||||||
|
const inputTxt = ref<string>('')
|
||||||
|
|
||||||
|
// const isLoading = ref(false)
|
||||||
|
const resultContent = ref<string>('卦象结果生成中')
|
||||||
|
const 本卦数字 = ref('')
|
||||||
|
const 体卦 = ref('')
|
||||||
|
const 用卦 = ref('')
|
||||||
|
const 动爻列表 = ref<string[]>([])
|
||||||
|
const 动爻数量 = ref<number>(0)
|
||||||
|
const 爻辞 = ref('')
|
||||||
|
const 动爻名称 = ['初九', '九二', '九三', '九四', '九五', '上九', '初六', '六二', '六三', '六四', '六五', '上六']
|
||||||
|
const 静爻列表 = ref<string[]>([])
|
||||||
|
const 静爻名称 = ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻']
|
||||||
|
|
||||||
|
const 获取动爻名称 = async () => {
|
||||||
|
const len = 动爻列表.value.length
|
||||||
|
|
||||||
|
if (len === 0) {
|
||||||
|
return '无'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取动爻的爻辞
|
||||||
|
if (len === 1) {
|
||||||
|
return 动爻列表.value[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取上面
|
||||||
|
if (len === 2) {
|
||||||
|
return 动爻列表.value[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取中间
|
||||||
|
if (len === 3) {
|
||||||
|
return 动爻列表.value[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取静爻靠下
|
||||||
|
if (len === 4) {
|
||||||
|
return 静爻列表.value[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取静爻
|
||||||
|
if (len === 5) {
|
||||||
|
return 静爻列表.value[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len === 6) {
|
||||||
|
if (本卦.value === '乾为天') {
|
||||||
|
return '用九'
|
||||||
|
} else if (本卦.value === '坤为地') {
|
||||||
|
return '用六'
|
||||||
|
} else {
|
||||||
|
return '无'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '无'
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCompleteLoading = ref<boolean>(false)
|
||||||
|
const showUserAgreeBox = ref<boolean>(false)
|
||||||
|
// 监听 lines 变化,当长度为 6 时,执行 AI 回答
|
||||||
|
watch(
|
||||||
|
() => lines.value,
|
||||||
|
async (newVal) => {
|
||||||
|
// console.log(newVal)
|
||||||
|
if (newVal.length === 6) {
|
||||||
|
|
||||||
|
showCompleteLoading.value = true
|
||||||
|
await sleep(3000)
|
||||||
|
showCompleteLoading.value = false
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 999999,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 本卦结果
|
||||||
|
let result1 = ''
|
||||||
|
let arr1 = JSON.parse(JSON.stringify(lines.value))
|
||||||
|
arr1.reverse().map(line => {
|
||||||
|
result1 += line.symbol
|
||||||
|
本卦数字.value += line.number
|
||||||
|
})
|
||||||
|
// 变卦结果
|
||||||
|
let result2 = ''
|
||||||
|
let arr2 = JSON.parse(JSON.stringify(movingInfo.value))
|
||||||
|
arr2.reverse().map(line => {
|
||||||
|
result2 += line.symbol
|
||||||
|
})
|
||||||
|
// 动爻及静爻结果
|
||||||
|
// let result3 = ''
|
||||||
|
let arr3 = JSON.parse(JSON.stringify(movingInfo.value))
|
||||||
|
arr3.reverse().map((line, i) => {
|
||||||
|
if (line.isMoving) {
|
||||||
|
if (line.number === '6') {
|
||||||
|
动爻列表.value.push(动爻名称[6 + i])
|
||||||
|
}
|
||||||
|
if (line.number === '9') {
|
||||||
|
动爻列表.value.push(动爻名称[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (line.number === '8') {
|
||||||
|
静爻列表.value.push(动爻名称[6 + i])
|
||||||
|
}
|
||||||
|
if (line.number === '7') {
|
||||||
|
静爻列表.value.push(动爻名称[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// console.log(动爻列表.value, 静爻列表.value)
|
||||||
|
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
if (六十四卦[i].symbol === result1) {
|
||||||
|
本卦.value = 六十四卦[i].name
|
||||||
|
体卦.value = 六十四卦[i].bottom
|
||||||
|
用卦.value = 六十四卦[i].top
|
||||||
|
}
|
||||||
|
if (六十四卦[i].symbol === result2) {
|
||||||
|
变卦.value = 六十四卦[i].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _动爻名称 = await 获取动爻名称()
|
||||||
|
if (_动爻名称 === '无') {
|
||||||
|
爻辞.value = '无动爻'
|
||||||
|
} else {
|
||||||
|
爻辞.value = await 获取易经爻辞(本卦.value, _动爻名称)
|
||||||
|
}
|
||||||
|
|
||||||
|
// // AI 回答
|
||||||
|
await AIChat(
|
||||||
|
inputTxt.value,
|
||||||
|
本卦.value,
|
||||||
|
变卦.value,
|
||||||
|
爻辞.value,
|
||||||
|
// count
|
||||||
|
).then(res => {
|
||||||
|
if (res.code) {
|
||||||
|
toast.error(res.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// watch(
|
||||||
|
// () => isLoading.value,
|
||||||
|
// (newVal) => {
|
||||||
|
// if (!newVal) {
|
||||||
|
// audio.seek(0)
|
||||||
|
// audio.play()
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// deep: true
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
// 用户同意查看占卜结果并播放音乐
|
||||||
|
const showUserAgreeButton = ref<boolean>(true)
|
||||||
|
const agreeMsg = '古人云,“善为易者不占”。前路吉凶,本是人心之倒影;占断微言,不过卦象之迷津。六爻所示,并非不可逆转的宿命,其所示者,无非易理推演,未必尽然无误。无论得遇何卦,请以平和之心查看。万事终究人为,是否查看占断结果?'
|
||||||
|
const showResponse = ref<boolean>(false)
|
||||||
|
const userAgree = () => {
|
||||||
|
// showUserAgreeBox.value = true
|
||||||
|
message
|
||||||
|
.confirm({
|
||||||
|
title: '提示',
|
||||||
|
msg: agreeMsg,
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
audio.seek(0)
|
||||||
|
audio.play()
|
||||||
|
showResponse.value = true
|
||||||
|
showResponseText.value = true
|
||||||
|
isLoading.value = true
|
||||||
|
showUserAgreeButton.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const agreeCancel = () => {
|
||||||
|
showUserAgreeBox.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const agreeConfirm = () => {
|
||||||
|
showUserAgreeBox.value = false
|
||||||
|
audio.seek(0)
|
||||||
|
audio.play()
|
||||||
|
showResponse.value = true
|
||||||
|
showResponseText.value = true
|
||||||
|
isLoading.value = true
|
||||||
|
showUserAgreeButton.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 保存占卜结果
|
||||||
|
const save = () => {
|
||||||
|
yaoList.value.unshift({
|
||||||
|
time: new Date().getTime(),
|
||||||
|
q: inputTxt.value,
|
||||||
|
symbol1: 本卦.value,
|
||||||
|
symbol2: 变卦.value,
|
||||||
|
symbolNum: 本卦数字.value,
|
||||||
|
result: responseText.value
|
||||||
|
})
|
||||||
|
responseText.value = ''
|
||||||
|
isDone.value = false
|
||||||
|
toast.success('保存成功')
|
||||||
|
|
||||||
|
resetState()
|
||||||
|
}
|
||||||
|
|
||||||
|
const coinLoading = ref<boolean>(false)
|
||||||
|
|
||||||
|
// 起卦逻辑
|
||||||
|
const handleCast = async () => {
|
||||||
|
|
||||||
|
ding.play()
|
||||||
|
|
||||||
|
// 如果已完成六爻,则先重置
|
||||||
|
if (lines.value.length === 6) {
|
||||||
|
|
||||||
|
resetState()
|
||||||
|
} else if (lines.value.length === 0) {
|
||||||
|
|
||||||
|
if (inputTxt.value === '') {
|
||||||
|
toast.error('请输入所问之事')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await createQ(inputTxt.value)
|
||||||
|
const func = await rateLimit(
|
||||||
|
30 * 60 * 1000,
|
||||||
|
3
|
||||||
|
)
|
||||||
|
if (!func()) {
|
||||||
|
toast.error('您短时间内占卜次数过多,请稍后再试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
coinLoading.value = true
|
||||||
|
// 未满六爻,正常摇卦
|
||||||
|
toast.info({
|
||||||
|
msg: '请静心片刻,默念所问之事',
|
||||||
|
closed: () => {
|
||||||
|
performCast()
|
||||||
|
coinLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
coinLoading.value = true
|
||||||
|
performCast()
|
||||||
|
setTimeout(() => {
|
||||||
|
coinLoading.value = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 当前已摇的爻数(1-6)
|
||||||
|
const currentStep = computed(() => lines.value.length)
|
||||||
|
// 是否已完成六爻
|
||||||
|
const isComplete = computed(() => lines.value.length === 6)
|
||||||
|
|
||||||
|
// 按钮动态文本
|
||||||
|
const buttonText = computed(() => {
|
||||||
|
if (currentStep.value === 0) {
|
||||||
|
return '开始请爻'
|
||||||
|
} else if (currentStep.value === 6) {
|
||||||
|
return '重新请爻'
|
||||||
|
} else {
|
||||||
|
return `继续请爻`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 主爻显示:将 lines 数组转为从下往上(初爻在下)展示,并配上爻位名称
|
||||||
|
// 传统卦象显示习惯:初爻在下,上爻在上,因此列表顺序为从初爻到上爻(上爻在最后)
|
||||||
|
const displayLines = computed(() => {
|
||||||
|
const positionNames = ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻']
|
||||||
|
return lines.value.map((line, index) => ({
|
||||||
|
position: positionNames[index],
|
||||||
|
symbol: line.symbol,
|
||||||
|
img: line.img,
|
||||||
|
number: line.number
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 动爻信息:根据 lines 中 isMoving 为 true 的爻,生成描述文本
|
||||||
|
const movingInfo = computed(() => {
|
||||||
|
const movingIndices = <any>[]
|
||||||
|
// return movingIndices
|
||||||
|
lines.value.forEach((line, idx) => {
|
||||||
|
let item = {
|
||||||
|
...line,
|
||||||
|
img: line.img
|
||||||
|
}
|
||||||
|
if (line.isMoving) {
|
||||||
|
item.position = `第${6 - idx}爻动`
|
||||||
|
item.symbol = line.symbol === '阴' ? '阳' : '阴'
|
||||||
|
if (line.type === '老阴') item.img = YangImg
|
||||||
|
if (line.type === '老阳') item.img = YinImg
|
||||||
|
}
|
||||||
|
movingIndices.push(item)
|
||||||
|
})
|
||||||
|
return movingIndices
|
||||||
|
|
||||||
|
// if (movingIndices.length === 0) return []
|
||||||
|
|
||||||
|
// const positionNames = ['初爻', '二爻', '三爻', '四爻', '五爻', '上爻']
|
||||||
|
// return movingIndices.map(idx => {
|
||||||
|
// const line = lines.value[idx]
|
||||||
|
// // return `${positionNames[idx]}动 (${line.type})`
|
||||||
|
// return line.symbol
|
||||||
|
// })
|
||||||
|
// return lines.value
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
// background: linear-gradient(145deg, #f7f3e9 0%, #e8e0d3 100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
// font-family: 'Segoe UI', '思源黑体', system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头部 */
|
||||||
|
.header {
|
||||||
|
// text-align: center;
|
||||||
|
// margin-bottom: 60rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
padding: 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3a2c1f;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
display: block;
|
||||||
|
text-shadow: 2rpx 2rpx 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-time {
|
||||||
|
margin-top: 32rpx;
|
||||||
|
gap: 32rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #7f6b4f;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 三枚铜钱区域 */
|
||||||
|
.coins-area {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 32rpx;
|
||||||
|
margin: 16rpx 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.coin {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
// border-radius: 50%;
|
||||||
|
filter: drop-shadow(0 8rpx 10rpx rgba(0, 0, 0, 0.25));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.cast-btn) {
|
||||||
|
width: 400rpx;
|
||||||
|
background: #4b3a2b;
|
||||||
|
color: #fef1df;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
box-shadow: 0 6rpx 0 #2b2118;
|
||||||
|
transition: all 0.1s linear;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮区域 */
|
||||||
|
.action-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 30rpx 0 40rpx;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #5c4a34;
|
||||||
|
background: rgba(255, 245, 225, 0.8);
|
||||||
|
padding: 8rpx 24rpx;
|
||||||
|
border-radius: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complete-text {
|
||||||
|
color: #9b6e3e;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部结果区域 (主爻 | 动爻) */
|
||||||
|
.result-area {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
// margin-top: 32rpx;
|
||||||
|
gap: 32rpx;
|
||||||
|
min-height: 348rpx;
|
||||||
|
// flex: 1;
|
||||||
|
|
||||||
|
.main-lines,
|
||||||
|
.moving-lines {
|
||||||
|
flex: 1;
|
||||||
|
background: #f3f4f6;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||||
|
position: relative;
|
||||||
|
// min-height: 400rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
// border-left: 8rpx solid #5d3f1e;
|
||||||
|
// padding-left: 20rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
color: #4b3a2b;
|
||||||
|
font-size: 28rpx;
|
||||||
|
padding: 40rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lines-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 48rpx);
|
||||||
|
bottom: 24rpx;
|
||||||
|
// gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-item {
|
||||||
|
// display: flex;
|
||||||
|
// justify-content: center;
|
||||||
|
// font-family: monospace;
|
||||||
|
margin: 8rpx 0;
|
||||||
|
padding: 12rpx 20rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
align-items: baseline;
|
||||||
|
// background: #fef7e8;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
// margin: 4rpx 0;
|
||||||
|
|
||||||
|
.line-position {
|
||||||
|
width: 80rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #b87c3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-symbol {
|
||||||
|
font-size: 32rpx;
|
||||||
|
// font-family: monospace;
|
||||||
|
letter-spacing: 6rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.moving-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 48rpx);
|
||||||
|
bottom: 24rpx;
|
||||||
|
|
||||||
|
.moving-item {
|
||||||
|
// background: #fff0e0;
|
||||||
|
margin: 8rpx 0;
|
||||||
|
padding: 12rpx 20rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
// color: #ff0000;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
// box-shadow: inset 0 1rpx 2rpx rgba(0, 0, 0, 0.02), 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.moving-symbol {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guaxiang {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 64rpx);
|
||||||
|
bottom: 32rpx;
|
||||||
|
// width: 100%;
|
||||||
|
color: #4b3a2b;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-content {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 32rpx;
|
||||||
|
padding: 32rpx;
|
||||||
|
gap: 32rpx;
|
||||||
|
height: auto;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.result-desc {
|
||||||
|
// mar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图例说明 */
|
||||||
|
.overlay-content {
|
||||||
|
// position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
.tips {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 32rpx;
|
||||||
|
width: 300px;
|
||||||
|
padding: 48rpx;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-agree-tips {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 64rpx;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #B8AD70;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 120rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
101
src/pages/user/history.vue
Normal file
101
src/pages/user/history.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<NavBar title="历史记录" />
|
||||||
|
|
||||||
|
<!-- <wd-img /> -->
|
||||||
|
<view class="content">
|
||||||
|
<wd-card
|
||||||
|
type="rectangle"
|
||||||
|
v-if="yaoList.length"
|
||||||
|
v-for="(item, index) in yaoList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<wd-text
|
||||||
|
:text="`${item.symbol1}变${item.symbol2}`"
|
||||||
|
color="#333"
|
||||||
|
size="32rpx"
|
||||||
|
bold
|
||||||
|
/>
|
||||||
|
<wd-text
|
||||||
|
v-if="item.q"
|
||||||
|
:text="`占${item.q}详解`"
|
||||||
|
color="#333"
|
||||||
|
size="32rpx"
|
||||||
|
bold
|
||||||
|
/>
|
||||||
|
<!-- <wd-text
|
||||||
|
v-if="item.symbolNum"
|
||||||
|
:text="item.symbolNum"
|
||||||
|
color="#333"
|
||||||
|
size="32rpx"
|
||||||
|
bold
|
||||||
|
/> -->
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<wd-text :text="item.result" />
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<view class="footer">
|
||||||
|
<wd-text :text="formatDate(new Date(item.time))" />
|
||||||
|
<wd-button size="small" type="error" plain @click="delHistory(index)">删除</wd-button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</wd-card>
|
||||||
|
<view v-else class="page">
|
||||||
|
<wd-status-tip image="https://asstes.snhaenigseal.cn/images/wot/content.png" tip="暂无内容" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<wd-message-box />
|
||||||
|
<wd-toast />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
import { useUserStore } from '@/stores'
|
||||||
|
import { formatDate } from '@/utils/common'
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useMessage, useToast } from 'wot-design-uni';
|
||||||
|
|
||||||
|
const {
|
||||||
|
yaoList
|
||||||
|
} = storeToRefs(useUserStore())
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const delHistory = (index: number) => {
|
||||||
|
message
|
||||||
|
.confirm({
|
||||||
|
title: '提示',
|
||||||
|
msg: '确定要删除此条记录吗'
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
yaoList.value.splice(index, 1)
|
||||||
|
toast.success('删除成功')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.content {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 720rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 32rpx;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
50
src/pages/user/suggestion.vue
Normal file
50
src/pages/user/suggestion.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<NavBar title="建议反馈" />
|
||||||
|
|
||||||
|
<view class="content flex-col gap-4">
|
||||||
|
<wd-textarea v-model="desc" placeholder="请填写建议反馈" />
|
||||||
|
|
||||||
|
<wd-button block @click="submit">提交</wd-button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<wd-toast />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
import { createSuggestion } from "@/api/modules/suggestion"
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useToast } from '@/uni_modules/wot-design-uni'
|
||||||
|
|
||||||
|
|
||||||
|
const desc = ref('')
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (!desc.value) {
|
||||||
|
toast.error('请填写建议反馈')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await createSuggestion(
|
||||||
|
desc.value
|
||||||
|
).then(res => {
|
||||||
|
if (res.data) {
|
||||||
|
toast.success('提交成功')
|
||||||
|
desc.value = ''
|
||||||
|
} else {
|
||||||
|
toast.error('提交失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.content {
|
||||||
|
width: calc(100% - 64rpx);
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
85
src/pages/user/user.vue
Normal file
85
src/pages/user/user.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<NavBar title="个人中心" />
|
||||||
|
|
||||||
|
<view class="avatar">
|
||||||
|
<wd-img
|
||||||
|
v-if="avatar"
|
||||||
|
:src="BASE_URL + avatar"
|
||||||
|
width="160rpx"
|
||||||
|
height="160rpx"
|
||||||
|
round
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<wd-cell-group border>
|
||||||
|
<wd-cell
|
||||||
|
v-for="(item, index) in list"
|
||||||
|
:title="item.title"
|
||||||
|
:is-link="item.isLink"
|
||||||
|
:to="item.url"
|
||||||
|
@click="item.onClick"
|
||||||
|
/>
|
||||||
|
</wd-cell-group>
|
||||||
|
|
||||||
|
<wd-message-box />
|
||||||
|
<wd-toast />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NavBar from '@/components/NavBar.vue'
|
||||||
|
import { useUserStore } from '@/stores'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useMessage, useToast } from '@/uni_modules/wot-design-uni'
|
||||||
|
import { getPageConfig } from '@/api/modules/config';
|
||||||
|
import { BASE_URL } from '@/config/api.config';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const {
|
||||||
|
yaoList
|
||||||
|
} = storeToRefs(useUserStore())
|
||||||
|
|
||||||
|
const avatar = ref<string>('')
|
||||||
|
getPageConfig().then(res => {
|
||||||
|
avatar.value = res.avatar.formats.medium.url
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = [
|
||||||
|
{
|
||||||
|
title: '历史记录',
|
||||||
|
isLink: true,
|
||||||
|
url: '/pages/user/history'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '清空缓存',
|
||||||
|
isLink: true,
|
||||||
|
onClick: () => {
|
||||||
|
message
|
||||||
|
.confirm({
|
||||||
|
title: '提示',
|
||||||
|
msg: '此操作将删除所有历史数据,是否确定?'
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
yaoList.value = []
|
||||||
|
toast.success('清除缓存成功')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '建议反馈',
|
||||||
|
isLink: true,
|
||||||
|
url: '/pages/user/suggestion'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.avatar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 48rpx auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
49
src/readme.md
Normal file
49
src/readme.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
# vue3版本!!!
|
||||||
|
vue2版本已经上线,欢迎下载使用。
|
||||||
|
[https://ext.dcloud.net.cn/plugin?id=13864](https://ext.dcloud.net.cn/plugin?id=13864)
|
||||||
|
|
||||||
|
## uniapp markdown渲染解析.md语法及代码高亮
|
||||||
|
> **组件名:uaMarkdown**
|
||||||
|
> 代码块: `<ua-markdown>`
|
||||||
|
|
||||||
|
|
||||||
|
uaMarkdown组件是基于uniapp+vue3自定义解析markdown语法结构插件、支持代码块高亮,编译兼容H5+小程序端+App端。
|
||||||
|
|
||||||
|
|
||||||
|
### 引入方式
|
||||||
|
|
||||||
|
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,只需将本组件`ua-markdown`放在components目录,在页面`template`中即可直接使用。
|
||||||
|
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
**示例**
|
||||||
|
|
||||||
|
- 基础用法
|
||||||
|
|
||||||
|
```html
|
||||||
|
const mdvalue = '### uniapp markdwon'
|
||||||
|
<ua-markdown :source="mdvalue" />
|
||||||
|
```
|
||||||
|
|
||||||
|
- 去掉代码块行号
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ua-markdown :source="xxx" :showLine="false" />
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
### uaMarkdown Props
|
||||||
|
|
||||||
|
|属性名|类型|默认值|说明|
|
||||||
|
|:-:|:-:|:-:|:-:|
|
||||||
|
|source|String|-| 渲染解析内容 |
|
||||||
|
|showLine|Boolean|true| 是否显示代码块行号 |
|
||||||
|
|
||||||
|
|
||||||
|
### 💝最后
|
||||||
|
|
||||||
|
开发不易,希望各位小伙伴们多多支持下哈~~ ☕️☕️
|
||||||
BIN
src/static/avatar.png
Normal file
BIN
src/static/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
BIN
src/static/logo.png
Normal file
BIN
src/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/static/mov_yang.png
Normal file
BIN
src/static/mov_yang.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/static/mov_yin.png
Normal file
BIN
src/static/mov_yin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/static/yang.png
Normal file
BIN
src/static/yang.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/static/yin.png
Normal file
BIN
src/static/yin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
27
src/stores/index.ts
Normal file
27
src/stores/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// src/store/index.ts
|
||||||
|
import type { App } from "vue";
|
||||||
|
import { createPinia } from "pinia"
|
||||||
|
import { createPersistedState } from 'pinia-plugin-persistedstate';
|
||||||
|
|
||||||
|
const createPersistUni = () => {
|
||||||
|
return createPersistedState({
|
||||||
|
storage: {
|
||||||
|
getItem: uni.getStorageSync,
|
||||||
|
setItem: uni.setStorageSync
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = createPinia();
|
||||||
|
store.use(createPersistUni());
|
||||||
|
|
||||||
|
export * from './modules/tabIndex'
|
||||||
|
export * from './modules/user'
|
||||||
|
export * from './modules/AIResponse'
|
||||||
|
export * from './modules/rateLimit'
|
||||||
|
|
||||||
|
|
||||||
|
// 注册 Pinia
|
||||||
|
export function setupStore(app: App<Element>) {
|
||||||
|
app.use(store); // 全局注册 Pinia
|
||||||
|
}
|
||||||
28
src/stores/modules/AIResponse.ts
Normal file
28
src/stores/modules/AIResponse.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useAIReponseStore = defineStore('AIResponse', () => {
|
||||||
|
const responseText = ref<string>('')
|
||||||
|
|
||||||
|
const showResponseText = ref<boolean>(false)
|
||||||
|
|
||||||
|
const isLoading = ref<boolean>(false)
|
||||||
|
|
||||||
|
const isDone = ref<boolean>(false)
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
responseText.value = ''
|
||||||
|
showResponseText.value = false
|
||||||
|
isDone.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
responseText,
|
||||||
|
showResponseText,
|
||||||
|
isLoading,
|
||||||
|
isDone,
|
||||||
|
reset
|
||||||
|
}
|
||||||
|
})
|
||||||
14
src/stores/modules/rateLimit.ts
Normal file
14
src/stores/modules/rateLimit.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useRateLimitStore = defineStore('rateLimit', () => {
|
||||||
|
const timestamps = ref<number[]>([])
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamps
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
persist: true
|
||||||
|
})
|
||||||
14
src/stores/modules/tabIndex.ts
Normal file
14
src/stores/modules/tabIndex.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useTabStore = defineStore('tab', () => {
|
||||||
|
const tabIndex = ref(1)
|
||||||
|
|
||||||
|
return {
|
||||||
|
tabIndex
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
persist: true
|
||||||
|
})
|
||||||
21
src/stores/modules/user.ts
Normal file
21
src/stores/modules/user.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
interface Yao {
|
||||||
|
time: number, // 时间轴
|
||||||
|
symbol1?: string, // 主爻结果
|
||||||
|
symbol2?: string, // 动爻内容
|
||||||
|
symbolNum: string // 主卦结果数字
|
||||||
|
result?: string, // 算卦结果
|
||||||
|
q: string, // 所问之事
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', () => {
|
||||||
|
const yaoList = ref<Yao[]>([])
|
||||||
|
|
||||||
|
return {
|
||||||
|
yaoList
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
persist: true
|
||||||
|
})
|
||||||
76
src/uni.scss
Normal file
76
src/uni.scss
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 这里是uni-app内置的常用样式变量
|
||||||
|
*
|
||||||
|
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||||
|
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||||
|
*
|
||||||
|
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
|
||||||
|
/* 行为相关颜色 */
|
||||||
|
$uni-color-primary: #007aff;
|
||||||
|
$uni-color-success: #4cd964;
|
||||||
|
$uni-color-warning: #f0ad4e;
|
||||||
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
/* 文字基本颜色 */
|
||||||
|
$uni-text-color:#333;//基本色
|
||||||
|
$uni-text-color-inverse:#fff;//反色
|
||||||
|
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-text-color-disable:#c0c0c0;
|
||||||
|
|
||||||
|
/* 背景颜色 */
|
||||||
|
$uni-bg-color:#ffffff;
|
||||||
|
$uni-bg-color-grey:#f8f8f8;
|
||||||
|
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||||
|
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
$uni-border-color:#c8c7cc;
|
||||||
|
|
||||||
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$uni-font-size-sm:24rpx;
|
||||||
|
$uni-font-size-base:28rpx;
|
||||||
|
$uni-font-size-lg:32rpx;
|
||||||
|
|
||||||
|
/* 图片尺寸 */
|
||||||
|
$uni-img-size-sm:40rpx;
|
||||||
|
$uni-img-size-base:52rpx;
|
||||||
|
$uni-img-size-lg:80rpx;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
$uni-border-radius-sm: 4rpx;
|
||||||
|
$uni-border-radius-base: 6rpx;
|
||||||
|
$uni-border-radius-lg: 12rpx;
|
||||||
|
$uni-border-radius-circle: 50%;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-row-sm: 10px;
|
||||||
|
$uni-spacing-row-base: 20rpx;
|
||||||
|
$uni-spacing-row-lg: 30rpx;
|
||||||
|
|
||||||
|
/* 垂直间距 */
|
||||||
|
$uni-spacing-col-sm: 8rpx;
|
||||||
|
$uni-spacing-col-base: 16rpx;
|
||||||
|
$uni-spacing-col-lg: 24rpx;
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
|
/* 文章场景相关 */
|
||||||
|
$uni-color-title: #2C405A; // 文章标题颜色
|
||||||
|
$uni-font-size-title:40rpx;
|
||||||
|
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||||
|
$uni-font-size-subtitle:36rpx;
|
||||||
|
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||||
|
$uni-font-size-paragraph:30rpx;
|
||||||
2277
src/uni_modules/wot-design-uni/changelog.md
Normal file
2277
src/uni_modules/wot-design-uni/changelog.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,28 @@
|
|||||||
|
export class AbortablePromise<T> {
|
||||||
|
promise: Promise<T>
|
||||||
|
private _reject: ((res?: any) => void) | null = null
|
||||||
|
|
||||||
|
constructor(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) {
|
||||||
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
|
executor(resolve, reject)
|
||||||
|
this._reject = reject // 保存reject方法的引用,以便在abort时调用
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 提供abort方法来中止Promise
|
||||||
|
abort(error?: any) {
|
||||||
|
if (this._reject) {
|
||||||
|
this._reject(error) // 调用reject方法来中止Promise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
then<TResult1 = T, TResult2 = never>(
|
||||||
|
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||||
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
|
||||||
|
): Promise<TResult1 | TResult2> {
|
||||||
|
return this.promise.then(onfulfilled, onrejected)
|
||||||
|
}
|
||||||
|
|
||||||
|
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> {
|
||||||
|
return this.promise.catch(onrejected)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* SCSS 配置项:命名空间以及BEM
|
||||||
|
*/
|
||||||
|
$namespace: 'wd';
|
||||||
|
$elementSeparator: '__';
|
||||||
|
$modifierSeparator: '--';
|
||||||
|
$state-prefix: 'is-';
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 辅助函数
|
||||||
|
*/
|
||||||
|
@import 'config';
|
||||||
|
$default-theme: #4d80f0 !default; // 正常色
|
||||||
|
|
||||||
|
/* 转换成字符串 */
|
||||||
|
@function selectorToString($selector) {
|
||||||
|
$selector: inspect($selector);
|
||||||
|
$selector: str-slice($selector, 2, -2);
|
||||||
|
|
||||||
|
@return $selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断是否存在 Modifier */
|
||||||
|
@function containsModifier($selector) {
|
||||||
|
$selector: selectorToString($selector);
|
||||||
|
|
||||||
|
@if str-index($selector, $modifierSeparator) {
|
||||||
|
@return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 判断是否存在伪类 */
|
||||||
|
@function containsPseudo($selector) {
|
||||||
|
$selector: selectorToString($selector);
|
||||||
|
|
||||||
|
@if str-index($selector, ':') {
|
||||||
|
@return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题色切换
|
||||||
|
* @params $theme-color 主题色
|
||||||
|
* @params $type 变暗’dark‘ 变亮 'light'
|
||||||
|
* @params $mix-color 自己设置的混色
|
||||||
|
*/
|
||||||
|
@function themeColor($theme-color, $type: "", $mix-color: "") {
|
||||||
|
@if $default-theme !=#4d80f0 {
|
||||||
|
@if $type=="dark" {
|
||||||
|
@return darken($theme-color, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $type=="light" {
|
||||||
|
@return lighten($theme-color, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return $theme-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@return $mix-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 颜色结果切换, 如果开启线性渐变色 使用渐变色,如果没有开启,那么使用主题色
|
||||||
|
* @params $open-linear 是否开启线性渐变色
|
||||||
|
* @params $deg 渐变色角度
|
||||||
|
* @params $theme-color 当前配色
|
||||||
|
* @params [Array] $set 主题色明暗设置,与 $color-list 数量对应
|
||||||
|
* @params [Array] $color-list 渐变色顺序, $color-list 和 $per-list 数量相同
|
||||||
|
* @params [Array] $per-list 渐变色比例
|
||||||
|
*/
|
||||||
|
@function resultColor($deg, $theme-color, $set, $color-list, $per-list) {
|
||||||
|
// 开启渐变
|
||||||
|
|
||||||
|
$len: length($color-list);
|
||||||
|
$arg: $deg;
|
||||||
|
|
||||||
|
@for $i from 1 through $len {
|
||||||
|
$arg: $arg + ","+ themeColor($theme-color, nth($set, $i), nth($color-list, $i)) + " "+ nth($per-list, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@return linear-gradient(unquote($arg));
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,385 @@
|
|||||||
|
/**
|
||||||
|
* 混合宏
|
||||||
|
*/
|
||||||
|
@import "config";
|
||||||
|
@import "function";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BEM,定义块(b)
|
||||||
|
*/
|
||||||
|
@mixin b($block) {
|
||||||
|
$B: $namespace + "-"+ $block !global;
|
||||||
|
|
||||||
|
.#{$B} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 定义元素(e),对于伪类,会自动将 e 嵌套在 伪类 底下 */
|
||||||
|
@mixin e($element...) {
|
||||||
|
$selector: &;
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@if containsPseudo($selector) {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + "." + $B + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selector} {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + $selector + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 此方法用于生成穿透样式 */
|
||||||
|
|
||||||
|
/* 定义元素(e),对于伪类,会自动将 e 嵌套在 伪类 底下 */
|
||||||
|
@mixin edeep($element...) {
|
||||||
|
$selector: &;
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@if containsPseudo($selector) {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + "." + $B + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selector} {
|
||||||
|
:deep() {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + $selector + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
:deep() {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 定义状态(m) */
|
||||||
|
@mixin m($modifier...) {
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@each $item in $modifier {
|
||||||
|
$selectors: #{$selectors + & + $modifierSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 定义状态(m) */
|
||||||
|
@mixin mdeep($modifier...) {
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@each $item in $modifier {
|
||||||
|
$selectors: #{$selectors + & + $modifierSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
:deep() {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对于需要需要嵌套在 m 底下的 e,调用这个混合宏,一般在切换整个组件的状态,如切换颜色的时候 */
|
||||||
|
@mixin me($element...) {
|
||||||
|
$selector: &;
|
||||||
|
$selectors: "";
|
||||||
|
|
||||||
|
@if containsModifier($selector) {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + "." + $B + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selector} {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
@each $item in $element {
|
||||||
|
$selectors: #{$selectors + $selector + $elementSeparator + $item + ","};
|
||||||
|
}
|
||||||
|
|
||||||
|
@at-root {
|
||||||
|
#{$selectors} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态,生成 is-$state 类名 */
|
||||||
|
@mixin when($states...) {
|
||||||
|
@at-root {
|
||||||
|
@each $state in $states {
|
||||||
|
&.#{$state-prefix + $state} {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用混合宏
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 单行超出隐藏 */
|
||||||
|
@mixin lineEllipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 多行超出隐藏 */
|
||||||
|
@mixin multiEllipsis($lineNumber: 3) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: $lineNumber;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 清除浮动 */
|
||||||
|
@mixin clearFloat {
|
||||||
|
&::after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
height: 0;
|
||||||
|
clear: both;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 0.5px 边框 指定方向*/
|
||||||
|
@mixin halfPixelBorder($direction: "bottom", $left: 0, $color: $-color-border-light) {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
@if ($left==0) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
width: calc(100% - #{$left});
|
||||||
|
}
|
||||||
|
|
||||||
|
height: 1px;
|
||||||
|
left: $left;
|
||||||
|
|
||||||
|
@if ($direction=="bottom") {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: scaleY(0.5);
|
||||||
|
background: $color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 0.5px 边框 环绕 */
|
||||||
|
@mixin halfPixelBorderSurround($color: $-color-border-light) {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: ' ';
|
||||||
|
pointer-events: none;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border: 1px solid $color;
|
||||||
|
transform: scale(0.5);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transform-origin: left top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin buttonClear {
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三角形实现尖角样式,适用于背景透明情况
|
||||||
|
* @param $size 三角形高,底边为 $size * 2
|
||||||
|
* @param $bg 三角形背景颜色
|
||||||
|
*/
|
||||||
|
@mixin triangleArrow($size, $bg) {
|
||||||
|
@include e(arrow) {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-down) {
|
||||||
|
border-left: $size solid transparent;
|
||||||
|
border-right: $size solid transparent;
|
||||||
|
border-top: $size solid $bg;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
bottom: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-up) {
|
||||||
|
border-left: $size solid transparent;
|
||||||
|
border-right: $size solid transparent;
|
||||||
|
border-bottom: $size solid $bg;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
top: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-left) {
|
||||||
|
border-top: $size solid transparent;
|
||||||
|
border-bottom: $size solid transparent;
|
||||||
|
border-right: $size solid $bg;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-right) {
|
||||||
|
border-top: $size solid transparent;
|
||||||
|
border-bottom: $size solid transparent;
|
||||||
|
border-left: $size solid $bg;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: calc(-1 * $size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 正方形实现尖角样式,适用于背景不透明情况
|
||||||
|
* @param $size 正方形边长
|
||||||
|
* @param $bg 正方形背景颜色
|
||||||
|
* @param $z-index z-index属性值,不得大于外部包裹器
|
||||||
|
* @param $box-shadow 阴影
|
||||||
|
*/
|
||||||
|
@mixin squareArrow($size, $bg, $z-index, $box-shadow) {
|
||||||
|
@include e(arrow) {
|
||||||
|
position: absolute;
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
z-index: $z-index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-down) {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: calc(-1 * $size / 2);
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-up) {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(-1 * $size / 2);
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-left) {
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(-1 * $size / 2);
|
||||||
|
top: 0;
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(arrow-right) {
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
background-color: $bg;
|
||||||
|
position: absolute;
|
||||||
|
right: calc(-1 * $size / 2);
|
||||||
|
top: 0;
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
box-shadow: $box-shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,974 @@
|
|||||||
|
@import './function';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI规范基础变量
|
||||||
|
*/
|
||||||
|
/*----------------------------------------- Theme color. start ----------------------------------------*/
|
||||||
|
/* 主题颜色 */
|
||||||
|
$-color-theme: var(--wot-color-theme, $default-theme) !default; // 主题色
|
||||||
|
$-color-white: var(--wot-color-white, rgb(255, 255, 255)) !default; // 用于mix的白色
|
||||||
|
$-color-black: var(--wot-color-black, rgb(0, 0, 0)) !default; // 用于mix的黑色
|
||||||
|
|
||||||
|
/* 辅助色 */
|
||||||
|
$-color-success: var(--wot-color-success, #34d19d) !default; // 成功色
|
||||||
|
$-color-warning: var(--wot-color-warning, #f0883a) !default; // 警告色
|
||||||
|
$-color-danger: var(--wot-color-danger, #fa4350) !default; // 危险出错色
|
||||||
|
$-color-purple: var(--wot-color-purple, #8268de) !default; // 紫色
|
||||||
|
$-color-yellow: var(--wot-color-yellow, #f0cd1d) !default; // 黄色
|
||||||
|
$-color-blue: var(--wot-color-blue, #2bb3ed) !default; // 蓝色
|
||||||
|
$-color-info: var(--wot-color-info, #909399) !default;
|
||||||
|
|
||||||
|
$-color-gray-1: var(--wot-color-gray-1, #f7f8fa) !default;
|
||||||
|
$-color-gray-2: var(--wot-color-gray-2, #f2f3f5) !default;
|
||||||
|
$-color-gray-3: var(--wot-color-gray-3, #ebedf0) !default;
|
||||||
|
$-color-gray-4: var(--wot-color-gray-4, #dcdee0) !default;
|
||||||
|
$-color-gray-5: var(--wot-color-gray-5, #c8c9cc) !default;
|
||||||
|
$-color-gray-6: var(--wot-color-gray-6, #969799) !default;
|
||||||
|
$-color-gray-7: var(--wot-color-gray-7, #646566) !default;
|
||||||
|
$-color-gray-8: var(--wot-color-gray-8, #323233) !default;
|
||||||
|
|
||||||
|
$-font-gray-1: var(--wot-font-gray-1, rgba(0, 0, 0, 0.9));
|
||||||
|
$-font-gray-2: var(--wot-font-gray-2, rgba(0, 0, 0, 0.6));
|
||||||
|
$-font-gray-3: var(--wot-font-gray-3, rgba(0, 0, 0, 0.4));
|
||||||
|
$-font-gray-4: var(--wot-font-gray-4, rgba(0, 0, 0, 0.26));
|
||||||
|
|
||||||
|
$-font-white-1: var(--wot-font-white-1, rgba(255, 255, 255, 1));
|
||||||
|
$-font-white-2: var(--wot-font-white-2, rgba(255, 255, 255, 0.55));
|
||||||
|
$-font-white-3: var(--wot-font-white-3, rgba(255, 255, 255, 0.35));
|
||||||
|
$-font-white-4: var(--wot-font-white-4, rgba(255, 255, 255, 0.22));
|
||||||
|
|
||||||
|
/* 文字颜色(默认浅色背景下 */
|
||||||
|
$-color-title: var(--wot-color-title, $-color-black) !default; // 模块标题/重要正文 000
|
||||||
|
$-color-content: var(--wot-color-content, #262626) !default; // 普通正文 262626
|
||||||
|
$-color-secondary: var(--wot-color-secondary, #595959) !default; // 次要信息,注释/补充/正文 595959
|
||||||
|
$-color-aid: var(--wot-color-aid, #8c8c8c) !default; // 辅助文字字号,弱化信息,引导性/不可点文字 8c8c8c
|
||||||
|
$-color-tip: var(--wot-color-tip, #bfbfbf) !default; // 失效、默认提示文字 bfbfbf
|
||||||
|
$-color-border: var(--wot-color-border, #d9d9d9) !default; // 控件边框线 d9d9d9
|
||||||
|
$-color-border-light: var(--wot-color-border-light, #e8e8e8) !default; // 分割线颜色 e8e8e8
|
||||||
|
$-color-bg: var(--wot-color-bg, #f5f5f5) !default; // 背景色、禁用填充色 f5f5f5
|
||||||
|
|
||||||
|
/* 暗黑模式 */
|
||||||
|
$-dark-background: var(--wot-dark-background, #131313) !default;
|
||||||
|
$-dark-background2: var(--wot-dark-background2, #1b1b1b) !default;
|
||||||
|
$-dark-background3: var(--wot-dark-background3, #141414) !default;
|
||||||
|
$-dark-background4: var(--wot-dark-background4, #323233) !default;
|
||||||
|
$-dark-background5: var(--wot-dark-background5, #646566) !default;
|
||||||
|
$-dark-background6: var(--wot-dark-background6, #380e08) !default;
|
||||||
|
$-dark-background7: var(--wot-dark-background7, #707070) !default;
|
||||||
|
$-dark-color: var(--wot-dark-color, $-color-white) !default;
|
||||||
|
$-dark-color2: var(--wot-dark-color2, #f2270c) !default;
|
||||||
|
$-dark-color3: var(--wot-dark-color3, rgba(232, 230, 227, 0.8)) !default;
|
||||||
|
$-dark-color-gray: var(--wot-dark-color-gray, $-color-secondary) !default;
|
||||||
|
$-dark-border-color: var(--wot-dark-border-color, #3a3a3c) !default;
|
||||||
|
|
||||||
|
/* 图形颜色 */
|
||||||
|
$-color-icon: var(--wot-color-icon, #d9d9d9) !default; // icon颜色
|
||||||
|
$-color-icon-active: var(--wot-color-icon-active, #eee) !default; // icon颜色hover
|
||||||
|
$-color-icon-disabled: var(--wot-color-icon-disabled, #a7a7a7) !default; // icon颜色disabled
|
||||||
|
|
||||||
|
/*----------------------------------------- Theme color. end -------------------------------------------*/
|
||||||
|
|
||||||
|
/*-------------------------------- Theme color application size. start --------------------------------*/
|
||||||
|
|
||||||
|
/* 文字字号 */
|
||||||
|
$-fs-big: var(--wot-fs-big, 24px) !default; // 大型标题
|
||||||
|
$-fs-important: var(--wot-fs-important, 19px) !default; // 重要数据
|
||||||
|
$-fs-title: var(--wot-fs-title, 16px) !default; // 标题字号/重要正文字号
|
||||||
|
$-fs-content: var(--wot-fs-content, 14px) !default; // 普通正文
|
||||||
|
$-fs-secondary: var(--wot-fs-secondary, 12px) !default; // 次要信息,注释/补充/正文
|
||||||
|
$-fs-aid: var(--wot-fs-aid, 10px) !default; // 辅助文字字号,弱化信息,引导性/不可点文字
|
||||||
|
|
||||||
|
/* 文字字重 */
|
||||||
|
$-fw-medium: var(--wot-fw-medium, 500) !default; // PingFangSC-Medium
|
||||||
|
$-fw-semibold: var(--wot-fw-semibold, 600) !default; // PingFangSC-Semibold
|
||||||
|
|
||||||
|
/* 尺寸 */
|
||||||
|
$-size-side-padding: var(--wot-size-side-padding, 15px) !default; // 屏幕两边留白
|
||||||
|
$-size-side-padding-small: var(--wot-size-side-padding-small, 6px) !default; // 屏幕两边留白小值
|
||||||
|
|
||||||
|
/*-------------------------------- Theme color application size. end --------------------------------*/
|
||||||
|
|
||||||
|
/* component var */
|
||||||
|
|
||||||
|
/* action-sheet */
|
||||||
|
$-action-sheet-weight: var(--wot-action-sheet-weight, 500) !default; // 面板字重
|
||||||
|
$-action-sheet-radius: var(--wot-action-sheet-radius, 16px) !default; // 面板圆角大小
|
||||||
|
$-action-sheet-loading-size: var(--wot-action-sheet-loading-size, 20px) !default; // loading动画尺寸
|
||||||
|
$-action-sheet-action-height: var(--wot-action-sheet-action-height, 48px) !default; // 单条菜单高度
|
||||||
|
$-action-sheet-color: var(--wot-action-sheet-color, rgba(0, 0, 0, 0.85)) !default; // 选项名称颜色
|
||||||
|
$-action-sheet-fs: var(--wot-action-sheet-fs, $-fs-title) !default; // 选项名称字号
|
||||||
|
$-action-sheet-active-color: var(--wot-action-sheet-active-color, $-color-bg) !default; // 点击高亮颜色
|
||||||
|
$-action-sheet-subname-fs: var(--wot-action-sheet-subname-fs, $-fs-secondary) !default; // 描述信息字号
|
||||||
|
$-action-sheet-subname-color: var(--wot-action-sheet-subname-color, rgba(0, 0, 0, 0.45)) !default; // 描述信息颜色
|
||||||
|
$-action-sheet-disabled-color: var(--wot-action-sheet-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 禁用颜色
|
||||||
|
$-action-sheet-bg: var(--wot-action-sheet-bg, $-color-white) !default; // 菜单容器颜色(取消按钮上方的颜色)
|
||||||
|
$-action-sheet-title-height: var(--wot-action-sheet-title-height, 64px) !default; // 标题高度
|
||||||
|
$-action-sheet-title-fs: var(--wot-action-sheet-title-fs, $-fs-title) !default; // 标题字号
|
||||||
|
$-action-sheet-close-fs: var(--wot-action-sheet-close-fs, $-fs-title) !default; // 关闭按钮大小
|
||||||
|
$-action-sheet-close-color: var(--wot-action-sheet-close-color, rgba(0, 0, 0, 0.65)) !default; // 关闭按钮颜色
|
||||||
|
$-action-sheet-close-top: var(--wot-action-sheet-close-top, 25px) !default; // 关闭按钮距离标题顶部距离
|
||||||
|
$-action-sheet-close-right: var(--wot-action-sheet-close-right, 15px) !default; // 关闭按钮距离标题右侧距离
|
||||||
|
$-action-sheet-cancel-color: var(--wot-action-sheet-cancel-color, #131415) !default; // 取消按钮颜色
|
||||||
|
$-action-sheet-cancel-height: var(--wot-action-sheet-cancel-height, 44px) !default; // 取消按钮高度
|
||||||
|
$-action-sheet-cancel-bg: var(--wot-action-sheet-cancel-bg, rgba(240, 240, 240, 1)) !default; // 取消按钮背景色
|
||||||
|
$-action-sheet-cancel-radius: var(--wot-action-sheet-cancel-radius, 22px) !default; // 取消按钮圆角大小
|
||||||
|
$-action-sheet-panel-padding: var(--wot-action-sheet-panel-padding, 12px 0 11px) !default; // 自定义面板内边距大小
|
||||||
|
$-action-sheet-panel-img-fs: var(--wot-action-sheet-panel-img-fs, 40px) !default; // 自定义面板图片大小
|
||||||
|
$-action-sheet-panel-img-radius: var(--wot-action-sheet-panel-img-radius, 4px) !default; // 自定义面板图片圆角大小
|
||||||
|
|
||||||
|
/* badge */
|
||||||
|
$-badge-bg: var(--wot-badge-bg, $-color-danger) !default; // 背景填充颜色
|
||||||
|
$-badge-color: var(--wot-badge-color, #fff) !default; // 文字颜色
|
||||||
|
$-badge-fs: var(--wot-badge-fs, 12px) !default; // 文字字号
|
||||||
|
$-badge-padding: var(--wot-badge-padding, 0 5px) !default; // padding
|
||||||
|
$-badge-height: var(--wot-badge-height, 16px) !default; // 高度
|
||||||
|
$-badge-primary: var(--wot-badge-primary, $-color-theme) !default;
|
||||||
|
$-badge-success: var(--wot-badge-success, $-color-success) !default;
|
||||||
|
$-badge-warning: var(--wot-badge-warning, $-color-warning) !default;
|
||||||
|
$-badge-danger: var(--wot-badge-danger, $-color-danger) !default;
|
||||||
|
$-badge-info: var(--wot-badge-info, $-color-info) !default;
|
||||||
|
$-badge-dot-size: var(--wot-badge-dot-size, 6px) !default; // dot 类型大小
|
||||||
|
$-badge-border: var(--wot-badge-border, 2px solid $-badge-color) !default; // 边框样式
|
||||||
|
|
||||||
|
/* button */
|
||||||
|
$-button-disabled-opacity: var(--wot-button-disabled-opacity, 0.6) !default; // button禁用透明度
|
||||||
|
$-button-small-height: var(--wot-button-small-height, 28px) !default; // 小型按钮高度
|
||||||
|
$-button-small-padding: var(--wot-button-small-padding, 0 12px) !default; // 小型按钮padding
|
||||||
|
$-button-small-fs: var(--wot-button-small-fs, $-fs-secondary) !default; // 小型按钮字号
|
||||||
|
$-button-small-radius: var(--wot-button-small-radius, 2px) !default; // 小型按钮圆角大小
|
||||||
|
$-button-small-loading: var(--wot-button-small-loading, 14px) !default; // 小型按钮loading图标大小
|
||||||
|
$-button-medium-height: var(--wot-button-medium-height, 36px) !default; // 中型按钮高度
|
||||||
|
$-button-medium-padding: var(--wot-button-medium-padding, 0 16px) !default; // 中型按钮padding
|
||||||
|
$-button-medium-fs: var(--wot-button-medium-fs, $-fs-content) !default; // 中型按钮字号
|
||||||
|
$-button-medium-radius: var(--wot-button-medium-radius, 4px) !default; // 中型按钮圆角大小
|
||||||
|
$-button-medium-loading: var(--wot-button-medium-loading, 18px) !default; // 中型按钮loading图标大小
|
||||||
|
$-button-medium-box-shadow-size: var(--wot-button-medium-box-shadow-size, 0px 2px 4px 0px) !default; // 中尺寸阴影尺寸
|
||||||
|
$-button-large-height: var(--wot-button-large-height, 44px) !default; // 大型按钮高度
|
||||||
|
$-button-large-padding: var(--wot-button-large-padding, 0 36px) !default; // 大型按钮padding
|
||||||
|
$-button-large-fs: var(--wot-button-large-fs, $-fs-title) !default; // 大型按钮字号
|
||||||
|
$-button-large-radius: var(--wot-button-large-radius, 8px) !default; // 大型按钮圆角大小
|
||||||
|
$-button-large-loading: var(--wot-button-large-loading, 24px) !default; // 大小按钮loading图标大小
|
||||||
|
$-button-large-box-shadow-size: var(--wot-button-large-box-shadow-size, 0px 4px 8px 0px) !default; // 大尺寸阴影尺寸
|
||||||
|
$-button-icon-fs: var(--wot-button-icon-fs, 18px) !default; // 带图标的按钮的图标大小
|
||||||
|
$-button-icon-size: var(--wot-button-icon-size, 40px) !default; // icon 类型按钮尺寸
|
||||||
|
$-button-icon-color: var(--wot-button-icon-color, rgba(0, 0, 0, 0.65)) !default; // icon 类型按钮颜色
|
||||||
|
$-button-icon-disabled-color: var(--wot-button-icon-disabled-color, $-color-icon-disabled) !default; // icon 类型按钮禁用颜色
|
||||||
|
$-button-normal-color: var(--wot-button-normal-color, $-color-title) !default; // 文字颜色
|
||||||
|
$-button-normal-disabled-color: var(--wot-button-normal-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 默认按钮禁用文字色
|
||||||
|
$-button-plain-bg-color: var(--wot-button-plain-bg-color, $-color-white) !default; // 幽灵按钮背景色
|
||||||
|
$-button-primary-color: var(--wot-button-primary-color, $-color-white) !default; // 主要按钮颜色
|
||||||
|
$-button-primary-bg-color: var(--wot-button-primary-bg-color, $-color-theme) !default; // 主要按钮背景颜色
|
||||||
|
$-button-success-color: var(--wot-button-success-color, $-color-white) !default; // 成功按钮文字颜色
|
||||||
|
$-button-success-bg-color: var(--wot-button-success-bg-color, $-color-success) !default; // 成功按钮颜色
|
||||||
|
$-button-info-color: var(--wot-button-info-color, $-color-title) !default; // 信息按钮颜色
|
||||||
|
$-button-info-bg-color: var(--wot-button-info-bg-color, #f0f0f0) !default; // 信息按钮背景颜色
|
||||||
|
$-button-info-plain-border-color: var(--wot-button-info-plain-border-color, rgba(0, 0, 0, 0.45)) !default; // 信息按钮禁用颜色
|
||||||
|
$-button-info-plain-normal-color: var(--wot-button-info-plain-normal-color, rgba(0, 0, 0, 0.85)) !default; // 信息幽灵按钮默认颜色
|
||||||
|
$-button-warning-color: var(--wot-button-warning-color, $-color-white) !default; // 警告按钮字体颜色
|
||||||
|
$-button-warning-bg-color: var(--wot-button-warning-bg-color, $-color-warning) !default; // 警告按钮背景颜色
|
||||||
|
$-button-error-color: var(--wot-button-error-color, $-color-white) !default; // 错误按钮颜色
|
||||||
|
$-button-error-bg-color: var(--wot-button-error-bg-color, $-color-danger) !default; // 错误按钮背景颜色
|
||||||
|
$-button-text-hover-opacity: var(--wot-button-text-hover-opacity, 0.7) !default; // 文字button激活时透明度
|
||||||
|
|
||||||
|
/* cell */
|
||||||
|
$-cell-padding: var(--wot-cell-padding, $-size-side-padding) !default; // cell 左右padding距离
|
||||||
|
$-cell-line-height: var(--wot-cell-line-height, 24px) !default; // 行高
|
||||||
|
|
||||||
|
$-cell-group-title-fs: var(--wot-cell-group-title-fs, $-fs-title) !default; // 组标题字号
|
||||||
|
$-cell-group-padding: var(--wot-cell-group-padding, 13px $-cell-padding) !default; // 组padding
|
||||||
|
$-cell-group-title-color: var(--wot-cell-group-title-color, rgba(0, 0, 0, 0.85)) !default; // 组标题文字颜色
|
||||||
|
$-cell-group-value-fs: var(--wot-cell-group-value-fs, $-fs-content) !default; // 组值字号
|
||||||
|
$-cell-group-value-color: var(--wot-cell-group-value-color, $-color-content) !default; // 组值文字颜色
|
||||||
|
|
||||||
|
$-cell-wrapper-padding: var(--wot-cell-wrapper-padding, 10px) !default; // cell 容器padding
|
||||||
|
$-cell-wrapper-padding-large: var(--wot-cell-wrapper-padding-large, 12px) !default; // large类型cell容器padding
|
||||||
|
|
||||||
|
$-cell-wrapper-padding-with-label: var(--wot-cell-wrapper-padding-with-label, 16px) !default; // cell 容器上下padding(有label情况下)
|
||||||
|
$-cell-icon-right: var(--wot-cell-icon-right, 4px) !default; // 图标距离右边缘
|
||||||
|
$-cell-icon-size: var(--wot-cell-icon-size, 16px) !default; // 图标大小
|
||||||
|
$-cell-title-fs: var(--wot-cell-title-fs, 14px) !default; // 标题字号
|
||||||
|
$-cell-title-color: var(--wot-cell-title-color, rgba(0, 0, 0, 0.85)) !default; // 标题文字颜色
|
||||||
|
$-cell-label-fs: var(--wot-cell-label-fs, 12px) !default; // 描述信息字号
|
||||||
|
$-cell-label-color: var(--wot-cell-label-color, rgba(0, 0, 0, 0.45)) !default; // 描述信息文字颜色
|
||||||
|
$-cell-value-fs: var(--wot-cell-value-fs, 14px) !default; // 右侧内容字号
|
||||||
|
$-cell-value-fs-large: var(--wot-cell-value-fs-large, 16px) !default; // 大尺寸右侧内容字号
|
||||||
|
$-cell-value-color: var(--wot-cell-value-color, rgba(0, 0, 0, 0.85)) !default; // 右侧内容文字颜色
|
||||||
|
$-cell-arrow-size: var(--wot-cell-arrow-size, 18px) !default; // 右箭头大小
|
||||||
|
$-cell-arrow-color: var(--wot-cell-arrow-color, rgba(0, 0, 0, 0.25)) !default; // 右箭头颜色
|
||||||
|
$-cell-clear-color: var(--wot-cell-clear-color, #585858) !default; // 清空按钮颜色
|
||||||
|
$-cell-tap-bg: var(--wot-cell-tap-bg, rgba(0, 0, 0, 0.06)) !default; // 点击态背景色
|
||||||
|
|
||||||
|
$-cell-title-fs-large: var(--wot-cell-title-fs-large, 16px) !default; // 大尺寸标题字号
|
||||||
|
$-cell-label-fs-large: var(--wot-cell-label-fs-large, 14px) !default; // 描述信息字号
|
||||||
|
$-cell-icon-size-large: var(--wot-cell-icon-size-large, 18px) !default; // 图标大小
|
||||||
|
|
||||||
|
$-cell-required-color: var(--wot-cell-required-color, $-color-danger) !default; // 要求必填*颜色
|
||||||
|
$-cell-required-size: var(--wot-cell-required-size, 18px) !default; // 必填*字号
|
||||||
|
$-cell-required-margin: var(--wot-cell-required-margin, 4px) !default; // 必填*间距
|
||||||
|
$-cell-vertical-top: var(--wot-cell-vertical-top, 16px) !default; // 表单类型-上下结构的间距
|
||||||
|
|
||||||
|
/* calendar */
|
||||||
|
$-calendar-fs: var(--wot-calendar-fs, 16px) !default;
|
||||||
|
$-calendar-panel-padding: var(--wot-calendar-panel-padding, 0 12px) !default;
|
||||||
|
$-calendar-panel-title-fs: var(--wot-calendar-panel-title-fs, 14px) !default;
|
||||||
|
$-calendar-panel-title-color: var(--wot-calendar-panel-title-color, rgba(0, 0, 0, 0.85)) !default;
|
||||||
|
$-calendar-week-color: var(--wot-calendar-week-color, rgba(0, 0, 0, 0.85)) !default;
|
||||||
|
$-calendar-week-height: var(--wot-calendar-week-height, 36px) !default;
|
||||||
|
$-calendar-week-fs: var(--wot-calendar-week-fs, 12px) !default;
|
||||||
|
$-calendar-day-fs: var(--wot-calendar-day-fs, 16px) !default;
|
||||||
|
$-calendar-day-color: var(--wot-calendar-day-color, rgba(0, 0, 0, 0.85)) !default;
|
||||||
|
$-calendar-day-fw: var(--wot-calendar-day-fw, 500) !default;
|
||||||
|
$-calendar-day-height: var(--wot-calendar-day-height, 64px) !default;
|
||||||
|
$-calendar-month-width: var(--wot-calendar-month-width, 50px) !default;
|
||||||
|
$-calendar-active-color: var(--wot-calendar-active-color, $-color-theme) !default;
|
||||||
|
$-calendar-selected-color: var(--wot-calendar-selected-color, $-color-white) !default;
|
||||||
|
$-calendar-disabled-color: var(--wot-calendar-disabled-color, rgba(0, 0, 0, 0.25)) !default;
|
||||||
|
$-calendar-range-color: var(--wot-calendar-range-color, rgba(#4d80f0, 0.09)) !default;
|
||||||
|
$-calendar-active-border: var(--wot-calendar-active-border, 8px) !default;
|
||||||
|
$-calendar-info-fs: var(--wot-calendar-info-fs, 10px) !default;
|
||||||
|
$-calendar-item-margin-bottom: var(--wot-calendar-item-margin-bottom, 4px) !default;
|
||||||
|
|
||||||
|
|
||||||
|
/* checkbox */
|
||||||
|
$-checkbox-margin: var(--wot-checkbox-margin, 10px) !default; // 多个复选框距离
|
||||||
|
$-checkbox-bg: var(--wot-checkbox-bg, $-color-white) !default; // 多个复选框距离
|
||||||
|
$-checkbox-label-margin: var(--wot-checkbox-label-margin, 9px) !default; // 右侧文字与左侧图标距离
|
||||||
|
$-checkbox-size: var(--wot-checkbox-size, 16px) !default; // 左侧图标尺寸
|
||||||
|
$-checkbox-icon-size: var(--wot-checkbox-icon-size, 14px) !default; // 左侧图标尺寸
|
||||||
|
$-checkbox-border-color: var(--wot-checkbox-border-color, #dcdcdc) !default; // 左侧图标边框颜色
|
||||||
|
$-checkbox-check-color: var(--wot-checkbox-check-color, $-color-white) !default; // 左侧图标边框颜色
|
||||||
|
$-checkbox-label-fs: var(--wot-checkbox-label-fs, 14px) !default; // 右侧文字字号
|
||||||
|
$-checkbox-label-color: var(--wot-checkbox-label-color, rgba(0, 0, 0, 0.85)) !default; // 右侧文字颜色
|
||||||
|
$-checkbox-checked-color: var(--wot-checkbox-checked-color, $-color-theme) !default; // 选中颜色
|
||||||
|
|
||||||
|
$-checkbox-disabled-color: var(--wot-checkbox-disabled-color, rgba(0, 0, 0, 0.04)) !default; // 禁用背景颜色
|
||||||
|
$-checkbox-disabled-label-color: var(--wot-checkbox-disabled-label-color, rgba(0, 0, 0, 0.25)) !default; // 禁用文字颜色
|
||||||
|
$-checkbox-disabled-check-color: var(--wot-checkbox-disabled-check-color, rgba(0, 0, 0, 0.15)) !default; // 禁用图标颜色
|
||||||
|
$-checkbox-disabled-check-bg: var(--wot-checkbox-disabled-check-bg, rgba(0, 0, 0, 0.15)) !default; // 禁用边框背景颜色
|
||||||
|
$-checkbox-square-radius: var(--wot-checkbox-square-radius, 4px) !default; // 方型圆角大小
|
||||||
|
|
||||||
|
$-checkbox-large-size: var(--wot-checkbox-large-size, 18px) !default; // 左侧图标尺寸
|
||||||
|
$-checkbox-large-label-fs: var(--wot-checkbox-large-label-fs, 16px) !default; // 右侧文字字号
|
||||||
|
|
||||||
|
$-checkbox-button-height: var(--wot-checkbox-button-height, 32px) !default; // 按钮模式复选框高
|
||||||
|
$-checkbox-button-min-width: var(--wot-checkbox-button-min-width, 78px) !default; // 按钮模式最小宽
|
||||||
|
$-checkbox-button-radius: var(--wot-checkbox-button-radius, 16px) !default; // 按钮圆角大小
|
||||||
|
$-checkbox-button-bg: var(--wot-checkbox-button-bg, rgba(0, 0, 0, 0.04)) !default; // 按钮模式背景颜色
|
||||||
|
$-checkbox-button-font-size: var(--wot-checkbox-button-font-size, 14px) !default; // 按钮模式字号
|
||||||
|
$-checkbox-button-border: var(--wot-checkbox-button-border, #f5f5f5) !default; // 按钮边框颜色
|
||||||
|
$-checkbox-button-disabled-border: var(--wot-checkbox-button-disabled-border, rgba(0, 0, 0, 0.15)) !default; // 按钮禁用边框颜色
|
||||||
|
|
||||||
|
/* collapse */
|
||||||
|
$-collapse-side-padding: var(--wot-collapse-side-padding, $-size-side-padding) !default; // 左右间距
|
||||||
|
$-collapse-body-padding: var(--wot-collapse-body-padding, 14px $-size-side-padding) !default; // body padding
|
||||||
|
$-collapse-header-padding: var(--wot-collapse-header-padding, 13px $-size-side-padding) !default; // 头部padding
|
||||||
|
$-collapse-title-color: var(--wot-collapse-title-color, rgba(0, 0, 0, 0.85)) !default; // 标题颜色
|
||||||
|
$-collapse-title-fs: var(--wot-collapse-title-fs, 16px) !default; // 标题字号
|
||||||
|
$-collapse-arrow-size: var(--wot-collapse-arrow-size, 18px) !default; // 箭头大小
|
||||||
|
$-collapse-arrow-color: var(--wot-collapse-arrow-color, #d8d8d8) !default; // 箭头颜色
|
||||||
|
$-collapse-body-fs: var(--wot-collapse-body-fs, 14px) !default; // 内容字号
|
||||||
|
$-collapse-body-color: var(--wot-collapse-body-color, rgba(0, 0, 0, 0.65)) !default; // 内容颜色
|
||||||
|
$-collapse-disabled-color: var(--wot-collapse-disabled-color, rgba(0, 0, 0, 0.15)) !default; // 禁用颜色
|
||||||
|
$-collapse-retract-fs: var(--wot-collapse-retract-fs, 14px) !default; // 更多 字号
|
||||||
|
$-collapse-more-color: var(--wot-collapse-more-color, $-color-theme) !default; // 更多 颜色
|
||||||
|
|
||||||
|
/* divider */
|
||||||
|
$-divider-padding: var(--wot-divider-padding, 0 $-size-side-padding) !default; // 两边间距
|
||||||
|
$-divider-margin: var(--wot-divider-margin, 16px 0) !default; // 上下间距
|
||||||
|
$-divider-color: var(--wot-divider-color, rgba(0, 0, 0, 0.45)) !default; // 字体颜色
|
||||||
|
$-divider-line-color: var(--wot-divider-line-color, currentColor) !default; // 线条颜色
|
||||||
|
$-divider-line-height: var(--wot-divider-line-height, 1px) !default; // 线条高度
|
||||||
|
$-divider-fs: var(--wot-divider-fs, 14px) !default; // 字体大小
|
||||||
|
$-divider-content-left-width: var(--wot-divider-content-left-width, 10%) !default; // 左侧内容宽度
|
||||||
|
$-divider-content-left-margin: var(--wot-divider-content-left-margin, 12px) !default; // 左侧内容距离线距离
|
||||||
|
$-divider-content-right-margin: var(--wot-divider-content-right-margin, 12px) !default; // 右侧内容距离线距离
|
||||||
|
$-divider-content-right-width: var(--wot-divider-content-right-width, 10%) !default; // 右侧内容宽度
|
||||||
|
$-divider-vertical-height: var(--wot-divider-vertical-height, 16px) !default; // 垂直分割线高度
|
||||||
|
$-divider-vertical-content-margin: var(--wot-divider-vertical-content-margin, 0 8px) !default; // 垂直分割线内容间距
|
||||||
|
$-divider-vertical-line-width: var(--wot-divider-vertical-line-width, 1px) !default; // 线条高度
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* drop-menu */
|
||||||
|
$-drop-menu-height: var(--wot-drop-menu-height, 48px) !default; // 展示选中项的高度
|
||||||
|
$-drop-menu-color: var(--wot-drop-menu-color, $-color-content) !default; // 展示选中项的颜色
|
||||||
|
$-drop-menu-fs: var(--wot-drop-menu-fs, $-fs-content) !default; // 展示选中项的字号
|
||||||
|
$-drop-menu-arrow-fs: var(--wot-drop-menu-arrow-fs, $-fs-secondary) !default; // 箭头图标大小
|
||||||
|
|
||||||
|
$-drop-menu-side-padding: var(--wot-drop-menu-side-padding, $-size-side-padding) !default; // 两边留白间距
|
||||||
|
$-drop-menu-disabled-color: var(--wot-drop-menu-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 禁用颜色
|
||||||
|
$-drop-menu-item-height: var(--wot-drop-menu-item-height, 48px) !default; // 选项高度
|
||||||
|
$-drop-menu-item-color: var(--wot-drop-menu-item-color, $-color-content) !default; // 选项颜色
|
||||||
|
$-drop-menu-item-fs: var(--wot-drop-menu-item-fs, $-fs-content) !default; // 选项字号
|
||||||
|
$-drop-menu-item-color-active: var(--wot-drop-menu-item-color-active, $-color-theme) !default; // 选中颜色
|
||||||
|
$-drop-menu-item-color-tip: var(--wot-drop-menu-item-color-tip, rgba(0, 0, 0, 0.45)) !default; // 提示文字颜色
|
||||||
|
$-drop-menu-item-fs-tip: var(--wot-drop-menu-item-fs-tip, $-fs-secondary) !default; // 提示文字字号
|
||||||
|
$-drop-menu-option-check-size: var(--wot-drop-menu-option-check-size, 20px) !default; // check 图标大小
|
||||||
|
$-drop-menu-line-color: var(--wot-drop-menu-line-color, $-color-theme) !default; // 下划线颜色
|
||||||
|
$-drop-menu-line-height: var(--wot-drop-menu-line-height, 3px) !default; // 下划线高度
|
||||||
|
|
||||||
|
/* input-number */
|
||||||
|
$-input-number-color: var(--wot-input-number-color, #262626) !default; // 文字颜色
|
||||||
|
$-input-number-border-color: var(--wot-input-number-border-color, #e8e8e8) !default; // 边框颜色
|
||||||
|
$-input-number-disabled-color: var(--wot-input-number-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 禁用颜色
|
||||||
|
$-input-number-height: var(--wot-input-number-height, 24px) !default; // 加减号按钮高度
|
||||||
|
$-input-number-btn-width: var(--wot-input-number-btn-width, 26px) !default; // 加减号按钮宽度
|
||||||
|
$-input-number-input-width: var(--wot-input-number-input-width, 36px) !default; // 输入框宽度
|
||||||
|
$-input-number-radius: var(--wot-input-number-radius, 4px) !default; // 加减号按钮圆角大小
|
||||||
|
$-input-number-fs: var(--wot-input-number-fs, 12px) !default; // 输入框字号
|
||||||
|
$-input-number-icon-size: var(--wot-input-number-icon-size, 14px) !default; // 加减号图标大小
|
||||||
|
$-input-number-icon-color: var(--wot-input-number-icon-color, rgba(0, 0, 0, 0.65)) !default; // icon颜色
|
||||||
|
|
||||||
|
/* input */
|
||||||
|
$-input-padding: var(--wot-input-padding, $-size-side-padding) !default; // input 左右padding距离
|
||||||
|
$-input-border-color: var(--wot-input-border-color, #dadada) !default; // 无label边框颜色
|
||||||
|
$-input-not-empty-border-color: var(--wot-input-not-empty-border-color, #262626) !default; // 输入框有值时 无label边框颜色
|
||||||
|
$-input-fs: var(--wot-input-fs, $-cell-title-fs) !default; // 字号
|
||||||
|
$-input-fs-large: var(--wot-input-fs-large, $-cell-title-fs-large) !default; // 大尺寸字号
|
||||||
|
$-input-icon-margin: var(--wot-input-icon-margin, 8px) !default; // 图标距离
|
||||||
|
$-input-color: var(--wot-input-color, #262626) !default; // 文字颜色
|
||||||
|
$-input-placeholder-color: var(--wot-input-placeholder-color, #bfbfbf) !default; // 占位符颜色
|
||||||
|
$-input-disabled-color: var(--wot-input-disabled-color, #d9d9d9) !default; // 输入框禁用颜色
|
||||||
|
$-input-error-color: var(--wot-input-error-color, $-color-danger) !default; // 输入框错误颜色
|
||||||
|
$-input-icon-color: var(--wot-input-icon-color, #bfbfbf) !default; // 图标颜色
|
||||||
|
$-input-clear-color: var(--wot-input-clear-color, #585858) !default; // 关闭按钮颜色
|
||||||
|
$-input-count-color: var(--wot-input-count-color, #bfbfbf) !default; // 计数文字颜色
|
||||||
|
$-input-count-current-color: var(--wot-input-count-current-color, #262626) !default; // 当前长度颜色
|
||||||
|
$-input-bg: var(--wot-input-bg, $-color-white) !default; // 默认背景颜色
|
||||||
|
|
||||||
|
$-input-cell-bg: var(--wot-input-cell-bg, $-color-white) !default; // cell 类型背景色
|
||||||
|
$-input-cell-border-color: var(--wot-input-cell-border-color, $-color-border-light) !default; // cell 类型边框颜色
|
||||||
|
$-input-cell-padding: var(--wot-input-cell-padding, 10px) !default; // cell 容器padding
|
||||||
|
$-input-cell-padding-large: var(--wot-input-cell-padding-large, 12px) !default; // large类型cell容器padding
|
||||||
|
$-input-cell-height: var(--wot-input-cell-height, 24px) !default; // cell 高度
|
||||||
|
$-input-cell-label-width: var(--wot-input-cell-label-width, 33%) !default; // cell 下 label 的宽度
|
||||||
|
$-input-inner-height: var(--wot-input-inner-height, 34px) !default; // 非cell和textarea下的高度
|
||||||
|
$-input-inner-height-no-border: var(--wot-input-inner-height-no-border, 24px) !default; // 无边框下的高度
|
||||||
|
$-input-count-fs: var(--wot-input-count-fs, 14px) !default; // 计数字号
|
||||||
|
$-input-count-fs-large: var(--wot-input-count-fs-large, 14px) !default; // 大尺寸计数字号
|
||||||
|
$-input-icon-size: var(--wot-input-icon-size, 16px) !default; // 图标大小
|
||||||
|
$-input-icon-size-large: var(--wot-input-icon-size-large, 18px) !default; // 大尺寸图标大小
|
||||||
|
|
||||||
|
/* textarea */
|
||||||
|
$-textarea-padding: var(--wot-textarea-padding, $-size-side-padding) !default; // textarea 左右padding距离
|
||||||
|
$-textarea-border-color: var(--wot-textarea-border-color, #dadada) !default; // 无label边框颜色
|
||||||
|
$-textarea-not-empty-border-color: var(--wot-textarea-not-empty-border-color, #262626) !default; // 输入框有值时 无label边框颜色
|
||||||
|
$-textarea-fs: var(--wot-textarea-fs, $-cell-title-fs) !default; // 字号
|
||||||
|
$-textarea-fs-large: var(--wot-textarea-fs-large, $-cell-title-fs-large) !default; // 大尺寸字号
|
||||||
|
$-textarea-icon-margin: var(--wot-textarea-icon-margin, 8px) !default; // 图标距离
|
||||||
|
$-textarea-color: var(--wot-textarea-color, #262626) !default; // 文字颜色
|
||||||
|
$-textarea-icon-color: var(--wot-textarea-icon-color, #bfbfbf) !default; // 图标颜色
|
||||||
|
$-textarea-clear-color: var(--wot-textarea-clear-color, #585858) !default; // 关闭按钮颜色
|
||||||
|
$-textarea-count-color: var(--wot-textarea-count-color, #bfbfbf) !default; // 计数文字颜色
|
||||||
|
$-textarea-count-current-color: var(--wot-textarea-count-current-color, #262626) !default; // 当前长度颜色
|
||||||
|
$-textarea-bg: var(--wot-textarea-bg, $-color-white) !default; // 默认背景颜色
|
||||||
|
$-textarea-cell-border-color: var(--wot-textarea-cell-border-color, $-color-border-light) !default; // cell 类型边框颜色
|
||||||
|
$-textarea-cell-padding: var(--wot-textarea-cell-padding, 10px) !default; // cell 容器padding
|
||||||
|
$-textarea-cell-padding-large: var(--wot-textarea-cell-padding-large, 12px) !default; // large类型cell容器padding
|
||||||
|
$-textarea-cell-height: var(--wot-textarea-cell-height, 24px) !default; // cell 高度
|
||||||
|
$-textarea-count-fs: var(--wot-textarea-count-fs, 14px) !default; // 计数字号
|
||||||
|
$-textarea-count-fs-large: var(--wot-textarea-count-fs-large, 14px) !default; // 大尺寸计数字号
|
||||||
|
$-textarea-icon-size: var(--wot-textarea-icon-size, 16px) !default; // 图标大小
|
||||||
|
$-textarea-icon-size-large: var(--wot-textarea-icon-size-large, 18px) !default; // 大尺寸图标大小
|
||||||
|
|
||||||
|
/* loadmore */
|
||||||
|
$-loadmore-height: var(--wot-loadmore-height, 48px) !default; // 高度
|
||||||
|
$-loadmore-color: var(--wot-loadmore-color, rgba(0, 0, 0, 0.45)) !default; // 颜色
|
||||||
|
$-loadmore-fs: var(--wot-loadmore-fs, 14px) !default; // 字号
|
||||||
|
$-loadmore-error-color: var(--wot-loadmore-error-color, $-color-theme) !default; // 点击重试颜色
|
||||||
|
$-loadmore-refresh-fs: var(--wot-loadmore-refresh-fs, $-fs-title) !default; // refresh图标字号
|
||||||
|
$-loadmore-loading-size: var(--wot-loadmore-loading-size, $-fs-title) !default; // loading尺寸
|
||||||
|
|
||||||
|
/* message-box */
|
||||||
|
$-message-box-width: var(--wot-message-box-width, 300px) !default; // 宽度
|
||||||
|
$-message-box-bg: var(--wot-message-box-bg, $-color-white) !default; // 默认背景颜色
|
||||||
|
$-message-box-radius: var(--wot-message-box-radius, 16px) !default; // 圆角大小
|
||||||
|
$-message-box-padding: var(--wot-message-box-padding, 25px 24px 0) !default; // 主体内容padding
|
||||||
|
$-message-box-title-fs: var(--wot-message-box-title-fs, 16px) !default; // 标题字号
|
||||||
|
$-message-box-title-color: var(--wot-message-box-title-color, rgba(0, 0, 0, 0.85)) !default; // 标题颜色
|
||||||
|
$-message-box-content-fs: var(--wot-message-box-content-fs, 14px) !default; // 内容字号
|
||||||
|
$-message-box-content-color: var(--wot-message-box-content-color, #666666) !default; // 内容颜色
|
||||||
|
$-message-box-content-max-height: var(--wot-message-box-content-max-height, 264px) !default; // 内容最大高度
|
||||||
|
$-message-box-content-scrollbar-width: var(--wot-message-box-content-scrollbar-width, 4px) !default; // 内容滚动条宽度
|
||||||
|
$-message-box-content-scrollbar-color: var(--wot-message-box-content-scrollbar-color, rgba(0, 0, 0, 0.1)) !default; // 内容滚动条颜色
|
||||||
|
$-message-box-input-error-color: var(--wot-message-box-input-error-color, $-input-error-color) !default; // 输入框错误颜色
|
||||||
|
|
||||||
|
/* notice-bar */
|
||||||
|
$-notice-bar-fs: var(--wot-notice-bar-fs, 12px) !default; // 字号
|
||||||
|
$-notice-bar-line-height: var(--wot-notice-bar-line-height, 18px) !default; // 行高
|
||||||
|
$-notice-bar-border-radius: var(--wot-notice-bar-border-radius, 8px) !default; // 圆角
|
||||||
|
$-notice-bar-padding: var(--wot-notice-bar-padding, 9px 20px 9px 15px) !default; // 非换行下的padding
|
||||||
|
$-notice-bar-warning-bg: var(--wot-notice-bar-warning-bg, #fff6c8) !default; // 背景色
|
||||||
|
$-notice-bar-info-bg: var(--wot-notice-bar-info-bg, #f4f9ff) !default; // 背景色
|
||||||
|
$-notice-bar-danger-bg: var(--wot-notice-bar-danger-bg, #feeced) !default; // 背景色
|
||||||
|
$-notice-bar-warning-color: var(--wot-notice-bar-warning-color, $-color-warning) !default; // 文字和图标颜色
|
||||||
|
$-notice-bar-info-color: var(--wot-notice-bar-info-color, $-color-theme) !default; // 文字和图标颜色
|
||||||
|
$-notice-bar-danger-color: var(--wot-notice-bar-danger-color, $-color-danger) !default; // 文字和图标颜色
|
||||||
|
$-notice-bar-prefix-size: var(--wot-notice-bar-prefix-size, 18px) !default; // 图标大小
|
||||||
|
$-notice-bar-close-bg: var(--wot-notice-bar-close-bg, rgba(0, 0, 0, 0.15)) !default; // 右侧关闭按钮背景颜色
|
||||||
|
$-notice-bar-close-size: var(--wot-notice-bar-close-size, 18px) !default; // 右侧关闭按钮背景颜色
|
||||||
|
$-notice-bar-close-color: var(--wot-notice-bar-close-color, $-color-white) !default; // 右侧关闭按钮颜色
|
||||||
|
$-notice-bar-wrap-padding: var(--wot-notice-bar-wrap-padding, 14px $-size-side-padding) !default; // 换行下的padding
|
||||||
|
|
||||||
|
/* pagination */
|
||||||
|
$-pagination-content-padding: var(--wot-pagination-content-padding, 10px 15px) !default;
|
||||||
|
$-pagination-message-padding: var(--wot-pagination-message-padding, 1px 0 16px 0) !default;
|
||||||
|
$-pagination-message-fs: var(--wot-pagination-message-fs, 12px) !default;
|
||||||
|
$-pagination-message-color: var(--wot-pagination-message-color, rgba(0, 0, 0, 0.69)) !default;
|
||||||
|
$-pagination-nav-border: var(--wot-pagination-nav-border, 1px solid rgba(0, 0, 0, 0.45)) !default;
|
||||||
|
$-pagination-nav-border-radius: var(--wot-pagination-nav-border-radius, 16px) !default;
|
||||||
|
$-pagination-nav-fs: var(--wot-pagination-nav-fs, 12px) !default;
|
||||||
|
$-pagination-nav-width: var(--wot-pagination-nav-width, 60px) !default;
|
||||||
|
$-pagination-nav-color: var(--wot-pagination-nav-color, rgba(0, 0, 0, 0.85)) !default;
|
||||||
|
$-pagination-nav-content-fs: var(--wot-pagination-nav-content-fs, 12px) !default;
|
||||||
|
$-pagination-nav-sepatator-padding: var(--wot-pagination-nav-sepatator-padding, 0 4px) !default;
|
||||||
|
$-pagination-nav-current-color: var(--wot-pagination-nav-current-color, $-color-theme) !default;
|
||||||
|
$-pagination-icon-size: var(--wot-pagination-icon-size, $-fs-content) !default;
|
||||||
|
|
||||||
|
/* picker */
|
||||||
|
$-picker-toolbar-height: var(--wot-picker-toolbar-height, 54px) !default; // toolbar 操作条的高度
|
||||||
|
$-picker-action-height: var(--wot-picker-action-height, 16px) !default; // toolbar 操作条的高度
|
||||||
|
$-picker-toolbar-finish-color: var(--wot-picker-toolbar-finish-color, $-color-theme) !default; // toolbar 操作条完成按钮的颜色
|
||||||
|
$-picker-toolbar-cancel-color: var(--wot-picker-toolbar-cancel-color, #666666) !default; // toolbar 操作条的边框颜色
|
||||||
|
$-picker-toolbar-fs: var(--wot-picker-toolbar-fs, $-fs-title) !default; // toolbar 操作条的字号
|
||||||
|
$-picker-toolbar-title-color: var(--wot-picker-toolbar-title-color, rgba(0, 0, 0, 0.85)) !default; // toolbar 操作台的标题颜色
|
||||||
|
$-picker-column-fs: var(--wot-picker-column-fs, 16px) !default; // 选择器选项的字号
|
||||||
|
$-picker-bg: var(--wot-picker-bg, $-color-white) !default; // 选择器选项的字号
|
||||||
|
$-picker-column-active-fs: var(--wot-picker-column-active-fs, 18px) !default; // 选择器选项被选中的字号
|
||||||
|
$-picker-column-color: var(--wot-picker-column-color, rgba(0, 0, 0, 0.85)) !default; // 选择器选项的颜色
|
||||||
|
$-picker-column-height: var(--wot-picker-column-height, 210px) !default; // 列高 滚筒外部的高度
|
||||||
|
$-picker-column-item-height: var(--wot-picker-column-item-height, 35px) !default; // 列高 滚筒外部的高度
|
||||||
|
$-picker-column-select-bg: var(--wot-picker-column-select-bg, #f5f5f5) !default;
|
||||||
|
$-picker-loading-button-color: var(--wot-picker-loading-button-color, rgba(0, 0, 0, 0.25)) !default; // loading 背景颜色
|
||||||
|
$-picker-column-padding: var(--wot-picker-column-padding, 0 $-size-side-padding-small) !default; // 选项内间距
|
||||||
|
|
||||||
|
$-picker-column-disabled-color: var(--wot-picker-column-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 选择器选项禁用的颜色
|
||||||
|
$-picker-mask: var(--wot-picker-mask, linear-gradient(180deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.25)))
|
||||||
|
linear-gradient(0deg, hsla(0, 0%, 100%, 0.9), hsla(0, 0%, 100%, 0.25)) !default; // 上下阴影
|
||||||
|
$-picker-loading-bg: var(--wot-picker-loading-bg, rgba($-color-white, 0.8)) !default; // loading 背景颜色
|
||||||
|
$-picker-region-separator-color: var(--wot-picker-region-separator-color, rgba(0, 0, 0, 0.65)) !default; // 区域选择文字颜色
|
||||||
|
$-picker-cell-arrow-size-large: var(--wot-picker-cell-arrow-size-large, $-cell-icon-size) !default; // cell 类型的大尺寸 右侧icon尺寸
|
||||||
|
|
||||||
|
$-picker-region-color: var(--wot-picker-region-color, rgba(0, 0, 0, 0.45)) !default; // 区域选择文字颜色
|
||||||
|
$-picker-region-bg-active-color: var(--wot-picker-region-bg-active-color, $-color-theme) !default; // 区域选择激活选中背景颜色
|
||||||
|
|
||||||
|
$-picker-region-fs: var(--wot-picker-region-fs, 14px) !default; // 区域选择文字字号
|
||||||
|
|
||||||
|
/* col-picker */
|
||||||
|
$-col-picker-selected-height: var(--wot-col-picker-selected-height, 44px) !default; // 弹框顶部值高度
|
||||||
|
$-col-picker-selected-padding: var(--wot-col-picker-selected-padding, 0 16px) !default; // 弹框顶部值左右间距
|
||||||
|
$-col-picker-selected-fs: var(--wot-col-picker-selected-fs, 14px) !default; // 弹框顶部值字号
|
||||||
|
$-col-picker-selected-color: var(--wot-col-picker-selected-color, rgba(0, 0, 0, 0.85)) !default; // 弹框顶部值文字颜色
|
||||||
|
$-col-picker-selected-fw: var(--wot-col-picker-selected-fw, 700) !default; // 弹框顶部值高亮字重
|
||||||
|
$-col-picker-line-width: var(--wot-col-picker-line-width, 16px) !default; // 弹框顶部值高亮线条宽度
|
||||||
|
$-col-picker-line-height: var(--wot-col-picker-line-height, 3px) !default; // 弹框顶部值高亮线条高度
|
||||||
|
$-col-picker-line-color: var(
|
||||||
|
--wot-col-picker-line-color,
|
||||||
|
linear-gradient(315deg, rgba(81, 124, 240, 1), rgba(118, 158, 245, 1))
|
||||||
|
) !default; // 弹框顶部值高亮线条颜色
|
||||||
|
$-col-picker-line-box-shadow: var(--wot-col-picker-line-box-shadow, 0px 1px 2px 0px rgba(1, 87, 255, 0.2)) !default; // 弹框顶部值高亮线条阴影
|
||||||
|
$-col-picker-list-height: var(--wot-col-picker-list-height, 53vh) !default; // 弹框列表高度
|
||||||
|
$-col-picker-list-padding-bottom: var(--wot-col-picker-list-padding-bottom, 30px) !default; // 弹框列表底部间距
|
||||||
|
$-col-picker-list-color: var(--wot-col-picker-list-color, rgba(0, 0, 0, 0.85)) !default; // 弹框列表文字颜色
|
||||||
|
$-col-picker-list-color-disabled: var(--wot-col-picker-list-color-disabled, rgba(0, 0, 0, 0.15)) !default; // 弹框列表文字禁用颜色
|
||||||
|
$-col-picker-list-color-tip: var(--wot-col-picker-list-color-tip, rgba(0, 0, 0, 0.45)) !default; // 弹框列表提示文字颜色
|
||||||
|
$-col-picker-list-fs: var(--wot-col-picker-list-fs, 14px) !default; // 弹框列表文字字号
|
||||||
|
$-col-picker-list-fs-tip: var(--wot-col-picker-list-fs-tip, 12px) !default; // 弹框列表提示文字字号
|
||||||
|
$-col-picker-list-item-padding: var(--wot-col-picker-list-item-padding, 12px 15px) !default; // 弹框列表选项间距
|
||||||
|
$-col-picker-list-checked-icon-size: var(--wot-col-picker-list-checked-icon-size, 18px) !default; // 弹框列表选中箭头大小
|
||||||
|
$-col-picker-list-color-checked: var(--wot-col-picker-list-color-checked, $-color-theme) !default; // 弹框列表选中选项颜色
|
||||||
|
|
||||||
|
/* overlay */
|
||||||
|
$-overlay-bg: var(--wot-overlay-bg, rgba(0, 0, 0, 0.65)) !default;
|
||||||
|
$-overlay-bg-dark: var(--wot-overlay-bg-dark, rgba(0, 0, 0, 0.75)) !default;
|
||||||
|
|
||||||
|
/* popup */
|
||||||
|
$-popup-close-size: var(--wot-popup-close-size, 24px) !default; // 关闭按钮尺寸
|
||||||
|
$-popup-close-color: var(--wot-popup-close-color, #666) !default; // 关闭按钮颜色
|
||||||
|
|
||||||
|
/* progress */
|
||||||
|
$-progress-padding: var(--wot-progress-padding, 9px 0 8px) !default; // 进度条内边距
|
||||||
|
$-progress-bg: var(--wot-progress-bg, rgba(229, 229, 229, 1)) !default; // 进度条底色
|
||||||
|
$-progress-danger-color: var(--wot-progress-danger-color, $-color-danger) !default; // 进度条danger颜色
|
||||||
|
$-progress-success-color: var(--wot-progress-success-color, $-color-success) !default; // 进度条success进度条颜色
|
||||||
|
$-progress-warning-color: var(--wot-progress-warning-color, $-color-warning) !default; // 进度条warning进度条颜色
|
||||||
|
|
||||||
|
$-progress-color: var(--wot-progress-color, $-color-theme) !default; // 进度条颜色
|
||||||
|
$-progress-height: var(--wot-progress-height, 3px) !default; // 进度条高度
|
||||||
|
$-progress-label-color: var(--wot-progress-label-color, #333) !default; // 文字颜色
|
||||||
|
$-progress-label-fs: var(--wot-progress-label-fs, 14px) !default; // 文字字号
|
||||||
|
$-progress-icon-fs: var(--wot-progress-icon-fs, 18px) !default; // 图标字号
|
||||||
|
|
||||||
|
/* radio */
|
||||||
|
$-radio-margin: var(--wot-radio-margin, $-checkbox-margin) !default; // 多个单选框距离
|
||||||
|
$-radio-label-margin: var(--wot-radio-label-margin, $-checkbox-label-margin) !default; // 右侧文字与左侧图标距离
|
||||||
|
$-radio-size: var(--wot-radio-size, 16px) !default; // 左侧图标尺寸
|
||||||
|
$-radio-bg: var(--wot-radio-bg, $-color-white) !default; // 左侧图标尺寸
|
||||||
|
$-radio-label-fs: var(--wot-radio-label-fs, $-checkbox-label-fs) !default; // 右侧文字字号
|
||||||
|
$-radio-label-color: var(--wot-radio-label-color, $-checkbox-label-color) !default; // 右侧文字颜色
|
||||||
|
$-radio-checked-color: var(--wot-radio-checked-color, $-checkbox-checked-color) !default; // 选中颜色
|
||||||
|
$-radio-disabled-color: var(--wot-radio-disabled-color, $-checkbox-disabled-color) !default; // 禁用颜色
|
||||||
|
$-radio-disabled-label-color: var(--wot-radio-disabled-label-color, $-checkbox-disabled-label-color) !default; // 禁用文字颜色
|
||||||
|
|
||||||
|
$-radio-large-size: var(--wot-radio-large-size, $-checkbox-large-size) !default; // 左侧图标尺寸
|
||||||
|
$-radio-large-label-fs: var(--wot-radio-large-label-fs, $-checkbox-large-label-fs) !default; // 右侧文字字号
|
||||||
|
|
||||||
|
$-radio-button-height: var(--wot-radio-button-height, $-checkbox-button-height) !default; // 按钮模式复选框高
|
||||||
|
$-radio-button-min-width: var(--wot-radio-button-min-width, 60px) !default; // 按钮模式最小宽
|
||||||
|
$-radio-button-max-width: var(--wot-radio-button-max-width, 144px) !default; // 按钮模式最大宽
|
||||||
|
$-radio-button-radius: var(--wot-radio-button-radius, $-checkbox-button-radius) !default; // 按钮圆角大小
|
||||||
|
$-radio-button-bg: var(--wot-radio-button-bg, $-checkbox-button-bg) !default; // 按钮模式背景颜色
|
||||||
|
$-radio-button-fs: var(--wot-radio-button-fs, $-checkbox-button-font-size) !default; // 按钮模式字号
|
||||||
|
$-radio-button-border: var(--wot-radio-button-border, $-checkbox-button-border) !default; // 按钮边框颜色
|
||||||
|
$-radio-button-disabled-border: var(--wot-radio-button-disabled-border, $-checkbox-button-disabled-border) !default; // 按钮禁用边框颜色
|
||||||
|
|
||||||
|
$-radio-dot-size: var(--wot-radio-dot-size, 8px) !default; // 单选dot模式圆点尺寸
|
||||||
|
$-radio-dot-large-size: var(--wot-radio-dot-large-size, 10px) !default; // 单选dot模式大尺寸圆点尺寸
|
||||||
|
$-radio-dot-checked-bg: var(--wot-radio-dot-checked-bg, $-color-theme) !default; // 单选dot模式选中背景色
|
||||||
|
$-radio-dot-checked-border-color: var(--wot-radio-dot-checked-border-color, $-color-theme) !default; // 单选dot模式选中边框色
|
||||||
|
$-radio-dot-border-color: var(--wot-radio-dot-border-color, #dcdcdc) !default; // 单选dot模式边框色
|
||||||
|
$-radio-dot-disabled-border: var(--wot-radio-dot-disabled-border, #d9d9d9) !default; // 单选dot模式禁用边框颜色
|
||||||
|
$-radio-dot-disabled-bg: var(--wot-radio-dot-disabled-bg, #d9d9d9) !default; // 单选dot模式禁用背景颜色
|
||||||
|
|
||||||
|
/* search */
|
||||||
|
$-search-side-padding: var(--wot-search-side-padding, $-size-side-padding) !default; // 左右间距
|
||||||
|
$-search-padding: var(--wot-search-padding, 10px 0 10px $-search-side-padding) !default; // 不包含取消按钮的间距
|
||||||
|
$-search-input-radius: var(--wot-search-input-radius, 15px) !default; // 输入框圆角大小
|
||||||
|
$-search-input-bg: var(--wot-search-input-bg, $-color-bg) !default; // 输入框背景色
|
||||||
|
$-search-input-height: var(--wot-search-input-height, 30px) !default; // 输入框高度
|
||||||
|
$-search-input-padding: var(--wot-search-input-padding, 0 32px 0 42px) !default; // 输入框间距
|
||||||
|
$-search-input-fs: var(--wot-search-input-fs, $-fs-content) !default; // 输入框字号
|
||||||
|
$-search-input-color: var(--wot-search-input-color, #262626) !default; // 输入框文字颜色
|
||||||
|
$-search-icon-color: var(--wot-search-icon-color, $-color-icon) !default; // 图标颜色
|
||||||
|
$-search-icon-size: var(--wot-search-icon-size, 18px) !default; // 图标大小
|
||||||
|
$-search-clear-icon-size: var(--wot-search-clear-icon-size, $-fs-title) !default; // 清除图标大小
|
||||||
|
$-search-placeholder-color: var(--wot-search-placeholder-color, #bfbfbf) !default; // placeholder 颜色
|
||||||
|
$-search-cancel-padding: var(--wot-search-cancel-padding, 0 $-search-side-padding 0 10px) !default; // 取消按钮间距
|
||||||
|
$-search-cancel-fs: var(--wot-search-cancel-fs, $-fs-title) !default; // 取消按钮字号
|
||||||
|
$-search-cancel-color: var(--wot-search-cancel-color, rgba(0, 0, 0, 0.65)) !default; // 取消按钮颜色
|
||||||
|
$-search-light-bg: var(--wot-search-light-bg, $-color-bg) !default; // light 类型的容器背景色
|
||||||
|
|
||||||
|
/* slider */
|
||||||
|
$-slider-fs: var(--wot-slider-fs, $-fs-content) !default; // 字体大小
|
||||||
|
$-slider-handle-radius: var(--wot-slider-handle-radius, 12px) !default; // 滑块半径
|
||||||
|
$-slider-handle-bg: var(--wot-slider-handle-bg, resultColor(139deg, $-color-theme, 'dark' 'light', #ffffff #f7f7f7, 0% 100%)) !default; // 滑块背景
|
||||||
|
$-slider-axie-height: var(--wot-slider-axie-height, 3px) !default; // 滑轴高度
|
||||||
|
$-slider-color: var(--wot-slider-color, #333) !default; // 字体颜色
|
||||||
|
$-slider-axie-bg: var(--wot-slider-axie-bg, #e5e5e5) !default; // 滑轴的默认背景色
|
||||||
|
$-slider-line-color: var(
|
||||||
|
--wot-slider-line-color,
|
||||||
|
resultColor(315deg, $-color-theme, 'dark' 'light', #517cf0 #769ef5, 0% 100%)
|
||||||
|
) !default; // 进度条颜色
|
||||||
|
$-slider-disabled-color: var(--wot-slider-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 禁用状态下字体颜色
|
||||||
|
|
||||||
|
/* sort-button */
|
||||||
|
$-sort-button-fs: var(--wot-sort-button-fs, $-fs-content) !default; // 字号
|
||||||
|
$-sort-button-color: var(--wot-sort-button-color, $-color-content) !default; // 颜色
|
||||||
|
$-sort-button-height: var(--wot-sort-button-height, 48px) !default; // 高度
|
||||||
|
$-sort-button-line-height: var(--wot-sort-button-line-height, 3px) !default; // 下划线高度
|
||||||
|
$-sort-button-line-color: var(--wot-sort-button-line-color, $-color-theme) !default; // 下划线颜色
|
||||||
|
|
||||||
|
/* steps */
|
||||||
|
$-steps-icon-size: var(--wot-steps-icon-size, 22px) !default; // 图标尺寸
|
||||||
|
$-steps-inactive-color: var(--wot-steps-inactive-color, rgba(0, 0, 0, 0.25)) !default; // 等待状态文字颜色
|
||||||
|
$-steps-finished-color: var(--wot-steps-finished-color, $-color-theme) !default; // 完成文字颜色
|
||||||
|
$-steps-icon-text-fs: var(--wot-steps-icon-text-fs, $-fs-content) !default; // 数字图标文字字号
|
||||||
|
$-steps-error-color: var(--wot-steps-error-color, $-color-danger) !default; // 异常颜色
|
||||||
|
$-steps-title-fs: var(--wot-steps-title-fs, $-fs-content) !default; // 标题字号
|
||||||
|
$-steps-title-fw: var(--wot-steps-title-fw, $-fw-medium) !default; // 标题字重
|
||||||
|
$-steps-label-fs: var(--wot-steps-label-fs, $-fs-secondary) !default; // 描述信息字号
|
||||||
|
$-steps-description-color: var(--wot-steps-description-color, rgba(0, 0, 0, 0.45)) !default; // 描述信息颜色
|
||||||
|
$-steps-is-icon-width: var(--wot-steps-is-icon-width, 30px) !default; // 自定义图标的宽度,给左右留白
|
||||||
|
$-steps-line-color: var(--wot-steps-line-color, rgba(0, 0, 0, 0.15)) !default; // 线条颜色
|
||||||
|
$-steps-dot-size: var(--wot-steps-dot-size, 7px) !default; // 点状大小
|
||||||
|
$-steps-dot-active-size: var(--wot-steps-dot-active-size, 9px) !default; // 点状高亮大小
|
||||||
|
|
||||||
|
/* switch */
|
||||||
|
$-switch-size: var(--wot-switch-size, 28px) !default; // switch大小
|
||||||
|
$-switch-width: var(--wot-switch-width, calc(1.8em + 4px)) !default; // 宽度
|
||||||
|
$-switch-height: var(--wot-switch-height, calc(1em + 4px)) !default; // 高度
|
||||||
|
$-switch-circle-size: var(--wot-switch-circle-size, 1em) !default; // 圆点大小
|
||||||
|
$-switch-border-color: var(--wot-switch-border-color, #e5e5e5) !default; // 边框颜色选中状态背景颜色
|
||||||
|
$-switch-active-color: var(--wot-switch-active-color, $-color-theme) !default; // 选中状态背景
|
||||||
|
$-switch-active-shadow-color: var(--wot-switch-active-shadow-color, rgba(0, 83, 162, 0.5)) !default; // 选中状态shadow颜色
|
||||||
|
$-switch-inactive-color: var(--wot-switch-inactive-color, #eaeaea) !default; // 非选中背景颜色
|
||||||
|
$-switch-inactive-shadow-color: var(--wot-switch-inactive-shadow-color, rgba(155, 155, 155, 0.5)) !default; // 非选中状态shadow颜色
|
||||||
|
|
||||||
|
/* tabs */
|
||||||
|
$-tabs-nav-arrow-fs: var(--wot-tabs-nav-arrow-fs, 18px) !default; // 全部Icon字号
|
||||||
|
$-tabs-nav-arrow-open-fs: var(--wot-tabs-nav-arrow-open-fs, 14px) !default; // 展开Icon字号
|
||||||
|
$-tabs-nav-width: var(--wot-tabs-nav-width, 100vw) !default; // tabs 头部切换宽度
|
||||||
|
$-tabs-nav-height: var(--wot-tabs-nav-height, 42px) !default; // 头部切换高度
|
||||||
|
$-tabs-nav-fs: var(--wot-tabs-nav-fs, $-fs-content) !default; // 头部切换文字大小
|
||||||
|
$-tabs-nav-color: var(--wot-tabs-nav-color, rgba(0, 0, 0, 0.85)) !default; // 头部切换文字颜色
|
||||||
|
$-tabs-nav-bg: var(--wot-tabs-nav-bg, $-color-white) !default; // 背景颜色
|
||||||
|
$-tabs-nav-active-color: var(--wot-tabs-nav-active-color, $-color-theme) !default; // 头部高亮颜色
|
||||||
|
$-tabs-nav-disabled-color: var(--wot-tabs-nav-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 头部禁用颜色
|
||||||
|
$-tabs-nav-line-height: var(--wot-tabs-nav-line-height, 3px) !default; // 高亮边框高度
|
||||||
|
$-tabs-nav-line-width: var(--wot-tabs-nav-line-width, 19px) !default; // 高亮边框宽度
|
||||||
|
$-tabs-nav-line-bg-color: var(--wot-tabs-nav-line-bg-color, $-color-theme) !default; // 底部条颜色
|
||||||
|
$-tabs-nav-map-fs: var(--wot-tabs-nav-map-fs, $-fs-content) !default; // map 类型按钮字号
|
||||||
|
$-tabs-nav-map-color: var(--wot-tabs-nav-map-color, rgba(0, 0, 0, 0.85)) !default; // map 类型按钮文字颜色
|
||||||
|
$-tabs-nav-map-arrow-color: var(--wot-tabs-nav-map-arrow-color, rgba(0, 0, 0, 0.65)) !default; // map 类型箭头颜色
|
||||||
|
$-tabs-nav-map-btn-before-bg: var(
|
||||||
|
--wot-tabs-nav-map-btn-before-bg,
|
||||||
|
linear-gradient(270deg, rgba(255, 255, 255, 1) 1%, rgba(255, 255, 255, 0) 100%)
|
||||||
|
) !default; // 左侧map遮罩阴影
|
||||||
|
$-tabs-nav-map-button-back-color: var(--wot-tabs-nav-map-button-back-color, rgba(0, 0, 0, 0.04)) !default; // map 类型按钮边框颜色
|
||||||
|
$-tabs-nav-map-button-radius: var(--wot-tabs-nav-map-button-radius, 16px) !default; // map 类型按钮圆角大小
|
||||||
|
$-tabs-nav-map-modal-bg: var(--wot-tabs-nav-map-modal-bg, $-overlay-bg) !default; // map 类型蒙层背景色
|
||||||
|
|
||||||
|
/* tag */
|
||||||
|
$-tag-fs: var(--wot-tag-fs, $-fs-secondary) !default; // 字号
|
||||||
|
$-tag-color: var(--wot-tag-color, $-color-white) !default; // 字体颜色
|
||||||
|
$-tag-small-fs: var(--wot-tag-small-fs, $-fs-aid) !default; // 小尺寸字号
|
||||||
|
$-tag-info-color: var(--wot-tag-info-color, #585858) !default; // info 颜色
|
||||||
|
$-tag-primary-color: var(--wot-tag-primary-color, $-color-theme) !default; // 主颜色
|
||||||
|
$-tag-danger-color: var(--wot-tag-danger-color, $-color-danger) !default; // danger 颜色
|
||||||
|
$-tag-warning-color: var(--wot-tag-warning-color, $-color-warning) !default; // warning 颜色
|
||||||
|
$-tag-success-color: var(--wot-tag-success-color, $-color-success) !default; // success 颜色
|
||||||
|
$-tag-info-bg: var(--wot-tag-info-bg, resultColor(49deg, $-color-black, 'dark' 'light', #808080 #999999, 0% 100%)) !default; // info 背景颜色
|
||||||
|
$-tag-primary-bg: var(--wot-tag-primary-bg, $-color-theme) !default; // 主背景颜色
|
||||||
|
$-tag-danger-bg: var(--wot-tag-danger-bg, $-color-danger) !default; // danger 背景颜色
|
||||||
|
$-tag-warning-bg: var(--wot-tag-warning-bg, $-color-warning) !default; // warning 背景颜色
|
||||||
|
$-tag-success-bg: var(--wot-tag-success-bg, $-color-success) !default; // success 背景颜色
|
||||||
|
$-tag-round-color: var(--wot-tag-round-color, rgba(102, 102, 102, 1)) !default; // round 字体颜色
|
||||||
|
$-tag-round-border-color: var(--wot-tag-round-border-color, rgba(225, 225, 225, 1)) !default; // round 边框颜色
|
||||||
|
$-tag-round-radius: var(--wot-tag-round-radius, 12px) !default; // round 圆角大小
|
||||||
|
$-tag-mark-radius: var(--wot-tag-mark-radius, 6px 2px 6px 2px) !default; // mark 圆角大小
|
||||||
|
$-tag-close-size: var(--wot-tag-close-size, 14px) !default; // 关闭按钮字号
|
||||||
|
$-tag-close-color: var(--wot-tag-close-color, $-tag-info-color) !default; // 关闭按钮颜色
|
||||||
|
$-tag-close-active-color: var(--wot-tag-close-active-color, rgba(0, 0, 0, 0.45)) !default; // 关闭按钮 active 颜色
|
||||||
|
|
||||||
|
/* toast */
|
||||||
|
$-toast-color: var(--wot-toast-color, $-color-white) !default; // 文字颜色
|
||||||
|
$-toast-padding: var(--wot-toast-padding, 16px 24px) !default; // padding
|
||||||
|
$-toast-max-width: var(--wot-toast-max-width, 300px) !default; // 最大宽度
|
||||||
|
$-toast-radius: var(--wot-toast-radius, 8px) !default; // 圆角大小
|
||||||
|
$-toast-bg: var(--wot-toast-bg, $-overlay-bg) !default; // 背景色
|
||||||
|
$-toast-fs: var(--wot-toast-fs, $-fs-content) !default; // 字号
|
||||||
|
$-toast-line-height: var(--wot-toast-line-height, 20px) !default; // 行高
|
||||||
|
$-toast-with-icon-min-width: var(--wot-toast-with-icon-min-width, 150px) !default; // 有图标的情况下最小宽度
|
||||||
|
$-toast-icon-size: var(--wot-toast-icon-size, 32px) !default; // 图标大小
|
||||||
|
$-toast-icon-margin-right: var(--wot-toast-icon-margin-right, 12px) !default; // 图标右边距
|
||||||
|
$-toast-icon-margin-bottom: var(--wot-toast-icon-margin-bottom, 12px) !default; // 图标下边距
|
||||||
|
$-toast-loading-padding: var(--wot-toast-loading-padding, 10px) !default; // loading状态下的padding
|
||||||
|
$-toast-loading-margin-bottom: var(--wot-toast-loading-margin-bottom, 16px) !default; // loading动画的margin-bottom
|
||||||
|
$-toast-box-shadow: var(--wot-toast-box-shadow, 0px 6px 16px 0px rgba(0, 0, 0, 0.08)) !default; // 外部阴影
|
||||||
|
|
||||||
|
/* loading */
|
||||||
|
$-loading-size: var(--wot-loading-size, 32px) !default; // loading 大小
|
||||||
|
|
||||||
|
/* tooltip */
|
||||||
|
$-tooltip-bg: var(--wot-tooltip-bg, rgba(38, 39, 40, 0.8)) !default; // 背景色
|
||||||
|
$-tooltip-color: var(--wot-tooltip-color, $-color-white) !default; // 文字颜色
|
||||||
|
$-tooltip-radius: var(--wot-tooltip-radius, 8px) !default; // 圆角大小
|
||||||
|
$-tooltip-arrow-size: var(--wot-tooltip-arrow-size, 5px) !default; // 箭头大小
|
||||||
|
$-tooltip-fs: var(--wot-tooltip-fs, $-fs-content) !default; // 字号
|
||||||
|
$-tooltip-blur: var(--wot-tooltip-blur, 10px) !default; // 背景高斯模糊效果
|
||||||
|
$-tooltip-padding: var(--wot-tooltip-padding, 9px 20px) !default; // 间距
|
||||||
|
$-tooltip-close-size: var(--wot-tooltip-close-size, 6px) !default; // 背景高斯模糊效果
|
||||||
|
$-tooltip-z-index: var(--wot-tooltip-z-index, 500) !default;
|
||||||
|
$-tooltip-line-height: var(--wot-tooltip-line-height, 18px) !default; // 行高
|
||||||
|
|
||||||
|
/* popover */
|
||||||
|
$-popover-bg: var(--wot-popover-bg, $-color-white) !default; // 背景色
|
||||||
|
$-popover-color: var(--wot-popover-color, rgba(0, 0, 0, 0.85)) !default; // 文字颜色
|
||||||
|
$-popover-box-shadow: var(--wot-popover-box-shadow, 0px 2px 10px 0px rgba(0, 0, 0, 0.1)) !default; // 阴影颜色
|
||||||
|
$-popover-arrow-box-shadow: var(--wot-popover-arrow-box-shadow, 0px 2px 10px 0px rgba(0, 0, 0, 0.2)) !default; // 阴影颜色
|
||||||
|
$-popover-border-color: var(--wot-popover-border-color, rgba(0, 0, 0, 0.09)) !default; // 阴影颜色
|
||||||
|
$-popover-radius: var(--wot-popover-radius, 4px) !default; // 圆角大小
|
||||||
|
$-popover-arrow-size: var(--wot-popover-arrow-size, 6px) !default; // 箭头大小
|
||||||
|
$-popover-fs: var(--wot-popover-fs, $-fs-content) !default; // 字号
|
||||||
|
$-popover-padding: var(--wot-popover-padding, 15px) !default; // 间距
|
||||||
|
$-popover-line-height: var(--wot-popover-line-height, 18px) !default; // 行高
|
||||||
|
$-popover-z-index: var(--wot-popover-z-index, $-tooltip-z-index) !default;
|
||||||
|
|
||||||
|
/* grid-item */
|
||||||
|
$-grid-item-fs: var(--wot-grid-item-fs, 12px) !default; // 字号
|
||||||
|
$-grid-item-bg: var(--wot-grid-item-bg, $-color-white) !default; // 字号
|
||||||
|
$-grid-item-padding: var(--wot-grid-item-padding, 14px 0px) !default; // 内容的 padding
|
||||||
|
$-grid-item-border-color: var(--wot-grid-item-border-color, $-color-border-light) !default; // 边框颜色
|
||||||
|
$-grid-item-hover-bg: var(--wot-grid-item-hover-bg, $-color-gray-3) !default; // hover背景色
|
||||||
|
$-grid-item-hover-bg-dark: var(--wot-grid-item-hover-bg-dark, $-color-gray-7) !default; // 暗黑模式hover背景色
|
||||||
|
|
||||||
|
/* statustip */
|
||||||
|
$-statustip-fs: var(--wot-statustip-fs, $-fs-content) !default; // 字号
|
||||||
|
$-statustip-color: var(--wot-statustip-color, rgba(0, 0, 0, 0.45)) !default; // 文字颜色
|
||||||
|
$-statustip-line-height: var(--wot-statustip-line-height, 16px) !default; // 文字行高
|
||||||
|
$-statustip-padding: var(--wot-statustip-padding, 5px 10px) !default; // 间距
|
||||||
|
|
||||||
|
/* card */
|
||||||
|
$-card-bg: var(--wot-card-bg, $-color-white) !default; // 背景色
|
||||||
|
$-card-fs: var(--wot-card-fs, $-fs-content) !default; // 卡片字号
|
||||||
|
$-card-padding: var(--wot-card-padding, 0 $-size-side-padding) !default; // 内边距
|
||||||
|
$-card-footer-padding: var(--wot-card-footer-padding, 12px 0 16px) !default; // 底部内边距
|
||||||
|
$-card-shadow-color: var(--wot-card-shadow-color, 0px 4px 8px 0px rgba(0, 0, 0, 0.02)) !default; // 阴影
|
||||||
|
$-card-radius: var(--wot-card-radius, 8px) !default; // 圆角大小
|
||||||
|
$-card-line-height: var(--wot-card-line-height, 1.1) !default; // 行高
|
||||||
|
$-card-margin: var(--wot-card-margin, 0 $-size-side-padding) !default; // 外边距
|
||||||
|
$-card-title-color: var(--wot-card-title-color, rgba(0, 0, 0, 0.85)) !default; // 标题颜色
|
||||||
|
$-card-title-fs: var(--wot-card-title-fs, $-fs-title) !default; // 矩形卡片标题字号
|
||||||
|
$-card-content-border-color: var(--wot-card-content-border-color, rgba(0, 0, 0, 0.09)) !default; // 内容边框
|
||||||
|
$-card-rectangle-title-padding: var(--wot-card-rectangle-title-padding, 15px 15px 12px) !default; // 矩形卡片头部内边距
|
||||||
|
$-card-rectangle-content-padding: var(--wot-card-rectangle-content-padding, 16px 0) !default; // 矩形卡片内容内边距
|
||||||
|
$-card-rectangle-footer-padding: var(--wot-card-rectangle-footer-padding, 12px 0) !default; // 矩形卡片底部内边距
|
||||||
|
$-card-content-color: var(--wot-card-content-color, rgba(0, 0, 0, 0.45)) !default; // 文本内容颜色
|
||||||
|
$-card-content-line-height: var(--wot-card-content-line-height, 1.428) !default; // 文本内容行高
|
||||||
|
$-card-content-margin: var(--wot-card-content-margin, 13px 0 12px) !default; // 内容外边距
|
||||||
|
$-card-content-rectangle-margin: var(--wot-card-content-rectangle-margin, 14px 0 12px) !default; // 矩形卡片内容外边距
|
||||||
|
|
||||||
|
/* upload */
|
||||||
|
$-upload-size: var(--wot-upload-size, 80px) !default; // upload的外边框默认尺寸
|
||||||
|
$-upload-evoke-icon-size: var(--wot-upload-evoke-icon-size, 32px) !default; // 唤起项的图标大小
|
||||||
|
$-upload-evoke-bg: var(--wot-upload-evoke-bg, rgba(0, 0, 0, 0.04)) !default; // 唤起项的背景色
|
||||||
|
$-upload-evoke-color: var(--wot-upload-evoke-color, rgba(0, 0, 0, 0.25)) !default; // 唤起项的图标颜色
|
||||||
|
$-upload-evoke-disabled-color: var(--wot-upload-evoke-disabled-color, rgba(0, 0, 0, 0.09)) !default; // 唤起项禁用颜色
|
||||||
|
$-upload-close-icon-size: var(--wot-upload-close-icon-size, 16px) !default; // 移除按钮尺寸
|
||||||
|
$-upload-close-icon-color: var(--wot-upload-close-icon-color, rgba(0, 0, 0, 0.65)) !default; // 移除按钮颜色
|
||||||
|
$-upload-progress-fs: var(--wot-upload-progress-fs, 14px) !default; // 进度文字字号
|
||||||
|
$-upload-file-fs: var(--wot-upload-file-fs, 12px) !default; // 文件名字号
|
||||||
|
$-upload-file-color: var(--wot-upload-file-color, $-color-secondary) !default; // 文件名字颜色
|
||||||
|
$-upload-preview-name-fs: var(--wot-upload-preview-name-fs, 12px) !default; // 预览图片名字号
|
||||||
|
$-upload-preview-icon-size: var(--wot-upload-preview-icon-size, 24px) !default; // 预览内部图标尺寸
|
||||||
|
$-upload-preview-name-bg: var(--wot-upload-preview-name-bg, rgba(0, 0, 0, 0.6)) !default; // 预览文件名背景色
|
||||||
|
$-upload-preview-name-height: var(--wot-upload-preview-name-height, 22px) !default; // 预览文件名背景高度
|
||||||
|
$-upload-cover-icon-size: var(--wot-upload-cover-icon-size, 22px) !default; // 视频/文件图标尺寸
|
||||||
|
|
||||||
|
/* curtain */
|
||||||
|
$-curtain-content-radius: var(--wot-curtain-content-radius, 24px) !default; // 内容圆角
|
||||||
|
$-curtain-content-close-color: var(--wot-curtain-content-close-color, $-color-white) !default; // 关闭按钮颜色
|
||||||
|
$-curtain-content-close-fs: var(--wot-curtain-content-close-fs, $-fs-big) !default; // 关闭按钮大小
|
||||||
|
|
||||||
|
/* notify */
|
||||||
|
$-notify-text-color: var(--wot-notify-text-color, $-color-white) !default;
|
||||||
|
$-notify-padding: var(--wot-notify-padding, 8px 16px) !default;
|
||||||
|
$-notify-font-size: var(--wot-notify-font-size, $-fs-content) !default;
|
||||||
|
$-notify-line-height: var(--wot-notify-line-height, 20px) !default;
|
||||||
|
$-notify-primary-background: var(--wot-notify-primary-background, $-color-theme) !default;
|
||||||
|
$-notify-success-background: var(--wot-notify-success-background, $-color-success) !default;
|
||||||
|
$-notify-danger-background: var(--wot-notify-danger-background, $-color-danger) !default;
|
||||||
|
$-notify-warning-background: var(--wot-notify-warning-background, $-color-warning) !default;
|
||||||
|
|
||||||
|
/* skeleton */
|
||||||
|
$-skeleton-background-color: var(--wot-skeleton-background-color, #eee) !default;
|
||||||
|
$-skeleton-animation-gradient: var(--wot-skeleton-animation-gradient, rgba(0, 0, 0, 0.04)) !default;
|
||||||
|
$-skeleton-animation-flashed: var(--wot-skeleton-animation-flashed, rgba(230, 230, 230, 0.3)) !default;
|
||||||
|
$-skeleton-text-height-default: var(--wot-skeleton-text-height-default, 16px) !default;
|
||||||
|
$-skeleton-rect-height-default: var(--wot-skeleton-rect-height-default, 16px) !default;
|
||||||
|
$-skeleton-circle-height-default: var(--wot-skeleton-circle-height-default, 48px) !default;
|
||||||
|
$-skeleton-row-margin-bottom: var(--wot-skeleton-row-margin-bottom, 16px) !default;
|
||||||
|
$-skeleton-border-radius-text: var(--wot-skeleton-border-radius-text, 2px) !default;
|
||||||
|
$-skeleton-border-radius-rect: var(--wot-skeleton-border-radius-rect, 4px) !default;
|
||||||
|
$-skeleton-border-radius-circle: var(--wot-skeleton-border-radius-circle, 50%) !default;
|
||||||
|
|
||||||
|
/* circle */
|
||||||
|
$-circle-text-color: var(--wot-circle-text-color, $-color-content) !default; // circle文字颜色
|
||||||
|
|
||||||
|
/* swiper */
|
||||||
|
$-swiper-radius: var(--wot-swiper-radius, 8px);
|
||||||
|
$-swiper-item-padding: var(--wot-swiper-item-padding, 0);
|
||||||
|
$-swiper-item-text-color: var(--wot-swiper-item-text-color, #ffffff);
|
||||||
|
$-swiper-item-text-fs: var(--wot-swiper-item-text-fs, $-fs-title);
|
||||||
|
|
||||||
|
|
||||||
|
/* swiper-nav */
|
||||||
|
// dot & dots-bar
|
||||||
|
$-swiper-nav-dot-color: var(--wot-swiper-nav-dot-color, $-font-white-2) !default;
|
||||||
|
$-swiper-nav-dot-active-color: var(--wot-swiper-nav-dot-active-color, $-font-white-1) !default;
|
||||||
|
$-swiper-nav-dot-size: var(--wot-swiper-nav-dot-size, 12rpx) !default;
|
||||||
|
$-swiper-nav-dots-bar-active-width: var(--wot-swiper-nav-dots-bar-active-width, 40rpx) !default;
|
||||||
|
// fraction
|
||||||
|
$-swiper-nav-fraction-color: var(--wot-swiper-nav-fraction-color, $-font-white-1) !default;
|
||||||
|
$-swiper-nav-fraction-bg-color: var(--wot-swiper-nav-fraction-bg-color, $-font-gray-3) !default;
|
||||||
|
$-swiper-nav-fraction-height: var(--wot-swiper-nav-fraction-height, 48rpx) !default;
|
||||||
|
$-swiper-nav-fraction-font-size: var(--wot-swiper-nav-fraction-font-size, 24rpx) !default;
|
||||||
|
// button
|
||||||
|
$-swiper-nav-btn-color: var(--wot-swiper-nav-btn-color, $-font-white-1) !default;
|
||||||
|
$-swiper-nav-btn-bg-color: var(--wot-swiper-nav-btn-bg-color, $-font-gray-3) !default;
|
||||||
|
$-swiper-nav-btn-size: var(--wot-swiper-nav-btn-size, 48rpx) !default;
|
||||||
|
|
||||||
|
/* segmented */
|
||||||
|
$-segmented-padding: var(--wot-segmented-padding, 4px) !default; // 分段器padding
|
||||||
|
$-segmented-item-bg-color: var(--wot-segmented-item-bg-color, #eeeeee) !default;
|
||||||
|
$-segmented-item-color: var(--wot-segmented-item-color, rgba(0, 0, 0, 0.85)) !default; // 标题文字颜色
|
||||||
|
$-segmented-item-acitve-bg: var(--wot-segmented-item-acitve-bg, #ffffff) !default; // 标题文字颜色
|
||||||
|
$-segmented-item-disabled-color: var(--wot-segmented-item-disabled-color, rgba(0, 0, 0, 0.25)) !default; // 标题文字禁用颜色
|
||||||
|
|
||||||
|
/* tabbar */
|
||||||
|
$-tabbar-height: var(--wot-tabbar-height, 50px) !default;
|
||||||
|
$-tabbar-box-shadow: var(
|
||||||
|
--wot-tabbar-box-shadow,
|
||||||
|
0 6px 30px 5px rgba(0, 0, 0, 0.05),
|
||||||
|
0 16px 24px 2px rgba(0, 0, 0, 0.04),
|
||||||
|
0 8px 10px -5px rgba(0, 0, 0, 0.08)
|
||||||
|
) !default; // round类型tabbar阴影
|
||||||
|
|
||||||
|
/* tabbar-item */
|
||||||
|
$-tabbar-item-title-font-size: var(--wot-tabbar-item-title-font-size, 10px) !default; // tabbar选项文字大小
|
||||||
|
$-tabbar-item-title-line-height: var(--wot-tabbar-item-title-line-height, initial) !default; // tabbar选项标题文字行高
|
||||||
|
$-tabbar-inactive-color: var(--wot-tabbar-inactive-color, $-color-title) !default; // 标题文字和图标颜色
|
||||||
|
$-tabbar-active-color: var(--wot-tabbar-active-color, $-color-theme) !default; // 选中文字和图标颜色
|
||||||
|
$-tabbar-item-icon-size: var(--wot-tabbar-item-icon-size, 20px) !default; // tabbar选项图标大小
|
||||||
|
|
||||||
|
/* navbar */
|
||||||
|
$-navbar-height: var(--wot-navbar-height, 44px) !default; // navbar高度
|
||||||
|
$-navbar-color: var(--wot-navbar-color, $-font-gray-1) !default; // navbar字体颜色
|
||||||
|
$-navbar-background: var(--wot-navbar-background, $-color-white) !default; // navbar背景颜色
|
||||||
|
$-navbar-arrow-size: var(--wot-navbar-arrow-size, 24px) !default; // navbar左箭头图标大小
|
||||||
|
$-navbar-desc-font-size: var(--wot-navbar-desc-font-size, 16px); // navbar 左箭头字体大小
|
||||||
|
$-navbar-desc-font-color: var(--wot-navbar-desc-font-color, $-font-gray-1) !default; // navbar左右两侧字体颜色
|
||||||
|
$-navbar-title-font-size: var(--wot-navbar-title-font-size, 18px); // navbar title字体大小
|
||||||
|
$-navbar-title-font-weight: var(--wot-navbar-title-font-weight, 600); // navbar title字重
|
||||||
|
$-navbar-disabled-opacity: var(--wot-navbar-disabled-opacity, 0.6) !default; // navbar左右两侧字体禁用
|
||||||
|
$-navbar-hover-color: var(--wot-navbar-hover-color, #eee) !default; // navbar hover样式
|
||||||
|
|
||||||
|
/* navbar-capsule */
|
||||||
|
$-navbar-capsule-border-color: var(--wot-navbar-capsule-border-color, #e7e7e7) !default;
|
||||||
|
$-navbar-capsule-border-radius: var(--wot-navbar-capsule-border-radius, 16px) !default;
|
||||||
|
$-navbar-capsule-width: var(--wot-navbar-capsule-width, 88px) !default;
|
||||||
|
$-navbar-capsule-height: var(--wot-navbar-capsule-height, 32px) !default;
|
||||||
|
$-navbar-capsule-icon-size: var(--wot-navbar-capsule-icon-size, 20px) !default; // navbar capsule图标大小
|
||||||
|
|
||||||
|
/* table */
|
||||||
|
$-table-color: var(--wot-table-color, $-font-gray-1) !default; // 表格字体颜色
|
||||||
|
$-table-bg: var(--wot-table-bg, #ffffff) !default; // 表格背景颜色
|
||||||
|
$-table-stripe-bg: var(--wot-table-stripe-bg, #f3f3f3) !default; // 表格背景颜色
|
||||||
|
$-table-border-color: var(--wot-table-border-color, #ececec) !default; // 表格边框颜色
|
||||||
|
$-table-font-size: var(--wot-table-font-size, 13px) !default; // 表格字体大小
|
||||||
|
|
||||||
|
/* sidebar */
|
||||||
|
$-sidebar-bg: var(--wot-sidebar-bg, $-color-gray-1) !default; // 侧边栏背景色
|
||||||
|
$-sidebar-width: var(--wot-sidebar-width, 104px) !default; // 侧边栏宽度
|
||||||
|
$-sidebar-height: var(--wot-sidebar-height, 100%) !default; // 侧边栏高度
|
||||||
|
|
||||||
|
/* sidebar-item */
|
||||||
|
$-sidebar-color: var(--wot-sidebar-color, $-font-gray-1) !default;
|
||||||
|
$-sidebar-item-height: var(--wot-sidebar-item-height, 56px) !default;
|
||||||
|
$-sidebar-item-line-height: var(--wot-sidebar-item-line-height, 24px) !default;
|
||||||
|
$-sidebar-disabled-color: var(--wot-side-bar-disabled-color, $-font-gray-4) !default;
|
||||||
|
$-sidebar-active-color: var(--wot-sidebar-active-color, $-color-theme) !default; // 激活项字体颜色
|
||||||
|
$-sidebar-active-bg: var(--wot-sidebar-active-bg, $-color-white) !default; // 激活项背景颜色
|
||||||
|
$-sidebar-hover-bg: var(--wot-sidebar-hover-bg, $-color-gray-2) !default; // 激活项点击背景颜色
|
||||||
|
$-sidebar-border-radius: var(--wot-sidebar-border-radius, 8px) !default;
|
||||||
|
$-sidebar-font-size: var(--wot-sidebar-font-size, 16px) !default;
|
||||||
|
$-sidebar-icon-size: var(--wot-sidebar-icon-size, 20px) !default;
|
||||||
|
$-sidebar-active-border-width: var(--wot-sidebar-active-border-width, 4px) !default;
|
||||||
|
$-sidebar-active-border-height: var(--wot-sidebar-active-border-height, 16px) !default;
|
||||||
|
|
||||||
|
/* fab */
|
||||||
|
$-fab-trigger-height: var(--wot-fab-trigger-height, 56px) !default;
|
||||||
|
$-fab-trigger-width: var(--wot-fab-trigger-width, 56px) !default;
|
||||||
|
$-fab-actions-padding: var(--wot-actions-padding, 12px) !default;
|
||||||
|
$-fab-icon-fs: var(--wot-fab-icon-fs, 20px) !default;
|
||||||
|
|
||||||
|
/* count-down */
|
||||||
|
$-count-down-text-color: var(--wot-count-down-text-color, $-color-gray-8) !default;
|
||||||
|
$-count-down-font-size: var(--wot-count-down-font-size, $-fs-content) !default;
|
||||||
|
$-count-down-line-height: var(--wot-count-down-line-height, 20px) !default;
|
||||||
|
|
||||||
|
/* keyboard */
|
||||||
|
$-keyboard-key-height: var(--wot-keyboard-key-height, 48px) !default;
|
||||||
|
$-keyboard-key-font-size: var(--wot-keyboard-key-font-size, 28px) !default;
|
||||||
|
$-keyboard-key-background: var(--wot-keyboard-key-background, $-color-white) !default;
|
||||||
|
$-keyboard-key-border-radius: var(--wot-keyboard-key-border-radius, 8px) !default;
|
||||||
|
$-keyboard-delete-font-size: var(--wot-keyboard-delete-font-size, 16px) !default;
|
||||||
|
$-keyboard-key-active-color: var(--wot-keyboard-key-active-color, $-color-gray-3) !default;
|
||||||
|
$-keyboard-button-text-color: var(--wot-keyboard-button-text-color, $-color-white) !default;
|
||||||
|
$-keyboard-button-background: var(--wot-keyboard--button-background, $-color-theme) !default;
|
||||||
|
$-keyboard-button-active-opacity: var(--wot-keyboard-button-active-opacity, 0.6) !default;
|
||||||
|
$-keyboard-background: var(--wot-keyboard-background, $-color-gray-2) !default;
|
||||||
|
$-keyboard-title-height: var(--wot-keyboard-title-height, 34px) !default;
|
||||||
|
$-keyboard-title-color: var(--wot-keyboard-title-color, $-color-gray-7) !default;
|
||||||
|
$-keyboard-title-font-size: var(--wot-keyboard-title-font-size, 16px) !default;
|
||||||
|
$-keyboard-close-padding: var(--wot-keyboard-title-font-size, 0 16px) !default;
|
||||||
|
$-keyboard-close-color: var(--wot-keyboard-close-color, $-color-theme) !default;
|
||||||
|
$-keyboard-close-font-size: var(--wot-keyboard-close-font-size, 14px) !default;
|
||||||
|
$-keyboard-icon-size: var(--wot-keyboard-icon-size, 22px) !default;
|
||||||
|
|
||||||
|
/* number-keyboard */
|
||||||
|
$-number-keyboard-key-height: var(--wot-number-keyboard-key-height, 48px) !default;
|
||||||
|
$-number-keyboard-key-font-size: var(--wot-number-keyboard-key-font-size, 28px) !default;
|
||||||
|
$-number-keyboard-key-background: var(--wot-number-keyboard-key-background, $-color-white) !default;
|
||||||
|
$-number-keyboard-key-border-radius: var(--wot-number-keyboard-key-border-radius, 8px) !default;
|
||||||
|
$-number-keyboard-delete-font-size: var(--wot-number-keyboard-delete-font-size, 16px) !default;
|
||||||
|
$-number-keyboard-key-active-color: var(--wot-number-keyboard-key-active-color, $-color-gray-3) !default;
|
||||||
|
$-number-keyboard-button-text-color: var(--wot-number-keyboard-button-text-color, $-color-white) !default;
|
||||||
|
$-number-keyboard-button-background: var(--wot-number-keyboard--button-background, $-color-theme) !default;
|
||||||
|
$-number-keyboard-button-active-opacity: var(--wot-number-keyboard-button-active-opacity, 0.6) !default;
|
||||||
|
$-number-keyboard-background: var(--wot-number-keyboard-background, $-color-gray-2) !default;
|
||||||
|
$-number-keyboard-title-height: var(--wot-number-keyboard-title-height, 34px) !default;
|
||||||
|
$-number-keyboard-title-color: var(--wot-number-keyboard-title-color, $-color-gray-7) !default;
|
||||||
|
$-number-keyboard-title-font-size: var(--wot-number-keyboard-title-font-size, 16px) !default;
|
||||||
|
$-number-keyboard-close-padding: var(--wot-number-keyboard-title-font-size, 0 16px) !default;
|
||||||
|
$-number-keyboard-close-color: var(--wot-number-keyboard-close-color, $-color-theme) !default;
|
||||||
|
$-number-keyboard-close-font-size: var(--wot-number-keyboard-close-font-size, 14px) !default;
|
||||||
|
$-number-keyboard-icon-size: var(--wot-number-keyboard-icon-size, 22px) !default;
|
||||||
|
|
||||||
|
/* passwod-input */
|
||||||
|
$-password-input-height: var(--wot-password-input-height, 50px);
|
||||||
|
$-password-input-margin: var(--wot-password-input-margin, 16px);
|
||||||
|
$-password-input-font-size: var(--wot-password-input-margin, 20px);
|
||||||
|
$-password-input-radius: var(--wot-password-input-radius, 6px);
|
||||||
|
$-password-input-background: var(--wot-password-input-background, #fff);
|
||||||
|
$-password-input-info-color: var(--wot-password-input-info-color, $-color-info);
|
||||||
|
$-password-input-info-font-size: var(--wot-password-input-info-font-size, $-fs-content);
|
||||||
|
$-password-input-border-color: var(--wot-password-border-color, #ebedf0);
|
||||||
|
$-password-input-error-info-color: var(--wot-password-input-error-info-color, $-color-danger);
|
||||||
|
$-password-input-dot-size: var(--wot-password-input-dot-size, 10px);
|
||||||
|
$-password-input-dot-color: var(--wot-password-input-dot-color, $-color-gray-8);
|
||||||
|
$-password-input-text-color: var(--wot-password-input-text-color, $-color-gray-8);
|
||||||
|
$-password-input-cursor-color: var(--wot-password-input-cursor-color, $-color-gray-8);
|
||||||
|
$-password-input-cursor-width: var(--wot-password-input-cursor-width, 1px);
|
||||||
|
$-password-input-cursor-height: var(--wot-password-input-cursor-height, 40%);
|
||||||
|
$-password-input-cursor-duration: var(--wot-password-input-cursor-duration, 1s);
|
||||||
|
|
||||||
|
/* form-item */
|
||||||
|
$-form-item-error-message-color: var(--wot-form-item-error-message-color, $-color-danger) !default;
|
||||||
|
$-form-item-error-message-font-size: var(--wot-form-item-error-message-font-size, $-fs-secondary) !default;
|
||||||
|
$-form-item-error-message-line-height: var(--wot-form-item-error-message-line-height, 24px) !default;
|
||||||
|
|
||||||
|
/* backtop */
|
||||||
|
$-backtop-bg: var(--wot-backtop-bg, #e1e1e1) !default;
|
||||||
|
$-backtop-icon-size: var(--wot-backtop-icon-size, 20px) !default;
|
||||||
|
|
||||||
|
/* index-bar */
|
||||||
|
$-index-bar-index-font-size: var(--wot-index-bar-index-font-size, $-fs-aid) !default;
|
||||||
|
|
||||||
|
/* text */
|
||||||
|
$-text-info-color: var(--wot-text-info-color, $-color-info) !default;
|
||||||
|
$-text-primary-color: var(--wot-text-primary-color, $-color-theme) !default;
|
||||||
|
$-text-error-color: var(--wot-text-error-color, $-color-danger) !default;
|
||||||
|
$-text-warning-color: var(--wot-text-warning-color, $-color-warning) !default;
|
||||||
|
$-text-success-color: var(--wot-text-success-color, $-color-success) !default;
|
||||||
|
|
||||||
|
/* video-preview */
|
||||||
|
$-video-preview-bg: var(--wot-video-preview-bg, rgba(0, 0, 0, 0.8)) !default; // 背景色
|
||||||
|
$-video-preview-close-color: var(--wot-video-preview-close-color, #fff) !default; // 图标颜色
|
||||||
|
$-video-preview-close-font-size: var(--wot-video-preview-close-font-size, 20px) !default; // 图标大小
|
||||||
|
|
||||||
|
/* img-cropper */
|
||||||
|
$-img-cropper-icon-size: var(--wot-img-cropper-icon-size, $-fs-big) !default; // 图标大小
|
||||||
|
$-img-cropper-icon-color: var(--wot-img-cropper-icon-color, #fff) !default; // 图标颜色
|
||||||
|
|
||||||
|
/* floating-panel */
|
||||||
|
$-floating-panel-bg: var(--wot-floating-panel-bg, $-color-white) !default; // 背景色
|
||||||
|
$-floating-panel-radius: var(--wot-floating-panel-radius, 16px) !default; // 圆角
|
||||||
|
$-floating-panel-z-index: var(--wot-floating-panel-z-index, 99) !default; // 层级
|
||||||
|
$-floating-panel-header-height: var(--wot-floating-panel-header-height, 30px) !default; // 头部高度
|
||||||
|
$-floating-panel-bar-width: var(--wot-floating-panel-bar-width, 20px) !default; // bar 宽度
|
||||||
|
$-floating-panel-bar-height: var(--wot-floating-panel-bar-height, 3px) !default; // bar 高度
|
||||||
|
$-floating-panel-bar-bg: var(--wot-floating-panel-bar-bg, $-color-gray-5) !default; // bar 背景色
|
||||||
|
$-floating-panel-bar-radius: var(--wot-floating-panel-bar-radius, 4px) !default; // bar 圆角
|
||||||
|
$-floating-panel-content-bg: var(--wot-floating-panel-content-bg, $-color-white) !default; // 内容背景色
|
||||||
|
|
||||||
|
/* signature */
|
||||||
|
$-signature-bg: var(--wot-signature-bg, $-color-white) !default; // 背景色
|
||||||
|
$-signature-radius: var(--wot-signature-radius, 4px) !default; // 圆角
|
||||||
|
$-signature-border: var(--wot-signature-border, 1px solid $-color-gray-5) !default; // 边框圆角
|
||||||
|
$-signature-footer-margin-top: var(--wot-signature-footer-margin-top, 8px) !default; // 底部按钮上边距
|
||||||
|
$-signature-button-margin-left: var(--wot-signature-button-margin-left, 8px) !default; // 底部按钮左边距
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
29
src/uni_modules/wot-design-uni/components/common/base64.ts
Normal file
29
src/uni_modules/wot-design-uni/components/common/base64.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const _b64chars: string[] = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/']
|
||||||
|
const _mkUriSafe = (src: string): string => src.replace(/[+/]/g, (m0: string) => (m0 === '+' ? '-' : '_')).replace(/=+\$/m, '')
|
||||||
|
const fromUint8Array = (src: Uint8Array, rfc4648 = false): string => {
|
||||||
|
let b64 = ''
|
||||||
|
for (let i = 0, l = src.length; i < l; i += 3) {
|
||||||
|
const [a0, a1, a2] = [src[i], src[i + 1], src[i + 2]]
|
||||||
|
const ord = (a0 << 16) | (a1 << 8) | a2
|
||||||
|
b64 += _b64chars[ord >>> 18]
|
||||||
|
b64 += _b64chars[(ord >>> 12) & 63]
|
||||||
|
b64 += typeof a1 !== 'undefined' ? _b64chars[(ord >>> 6) & 63] : '='
|
||||||
|
b64 += typeof a2 !== 'undefined' ? _b64chars[ord & 63] : '='
|
||||||
|
}
|
||||||
|
return rfc4648 ? _mkUriSafe(b64) : b64
|
||||||
|
}
|
||||||
|
const _btoa: (s: string) => string =
|
||||||
|
typeof btoa === 'function'
|
||||||
|
? (s: string) => btoa(s)
|
||||||
|
: (s: string) => {
|
||||||
|
if (s.charCodeAt(0) > 255) {
|
||||||
|
throw new RangeError('The string contains invalid characters.')
|
||||||
|
}
|
||||||
|
return fromUint8Array(Uint8Array.from(s, (c: string) => c.charCodeAt(0)))
|
||||||
|
}
|
||||||
|
const utob = (src: string): string => unescape(encodeURIComponent(src))
|
||||||
|
|
||||||
|
export default function encode(src: string, rfc4648 = false): string {
|
||||||
|
const b64 = _btoa(utob(src))
|
||||||
|
return rfc4648 ? _mkUriSafe(b64) : b64
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* 适配 canvas 2d 上下文
|
||||||
|
* @param ctx canvas 2d 上下文
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function canvas2dAdapter(ctx: CanvasRenderingContext2D): UniApp.CanvasContext {
|
||||||
|
return Object.assign(ctx, {
|
||||||
|
setFillStyle(color: string | CanvasGradient) {
|
||||||
|
ctx.fillStyle = color
|
||||||
|
},
|
||||||
|
setStrokeStyle(color: string | CanvasGradient | CanvasPattern) {
|
||||||
|
ctx.strokeStyle = color
|
||||||
|
},
|
||||||
|
setLineWidth(lineWidth: number) {
|
||||||
|
ctx.lineWidth = lineWidth
|
||||||
|
},
|
||||||
|
setLineCap(lineCap: 'butt' | 'round' | 'square') {
|
||||||
|
ctx.lineCap = lineCap
|
||||||
|
},
|
||||||
|
|
||||||
|
setFontSize(font: string) {
|
||||||
|
ctx.font = font
|
||||||
|
},
|
||||||
|
setGlobalAlpha(alpha: number) {
|
||||||
|
ctx.globalAlpha = alpha
|
||||||
|
},
|
||||||
|
setLineJoin(lineJoin: 'bevel' | 'round' | 'miter') {
|
||||||
|
ctx.lineJoin = lineJoin
|
||||||
|
},
|
||||||
|
setTextAlign(align: 'left' | 'center' | 'right') {
|
||||||
|
ctx.textAlign = align
|
||||||
|
},
|
||||||
|
setMiterLimit(miterLimit: number) {
|
||||||
|
ctx.miterLimit = miterLimit
|
||||||
|
},
|
||||||
|
setShadow(offsetX: number, offsetY: number, blur: number, color: string) {
|
||||||
|
ctx.shadowOffsetX = offsetX
|
||||||
|
ctx.shadowOffsetY = offsetY
|
||||||
|
ctx.shadowBlur = blur
|
||||||
|
ctx.shadowColor = color
|
||||||
|
},
|
||||||
|
setTextBaseline(textBaseline: 'top' | 'bottom' | 'middle') {
|
||||||
|
ctx.textBaseline = textBaseline
|
||||||
|
},
|
||||||
|
createCircularGradient() {},
|
||||||
|
draw() {},
|
||||||
|
addColorStop() {}
|
||||||
|
}) as unknown as UniApp.CanvasContext
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2023-07-02 22:51:06
|
||||||
|
* @LastEditTime: 2024-03-16 19:59:07
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/common/clickoutside.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
let queue: any[] = []
|
||||||
|
|
||||||
|
export function pushToQueue(comp: any) {
|
||||||
|
queue.push(comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFromQueue(comp: any) {
|
||||||
|
queue = queue.filter((item) => {
|
||||||
|
return item.$.uid !== comp.$.uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeOther(comp: any) {
|
||||||
|
queue.forEach((item) => {
|
||||||
|
if (item.$.uid !== comp.$.uid) {
|
||||||
|
item.$.exposed.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeOutside() {
|
||||||
|
queue.forEach((item) => {
|
||||||
|
item.$.exposed.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
export const UPDATE_MODEL_EVENT = 'update:modelValue'
|
||||||
|
export const CHANGE_EVENT = 'change'
|
||||||
|
export const INPUT_EVENT = 'input'
|
||||||
|
export const CLICK_EVENT = 'click'
|
||||||
|
export const CLOSE_EVENT = 'close'
|
||||||
|
export const OPEN_EVENT = 'open'
|
||||||
|
export const CONFIRM_EVENT = 'confirm'
|
||||||
|
export const CANCEL_EVENT = 'cancel'
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { isPromise } from './util'
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
export type Interceptor = (...args: any[]) => Promise<boolean> | boolean | undefined | void
|
||||||
|
|
||||||
|
export function callInterceptor(
|
||||||
|
interceptor: Interceptor | undefined,
|
||||||
|
{
|
||||||
|
args = [],
|
||||||
|
done,
|
||||||
|
canceled,
|
||||||
|
error
|
||||||
|
}: {
|
||||||
|
args?: unknown[]
|
||||||
|
done: () => void
|
||||||
|
canceled?: () => void
|
||||||
|
error?: () => void
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (interceptor) {
|
||||||
|
// eslint-disable-next-line prefer-spread
|
||||||
|
const returnVal = interceptor.apply(null, args)
|
||||||
|
|
||||||
|
if (isPromise(returnVal)) {
|
||||||
|
returnVal
|
||||||
|
.then((value) => {
|
||||||
|
if (value) {
|
||||||
|
done()
|
||||||
|
} else if (canceled) {
|
||||||
|
canceled()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error || noop)
|
||||||
|
} else if (returnVal) {
|
||||||
|
done()
|
||||||
|
} else if (canceled) {
|
||||||
|
canceled()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/uni_modules/wot-design-uni/components/common/props.ts
Normal file
51
src/uni_modules/wot-design-uni/components/common/props.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
|
||||||
|
export const unknownProp = null as unknown as PropType<unknown>
|
||||||
|
|
||||||
|
export const numericProp = [Number, String]
|
||||||
|
|
||||||
|
export const truthProp = {
|
||||||
|
type: Boolean,
|
||||||
|
default: true as const
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeRequiredProp = <T>(type: T) => ({
|
||||||
|
type,
|
||||||
|
required: true as const
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeArrayProp = <T>() => ({
|
||||||
|
type: Array as PropType<T[]>,
|
||||||
|
default: () => []
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeBooleanProp = <T>(defaultVal: T) => ({
|
||||||
|
type: Boolean,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeNumberProp = <T>(defaultVal: T) => ({
|
||||||
|
type: Number,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeNumericProp = <T>(defaultVal: T) => ({
|
||||||
|
type: numericProp,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeStringProp = <T>(defaultVal: T) => ({
|
||||||
|
type: String as unknown as PropType<T>,
|
||||||
|
default: defaultVal
|
||||||
|
})
|
||||||
|
|
||||||
|
export const baseProps = {
|
||||||
|
/**
|
||||||
|
* 自定义根节点样式
|
||||||
|
*/
|
||||||
|
customStyle: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 自定义根节点样式类
|
||||||
|
*/
|
||||||
|
customClass: makeStringProp('')
|
||||||
|
}
|
||||||
778
src/uni_modules/wot-design-uni/components/common/util.ts
Normal file
778
src/uni_modules/wot-design-uni/components/common/util.ts
Normal file
@ -0,0 +1,778 @@
|
|||||||
|
import { AbortablePromise } from './AbortablePromise'
|
||||||
|
|
||||||
|
type NotUndefined<T> = T extends undefined ? never : T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成uuid
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function uuid() {
|
||||||
|
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4()
|
||||||
|
}
|
||||||
|
|
||||||
|
function s4() {
|
||||||
|
return Math.floor((1 + Math.random()) * 0x10000)
|
||||||
|
.toString(16)
|
||||||
|
.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 对num自动填充px
|
||||||
|
* @param {Number} num
|
||||||
|
* @return {string} num+px
|
||||||
|
*/
|
||||||
|
export function addUnit(num: number | string) {
|
||||||
|
return Number.isNaN(Number(num)) ? `${num}` : `${num}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 判断target是否对象
|
||||||
|
* @param value
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isObj(value: any): value is object {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Object]' || typeof value === 'object'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标原始类型
|
||||||
|
* @param target 任意类型
|
||||||
|
* @returns {string} type 数据类型
|
||||||
|
*/
|
||||||
|
export function getType(target: unknown): string {
|
||||||
|
// 得到原生类型
|
||||||
|
const typeStr = Object.prototype.toString.call(target)
|
||||||
|
// 拿到类型值
|
||||||
|
const match = typeStr.match(/\[object (\w+)\]/)
|
||||||
|
const type = match && match.length ? match[1].toLowerCase() : ''
|
||||||
|
// 类型值转小写并返回
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 默认的外部格式化函数 - picker 组件
|
||||||
|
* @param items - 要格式化的数据项数组或单个数据项
|
||||||
|
* @param kv - 配置对象,包含 labelKey 作为键值
|
||||||
|
* @returns 格式化后的字符串
|
||||||
|
*/
|
||||||
|
export const defaultDisplayFormat = function (items: any[] | Record<string, any>, kv?: { labelKey?: string }): string {
|
||||||
|
const labelKey: string = kv?.labelKey || 'value'
|
||||||
|
|
||||||
|
if (Array.isArray(items)) {
|
||||||
|
return items.map((item) => item[labelKey]).join(', ')
|
||||||
|
} else {
|
||||||
|
return items[labelKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 默认函数占位符 - pickerView组件
|
||||||
|
* @param value 值
|
||||||
|
* @return value
|
||||||
|
*/
|
||||||
|
export const defaultFunction = <T>(value: T): T => value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 检查值是否不为空
|
||||||
|
* @param value 值
|
||||||
|
* @return {Boolean} 是否不为空
|
||||||
|
*/
|
||||||
|
export const isDef = <T>(value: T): value is NonNullable<T> => value !== undefined && value !== null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 防止数字小于零
|
||||||
|
* @param {number} num
|
||||||
|
* @param {string} label 标签
|
||||||
|
*/
|
||||||
|
export const checkNumRange = (num: number, label: string = 'value'): void => {
|
||||||
|
if (num < 0) {
|
||||||
|
throw new Error(`${label} shouldn't be less than zero`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 防止 pixel 无意义
|
||||||
|
* @param {number} num
|
||||||
|
* @param {string} label 标签
|
||||||
|
*/
|
||||||
|
export const checkPixelRange = (num: number, label: string = 'value'): void => {
|
||||||
|
if (num <= 0) {
|
||||||
|
throw new Error(`${label} should be greater than zero`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 RGB 值转换为十六进制颜色代码。
|
||||||
|
* @param {number} r - 红色分量 (0-255)。
|
||||||
|
* @param {number} g - 绿色分量 (0-255)。
|
||||||
|
* @param {number} b - 蓝色分量 (0-255)。
|
||||||
|
* @returns {string} 十六进制颜色代码 (#RRGGBB)。
|
||||||
|
*/
|
||||||
|
export function rgbToHex(r: number, g: number, b: number): string {
|
||||||
|
// 将 RGB 分量组合成一个十六进制数。
|
||||||
|
const hex = ((r << 16) | (g << 8) | b).toString(16)
|
||||||
|
|
||||||
|
// 使用零填充十六进制数,确保它有 6 位数字(RGB 范围)。
|
||||||
|
const paddedHex = '#' + '0'.repeat(Math.max(0, 6 - hex.length)) + hex
|
||||||
|
|
||||||
|
return paddedHex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将十六进制颜色代码转换为 RGB 颜色数组。
|
||||||
|
* @param hex 十六进制颜色代码(例如:'#RRGGBB')
|
||||||
|
* @returns 包含红、绿、蓝三个颜色分量的数组
|
||||||
|
*/
|
||||||
|
export function hexToRgb(hex: string): number[] {
|
||||||
|
const rgb: number[] = []
|
||||||
|
|
||||||
|
// 从第一个字符开始,每两个字符代表一个颜色分量
|
||||||
|
for (let i = 1; i < 7; i += 2) {
|
||||||
|
// 将两个字符的十六进制转换为十进制,并添加到 rgb 数组中
|
||||||
|
rgb.push(parseInt('0x' + hex.slice(i, i + 2), 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算渐变色的中间变量数组。
|
||||||
|
* @param {string} startColor 开始颜色
|
||||||
|
* @param {string} endColor 结束颜色
|
||||||
|
* @param {number} step 获取渲染位置,默认为中间位置
|
||||||
|
* @returns {string[]} 渐变色中间颜色变量数组
|
||||||
|
*/
|
||||||
|
export const gradient = (startColor: string, endColor: string, step: number = 2): string[] => {
|
||||||
|
// 将hex转换为rgb
|
||||||
|
const sColor: number[] = hexToRgb(startColor)
|
||||||
|
const eColor: number[] = hexToRgb(endColor)
|
||||||
|
|
||||||
|
// 计算R\G\B每一步的差值
|
||||||
|
const rStep: number = (eColor[0] - sColor[0]) / step
|
||||||
|
const gStep: number = (eColor[1] - sColor[1]) / step
|
||||||
|
const bStep: number = (eColor[2] - sColor[2]) / step
|
||||||
|
|
||||||
|
const gradientColorArr: string[] = []
|
||||||
|
for (let i = 0; i < step; i++) {
|
||||||
|
// 计算每一步的hex值
|
||||||
|
gradientColorArr.push(
|
||||||
|
rgbToHex(parseInt(String(rStep * i + sColor[0])), parseInt(String(gStep * i + sColor[1])), parseInt(String(bStep * i + sColor[2])))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return gradientColorArr
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保数值不超出指定范围。
|
||||||
|
* @param {number} num 要限制范围的数值
|
||||||
|
* @param {number} min 最小范围
|
||||||
|
* @param {number} max 最大范围
|
||||||
|
* @returns {number} 在指定范围内的数值
|
||||||
|
*/
|
||||||
|
export const range = (num: number, min: number, max: number): number => {
|
||||||
|
// 使用 Math.min 和 Math.max 保证 num 不会超出指定范围
|
||||||
|
return Math.min(Math.max(num, min), max)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个值是否相等。
|
||||||
|
* @param {any} value1 第一个值
|
||||||
|
* @param {any} value2 第二个值
|
||||||
|
* @returns {boolean} 如果值相等则为 true,否则为 false
|
||||||
|
*/
|
||||||
|
export const isEqual = (value1: any, value2: any): boolean => {
|
||||||
|
// 使用严格相等运算符比较值是否相等
|
||||||
|
if (value1 === value2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果其中一个值不是数组,则认为值不相等
|
||||||
|
if (!Array.isArray(value1) || !Array.isArray(value2)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果数组长度不相等,则认为值不相等
|
||||||
|
if (value1.length !== value2.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐个比较数组元素是否相等
|
||||||
|
for (let i = 0; i < value1.length; ++i) {
|
||||||
|
if (value1[i] !== value2[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有比较均通过,则认为值相等
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在数字前补零,使其达到指定长度。
|
||||||
|
* @param {number | string} number 要补零的数字
|
||||||
|
* @param {number} length 目标长度,默认为 2
|
||||||
|
* @returns {string} 补零后的结果
|
||||||
|
*/
|
||||||
|
export const padZero = (number: number | string, length: number = 2): string => {
|
||||||
|
// 将输入转换为字符串
|
||||||
|
let numStr: string = number.toString()
|
||||||
|
|
||||||
|
// 在数字前补零,直到达到指定长度
|
||||||
|
while (numStr.length < length) {
|
||||||
|
numStr = '0' + numStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return numStr
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @description 全局变量id */
|
||||||
|
export const context = {
|
||||||
|
id: 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RectResultType<T extends boolean> = T extends true ? UniApp.NodeInfo[] : UniApp.NodeInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点信息
|
||||||
|
* @param selector 节点选择器 #id,.class
|
||||||
|
* @param all 是否返回所有 selector 对应的节点
|
||||||
|
* @param scope 作用域(支付宝小程序无效)
|
||||||
|
* @param useFields 是否使用 fields 方法获取节点信息
|
||||||
|
* @returns 节点信息或节点信息数组
|
||||||
|
*/
|
||||||
|
export function getRect<T extends boolean>(selector: string, all: T, scope?: any, useFields?: boolean): Promise<RectResultType<T>> {
|
||||||
|
return new Promise<RectResultType<T>>((resolve, reject) => {
|
||||||
|
let query: UniNamespace.SelectorQuery | null = null
|
||||||
|
if (scope) {
|
||||||
|
query = uni.createSelectorQuery().in(scope)
|
||||||
|
} else {
|
||||||
|
query = uni.createSelectorQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = all ? 'selectAll' : 'select'
|
||||||
|
|
||||||
|
const callback = (rect: UniApp.NodeInfo | UniApp.NodeInfo[]) => {
|
||||||
|
if (all && isArray(rect) && rect.length > 0) {
|
||||||
|
resolve(rect as RectResultType<T>)
|
||||||
|
} else if (!all && rect) {
|
||||||
|
resolve(rect as RectResultType<T>)
|
||||||
|
} else {
|
||||||
|
reject(new Error('No nodes found'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useFields) {
|
||||||
|
query[method](selector).fields({ size: true, node: true }, callback).exec()
|
||||||
|
} else {
|
||||||
|
query[method](selector).boundingClientRect(callback).exec()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将驼峰命名转换为短横线命名。
|
||||||
|
* @param {string} word 待转换的词条
|
||||||
|
* @returns {string} 转换后的结果
|
||||||
|
*/
|
||||||
|
export function kebabCase(word: string): string {
|
||||||
|
// 使用正则表达式匹配所有大写字母,并在前面加上短横线,然后转换为小写
|
||||||
|
const newWord: string = word
|
||||||
|
.replace(/[A-Z]/g, function (match) {
|
||||||
|
return '-' + match
|
||||||
|
})
|
||||||
|
.toLowerCase()
|
||||||
|
|
||||||
|
return newWord
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将短横线链接转换为驼峰命名
|
||||||
|
* @param word 需要转换的短横线链接
|
||||||
|
* @returns 转换后的驼峰命名字符串
|
||||||
|
*/
|
||||||
|
export function camelCase(word: string): string {
|
||||||
|
return word.replace(/-(\w)/g, (_, c) => c.toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为数组。
|
||||||
|
* @param {any} value 要检查的值
|
||||||
|
* @returns {boolean} 如果是数组则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isArray(value: any): value is Array<any> {
|
||||||
|
// 如果 Array.isArray 函数可用,直接使用该函数检查
|
||||||
|
if (typeof Array.isArray === 'function') {
|
||||||
|
return Array.isArray(value)
|
||||||
|
}
|
||||||
|
// 否则,使用对象原型的 toString 方法进行检查
|
||||||
|
return Object.prototype.toString.call(value) === '[object Array]'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为函数。
|
||||||
|
* @param {any} value 要检查的值
|
||||||
|
* @returns {boolean} 如果是函数则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export function isFunction<T extends Function>(value: any): value is T {
|
||||||
|
return getType(value) === 'function' || getType(value) === 'asyncfunction'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为字符串。
|
||||||
|
* @param {unknown} value 要检查的值
|
||||||
|
* @returns {value is string} 如果是字符串则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isString(value: unknown): value is string {
|
||||||
|
return getType(value) === 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 否是数值
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
export function isNumber(value: any): value is number {
|
||||||
|
return getType(value) === 'number'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定值是否为 Promise 对象。
|
||||||
|
* @param {unknown} value 要检查的值
|
||||||
|
* @returns {value is Promise<any>} 如果是 Promise 对象则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isPromise(value: unknown): value is Promise<any> {
|
||||||
|
// 先将 value 断言为 object 类型
|
||||||
|
if (isObj(value) && isDef(value)) {
|
||||||
|
// 然后进一步检查 value 是否具有 then 和 catch 方法,并且它们是函数类型
|
||||||
|
return isFunction((value as Promise<any>).then) && isFunction((value as Promise<any>).catch)
|
||||||
|
}
|
||||||
|
return false // 如果 value 不是对象类型,则肯定不是 Promise
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定的值是否为布尔类型
|
||||||
|
* @param value 要检查的值
|
||||||
|
* @returns 如果值为布尔类型,则返回true,否则返回false
|
||||||
|
*/
|
||||||
|
export function isBoolean(value: any): value is boolean {
|
||||||
|
return typeof value === 'boolean'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUndefined(value: any): value is undefined {
|
||||||
|
return typeof value === 'undefined'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotUndefined<T>(value: T): value is NotUndefined<T> {
|
||||||
|
return !isUndefined(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查给定的值是否为奇数
|
||||||
|
* @param value 要检查的值
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function isOdd(value: number): boolean {
|
||||||
|
if (typeof value !== 'number') {
|
||||||
|
throw new Error('输入必须为数字')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用取模运算符来判断是否为奇数
|
||||||
|
// 如果 number 除以 2 的余数为 1,就是奇数
|
||||||
|
// 否则是偶数
|
||||||
|
return value % 2 === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为base64图片
|
||||||
|
* @param {string} url
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
export function isBase64Image(url: string) {
|
||||||
|
// 使用正则表达式检查URL是否以"data:image"开头,这是Base64图片的常见前缀
|
||||||
|
return /^data:image\/(png|jpg|jpeg|gif|bmp);base64,/.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将外部传入的样式格式化为可读的 CSS 样式。
|
||||||
|
* @param {object | object[]} styles 外部传入的样式对象或数组
|
||||||
|
* @returns {string} 格式化后的 CSS 样式字符串
|
||||||
|
*/
|
||||||
|
export function objToStyle(styles: Record<string, any> | Record<string, any>[]): string {
|
||||||
|
// 如果 styles 是数组类型
|
||||||
|
if (isArray(styles)) {
|
||||||
|
// 使用过滤函数去除空值和 null 值的元素
|
||||||
|
// 对每个非空元素递归调用 objToStyle,然后通过分号连接
|
||||||
|
const result = styles
|
||||||
|
.filter(function (item) {
|
||||||
|
return item != null && item !== ''
|
||||||
|
})
|
||||||
|
.map(function (item) {
|
||||||
|
return objToStyle(item)
|
||||||
|
})
|
||||||
|
.join(';')
|
||||||
|
|
||||||
|
// 如果结果不为空,确保末尾有分号
|
||||||
|
return result ? (result.endsWith(';') ? result : result + ';') : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isString(styles)) {
|
||||||
|
// 如果是字符串且不为空,确保末尾有分号
|
||||||
|
return styles ? (styles.endsWith(';') ? styles : styles + ';') : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 styles 是对象类型
|
||||||
|
if (isObj(styles)) {
|
||||||
|
// 使用 Object.keys 获取所有属性名
|
||||||
|
// 使用过滤函数去除值为 null 或空字符串的属性
|
||||||
|
// 对每个属性名和属性值进行格式化,通过分号连接
|
||||||
|
const result = Object.keys(styles)
|
||||||
|
.filter(function (key) {
|
||||||
|
return styles[key] != null && styles[key] !== ''
|
||||||
|
})
|
||||||
|
.map(function (key) {
|
||||||
|
// 使用 kebabCase 函数将属性名转换为 kebab-case 格式
|
||||||
|
// 将属性名和属性值格式化为 CSS 样式的键值对
|
||||||
|
return [kebabCase(key), styles[key]].join(':')
|
||||||
|
})
|
||||||
|
.join(';')
|
||||||
|
|
||||||
|
// 如果结果不为空,确保末尾有分号
|
||||||
|
return result ? (result.endsWith(';') ? result : result + ';') : ''
|
||||||
|
}
|
||||||
|
// 如果 styles 不是对象也不是数组,则直接返回
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个对象是否包含任何字段
|
||||||
|
* @param obj 要检查的对象
|
||||||
|
* @returns {boolean} 如果对象为空(不包含任何字段)则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function hasFields(obj: unknown): boolean {
|
||||||
|
// 如果不是对象类型或为 null,则认为没有字段
|
||||||
|
if (!isObj(obj) || obj === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Object.keys 检查对象是否有属性
|
||||||
|
return Object.keys(obj).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断一个对象是否为空对象(不包含任何字段)
|
||||||
|
* @param obj 要检查的对象
|
||||||
|
* @returns {boolean} 如果对象为空(不包含任何字段)则返回 true,否则返回 false
|
||||||
|
*/
|
||||||
|
export function isEmptyObj(obj: unknown): boolean {
|
||||||
|
return !hasFields(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestAnimationFrame = (cb = () => {}) => {
|
||||||
|
return new AbortablePromise((resolve) => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
clearInterval(timer)
|
||||||
|
resolve(true)
|
||||||
|
cb()
|
||||||
|
}, 1000 / 30)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停指定时间函数
|
||||||
|
* @param ms 延迟时间
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pause = (ms: number = 1000 / 30) => {
|
||||||
|
return new AbortablePromise((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
resolve(true)
|
||||||
|
}, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深拷贝函数,用于将对象进行完整复制。
|
||||||
|
* @param obj 要深拷贝的对象
|
||||||
|
* @param cache 用于缓存已复制的对象,防止循环引用
|
||||||
|
* @returns 深拷贝后的对象副本
|
||||||
|
*/
|
||||||
|
export function deepClone<T>(obj: T, cache: Map<any, any> = new Map()): T {
|
||||||
|
// 如果对象为 null 或或者不是对象类型,则直接返回该对象
|
||||||
|
if (obj === null || typeof obj !== 'object') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特殊对象类型:日期、正则表达式、错误对象
|
||||||
|
if (isDate(obj)) {
|
||||||
|
return new Date(obj.getTime()) as any
|
||||||
|
}
|
||||||
|
if (obj instanceof RegExp) {
|
||||||
|
return new RegExp(obj.source, obj.flags) as any
|
||||||
|
}
|
||||||
|
if (obj instanceof Error) {
|
||||||
|
const errorCopy = new Error(obj.message) as any
|
||||||
|
errorCopy.stack = obj.stack
|
||||||
|
return errorCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查缓存中是否已存在该对象的复制
|
||||||
|
if (cache.has(obj)) {
|
||||||
|
return cache.get(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据原始对象的类型创建对应的空对象或数组
|
||||||
|
const copy: any = Array.isArray(obj) ? [] : {}
|
||||||
|
|
||||||
|
// 将当前对象添加到缓存中
|
||||||
|
cache.set(obj, copy)
|
||||||
|
|
||||||
|
// 递归地深拷贝对象的每个属性
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
copy[key] = deepClone(obj[key], cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy as T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度合并两个对象。
|
||||||
|
* @param target 目标对象,将合并的结果存放在此对象中
|
||||||
|
* @param source 源对象,要合并到目标对象的对象
|
||||||
|
* @returns 合并后的目标对象
|
||||||
|
*/
|
||||||
|
export function deepMerge<T extends Record<string, any>>(target: T, source: Record<string, any>): T {
|
||||||
|
// 深拷贝目标对象,避免修改原始对象
|
||||||
|
target = deepClone(target)
|
||||||
|
|
||||||
|
// 检查目标和源是否都是对象类型
|
||||||
|
if (typeof target !== 'object' || typeof source !== 'object') {
|
||||||
|
throw new Error('Both target and source must be objects.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历源对象的属性
|
||||||
|
for (const prop in source) {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (!source.hasOwnProperty(prop))
|
||||||
|
continue
|
||||||
|
// 使用类型断言,告诉 TypeScript 这是有效的属性
|
||||||
|
;(target as Record<string, any>)[prop] = source[prop]
|
||||||
|
}
|
||||||
|
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度合并两个对象。
|
||||||
|
* @param target
|
||||||
|
* @param source
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function deepAssign(target: Record<string, any>, source: Record<string, any>): Record<string, any> {
|
||||||
|
Object.keys(source).forEach((key) => {
|
||||||
|
const targetValue = target[key]
|
||||||
|
const newObjValue = source[key]
|
||||||
|
if (isObj(targetValue) && isObj(newObjValue)) {
|
||||||
|
deepAssign(targetValue, newObjValue)
|
||||||
|
} else {
|
||||||
|
target[key] = newObjValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建带参数的URL
|
||||||
|
* @param baseUrl 基础URL
|
||||||
|
* @param params 参数对象,键值对表示要添加到URL的参数
|
||||||
|
* @returns 返回带有参数的URL
|
||||||
|
*/
|
||||||
|
export function buildUrlWithParams(baseUrl: string, params: Record<string, string>) {
|
||||||
|
// 将参数对象转换为查询字符串
|
||||||
|
const queryString = Object.entries(params)
|
||||||
|
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||||
|
.join('&')
|
||||||
|
|
||||||
|
// 检查基础URL是否已包含查询字符串,并选择适当的分隔符
|
||||||
|
const separator = baseUrl.includes('?') ? '&' : '?'
|
||||||
|
|
||||||
|
// 返回带有参数的URL
|
||||||
|
return `${baseUrl}${separator}${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DebounceOptions = {
|
||||||
|
leading?: boolean // 是否在延迟时间开始时调用函数
|
||||||
|
trailing?: boolean // 是否在延迟时间结束时调用函数
|
||||||
|
}
|
||||||
|
|
||||||
|
export function debounce<T extends (...args: any[]) => any>(func: T, wait: number, options: DebounceOptions = {}): T {
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let lastArgs: any[] | undefined
|
||||||
|
let lastThis: any
|
||||||
|
let result: ReturnType<T> | undefined
|
||||||
|
const leading = isDef(options.leading) ? options.leading : false
|
||||||
|
const trailing = isDef(options.trailing) ? options.trailing : true
|
||||||
|
|
||||||
|
function invokeFunc() {
|
||||||
|
if (lastArgs !== undefined) {
|
||||||
|
result = func.apply(lastThis, lastArgs)
|
||||||
|
lastArgs = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
timeoutId = null
|
||||||
|
if (trailing) {
|
||||||
|
invokeFunc()
|
||||||
|
}
|
||||||
|
}, wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelTimer() {
|
||||||
|
if (timeoutId !== null) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounced(this: any, ...args: Parameters<T>): ReturnType<T> | undefined {
|
||||||
|
lastArgs = args
|
||||||
|
lastThis = this
|
||||||
|
|
||||||
|
if (timeoutId === null) {
|
||||||
|
if (leading) {
|
||||||
|
invokeFunc()
|
||||||
|
}
|
||||||
|
startTimer()
|
||||||
|
} else if (trailing) {
|
||||||
|
cancelTimer()
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return debounced as T
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
export function throttle(func: Function, wait: number): Function {
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let previous: number = 0
|
||||||
|
|
||||||
|
const throttled = function (this: any, ...args: any[]) {
|
||||||
|
const now = Date.now()
|
||||||
|
const remaining = wait - (now - previous)
|
||||||
|
|
||||||
|
if (remaining <= 0) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = null
|
||||||
|
}
|
||||||
|
previous = now
|
||||||
|
func.apply(this, args)
|
||||||
|
} else if (!timeout) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
previous = Date.now()
|
||||||
|
timeout = null
|
||||||
|
func.apply(this, args)
|
||||||
|
}, remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return throttled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据属性路径获取对象中的属性值
|
||||||
|
* @param obj 目标对象
|
||||||
|
* @param path 属性路径,可以是字符串或字符串数组
|
||||||
|
* @returns 属性值,如果属性不存在或中间的属性为 null 或 undefined,则返回 undefined
|
||||||
|
*/
|
||||||
|
export const getPropByPath = (obj: any, path: string): any => {
|
||||||
|
const keys: string[] = path.split('.')
|
||||||
|
|
||||||
|
try {
|
||||||
|
return keys.reduce((acc: any, key: string) => (acc !== undefined && acc !== null ? acc[key] : undefined), obj)
|
||||||
|
} catch (error) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查一个值是否为Date类型
|
||||||
|
* @param val 要检查的值
|
||||||
|
* @returns 如果值是Date类型,则返回true,否则返回false
|
||||||
|
*/
|
||||||
|
export const isDate = (val: unknown): val is Date => Object.prototype.toString.call(val) === '[object Date]' && !Number.isNaN((val as Date).getTime())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查提供的URL是否为视频链接。
|
||||||
|
* @param url 需要检查的URL字符串。
|
||||||
|
* @returns 返回一个布尔值,如果URL是视频链接则为true,否则为false。
|
||||||
|
*/
|
||||||
|
export function isVideoUrl(url: string): boolean {
|
||||||
|
// 使用正则表达式匹配视频文件类型的URL
|
||||||
|
const videoRegex = /\.(ogm|webm|ogv|asx|m4v|mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|video)(?=$|[?#])/i
|
||||||
|
return videoRegex.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查提供的URL是否为图片URL。
|
||||||
|
* @param url 待检查的URL字符串。
|
||||||
|
* @returns 返回一个布尔值,如果URL是图片格式,则为true;否则为false。
|
||||||
|
*/
|
||||||
|
export function isImageUrl(url: string): boolean {
|
||||||
|
// 使用正则表达式匹配图片URL
|
||||||
|
const imageRegex = /\.(xbm|tif|pjp|apng|svgz|jpeg|jpg|heif|ico|tiff|heic|pjpeg|avif|gif|png|svg|webp|jfif|bmp|dpg|image)(?=$|[?#])/i
|
||||||
|
return imageRegex.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断环境是否是H5
|
||||||
|
*/
|
||||||
|
export const isH5 = (() => {
|
||||||
|
let isH5 = false
|
||||||
|
// #ifdef H5
|
||||||
|
isH5 = true
|
||||||
|
// #endif
|
||||||
|
return isH5
|
||||||
|
})()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 剔除对象中的某些属性
|
||||||
|
* @param obj
|
||||||
|
* @param predicate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function omitBy<O extends Record<string, any>>(obj: O, predicate: (value: any, key: keyof O) => boolean): Partial<O> {
|
||||||
|
const newObj = deepClone(obj)
|
||||||
|
Object.keys(newObj).forEach((key) => predicate(newObj[key], key) && delete newObj[key]) // 遍历对象的键,删除值为不满足predicate的字段
|
||||||
|
return newObj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓动函数,用于在动画或过渡效果中根据时间参数计算当前值
|
||||||
|
* @param t 当前时间,通常是从动画开始经过的时间
|
||||||
|
* @param b 初始值,动画属性的初始值
|
||||||
|
* @param c 变化量,动画属性的目标值与初始值的差值
|
||||||
|
* @param d 持续时间,动画持续的总时间长度
|
||||||
|
* @returns 计算出的当前值
|
||||||
|
*/
|
||||||
|
export function easingFn(t: number = 0, b: number = 0, c: number = 0, d: number = 0): number {
|
||||||
|
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数组中寻找最接近目标值的元素
|
||||||
|
*
|
||||||
|
* @param arr 数组
|
||||||
|
* @param target 目标值
|
||||||
|
* @returns 最接近目标值的元素
|
||||||
|
*/
|
||||||
|
export function closest(arr: number[], target: number) {
|
||||||
|
return arr.reduce((prev, curr) => (Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev))
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
export { useCell } from './useCell'
|
||||||
|
export { useChildren, flattenVNodes, sortChildren } from './useChildren'
|
||||||
|
export { useCountDown } from './useCountDown'
|
||||||
|
export { useLockScroll } from './useLockScroll'
|
||||||
|
export { useParent } from './useParent'
|
||||||
|
export { usePopover } from './usePopover'
|
||||||
|
export { useQueue } from './useQueue'
|
||||||
|
export { useRaf } from './useRaf'
|
||||||
|
export { useTouch } from './useTouch'
|
||||||
|
export { useTranslate } from './useTranslate'
|
||||||
|
export { useUpload } from './useUpload'
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import { useParent } from './useParent'
|
||||||
|
import { CELL_GROUP_KEY } from '../wd-cell-group/types'
|
||||||
|
|
||||||
|
export function useCell() {
|
||||||
|
const { parent: cellGroup, index } = useParent(CELL_GROUP_KEY)
|
||||||
|
|
||||||
|
const border = computed(() => {
|
||||||
|
return cellGroup && cellGroup.props.border && index.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return { border }
|
||||||
|
}
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
import {
|
||||||
|
provide,
|
||||||
|
reactive,
|
||||||
|
getCurrentInstance,
|
||||||
|
type VNode,
|
||||||
|
type InjectionKey,
|
||||||
|
type VNodeNormalizedChildren,
|
||||||
|
type ComponentPublicInstance,
|
||||||
|
type ComponentInternalInstance
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
|
// 小程序端不支持从vue导出的isVNode方法,参考uni-mp-vue的实现
|
||||||
|
function isVNode(value: any): value is VNode {
|
||||||
|
return value ? value.__v_isVNode === true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenVNodes(children: VNodeNormalizedChildren) {
|
||||||
|
const result: VNode[] = []
|
||||||
|
|
||||||
|
const traverse = (children: VNodeNormalizedChildren) => {
|
||||||
|
if (Array.isArray(children)) {
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (isVNode(child)) {
|
||||||
|
result.push(child)
|
||||||
|
|
||||||
|
if (child.component?.subTree) {
|
||||||
|
result.push(child.component.subTree)
|
||||||
|
traverse(child.component.subTree.children)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.children) {
|
||||||
|
traverse(child.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(children)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const findVNodeIndex = (vnodes: VNode[], vnode: VNode) => {
|
||||||
|
const index = vnodes.indexOf(vnode)
|
||||||
|
if (index === -1) {
|
||||||
|
return vnodes.findIndex((item) => vnode.key !== undefined && vnode.key !== null && item.type === vnode.type && item.key === vnode.key)
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort children instances by vnodes order
|
||||||
|
export function sortChildren(
|
||||||
|
parent: ComponentInternalInstance,
|
||||||
|
publicChildren: ComponentPublicInstance[],
|
||||||
|
internalChildren: ComponentInternalInstance[]
|
||||||
|
) {
|
||||||
|
const vnodes = parent && parent.subTree && parent.subTree.children ? flattenVNodes(parent.subTree.children) : []
|
||||||
|
|
||||||
|
internalChildren.sort((a, b) => findVNodeIndex(vnodes, a.vnode) - findVNodeIndex(vnodes, b.vnode))
|
||||||
|
|
||||||
|
const orderedPublicChildren = internalChildren.map((item) => item.proxy!)
|
||||||
|
|
||||||
|
publicChildren.sort((a, b) => {
|
||||||
|
const indexA = orderedPublicChildren.indexOf(a)
|
||||||
|
const indexB = orderedPublicChildren.indexOf(b)
|
||||||
|
return indexA - indexB
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useChildren<
|
||||||
|
// eslint-disable-next-line
|
||||||
|
Child extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
|
||||||
|
ProvideValue = never
|
||||||
|
>(key: InjectionKey<ProvideValue>) {
|
||||||
|
const publicChildren: Child[] = reactive([])
|
||||||
|
const internalChildren: ComponentInternalInstance[] = reactive([])
|
||||||
|
const parent = getCurrentInstance()!
|
||||||
|
|
||||||
|
const linkChildren = (value?: ProvideValue) => {
|
||||||
|
const link = (child: ComponentInternalInstance) => {
|
||||||
|
if (child.proxy) {
|
||||||
|
internalChildren.push(child)
|
||||||
|
publicChildren.push(child.proxy as Child)
|
||||||
|
sortChildren(parent, publicChildren, internalChildren)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlink = (child: ComponentInternalInstance) => {
|
||||||
|
const index = internalChildren.indexOf(child)
|
||||||
|
publicChildren.splice(index, 1)
|
||||||
|
internalChildren.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(
|
||||||
|
key,
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
link,
|
||||||
|
unlink,
|
||||||
|
children: publicChildren,
|
||||||
|
internalChildren
|
||||||
|
},
|
||||||
|
value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
children: publicChildren,
|
||||||
|
linkChildren
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
import { ref, computed, onBeforeUnmount } from 'vue'
|
||||||
|
import { isDef } from '../common/util'
|
||||||
|
import { useRaf } from './useRaf'
|
||||||
|
|
||||||
|
// 定义倒计时时间的数据结构
|
||||||
|
export type CurrentTime = {
|
||||||
|
days: number
|
||||||
|
hours: number
|
||||||
|
total: number
|
||||||
|
minutes: number
|
||||||
|
seconds: number
|
||||||
|
milliseconds: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义倒计时的配置项
|
||||||
|
export type UseCountDownOptions = {
|
||||||
|
time: number // 倒计时总时间,单位为毫秒
|
||||||
|
millisecond?: boolean // 是否开启毫秒级倒计时,默认为 false
|
||||||
|
onChange?: (current: CurrentTime) => void // 倒计时每次变化时的回调函数
|
||||||
|
onFinish?: () => void // 倒计时结束时的回调函数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义常量
|
||||||
|
const SECOND = 1000
|
||||||
|
const MINUTE = 60 * SECOND
|
||||||
|
const HOUR = 60 * MINUTE
|
||||||
|
const DAY = 24 * HOUR
|
||||||
|
|
||||||
|
// 将时间转换为倒计时数据结构
|
||||||
|
function parseTime(time: number): CurrentTime {
|
||||||
|
const days = Math.floor(time / DAY)
|
||||||
|
const hours = Math.floor((time % DAY) / HOUR)
|
||||||
|
const minutes = Math.floor((time % HOUR) / MINUTE)
|
||||||
|
const seconds = Math.floor((time % MINUTE) / SECOND)
|
||||||
|
const milliseconds = Math.floor(time % SECOND)
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: time,
|
||||||
|
days,
|
||||||
|
hours,
|
||||||
|
minutes,
|
||||||
|
seconds,
|
||||||
|
milliseconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断两个时间是否在同一秒内
|
||||||
|
function isSameSecond(time1: number, time2: number): boolean {
|
||||||
|
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义 useCountDown 函数
|
||||||
|
export function useCountDown(options: UseCountDownOptions) {
|
||||||
|
let endTime: number // 结束时间
|
||||||
|
let counting: boolean // 是否计时中
|
||||||
|
|
||||||
|
const { start: startRaf, cancel: cancelRaf } = useRaf(tick)
|
||||||
|
|
||||||
|
const remain = ref(options.time) // 剩余时间
|
||||||
|
const current = computed(() => parseTime(remain.value)) // 当前倒计时数据
|
||||||
|
|
||||||
|
// 暂停倒计时
|
||||||
|
const pause = () => {
|
||||||
|
counting = false
|
||||||
|
cancelRaf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前剩余时间
|
||||||
|
const getCurrentRemain = () => Math.max(endTime - Date.now(), 0)
|
||||||
|
|
||||||
|
// 设置剩余时间
|
||||||
|
const setRemain = (value: number) => {
|
||||||
|
remain.value = value
|
||||||
|
isDef(options.onChange) && options.onChange(current.value)
|
||||||
|
if (value === 0) {
|
||||||
|
pause()
|
||||||
|
isDef(options.onFinish) && options.onFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每毫秒更新一次倒计时
|
||||||
|
const microTick = () => {
|
||||||
|
if (counting) {
|
||||||
|
setRemain(getCurrentRemain())
|
||||||
|
if (remain.value > 0) {
|
||||||
|
startRaf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每秒更新一次倒计时
|
||||||
|
const macroTick = () => {
|
||||||
|
if (counting) {
|
||||||
|
const remainRemain = getCurrentRemain()
|
||||||
|
if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) {
|
||||||
|
setRemain(remainRemain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remain.value > 0) {
|
||||||
|
startRaf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据配置项选择更新方式
|
||||||
|
function tick() {
|
||||||
|
if (options.millisecond) {
|
||||||
|
microTick()
|
||||||
|
} else {
|
||||||
|
macroTick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
const start = () => {
|
||||||
|
if (!counting) {
|
||||||
|
endTime = Date.now() + remain.value
|
||||||
|
counting = true
|
||||||
|
startRaf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置倒计时
|
||||||
|
const reset = (totalTime: number = options.time) => {
|
||||||
|
pause()
|
||||||
|
remain.value = totalTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在组件卸载前暂停倒计时
|
||||||
|
onBeforeUnmount(pause)
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
pause,
|
||||||
|
reset,
|
||||||
|
current
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { onBeforeUnmount, onDeactivated, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
export function useLockScroll(shouldLock: () => boolean) {
|
||||||
|
const scrollLockCount = ref(0)
|
||||||
|
|
||||||
|
const lock = () => {
|
||||||
|
if (scrollLockCount.value === 0) {
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
scrollLockCount.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
const unlock = () => {
|
||||||
|
if (scrollLockCount.value > 0) {
|
||||||
|
scrollLockCount.value--
|
||||||
|
if (scrollLockCount.value === 0) {
|
||||||
|
document.getElementsByTagName('body')[0].style.overflow = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
shouldLock() && unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(shouldLock, (value) => {
|
||||||
|
value ? lock() : unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
onDeactivated(destroy)
|
||||||
|
onBeforeUnmount(destroy)
|
||||||
|
|
||||||
|
return {
|
||||||
|
lock,
|
||||||
|
unlock
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
ref,
|
||||||
|
inject,
|
||||||
|
computed,
|
||||||
|
onUnmounted,
|
||||||
|
type InjectionKey,
|
||||||
|
getCurrentInstance,
|
||||||
|
type ComponentPublicInstance,
|
||||||
|
type ComponentInternalInstance
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
|
type ParentProvide<T> = T & {
|
||||||
|
link(child: ComponentInternalInstance): void
|
||||||
|
unlink(child: ComponentInternalInstance): void
|
||||||
|
children: ComponentPublicInstance[]
|
||||||
|
internalChildren: ComponentInternalInstance[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useParent<T>(key: InjectionKey<ParentProvide<T>>) {
|
||||||
|
const parent = inject(key, null)
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
const instance = getCurrentInstance()!
|
||||||
|
const { link, unlink, internalChildren } = parent
|
||||||
|
|
||||||
|
link(instance)
|
||||||
|
onUnmounted(() => unlink(instance))
|
||||||
|
|
||||||
|
const index = computed(() => internalChildren.indexOf(instance))
|
||||||
|
|
||||||
|
return {
|
||||||
|
parent,
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
parent: null,
|
||||||
|
index: ref(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
import { getCurrentInstance, ref } from 'vue'
|
||||||
|
import { getRect, isObj } from '../common/util'
|
||||||
|
|
||||||
|
export function usePopover(visibleArrow = true) {
|
||||||
|
const { proxy } = getCurrentInstance() as any
|
||||||
|
const popStyle = ref<string>('')
|
||||||
|
const arrowStyle = ref<string>('')
|
||||||
|
const showStyle = ref<string>('')
|
||||||
|
const arrowClass = ref<string>('')
|
||||||
|
const popWidth = ref<number>(0)
|
||||||
|
const popHeight = ref<number>(0)
|
||||||
|
const left = ref<number>(0)
|
||||||
|
const bottom = ref<number>(0)
|
||||||
|
const width = ref<number>(0)
|
||||||
|
const height = ref<number>(0)
|
||||||
|
const top = ref<number>(0)
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
function init(
|
||||||
|
placement:
|
||||||
|
| 'top'
|
||||||
|
| 'top-start'
|
||||||
|
| 'top-end'
|
||||||
|
| 'bottom'
|
||||||
|
| 'bottom-start'
|
||||||
|
| 'bottom-end'
|
||||||
|
| 'left'
|
||||||
|
| 'left-start'
|
||||||
|
| 'left-end'
|
||||||
|
| 'right'
|
||||||
|
| 'right-start'
|
||||||
|
| 'right-end',
|
||||||
|
visibleArrow: boolean,
|
||||||
|
selector: string
|
||||||
|
) {
|
||||||
|
// 初始化 class
|
||||||
|
if (visibleArrow) {
|
||||||
|
const arrowClassArr = [
|
||||||
|
`wd-${selector}__arrow`,
|
||||||
|
placement === 'bottom' || placement === 'bottom-start' || placement === 'bottom-end' ? `wd-${selector}__arrow-up` : '',
|
||||||
|
placement === 'left' || placement === 'left-start' || placement === 'left-end' ? `wd-${selector}__arrow-right` : '',
|
||||||
|
placement === 'right' || placement === 'right-start' || placement === 'right-end' ? `wd-${selector}__arrow-left` : '',
|
||||||
|
placement === 'top' || placement === 'top-start' || placement === 'top-end' ? `wd-${selector}__arrow-down` : ''
|
||||||
|
]
|
||||||
|
arrowClass.value = arrowClassArr.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化数据获取
|
||||||
|
getRect('#target', false, proxy).then((rect) => {
|
||||||
|
if (!rect) return
|
||||||
|
left.value = rect.left as number
|
||||||
|
bottom.value = rect.bottom as number
|
||||||
|
width.value = rect.width as number
|
||||||
|
height.value = rect.height as number
|
||||||
|
top.value = rect.top as number
|
||||||
|
})
|
||||||
|
// 用透明度可在初始化时获取到pop尺寸
|
||||||
|
getRect('#pos', false, proxy).then((rect) => {
|
||||||
|
if (!rect) return
|
||||||
|
popWidth.value = rect.width as number
|
||||||
|
popHeight.value = rect.height as number
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function control(
|
||||||
|
placement:
|
||||||
|
| 'top'
|
||||||
|
| 'top-start'
|
||||||
|
| 'top-end'
|
||||||
|
| 'bottom'
|
||||||
|
| 'bottom-start'
|
||||||
|
| 'bottom-end'
|
||||||
|
| 'left'
|
||||||
|
| 'left-start'
|
||||||
|
| 'left-end'
|
||||||
|
| 'right'
|
||||||
|
| 'right-start'
|
||||||
|
| 'right-end',
|
||||||
|
offset: number | number[] | Record<'x' | 'y', number>
|
||||||
|
) {
|
||||||
|
// arrow size
|
||||||
|
const arrowSize = visibleArrow ? 9 : 0
|
||||||
|
// 上下位(纵轴)对应的距离左边的距离
|
||||||
|
const verticalX = width.value / 2
|
||||||
|
// 上下位(纵轴)对应的距离底部的距离
|
||||||
|
const verticalY = arrowSize + height.value + 5
|
||||||
|
// 左右位(横轴)对应的距离左边的距离
|
||||||
|
const horizontalX = width.value + arrowSize + 5
|
||||||
|
// 左右位(横轴)对应的距离底部的距离
|
||||||
|
const horizontalY = height.value / 2
|
||||||
|
|
||||||
|
let offsetX = 0
|
||||||
|
let offsetY = 0
|
||||||
|
if (Array.isArray(offset)) {
|
||||||
|
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset[0]
|
||||||
|
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + (offset[1] ? offset[1] : offset[0])
|
||||||
|
} else if (isObj(offset)) {
|
||||||
|
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset.x
|
||||||
|
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset.y
|
||||||
|
} else {
|
||||||
|
offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset
|
||||||
|
offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset
|
||||||
|
}
|
||||||
|
// const offsetX = (verticalX - 17 > 0 ? 0 : verticalX - 25) + offset
|
||||||
|
// const offsetY = (horizontalY - 17 > 0 ? 0 : horizontalY - 25) + offset
|
||||||
|
|
||||||
|
const placements = new Map([
|
||||||
|
// 上
|
||||||
|
['top', [`left: ${verticalX}px; bottom: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
|
||||||
|
[
|
||||||
|
'top-start',
|
||||||
|
[
|
||||||
|
`left: ${offsetX}px; bottom: ${verticalY}px;`,
|
||||||
|
`left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'top-end',
|
||||||
|
[
|
||||||
|
`right: ${offsetX}px; bottom: ${verticalY}px;`,
|
||||||
|
`right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// 下
|
||||||
|
['bottom', [`left: ${verticalX}px; top: ${verticalY}px; transform: translateX(-50%);`, 'left: 50%;']],
|
||||||
|
[
|
||||||
|
'bottom-start',
|
||||||
|
[`left: ${offsetX}px; top: ${verticalY}px;`, `left: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px;`]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'bottom-end',
|
||||||
|
[
|
||||||
|
`right: ${offsetX}px; top: ${verticalY}px;`,
|
||||||
|
`right: ${(popWidth.value >= width.value ? width.value / 2 : popWidth.value - 25) - offsetX}px; transform: translateX(50%);`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// 左
|
||||||
|
['left', [`right: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
|
||||||
|
[
|
||||||
|
'left-start',
|
||||||
|
[
|
||||||
|
`right: ${horizontalX}px; top: ${offsetY}px;`,
|
||||||
|
`top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'left-end',
|
||||||
|
[
|
||||||
|
`right: ${horizontalX}px; bottom: ${offsetY}px;`,
|
||||||
|
`bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// 右
|
||||||
|
['right', [`left: ${horizontalX}px; top: ${horizontalY}px; transform: translateY(-50%);`, 'top: 50%']],
|
||||||
|
[
|
||||||
|
'right-start',
|
||||||
|
[
|
||||||
|
`left: ${horizontalX}px; top: ${offsetY}px;`,
|
||||||
|
`top: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px;`
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'right-end',
|
||||||
|
[
|
||||||
|
`left: ${horizontalX}px; bottom: ${offsetY}px;`,
|
||||||
|
`bottom: ${(popHeight.value >= height.value ? height.value / 2 : popHeight.value - 20) - offsetY}px; transform: translateY(50%);`
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
popStyle.value = placements.get(placement)![0]
|
||||||
|
arrowStyle.value = placements.get(placement)![1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return { popStyle, arrowStyle, showStyle, arrowClass, init, control, noop }
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { type Ref, provide, ref } from 'vue'
|
||||||
|
|
||||||
|
export const queueKey = '__QUEUE_KEY__'
|
||||||
|
|
||||||
|
export interface Queue {
|
||||||
|
queue: Ref<any[]>
|
||||||
|
pushToQueue: (comp: any) => void
|
||||||
|
removeFromQueue: (comp: any) => void
|
||||||
|
closeOther: (comp: any) => void
|
||||||
|
closeOutside: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useQueue() {
|
||||||
|
const queue = ref<any[]>([])
|
||||||
|
|
||||||
|
function pushToQueue(comp: any) {
|
||||||
|
queue.value.push(comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromQueue(comp: any) {
|
||||||
|
queue.value = queue.value.filter((item) => {
|
||||||
|
return item.$.uid !== comp.$.uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOther(comp: any) {
|
||||||
|
queue.value.forEach((item) => {
|
||||||
|
if (item.$.uid !== comp.$.uid) {
|
||||||
|
item.$.exposed.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOutside() {
|
||||||
|
queue.value.forEach((item) => {
|
||||||
|
item.$.exposed.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(queueKey, {
|
||||||
|
queue,
|
||||||
|
pushToQueue,
|
||||||
|
removeFromQueue,
|
||||||
|
closeOther,
|
||||||
|
closeOutside
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeOther,
|
||||||
|
closeOutside
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { ref, onUnmounted } from 'vue'
|
||||||
|
import { isDef, isH5, isNumber } from '../common/util'
|
||||||
|
|
||||||
|
// 定义回调函数类型
|
||||||
|
type RafCallback = (time: number) => void
|
||||||
|
|
||||||
|
export function useRaf(callback: RafCallback) {
|
||||||
|
const requestRef = ref<number | null | ReturnType<typeof setTimeout>>(null)
|
||||||
|
|
||||||
|
// 启动动画帧
|
||||||
|
const start = () => {
|
||||||
|
const handle = (time: number) => {
|
||||||
|
callback(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isH5) {
|
||||||
|
requestRef.value = requestAnimationFrame(handle)
|
||||||
|
} else {
|
||||||
|
requestRef.value = setTimeout(() => handle(Date.now()), 1000 / 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消动画帧
|
||||||
|
const cancel = () => {
|
||||||
|
if (isH5 && isNumber(requestRef.value)) {
|
||||||
|
cancelAnimationFrame(requestRef.value!)
|
||||||
|
} else if (isDef(requestRef.value)) {
|
||||||
|
clearTimeout(requestRef.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancel()
|
||||||
|
})
|
||||||
|
|
||||||
|
return { start, cancel }
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export function useTouch() {
|
||||||
|
const direction = ref<string>('')
|
||||||
|
const deltaX = ref<number>(0)
|
||||||
|
const deltaY = ref<number>(0)
|
||||||
|
const offsetX = ref<number>(0)
|
||||||
|
const offsetY = ref<number>(0)
|
||||||
|
const startX = ref<number>(0)
|
||||||
|
const startY = ref<number>(0)
|
||||||
|
|
||||||
|
function touchStart(event: any) {
|
||||||
|
const touch = event.touches[0]
|
||||||
|
direction.value = ''
|
||||||
|
deltaX.value = 0
|
||||||
|
deltaY.value = 0
|
||||||
|
offsetX.value = 0
|
||||||
|
offsetY.value = 0
|
||||||
|
startX.value = touch.clientX
|
||||||
|
startY.value = touch.clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
function touchMove(event: any) {
|
||||||
|
const touch = event.touches[0]
|
||||||
|
deltaX.value = touch.clientX - startX.value
|
||||||
|
deltaY.value = touch.clientY - startY.value
|
||||||
|
offsetX.value = Math.abs(deltaX.value)
|
||||||
|
offsetY.value = Math.abs(deltaY.value)
|
||||||
|
direction.value = offsetX.value > offsetY.value ? 'horizontal' : offsetX.value < offsetY.value ? 'vertical' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
touchStart,
|
||||||
|
touchMove,
|
||||||
|
direction,
|
||||||
|
deltaX,
|
||||||
|
deltaY,
|
||||||
|
offsetX,
|
||||||
|
offsetY,
|
||||||
|
startX,
|
||||||
|
startY
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { camelCase, getPropByPath, isDef, isFunction } from '../common/util'
|
||||||
|
import Locale from '../../locale'
|
||||||
|
|
||||||
|
export const useTranslate = (name?: string) => {
|
||||||
|
const prefix = name ? camelCase(name) + '.' : ''
|
||||||
|
const translate = (key: string, ...args: unknown[]) => {
|
||||||
|
const currentMessages = Locale.messages()
|
||||||
|
const message = getPropByPath(currentMessages, prefix + key)
|
||||||
|
return isFunction(message) ? message(...args) : isDef(message) ? message : `${prefix}${key}`
|
||||||
|
}
|
||||||
|
return { translate }
|
||||||
|
}
|
||||||
@ -0,0 +1,364 @@
|
|||||||
|
import { isArray, isDef, isFunction } from '../common/util'
|
||||||
|
import type { ChooseFile, ChooseFileOption, UploadFileItem, UploadMethod, UploadStatusType } from '../wd-upload/types'
|
||||||
|
|
||||||
|
export const UPLOAD_STATUS: Record<string, UploadStatusType> = {
|
||||||
|
PENDING: 'pending',
|
||||||
|
LOADING: 'loading',
|
||||||
|
SUCCESS: 'success',
|
||||||
|
FAIL: 'fail'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseUploadReturn {
|
||||||
|
// 开始上传文件
|
||||||
|
startUpload: (file: UploadFileItem, options: UseUploadOptions) => UniApp.UploadTask | void | Promise<void>
|
||||||
|
// 中断上传
|
||||||
|
abort: (task?: UniApp.UploadTask) => void
|
||||||
|
// 上传状态常量
|
||||||
|
UPLOAD_STATUS: Record<string, UploadStatusType>
|
||||||
|
// 选择文件
|
||||||
|
chooseFile: (options: ChooseFileOption) => Promise<ChooseFile[]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseUploadOptions {
|
||||||
|
// 上传地址
|
||||||
|
action: string
|
||||||
|
// 请求头
|
||||||
|
header?: Record<string, any>
|
||||||
|
// 文件对应的 key
|
||||||
|
name?: string
|
||||||
|
// 其它表单数据
|
||||||
|
formData?: Record<string, any>
|
||||||
|
// 文件类型 仅支付宝支持且在支付宝平台必填
|
||||||
|
fileType?: 'image' | 'video' | 'audio'
|
||||||
|
// 成功状态码
|
||||||
|
statusCode?: number
|
||||||
|
// 文件状态的key
|
||||||
|
statusKey?: string
|
||||||
|
// 自定义上传方法
|
||||||
|
uploadMethod?: UploadMethod
|
||||||
|
// 上传成功回调
|
||||||
|
onSuccess?: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
|
||||||
|
// 上传失败回调
|
||||||
|
onError?: (res: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => void
|
||||||
|
// 上传进度回调
|
||||||
|
onProgress?: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => void
|
||||||
|
// 是否自动中断之前的上传任务
|
||||||
|
abortPrevious?: boolean
|
||||||
|
// 根据文件拓展名过滤(H5支持全部类型过滤,微信小程序支持all和file时过滤,其余平台不支持)
|
||||||
|
extension?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpload(): UseUploadReturn {
|
||||||
|
let currentTask: UniApp.UploadTask | null = null
|
||||||
|
|
||||||
|
// 中断上传
|
||||||
|
const abort = (task?: UniApp.UploadTask) => {
|
||||||
|
if (task) {
|
||||||
|
task.abort()
|
||||||
|
} else if (currentTask) {
|
||||||
|
currentTask.abort()
|
||||||
|
currentTask = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认上传方法
|
||||||
|
*/
|
||||||
|
const defaultUpload: UploadMethod = (file, formData, options) => {
|
||||||
|
// 如果配置了自动中断,则中断之前的上传任务
|
||||||
|
if (options.abortPrevious) {
|
||||||
|
abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadTask = uni.uploadFile({
|
||||||
|
url: options.action,
|
||||||
|
header: options.header,
|
||||||
|
name: options.name,
|
||||||
|
fileName: options.name,
|
||||||
|
fileType: options.fileType,
|
||||||
|
formData,
|
||||||
|
filePath: file.url,
|
||||||
|
success(res) {
|
||||||
|
if (res.statusCode === options.statusCode) {
|
||||||
|
// 上传成功
|
||||||
|
options.onSuccess(res, file, formData)
|
||||||
|
} else {
|
||||||
|
// 上传失败
|
||||||
|
options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
// 上传失败
|
||||||
|
options.onError(err, file, formData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
currentTask = uploadTask
|
||||||
|
|
||||||
|
// 获取当前文件加载的百分比
|
||||||
|
uploadTask.onProgressUpdate((res) => {
|
||||||
|
options.onProgress(res, file)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返回上传任务实例,让外部可以控制上传过程
|
||||||
|
return uploadTask
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始上传文件
|
||||||
|
*/
|
||||||
|
const startUpload = (file: UploadFileItem, options: UseUploadOptions) => {
|
||||||
|
const {
|
||||||
|
uploadMethod,
|
||||||
|
formData = {},
|
||||||
|
action,
|
||||||
|
name = 'file',
|
||||||
|
header = {},
|
||||||
|
fileType = 'image',
|
||||||
|
statusCode = 200,
|
||||||
|
statusKey = 'status',
|
||||||
|
abortPrevious = false
|
||||||
|
} = options
|
||||||
|
|
||||||
|
// 设置上传中状态
|
||||||
|
file[statusKey] = UPLOAD_STATUS.LOADING
|
||||||
|
|
||||||
|
const uploadOptions = {
|
||||||
|
action,
|
||||||
|
header,
|
||||||
|
name,
|
||||||
|
fileName: name,
|
||||||
|
fileType,
|
||||||
|
statusCode,
|
||||||
|
abortPrevious,
|
||||||
|
onSuccess: (res: UniApp.UploadFileSuccessCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
|
||||||
|
// 更新文件状态
|
||||||
|
file[statusKey] = UPLOAD_STATUS.SUCCESS
|
||||||
|
currentTask = null
|
||||||
|
options.onSuccess?.(res, file, formData)
|
||||||
|
},
|
||||||
|
onError: (error: UniApp.GeneralCallbackResult, file: UploadFileItem, formData: Record<string, any>) => {
|
||||||
|
// 更新文件状态和错误信息
|
||||||
|
file[statusKey] = UPLOAD_STATUS.FAIL
|
||||||
|
file.error = error.errMsg
|
||||||
|
currentTask = null
|
||||||
|
options.onError?.(error, file, formData)
|
||||||
|
},
|
||||||
|
onProgress: (res: UniApp.OnProgressUpdateResult, file: UploadFileItem) => {
|
||||||
|
// 更新上传进度
|
||||||
|
file.percent = res.progress
|
||||||
|
options.onProgress?.(res, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上传任务实例,支持外部获取uploadTask进行操作
|
||||||
|
if (isFunction(uploadMethod)) {
|
||||||
|
return uploadMethod(file, formData, uploadOptions)
|
||||||
|
} else {
|
||||||
|
return defaultUpload(file, formData, uploadOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化图片信息
|
||||||
|
*/
|
||||||
|
function formatImage(res: UniApp.ChooseImageSuccessCallbackResult): ChooseFile[] {
|
||||||
|
// #ifdef MP-DINGTALK
|
||||||
|
// 钉钉文件在files中
|
||||||
|
res.tempFiles = isDef((res as any).files) ? (res as any).files : res.tempFiles
|
||||||
|
// #endif
|
||||||
|
if (isArray(res.tempFiles)) {
|
||||||
|
return res.tempFiles.map((item: any) => ({
|
||||||
|
path: item.path || '',
|
||||||
|
name: item.name || '',
|
||||||
|
size: item.size,
|
||||||
|
type: 'image',
|
||||||
|
thumb: item.path || ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
path: (res.tempFiles as any).path || '',
|
||||||
|
name: (res.tempFiles as any).name || '',
|
||||||
|
size: (res.tempFiles as any).size,
|
||||||
|
type: 'image',
|
||||||
|
thumb: (res.tempFiles as any).path || ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化视频信息
|
||||||
|
*/
|
||||||
|
function formatVideo(res: UniApp.ChooseVideoSuccess): ChooseFile[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
path: res.tempFilePath || (res as any).filePath || '',
|
||||||
|
name: res.name || '',
|
||||||
|
size: res.size,
|
||||||
|
type: 'video',
|
||||||
|
thumb: (res as any).thumbTempFilePath || '',
|
||||||
|
duration: res.duration
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化媒体信息
|
||||||
|
*/
|
||||||
|
function formatMedia(res: UniApp.ChooseMediaSuccessCallbackResult): ChooseFile[] {
|
||||||
|
return res.tempFiles.map((item) => ({
|
||||||
|
type: item.fileType,
|
||||||
|
path: item.tempFilePath,
|
||||||
|
thumb: item.fileType === 'video' ? item.thumbTempFilePath : item.tempFilePath,
|
||||||
|
size: item.size,
|
||||||
|
duration: item.duration
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择文件
|
||||||
|
*/
|
||||||
|
function chooseFile({
|
||||||
|
multiple,
|
||||||
|
sizeType,
|
||||||
|
sourceType,
|
||||||
|
maxCount,
|
||||||
|
accept,
|
||||||
|
compressed,
|
||||||
|
maxDuration,
|
||||||
|
camera,
|
||||||
|
extension
|
||||||
|
}: ChooseFileOption): Promise<ChooseFile[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
switch (accept) {
|
||||||
|
case 'image':
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
mediaType: ['image'],
|
||||||
|
sourceType,
|
||||||
|
sizeType,
|
||||||
|
camera,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.chooseImage({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
sizeType,
|
||||||
|
sourceType,
|
||||||
|
// #ifdef H5
|
||||||
|
extension,
|
||||||
|
// #endif
|
||||||
|
success: (res) => resolve(formatImage(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
break
|
||||||
|
case 'video':
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
mediaType: ['video'],
|
||||||
|
sourceType,
|
||||||
|
camera,
|
||||||
|
maxDuration,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.chooseVideo({
|
||||||
|
sourceType,
|
||||||
|
compressed,
|
||||||
|
maxDuration,
|
||||||
|
camera,
|
||||||
|
// #ifdef H5
|
||||||
|
extension,
|
||||||
|
// #endif
|
||||||
|
success: (res) => resolve(formatVideo(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
break
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
case 'media':
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
sourceType,
|
||||||
|
sizeType,
|
||||||
|
camera,
|
||||||
|
maxDuration,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'file':
|
||||||
|
uni.chooseMessageFile({
|
||||||
|
count: multiple ? (isDef(maxCount) ? maxCount : 100) : 1,
|
||||||
|
type: accept,
|
||||||
|
extension,
|
||||||
|
success: (res) => resolve(res.tempFiles),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
break
|
||||||
|
// #endif
|
||||||
|
case 'all':
|
||||||
|
// #ifdef H5
|
||||||
|
uni.chooseFile({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
type: accept,
|
||||||
|
extension,
|
||||||
|
success: (res) => resolve(res.tempFiles as ChooseFile[]),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMessageFile({
|
||||||
|
count: multiple ? Number(maxCount) : 1,
|
||||||
|
type: accept,
|
||||||
|
extension,
|
||||||
|
success: (res) => resolve(res.tempFiles),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.chooseMedia({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
mediaType: ['image'],
|
||||||
|
sourceType,
|
||||||
|
sizeType,
|
||||||
|
camera,
|
||||||
|
success: (res) => resolve(formatMedia(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.chooseImage({
|
||||||
|
count: multiple ? maxCount : 1,
|
||||||
|
sizeType,
|
||||||
|
sourceType,
|
||||||
|
// #ifdef H5
|
||||||
|
extension,
|
||||||
|
// #endif
|
||||||
|
success: (res) => resolve(formatImage(res)),
|
||||||
|
fail: reject
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startUpload,
|
||||||
|
abort,
|
||||||
|
UPLOAD_STATUS,
|
||||||
|
chooseFile
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,204 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(action-sheet) {
|
||||||
|
background-color: $-dark-background2;
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
@include e(action) {
|
||||||
|
color: $-dark-color;
|
||||||
|
background: $-dark-background2;
|
||||||
|
|
||||||
|
&:not(.wd-action-sheet__action--disabled):not(.wd-action-sheet__action--loading):active {
|
||||||
|
background: $-dark-background4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(disabled) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(subname) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(cancel) {
|
||||||
|
color: $-dark-color;
|
||||||
|
background: $-dark-background4;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: $-dark-background5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-action-sheet__close) {
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel-title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(header) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-action-sheet__popup) {
|
||||||
|
border-radius: $-action-sheet-radius $-action-sheet-radius 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(action-sheet) {
|
||||||
|
background-color: $-color-white;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
|
||||||
|
@include edeep(popup) {
|
||||||
|
border-radius: $-action-sheet-radius $-action-sheet-radius 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(actions) {
|
||||||
|
padding: 8px 0;
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(action) {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: $-action-sheet-action-height;
|
||||||
|
line-height: $-action-sheet-action-height;
|
||||||
|
color: $-action-sheet-color;
|
||||||
|
font-size: $-action-sheet-fs;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
background: $-action-sheet-bg;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(&--disabled):not(&--loading):active {
|
||||||
|
background: $-action-sheet-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(disabled) {
|
||||||
|
color: $-action-sheet-disabled-color;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(loading) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(action-loading){
|
||||||
|
width: $-action-sheet-loading-size;
|
||||||
|
height: $-action-sheet-loading-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(name) {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(subname) {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: $-action-sheet-subname-fs;
|
||||||
|
color: $-action-sheet-subname-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(cancel) {
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 48px);
|
||||||
|
line-height: $-action-sheet-cancel-height;
|
||||||
|
padding: 0;
|
||||||
|
color: $-action-sheet-cancel-color;
|
||||||
|
font-size: $-action-sheet-fs;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: $-action-sheet-cancel-radius;
|
||||||
|
border: none;
|
||||||
|
background: $-action-sheet-cancel-bg;
|
||||||
|
outline: none;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
font-weight: $-action-sheet-weight;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: $-action-sheet-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(header) {
|
||||||
|
color: $-action-sheet-color;
|
||||||
|
position: relative;
|
||||||
|
height: $-action-sheet-title-height;
|
||||||
|
line-height: $-action-sheet-title-height;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $-action-sheet-title-fs;
|
||||||
|
font-weight: $-action-sheet-weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(close) {
|
||||||
|
position: absolute;
|
||||||
|
top: $-action-sheet-close-top;
|
||||||
|
right: $-action-sheet-close-right;
|
||||||
|
color: $-action-sheet-close-color;
|
||||||
|
font-size: $-action-sheet-close-fs;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panels) {
|
||||||
|
height: 84px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panels-content) {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel) {
|
||||||
|
width: 88px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: inline-block;
|
||||||
|
padding: $-action-sheet-panel-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel-img) {
|
||||||
|
display: block;
|
||||||
|
width: $-action-sheet-panel-img-fs;
|
||||||
|
height: $-action-sheet-panel-img-fs;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
border-radius: $-action-sheet-panel-img-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(panel-title) {
|
||||||
|
font-size: $-action-sheet-subname-fs;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: center;
|
||||||
|
color: $-action-sheet-color;
|
||||||
|
@include lineEllipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
import type { ExtractPropTypes } from 'vue'
|
||||||
|
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type Action = {
|
||||||
|
/**
|
||||||
|
* 选项名称
|
||||||
|
*/
|
||||||
|
name: string
|
||||||
|
/**
|
||||||
|
* 描述信息
|
||||||
|
*/
|
||||||
|
subname?: string
|
||||||
|
/**
|
||||||
|
* 颜色
|
||||||
|
*/
|
||||||
|
color?: string
|
||||||
|
/**
|
||||||
|
* 禁用
|
||||||
|
*/
|
||||||
|
disabled?: boolean
|
||||||
|
/**
|
||||||
|
* 加载中状态
|
||||||
|
*/
|
||||||
|
loading?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Panel = {
|
||||||
|
/**
|
||||||
|
* 图片地址
|
||||||
|
*/
|
||||||
|
iconUrl: string
|
||||||
|
/**
|
||||||
|
* 标题内容
|
||||||
|
*/
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actionSheetProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* header 头部样式
|
||||||
|
* @default ''
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
customHeaderClass: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 设置菜单显示隐藏
|
||||||
|
* @default false
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
modelValue: { ...makeBooleanProp(false), ...makeRequiredProp(Boolean) },
|
||||||
|
/**
|
||||||
|
* 菜单选项
|
||||||
|
* @default []
|
||||||
|
* @type {Action[]}
|
||||||
|
*/
|
||||||
|
actions: makeArrayProp<Action>(),
|
||||||
|
/**
|
||||||
|
* 自定义面板项,可以为字符串数组,也可以为对象数组,如果为二维数组,则为多行展示
|
||||||
|
* @default []
|
||||||
|
* @type {Array<Panel | Panel[]>}
|
||||||
|
*/
|
||||||
|
panels: makeArrayProp<Panel | Panel[]>(),
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
title: String,
|
||||||
|
/**
|
||||||
|
* 取消按钮文案
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
cancelText: String,
|
||||||
|
/**
|
||||||
|
* 点击选项后是否关闭菜单
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
closeOnClickAction: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 点击遮罩是否关闭
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
closeOnClickModal: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 弹框动画持续时间
|
||||||
|
* @default 200
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
duration: makeNumberProp(200),
|
||||||
|
/**
|
||||||
|
* 菜单层级
|
||||||
|
* @default 10
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
zIndex: makeNumberProp(10),
|
||||||
|
/**
|
||||||
|
* 弹层内容懒渲染,触发展示时才渲染内容
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
lazyRender: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 弹出面板是否设置底部安全距离(iphone X 类型的机型)
|
||||||
|
* @default true
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
safeAreaInsetBottom: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 是否从页面中脱离出来,用于解决各种 fixed 失效问题 (H5: teleport, APP: renderjs, 小程序: root-portal)
|
||||||
|
* 类型:boolean
|
||||||
|
* 默认值:false
|
||||||
|
*/
|
||||||
|
rootPortal: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActionSheetProps = ExtractPropTypes<typeof actionSheetProps>
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<wd-popup
|
||||||
|
custom-class="wd-action-sheet__popup"
|
||||||
|
:custom-style="`${(actions && actions.length) || (panels && panels.length) ? 'background: transparent;' : ''}`"
|
||||||
|
v-model="showPopup"
|
||||||
|
:duration="duration"
|
||||||
|
position="bottom"
|
||||||
|
:close-on-click-modal="closeOnClickModal"
|
||||||
|
:safe-area-inset-bottom="safeAreaInsetBottom"
|
||||||
|
:lazy-render="lazyRender"
|
||||||
|
:root-portal="rootPortal"
|
||||||
|
@enter="handleOpen"
|
||||||
|
@close="close"
|
||||||
|
@after-enter="handleOpened"
|
||||||
|
@after-leave="handleClosed"
|
||||||
|
@click-modal="handleClickModal"
|
||||||
|
:z-index="zIndex"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
:class="`wd-action-sheet ${customClass}`"
|
||||||
|
:style="`${
|
||||||
|
(actions && actions.length) || (panels && panels.length)
|
||||||
|
? 'margin: 0 10px calc(var(--window-bottom) + 10px) 10px; border-radius: 16px;'
|
||||||
|
: 'margin-bottom: var(--window-bottom);'
|
||||||
|
} ${customStyle}`"
|
||||||
|
>
|
||||||
|
<view v-if="title" :class="`wd-action-sheet__header ${customHeaderClass}`">
|
||||||
|
{{ title }}
|
||||||
|
<wd-icon custom-class="wd-action-sheet__close" name="add" @click="close" />
|
||||||
|
</view>
|
||||||
|
<view class="wd-action-sheet__actions" v-if="actions && actions.length">
|
||||||
|
<button
|
||||||
|
v-for="(action, rowIndex) in actions"
|
||||||
|
:key="rowIndex"
|
||||||
|
:class="`wd-action-sheet__action ${action.disabled ? 'wd-action-sheet__action--disabled' : ''} ${
|
||||||
|
action.loading ? 'wd-action-sheet__action--loading' : ''
|
||||||
|
}`"
|
||||||
|
:style="`color: ${action.color}`"
|
||||||
|
@click="select(rowIndex, 'action')"
|
||||||
|
>
|
||||||
|
<wd-loading custom-class="`wd-action-sheet__action-loading" v-if="action.loading" />
|
||||||
|
<view v-else class="wd-action-sheet__name">{{ action.name }}</view>
|
||||||
|
<view v-if="!action.loading && action.subname" class="wd-action-sheet__subname">{{ action.subname }}</view>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
<view v-if="formatPanels && formatPanels.length">
|
||||||
|
<view v-for="(panel, rowIndex) in formatPanels" :key="rowIndex" class="wd-action-sheet__panels">
|
||||||
|
<view class="wd-action-sheet__panels-content">
|
||||||
|
<view v-for="(col, colIndex) in panel" :key="colIndex" class="wd-action-sheet__panel" @click="select(rowIndex, 'panels', colIndex)">
|
||||||
|
<image class="wd-action-sheet__panel-img" :src="(col as any).iconUrl" />
|
||||||
|
<view class="wd-action-sheet__panel-title">{{ (col as any).title }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<slot />
|
||||||
|
<button v-if="cancelText" class="wd-action-sheet__cancel" @click="handleCancel">{{ cancelText }}</button>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-action-sheet',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdPopup from '../wd-popup/wd-popup.vue'
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import wdLoading from '../wd-loading/wd-loading.vue'
|
||||||
|
import { watch, ref } from 'vue'
|
||||||
|
import { actionSheetProps, type Panel } from './types'
|
||||||
|
import { isArray } from '../common/util'
|
||||||
|
|
||||||
|
const props = defineProps(actionSheetProps)
|
||||||
|
const emit = defineEmits(['select', 'click-modal', 'cancel', 'closed', 'close', 'open', 'opened', 'update:modelValue'])
|
||||||
|
|
||||||
|
const formatPanels = ref<Array<Panel> | Array<Panel[]>>([])
|
||||||
|
|
||||||
|
const showPopup = ref<boolean>(false)
|
||||||
|
|
||||||
|
watch(() => props.panels, computedValue, { deep: true, immediate: true })
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
showPopup.value = newValue
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
function isPanelArray() {
|
||||||
|
return props.panels.length && !isArray(props.panels[0])
|
||||||
|
}
|
||||||
|
function computedValue() {
|
||||||
|
formatPanels.value = isPanelArray() ? [props.panels as Panel[]] : (props.panels as Panel[][])
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(rowIndex: number, type: 'action' | 'panels', colIndex?: number) {
|
||||||
|
if (type === 'action') {
|
||||||
|
if (props.actions[rowIndex].disabled || props.actions[rowIndex].loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('select', {
|
||||||
|
item: props.actions[rowIndex],
|
||||||
|
index: rowIndex
|
||||||
|
})
|
||||||
|
} else if (isPanelArray()) {
|
||||||
|
emit('select', {
|
||||||
|
item: props.panels[Number(colIndex)],
|
||||||
|
index: colIndex
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
emit('select', {
|
||||||
|
item: (props.panels as Panel[][])[rowIndex][Number(colIndex)],
|
||||||
|
rowIndex,
|
||||||
|
colIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (props.closeOnClickAction) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleClickModal() {
|
||||||
|
emit('click-modal')
|
||||||
|
}
|
||||||
|
function handleCancel() {
|
||||||
|
emit('cancel')
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
function close() {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
function handleOpen() {
|
||||||
|
emit('open')
|
||||||
|
}
|
||||||
|
function handleOpened() {
|
||||||
|
emit('opened')
|
||||||
|
}
|
||||||
|
function handleClosed() {
|
||||||
|
emit('closed')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
@import '../common/abstracts/variable';
|
||||||
|
@import '../common/abstracts/mixin';
|
||||||
|
|
||||||
|
@include b(backtop) {
|
||||||
|
position: fixed;
|
||||||
|
background-color: $-backtop-bg;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: $-color-gray-8;
|
||||||
|
|
||||||
|
@include edeep(backicon) {
|
||||||
|
font-size: $-backtop-icon-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(circle) {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(square) {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { baseProps, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export const backtopProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 页面滚动距离
|
||||||
|
*/
|
||||||
|
scrollTop: makeRequiredProp(Number),
|
||||||
|
/**
|
||||||
|
* 距离顶部多少距离时显示
|
||||||
|
*/
|
||||||
|
top: makeNumberProp(300),
|
||||||
|
/**
|
||||||
|
* 返回顶部滚动时间
|
||||||
|
*/
|
||||||
|
duration: makeNumberProp(100),
|
||||||
|
/**
|
||||||
|
* 层级
|
||||||
|
*/
|
||||||
|
zIndex: makeNumberProp(10),
|
||||||
|
/**
|
||||||
|
* icon样式
|
||||||
|
*/
|
||||||
|
iconStyle: makeStringProp(''),
|
||||||
|
/**
|
||||||
|
* 形状
|
||||||
|
*/
|
||||||
|
shape: makeStringProp('circle'),
|
||||||
|
/**
|
||||||
|
* 距离屏幕底部距离
|
||||||
|
*/
|
||||||
|
bottom: makeNumberProp(100),
|
||||||
|
/**
|
||||||
|
* 距离屏幕右边距离
|
||||||
|
*/
|
||||||
|
right: makeNumberProp(20)
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<wd-transition :show="show" name="fade">
|
||||||
|
<view
|
||||||
|
:class="`wd-backtop ${customClass} is-${shape}`"
|
||||||
|
:style="`z-index: ${zIndex}; bottom: ${bottom}px; right: ${right}px; ${customStyle}`"
|
||||||
|
@click="handleBacktop"
|
||||||
|
>
|
||||||
|
<slot v-if="$slots.default"></slot>
|
||||||
|
<wd-icon v-else custom-class="wd-backtop__backicon" name="backtop" :custom-style="iconStyle" />
|
||||||
|
</view>
|
||||||
|
</wd-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-backtop',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdTransition from '../wd-transition/wd-transition.vue'
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { backtopProps } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(backtopProps)
|
||||||
|
|
||||||
|
const show = computed(() => props.scrollTop > props.top)
|
||||||
|
|
||||||
|
function handleBacktop() {
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: props.duration
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
@import './../common/abstracts/_mixin.scss';
|
||||||
|
@import './../common/abstracts/variable.scss';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(badge) {
|
||||||
|
@include e(content) {
|
||||||
|
border-color: $-dark-background2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@include b(badge) {
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
@include e(content) {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: $-badge-height;
|
||||||
|
line-height: $-badge-height;
|
||||||
|
padding: $-badge-padding;
|
||||||
|
background-color: $-badge-bg;
|
||||||
|
border-radius: calc($-badge-height / 2 + 2px);
|
||||||
|
color: $-badge-color;
|
||||||
|
font-size: $-badge-fs;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: $-badge-border;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
@include when(fixed) {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
transform: translateY(-50%) translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(dot) {
|
||||||
|
height: $-badge-dot-size;
|
||||||
|
width: $-badge-dot-size;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $type in (primary, success, warning, info, danger) {
|
||||||
|
@include m($type) {
|
||||||
|
@if $type == primary {
|
||||||
|
background-color: $-badge-primary;
|
||||||
|
} @else if $type == success {
|
||||||
|
background-color: $-badge-success;
|
||||||
|
} @else if $type == warning {
|
||||||
|
background-color: $-badge-warning;
|
||||||
|
} @else if $type == info {
|
||||||
|
background-color: $-badge-info;
|
||||||
|
} @else {
|
||||||
|
background-color: $-badge-danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/uni_modules/wot-design-uni/components/wd-badge/types.ts
Normal file
50
src/uni_modules/wot-design-uni/components/wd-badge/types.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2024-03-15 11:36:12
|
||||||
|
* @LastEditTime: 2024-11-20 20:29:03
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-badge/types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeStringProp, numericProp } from '../common/props'
|
||||||
|
|
||||||
|
export type BadgeType = 'primary' | 'success' | 'warning' | 'danger' | 'info'
|
||||||
|
|
||||||
|
export const badgeProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 显示值
|
||||||
|
*/
|
||||||
|
modelValue: numericProp,
|
||||||
|
/** 当数值为 0 时,是否展示徽标 */
|
||||||
|
showZero: makeBooleanProp(false),
|
||||||
|
bgColor: String,
|
||||||
|
/**
|
||||||
|
* 最大值,超过最大值会显示 '{max}+',要求 value 是 Number 类型
|
||||||
|
*/
|
||||||
|
max: Number,
|
||||||
|
/**
|
||||||
|
* 是否为红色点状标注
|
||||||
|
*/
|
||||||
|
isDot: Boolean,
|
||||||
|
/**
|
||||||
|
* 是否隐藏 badge
|
||||||
|
*/
|
||||||
|
hidden: Boolean,
|
||||||
|
/**
|
||||||
|
* badge类型,可选值primary / success / warning / danger / info
|
||||||
|
*/
|
||||||
|
type: makeStringProp<BadgeType | undefined>(undefined),
|
||||||
|
/**
|
||||||
|
* 为正时,角标向下偏移对应的像素
|
||||||
|
*/
|
||||||
|
top: numericProp,
|
||||||
|
/**
|
||||||
|
* 为正时,角标向左偏移对应的像素
|
||||||
|
*/
|
||||||
|
right: numericProp
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BadgeProps = ExtractPropTypes<typeof badgeProps>
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="['wd-badge', customClass]" :style="customStyle">
|
||||||
|
<slot></slot>
|
||||||
|
<view
|
||||||
|
v-if="shouldShowBadge"
|
||||||
|
:class="['wd-badge__content', 'is-fixed', type ? 'wd-badge__content--' + type : '', isDot ? 'is-dot' : '']"
|
||||||
|
:style="contentStyle"
|
||||||
|
>
|
||||||
|
{{ content }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-badge',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, type CSSProperties } from 'vue'
|
||||||
|
import { badgeProps } from './types'
|
||||||
|
import { addUnit, isDef, isNumber, objToStyle } from '../common/util'
|
||||||
|
|
||||||
|
const props = defineProps(badgeProps)
|
||||||
|
const content = computed(() => {
|
||||||
|
const { modelValue, max, isDot } = props
|
||||||
|
if (isDot) return ''
|
||||||
|
let value = modelValue
|
||||||
|
if (value && max && isNumber(value) && !Number.isNaN(value) && !Number.isNaN(max)) {
|
||||||
|
value = max < value ? `${max}+` : value
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
|
||||||
|
const contentStyle = computed(() => {
|
||||||
|
const style: CSSProperties = {}
|
||||||
|
if (isDef(props.bgColor)) {
|
||||||
|
style.backgroundColor = props.bgColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDef(props.top)) {
|
||||||
|
style.top = addUnit(props.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDef(props.right)) {
|
||||||
|
style.right = addUnit(props.right)
|
||||||
|
}
|
||||||
|
return objToStyle(style)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否展示徽标数字
|
||||||
|
const shouldShowBadge = computed(() => !props.hidden && (content.value || (content.value === 0 && props.showZero) || props.isDot))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
336
src/uni_modules/wot-design-uni/components/wd-button/index.scss
Normal file
336
src/uni_modules/wot-design-uni/components/wd-button/index.scss
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
@import './../common/abstracts/_mixin.scss';
|
||||||
|
@import './../common/abstracts/variable.scss';
|
||||||
|
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(button) {
|
||||||
|
@include when(info) {
|
||||||
|
background: $-dark-background4;
|
||||||
|
color: $-dark-color3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(plain) {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
@include when(info) {
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-color: $-dark-background5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(text) {
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(icon) {
|
||||||
|
color: $-dark-color;
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(button) {
|
||||||
|
margin-left: initial;
|
||||||
|
margin-right: initial;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
color: $-button-normal-color;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
user-select: none;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: $-color-black;
|
||||||
|
border: inherit;
|
||||||
|
border-color: $-color-black;
|
||||||
|
border-radius: inherit;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
opacity: 0;
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(content) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include m(active) {
|
||||||
|
&:active::before {
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
opacity: $-button-disabled-opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(loading) {
|
||||||
|
margin-right: 5px;
|
||||||
|
animation: wd-rotate 0.8s linear infinite;
|
||||||
|
animation-duration: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(loading-svg) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(loading) {}
|
||||||
|
|
||||||
|
@include when(primary) {
|
||||||
|
background: $-button-primary-bg-color;
|
||||||
|
color: $-button-primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(success) {
|
||||||
|
background: $-button-success-bg-color;
|
||||||
|
color: $-button-success-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(info) {
|
||||||
|
background: $-button-info-bg-color;
|
||||||
|
color: $-button-info-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(warning) {
|
||||||
|
background: $-button-warning-bg-color;
|
||||||
|
color: $-button-warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(error) {
|
||||||
|
background: $-button-error-bg-color;
|
||||||
|
color: $-button-error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(small) {
|
||||||
|
height: $-button-small-height;
|
||||||
|
padding: $-button-small-padding;
|
||||||
|
border-radius: $-button-small-radius;
|
||||||
|
font-size: $-button-small-fs;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
.wd-button__loading {
|
||||||
|
width: $-button-small-loading;
|
||||||
|
height: $-button-small-loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(medium) {
|
||||||
|
height: $-button-medium-height;
|
||||||
|
padding: $-button-medium-padding;
|
||||||
|
border-radius: $-button-medium-radius;
|
||||||
|
font-size: $-button-medium-fs;
|
||||||
|
min-width: 120px;
|
||||||
|
|
||||||
|
|
||||||
|
@include when(round) {
|
||||||
|
|
||||||
|
@include when(icon) {
|
||||||
|
min-width: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(text) {
|
||||||
|
border-radius: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-button__loading {
|
||||||
|
width: $-button-medium-loading;
|
||||||
|
height: $-button-medium-loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(large) {
|
||||||
|
height: $-button-large-height;
|
||||||
|
padding: $-button-large-padding;
|
||||||
|
border-radius: $-button-large-radius;
|
||||||
|
font-size: $-button-large-fs;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-radius: $-button-large-radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-button__loading {
|
||||||
|
width: $-button-large-loading;
|
||||||
|
height: $-button-large-loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@include when(round) {
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(text) {
|
||||||
|
color: $-button-primary-bg-color;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.wd-button--active {
|
||||||
|
opacity: $-button-text-hover-opacity;
|
||||||
|
|
||||||
|
&:active::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-button-normal-disabled-color;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(plain) {
|
||||||
|
background: $-button-plain-bg-color;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
|
||||||
|
@include when(primary) {
|
||||||
|
color: $-button-primary-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(success) {
|
||||||
|
color: $-button-success-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(info) {
|
||||||
|
color: $-button-info-plain-normal-color;
|
||||||
|
border-color: $-button-info-plain-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(warning) {
|
||||||
|
color: $-button-warning-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(error) {
|
||||||
|
color: $-button-error-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(hairline) {
|
||||||
|
border-width: 0;
|
||||||
|
|
||||||
|
&.is-plain {
|
||||||
|
@include halfPixelBorderSurround();
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-round {
|
||||||
|
&::after {
|
||||||
|
border-radius: inherit !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-large {
|
||||||
|
&::after {
|
||||||
|
border-radius: calc(2 * $-button-large-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-medium {
|
||||||
|
&::after {
|
||||||
|
border-radius: calc(2 * $-button-medium-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-small {
|
||||||
|
&::after {
|
||||||
|
border-radius: calc(2 * $-button-small-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(block) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(icon) {
|
||||||
|
width: $-button-icon-size;
|
||||||
|
height: $-button-icon-size;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: $-button-icon-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-button__icon) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
color: $-button-icon-disabled-color;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include edeep(icon) {
|
||||||
|
display: block;
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: $-button-icon-fs;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(text) {
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wd-rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
142
src/uni_modules/wot-design-uni/components/wd-button/types.ts
Normal file
142
src/uni_modules/wot-design-uni/components/wd-button/types.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2024-03-15 11:36:12
|
||||||
|
* @LastEditTime: 2024-11-04 21:33:52
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-button\types.ts
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
|
import type { ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type ButtonType = 'primary' | 'success' | 'info' | 'warning' | 'error' | 'default' | 'text' | 'icon'
|
||||||
|
export type ButtonSize = 'small' | 'medium' | 'large'
|
||||||
|
export type ButtonLang = 'zh_CN' | 'zh_TW' | 'en'
|
||||||
|
|
||||||
|
export type ButtonOpenType =
|
||||||
|
| 'feedback'
|
||||||
|
| 'share'
|
||||||
|
| 'getUserInfo'
|
||||||
|
| 'contact'
|
||||||
|
| 'getPhoneNumber'
|
||||||
|
| 'getRealtimePhoneNumber'
|
||||||
|
| 'launchApp'
|
||||||
|
| 'openSetting'
|
||||||
|
| 'chooseAvatar'
|
||||||
|
| 'getAuthorize'
|
||||||
|
| 'lifestyle'
|
||||||
|
| 'contactShare'
|
||||||
|
| 'openGroupProfile'
|
||||||
|
| 'openGuildProfile'
|
||||||
|
| 'openPublicProfile'
|
||||||
|
| 'shareMessageToFriend'
|
||||||
|
| 'addFriend'
|
||||||
|
| 'addColorSign'
|
||||||
|
| 'addGroupApp'
|
||||||
|
| 'addToFavorites'
|
||||||
|
| 'chooseAddress'
|
||||||
|
| 'chooseInvoiceTitle'
|
||||||
|
| 'login'
|
||||||
|
| 'subscribe'
|
||||||
|
| 'favorite'
|
||||||
|
| 'watchLater'
|
||||||
|
| 'openProfile'
|
||||||
|
| 'agreePrivacyAuthorization'
|
||||||
|
|
||||||
|
export type ButtonScope = 'phoneNumber' | 'userInfo'
|
||||||
|
|
||||||
|
export const buttonProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 幽灵按钮
|
||||||
|
*/
|
||||||
|
plain: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 圆角按钮
|
||||||
|
*/
|
||||||
|
round: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 禁用按钮
|
||||||
|
*/
|
||||||
|
disabled: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否细边框
|
||||||
|
*/
|
||||||
|
hairline: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 块状按钮
|
||||||
|
*/
|
||||||
|
block: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 按钮类型,可选值:primary / success / info / warning / error / text / icon
|
||||||
|
*/
|
||||||
|
type: makeStringProp<ButtonType>('primary'),
|
||||||
|
/**
|
||||||
|
* 按钮尺寸,可选值:small / medium / large
|
||||||
|
*/
|
||||||
|
size: makeStringProp<ButtonSize>('medium'),
|
||||||
|
/**
|
||||||
|
* 图标类名
|
||||||
|
*/
|
||||||
|
icon: String,
|
||||||
|
/**
|
||||||
|
* 类名前缀,用于使用自定义图标,用法参考Icon组件
|
||||||
|
*/
|
||||||
|
classPrefix: makeStringProp('wd-icon'),
|
||||||
|
/**
|
||||||
|
* 加载中按钮
|
||||||
|
*/
|
||||||
|
loading: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 加载图标颜色
|
||||||
|
*/
|
||||||
|
loadingColor: String,
|
||||||
|
/**
|
||||||
|
* 开放能力
|
||||||
|
*/
|
||||||
|
openType: String as PropType<ButtonOpenType>,
|
||||||
|
/**
|
||||||
|
* 指定是否阻止本节点的祖先节点出现点击态
|
||||||
|
*/
|
||||||
|
hoverStopPropagation: Boolean,
|
||||||
|
/**
|
||||||
|
* 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
|
||||||
|
*/
|
||||||
|
lang: String as PropType<ButtonLang>,
|
||||||
|
/**
|
||||||
|
* 会话来源,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sessionFrom: String,
|
||||||
|
/**
|
||||||
|
* 会话内消息卡片标题,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sendMessageTitle: String,
|
||||||
|
/**
|
||||||
|
* 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sendMessagePath: String,
|
||||||
|
/**
|
||||||
|
* 会话内消息卡片图片,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
sendMessageImg: String,
|
||||||
|
/**
|
||||||
|
* 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||||
|
*/
|
||||||
|
appParameter: String,
|
||||||
|
/**
|
||||||
|
* 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||||
|
*/
|
||||||
|
showMessageCard: Boolean,
|
||||||
|
/**
|
||||||
|
* 按钮的唯一标识,可用于设置隐私同意授权按钮的id
|
||||||
|
*/
|
||||||
|
buttonId: String,
|
||||||
|
/**
|
||||||
|
* 支付宝小程序,当 open-type 为 getAuthorize 时有效。
|
||||||
|
* 可选值:'phoneNumber' | 'userInfo'
|
||||||
|
*/
|
||||||
|
scope: String as PropType<ButtonScope>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ButtonProps = ExtractPropTypes<typeof buttonProps>
|
||||||
@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
:id="buttonId"
|
||||||
|
:hover-class="`${disabled || loading ? '' : 'wd-button--active'}`"
|
||||||
|
:style="customStyle"
|
||||||
|
:class="[
|
||||||
|
'wd-button',
|
||||||
|
'is-' + type,
|
||||||
|
'is-' + size,
|
||||||
|
round ? 'is-round' : '',
|
||||||
|
hairline ? 'is-hairline' : '',
|
||||||
|
plain ? 'is-plain' : '',
|
||||||
|
disabled ? 'is-disabled' : '',
|
||||||
|
block ? 'is-block' : '',
|
||||||
|
loading ? 'is-loading' : '',
|
||||||
|
customClass
|
||||||
|
]"
|
||||||
|
:hover-start-time="hoverStartTime"
|
||||||
|
:hover-stay-time="hoverStayTime"
|
||||||
|
:open-type="disabled || loading ? undefined : openType"
|
||||||
|
:send-message-title="sendMessageTitle"
|
||||||
|
:send-message-path="sendMessagePath"
|
||||||
|
:send-message-img="sendMessageImg"
|
||||||
|
:app-parameter="appParameter"
|
||||||
|
:show-message-card="showMessageCard"
|
||||||
|
:session-from="sessionFrom"
|
||||||
|
:lang="lang"
|
||||||
|
:hover-stop-propagation="hoverStopPropagation"
|
||||||
|
:scope="scope"
|
||||||
|
@click="handleClick"
|
||||||
|
@getAuthorize="handleGetAuthorize"
|
||||||
|
@getuserinfo="handleGetuserinfo"
|
||||||
|
@contact="handleConcat"
|
||||||
|
@getphonenumber="handleGetphonenumber"
|
||||||
|
@getrealtimephonenumber="handleGetrealtimephonenumber"
|
||||||
|
@error="handleError"
|
||||||
|
@launchapp="handleLaunchapp"
|
||||||
|
@opensetting="handleOpensetting"
|
||||||
|
@chooseavatar="handleChooseavatar"
|
||||||
|
@agreeprivacyauthorization="handleAgreePrivacyAuthorization"
|
||||||
|
>
|
||||||
|
<view class="wd-button__content">
|
||||||
|
<view v-if="loading" class="wd-button__loading">
|
||||||
|
<view class="wd-button__loading-svg" :style="loadingStyle"></view>
|
||||||
|
</view>
|
||||||
|
<wd-icon v-else-if="icon" custom-class="wd-button__icon" :name="icon" :classPrefix="classPrefix"></wd-icon>
|
||||||
|
<view class="wd-button__text"><slot /></view>
|
||||||
|
</view>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-button',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
|
import { computed, watch } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import base64 from '../common/base64'
|
||||||
|
import { buttonProps } from './types'
|
||||||
|
|
||||||
|
const loadingIcon = (color = '#4D80F0', reverse = true) => {
|
||||||
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 42 42"><defs><linearGradient x1="100%" y1="0%" x2="0%" y2="0%" id="a"><stop stop-color="${
|
||||||
|
reverse ? color : '#fff'
|
||||||
|
}" offset="0%" stop-opacity="0"/><stop stop-color="${
|
||||||
|
reverse ? color : '#fff'
|
||||||
|
}" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path d="M21 1c11.046 0 20 8.954 20 20s-8.954 20-20 20S1 32.046 1 21 9.954 1 21 1zm0 7C13.82 8 8 13.82 8 21s5.82 13 13 13 13-5.82 13-13S28.18 8 21 8z" fill="${
|
||||||
|
reverse ? '#fff' : color
|
||||||
|
}"/><path d="M4.599 21c0 9.044 7.332 16.376 16.376 16.376 9.045 0 16.376-7.332 16.376-16.376" stroke="url(#a)" stroke-width="3.5" stroke-linecap="round"/></g></svg>`
|
||||||
|
}
|
||||||
|
const props = defineProps(buttonProps)
|
||||||
|
const emit = defineEmits([
|
||||||
|
'click',
|
||||||
|
'getuserinfo',
|
||||||
|
'contact',
|
||||||
|
'getphonenumber',
|
||||||
|
'getrealtimephonenumber',
|
||||||
|
'error',
|
||||||
|
'launchapp',
|
||||||
|
'opensetting',
|
||||||
|
'chooseavatar',
|
||||||
|
'agreeprivacyauthorization'
|
||||||
|
])
|
||||||
|
|
||||||
|
const hoverStartTime = ref<number>(20)
|
||||||
|
const hoverStayTime = ref<number>(70)
|
||||||
|
const loadingIconSvg = ref<string>('')
|
||||||
|
|
||||||
|
const loadingStyle = computed(() => {
|
||||||
|
return `background-image: url(${loadingIconSvg.value});`
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.loading,
|
||||||
|
() => {
|
||||||
|
buildLoadingSvg()
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
function handleClick(event: any) {
|
||||||
|
if (!props.disabled && !props.loading) {
|
||||||
|
emit('click', event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝小程序授权
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
function handleGetAuthorize(event: any) {
|
||||||
|
if (props.scope === 'phoneNumber') {
|
||||||
|
handleGetphonenumber(event)
|
||||||
|
} else if (props.scope === 'userInfo') {
|
||||||
|
handleGetuserinfo(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGetuserinfo(event: any) {
|
||||||
|
emit('getuserinfo', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConcat(event: any) {
|
||||||
|
emit('contact', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGetphonenumber(event: any) {
|
||||||
|
emit('getphonenumber', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGetrealtimephonenumber(event: any) {
|
||||||
|
emit('getrealtimephonenumber', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(event: any) {
|
||||||
|
emit('error', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLaunchapp(event: any) {
|
||||||
|
emit('launchapp', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpensetting(event: any) {
|
||||||
|
emit('opensetting', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChooseavatar(event: any) {
|
||||||
|
emit('chooseavatar', event.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAgreePrivacyAuthorization(event: any) {
|
||||||
|
emit('agreeprivacyauthorization', event.detail)
|
||||||
|
}
|
||||||
|
function buildLoadingSvg() {
|
||||||
|
const { loadingColor, type, plain } = props
|
||||||
|
let color = loadingColor
|
||||||
|
if (!color) {
|
||||||
|
switch (type) {
|
||||||
|
case 'primary':
|
||||||
|
color = '#4D80F0'
|
||||||
|
break
|
||||||
|
case 'success':
|
||||||
|
color = '#34d19d'
|
||||||
|
break
|
||||||
|
case 'info':
|
||||||
|
color = '#333'
|
||||||
|
break
|
||||||
|
case 'warning':
|
||||||
|
color = '#f0883a'
|
||||||
|
break
|
||||||
|
case 'error':
|
||||||
|
color = '#fa4350'
|
||||||
|
break
|
||||||
|
case 'default':
|
||||||
|
color = '#333'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const svg = loadingIcon(color, !plain)
|
||||||
|
loadingIconSvg.value = `"data:image/svg+xml;base64,${base64(svg)}"`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2023-06-12 10:04:19
|
||||||
|
* @LastEditTime: 2023-07-15 16:16:34
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-calendar-view\index.scss
|
||||||
|
* 记得注释
|
||||||
|
*/
|
||||||
@ -0,0 +1,162 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(month) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(days) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day) {
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-month__day-text {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(month) {
|
||||||
|
@include e(title) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 45px;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(days) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: $-calendar-day-fs;
|
||||||
|
color: $-calendar-day-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day) {
|
||||||
|
position: relative;
|
||||||
|
width: 14.285%;
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
line-height: $-calendar-day-height;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: $-calendar-item-margin-bottom;
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-month__day-text {
|
||||||
|
color: $-calendar-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(current) {
|
||||||
|
color: $-calendar-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(selected, multiple-selected) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(middle) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(multiple-middle) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(start) {
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 50%;
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-without-end::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
border-radius: $-calendar-active-border 0 0 $-calendar-active-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(end) {
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 50%;
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
border-radius: 0 $-calendar-active-border $-calendar-active-border 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(same) {
|
||||||
|
.wd-month__day-container {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(last-row){
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-container) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-text) {
|
||||||
|
font-weight: $-calendar-day-fw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-top) {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(day-bottom) {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,389 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<wd-toast selector="wd-month" />
|
||||||
|
<view class="month">
|
||||||
|
<view class="wd-month">
|
||||||
|
<view class="wd-month__title" v-if="showTitle">{{ monthTitle(date) }}</view>
|
||||||
|
<view class="wd-month__days">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in days"
|
||||||
|
:key="index"
|
||||||
|
:class="`wd-month__day ${item.disabled ? 'is-disabled' : ''} ${item.isLastRow ? 'is-last-row' : ''} ${
|
||||||
|
item.type ? dayTypeClass(item.type) : ''
|
||||||
|
}`"
|
||||||
|
:style="index === 0 ? firstDayStyle : ''"
|
||||||
|
@click="handleDateClick(index)"
|
||||||
|
>
|
||||||
|
<view class="wd-month__day-container">
|
||||||
|
<view class="wd-month__day-top">{{ item.topInfo }}</view>
|
||||||
|
<view class="wd-month__day-text">
|
||||||
|
{{ item.text }}
|
||||||
|
</view>
|
||||||
|
<view class="wd-month__day-bottom">{{ item.bottomInfo }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdToast from '../../wd-toast/wd-toast.vue'
|
||||||
|
import { computed, ref, watch, type CSSProperties } from 'vue'
|
||||||
|
import {
|
||||||
|
compareDate,
|
||||||
|
formatMonthTitle,
|
||||||
|
getDateByDefaultTime,
|
||||||
|
getDayByOffset,
|
||||||
|
getDayOffset,
|
||||||
|
getItemClass,
|
||||||
|
getMonthEndDay,
|
||||||
|
getNextDay,
|
||||||
|
getPrevDay,
|
||||||
|
getWeekRange
|
||||||
|
} from '../utils'
|
||||||
|
import { useToast } from '../../wd-toast'
|
||||||
|
import { deepClone, isArray, isFunction, objToStyle } from '../../common/util'
|
||||||
|
import { useTranslate } from '../../composables/useTranslate'
|
||||||
|
import type { CalendarDayItem, CalendarDayType } from '../types'
|
||||||
|
import { monthProps } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(monthProps)
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const days = ref<Array<CalendarDayItem>>([])
|
||||||
|
|
||||||
|
const toast = useToast('wd-month')
|
||||||
|
|
||||||
|
const offset = computed(() => {
|
||||||
|
const firstDayOfWeek = props.firstDayOfWeek >= 7 ? props.firstDayOfWeek % 7 : props.firstDayOfWeek
|
||||||
|
const offset = (7 + new Date(props.date).getDay() - firstDayOfWeek) % 7
|
||||||
|
return offset
|
||||||
|
})
|
||||||
|
|
||||||
|
const dayTypeClass = computed(() => {
|
||||||
|
return (monthType: CalendarDayType) => {
|
||||||
|
return getItemClass(monthType, props.value, props.type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const monthTitle = computed(() => {
|
||||||
|
return (date: number) => {
|
||||||
|
return formatMonthTitle(date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const firstDayStyle = computed(() => {
|
||||||
|
const dayStyle: CSSProperties = {}
|
||||||
|
dayStyle.marginLeft = `${(100 / 7) * offset.value}%`
|
||||||
|
return objToStyle(dayStyle)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLastRow = (date: number) => {
|
||||||
|
const currentDate = new Date(date)
|
||||||
|
const currentDay = currentDate.getDate()
|
||||||
|
const daysInMonth = getMonthEndDay(currentDate.getFullYear(), currentDate.getMonth() + 1)
|
||||||
|
const totalDaysShown = offset.value + daysInMonth
|
||||||
|
const totalRows = Math.ceil(totalDaysShown / 7)
|
||||||
|
return Math.ceil((offset.value + currentDay) / 7) === totalRows
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
[() => props.type, () => props.date, () => props.value, () => props.minDate, () => props.maxDate, () => props.formatter],
|
||||||
|
() => {
|
||||||
|
setDays()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function setDays() {
|
||||||
|
const dayList: Array<CalendarDayItem> = []
|
||||||
|
const date = new Date(props.date)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = date.getMonth()
|
||||||
|
const totalDay = getMonthEndDay(year, month + 1)
|
||||||
|
let value = props.value
|
||||||
|
if ((props.type === 'week' || props.type === 'weekrange') && value) {
|
||||||
|
value = getWeekValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let day = 1; day <= totalDay; day++) {
|
||||||
|
const date = new Date(year, month, day).getTime()
|
||||||
|
let type: CalendarDayType = getDayType(date, value as number | number[] | null)
|
||||||
|
if (!type && compareDate(date, Date.now()) === 0) {
|
||||||
|
type = 'current'
|
||||||
|
}
|
||||||
|
const dayObj = getFormatterDate(date, day, type)
|
||||||
|
dayList.push(dayObj)
|
||||||
|
}
|
||||||
|
days.value = dayList
|
||||||
|
}
|
||||||
|
function getDayType(date: number, value: number | number[] | null): CalendarDayType {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'date':
|
||||||
|
case 'datetime':
|
||||||
|
return getDateType(date)
|
||||||
|
case 'dates':
|
||||||
|
return getDatesType(date)
|
||||||
|
case 'daterange':
|
||||||
|
case 'datetimerange':
|
||||||
|
return getDatetimeType(date, value)
|
||||||
|
case 'week':
|
||||||
|
return getWeektimeType(date, value)
|
||||||
|
case 'weekrange':
|
||||||
|
return getWeektimeType(date, value)
|
||||||
|
default:
|
||||||
|
return getDateType(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getDateType(date: number): CalendarDayType {
|
||||||
|
if (props.value && compareDate(date, props.value as number) === 0) {
|
||||||
|
return 'selected'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatesType(date: number): CalendarDayType {
|
||||||
|
const { value } = props
|
||||||
|
let type: CalendarDayType = ''
|
||||||
|
|
||||||
|
if (!isArray(value)) return type
|
||||||
|
const isSelected = (day: number) => {
|
||||||
|
return value.some((item) => compareDate(day, item) === 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected(date)) {
|
||||||
|
const prevDay = getPrevDay(date)
|
||||||
|
const nextDay = getNextDay(date)
|
||||||
|
const prevSelected = isSelected(prevDay)
|
||||||
|
const nextSelected = isSelected(nextDay)
|
||||||
|
if (prevSelected && nextSelected) {
|
||||||
|
type = 'multiple-middle'
|
||||||
|
} else if (prevSelected) {
|
||||||
|
type = 'end'
|
||||||
|
} else if (nextSelected) {
|
||||||
|
type = 'start'
|
||||||
|
} else {
|
||||||
|
type = 'multiple-selected'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
function getDatetimeType(date: number, value: number | number[] | null) {
|
||||||
|
const [startDate, endDate] = isArray(value) ? value : []
|
||||||
|
|
||||||
|
if (startDate && compareDate(date, startDate) === 0) {
|
||||||
|
if (props.allowSameDay && endDate && compareDate(startDate, endDate) === 0) {
|
||||||
|
return 'same'
|
||||||
|
}
|
||||||
|
return 'start'
|
||||||
|
} else if (endDate && compareDate(date, endDate) === 0) {
|
||||||
|
return 'end'
|
||||||
|
} else if (startDate && endDate && compareDate(date, startDate) === 1 && compareDate(date, endDate) === -1) {
|
||||||
|
return 'middle'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getWeektimeType(date: number, value: number | number[] | null) {
|
||||||
|
const [startDate, endDate] = isArray(value) ? value : []
|
||||||
|
|
||||||
|
if (startDate && compareDate(date, startDate) === 0) {
|
||||||
|
return 'start'
|
||||||
|
} else if (endDate && compareDate(date, endDate) === 0) {
|
||||||
|
return 'end'
|
||||||
|
} else if (startDate && endDate && compareDate(date, startDate) === 1 && compareDate(date, endDate) === -1) {
|
||||||
|
return 'middle'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getWeekValue() {
|
||||||
|
if (props.type === 'week') {
|
||||||
|
return getWeekRange(props.value as number, props.firstDayOfWeek)
|
||||||
|
} else {
|
||||||
|
const [startDate, endDate] = (props.value as any) || []
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
const firstWeekRange = getWeekRange(startDate, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
if (endDate) {
|
||||||
|
const endWeekRange = getWeekRange(endDate, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
return [firstWeekRange[0], endWeekRange[1]]
|
||||||
|
} else {
|
||||||
|
return firstWeekRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleDateClick(index: number) {
|
||||||
|
const date = days.value[index]
|
||||||
|
switch (props.type) {
|
||||||
|
case 'date':
|
||||||
|
case 'datetime':
|
||||||
|
handleDateChange(date)
|
||||||
|
break
|
||||||
|
case 'dates':
|
||||||
|
handleDatesChange(date)
|
||||||
|
break
|
||||||
|
case 'daterange':
|
||||||
|
case 'datetimerange':
|
||||||
|
handleDateRangeChange(date)
|
||||||
|
break
|
||||||
|
case 'week':
|
||||||
|
handleWeekChange(date)
|
||||||
|
break
|
||||||
|
case 'weekrange':
|
||||||
|
handleWeekRangeChange(date)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
handleDateChange(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getDate(date: number, isEnd: boolean = false) {
|
||||||
|
date = props.defaultTime && props.defaultTime.length > 0 ? getDateByDefaultTime(date, isEnd ? props.defaultTime[1] : props.defaultTime[0]) : date
|
||||||
|
|
||||||
|
if (date < props.minDate) return props.minDate
|
||||||
|
|
||||||
|
if (date > props.maxDate) return props.maxDate
|
||||||
|
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDateChange(date: CalendarDayItem) {
|
||||||
|
if (date.disabled) return
|
||||||
|
|
||||||
|
if (date.type !== 'selected') {
|
||||||
|
emit('change', {
|
||||||
|
value: getDate(date.date),
|
||||||
|
type: 'start'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleDatesChange(date: CalendarDayItem) {
|
||||||
|
if (date.disabled) return
|
||||||
|
const currentValue = deepClone(isArray(props.value) ? props.value : [])
|
||||||
|
const dateIndex = currentValue.findIndex((item) => item && compareDate(item, date.date) === 0)
|
||||||
|
const value = dateIndex === -1 ? [...currentValue, getDate(date.date)] : currentValue.filter((_, index) => index !== dateIndex)
|
||||||
|
emit('change', { value })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDateRangeChange(date: CalendarDayItem) {
|
||||||
|
if (date.disabled) return
|
||||||
|
|
||||||
|
let value: (number | null)[] = []
|
||||||
|
let type: CalendarDayType = ''
|
||||||
|
const [startDate, endDate] = deepClone(isArray(props.value) ? props.value : [])
|
||||||
|
const compare = compareDate(date.date, startDate)
|
||||||
|
|
||||||
|
// 禁止选择同个日期
|
||||||
|
if (!props.allowSameDay && compare === 0 && (props.type === 'daterange' || props.type === 'datetimerange') && !endDate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate && !endDate && compare > -1) {
|
||||||
|
// 不能选择超过最大范围的日期
|
||||||
|
if (props.maxRange && getDayOffset(date.date, startDate) > props.maxRange) {
|
||||||
|
const maxEndDate = getDayByOffset(startDate, props.maxRange - 1)
|
||||||
|
value = [startDate, getDate(maxEndDate, true)]
|
||||||
|
toast.show({
|
||||||
|
msg: props.rangePrompt || translate('rangePrompt', props.maxRange)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
value = [startDate, getDate(date.date, true)]
|
||||||
|
}
|
||||||
|
} else if (props.type === 'datetimerange' && startDate && endDate) {
|
||||||
|
// 时间范围类型,且有开始时间和结束时间,需要支持重新点击开始日期和结束日期可以重新修改时间
|
||||||
|
if (compare === 0) {
|
||||||
|
type = 'start'
|
||||||
|
value = props.value as number[]
|
||||||
|
} else if (compareDate(date.date, endDate) === 0) {
|
||||||
|
type = 'end'
|
||||||
|
value = props.value as number[]
|
||||||
|
} else {
|
||||||
|
value = [getDate(date.date), null]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = [getDate(date.date), null]
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value,
|
||||||
|
type: type || (value[1] ? 'end' : 'start')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleWeekChange(date: CalendarDayItem) {
|
||||||
|
const [weekStart] = getWeekRange(date.date, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
// 周的第一天如果是禁用状态,则不可选中
|
||||||
|
if (getFormatterDate(weekStart, new Date(weekStart).getDate()).disabled) return
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value: getDate(weekStart) + 24 * 60 * 60 * 1000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleWeekRangeChange(date: CalendarDayItem) {
|
||||||
|
const [weekStartDate] = getWeekRange(date.date, props.firstDayOfWeek)
|
||||||
|
|
||||||
|
// 周的第一天如果是禁用状态,则不可选中
|
||||||
|
if (getFormatterDate(weekStartDate, new Date(weekStartDate).getDate()).disabled) return
|
||||||
|
|
||||||
|
let value: (number | null)[] = []
|
||||||
|
const [startDate, endDate] = deepClone(isArray(props.value) ? props.value : [])
|
||||||
|
const [startWeekStartDate] = startDate ? getWeekRange(startDate, props.firstDayOfWeek) : []
|
||||||
|
const compare = compareDate(weekStartDate, startWeekStartDate)
|
||||||
|
|
||||||
|
if (startDate && !endDate && compare > -1) {
|
||||||
|
if (!props.allowSameDay && compare === 0) return
|
||||||
|
|
||||||
|
value = [getDate(startWeekStartDate) + 24 * 60 * 60 * 1000, getDate(weekStartDate) + 24 * 60 * 60 * 1000]
|
||||||
|
} else {
|
||||||
|
value = [getDate(weekStartDate) + 24 * 60 * 60 * 1000, null]
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getFormatterDate(date: number, day: string | number, type?: CalendarDayType) {
|
||||||
|
let dayObj: CalendarDayItem = {
|
||||||
|
date: date,
|
||||||
|
text: day,
|
||||||
|
topInfo: '',
|
||||||
|
bottomInfo: '',
|
||||||
|
type,
|
||||||
|
disabled: compareDate(date, props.minDate) === -1 || compareDate(date, props.maxDate) === 1,
|
||||||
|
isLastRow: isLastRow(date)
|
||||||
|
}
|
||||||
|
if (props.formatter) {
|
||||||
|
if (isFunction(props.formatter)) {
|
||||||
|
dayObj = props.formatter(dayObj)
|
||||||
|
} else {
|
||||||
|
console.error('[wot-design] error(wd-calendar-view): the formatter prop of wd-calendar-view should be a function')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dayObj
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
import { makeBooleanProp, makeRequiredProp } from '../../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarType } from '../types'
|
||||||
|
|
||||||
|
export const monthProps = {
|
||||||
|
type: makeRequiredProp(String as PropType<CalendarType>),
|
||||||
|
date: makeRequiredProp(Number),
|
||||||
|
value: makeRequiredProp([Number, Array, null] as PropType<number | (number | null)[] | null>),
|
||||||
|
minDate: makeRequiredProp(Number),
|
||||||
|
maxDate: makeRequiredProp(Number),
|
||||||
|
firstDayOfWeek: makeRequiredProp(Number),
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
maxRange: Number,
|
||||||
|
rangePrompt: String,
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
defaultTime: {
|
||||||
|
type: [Array] as PropType<Array<number[]>>
|
||||||
|
},
|
||||||
|
showTitle: makeBooleanProp(true)
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(month-panel) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(weeks) {
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(255, 255, 255, 0.02);
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-label) {
|
||||||
|
color: $-dark-color;
|
||||||
|
&::after{
|
||||||
|
background: $-dark-background4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(month-panel) {
|
||||||
|
font-size: $-calendar-fs;
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
padding: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(weeks) {
|
||||||
|
display: flex;
|
||||||
|
height: $-calendar-week-height;
|
||||||
|
line-height: $-calendar-week-height;
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(0, 0, 0, 0.02);
|
||||||
|
color: $-calendar-week-color;
|
||||||
|
font-size: $-calendar-week-fs;
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(week) {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(container) {
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time) {
|
||||||
|
display: flex;
|
||||||
|
box-shadow: 0px -4px 8px 0px rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-label) {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
font-size: $-picker-column-fs;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 125px;
|
||||||
|
color: $-picker-column-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 35px;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: $-picker-column-select-bg;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-text) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(time-picker) {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,374 @@
|
|||||||
|
<template>
|
||||||
|
<view class="wd-month-panel">
|
||||||
|
<view v-if="showPanelTitle" class="wd-month-panel__title">
|
||||||
|
{{ title }}
|
||||||
|
</view>
|
||||||
|
<view class="wd-month-panel__weeks">
|
||||||
|
<view v-for="item in 7" :key="item" class="wd-month-panel__week">{{ weekLabel(item + firstDayOfWeek) }}</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view
|
||||||
|
:class="`wd-month-panel__container ${!!timeType ? 'wd-month-panel__container--time' : ''}`"
|
||||||
|
:style="`height: ${scrollHeight}px`"
|
||||||
|
scroll-y
|
||||||
|
@scroll="monthScroll"
|
||||||
|
:scroll-top="scrollTop"
|
||||||
|
>
|
||||||
|
<view v-for="(item, index) in months" :key="index" :id="`month${index}`">
|
||||||
|
<month
|
||||||
|
:type="type"
|
||||||
|
:date="item.date"
|
||||||
|
:value="value"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:formatter="formatter"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:default-time="defaultTime"
|
||||||
|
:showTitle="index !== 0"
|
||||||
|
@change="handleDateChange"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view v-if="timeType" class="wd-month-panel__time">
|
||||||
|
<view v-if="type === 'datetimerange'" class="wd-month-panel__time-label">
|
||||||
|
<view class="wd-month-panel__time-text">{{ timeType === 'start' ? translate('startTime') : translate('endTime') }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="wd-month-panel__time-picker">
|
||||||
|
<wd-picker-view
|
||||||
|
v-if="timeData.length"
|
||||||
|
v-model="timeValue"
|
||||||
|
:columns="timeData"
|
||||||
|
:columns-height="125"
|
||||||
|
:immediate-change="immediateChange"
|
||||||
|
@change="handleTimeChange"
|
||||||
|
@pickstart="handlePickStart"
|
||||||
|
@pickend="handlePickEnd"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdPickerView from '../../wd-picker-view/wd-picker-view.vue'
|
||||||
|
import { computed, ref, watch, onMounted } from 'vue'
|
||||||
|
import { debounce, isArray, isEqual, isNumber, pause } from '../../common/util'
|
||||||
|
import { compareMonth, formatMonthTitle, getMonthEndDay, getMonths, getTimeData, getWeekLabel } from '../utils'
|
||||||
|
import Month from '../month/month.vue'
|
||||||
|
import { monthPanelProps, type MonthInfo, type MonthPanelTimeType, type MonthPanelExpose } from './types'
|
||||||
|
import { useTranslate } from '../../composables/useTranslate'
|
||||||
|
import type { CalendarItem } from '../types'
|
||||||
|
|
||||||
|
const props = defineProps(monthPanelProps)
|
||||||
|
const emit = defineEmits(['change', 'pickstart', 'pickend'])
|
||||||
|
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const scrollTop = ref<number>(0) // 滚动位置
|
||||||
|
const scrollIndex = ref<number>(0) // 当前显示的月份索引
|
||||||
|
const timeValue = ref<number[]>([]) // 当前选中的时分秒
|
||||||
|
|
||||||
|
const timeType = ref<MonthPanelTimeType>('') // 当前时间类型,是开始还是结束
|
||||||
|
const innerValue = ref<string | number | (number | null)[]>('') // 内部保存一个值,用于判断新老值,避免监听器触发
|
||||||
|
|
||||||
|
const handleChange = debounce((value) => {
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}, 50)
|
||||||
|
|
||||||
|
// 时间picker的列数据
|
||||||
|
const timeData = computed<Array<CalendarItem[]>>(() => {
|
||||||
|
let timeColumns: Array<CalendarItem[]> = []
|
||||||
|
if (props.type === 'datetime' && isNumber(props.value)) {
|
||||||
|
const date = new Date(props.value)
|
||||||
|
date.setHours(timeValue.value[0])
|
||||||
|
date.setMinutes(timeValue.value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : timeValue.value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
timeColumns = getTime(dateTime) || []
|
||||||
|
} else if (isArray(props.value) && props.type === 'datetimerange') {
|
||||||
|
const [start, end] = props.value!
|
||||||
|
const dataValue = timeType.value === 'start' ? start : end
|
||||||
|
const date = new Date(dataValue || '')
|
||||||
|
date.setHours(timeValue.value[0])
|
||||||
|
date.setMinutes(timeValue.value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : timeValue.value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
const finalValue = [start, end]
|
||||||
|
if (timeType.value === 'start') {
|
||||||
|
finalValue[0] = dateTime
|
||||||
|
} else {
|
||||||
|
finalValue[1] = dateTime
|
||||||
|
}
|
||||||
|
timeColumns = getTime(finalValue, timeType.value) || []
|
||||||
|
}
|
||||||
|
return timeColumns
|
||||||
|
})
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const title = computed(() => {
|
||||||
|
return formatMonthTitle(months.value[scrollIndex.value].date)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 周标题
|
||||||
|
const weekLabel = computed(() => {
|
||||||
|
return (index: number) => {
|
||||||
|
return getWeekLabel(index - 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 滚动区域的高度
|
||||||
|
const scrollHeight = computed(() => {
|
||||||
|
const scrollHeight: number = timeType.value ? props.panelHeight - 125 : props.panelHeight
|
||||||
|
return scrollHeight
|
||||||
|
})
|
||||||
|
|
||||||
|
// 月份日期和月份高度
|
||||||
|
const months = computed<MonthInfo[]>(() => {
|
||||||
|
return getMonths(props.minDate, props.maxDate).map((month, index) => {
|
||||||
|
const offset = (7 + new Date(month).getDay() - props.firstDayOfWeek) % 7
|
||||||
|
const totalDay = getMonthEndDay(new Date(month).getFullYear(), new Date(month).getMonth() + 1)
|
||||||
|
const rows = Math.ceil((offset + totalDay) / 7)
|
||||||
|
return {
|
||||||
|
height: rows * 64 + (rows - 1) * 4 + (index === 0 ? 0 : 45), // 每行64px高度,除最后一行外每行加4px margin,加上标题45px
|
||||||
|
date: month
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.type,
|
||||||
|
(val) => {
|
||||||
|
if (
|
||||||
|
(val === 'datetime' && props.value) ||
|
||||||
|
(val === 'datetimerange' && isArray(props.value) && props.value && props.value.length > 0 && props.value[0])
|
||||||
|
) {
|
||||||
|
setTime(props.value, 'start')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(val) => {
|
||||||
|
if (isEqual(val, innerValue.value)) return
|
||||||
|
|
||||||
|
if ((props.type === 'datetime' && val) || (props.type === 'datetimerange' && val && isArray(val) && val.length > 0 && val[0])) {
|
||||||
|
setTime(val, 'start')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
scrollIntoView()
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
async function scrollIntoView() {
|
||||||
|
// 等待渲染完毕
|
||||||
|
await pause()
|
||||||
|
let activeDate: number | null = 0
|
||||||
|
if (isArray(props.value)) {
|
||||||
|
// 对数组按时间排序,取第一个值
|
||||||
|
const sortedValue = [...props.value].sort((a, b) => (a || 0) - (b || 0))
|
||||||
|
activeDate = sortedValue[0]
|
||||||
|
} else if (isNumber(props.value)) {
|
||||||
|
activeDate = props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeDate) {
|
||||||
|
activeDate = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
let top: number = 0
|
||||||
|
let activeMonthIndex = -1
|
||||||
|
for (let index = 0; index < months.value.length; index++) {
|
||||||
|
if (compareMonth(months.value[index].date, activeDate) === 0) {
|
||||||
|
activeMonthIndex = index
|
||||||
|
// 找到选中月份后,计算选中日期在月份中的位置
|
||||||
|
const date = new Date(activeDate)
|
||||||
|
const day = date.getDate()
|
||||||
|
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1)
|
||||||
|
const offset = (7 + firstDay.getDay() - props.firstDayOfWeek) % 7
|
||||||
|
const row = Math.floor((offset + day - 1) / 7)
|
||||||
|
// 每行高度64px,每行加4px margin
|
||||||
|
top += row * 64 + row * 4
|
||||||
|
break
|
||||||
|
}
|
||||||
|
top += months.value[index] ? Number(months.value[index].height) : 0
|
||||||
|
}
|
||||||
|
scrollTop.value = 0
|
||||||
|
if (top > 0) {
|
||||||
|
await pause()
|
||||||
|
// 如果不是第一个月才加45
|
||||||
|
scrollTop.value = top + (activeMonthIndex > 0 ? 45 : 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取时间 picker 的数据
|
||||||
|
* @param {timestamp|array} value 当前时间
|
||||||
|
* @param {string} type 类型,是开始还是结束
|
||||||
|
*/
|
||||||
|
function getTime(value: number | (number | null)[], type?: string) {
|
||||||
|
if (props.type === 'datetime') {
|
||||||
|
return getTimeData({
|
||||||
|
date: value as number,
|
||||||
|
minDate: props.minDate,
|
||||||
|
maxDate: props.maxDate,
|
||||||
|
filter: props.timeFilter,
|
||||||
|
isHideSecond: props.hideSecond
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (type === 'start' && isArray(props.value)) {
|
||||||
|
return getTimeData({
|
||||||
|
date: (value as Array<number>)[0],
|
||||||
|
minDate: props.minDate,
|
||||||
|
maxDate: props.value[1] ? props.value[1] : props.maxDate,
|
||||||
|
filter: props.timeFilter,
|
||||||
|
isHideSecond: props.hideSecond
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return getTimeData({
|
||||||
|
date: (value as Array<number>)[1],
|
||||||
|
minDate: (value as Array<number>)[0],
|
||||||
|
maxDate: props.maxDate,
|
||||||
|
filter: props.timeFilter,
|
||||||
|
isHideSecond: props.hideSecond
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取 date 的时分秒
|
||||||
|
* @param {timestamp} date 时间
|
||||||
|
* @param {string} type 类型,是开始还是结束
|
||||||
|
*/
|
||||||
|
function getTimeValue(date: number | (number | null)[], type: MonthPanelTimeType) {
|
||||||
|
let dateValue: Date = new Date()
|
||||||
|
if (props.type === 'datetime') {
|
||||||
|
dateValue = new Date(date as number)
|
||||||
|
} else if (isArray(date)) {
|
||||||
|
if (type === 'start') {
|
||||||
|
dateValue = new Date(date[0] || '')
|
||||||
|
} else {
|
||||||
|
dateValue = new Date(date[1] || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hour = dateValue.getHours()
|
||||||
|
const minute = dateValue.getMinutes()
|
||||||
|
const second = dateValue.getSeconds()
|
||||||
|
return props.hideSecond ? [hour, minute] : [hour, minute, second]
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTime(value: number | (number | null)[], type?: MonthPanelTimeType) {
|
||||||
|
if (isArray(value) && value[0] && value[1] && type === 'start' && timeType.value === 'start') {
|
||||||
|
type = 'end'
|
||||||
|
}
|
||||||
|
timeType.value = type || ''
|
||||||
|
timeValue.value = getTimeValue(value, type || '')
|
||||||
|
}
|
||||||
|
function handleDateChange({ value, type }: { value: number | (number | null)[]; type?: MonthPanelTimeType }) {
|
||||||
|
if (!isEqual(value, props.value)) {
|
||||||
|
// 内部保存一个值,用于判断新老值,避免监听器触发
|
||||||
|
innerValue.value = value
|
||||||
|
handleChange(value)
|
||||||
|
}
|
||||||
|
// datetime 和 datetimerange 类型,需要计算 timeData 并做展示
|
||||||
|
if (props.type.indexOf('time') > -1) {
|
||||||
|
setTime(value, type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleTimeChange({ value }: { value: any[] }) {
|
||||||
|
if (!props.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (props.type === 'datetime' && isNumber(props.value)) {
|
||||||
|
const date = new Date(props.value)
|
||||||
|
date.setHours(value[0])
|
||||||
|
date.setMinutes(value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
handleChange(dateTime)
|
||||||
|
} else if (isArray(props.value) && props.type === 'datetimerange') {
|
||||||
|
const [start, end] = props.value!
|
||||||
|
const dataValue = timeType.value === 'start' ? start : end
|
||||||
|
const date = new Date(dataValue || '')
|
||||||
|
date.setHours(value[0])
|
||||||
|
date.setMinutes(value[1])
|
||||||
|
date.setSeconds(props.hideSecond ? 0 : value[2])
|
||||||
|
const dateTime = date.getTime()
|
||||||
|
|
||||||
|
if (dateTime === dataValue) return
|
||||||
|
|
||||||
|
const finalValue = [start, end]
|
||||||
|
if (timeType.value === 'start') {
|
||||||
|
finalValue[0] = dateTime
|
||||||
|
} else {
|
||||||
|
finalValue[1] = dateTime
|
||||||
|
}
|
||||||
|
innerValue.value = finalValue // 内部保存一个值,用于判断新老值,避免监听器触发
|
||||||
|
handleChange(finalValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handlePickStart() {
|
||||||
|
emit('pickstart')
|
||||||
|
}
|
||||||
|
function handlePickEnd() {
|
||||||
|
emit('pickend')
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthScroll = (event: { detail: { scrollTop: number } }) => {
|
||||||
|
if (months.value.length <= 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const scrollTop = Math.max(0, event.detail.scrollTop)
|
||||||
|
doSetSubtitle(scrollTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置小标题
|
||||||
|
* scrollTop 滚动条位置
|
||||||
|
*/
|
||||||
|
function doSetSubtitle(scrollTop: number) {
|
||||||
|
let height: number = 0 // 月份高度和
|
||||||
|
for (let index = 0; index < months.value.length; index++) {
|
||||||
|
height = height + months.value[index].height
|
||||||
|
if (scrollTop < height) {
|
||||||
|
scrollIndex.value = index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<MonthPanelExpose>({
|
||||||
|
scrollIntoView
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { makeBooleanProp, makeRequiredProp } from '../../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarTimeFilter, CalendarType } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 月份信息
|
||||||
|
*/
|
||||||
|
export interface MonthInfo {
|
||||||
|
date: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const monthPanelProps = {
|
||||||
|
type: makeRequiredProp(String as PropType<CalendarType>),
|
||||||
|
value: makeRequiredProp([Number, Array, null] as PropType<number | (number | null)[] | null>),
|
||||||
|
minDate: makeRequiredProp(Number),
|
||||||
|
maxDate: makeRequiredProp(Number),
|
||||||
|
firstDayOfWeek: makeRequiredProp(Number),
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
maxRange: Number,
|
||||||
|
rangePrompt: String,
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
showPanelTitle: makeBooleanProp(false),
|
||||||
|
defaultTime: {
|
||||||
|
type: [Array] as PropType<Array<number[]>>
|
||||||
|
},
|
||||||
|
panelHeight: makeRequiredProp(Number),
|
||||||
|
// type 为 'datetime' 或 'datetimerange' 时有效,用于过滤时间选择器的数据
|
||||||
|
timeFilter: Function as PropType<CalendarTimeFilter>,
|
||||||
|
hideSecond: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件,1.2.25版本起提供,仅微信小程序和支付宝小程序支持。
|
||||||
|
*/
|
||||||
|
immediateChange: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonthPanelProps = ExtractPropTypes<typeof monthPanelProps>
|
||||||
|
|
||||||
|
export type MonthPanelTimeType = 'start' | 'end' | ''
|
||||||
|
|
||||||
|
export type MonthPanelExpose = {
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
scrollIntoView: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonthPanelInstance = ComponentPublicInstance<MonthPanelProps, MonthPanelExpose>
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
|
||||||
|
import { baseProps, makeBooleanProp, makeNumberProp, makeRequiredProp, makeStringProp } from '../common/props'
|
||||||
|
|
||||||
|
export type CalendarType = 'date' | 'dates' | 'datetime' | 'week' | 'month' | 'daterange' | 'datetimerange' | 'weekrange' | 'monthrange'
|
||||||
|
|
||||||
|
export const calendarViewProps = {
|
||||||
|
...baseProps,
|
||||||
|
/**
|
||||||
|
* 选中值,为 13 位时间戳或时间戳数组
|
||||||
|
*/
|
||||||
|
modelValue: makeRequiredProp([Number, Array, null] as PropType<number | number[] | null>),
|
||||||
|
/**
|
||||||
|
* 日期类型
|
||||||
|
*/
|
||||||
|
type: makeStringProp<CalendarType>('date'),
|
||||||
|
/**
|
||||||
|
* 最小日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
minDate: makeNumberProp(new Date(new Date().getFullYear(), new Date().getMonth() - 6, new Date().getDate()).getTime()),
|
||||||
|
/**
|
||||||
|
* 最大日期,为 13 位时间戳
|
||||||
|
*/
|
||||||
|
maxDate: makeNumberProp(new Date(new Date().getFullYear(), new Date().getMonth() + 6, new Date().getDate(), 23, 59, 59).getTime()),
|
||||||
|
/**
|
||||||
|
* 周起始天
|
||||||
|
*/
|
||||||
|
firstDayOfWeek: makeNumberProp(0),
|
||||||
|
/**
|
||||||
|
* 日期格式化函数
|
||||||
|
*/
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,最大日期范围
|
||||||
|
*/
|
||||||
|
maxRange: Number,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,选择超出最大日期范围时的错误提示文案
|
||||||
|
*/
|
||||||
|
rangePrompt: String,
|
||||||
|
/**
|
||||||
|
* type 为范围选择时有效,是否允许选择同一天
|
||||||
|
*/
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
// 是否展示面板标题,自动计算当前滚动的日期月份
|
||||||
|
showPanelTitle: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 选中日期所使用的当日内具体时刻
|
||||||
|
*/
|
||||||
|
defaultTime: {
|
||||||
|
type: [String, Array] as PropType<string | string[]>,
|
||||||
|
default: '00:00:00'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 可滚动面板的高度
|
||||||
|
*/
|
||||||
|
panelHeight: makeNumberProp(378),
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,用于过滤时间选择器的数据
|
||||||
|
*/
|
||||||
|
timeFilter: Function as PropType<CalendarTimeFilter>,
|
||||||
|
/**
|
||||||
|
* type 为 'datetime' 或 'datetimerange' 时有效,是否不展示秒修改
|
||||||
|
*/
|
||||||
|
hideSecond: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件,1.2.25版本起提供,仅微信小程序和支付宝小程序支持。
|
||||||
|
*/
|
||||||
|
immediateChange: makeBooleanProp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarViewProps = ExtractPropTypes<typeof calendarViewProps>
|
||||||
|
|
||||||
|
export type CalendarDayType = '' | 'start' | 'middle' | 'end' | 'selected' | 'same' | 'current' | 'multiple-middle' | 'multiple-selected'
|
||||||
|
|
||||||
|
export type CalendarDayItem = {
|
||||||
|
date: number
|
||||||
|
text?: number | string
|
||||||
|
topInfo?: string
|
||||||
|
bottomInfo?: string
|
||||||
|
type?: CalendarDayType
|
||||||
|
disabled?: boolean
|
||||||
|
isLastRow?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarFormatter = (day: CalendarDayItem) => CalendarDayItem
|
||||||
|
|
||||||
|
export type CalendarTimeFilterOptionType = 'hour' | 'minute' | 'second'
|
||||||
|
|
||||||
|
export type CalendarTimeFilterOption = {
|
||||||
|
type: CalendarTimeFilterOptionType
|
||||||
|
values: CalendarItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarTimeFilter = (option: CalendarTimeFilterOption) => CalendarItem[]
|
||||||
|
|
||||||
|
export type CalendarItem = {
|
||||||
|
label: string
|
||||||
|
value: number
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarViewExpose = {
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
scrollIntoView: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CalendarViewInstance = ComponentPublicInstance<CalendarViewExpose, CalendarViewProps>
|
||||||
@ -0,0 +1,429 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import dayjs from '../../dayjs'
|
||||||
|
import { isArray, isFunction, padZero } from '../common/util'
|
||||||
|
import { useTranslate } from '../composables/useTranslate'
|
||||||
|
import type { CalendarDayType, CalendarItem, CalendarTimeFilter, CalendarType } from './types'
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const weeks = computed(() => {
|
||||||
|
return [
|
||||||
|
translate('weeks.sun'),
|
||||||
|
translate('weeks.mon'),
|
||||||
|
translate('weeks.tue'),
|
||||||
|
translate('weeks.wed'),
|
||||||
|
translate('weeks.thu'),
|
||||||
|
translate('weeks.fri'),
|
||||||
|
translate('weeks.sat')
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个时间的日期是否相等
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function compareDate(date1: number, date2: number | null) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2 || '')
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
const month1 = dateValue1.getMonth()
|
||||||
|
const month2 = dateValue2.getMonth()
|
||||||
|
const day1 = dateValue1.getDate()
|
||||||
|
const day2 = dateValue2.getDate()
|
||||||
|
|
||||||
|
if (year1 === year2) {
|
||||||
|
if (month1 === month2) {
|
||||||
|
return day1 === day2 ? 0 : day1 > day2 ? 1 : -1
|
||||||
|
}
|
||||||
|
return month1 === month2 ? 0 : month1 > month2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return year1 > year2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否是范围选择
|
||||||
|
* @param {string} type
|
||||||
|
*/
|
||||||
|
export function isRange(type: CalendarType) {
|
||||||
|
return type.indexOf('range') > -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个日期的月份是否相等
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function compareMonth(date1: number, date2: number) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2)
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
const month1 = dateValue1.getMonth()
|
||||||
|
const month2 = dateValue2.getMonth()
|
||||||
|
|
||||||
|
if (year1 === year2) {
|
||||||
|
return month1 === month2 ? 0 : month1 > month2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return year1 > year2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个日期的年份是否一致
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function compareYear(date1: number, date2: number) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2)
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
|
||||||
|
return year1 === year2 ? 0 : year1 > year2 ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个月的最后一天
|
||||||
|
* @param {number} year
|
||||||
|
* @param {number} month
|
||||||
|
*/
|
||||||
|
export function getMonthEndDay(year: number, month: number) {
|
||||||
|
return 32 - new Date(year, month - 1, 32).getDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化年月
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function formatMonthTitle(date: number) {
|
||||||
|
return dayjs(date).format(translate('monthTitle'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据下标获取星期
|
||||||
|
* @param {number} index
|
||||||
|
*/
|
||||||
|
export function getWeekLabel(index: number) {
|
||||||
|
if (index >= 7) {
|
||||||
|
index = index % 7
|
||||||
|
}
|
||||||
|
|
||||||
|
return weeks.value[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化年份
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function formatYearTitle(date: number) {
|
||||||
|
return dayjs(date).format(translate('yearTitle'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最小日期和最大日期获取这之间总共有几个月份
|
||||||
|
* @param {timestamp} minDate
|
||||||
|
* @param {timestamp} maxDate
|
||||||
|
*/
|
||||||
|
export function getMonths(minDate: number, maxDate: number) {
|
||||||
|
const months: number[] = []
|
||||||
|
const month = new Date(minDate)
|
||||||
|
month.setDate(1)
|
||||||
|
|
||||||
|
while (compareMonth(month.getTime(), maxDate) < 1) {
|
||||||
|
months.push(month.getTime())
|
||||||
|
month.setMonth(month.getMonth() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return months
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最小日期和最大日期获取这之间总共有几年
|
||||||
|
* @param {timestamp} minDate
|
||||||
|
* @param {timestamp} maxDate
|
||||||
|
*/
|
||||||
|
export function getYears(minDate: number, maxDate: number) {
|
||||||
|
const years: number[] = []
|
||||||
|
const year = new Date(minDate)
|
||||||
|
year.setMonth(0)
|
||||||
|
year.setDate(1)
|
||||||
|
|
||||||
|
while (compareYear(year.getTime(), maxDate) < 1) {
|
||||||
|
years.push(year.getTime())
|
||||||
|
year.setFullYear(year.getFullYear() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return years
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个日期所在周的第一天和最后一天
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function getWeekRange(date: number, firstDayOfWeek: number) {
|
||||||
|
if (firstDayOfWeek >= 7) {
|
||||||
|
firstDayOfWeek = firstDayOfWeek % 7
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setHours(0, 0, 0, 0)
|
||||||
|
const year = dateValue.getFullYear()
|
||||||
|
const month = dateValue.getMonth()
|
||||||
|
const day = dateValue.getDate()
|
||||||
|
const week = dateValue.getDay()
|
||||||
|
|
||||||
|
const weekStart = new Date(year, month, day - ((7 + week - firstDayOfWeek) % 7))
|
||||||
|
const weekEnd = new Date(year, month, day + 6 - ((7 + week - firstDayOfWeek) % 7))
|
||||||
|
|
||||||
|
return [weekStart.getTime(), weekEnd.getTime()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取日期偏移量
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function getDayOffset(date1: number, date2: number) {
|
||||||
|
return (date1 - date2) / (24 * 60 * 60 * 1000) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取偏移日期
|
||||||
|
* @param {timestamp} date
|
||||||
|
* @param {number} offset
|
||||||
|
*/
|
||||||
|
export function getDayByOffset(date: number, offset: number) {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setDate(dateValue.getDate() + offset)
|
||||||
|
|
||||||
|
return dateValue.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPrevDay = (date: number) => getDayByOffset(date, -1)
|
||||||
|
export const getNextDay = (date: number) => getDayByOffset(date, 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取月份偏移量
|
||||||
|
* @param {timestamp} date1
|
||||||
|
* @param {timestamp} date2
|
||||||
|
*/
|
||||||
|
export function getMonthOffset(date1: number, date2: number) {
|
||||||
|
const dateValue1 = new Date(date1)
|
||||||
|
const dateValue2 = new Date(date2)
|
||||||
|
|
||||||
|
const year1 = dateValue1.getFullYear()
|
||||||
|
const year2 = dateValue2.getFullYear()
|
||||||
|
let month1 = dateValue1.getMonth()
|
||||||
|
const month2 = dateValue2.getMonth()
|
||||||
|
|
||||||
|
month1 = (year1 - year2) * 12 + month1
|
||||||
|
|
||||||
|
return month1 - month2 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取偏移月份
|
||||||
|
* @param {timestamp} date
|
||||||
|
* @param {number} offset
|
||||||
|
*/
|
||||||
|
export function getMonthByOffset(date: number, offset: number) {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setMonth(dateValue.getMonth() + offset)
|
||||||
|
|
||||||
|
return dateValue.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认时间,格式化为数组
|
||||||
|
* @param {array|string|null} defaultTime
|
||||||
|
*/
|
||||||
|
export function getDefaultTime(defaultTime: string[] | string | null) {
|
||||||
|
if (isArray(defaultTime)) {
|
||||||
|
const startTime = (defaultTime[0] || '00:00:00').split(':').map((item: string) => {
|
||||||
|
return parseInt(item)
|
||||||
|
})
|
||||||
|
const endTime = (defaultTime[1] || '00:00:00').split(':').map((item) => {
|
||||||
|
return parseInt(item)
|
||||||
|
})
|
||||||
|
return [startTime, endTime]
|
||||||
|
} else {
|
||||||
|
const time = (defaultTime || '00:00:00').split(':').map((item) => {
|
||||||
|
return parseInt(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
return [time, time]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据默认时间获取日期
|
||||||
|
* @param {timestamp} date
|
||||||
|
* @param {array} defaultTime
|
||||||
|
*/
|
||||||
|
export function getDateByDefaultTime(date: number, defaultTime: number[]) {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
dateValue.setHours(defaultTime[0])
|
||||||
|
dateValue.setMinutes(defaultTime[1])
|
||||||
|
dateValue.setSeconds(defaultTime[2])
|
||||||
|
|
||||||
|
return dateValue.getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取经过 iteratee 格式化后的长度为 n 的数组
|
||||||
|
* @param {number} n
|
||||||
|
* @param {function} iteratee
|
||||||
|
*/
|
||||||
|
const times = (n: number, iteratee: (index: number) => CalendarItem) => {
|
||||||
|
let index: number = -1
|
||||||
|
const result: CalendarItem[] = Array(n < 0 ? 0 : n)
|
||||||
|
while (++index < n) {
|
||||||
|
result[index] = iteratee(index)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取时分秒
|
||||||
|
* @param {timestamp}} date
|
||||||
|
*/
|
||||||
|
const getTime = (date: number) => {
|
||||||
|
const dateValue = new Date(date)
|
||||||
|
return [dateValue.getHours(), dateValue.getMinutes(), dateValue.getSeconds()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最小最大日期获取时间数据,用于填入picker
|
||||||
|
* @param {*} param0
|
||||||
|
*/
|
||||||
|
export function getTimeData({
|
||||||
|
date,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
isHideSecond,
|
||||||
|
filter
|
||||||
|
}: {
|
||||||
|
date: number
|
||||||
|
minDate: number
|
||||||
|
maxDate: number
|
||||||
|
isHideSecond: boolean
|
||||||
|
filter?: CalendarTimeFilter
|
||||||
|
}) {
|
||||||
|
const compareMin = compareDate(date, minDate)
|
||||||
|
const compareMax = compareDate(date, maxDate)
|
||||||
|
|
||||||
|
let minHour = 0
|
||||||
|
let maxHour = 23
|
||||||
|
let minMinute = 0
|
||||||
|
let maxMinute = 59
|
||||||
|
let minSecond = 0
|
||||||
|
let maxSecond = 59
|
||||||
|
|
||||||
|
if (compareMin === 0) {
|
||||||
|
const minTime = getTime(minDate)
|
||||||
|
const currentTime = getTime(date)
|
||||||
|
|
||||||
|
minHour = minTime[0]
|
||||||
|
if (minTime[0] === currentTime[0]) {
|
||||||
|
minMinute = minTime[1]
|
||||||
|
|
||||||
|
if (minTime[1] === currentTime[1]) {
|
||||||
|
minSecond = minTime[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compareMax === 0) {
|
||||||
|
const maxTime = getTime(maxDate)
|
||||||
|
const currentTime = getTime(date)
|
||||||
|
|
||||||
|
maxHour = maxTime[0]
|
||||||
|
if (maxTime[0] === currentTime[0]) {
|
||||||
|
maxMinute = maxTime[1]
|
||||||
|
|
||||||
|
if (maxTime[1] === currentTime[1]) {
|
||||||
|
maxSecond = maxTime[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let columns: CalendarItem[][] = []
|
||||||
|
let hours = times(24, (index) => {
|
||||||
|
return {
|
||||||
|
label: translate('hour', padZero(index)),
|
||||||
|
value: index,
|
||||||
|
disabled: index < minHour || index > maxHour
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let minutes = times(60, (index) => {
|
||||||
|
return {
|
||||||
|
label: translate('minute', padZero(index)),
|
||||||
|
value: index,
|
||||||
|
disabled: index < minMinute || index > maxMinute
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let seconds: CalendarItem[] = []
|
||||||
|
if (filter && isFunction(filter)) {
|
||||||
|
hours = filter({
|
||||||
|
type: 'hour',
|
||||||
|
values: hours
|
||||||
|
})
|
||||||
|
minutes = filter({
|
||||||
|
type: 'minute',
|
||||||
|
values: minutes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHideSecond) {
|
||||||
|
seconds = times(60, (index) => {
|
||||||
|
return {
|
||||||
|
label: translate('second', padZero(index)),
|
||||||
|
value: index,
|
||||||
|
disabled: index < minSecond || index > maxSecond
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (filter && isFunction(filter)) {
|
||||||
|
seconds = filter({
|
||||||
|
type: 'second',
|
||||||
|
values: seconds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = isHideSecond ? [hours, minutes] : [hours, minutes, seconds]
|
||||||
|
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前是第几周
|
||||||
|
* @param {timestamp} date
|
||||||
|
*/
|
||||||
|
export function getWeekNumber(date: number | Date) {
|
||||||
|
date = new Date(date)
|
||||||
|
date.setHours(0, 0, 0, 0)
|
||||||
|
// Thursday in current week decides the year.
|
||||||
|
date.setDate(date.getDate() + 3 - ((date.getDay() + 6) % 7))
|
||||||
|
// January 4 is always in week 1.
|
||||||
|
const week = new Date(date.getFullYear(), 0, 4)
|
||||||
|
// Adjust to Thursday in week 1 and count number of weeks from date to week 1.
|
||||||
|
// Rounding should be fine for Daylight Saving Time. Its shift should never be more than 12 hours.
|
||||||
|
return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + ((week.getDay() + 6) % 7)) / 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getItemClass(monthType: CalendarDayType, value: number | null | (number | null)[], type: CalendarType) {
|
||||||
|
const classList = ['is-' + monthType]
|
||||||
|
|
||||||
|
if (type.indexOf('range') > -1 && isArray(value)) {
|
||||||
|
if (!value || !value[1]) {
|
||||||
|
classList.push('is-without-end')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return classList.join(' ')
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<view :class="`wd-calendar-view ${customClass}`">
|
||||||
|
<year-panel
|
||||||
|
v-if="type === 'month' || type === 'monthrange'"
|
||||||
|
ref="yearPanelRef"
|
||||||
|
:type="type"
|
||||||
|
:value="modelValue"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:formatter="formatter"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:show-panel-title="showPanelTitle"
|
||||||
|
:default-time="formatDefauleTime"
|
||||||
|
:panel-height="panelHeight"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
<month-panel
|
||||||
|
v-else
|
||||||
|
ref="monthPanelRef"
|
||||||
|
:type="type"
|
||||||
|
:value="modelValue"
|
||||||
|
:min-date="minDate"
|
||||||
|
:max-date="maxDate"
|
||||||
|
:first-day-of-week="firstDayOfWeek"
|
||||||
|
:formatter="formatter"
|
||||||
|
:max-range="maxRange"
|
||||||
|
:range-prompt="rangePrompt"
|
||||||
|
:allow-same-day="allowSameDay"
|
||||||
|
:show-panel-title="showPanelTitle"
|
||||||
|
:default-time="formatDefauleTime"
|
||||||
|
:panel-height="panelHeight"
|
||||||
|
:immediate-change="immediateChange"
|
||||||
|
:time-filter="timeFilter"
|
||||||
|
:hide-second="hideSecond"
|
||||||
|
@change="handleChange"
|
||||||
|
@pickstart="handlePickStart"
|
||||||
|
@pickend="handlePickEnd"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'wd-calendar-view',
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { getDefaultTime } from './utils'
|
||||||
|
import yearPanel from './yearPanel/year-panel.vue'
|
||||||
|
import MonthPanel from './monthPanel/month-panel.vue'
|
||||||
|
import { calendarViewProps, type CalendarViewExpose } from './types'
|
||||||
|
|
||||||
|
const props = defineProps(calendarViewProps)
|
||||||
|
const emit = defineEmits(['change', 'update:modelValue', 'pickstart', 'pickend'])
|
||||||
|
const formatDefauleTime = ref<number[][]>([])
|
||||||
|
|
||||||
|
const yearPanelRef = ref()
|
||||||
|
const monthPanelRef = ref()
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.defaultTime,
|
||||||
|
(newValue) => {
|
||||||
|
formatDefauleTime.value = getDefaultTime(newValue)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使当前日期或者选中日期滚动到可视区域
|
||||||
|
*/
|
||||||
|
function scrollIntoView() {
|
||||||
|
const panel = getPanel()
|
||||||
|
panel.scrollIntoView && panel.scrollIntoView()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPanel() {
|
||||||
|
return props.type.indexOf('month') > -1 ? yearPanelRef.value : monthPanelRef.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChange({ value }: { value: number | number[] | null }) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handlePickStart() {
|
||||||
|
emit('pickstart')
|
||||||
|
}
|
||||||
|
function handlePickEnd() {
|
||||||
|
emit('pickend')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose<CalendarViewExpose>({
|
||||||
|
scrollIntoView
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(year) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(months) {
|
||||||
|
color: $-dark-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month) {
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-year__month-text {
|
||||||
|
color: $-dark-color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(year) {
|
||||||
|
@include e(title) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 45px;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(months) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: $-calendar-day-fs;
|
||||||
|
color: $-calendar-day-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month) {
|
||||||
|
position: relative;
|
||||||
|
width: 25%;
|
||||||
|
height: $-calendar-day-height;
|
||||||
|
line-height: $-calendar-day-height;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: $-calendar-item-margin-bottom;
|
||||||
|
|
||||||
|
@include when(disabled) {
|
||||||
|
.wd-year__month-text {
|
||||||
|
color: $-calendar-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(current) {
|
||||||
|
color: $-calendar-active-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(selected) {
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(middle) {
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(start) {
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
border-radius: $-calendar-active-border 0 0 $-calendar-active-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-without-end::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(end) {
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
background: $-calendar-range-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
border-radius: 0 $-calendar-active-border $-calendar-active-border 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include when(same) {
|
||||||
|
color: $-calendar-selected-color;
|
||||||
|
|
||||||
|
.wd-year__month-text {
|
||||||
|
background: $-calendar-active-color;
|
||||||
|
border-radius: $-calendar-active-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include when(last-row){
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month-text) {
|
||||||
|
width: $-calendar-month-width;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month-top) {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include e(month-bottom) {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: $-calendar-info-fs;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import type { PropType } from 'vue'
|
||||||
|
import { makeBooleanProp, makeRequiredProp } from '../../common/props'
|
||||||
|
import type { CalendarFormatter, CalendarType } from '../types'
|
||||||
|
|
||||||
|
export const yearProps = {
|
||||||
|
type: makeRequiredProp(String as PropType<CalendarType>),
|
||||||
|
date: makeRequiredProp(Number),
|
||||||
|
value: makeRequiredProp([Number, Array] as PropType<number | (number | null)[] | null>),
|
||||||
|
minDate: makeRequiredProp(Number),
|
||||||
|
maxDate: makeRequiredProp(Number),
|
||||||
|
// 日期格式化函数
|
||||||
|
formatter: Function as PropType<CalendarFormatter>,
|
||||||
|
maxRange: Number,
|
||||||
|
rangePrompt: String,
|
||||||
|
allowSameDay: makeBooleanProp(false),
|
||||||
|
defaultTime: {
|
||||||
|
type: [Array] as PropType<Array<number[]>>
|
||||||
|
},
|
||||||
|
showTitle: makeBooleanProp(true)
|
||||||
|
}
|
||||||
@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<wd-toast selector="wd-year" />
|
||||||
|
|
||||||
|
<view class="wd-year year">
|
||||||
|
<view class="wd-year__title" v-if="showTitle">{{ yearTitle(date) }}</view>
|
||||||
|
<view class="wd-year__months">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in months"
|
||||||
|
:key="index"
|
||||||
|
:class="`wd-year__month ${item.disabled ? 'is-disabled' : ''} ${item.isLastRow ? 'is-last-row' : ''} ${
|
||||||
|
item.type ? monthTypeClass(item.type) : ''
|
||||||
|
}`"
|
||||||
|
@click="handleDateClick(index)"
|
||||||
|
>
|
||||||
|
<view class="wd-year__month-top">{{ item.topInfo }}</view>
|
||||||
|
<view class="wd-year__month-text">{{ getMonthLabel(item.date) }}</view>
|
||||||
|
<view class="wd-year__month-bottom">{{ item.bottomInfo }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
virtualHost: true,
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import wdToast from '../../wd-toast/wd-toast.vue'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { deepClone, isArray, isFunction } from '../../common/util'
|
||||||
|
import { compareMonth, formatYearTitle, getDateByDefaultTime, getItemClass, getMonthByOffset, getMonthOffset } from '../utils'
|
||||||
|
import { useToast } from '../../wd-toast'
|
||||||
|
import { useTranslate } from '../../composables/useTranslate'
|
||||||
|
import dayjs from '../../../dayjs'
|
||||||
|
import { yearProps } from './types'
|
||||||
|
import type { CalendarDayItem, CalendarDayType } from '../types'
|
||||||
|
|
||||||
|
const props = defineProps(yearProps)
|
||||||
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
|
const toast = useToast('wd-year')
|
||||||
|
const { translate } = useTranslate('calendar-view')
|
||||||
|
|
||||||
|
const months = ref<CalendarDayItem[]>([])
|
||||||
|
|
||||||
|
const monthTypeClass = computed(() => {
|
||||||
|
return (monthType: CalendarDayType) => {
|
||||||
|
return getItemClass(monthType, props.value, props.type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const yearTitle = computed(() => {
|
||||||
|
return (date: number) => {
|
||||||
|
return formatYearTitle(date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.type, () => props.date, () => props.value, () => props.minDate, () => props.maxDate, () => props.formatter],
|
||||||
|
() => {
|
||||||
|
setMonths()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function getMonthLabel(date: number) {
|
||||||
|
return dayjs(date).format(translate('month', date))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMonths() {
|
||||||
|
const monthList: CalendarDayItem[] = []
|
||||||
|
const date = new Date(props.date)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const value = props.value
|
||||||
|
|
||||||
|
if (props.type.indexOf('range') > -1 && value && !isArray(value)) {
|
||||||
|
console.error('[wot-design] value should be array when type is range')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let month = 0; month < 12; month++) {
|
||||||
|
const date = new Date(year, month, 1).getTime()
|
||||||
|
let type: CalendarDayType = getMonthType(date)
|
||||||
|
if (!type && compareMonth(date, Date.now()) === 0) {
|
||||||
|
type = 'current'
|
||||||
|
}
|
||||||
|
const monthObj = getFormatterDate(date, month, type)
|
||||||
|
monthList.push(monthObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
months.value = deepClone(monthList)
|
||||||
|
}
|
||||||
|
function getMonthType(date: number) {
|
||||||
|
if (props.type === 'monthrange' && isArray(props.value)) {
|
||||||
|
const [startDate, endDate] = props.value || []
|
||||||
|
|
||||||
|
if (startDate && compareMonth(date, startDate) === 0) {
|
||||||
|
if (endDate && compareMonth(startDate, endDate) === 0) {
|
||||||
|
return 'same'
|
||||||
|
}
|
||||||
|
return 'start'
|
||||||
|
} else if (endDate && compareMonth(date, endDate) === 0) {
|
||||||
|
return 'end'
|
||||||
|
} else if (startDate && endDate && compareMonth(date, startDate) === 1 && compareMonth(date, endDate) === -1) {
|
||||||
|
return 'middle'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.value && compareMonth(date, props.value as number) === 0) {
|
||||||
|
return 'selected'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleDateClick(index: number) {
|
||||||
|
const date = months.value[index]
|
||||||
|
|
||||||
|
if (date.disabled) return
|
||||||
|
|
||||||
|
switch (props.type) {
|
||||||
|
case 'month':
|
||||||
|
handleMonthChange(date)
|
||||||
|
break
|
||||||
|
case 'monthrange':
|
||||||
|
handleMonthRangeChange(date)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
handleMonthChange(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getDate(date: number) {
|
||||||
|
return props.defaultTime && props.defaultTime.length > 0 ? getDateByDefaultTime(date, props.defaultTime[0]) : date
|
||||||
|
}
|
||||||
|
function handleMonthChange(date: CalendarDayItem) {
|
||||||
|
if (date.type !== 'selected') {
|
||||||
|
emit('change', {
|
||||||
|
value: getDate(date.date)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleMonthRangeChange(date: CalendarDayItem) {
|
||||||
|
let value: (number | null)[] = []
|
||||||
|
const [startDate, endDate] = isArray(props.value) ? props.value || [] : []
|
||||||
|
const compare = compareMonth(date.date, startDate!)
|
||||||
|
|
||||||
|
// 禁止选择同个日期
|
||||||
|
if (!props.allowSameDay && !endDate && compare === 0) return
|
||||||
|
|
||||||
|
if (startDate && !endDate && compare > -1) {
|
||||||
|
if (props.maxRange && getMonthOffset(date.date, startDate) > props.maxRange) {
|
||||||
|
const maxEndDate = getMonthByOffset(startDate, props.maxRange - 1)
|
||||||
|
value = [startDate, getDate(maxEndDate)]
|
||||||
|
toast.show({
|
||||||
|
msg: props.rangePrompt || translate('rangePromptMonth', props.maxRange)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
value = [startDate, getDate(date.date)]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = [getDate(date.date), null]
|
||||||
|
}
|
||||||
|
emit('change', {
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatterDate(date: number, month: number, type?: CalendarDayType) {
|
||||||
|
let monthObj: CalendarDayItem = {
|
||||||
|
date: date,
|
||||||
|
text: month + 1,
|
||||||
|
topInfo: '',
|
||||||
|
bottomInfo: '',
|
||||||
|
type,
|
||||||
|
disabled: compareMonth(date, props.minDate) === -1 || compareMonth(date, props.maxDate) === 1,
|
||||||
|
isLastRow: month >= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.formatter) {
|
||||||
|
if (isFunction(props.formatter)) {
|
||||||
|
monthObj = props.formatter(monthObj)
|
||||||
|
} else {
|
||||||
|
console.error('[wot-design] error(wd-calendar-view): the formatter prop of wd-calendar-view should be a function')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return monthObj
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import './index.scss';
|
||||||
|
</style>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
@import '../../common/abstracts/variable';
|
||||||
|
@import '../../common/abstracts/mixin';
|
||||||
|
|
||||||
|
.wot-theme-dark {
|
||||||
|
@include b(year-panel) {
|
||||||
|
@include e(title) {
|
||||||
|
color: $-dark-color;
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(255, 255,255, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include b(year-panel) {
|
||||||
|
font-size: $-calendar-fs;
|
||||||
|
padding: $-calendar-panel-padding;
|
||||||
|
|
||||||
|
@include e(title) {
|
||||||
|
padding: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: $-calendar-panel-title-fs;
|
||||||
|
color: $-calendar-panel-title-color;
|
||||||
|
box-shadow: 0px 4px 8px 0 rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
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