mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 09:08:51 +08:00
feat: ✨ 增加message-box组件
This commit is contained in:
parent
62d3c53575
commit
26c5bc9074
@ -91,7 +91,6 @@
|
||||
"sass": "^1.59.3",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^4.9.4",
|
||||
"uni-mini-router": "^0.0.12",
|
||||
"uni-read-pages-vite": "^0.0.6",
|
||||
"vite": "4.0.3",
|
||||
"vitest": "^0.30.1",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<!--
|
||||
* @Author: weisheng
|
||||
* @Date: 2023-03-09 19:23:03
|
||||
* @LastEditTime: 2023-06-11 13:23:52
|
||||
* @LastEditTime: 2023-06-18 23:54:22
|
||||
* @LastEditors: weisheng
|
||||
* @Description:
|
||||
* @FilePath: \wot-design-uni\src\App.vue
|
||||
@ -20,6 +20,10 @@ onHide(() => {
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import './uni_modules/wot-design-uni/components/wd-modal/custom.scss';
|
||||
@import './uni_modules/wot-design-uni/components/wd-input/custom.scss';
|
||||
@import './uni_modules/wot-design-uni/components/wd-search/custom.scss';
|
||||
|
||||
page {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
@ -9,11 +9,9 @@
|
||||
*/
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
app.config.warnHandler = () => null
|
||||
app.use(router)
|
||||
return {
|
||||
app
|
||||
}
|
||||
|
||||
@ -100,6 +100,76 @@
|
||||
},
|
||||
"navigationBarTitleText": "Tag 单元格"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/search/Index",
|
||||
"name": "search",
|
||||
"style": {
|
||||
"mp-alipay": {
|
||||
"allowsBounceVertical": "NO"
|
||||
},
|
||||
"navigationBarTitleText": "Search 单元格"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/transition/Index",
|
||||
"name": "transition",
|
||||
"style": {
|
||||
"mp-alipay": {
|
||||
"allowsBounceVertical": "NO"
|
||||
},
|
||||
"navigationBarTitleText": "Transition 单元格"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/popup/Index",
|
||||
"name": "popup",
|
||||
"style": {
|
||||
"mp-alipay": {
|
||||
"allowsBounceVertical": "NO"
|
||||
},
|
||||
"navigationBarTitleText": "Popup 单元格"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/divider/Index",
|
||||
"name": "divider",
|
||||
"style": {
|
||||
"mp-alipay": {
|
||||
"allowsBounceVertical": "NO"
|
||||
},
|
||||
"navigationBarTitleText": "Divider 单元格"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/switch/Index",
|
||||
"name": "switch",
|
||||
"style": {
|
||||
"mp-alipay": {
|
||||
"allowsBounceVertical": "NO"
|
||||
},
|
||||
"navigationBarTitleText": "Switch 单元格"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/input/Index",
|
||||
"name": "input",
|
||||
"style": {
|
||||
"mp-alipay": {
|
||||
"allowsBounceVertical": "NO"
|
||||
},
|
||||
"navigationBarTitleText": "Input 单元格"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/messageBox/Index",
|
||||
"name": "messageBox",
|
||||
"style": {
|
||||
"mp-alipay": {
|
||||
"allowsBounceVertical": "NO"
|
||||
},
|
||||
"navigationBarTitleText": "MessageBox 单元格"
|
||||
}
|
||||
}
|
||||
],
|
||||
// "tabBar": {
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<style lang="scss" scoped>
|
||||
.badge {
|
||||
:deep(.badge) {
|
||||
margin: 0 30px 20px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@ -1,7 +1,19 @@
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<!--
|
||||
* @Author: weisheng
|
||||
* @Date: 2023-06-13 11:47:12
|
||||
* @LastEditTime: 2023-06-14 18:47:38
|
||||
* @LastEditors: weisheng
|
||||
* @Description:
|
||||
* @FilePath: \wot-design-uni\src\pages\divider\Index.vue
|
||||
* 记得注释
|
||||
-->
|
||||
<template>
|
||||
<demo-block title="基本用法" transparent>
|
||||
<wd-divider>这是分割线</wd-divider>
|
||||
</demo-block>
|
||||
<demo-block title="自定义颜色" transparent>
|
||||
<wd-divider color="#4D80F0">自定义颜色</wd-divider>
|
||||
</demo-block>
|
||||
</template>
|
||||
<script lang="ts" setup></script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -1,7 +1,115 @@
|
||||
<template>
|
||||
<view>
|
||||
<demo-block title="基本用法">
|
||||
<wd-input type="text" v-model="value" placeholder="请输入用户名" @change="handleChange" @blur="handleBlur" />
|
||||
</demo-block>
|
||||
<demo-block title="禁用状态">
|
||||
<wd-input type="text" v-model="value1" disabled="true" />
|
||||
</demo-block>
|
||||
<demo-block title="只读状态">
|
||||
<wd-input type="text" v-model="value2" readonly />
|
||||
</demo-block>
|
||||
<demo-block title="错误状态">
|
||||
<wd-input type="text" v-model="value3" placeholder="请输入用户名" error />
|
||||
</demo-block>
|
||||
<demo-block title="清空按钮">
|
||||
<wd-input type="text" v-model="value4" clearable @change="handleChange1" />
|
||||
</demo-block>
|
||||
<demo-block title="密码框">
|
||||
<wd-input type="text" v-model="value5" clearable show-password @change="handleChange2" />
|
||||
</demo-block>
|
||||
<demo-block title="设置前后Icon">
|
||||
<wd-input type="text" v-model="value6" prefix-icon="dong" suffix-icon="list" clearable @change="handleChange3" />
|
||||
</demo-block>
|
||||
<demo-block title="字数限制">
|
||||
<wd-input type="text" v-model="value7" maxlength="20" show-word-limit />
|
||||
</demo-block>
|
||||
<demo-block title="取消底部边框,自定义使用">
|
||||
<wd-input v-model="value8" no-border placeholder="请输入价格" custom-style="display: inline-block; width: 70px; vertical-align: middle;" />
|
||||
<text style="display: inline-block; vertical-align: middle; font-size: 14px">元</text>
|
||||
</demo-block>
|
||||
<demo-block title="textarea" transparent>
|
||||
<wd-input type="textarea" v-model="value9" placeholder="请填写评价" @blur="handleBlur" />
|
||||
</demo-block>
|
||||
<demo-block title="textarea 清空按钮 和 字数限制" transparent>
|
||||
<wd-input type="textarea" v-model="value10" :maxlength="120" clearable show-word-limit />
|
||||
</demo-block>
|
||||
<demo-block title="textarea 高度自适应">
|
||||
<wd-input type="textarea" v-model="value11" auto-height="true" clearable></wd-input>
|
||||
</demo-block>
|
||||
<demo-block title="cell 类型" transparent>
|
||||
<wd-cell-group border>
|
||||
<wd-input type="text" label="基本用法" v-model="value12" placeholder="请输入..." />
|
||||
<wd-input type="text" label="禁用" v-model="value13" disabled placeholder="用户名" />
|
||||
<wd-input type="text" label="清除、密码" v-model="value14" placeholder="密码" clearable show-password />
|
||||
<wd-input type="text" label="错误状态" v-model="value15" placeholder="请输入用户名" error />
|
||||
<wd-input type="text" label="必填" v-model="value16" placeholder="请输入用户名" required />
|
||||
<wd-input type="text" label="图标" v-model="value17" placeholder="请输入..." prefix-icon="dong" suffix-icon="list" />
|
||||
<wd-input type="text" label="自定义插槽" v-model="value18" placeholder="请输入..." use-suffix-slot clearable>
|
||||
<template #suffix>
|
||||
<wd-button size="small" custom-class="button">获取验证码</wd-button>
|
||||
</template>
|
||||
</wd-input>
|
||||
<wd-input type="text" label="大尺寸" size="large" v-model="value19" placeholder="请输入..." />
|
||||
</wd-cell-group>
|
||||
</demo-block>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
const value = ref<string>('')
|
||||
const value1 = ref<string>('这是禁用状态')
|
||||
const value2 = ref<string>('这是只读状态')
|
||||
const value3 = ref<string>('123456')
|
||||
const value4 = ref<string>('支持清空')
|
||||
const value5 = ref<string>('password')
|
||||
const value6 = ref<string>('')
|
||||
const value7 = ref<string>('1234')
|
||||
const value8 = ref<string>('')
|
||||
const value9 = ref<string>('')
|
||||
const value10 = ref<string>('支持清空和字数限制的文本域')
|
||||
const value11 = ref<string>('输入文字后,输入框高度跟随字数多少变化')
|
||||
const value12 = ref<string>('')
|
||||
const value13 = ref<string>('该输入框禁用')
|
||||
const value14 = ref<string>('12345678')
|
||||
const value15 = ref<string>('')
|
||||
const value16 = ref<string>('')
|
||||
const value17 = ref<string>('')
|
||||
const value18 = ref<string>('')
|
||||
const value19 = ref<string>('')
|
||||
|
||||
function handleChange(event) {
|
||||
console.log(event)
|
||||
}
|
||||
function handleChange1(event) {
|
||||
console.log(event)
|
||||
}
|
||||
function handleChange2(event) {
|
||||
console.log(event)
|
||||
}
|
||||
function handleChange3(event) {
|
||||
console.log(event)
|
||||
}
|
||||
function handleBlur(event) {
|
||||
console.log('失焦', event)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.suffix-slot {
|
||||
display: inline-block;
|
||||
height: 37px;
|
||||
line-height: 37px;
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
:deep(.button) {
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<template></template>
|
||||
<script lang="ts" setup></script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -1,7 +1,86 @@
|
||||
<template>
|
||||
<wd-message-box></wd-message-box>
|
||||
<wd-toast id="wd-toast"></wd-toast>
|
||||
<wd-message-box selector="wd-message-box-slot" use-slot>
|
||||
<wd-rate custom-class="custom-rate-class" v-model="value" />
|
||||
</wd-message-box>
|
||||
<demo-block title="alert">
|
||||
<wd-button @click="alert">alert</wd-button>
|
||||
</demo-block>
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<demo-block title="显示标题">
|
||||
<wd-button @click="alertWithTitle">alert</wd-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="confirm">
|
||||
<wd-button @click="confirm">confirm</wd-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="prompt">
|
||||
<wd-button @click="prompt">prompt</wd-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="当文案过长时,弹框的高度不再增加,而是将文案内容设置成滚动">
|
||||
<wd-button @click="alertWithLongChar">alert</wd-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="使用wd-message-box组件,通过slot插入其他组件内容">
|
||||
<wd-button @click="withSlot">custom</wd-button>
|
||||
</demo-block>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from '@/uni_modules/wot-design-uni/components/wd-message-box'
|
||||
import { ref } from 'vue'
|
||||
const value = ref<number>(1)
|
||||
const value1 = ref<string>('')
|
||||
|
||||
const message = useMessage()
|
||||
const message1 = useMessage('wd-message-box-slot')
|
||||
|
||||
function alert() {
|
||||
message.alert('操作成功')
|
||||
}
|
||||
function alertWithTitle() {
|
||||
message.alert({
|
||||
msg: '提示文案',
|
||||
title: '标题'
|
||||
})
|
||||
}
|
||||
function confirm() {
|
||||
message.confirm({
|
||||
msg: '是否删除',
|
||||
title: '提示'
|
||||
})
|
||||
}
|
||||
function prompt() {
|
||||
message
|
||||
.prompt({
|
||||
title: '请输入邮箱',
|
||||
inputValue: value1.value,
|
||||
inputPattern: /.+@.+\..+/i
|
||||
})
|
||||
.then((resp) => {
|
||||
console.log(resp)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
function alertWithLongChar() {
|
||||
message.alert({
|
||||
msg: '以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文字是示意以上文',
|
||||
title: '标题'
|
||||
})
|
||||
}
|
||||
function withSlot() {
|
||||
message1.show({
|
||||
title: '评分'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.custom-rate-class) {
|
||||
display: block;
|
||||
height: 22px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,75 @@
|
||||
<template>
|
||||
<view>
|
||||
<demo-block title="基本用法">
|
||||
<wd-button @click="handleClick1">弹出层</wd-button>
|
||||
</demo-block>
|
||||
<demo-block title="弹出位置">
|
||||
<wd-button @click="handleClick2">顶部</wd-button>
|
||||
<wd-button @click="handleClick3">右侧</wd-button>
|
||||
<wd-button @click="handleClick4">底部</wd-button>
|
||||
<wd-button @click="handleClick5">左侧</wd-button>
|
||||
</demo-block>
|
||||
<demo-block title="关闭按钮">
|
||||
<wd-button @click="handleClick6">关闭按钮</wd-button>
|
||||
</demo-block>
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<wd-popup v-model="show1" custom-style="padding: 30px 40px;" @close="handleClose1">内容</wd-popup>
|
||||
<wd-popup v-model="show2" position="top" custom-style="height: 200px;" @close="handleClose2"></wd-popup>
|
||||
<wd-popup v-model="show3" position="right" custom-style="width: 200px;" @close="handleClose3"></wd-popup>
|
||||
<wd-popup v-model="show4" position="bottom" custom-style="height: 200px;" @close="handleClose4"></wd-popup>
|
||||
<wd-popup v-model="show5" position="left" custom-style="width: 200px;" @close="handleClose5"></wd-popup>
|
||||
<wd-popup v-model="show6" position="bottom" closable custom-style="height: 200px;" @close="handleClose6"></wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const show1 = ref<boolean>(false)
|
||||
const show2 = ref<boolean>(false)
|
||||
const show3 = ref<boolean>(false)
|
||||
const show4 = ref<boolean>(false)
|
||||
const show5 = ref<boolean>(false)
|
||||
const show6 = ref<boolean>(false)
|
||||
|
||||
function handleClick1() {
|
||||
show1.value = true
|
||||
}
|
||||
function handleClose1() {
|
||||
show1.value = false
|
||||
}
|
||||
function handleClick2() {
|
||||
show2.value = true
|
||||
}
|
||||
function handleClose2() {
|
||||
show2.value = false
|
||||
}
|
||||
function handleClick3() {
|
||||
show3.value = true
|
||||
}
|
||||
function handleClose3() {
|
||||
show3.value = false
|
||||
}
|
||||
function handleClick4() {
|
||||
show4.value = true
|
||||
}
|
||||
function handleClose4() {
|
||||
show4.value = false
|
||||
}
|
||||
function handleClick5() {
|
||||
show5.value = true
|
||||
}
|
||||
function handleClose5() {
|
||||
show5.value = false
|
||||
}
|
||||
function handleClick6() {
|
||||
show6.value = true
|
||||
}
|
||||
function handleClose6() {
|
||||
show6.value = false
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(button) {
|
||||
margin: 0 10px 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,106 @@
|
||||
<template>
|
||||
<wd-toast id="wd-toast" />
|
||||
<demo-block title="基本用法" transparent>
|
||||
<wd-search v-model="value1" @search="search" @change="change" @cancel="cancel" @clear="clear" />
|
||||
</demo-block>
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<demo-block title="白色输入框" transparent>
|
||||
<wd-search light />
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="搜索占位符居左" transparent>
|
||||
<wd-search placeholder-left />
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="禁用且隐藏取消按钮" transparent>
|
||||
<wd-search disabled hide-cancel />
|
||||
</demo-block>
|
||||
|
||||
<view style="margin: 15px 0; color: #666">
|
||||
<view style="padding: 0 15px; margin: 10px 0; font-size: 13px">自定义左侧插槽</view>
|
||||
<wd-search v-model="value3">
|
||||
<template #prefix>
|
||||
<wd-popover mode="menu" :content="menu" @menuclick="changeSearchType">
|
||||
<view class="search-type">
|
||||
<text>{{ searchType }}</text>
|
||||
<wd-icon class="icon-arrow" name="fill-arrow-down"></wd-icon>
|
||||
</view>
|
||||
</wd-popover>
|
||||
</template>
|
||||
</wd-search>
|
||||
</view>
|
||||
|
||||
<demo-block title="自定义右侧文案" transparent>
|
||||
<wd-search placeholder="请输入订单号/订单名称" cancel-txt="搜索" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block title="设置最大长度" transparent>
|
||||
<wd-search v-model="value2" :maxlength="4" />
|
||||
</demo-block>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const value1 = ref<string>('')
|
||||
const value2 = ref<string>('初始文案')
|
||||
const value3 = ref<string>('')
|
||||
const searchType = ref<string>('全部')
|
||||
const menu = ref([
|
||||
{
|
||||
content: '全部'
|
||||
},
|
||||
{
|
||||
content: '订单号'
|
||||
},
|
||||
{
|
||||
content: '退款单号'
|
||||
}
|
||||
])
|
||||
|
||||
function search(e) {
|
||||
uni.showToast({ title: '搜索' + e.value })
|
||||
}
|
||||
function clear() {
|
||||
uni.showToast({ title: '清空' })
|
||||
}
|
||||
function cancel() {
|
||||
uni.showToast({ title: '取消' })
|
||||
}
|
||||
function change(e) {
|
||||
console.log(e.value)
|
||||
}
|
||||
function changeSearchType(e) {
|
||||
// this.setData({
|
||||
// searchType: e.detail.item.content
|
||||
// })
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.search-type {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 8px 0 16px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
.search-type::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 1px;
|
||||
right: 0;
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
.search-type .icon-arrow {
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
.overflowauto {
|
||||
overflow: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,99 @@
|
||||
<template>
|
||||
<!-- <wd-message-box id="wd-message-box"></wd-message-box> -->
|
||||
<view>
|
||||
<demo-block title="基本用法">
|
||||
<wd-switch v-model="checked1" @change="handleChange1" />
|
||||
</demo-block>
|
||||
<demo-block title="修改值 active-value 、 inactive-value">
|
||||
<view style="margin-bottom: 10px">{{ checked2 }}</view>
|
||||
<wd-switch v-model="checked2" active-value="京麦" inactive-value="商家后台" @change="handleChange2" />
|
||||
</demo-block>
|
||||
<demo-block title="自定义颜色 active-color 、 inactive-color">
|
||||
<wd-switch v-model="checked3" active-color="#13ce66" inactive-color="#f00" @change="handleChange3" />
|
||||
</demo-block>
|
||||
<demo-block title="修改大小">
|
||||
<wd-switch v-model="checked4" size="20px" @change="handleChange4" />
|
||||
</demo-block>
|
||||
<demo-block title="选中禁用">
|
||||
<wd-switch v-model="checked5" disabled />
|
||||
</demo-block>
|
||||
<demo-block title="非选中禁用">
|
||||
<wd-switch v-model="checked6" disabled />
|
||||
</demo-block>
|
||||
<demo-block title="before-change 修改前钩子函数">
|
||||
<wd-switch v-model="checked7" :before-change="beforeChange" @change="handleChange5" />
|
||||
</demo-block>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
const checked1 = ref<boolean>(true)
|
||||
const checked2 = ref<string>('京麦')
|
||||
const checked3 = ref<boolean>(true)
|
||||
const checked4 = ref<boolean>(true)
|
||||
const checked5 = ref<boolean>(true)
|
||||
const checked6 = ref<boolean>(false)
|
||||
const checked7 = ref<boolean>(false)
|
||||
|
||||
const beforeChange = ({ value, resolve }) => {
|
||||
console.log(value)
|
||||
|
||||
resolve(true)
|
||||
// MessageBox.confirm('是否切换开关')
|
||||
// .then(() => {
|
||||
// resolve(true)
|
||||
// })
|
||||
// .catch(() => {
|
||||
// resolve(false)
|
||||
// })
|
||||
}
|
||||
function handleChange1({ value }) {
|
||||
console.log(value)
|
||||
}
|
||||
function handleChange2({ value }) {
|
||||
console.log(value)
|
||||
}
|
||||
function handleChange3({ value }) {
|
||||
console.log(value)
|
||||
}
|
||||
function handleChange4({ value }) {
|
||||
console.log(value)
|
||||
}
|
||||
function handleChange5({ value }) {
|
||||
console.log(value)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ededed;
|
||||
}
|
||||
.row {
|
||||
margin: 10px 0;
|
||||
padding: 0 10px;
|
||||
background: rgb(255, 255, 255);
|
||||
}
|
||||
.desc {
|
||||
padding: 0 15px;
|
||||
font-size: 14px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.code {
|
||||
color: rgb(0, 131, 255);
|
||||
white-space: nowrap;
|
||||
font-size: 0.8em;
|
||||
background-color: rgb(248, 248, 248);
|
||||
-webkit-font-smoothing: initial;
|
||||
padding: 1px 2px;
|
||||
margin: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.center {
|
||||
text-align: center;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.test {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* @Author: weisheng
|
||||
* @Date: 2023-06-12 18:40:59
|
||||
* @LastEditTime: 2023-06-15 12:46:49
|
||||
* @LastEditors: weisheng
|
||||
* @Description:
|
||||
* @FilePath: \wot-design-uni\src\pages\switch\index.js
|
||||
* 记得注释
|
||||
*/
|
||||
import MessageBox from '../../wot-design/messageBox/messageBox'
|
||||
|
||||
Page({
|
||||
@ -9,37 +18,39 @@ Page({
|
||||
checked5: true,
|
||||
checked6: false,
|
||||
checked7: false,
|
||||
beforeChange ({ value, resolve }) {
|
||||
MessageBox.confirm('是否切换开关').then(() => {
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
resolve(false)
|
||||
})
|
||||
beforeChange({ value, resolve }) {
|
||||
MessageBox.confirm('是否切换开关')
|
||||
.then(() => {
|
||||
resolve(true)
|
||||
})
|
||||
.catch(() => {
|
||||
resolve(false)
|
||||
})
|
||||
}
|
||||
},
|
||||
handleChange1 ({ detail }) {
|
||||
handleChange1({ detail }) {
|
||||
this.setData({
|
||||
checked1: detail.value
|
||||
})
|
||||
},
|
||||
handleChange2 ({ detail }) {
|
||||
handleChange2({ detail }) {
|
||||
this.setData({
|
||||
checked2: detail.value
|
||||
})
|
||||
},
|
||||
handleChange3 ({ detail }) {
|
||||
handleChange3({ detail }) {
|
||||
this.setData({
|
||||
checked3: detail.value
|
||||
})
|
||||
},
|
||||
handleChange4 ({ detail }) {
|
||||
handleChange4({ detail }) {
|
||||
this.setData({
|
||||
checked4: detail.value
|
||||
})
|
||||
},
|
||||
handleChange5 ({ detail }) {
|
||||
handleChange5({ detail }) {
|
||||
this.setData({
|
||||
checked7: detail.value
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,3 +1,114 @@
|
||||
<template></template>
|
||||
<script lang="ts" setup></script>
|
||||
<style lang="scss" scoped></style>
|
||||
<template>
|
||||
<view>
|
||||
<demo-block title="Fade 动画">
|
||||
<wd-button @click="fade">fade</wd-button>
|
||||
<wd-button @click="fadeUp">fade-up</wd-button>
|
||||
<wd-button @click="fadeDown">fade-down</wd-button>
|
||||
<wd-button @click="fadeLeft">fade-left</wd-button>
|
||||
<wd-button @click="fadeRight">fade-right</wd-button>
|
||||
</demo-block>
|
||||
<demo-block title="Slide 动画">
|
||||
<wd-button @click="slideUp">slide-up</wd-button>
|
||||
<wd-button @click="slideDown">slide-down</wd-button>
|
||||
<wd-button @click="slideLeft">slide-left</wd-button>
|
||||
<wd-button @click="slideRight">slide-right</wd-button>
|
||||
</demo-block>
|
||||
<demo-block title="Zoom 动画">
|
||||
<wd-button @click="zoomIn">zoom-in</wd-button>
|
||||
</demo-block>
|
||||
<demo-block title="自定义动画">
|
||||
<wd-button @click="custom">custom</wd-button>
|
||||
</demo-block>
|
||||
|
||||
<wd-transition :show="show" :name="name" custom-class="block" />
|
||||
|
||||
<wd-transition
|
||||
:show="customShow"
|
||||
:duration="{ enter: 700, leave: 1000 }"
|
||||
enter-class="custom-enter"
|
||||
enter-active-class="custom-enter-active"
|
||||
enter-to-class="custom-enter-to"
|
||||
leave-class="custom-leave"
|
||||
leave-active-class="custom-leave-active"
|
||||
leave-to-class="custom-leave-to"
|
||||
custom-class="block"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const show = ref<boolean>(false)
|
||||
const name = ref<string>('')
|
||||
const customShow = ref<boolean>(false)
|
||||
function fade() {
|
||||
transition('fade')
|
||||
}
|
||||
function fadeUp() {
|
||||
transition('fade-up')
|
||||
}
|
||||
function fadeDown() {
|
||||
transition('fade-down')
|
||||
}
|
||||
function fadeLeft() {
|
||||
transition('fade-left')
|
||||
}
|
||||
function fadeRight() {
|
||||
transition('fade-right')
|
||||
}
|
||||
function slideUp() {
|
||||
transition('slide-up')
|
||||
}
|
||||
function slideDown() {
|
||||
transition('slide-down')
|
||||
}
|
||||
function slideLeft() {
|
||||
transition('slide-left')
|
||||
}
|
||||
function slideRight() {
|
||||
transition('slide-right')
|
||||
}
|
||||
function zoomIn() {
|
||||
transition('zoom-in')
|
||||
}
|
||||
function custom() {
|
||||
customShow.value = true
|
||||
setTimeout(() => {
|
||||
customShow.value = false
|
||||
}, 1200)
|
||||
}
|
||||
function transition(transition) {
|
||||
name.value = transition
|
||||
show.value = true
|
||||
setTimeout(() => {
|
||||
show.value = false
|
||||
}, 500)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(button) {
|
||||
margin: 0 10px 10px 0;
|
||||
}
|
||||
:deep(.block) {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin: -50px 0 0 -50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: #0083ff;
|
||||
}
|
||||
|
||||
:deep(.custom-enter-active),
|
||||
:deep(.custom-leave-active) {
|
||||
transition-property: background, transform;
|
||||
}
|
||||
:deep(.custom-enter) {
|
||||
transform: translate3d(-100px, -100px, 0) rotate(-180deg);
|
||||
background: #ff0000;
|
||||
}
|
||||
:deep(.custom-leave-to) {
|
||||
transform: translate3d(100px, 100px, 0) rotate(180deg);
|
||||
background: #ff0000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,54 @@
|
||||
|
||||
<template>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<!--
|
||||
* @Author: weisheng
|
||||
* @Date: 2023-06-13 11:47:12
|
||||
* @LastEditTime: 2023-06-13 22:39:45
|
||||
* @LastEditors: weisheng
|
||||
* @Description:
|
||||
* @FilePath: \wot-design-uni\src\pages\upload\Index.vue
|
||||
* 记得注释
|
||||
-->
|
||||
<template>
|
||||
<wd-message-box id="wd-message-box"></wd-message-box>
|
||||
<wd-toast id="wd-toast"></wd-toast>
|
||||
<demo-block title="基本用法">
|
||||
<wd-upload file-list="{{fileList1}}" action="{{action}}" bind:change="handleChange1"></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="多选上传">
|
||||
<wd-upload file-list="{{fileList2}}" multiple action="{{action}}" bind:change="handleChange2"></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="最大上传数限制">
|
||||
<wd-upload file-list="{{fileList3}}" limit="{{3}}" action="{{action}}" bind:change="handleChange3"></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="拦截预览图片操作">
|
||||
<wd-upload file-list="{{fileList4}}" action="{{action}}" bind:change="handleChange4" before-preview="{{beforePreview}}"></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="上传前置处理">
|
||||
<wd-upload file-list="{{fileList5}}" action="{{action}}" bind:change="handleChange5" before-upload="{{beforeUpload}}"></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="移除图片前置处理">
|
||||
<wd-upload file-list="{{fileList6}}" action="{{action}}" bind:change="handleChange6" before-remove="{{beforeRemove}}"></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="上传状态钩子">
|
||||
<wd-upload
|
||||
file-list="{{fileList7}}"
|
||||
action="{{action}}"
|
||||
bind:change="handleChange7"
|
||||
bindsuccess="handleSuccess"
|
||||
bindfail="handleFail"
|
||||
bindprogress="handleProgess"
|
||||
></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="禁用">
|
||||
<wd-upload file-list="{{fileList8}}" disabled action="{{action}}" bind:change="handleChange8"></wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="自定义唤起上传样式">
|
||||
<wd-upload file-list="{{fileList9}}" action="{{action}}" bind:change="handleChange9" use-default-slot>
|
||||
<wd-button>自定义唤起样式</wd-button>
|
||||
</wd-upload>
|
||||
</demo-block>
|
||||
<demo-block title="选择文件前置处理">
|
||||
<wd-upload file-list="{{fileList10}}" action="{{action}}" bind:change="handleChange10" before-choose="{{beforeChoose}}"></wd-upload>
|
||||
</demo-block>
|
||||
</template>
|
||||
<script lang="ts" setup></script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -4,12 +4,16 @@ import Toast from '../../wot-design/toast/toast'
|
||||
Page({
|
||||
data: {
|
||||
action: 'https://ftf.jd.com/api/uploadImg',
|
||||
fileList1: [{
|
||||
url: 'https://img12.360buyimg.com//n0/jfs/t1/29118/6/4823/55969/5c35c16bE7c262192/c9fdecec4b419355.jpg'
|
||||
}],
|
||||
fileList2: [{
|
||||
url: 'https://img12.360buyimg.com//n0/jfs/t1/29118/6/4823/55969/5c35c16bE7c262192/c9fdecec4b419355.jpg'
|
||||
}],
|
||||
fileList1: [
|
||||
{
|
||||
url: 'https://img12.360buyimg.com//n0/jfs/t1/29118/6/4823/55969/5c35c16bE7c262192/c9fdecec4b419355.jpg'
|
||||
}
|
||||
],
|
||||
fileList2: [
|
||||
{
|
||||
url: 'https://img12.360buyimg.com//n0/jfs/t1/29118/6/4823/55969/5c35c16bE7c262192/c9fdecec4b419355.jpg'
|
||||
}
|
||||
],
|
||||
fileList3: [],
|
||||
fileList4: [],
|
||||
fileList5: [],
|
||||
@ -18,110 +22,118 @@ Page({
|
||||
fileList8: [],
|
||||
fileList9: [],
|
||||
fileList10: [],
|
||||
beforeChoose (file, resolve) {
|
||||
beforeChoose(file, resolve) {
|
||||
MessageBox.confirm({
|
||||
msg: '是否选择',
|
||||
title: '提示'
|
||||
}).then(() => {
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
Toast('取消选择操作')
|
||||
})
|
||||
.then(() => {
|
||||
resolve(true)
|
||||
})
|
||||
.catch(() => {
|
||||
Toast('取消选择操作')
|
||||
})
|
||||
},
|
||||
beforePreview ({ file, resolve }) {
|
||||
beforePreview({ file, resolve }) {
|
||||
MessageBox.confirm({
|
||||
msg: '是否预览图片',
|
||||
title: '提示'
|
||||
}).then(() => {
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
Toast('取消预览操作')
|
||||
})
|
||||
.then(() => {
|
||||
resolve(true)
|
||||
})
|
||||
.catch(() => {
|
||||
Toast('取消预览操作')
|
||||
})
|
||||
},
|
||||
beforeUpload ({ file, resolve }) {
|
||||
beforeUpload({ file, resolve }) {
|
||||
MessageBox.confirm({
|
||||
msg: '是否上传',
|
||||
title: '提示'
|
||||
}).then(() => {
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
Toast('取消上传操作')
|
||||
})
|
||||
.then(() => {
|
||||
resolve(true)
|
||||
})
|
||||
.catch(() => {
|
||||
Toast('取消上传操作')
|
||||
})
|
||||
},
|
||||
beforeRemove ({ file, fileList, resolve }) {
|
||||
beforeRemove({ file, fileList, resolve }) {
|
||||
MessageBox.confirm({
|
||||
msg: '是否删除',
|
||||
title: '提示'
|
||||
}).then(() => {
|
||||
Toast.success('删除成功')
|
||||
resolve(true)
|
||||
}).catch(() => {
|
||||
Toast('取消删除操作')
|
||||
})
|
||||
.then(() => {
|
||||
Toast.success('删除成功')
|
||||
resolve(true)
|
||||
})
|
||||
.catch(() => {
|
||||
Toast('取消删除操作')
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSuccess (event) {
|
||||
handleSuccess(event) {
|
||||
console.log('成功', event.detail)
|
||||
},
|
||||
handleFail (event) {
|
||||
handleFail(event) {
|
||||
console.log('失败')
|
||||
},
|
||||
handleProgess (event) {
|
||||
handleProgess(event) {
|
||||
console.log('加载中')
|
||||
},
|
||||
handleChange1 (event) {
|
||||
handleChange1(event) {
|
||||
this.setData({
|
||||
fileList1: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange2 (event) {
|
||||
handleChange2(event) {
|
||||
this.setData({
|
||||
fileList2: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange3 (event) {
|
||||
handleChange3(event) {
|
||||
this.setData({
|
||||
fileList3: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange4 (event) {
|
||||
handleChange4(event) {
|
||||
this.setData({
|
||||
fileList4: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange5 (event) {
|
||||
handleChange5(event) {
|
||||
this.setData({
|
||||
fileList5: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange6 (event) {
|
||||
handleChange6(event) {
|
||||
this.setData({
|
||||
fileList6: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange7 (event) {
|
||||
handleChange7(event) {
|
||||
this.setData({
|
||||
fileList7: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange8 (event) {
|
||||
handleChange8(event) {
|
||||
this.setData({
|
||||
fileList8: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange9 (event) {
|
||||
handleChange9(event) {
|
||||
this.setData({
|
||||
fileList9: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange10 (event) {
|
||||
handleChange10(event) {
|
||||
this.setData({
|
||||
fileList10: event.detail.fileList
|
||||
})
|
||||
},
|
||||
handleChange11 (event) {
|
||||
handleChange11(event) {
|
||||
this.setData({
|
||||
fileList11: event.detail.fileList
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* @Author: weisheng
|
||||
* @Date: 2021-10-13 11:15:00
|
||||
* @LastEditTime: 2023-04-06 20:10:46
|
||||
* @LastEditors: weisheng
|
||||
* @Description:
|
||||
* @FilePath: \fant-mini-plus\src\router\index.ts
|
||||
* 记得注释
|
||||
*/
|
||||
import { createRouter } from 'uni-mini-router'
|
||||
|
||||
const router = createRouter({
|
||||
routes: [...ROUTES]
|
||||
})
|
||||
router.beforeEach((to, from, next) => {
|
||||
console.log(to, 'to')
|
||||
console.log(from, 'from')
|
||||
console.log('进入路由之前调用')
|
||||
next()
|
||||
})
|
||||
router.afterEach((to, from) => {
|
||||
console.log(to, 'to')
|
||||
console.log(from, 'from')
|
||||
console.log('进入路由之后调用')
|
||||
})
|
||||
|
||||
export default router
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-prototype-builtins */
|
||||
import debounce from './lodash/debounce'
|
||||
|
||||
/**
|
||||
@ -261,3 +262,70 @@ export function objToStyle(styles) {
|
||||
}
|
||||
return styles
|
||||
}
|
||||
|
||||
export const requestAnimationFrame = (cb = () => void 0) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.selectViewport()
|
||||
.boundingClientRect()
|
||||
.exec(() => {
|
||||
resolve(true)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝
|
||||
* @param obj 深度拷贝的对象
|
||||
* @param cache
|
||||
* @returns
|
||||
*/
|
||||
export function deepClone(obj, cache: any = []) {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(obj) === '[object Date]') return new Date(obj)
|
||||
if (Object.prototype.toString.call(obj) === '[object RegExp]') return new RegExp(obj)
|
||||
if (Object.prototype.toString.call(obj) === '[object Error]') return new Error(obj)
|
||||
|
||||
const item: any = cache.filter((item: any) => item.original === obj)[0]
|
||||
if (item) return item.copy
|
||||
|
||||
const copy = Array.isArray(obj) ? [] : {}
|
||||
cache.push({
|
||||
original: obj,
|
||||
copy
|
||||
})
|
||||
|
||||
Object.keys(obj).forEach((key) => {
|
||||
copy[key] = deepClone(obj[key], cache)
|
||||
})
|
||||
|
||||
return copy
|
||||
}
|
||||
|
||||
// JS对象深度合并
|
||||
export function deepMerge(target = {}, source = {}) {
|
||||
target = deepClone(target)
|
||||
if (typeof target !== 'object' || typeof source !== 'object') return false
|
||||
for (const prop in source) {
|
||||
if (!source.hasOwnProperty(prop)) continue
|
||||
if (prop in target) {
|
||||
if (typeof target[prop] !== 'object') {
|
||||
target[prop] = source[prop]
|
||||
} else if (typeof source[prop] !== 'object') {
|
||||
target[prop] = source[prop]
|
||||
} else if (target[prop].concat && source[prop].concat) {
|
||||
target[prop] = target[prop].concat(source[prop])
|
||||
} else {
|
||||
target[prop] = deepMerge(target[prop], source[prop])
|
||||
}
|
||||
} else {
|
||||
target[prop] = source[prop]
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { onBeforeMount, ref, watch } from 'vue'
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue'
|
||||
import { isObj } from '../common/util'
|
||||
|
||||
const getClassNames = (name) => {
|
||||
@ -51,6 +51,7 @@ interface Props {
|
||||
name: TransitionName
|
||||
customStyle: string
|
||||
lazyRender: boolean
|
||||
customClass?: string
|
||||
// 定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
|
||||
enterClass?: string
|
||||
// 定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
|
||||
@ -87,6 +88,16 @@ const classes = ref<string>('')
|
||||
|
||||
const emit = defineEmits(['click', 'before-enter', 'enter', 'before-leave', 'leave', 'after-leave', 'after-enter'])
|
||||
|
||||
const style = computed(() => {
|
||||
return `-webkit-transition-duration:${currentDuration.value}ms;transition-duration:${currentDuration.value}ms;${
|
||||
display.value ? '' : 'display: none;'
|
||||
}${props.customStyle}`
|
||||
})
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-transition ${props.customClass} ${classes.value}`
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.show) {
|
||||
enter()
|
||||
@ -97,7 +108,8 @@ watch(
|
||||
() => props.show,
|
||||
(newVal) => {
|
||||
observerShow(newVal)
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
function observerShow(value: boolean) {
|
||||
@ -158,3 +170,5 @@ function onTransitionEnd() {
|
||||
display.value = false
|
||||
}
|
||||
}
|
||||
|
||||
export function useTransition() {}
|
||||
|
||||
45
src/uni_modules/wot-design-uni/components/mixins/useCell.ts
Normal file
45
src/uni_modules/wot-design-uni/components/mixins/useCell.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { getCurrentInstance, inject, nextTick, onBeforeMount, onMounted, ref, watch } from 'vue'
|
||||
|
||||
export function useCell() {
|
||||
const border = ref<boolean>(false) // 是否展示边框
|
||||
const cellGroup: any = inject('cell-group') || {}
|
||||
const cellList: any = inject('cell-list') || ref<any[]>([])
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
watch(
|
||||
() => cellGroup.border,
|
||||
(newVal) => {
|
||||
setIndexAndStatus(Boolean(newVal), proxy.$.uid)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
cellList.value = [...cellList.value.concat([{ uid: proxy.$.uid }])]
|
||||
setIndexAndStatus(cellGroup.border, proxy.$.uid)
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 从cellGroup获取此组件的索引
|
||||
* @return {Number} 此组件的索引
|
||||
*/
|
||||
function getIndex(uuid: string) {
|
||||
if (!cellList || !cellList.value) return
|
||||
return cellList.value.findIndex((cell) => {
|
||||
return cell.uid === uuid
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 为所有索引非0的组件设置刘海线,此方法由cellGroup调用
|
||||
*/
|
||||
function setIndexAndStatus(isBorder: boolean, uuid: string) {
|
||||
const index = getIndex(uuid)
|
||||
border.value = isBorder && index
|
||||
}
|
||||
|
||||
return { border, cellGroup, cellList, setIndexAndStatus, getIndex }
|
||||
}
|
||||
@ -71,10 +71,7 @@ type ButtonSize = 'small' | 'medium' | 'large'
|
||||
interface Props {
|
||||
plain: boolean
|
||||
disabled: boolean
|
||||
round: {
|
||||
type: boolean
|
||||
value: true
|
||||
}
|
||||
round: boolean
|
||||
suck: boolean
|
||||
block: boolean
|
||||
type: ButtonType
|
||||
@ -97,7 +94,13 @@ interface Props {
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'primary',
|
||||
size: 'medium'
|
||||
size: 'medium',
|
||||
round: true,
|
||||
plain: false,
|
||||
loading: false,
|
||||
suck: false,
|
||||
block: false,
|
||||
disabled: false
|
||||
})
|
||||
|
||||
const hoverStartTime = ref<number>(20)
|
||||
|
||||
@ -1,8 +1,23 @@
|
||||
<template>
|
||||
<view class="wd-divider custom-class">
|
||||
<view class="wd-divider__line" :style="color ? 'background: ' + color : ''"></view>
|
||||
<view class="wd-divider__content" :style="color ? 'color: ' + color : ''">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="wd-divider__line" :style="color ? 'background: ' + color : ''"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
interface Props {
|
||||
color: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@import './index.scss';
|
||||
</style>
|
||||
|
||||
@ -1,24 +1,32 @@
|
||||
<template>
|
||||
<view
|
||||
:class="['wd-icon', 'custom-class', isImageUrl ? 'wd-icon--image' : 'wd-icon-' + name]"
|
||||
:style="`color: ${color}; font-size: ${size}; ${customStyle}`"
|
||||
>
|
||||
<view @click="handleClick" :class="rootClass" :style="rootStyle">
|
||||
<image v-if="isImageUrl" class="wd-icon__image" :src="name"></image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
// 将自定义节点设置成虚拟的,更加接近Vue组件的表现,可以去掉微信小程序自定义组件多出的最外层标签
|
||||
options: {
|
||||
virtualHost: true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { objToStyle } from '../common/util'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
color?: string
|
||||
size?: string
|
||||
customStyle?: string
|
||||
customClass?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: 'inherit',
|
||||
size: 'inherit'
|
||||
customStyle: '',
|
||||
customClass: ''
|
||||
})
|
||||
|
||||
const isImageUrl = ref<boolean>(false)
|
||||
@ -29,6 +37,27 @@ watch(
|
||||
isImageUrl.value = val.indexOf('/') > -1
|
||||
}
|
||||
)
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-icon ${props.customClass} ${isImageUrl.value ? 'wd-icon--image' : 'wd-icon-' + props.name}`
|
||||
})
|
||||
|
||||
const rootStyle = computed(() => {
|
||||
const style: Record<string, any> = {}
|
||||
if (props.color) {
|
||||
style['color'] = props.color
|
||||
}
|
||||
if (props.size) {
|
||||
style['font-size'] = props.size
|
||||
}
|
||||
return `${objToStyle(style)}; ${props.customStyle}`
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
function handleClick(e) {
|
||||
emit('click', e)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
@import "../common/abstracts/variable";
|
||||
@import "../common/abstracts/mixin";
|
||||
|
||||
@include b(input) {
|
||||
@include e(prefix) {
|
||||
.wd-input__icon,
|
||||
.wd-input__clear {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@include when(cell) {
|
||||
|
||||
.wd-input__icon,
|
||||
.wd-input__clear {
|
||||
height: $-input-cell-height;
|
||||
line-height: $-input-cell-height;
|
||||
}
|
||||
}
|
||||
@include when(large) {
|
||||
.wd-input__icon,
|
||||
.wd-input__clear {
|
||||
height: $-input-cell-height-large;
|
||||
font-size: $-input-icon-size-large;
|
||||
line-height: $-input-cell-height-large;
|
||||
}
|
||||
.wd-input__textarea-icon {
|
||||
font-size: $-input-icon-size-large;
|
||||
}
|
||||
}
|
||||
@include e(icon) {
|
||||
margin-left: 8px;
|
||||
font-size: $-input-icon-size;
|
||||
color: $-input-icon-color;
|
||||
vertical-align: middle;
|
||||
background: $-input-bg;
|
||||
}
|
||||
@include e(clear) {
|
||||
margin-left: 8px;
|
||||
font-size: $-input-icon-size;
|
||||
color: $-input-clear-color;
|
||||
vertical-align: middle;
|
||||
background: $-input-bg;
|
||||
}
|
||||
|
||||
@include e(textarea-icon) {
|
||||
margin-left: 8px;
|
||||
font-size: $-input-icon-size;
|
||||
line-height: 20px;
|
||||
color: $-input-icon-color;
|
||||
background: #fff;
|
||||
}
|
||||
@include e(textarea-icon) {
|
||||
margin-left: 8px;
|
||||
font-size: $-input-icon-size;
|
||||
line-height: 20px;
|
||||
color: $-input-clear-color;
|
||||
background: $-input-bg;
|
||||
}
|
||||
.wd-input__textarea-count,
|
||||
.wd-input__count,
|
||||
.wd-input__count-current {
|
||||
display: inline-flex;
|
||||
}
|
||||
@include e(textarea-map) {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@ -188,10 +188,10 @@
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(inner) {
|
||||
flex: 1;
|
||||
height: $-input-inner-height;
|
||||
padding: $-input-inner-padding;
|
||||
font-size: $-input-fs;
|
||||
color: $-input-color;
|
||||
outline: none;
|
||||
@ -206,6 +206,14 @@
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(readonly){
|
||||
padding: $-input-inner-padding;
|
||||
}
|
||||
@include e(textarea-inner){
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include e(icon) {
|
||||
margin-left: 8px;
|
||||
font-size: $-input-icon-size;
|
||||
@ -255,7 +263,7 @@
|
||||
}
|
||||
@include e(textarea-inner) {
|
||||
flex: 1;
|
||||
width: 1px;
|
||||
// width: 1px;
|
||||
padding: 0;
|
||||
font-size: $-input-fs;
|
||||
line-height: 1.43;
|
||||
@ -311,7 +319,7 @@
|
||||
.wd-input__textarea-count,
|
||||
.wd-input__count,
|
||||
.wd-input__count-current {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
}
|
||||
@include e(textarea-map) {
|
||||
position: absolute;
|
||||
|
||||
@ -1,8 +1,377 @@
|
||||
<template>
|
||||
<view :class="rootClass" :style="customStyle">
|
||||
<view v-if="label || useLabelSlot" :class="labelClass" :style="labelStyle">
|
||||
<view v-if="prefixIcon || usePrefixSlot" class="wd-input__prefix">
|
||||
<wd-icon v-if="prefixIcon && !usePrefixSlot" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" />
|
||||
<slot v-else name="prefix"></slot>
|
||||
</view>
|
||||
<view style="display: inline-flex">
|
||||
<view class="wd-input__label-inner">
|
||||
<template v-if="label">{{ label }}</template>
|
||||
<slot v-else name="label"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 文本域 -->
|
||||
<view v-if="type === 'textarea'" :class="`wd-input__textarea custom-textarea-container-class ${showWordCount ? 'is-show-limit' : ''}`">
|
||||
<!-- readonly -->
|
||||
<view v-if="readonly" class="wd-input__textarea-inner">{{ inputValue }}</view>
|
||||
<template v-else>
|
||||
<textarea
|
||||
:class="`wd-input__textarea-inner ${showClear ? 'is-suffix' : ''} ${customTextareaClass}`"
|
||||
v-model="inputValue"
|
||||
:show-count="false"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"
|
||||
:focus="isFocus"
|
||||
:placeholder-style="placeholderStyle"
|
||||
:placeholder-class="inputPlaceholderClass"
|
||||
:auto-height="autoHeight"
|
||||
:cursor-spacing="cursorSpacing"
|
||||
:fixed="fixed"
|
||||
:cursor="cursor"
|
||||
:show-confirm-barb="showConfirmBar"
|
||||
:selection-start="selectionStart"
|
||||
:selection-end="selectionEnd"
|
||||
:adjust-position="adjustPosition"
|
||||
:hold-keyboard="holdKeyboard"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@confirm="handleConfirm"
|
||||
@linechange="handleLineChange"
|
||||
@keyboardheightchange="handleKeyboardheightchange"
|
||||
/>
|
||||
<view class="wd-input__textarea-suffix">
|
||||
<wd-icon v-if="showClear" custom-class="wd-input__textarea-icon" name="error-fill" @click="clear" />
|
||||
<view v-if="showWordCount" class="wd-input__textarea-count">
|
||||
<text
|
||||
:class="[
|
||||
inputValue && String(inputValue).length > 0 ? 'wd-input__textarea-count-current' : '',
|
||||
String(inputValue).length > parseInt(String(maxlength)) ? 'is-error' : ''
|
||||
]"
|
||||
>
|
||||
{{ String(inputValue).length }}
|
||||
</text>
|
||||
/{{ maxlength }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<!-- 输入域 -->
|
||||
<view v-else class="wd-input__block">
|
||||
<view v-if="(prefixIcon || usePrefixSlot) && !label" class="wd-input__prefix">
|
||||
<wd-icon v-if="prefixIcon" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" />
|
||||
<slot name="prefix"></slot>
|
||||
</view>
|
||||
<!-- readonly -->
|
||||
<view v-if="readonly" class="wd-input__inner wd-input__readonly">{{ inputValue }}</view>
|
||||
<template v-else>
|
||||
<input
|
||||
:class="[
|
||||
'wd-input__inner',
|
||||
prefixIcon ? 'wd-input__inner--prefix' : '',
|
||||
showWordCount ? 'wd-input__inner--count' : '',
|
||||
alignRight ? 'is-align-right' : '',
|
||||
customInputClass
|
||||
]"
|
||||
:type="type"
|
||||
:password="showPassword && !isPwdVisible"
|
||||
v-model="inputValue"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:minlength="minlength"
|
||||
:maxlength="maxlength"
|
||||
:focus="isFocus"
|
||||
:confirm-type="confirmType"
|
||||
:confirm-hold="confirmHold"
|
||||
:cursor="cursor"
|
||||
:cursor-spacing="cursorSpacing"
|
||||
:placeholder-style="placeholderStyle"
|
||||
:selection-start="selectionStart"
|
||||
:selection-end="selectionEnd"
|
||||
:adjust-position="adjustPosition"
|
||||
:hold-keyboard="holdKeyboard"
|
||||
:placeholder-class="inputPlaceholderClass"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@confirm="handleConfirm"
|
||||
@keyboardheightchange="handleKeyboardheightchange"
|
||||
/>
|
||||
<view v-if="showClear || showPassword || suffixIcon || showWordCount || useSuffixSlot" class="wd-input__suffix">
|
||||
<wd-icon v-if="showClear" custom-class="wd-input__clear" name="error-fill" @click="clear" />
|
||||
<wd-icon v-if="showPassword" custom-class="wd-input__icon" :name="isPwdVisible ? 'view' : 'eye-close'" @click="togglePwdVisible" />
|
||||
<view v-if="showWordCount" class="wd-input__count">
|
||||
<text
|
||||
:class="[
|
||||
inputValue && String(inputValue).length > 0 ? 'wd-input__count-current' : '',
|
||||
String(inputValue).length > maxlength ? 'is-error' : ''
|
||||
]"
|
||||
>
|
||||
{{ String(inputValue).length }}
|
||||
</text>
|
||||
/{{ maxlength }}
|
||||
</view>
|
||||
<wd-icon v-if="suffixIcon" custom-class="wd-input__icon" :name="suffixIcon" @click="onClickSuffixIcon" />
|
||||
<slot name="suffix"></slot>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
// 将自定义节点设置成虚拟的,更加接近Vue组件的表现,可以去掉微信小程序自定义组件多出的最外层标签
|
||||
options: {
|
||||
virtualHost: true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue'
|
||||
import { objToStyle, requestAnimationFrame } from '../common/util'
|
||||
import { useCell } from '../mixins/useCell'
|
||||
|
||||
interface Props {
|
||||
customTextareaContainerClass: string
|
||||
customTextareaClass: string
|
||||
customInputClass: string
|
||||
customLabelClass: string
|
||||
customClass: string
|
||||
customStyle: string
|
||||
// 原生属性
|
||||
placeholder: string
|
||||
placeholderStyle: string
|
||||
placeholderClass: string
|
||||
autoHeight: boolean
|
||||
fixed: boolean
|
||||
cursorSpacing: number
|
||||
cursor: number
|
||||
showConfirmBar: boolean
|
||||
selectionStart: number
|
||||
selectionEnd: number
|
||||
adjustPosition: boolean
|
||||
holdKeyboard: boolean
|
||||
confirmType: string
|
||||
confirmHold: boolean
|
||||
focus: boolean
|
||||
type: string
|
||||
maxlength: number
|
||||
disabled: boolean
|
||||
alignRight: boolean
|
||||
// 原生属性结束
|
||||
modelValue: string | number
|
||||
minlength: number
|
||||
showPassword: boolean
|
||||
clearable: boolean
|
||||
showClear: boolean
|
||||
readonly: boolean
|
||||
useSuffixSlot: boolean
|
||||
usePrefixSlot: boolean
|
||||
prefixIcon: string
|
||||
suffixIcon: string
|
||||
showWordLimit: boolean
|
||||
showWordCount: boolean
|
||||
suffix: string
|
||||
suffixCount: number
|
||||
label: string
|
||||
labelWidth: string
|
||||
useLabelSlot: boolean
|
||||
size: string
|
||||
error: boolean
|
||||
center: boolean
|
||||
noBorder: boolean
|
||||
required: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
customTextareaContainerClass: '',
|
||||
customTextareaClass: '',
|
||||
customInputClass: '',
|
||||
customLabelClass: '',
|
||||
customClass: '',
|
||||
customStyle: '',
|
||||
type: 'text',
|
||||
maxlength: -1,
|
||||
modelValue: '',
|
||||
placeholder: '请输入...',
|
||||
clearable: false,
|
||||
showPassword: false,
|
||||
disabled: false,
|
||||
readonly: false,
|
||||
showWordLimit: false,
|
||||
confirmType: 'done',
|
||||
placeholderClass: 'textarea-placeholder',
|
||||
focus: false,
|
||||
cursorSpacing: 0,
|
||||
fixed: false,
|
||||
cursor: -1,
|
||||
showConfirmBar: true,
|
||||
selectionStart: -1,
|
||||
selectionEndZ: -1,
|
||||
adjustPosition: true,
|
||||
error: false,
|
||||
center: false,
|
||||
labelWidth: '33%',
|
||||
useLabelSlot: false,
|
||||
required: false,
|
||||
noBorder: false
|
||||
})
|
||||
|
||||
const showClear = ref<boolean>(false)
|
||||
const showWordCount = ref<boolean>(false)
|
||||
const isPwdVisible = ref<boolean>(false)
|
||||
const clearing = ref<boolean>(false)
|
||||
const isFocus = ref<boolean>(false) // 是否聚焦
|
||||
const inputValue = ref<string | number>('') // 输入框的值
|
||||
const cell = useCell()
|
||||
|
||||
watch(
|
||||
() => props.focus,
|
||||
(newValue) => {
|
||||
isFocus.value = newValue
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
const { disabled, readonly, clearable } = props
|
||||
// 类型校验,支持所有值(除null、undefined。undefined建议统一写成void (0)防止全局undefined被覆盖)
|
||||
if (newValue === null || newValue === undefined) {
|
||||
throw Error('value can not be null or undefined')
|
||||
}
|
||||
inputValue.value = newValue
|
||||
showClear.value = Boolean(clearable && !disabled && !readonly && newValue)
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-input ${props.type === 'textarea' ? 'is-textarea' : ''} ${props.label || props.useLabelSlot ? 'is-cell' : ''} ${
|
||||
props.center ? 'is-center' : ''
|
||||
} ${cell.border.value ? 'is-border' : ''} ${props.size ? 'is-' + props.size : ''} ${props.error ? 'is-error' : ''} ${
|
||||
props.disabled ? 'is-disabled' : ''
|
||||
} ${props.autoHeight ? 'is-auto-height' : ''} ${inputValue.value && String(inputValue.value).length > 0 ? 'is-not-empty' : ''} ${
|
||||
props.noBorder ? 'is-no-border' : ''
|
||||
} ${props.customClass}`
|
||||
})
|
||||
|
||||
const labelClass = computed(() => {
|
||||
return `wd-input__label ${props.customLabelClass} ${props.required ? 'is-required' : ''}`
|
||||
})
|
||||
|
||||
const inputPlaceholderClass = computed(() => {
|
||||
return `wd-input__placeholder ${props.placeholderClass}`
|
||||
})
|
||||
|
||||
const labelStyle = computed(() => {
|
||||
return props.labelWidth
|
||||
? objToStyle({
|
||||
'min-width': props.labelWidth,
|
||||
'max-width': props.labelWidth
|
||||
})
|
||||
: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'clear',
|
||||
'change',
|
||||
'blur',
|
||||
'focus',
|
||||
'input',
|
||||
'keyboardheightchange',
|
||||
'confirm',
|
||||
'linechange',
|
||||
'clicksuffixicon',
|
||||
'clickprefixicon'
|
||||
])
|
||||
|
||||
onBeforeMount(() => {
|
||||
initState()
|
||||
})
|
||||
|
||||
// 状态初始化
|
||||
function initState() {
|
||||
const { disabled, readonly, clearable, maxlength, showWordLimit } = props
|
||||
let newVal = ''
|
||||
if (showWordLimit && maxlength && inputValue.value.toString().length > maxlength) {
|
||||
newVal = inputValue.value.toString().substring(0, maxlength)
|
||||
}
|
||||
showClear.value = Boolean(!disabled && !readonly && clearable && inputValue.value)
|
||||
showWordCount.value = Boolean(!disabled && !readonly && maxlength && showWordLimit)
|
||||
inputValue.value = newVal || inputValue.value
|
||||
emit('update:modelValue', inputValue.value)
|
||||
}
|
||||
function togglePwdVisible() {
|
||||
// password属性设置false不生效,置空生效
|
||||
isPwdVisible.value = !isPwdVisible.value
|
||||
}
|
||||
function clear() {
|
||||
inputValue.value = ''
|
||||
requestAnimationFrame()
|
||||
.then(() => requestAnimationFrame())
|
||||
.then(() => requestAnimationFrame())
|
||||
.then(() => {
|
||||
isFocus.value = true
|
||||
emit('clear')
|
||||
emit('change', {
|
||||
value: ''
|
||||
})
|
||||
emit('update:modelValue', inputValue.value)
|
||||
})
|
||||
}
|
||||
// 失去焦点时会先后触发change、blur,未输入内容但失焦不触发 change 只触发 blur
|
||||
function handleBlur({ detail }) {
|
||||
isFocus.value = false
|
||||
emit('change', {
|
||||
value: inputValue.value
|
||||
})
|
||||
emit('update:modelValue', inputValue.value)
|
||||
emit('blur', {
|
||||
value: inputValue.value,
|
||||
// textarea 有 cursor
|
||||
cursor: detail.cursor ? detail.cursor : null
|
||||
})
|
||||
}
|
||||
function handleFocus({ detail }) {
|
||||
if (clearing.value) {
|
||||
clearing.value = false
|
||||
return
|
||||
}
|
||||
isFocus.value = true
|
||||
emit('focus', detail)
|
||||
}
|
||||
// input事件需要传入
|
||||
function handleInput() {
|
||||
emit('update:modelValue', inputValue.value)
|
||||
emit('input', inputValue.value)
|
||||
}
|
||||
function handleKeyboardheightchange(event) {
|
||||
emit('keyboardheightchange', event.detail)
|
||||
}
|
||||
function handleConfirm({ detail }) {
|
||||
emit('confirm', detail)
|
||||
}
|
||||
function handleLineChange(event) {
|
||||
emit('linechange', event.detail)
|
||||
}
|
||||
function onClickSuffixIcon() {
|
||||
emit('clicksuffixicon')
|
||||
}
|
||||
function onClickPrefixIcon() {
|
||||
emit('clickprefixicon')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@import './index.scss';
|
||||
</style>
|
||||
|
||||
@ -1,121 +0,0 @@
|
||||
import VueComponent from '../common/component'
|
||||
|
||||
VueComponent({
|
||||
props: {
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
showConfirmButton: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
title: String,
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: 'resetErr'
|
||||
},
|
||||
closeOnClickModal: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
confirmButtonText: String,
|
||||
cancelButtonText: String,
|
||||
zIndex: {
|
||||
type: Number,
|
||||
value: 99
|
||||
},
|
||||
lazyRender: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
}
|
||||
},
|
||||
data: {
|
||||
msg: '',
|
||||
type: 'alert',
|
||||
inputType: 'text',
|
||||
inputValue: '',
|
||||
inputPlaceholder: '',
|
||||
inputPattern: '',
|
||||
inputValidate: '',
|
||||
showErr: false,
|
||||
inputError: '',
|
||||
onConfirm: '',
|
||||
onCancel: ''
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 关闭消息框的统一主调 handle
|
||||
* @param {'cancel' | 'confirm'} action
|
||||
*/
|
||||
toggleModal ({ currentTarget: el }) {
|
||||
const { action } = el.dataset
|
||||
// closeOnClickModal为false,此时点击蒙层没任何效果
|
||||
if (action === 'modal' && !this.data.closeOnClickModal) {
|
||||
return
|
||||
}
|
||||
// prompt类型的弹窗 文案没有通过校验,点击了确定按钮没有任何效果
|
||||
if (this.data.type === 'prompt' && action === 'confirm' && !this.validate()) {
|
||||
return
|
||||
}
|
||||
this.setData({ show: false })
|
||||
const { onConfirm, onCancel } = this.data
|
||||
|
||||
switch (action) {
|
||||
case 'confirm':
|
||||
onConfirm(action)
|
||||
break
|
||||
case 'cancel':
|
||||
onCancel(action)
|
||||
break
|
||||
default:
|
||||
onCancel('modal')
|
||||
break
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @description 如果存在校验规则行为,则进行判断校验是否通过规则。默认不存在校验直接铜鼓。
|
||||
* @return {Boolean} 是否通过校验
|
||||
*/
|
||||
validate () {
|
||||
const { inputPattern, inputValidate, inputValue } = this.data
|
||||
if (inputPattern && !inputPattern.test(inputValue)) {
|
||||
this.setData({ showErr: true })
|
||||
return false
|
||||
}
|
||||
if (typeof inputValidate === 'function') {
|
||||
const validateResult = inputValidate(inputValue)
|
||||
if (!validateResult) {
|
||||
this.setData({ showErr: true })
|
||||
return false
|
||||
}
|
||||
}
|
||||
this.setData({ showErr: false })
|
||||
return true
|
||||
},
|
||||
/**
|
||||
* @description show关闭时,销毁错误提示
|
||||
* @param val
|
||||
*/
|
||||
resetErr (val) {
|
||||
if (val === false) {
|
||||
this.setData({ showErr: false })
|
||||
}
|
||||
},
|
||||
inputValChange ({ detail }) {
|
||||
const { value } = detail
|
||||
if (value === '') {
|
||||
this.setData({ showErr: false })
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
inputValue: value
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -41,7 +41,7 @@
|
||||
&::-webkit-scrollbar-thumb {
|
||||
width: $-message-box-content-scrollbar-width;
|
||||
background: $-message-box-content-scrollbar-color;
|
||||
border-radius: $-message-box-content-scrollbar-width / 2;
|
||||
border-radius: calc($-message-box-content-scrollbar-width / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* @Author: weisheng
|
||||
* @Date: 2022-12-14 17:33:21
|
||||
* @LastEditTime: 2023-06-18 22:46:43
|
||||
* @LastEditors: weisheng
|
||||
* @Description:
|
||||
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-message-box\index.ts
|
||||
* 记得注释
|
||||
*/
|
||||
import { InjectionKey, Ref, nextTick, provide, ref } from 'vue'
|
||||
import { ActionType, Message, MessageOptions, MessageResult, MessageType } from './types'
|
||||
import { deepMerge } from '../common/util'
|
||||
|
||||
/**
|
||||
* useMessage 用到的key
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const messageDefaultKey = Symbol('__MESSAGE__') as InjectionKey<Ref<boolean>>
|
||||
export const messageDefaultOptionKey = Symbol('__MESSAGE_OPTION__') as InjectionKey<Ref<MessageOptions>>
|
||||
|
||||
// 默认模板
|
||||
export const defaultOptions: MessageOptions = {
|
||||
title: '',
|
||||
showCancelButton: false,
|
||||
show: false,
|
||||
closeOnClickModal: true,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
msg: '',
|
||||
type: 'alert',
|
||||
inputType: 'text',
|
||||
inputValue: '',
|
||||
inputPlaceholder: '请输入',
|
||||
inputValidate: null,
|
||||
showErr: false,
|
||||
zIndex: 99,
|
||||
lazyRender: true,
|
||||
inputError: ''
|
||||
}
|
||||
|
||||
export function useMessage(selector: string = ''): Message {
|
||||
const messageOption = ref<MessageOptions>(defaultOptions) // Message选项
|
||||
const messageOptionKey = selector ? '__MESSAGE_OPTION__' + selector : messageDefaultOptionKey
|
||||
provide(messageOptionKey, messageOption)
|
||||
|
||||
const createMethod = (type: MessageType) => {
|
||||
// 优先级:options->toastOptions->defaultOptions
|
||||
return (options: MessageOptions | string) => {
|
||||
return show(deepMerge({ type: type }, typeof options === 'string' ? { title: options } : options) as MessageOptions)
|
||||
}
|
||||
}
|
||||
|
||||
const show = (option: MessageOptions | string) => {
|
||||
// 返回一个promise
|
||||
return new Promise<MessageResult>((resolve, reject) => {
|
||||
const options = deepMerge(defaultOptions, typeof option === 'string' ? { title: option } : option) as MessageOptions
|
||||
messageOption.value = deepMerge(options, {
|
||||
show: true,
|
||||
onConfirm: (res: MessageResult) => {
|
||||
resolve(res)
|
||||
},
|
||||
onCancel: (res: MessageResult) => {
|
||||
reject(res)
|
||||
}
|
||||
}) as MessageOptions
|
||||
})
|
||||
}
|
||||
|
||||
// 打开Alert 弹框
|
||||
const alert = createMethod('alert')
|
||||
// 打开Confirm 弹框
|
||||
const confirm = createMethod('confirm')
|
||||
// 打开Prompt 弹框
|
||||
const prompt = createMethod('prompt')
|
||||
|
||||
const close = () => {
|
||||
messageOption.value = { ...defaultOptions }
|
||||
}
|
||||
return {
|
||||
show,
|
||||
alert,
|
||||
confirm,
|
||||
prompt,
|
||||
close
|
||||
}
|
||||
}
|
||||
@ -40,17 +40,29 @@ const parseOptions = (msg) => {
|
||||
*/
|
||||
const MessageBox = (MessageBoxOptions) => {
|
||||
if (MessageBoxOptions.type === 'alert' || !MessageBoxOptions.type) {
|
||||
MessageBoxOptions = Object.assign({}, {
|
||||
closeOnClickModal: false
|
||||
}, MessageBoxOptions)
|
||||
MessageBoxOptions = Object.assign(
|
||||
{},
|
||||
{
|
||||
closeOnClickModal: false
|
||||
},
|
||||
MessageBoxOptions
|
||||
)
|
||||
} else if (MessageBoxOptions.type === 'confirm') {
|
||||
MessageBoxOptions = Object.assign({}, {
|
||||
showCancelButton: true
|
||||
}, MessageBoxOptions)
|
||||
MessageBoxOptions = Object.assign(
|
||||
{},
|
||||
{
|
||||
showCancelButton: true
|
||||
},
|
||||
MessageBoxOptions
|
||||
)
|
||||
} else if (MessageBoxOptions.type === 'prompt') {
|
||||
MessageBoxOptions = Object.assign({}, {
|
||||
showCancelButton: true
|
||||
}, MessageBoxOptions)
|
||||
MessageBoxOptions = Object.assign(
|
||||
{},
|
||||
{
|
||||
showCancelButton: true
|
||||
},
|
||||
MessageBoxOptions
|
||||
)
|
||||
}
|
||||
|
||||
// 覆盖模板中的选项
|
||||
@ -98,4 +110,4 @@ MessageBox.close = (MessageBoxOptions) => {
|
||||
}
|
||||
}
|
||||
|
||||
export default MessageBox
|
||||
export default MessageBox
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
export type MessageType = 'alert' | 'confirm' | 'prompt'
|
||||
|
||||
export type MessageOptions = {
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* 是否展示取消按钮
|
||||
*/
|
||||
showCancelButton?: boolean
|
||||
|
||||
show?: boolean
|
||||
/**
|
||||
* 是否支持点击蒙层进行关闭,点击蒙层回调传入的action为'modal'
|
||||
*/
|
||||
closeOnClickModal?: boolean
|
||||
/**
|
||||
* 确定按钮文案
|
||||
*/
|
||||
confirmButtonText?: string
|
||||
/**
|
||||
* 取消按钮文案
|
||||
*/
|
||||
cancelButtonText?: string
|
||||
/**
|
||||
* 消息文案
|
||||
*/
|
||||
msg?: string
|
||||
/**
|
||||
* 弹框类型
|
||||
*/
|
||||
type?: MessageType
|
||||
/**
|
||||
* 当type为prompt时,输入框类型
|
||||
*/
|
||||
inputType?: string
|
||||
/**
|
||||
* 当type为prompt时,输入框初始值
|
||||
*/
|
||||
inputValue?: string | number
|
||||
/**
|
||||
* 当type为prompt时,输入框placeholder
|
||||
*/
|
||||
inputPlaceholder?: string
|
||||
/**
|
||||
* 当type为prompt时,输入框正则校验,点击确定按钮时进行校验
|
||||
*/
|
||||
inputPattern?: RegExp
|
||||
/**
|
||||
* 当type为prompt时,输入框校验函数,点击确定按钮时进行校验
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
inputValidate?: Function | null
|
||||
/**
|
||||
* 当type为prompt时,输入框检验不通过时的错误提示文案
|
||||
*/
|
||||
inputError?: string
|
||||
showErr?: boolean
|
||||
/**
|
||||
* 弹窗层级
|
||||
*/
|
||||
zIndex?: number
|
||||
/**
|
||||
* 弹层内容懒渲染,触发展示时才渲染内容
|
||||
*/
|
||||
lazyRender?: boolean
|
||||
}
|
||||
|
||||
export type ActionType = 'confirm' | 'cancel' | 'modal'
|
||||
|
||||
export interface MessageResult {
|
||||
action: ActionType
|
||||
value?: string | number
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
// 打开Message
|
||||
show(toastOptions: MessageOptions | string): Promise<MessageResult>
|
||||
// 打开Alert 弹框
|
||||
alert(toastOptions: MessageOptions | string): Promise<MessageResult>
|
||||
// 打开Confirm 弹框
|
||||
confirm(toastOptions: MessageOptions | string): Promise<MessageResult>
|
||||
// 打开Prompt 弹框
|
||||
prompt(toastOptions: MessageOptions | string): Promise<MessageResult>
|
||||
// 关闭Message
|
||||
close(): void
|
||||
}
|
||||
@ -1,5 +1,278 @@
|
||||
<template></template>
|
||||
<template>
|
||||
<wd-popup
|
||||
transition="zoom-in"
|
||||
v-model="show"
|
||||
:close-on-click-modal="closeOnClickModal"
|
||||
:lazy-render="lazyRender"
|
||||
custom-class="wd-message-box"
|
||||
@clickmodal="toggleModal('modal')"
|
||||
:z-index="zIndex"
|
||||
:duration="200"
|
||||
>
|
||||
<view :class="rootClass">
|
||||
<!--内容部分-->
|
||||
<view :class="bodyClass">
|
||||
<!--公共title-->
|
||||
<view v-if="title" class="wd-message-box__title">
|
||||
{{ title }}
|
||||
</view>
|
||||
<!--其它类型-->
|
||||
<view class="wd-message-box__content">
|
||||
<!--prompt类型-->
|
||||
<block v-if="type === 'prompt'">
|
||||
<!--输入框-->
|
||||
<wd-input v-model="inputValue" :type="inputType" size="large" :placeholder="inputPlaceholder || '请输入'" @input="inputValChange" />
|
||||
<!--错误提示-->
|
||||
<view v-if="showErr" class="wd-message-box__input-error">
|
||||
{{ inputError || '输入的数据不合法' }}
|
||||
</view>
|
||||
</block>
|
||||
<!--使用插槽-->
|
||||
<slot v-if="useSlot"></slot>
|
||||
<!--使用文本-->
|
||||
<block v-else>{{ msg }}</block>
|
||||
</view>
|
||||
</view>
|
||||
<!--action按钮组合-->
|
||||
<view :class="`wd-message-box__actions ${showCancelButton ? 'wd-message-box__flex' : 'wd-message-box__block'}`">
|
||||
<wd-button type="info" block v-if="showCancelButton" custom-style="margin-right: 16px;" @click="toggleModal('cancel')">
|
||||
{{ cancelButtonText || '取消' }}
|
||||
</wd-button>
|
||||
<wd-button block @click="toggleModal('confirm')">
|
||||
{{ confirmButtonText || '确定' }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<script></script>
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import { MessageOptions, MessageType } from './types'
|
||||
import { defaultOptions, messageDefaultOptionKey } from '.'
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
interface Props {
|
||||
useSlot?: boolean
|
||||
selector?: string
|
||||
customClass?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
useSlot: false,
|
||||
customClass: '',
|
||||
selector: ''
|
||||
})
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-message-box__container ${props.customClass}`
|
||||
})
|
||||
|
||||
const bodyClass = computed(() => {
|
||||
return `wd-message-box__body ${!title.value ? 'is-no-title' : ''} ${type.value === 'prompt' ? 'is-prompt' : ''}`
|
||||
})
|
||||
|
||||
const messageOptionKey = props.selector ? '__MESSAGE_OPTION__' + props.selector : messageDefaultOptionKey
|
||||
const messageOption = inject(messageOptionKey) || ref<MessageOptions>(defaultOptions) // message选项
|
||||
|
||||
/**
|
||||
* 消息文案
|
||||
*/
|
||||
const msg = ref<string>('')
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
let onConfirm: Function | null = null
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
let onCancel: Function | null = null
|
||||
const show = ref<boolean>(false)
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
const title = ref<string>('')
|
||||
/**
|
||||
* 是否展示取消按钮
|
||||
*/
|
||||
const showCancelButton = ref<boolean>(false)
|
||||
/**
|
||||
* 是否支持点击蒙层进行关闭,点击蒙层回调传入的action为'modal'
|
||||
*/
|
||||
const closeOnClickModal = ref<boolean>(true)
|
||||
/**
|
||||
* 确定按钮文案
|
||||
*/
|
||||
const confirmButtonText = ref<string>('')
|
||||
/**
|
||||
* 取消按钮文案
|
||||
*/
|
||||
const cancelButtonText = ref<string>('')
|
||||
|
||||
/**
|
||||
* 弹框类型
|
||||
*/
|
||||
const type = ref<MessageType>('alert')
|
||||
|
||||
/**
|
||||
* 当type为prompt时,输入框类型
|
||||
*/
|
||||
const inputType = ref<string>('text')
|
||||
|
||||
/**
|
||||
* 当type为prompt时,输入框初始值
|
||||
*/
|
||||
const inputValue = ref<string | number>('')
|
||||
|
||||
/**
|
||||
* 当type为prompt时,输入框placeholder
|
||||
*/
|
||||
const inputPlaceholder = ref<string>('')
|
||||
|
||||
/**
|
||||
* 当type为prompt时,输入框正则校验,点击确定按钮时进行校验
|
||||
*/
|
||||
const inputPattern = ref<RegExp>()
|
||||
|
||||
/**
|
||||
* 当type为prompt时,输入框校验函数,点击确定按钮时进行校验
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
let inputValidate: Function | null = null
|
||||
|
||||
/**
|
||||
* 当type为prompt时,输入框检验不通过时的错误提示文案
|
||||
*/
|
||||
const inputError = ref<string>('')
|
||||
const showErr = ref<boolean>(false)
|
||||
/**
|
||||
* 弹窗层级
|
||||
*/
|
||||
const zIndex = ref<number>(99)
|
||||
/**
|
||||
* 弹层内容懒渲染,触发展示时才渲染内容
|
||||
*/
|
||||
const lazyRender = ref<boolean>(true)
|
||||
|
||||
// 监听options变化展示
|
||||
watch(
|
||||
() => messageOption.value,
|
||||
(newVal: MessageOptions) => {
|
||||
reset(newVal)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => show.value,
|
||||
(newValue) => {
|
||||
resetErr(newValue)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* @description 关闭消息框的统一主调 handle
|
||||
* @param {'cancel' | 'confirm'} action
|
||||
*/
|
||||
function toggleModal(action: 'confirm' | 'cancel' | 'modal') {
|
||||
// closeOnClickModal为false,此时点击蒙层没任何效果
|
||||
if (action === 'modal' && !closeOnClickModal.value) {
|
||||
return
|
||||
}
|
||||
// prompt类型的弹窗 文案没有通过校验,点击了确定按钮没有任何效果
|
||||
if (type.value === 'prompt' && action === 'confirm' && !validate()) {
|
||||
return
|
||||
}
|
||||
show.value = false
|
||||
switch (action) {
|
||||
case 'confirm':
|
||||
onConfirm &&
|
||||
onConfirm({
|
||||
action: action,
|
||||
value: inputValue.value
|
||||
})
|
||||
break
|
||||
case 'cancel':
|
||||
onCancel &&
|
||||
onCancel({
|
||||
action: action
|
||||
})
|
||||
break
|
||||
default:
|
||||
onCancel &&
|
||||
onCancel({
|
||||
action: 'modal'
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @description 如果存在校验规则行为,则进行判断校验是否通过规则。默认不存在校验直接铜鼓。
|
||||
* @return {Boolean} 是否通过校验
|
||||
*/
|
||||
function validate() {
|
||||
if (inputPattern.value && !inputPattern.value.test(String(inputValue.value))) {
|
||||
showErr.value = true
|
||||
return false
|
||||
}
|
||||
if (typeof inputValidate === 'function') {
|
||||
const validateResult = inputValidate(inputValue)
|
||||
if (!validateResult) {
|
||||
showErr.value = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
showErr.value = false
|
||||
return true
|
||||
}
|
||||
/**
|
||||
* @description show关闭时,销毁错误提示
|
||||
* @param val
|
||||
*/
|
||||
function resetErr(val) {
|
||||
if (val === false) {
|
||||
showErr.value = false
|
||||
}
|
||||
}
|
||||
function inputValChange(value: string | number) {
|
||||
if (value === '') {
|
||||
showErr.value = false
|
||||
return
|
||||
}
|
||||
inputValue.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置message选项值
|
||||
* @param option message选项值
|
||||
*/
|
||||
function reset(option: MessageOptions) {
|
||||
if (option) {
|
||||
title.value = option.title || title.value
|
||||
showCancelButton.value = option.showCancelButton || showCancelButton.value
|
||||
show.value = option.show!
|
||||
closeOnClickModal.value = option.closeOnClickModal!
|
||||
confirmButtonText.value = option.confirmButtonText!
|
||||
cancelButtonText.value = option.cancelButtonText!
|
||||
msg.value = option.msg!
|
||||
type.value = option.type!
|
||||
inputType.value = option.inputType!
|
||||
inputValue.value = option.inputValue!
|
||||
inputPlaceholder.value = option.inputPlaceholder!
|
||||
inputPattern.value = option.inputPattern!
|
||||
inputValidate = option.inputValidate!
|
||||
onConfirm = (option as any).onConfirm
|
||||
onCancel = (option as any).onCancel
|
||||
inputError.value = option.inputError!
|
||||
showErr.value = option.showErr!
|
||||
zIndex.value = option.zIndex!
|
||||
lazyRender.value = option.lazyRender!
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
@import "./../common/abstracts/_mixin.scss";
|
||||
@import "./../common/abstracts/variable.scss";
|
||||
|
||||
|
||||
@include b(modal) {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: $-modal-bg;
|
||||
}
|
||||
@ -16,9 +16,9 @@ VueComponent({
|
||||
customStyle: String
|
||||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
handleClick() {
|
||||
this.$emit('click')
|
||||
},
|
||||
noop () {}
|
||||
noop() {}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
@import "./../common/abstracts/_mixin.scss";
|
||||
@import "./../common/abstracts/variable.scss";
|
||||
|
||||
|
||||
@include b(modal) {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
||||
@ -1,22 +1,51 @@
|
||||
<template>
|
||||
<wd-transition
|
||||
show="{{ show }}"
|
||||
:show="show"
|
||||
name="fade"
|
||||
custom-class="wd-modal"
|
||||
duration="{{ duration }}"
|
||||
custom-style="z-index: {{ zIndex }}; {{ customStyle }}"
|
||||
bind:tap="handleClick"
|
||||
catch:touchmove="noop"
|
||||
:duration="duration"
|
||||
:custom-style="`z-index: ${zIndex}; ${customStyle}`"
|
||||
@click="handleClick"
|
||||
@touchmove="noop"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
// 将自定义节点设置成虚拟的,更加接近Vue组件的表现,可以去掉微信小程序自定义组件多出的最外层标签
|
||||
options: {
|
||||
virtualHost: true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script></script>
|
||||
<script lang="ts" setup>
|
||||
interface Props {
|
||||
show: boolean
|
||||
duration: Record<string, number> | number
|
||||
zIndex: number
|
||||
customStyle: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
show: false,
|
||||
duration: 300,
|
||||
zIndex: 10
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
function handleClick() {
|
||||
emit('click')
|
||||
}
|
||||
function noop() {}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './../common/abstracts/_mixin.scss';
|
||||
@import './../common/abstracts/variable.scss';
|
||||
// @import './index.scss';
|
||||
|
||||
@include b(modal) {
|
||||
:deep(.wd-modal) {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
@ -1,6 +1,315 @@
|
||||
<template>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<wd-modal
|
||||
v-if="modal"
|
||||
:show="modelValue"
|
||||
:z-index="zIndex"
|
||||
:duration="duration"
|
||||
:custom-style="modalStyle"
|
||||
@click="handleClickModal"
|
||||
@touchmove="noop"
|
||||
/>
|
||||
<view v-if="!lazyRender || inited" :class="rootClass" :style="style" @transitionend="onTransitionEnd">
|
||||
<slot />
|
||||
<wd-icon v-if="closable" class="wd-popup__close" name="add" @click="close" />
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
// 将自定义节点设置成虚拟的,更加接近Vue组件的表现,可以去掉微信小程序自定义组件多出的最外层标签
|
||||
options: {
|
||||
virtualHost: true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue'
|
||||
import { isObj } from '../common/util'
|
||||
|
||||
interface Props {
|
||||
transition: string
|
||||
closable: boolean
|
||||
position: string
|
||||
closeOnClickModal: boolean
|
||||
duration: number | boolean
|
||||
modal: boolean
|
||||
zIndex: number
|
||||
hideWhenClose: boolean
|
||||
modalStyle: string
|
||||
safeAreaInsetBottom: boolean
|
||||
modelValue: boolean
|
||||
customStyle: string
|
||||
lazyRender: boolean
|
||||
customClass?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
position: 'center',
|
||||
closeOnClickModal: true,
|
||||
modal: true,
|
||||
closable: false,
|
||||
duration: 300,
|
||||
zIndex: 10,
|
||||
hideWhenClose: true,
|
||||
lazyRender: true,
|
||||
safeAreaInsetBottom: false,
|
||||
modelValue: false
|
||||
})
|
||||
|
||||
const getClassNames = (name) => {
|
||||
if (!name) {
|
||||
return {
|
||||
enter: 'enter-class enter-active-class',
|
||||
'enter-to': 'enter-to-class enter-active-class',
|
||||
leave: 'leave-class leave-active-class',
|
||||
'leave-to': 'leave-to-class leave-active-class'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
enter: `wd-${name}-enter wd-${name}-enter-active`,
|
||||
'enter-to': `wd-${name}-enter-to wd-${name}-enter-active`,
|
||||
leave: `wd-${name}-leave wd-${name}-leave-active`,
|
||||
'leave-to': `wd-${name}-leave-to wd-${name}-leave-active`
|
||||
}
|
||||
}
|
||||
|
||||
const requestAnimationFrame = (cb = () => void 0) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.selectViewport()
|
||||
.boundingClientRect()
|
||||
.exec(() => {
|
||||
resolve(true)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化是否完成
|
||||
const inited = ref<boolean>(false)
|
||||
// 是否显示
|
||||
const display = ref<boolean>(false)
|
||||
// 当前动画状态
|
||||
const status = ref<string>('')
|
||||
// 动画是否结束
|
||||
const transitionEnded = ref<boolean>(false)
|
||||
// 动画持续时间
|
||||
const currentDuration = ref<number>(300)
|
||||
// 类名
|
||||
const classes = ref<string>('')
|
||||
|
||||
const safeBottom = ref<number>(0)
|
||||
|
||||
const name = ref<string>('') // 动画名
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'click',
|
||||
'before-enter',
|
||||
'enter',
|
||||
'before-leave',
|
||||
'leave',
|
||||
'after-leave',
|
||||
'after-enter',
|
||||
'clickmodal',
|
||||
'close'
|
||||
])
|
||||
|
||||
const style = computed(() => {
|
||||
return `z-index: ${props.zIndex}; padding-bottom: ${safeBottom.value}px; -webkit-transition-duration: ${
|
||||
currentDuration.value
|
||||
}ms; transition-duration: ${currentDuration.value}ms; ${display.value || !props.hideWhenClose ? '' : 'display: none;'} ${props.customStyle}`
|
||||
})
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-popup wd-popup--${props.position} ${props.customClass || ''} ${classes.value || ''}`
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.modelValue) {
|
||||
enter()
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
observermodelValue(newVal)
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
function observermodelValue(value: boolean) {
|
||||
value ? enter() : leave()
|
||||
}
|
||||
|
||||
function enter() {
|
||||
const classNames = getClassNames(props.transition || props.position)
|
||||
const duration = props.transition === 'none' ? 0 : isObj(props.duration) ? (props.duration as any).enter : props.duration
|
||||
status.value = 'enter'
|
||||
emit('before-enter')
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
emit('enter')
|
||||
classes.value = classNames.enter
|
||||
currentDuration.value = duration
|
||||
requestAnimationFrame(() => {
|
||||
inited.value = true
|
||||
display.value = true
|
||||
requestAnimationFrame(() => {
|
||||
transitionEnded.value = false
|
||||
classes.value = classNames['enter-to']
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
function leave() {
|
||||
if (!display.value) return
|
||||
const classNames = getClassNames(props.transition || props.position)
|
||||
const duration = props.transition === 'none' ? 0 : isObj(props.duration) ? (props.duration as any).leave : props.duration
|
||||
status.value = 'leave'
|
||||
emit('before-leave')
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
emit('leave')
|
||||
classes.value = classNames.leave
|
||||
currentDuration.value = duration
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
transitionEnded.value = false
|
||||
setTimeout(() => onTransitionEnd(), currentDuration.value)
|
||||
classes.value = classNames['leave-to']
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function onTransitionEnd() {
|
||||
if (transitionEnded.value) return
|
||||
|
||||
transitionEnded.value = true
|
||||
if (status.value === 'leave') {
|
||||
// 离开后触发
|
||||
emit('after-leave')
|
||||
} else if (status.value === 'enter') {
|
||||
// 进入后触发
|
||||
emit('after-enter')
|
||||
}
|
||||
if (!props.modelValue && display.value) {
|
||||
display.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickModal() {
|
||||
emit('clickmodal')
|
||||
if (props.closeOnClickModal) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('close')
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
function noop() {}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import './../common/abstracts/_mixin.scss';
|
||||
@import './../common/abstracts/variable.scss';
|
||||
@import '../wd-modal/index.scss';
|
||||
|
||||
@include b(popup) {
|
||||
position: fixed;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
|
||||
@include e(close) {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
color: $-popup-close-color;
|
||||
font-size: $-popup-close-size;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
@include m(center) {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
@include m(left) {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
@include m(right) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
@include m(top) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
@include m(bottom) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wd-center-enter-active,
|
||||
.wd-center-leave-active {
|
||||
transition-property: opacity;
|
||||
}
|
||||
|
||||
.wd-center-enter,
|
||||
.wd-center-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.wd-top-enter-active,
|
||||
.wd-top-leave-active,
|
||||
.wd-bottom-enter-active,
|
||||
.wd-bottom-leave-active,
|
||||
.wd-left-enter-active,
|
||||
.wd-left-leave-active,
|
||||
.wd-right-enter-active,
|
||||
.wd-right-enter-active {
|
||||
transition-property: transform;
|
||||
}
|
||||
|
||||
.wd-top-enter,
|
||||
.wd-top-leave-to {
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
.wd-bottom-enter,
|
||||
.wd-bottom-leave-to {
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
.wd-left-enter,
|
||||
.wd-left-leave-to {
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
|
||||
.wd-right-enter,
|
||||
.wd-right-leave-to {
|
||||
transform: translate3d(100%, 0, 0);
|
||||
}
|
||||
|
||||
.wd-zoom-in-enter-active,
|
||||
.wd-zoom-in-leave-active {
|
||||
transition-property: opacity, transform;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.wd-zoom-in-enter,
|
||||
.wd-zoom-in-leave-to {
|
||||
opacity: 0;
|
||||
transform: translate3d(-50%, -50%, 0) scale(0.7);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="wd-rate custom-class">
|
||||
<view :class="`wd-rate ${customClass}`">
|
||||
<view
|
||||
v-for="(rate, index) in rateList"
|
||||
:key="index"
|
||||
@ -21,6 +21,7 @@
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
interface Props {
|
||||
customClass?: string
|
||||
num: number
|
||||
modelValue: string | number | null
|
||||
readonly: boolean
|
||||
@ -35,6 +36,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
customClass: '',
|
||||
num: 5,
|
||||
modelValue: null,
|
||||
readonly: false,
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
@import "../common/abstracts/_mixin.scss";
|
||||
@import "../common/abstracts/variable.scss";
|
||||
|
||||
@include b(search) {
|
||||
@include e(search-icon) {
|
||||
margin-right: 8px;
|
||||
color: $-search-icon-color;
|
||||
font-size: $-search-input-fs;
|
||||
height: $-search-input-height;
|
||||
}
|
||||
@include e(search-left-icon) {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
height: $-search-input-height;
|
||||
line-height: $-search-input-height;
|
||||
top: 50%;
|
||||
left: 16px;
|
||||
transform: translateY(-50%);
|
||||
color: $-search-icon-color;
|
||||
font-size: $-search-input-fs;
|
||||
}
|
||||
@include e(clear-icon) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,7 @@ VueComponent({
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
observer (s) {
|
||||
observer(s) {
|
||||
this.setData({ str: s })
|
||||
}
|
||||
},
|
||||
@ -44,7 +44,7 @@ VueComponent({
|
||||
clearing: false
|
||||
},
|
||||
methods: {
|
||||
closeCover () {
|
||||
closeCover() {
|
||||
if (this.data.disabled) return
|
||||
this.requestAnimationFrame()
|
||||
.then(() => this.requestAnimationFrame())
|
||||
@ -60,7 +60,7 @@ VueComponent({
|
||||
* @description input的input事件handle
|
||||
* @param value
|
||||
*/
|
||||
inputValue ({ detail: { value } }) {
|
||||
inputValue({ detail: { value } }) {
|
||||
this.setData({ str: value }, () => {
|
||||
this.$emit('change', {
|
||||
value
|
||||
@ -70,7 +70,7 @@ VueComponent({
|
||||
/**
|
||||
* @description 点击清空icon的handle
|
||||
*/
|
||||
clearSearch () {
|
||||
clearSearch() {
|
||||
this.data.clearing = true
|
||||
this.setData({ str: '' })
|
||||
this.requestAnimationFrame()
|
||||
@ -94,7 +94,7 @@ VueComponent({
|
||||
* @description 点击搜索按钮时的handle
|
||||
* @param value
|
||||
*/
|
||||
search ({ detail: { value } }) {
|
||||
search({ detail: { value } }) {
|
||||
// 组件触发search事件
|
||||
this.$emit('search', {
|
||||
value
|
||||
@ -103,29 +103,30 @@ VueComponent({
|
||||
/**
|
||||
* @description 输入框聚焦时的handle
|
||||
*/
|
||||
searchFocus () {
|
||||
searchFocus() {
|
||||
if (this.data.clearing) {
|
||||
this.data.clearing = false
|
||||
return
|
||||
}
|
||||
this.setData({
|
||||
showPlaceHolder: false,
|
||||
focus: true
|
||||
},
|
||||
() => this.$emit('focus', {
|
||||
value: this.data.str
|
||||
})
|
||||
this.setData(
|
||||
{
|
||||
showPlaceHolder: false,
|
||||
focus: true
|
||||
},
|
||||
() =>
|
||||
this.$emit('focus', {
|
||||
value: this.data.str
|
||||
})
|
||||
)
|
||||
},
|
||||
/**
|
||||
* @description 输入框失焦的handle
|
||||
*/
|
||||
searchBlur () {
|
||||
searchBlur() {
|
||||
if (this.data.clearing) return
|
||||
// 组件触发blur事件
|
||||
this.setData(
|
||||
{ showPlaceHolder: !this.data.str },
|
||||
() => this.$emit('blur', {
|
||||
this.setData({ showPlaceHolder: !this.data.str }, () =>
|
||||
this.$emit('blur', {
|
||||
value: this.data.str
|
||||
})
|
||||
)
|
||||
@ -133,11 +134,11 @@ VueComponent({
|
||||
/**
|
||||
* @description 点击取消搜索按钮的handle
|
||||
*/
|
||||
handleCancel () {
|
||||
handleCancel() {
|
||||
// 组件触发cancel事件
|
||||
this.$emit('cancel', {
|
||||
value: this.data.str
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,306 @@
|
||||
<template>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<view :class="rootClass">
|
||||
<!--自定义label插槽-->
|
||||
<!--搜索框主体-->
|
||||
<view class="wd-search__block">
|
||||
<slot name="prefix"></slot>
|
||||
<view class="wd-search__field">
|
||||
<view
|
||||
v-if="!placeholderLeft"
|
||||
:style="{ display: str === '' && showPlaceHolder ? 'flex' : 'none' }"
|
||||
class="wd-search__cover"
|
||||
@click="closeCover"
|
||||
>
|
||||
<wd-icon name="search" size="18px" custom-class="wd-search__search-icon"></wd-icon>
|
||||
<text class="wd-search__placeholder-txt">{{ placeholder || '搜索' }}</text>
|
||||
</view>
|
||||
<!--icon:search-->
|
||||
<wd-icon name="search" size="18px" custom-class="wd-search__search-left-icon"></wd-icon>
|
||||
<!--搜索框-->
|
||||
<input
|
||||
:placeholder="placeholder || '搜索'"
|
||||
placeholder-class="wd-search__placeholder-txt"
|
||||
confirm-type="search"
|
||||
v-model="str"
|
||||
class="wd-search__input"
|
||||
@focus="searchFocus"
|
||||
@input="inputValue"
|
||||
@blur="searchBlur"
|
||||
@confirm="search"
|
||||
:disabled="disabled"
|
||||
:maxlength="maxlength"
|
||||
:focus="focus"
|
||||
/>
|
||||
<!--icon:clear-->
|
||||
<wd-icon v-if="str" custom-class="wd-search__clear-icon" name="error-fill" size="16px" class="wd-search__clear" @click="clearSearch" />
|
||||
</view>
|
||||
</view>
|
||||
<!--the button behind input,care for hideCancel without displaying-->
|
||||
<block v-if="!hideCancel">
|
||||
<!--有插槽就不用默认的按钮了-->
|
||||
<slot v-if="userSuffixSlot" name="suffix"></slot>
|
||||
<!--默认button-->
|
||||
<view v-else class="wd-search__cancel" @click="handleCancel">
|
||||
{{ cancelTxt || '取消' }}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
// 将自定义节点设置成虚拟的,更加接近Vue组件的表现,可以去掉微信小程序自定义组件多出的最外层标签
|
||||
options: {
|
||||
virtualHost: true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { requestAnimationFrame } from '../common/util'
|
||||
|
||||
interface Props {
|
||||
useActionSlot: boolean
|
||||
useLabelSlot: boolean
|
||||
userSuffixSlot: boolean
|
||||
placeholder: string
|
||||
cancelTxt: string
|
||||
light: boolean
|
||||
hideCancel: boolean
|
||||
disabled: boolean
|
||||
maxlength: number
|
||||
modelValue: string
|
||||
placeholderLeft: boolean
|
||||
customClass: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
useActionSlot: false,
|
||||
useLabelSlot: false,
|
||||
userSuffixSlot: false,
|
||||
hideCancel: false,
|
||||
disabled: false,
|
||||
maxlength: -1,
|
||||
placeholderLeft: false
|
||||
})
|
||||
|
||||
const focus = ref<boolean>(false)
|
||||
const str = ref('')
|
||||
const showPlaceHolder = ref<boolean>(true)
|
||||
const clearing = ref<boolean>(false)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
str.value = newValue
|
||||
}
|
||||
)
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-search ${props.light ? 'is-light' : ''} ${props.hideCancel ? 'is-without-cancel' : ''} ${props.customClass}`
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'clear', 'search', 'focus', 'blur', 'cancel'])
|
||||
|
||||
function closeCover() {
|
||||
if (props.disabled) return
|
||||
requestAnimationFrame()
|
||||
.then(() => requestAnimationFrame())
|
||||
.then(() => requestAnimationFrame())
|
||||
.then(() => {
|
||||
showPlaceHolder.value = false
|
||||
focus.value = true
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description input的input事件handle
|
||||
* @param value
|
||||
*/
|
||||
function inputValue({ detail: { value } }) {
|
||||
str.value = value
|
||||
emit('update:modelValue', value)
|
||||
emit('change', {
|
||||
value
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 点击清空icon的handle
|
||||
*/
|
||||
function clearSearch() {
|
||||
clearing.value = true
|
||||
str.value = ''
|
||||
requestAnimationFrame()
|
||||
.then(() => requestAnimationFrame())
|
||||
.then(() => {
|
||||
showPlaceHolder.value = false
|
||||
return requestAnimationFrame()
|
||||
})
|
||||
.then(() => {
|
||||
focus.value = true
|
||||
emit('clear')
|
||||
emit('change', {
|
||||
value: ''
|
||||
})
|
||||
emit('update:modelValue', '')
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 点击搜索按钮时的handle
|
||||
* @param value
|
||||
*/
|
||||
function search({ detail: { value } }) {
|
||||
// 组件触发search事件
|
||||
emit('search', {
|
||||
value
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 输入框聚焦时的handle
|
||||
*/
|
||||
function searchFocus() {
|
||||
if (clearing.value) {
|
||||
clearing.value = false
|
||||
return
|
||||
}
|
||||
showPlaceHolder.value = false
|
||||
focus.value = true
|
||||
emit('focus', {
|
||||
value: str.value
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 输入框失焦的handle
|
||||
*/
|
||||
function searchBlur() {
|
||||
if (clearing.value) return
|
||||
// 组件触发blur事件
|
||||
showPlaceHolder.value = !str.value
|
||||
emit('blur', {
|
||||
value: str.value
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 点击取消搜索按钮的handle
|
||||
*/
|
||||
function handleCancel() {
|
||||
// 组件触发cancel事件
|
||||
emit('cancel', {
|
||||
value: str.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '../common/abstracts/_mixin.scss';
|
||||
@import '../common/abstracts/variable.scss';
|
||||
|
||||
@include b(search) {
|
||||
display: flex;
|
||||
padding: $-search-padding;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
|
||||
@include e(block) {
|
||||
flex: 1;
|
||||
background-color: $-search-input-bg;
|
||||
border-radius: $-search-input-radius;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
@include e(field) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
@include e(input) {
|
||||
flex: 1;
|
||||
height: $-search-input-height;
|
||||
box-sizing: border-box;
|
||||
padding: $-search-input-padding;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: $-search-input-fs;
|
||||
-webkit-appearance: none;
|
||||
outline: none;
|
||||
color: $-search-input-color;
|
||||
z-index: 0;
|
||||
|
||||
@include lineEllipsis;
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
@include e(cover) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: $-search-input-height;
|
||||
background-color: $-search-input-bg;
|
||||
line-height: $-search-input-height;
|
||||
font-size: $-search-input-fs;
|
||||
border-radius: $-search-input-radius;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@include e(search-icon) {
|
||||
margin-right: 8px;
|
||||
color: $-search-icon-color;
|
||||
font-size: $-search-input-fs;
|
||||
height: $-search-input-height;
|
||||
}
|
||||
@include e(search-left-icon) {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
height: $-search-input-height;
|
||||
line-height: $-search-input-height;
|
||||
top: 50%;
|
||||
left: 16px;
|
||||
transform: translateY(-50%);
|
||||
color: $-search-icon-color;
|
||||
font-size: $-search-input-fs;
|
||||
}
|
||||
@include e(placeholder-txt) {
|
||||
color: $-search-placeholder-color;
|
||||
font-size: $-search-input-fs;
|
||||
}
|
||||
@include e(clear) {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
padding: 6px 9px 6px 7px;
|
||||
color: $-search-cancel-color;
|
||||
}
|
||||
@include e(clear-icon) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@include e(cancel) {
|
||||
padding: $-search-cancel-padding;
|
||||
height: $-search-input-height;
|
||||
line-height: $-search-input-height;
|
||||
font-size: $-search-cancel-fs;
|
||||
color: $-search-cancel-color;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
@include when(light) {
|
||||
background: $-search-light-bg;
|
||||
|
||||
.wd-search__block {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.wd-search__cover {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
@include when(without-cancel) {
|
||||
padding-right: $-search-side-padding;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -23,40 +23,5 @@ VueComponent({
|
||||
beforeChange: null
|
||||
},
|
||||
methods: {
|
||||
switchValue () {
|
||||
if (this.data.disabled) return
|
||||
|
||||
const newVal = this.data.value === this.data.activeValue ? this.data.inactiveValue : this.data.activeValue
|
||||
|
||||
if (this.data.beforeChange && getType(this.data.beforeChange) === 'function') {
|
||||
this.data.beforeChange({
|
||||
value: newVal,
|
||||
resolve: (pass) => {
|
||||
if (pass) {
|
||||
this.setData({
|
||||
value: newVal
|
||||
})
|
||||
this.$emit('change', {
|
||||
value: newVal
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
value: newVal
|
||||
})
|
||||
this.$emit('change', {
|
||||
value: newVal
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
attached () {
|
||||
if ([this.data.activeValue, this.data.inactiveValue].indexOf(this.data.value) === -1) {
|
||||
this.$emit('change', {
|
||||
value: this.data.inactiveValue
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,90 @@
|
||||
<template>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<view :class="rootClass" :style="rootStyle" @click="switchValue">
|
||||
<view class="wd-switch__circle" :style="circleStyle"></view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onBeforeMount } from 'vue'
|
||||
import { getType, objToStyle } from '../common/util'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
disabled: boolean
|
||||
activeValue: boolean
|
||||
inactiveValue: boolean
|
||||
activeColor: string
|
||||
inactiveColor: string
|
||||
size: string
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
beforeChange: Function
|
||||
customClass: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
customClass: '',
|
||||
disabled: false,
|
||||
modelValue: false,
|
||||
activeValue: true,
|
||||
inactiveValue: false,
|
||||
size: '28px'
|
||||
})
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-switch ${props.customClass} ${props.disabled ? 'is-disabled' : ''} ${props.modelValue === props.activeValue ? 'is-checked' : ''}`
|
||||
})
|
||||
|
||||
const rootStyle = computed(() => {
|
||||
const rootStyle: Record<string, any> = {
|
||||
'font-size': props.size,
|
||||
background: props.modelValue === props.activeValue ? props.activeColor : props.inactiveColor,
|
||||
'border-color': props.modelValue === props.activeValue ? props.activeColor : props.inactiveColor
|
||||
}
|
||||
return objToStyle(rootStyle)
|
||||
})
|
||||
|
||||
const circleStyle = computed(() => {
|
||||
const circleStyle: string =
|
||||
(props.modelValue === props.activeValue && props.activeColor) || (props.modelValue !== props.activeValue && props.inactiveColor)
|
||||
? 'box-shadow: none;'
|
||||
: ''
|
||||
return circleStyle
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
|
||||
function switchValue() {
|
||||
if (props.disabled) return
|
||||
const newVal = props.modelValue === props.activeValue ? props.inactiveValue : props.activeValue
|
||||
|
||||
if (props.beforeChange && getType(props.beforeChange) === 'function') {
|
||||
props.beforeChange({
|
||||
value: newVal,
|
||||
resolve: (pass) => {
|
||||
if (pass) {
|
||||
emit('update:modelValue', newVal)
|
||||
emit('change', {
|
||||
value: newVal
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
emit('update:modelValue', newVal)
|
||||
emit('change', {
|
||||
value: newVal
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if ([props.activeValue, props.inactiveValue].indexOf(props.modelValue) === -1) {
|
||||
emit('update:modelValue', props.inactiveValue)
|
||||
emit('change', {
|
||||
value: props.inactiveValue
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
</style>
|
||||
|
||||
@ -3,12 +3,5 @@ import transition from '../mixins/transition'
|
||||
|
||||
VueComponent({
|
||||
mixins: [transition()],
|
||||
externalClasses: [
|
||||
'enter-class',
|
||||
'enter-active-class',
|
||||
'enter-to-class',
|
||||
'leave-class',
|
||||
'leave-active-class',
|
||||
'leave-to-class'
|
||||
]
|
||||
})
|
||||
externalClasses: ['enter-class', 'enter-active-class', 'enter-to-class', 'leave-class', 'leave-active-class', 'leave-to-class']
|
||||
})
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
<template>
|
||||
<view v-if="inited" :class="'wd-transition custom-class ' + classes" :style="style" @transitionend="onTransitionEnd">
|
||||
<view v-if="inited" :class="rootClass" :style="style" @transitionend="onTransitionEnd" @click="handleClick">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
// 将自定义节点设置成虚拟的,更加接近Vue组件的表现,可以去掉微信小程序自定义组件多出的最外层标签
|
||||
options: {
|
||||
virtualHost: true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue'
|
||||
import { isObj } from '../common/util'
|
||||
@ -57,6 +67,7 @@ interface Props {
|
||||
name: TransitionName
|
||||
customStyle: string
|
||||
lazyRender: boolean
|
||||
customClass?: string
|
||||
// 定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
|
||||
enterClass?: string
|
||||
// 定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
|
||||
@ -99,6 +110,10 @@ const style = computed(() => {
|
||||
}${props.customStyle}`
|
||||
})
|
||||
|
||||
const rootClass = computed(() => {
|
||||
return `wd-transition ${props.customClass} ${classes.value}`
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.show) {
|
||||
enter()
|
||||
@ -109,9 +124,14 @@ watch(
|
||||
() => props.show,
|
||||
(newVal) => {
|
||||
observerShow(newVal)
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
function handleClick() {
|
||||
emit('click')
|
||||
}
|
||||
|
||||
function observerShow(value: boolean) {
|
||||
value ? enter() : leave()
|
||||
}
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
// 后续会对外暴露选中视频文件
|
||||
export function chooseFile ({
|
||||
multiple,
|
||||
sizeType,
|
||||
sourceType,
|
||||
maxCount
|
||||
}) {
|
||||
export function chooseFile({ multiple, sizeType, sourceType, maxCount }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jd.chooseImage({
|
||||
uni.chooseImage({
|
||||
count: multiple ? Math.min(maxCount, 9) : 1, // 最多可以选择的数量,如果不支持多选则数量为1
|
||||
sizeType,
|
||||
sourceType,
|
||||
@ -14,4 +9,4 @@ export function chooseFile ({
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,490 @@
|
||||
<template>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<view :class="['wd-upload', customClass]">
|
||||
<!-- 预览列表 -->
|
||||
<view :class="['wd-upload__preview', customPreviewClass]" v-for="(file, index) in uploadFiles" :key="index">
|
||||
<!-- 成功时展示图片 -->
|
||||
<view class="wd-upload__status-content">
|
||||
<image :src="file.url" mode="aspectFit" class="wd-upload__picture" @click="onPreviewImage(index)" />
|
||||
</view>
|
||||
|
||||
<view v-if="file.status !== 'success'" class="wd-upload__mask wd-upload__status-content">
|
||||
<!-- loading时展示loading图标和进度 -->
|
||||
<view v-if="file.status === 'loading'" class="wd-upload__status-content">
|
||||
<wd-loading :size="24" :color="loadingColor" />
|
||||
<text class="wd-upload__progress-txt">{{ file.percent }}%</text>
|
||||
</view>
|
||||
<!-- 失败时展示失败图标以及失败信息 -->
|
||||
<view v-if="file.status === 'fail'" class="wd-upload__status-content">
|
||||
<wd-icon name="close-outline" custom-class="wd-upload__icon"></wd-icon>
|
||||
<text class="wd-upload__progress-txt">{{ file.error || '上传失败' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 上传状态为上传中时不展示移除按钮 -->
|
||||
<wd-icon v-if="file.status !== 'loading' && !disabled" name="error-fill" class="wd-upload__close" @clcik="removeFile(index)"></wd-icon>
|
||||
</view>
|
||||
|
||||
<view bindtap="handleChoose">
|
||||
<slot v-if="useDefaultSlot"></slot>
|
||||
<!-- 唤起项 -->
|
||||
<view
|
||||
v-if="!useDefaultSlot && (!limit || uploadFiles.length < limit)"
|
||||
:class="['wd-upload__evoke', disabled ? 'is-disabled' : '', customEvokeClass]"
|
||||
>
|
||||
<!-- 唤起项图标 -->
|
||||
<wd-icon class="wd-upload__evoke-icon" name="fill-camera"></wd-icon>
|
||||
<!-- 有限制个数时确认是否展示限制个数 -->
|
||||
<view v-if="limit && showLimitMum" class="wd-upload__evoke-num">({{ uploadFiles.length }}/{{ limit }})</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { context } from '../common/util'
|
||||
import { chooseFile } from './utils'
|
||||
|
||||
interface Props {
|
||||
customClass: string
|
||||
customEvokeClass: string
|
||||
customPreviewClass: string
|
||||
|
||||
// 多选
|
||||
multiple: boolean
|
||||
// 接受类型 暂定接受类型为图片,视频后续添加
|
||||
accept: string
|
||||
sizeType: Array<string>
|
||||
sourceType: Array<string>
|
||||
header: Record<string, any>
|
||||
name: string
|
||||
formData: Record<string, any>
|
||||
// 上传相关
|
||||
action: string
|
||||
fileList: Record<string, any>[]
|
||||
statusKey: string
|
||||
maxSize: number
|
||||
limit: number
|
||||
showLimitMum: boolean
|
||||
disabled: boolean
|
||||
useDefaultSlot: boolean
|
||||
// loading 相关
|
||||
loadingType: string
|
||||
loadingColor: string
|
||||
loadingSize: string
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
beforePreview: Function
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
onPreviewFail: Function
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
beforeRemove: Function
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
beforeUpload: Function
|
||||
// 图片预览相关
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
beforeChoose: Function
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fileList: () => [] as Record<string, any>[],
|
||||
showLimitMum: false,
|
||||
sourceType: () => ['album', 'camera'],
|
||||
sizeType: () => ['original', 'compressed'],
|
||||
name: 'file',
|
||||
loadingType: 'circular-ring',
|
||||
loadingColor: '#ffffff',
|
||||
loadingSize: '24px',
|
||||
useDefaultSlot: false,
|
||||
statusKey: 'status'
|
||||
})
|
||||
|
||||
const uploadFiles = ref<Record<string, any>[]>([])
|
||||
|
||||
const emit = defineEmits(['fail', 'change', 'success', 'progress', 'oversize', 'chooseerror', 'remove'])
|
||||
|
||||
/**
|
||||
* @description 初始化文件数据
|
||||
* @param {Object} file 上传的文件
|
||||
*/
|
||||
function initFile(file) {
|
||||
// 状态初始化
|
||||
const initState = {
|
||||
uid: context.id++,
|
||||
status: 'loading',
|
||||
size: file.size,
|
||||
url: file.path,
|
||||
action: props.action,
|
||||
percent: 0
|
||||
}
|
||||
|
||||
uploadFiles.value.push(initState)
|
||||
handleUpload(initState)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 上传失败捕获
|
||||
* @param {Object} err 错误返回信息
|
||||
* @param {Object} file 上传的文件
|
||||
*/
|
||||
function handleError(err, file) {
|
||||
const { statusKey } = props
|
||||
const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
|
||||
if (index > -1) {
|
||||
uploadFiles.value[index][statusKey] = 'fail'
|
||||
uploadFiles.value[index].error = err.message
|
||||
uploadFiles.value[index].response = err
|
||||
emit('fail', { error: err, file })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 上传成功捕获
|
||||
* @param {Object} res 接口返回信息
|
||||
* @param {Object} file 上传的文件
|
||||
*/
|
||||
function handleSuccess(res, file) {
|
||||
const { statusKey } = props
|
||||
const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
|
||||
if (index > -1) {
|
||||
uploadFiles.value[index][statusKey] = 'success'
|
||||
uploadFiles.value[index].response = res.data
|
||||
emit('change', { fileList: uploadFiles.value })
|
||||
emit('success', { file, fileList: uploadFiles.value })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 上传中捕获
|
||||
* @param {Object} res 接口返回信息
|
||||
* @param {Object} file 上传的文件
|
||||
*/
|
||||
function handleProgress(res, file) {
|
||||
const index = uploadFiles.value.findIndex((item) => item.uid === file.uid)
|
||||
if (index > -1) {
|
||||
uploadFiles.value[index].percent = res.progress
|
||||
emit('progress', { response: res, file })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 上传操作
|
||||
* @param {Object} file 上传的文件
|
||||
*/
|
||||
function handleUpload(file) {
|
||||
const { action, name, formData = {}, header = {} } = props
|
||||
const uploadTask = uni.uploadFile({
|
||||
url: action,
|
||||
header,
|
||||
name,
|
||||
formData,
|
||||
filePath: file.url,
|
||||
success(res) {
|
||||
// 上传成功进行文件列表拼接
|
||||
handleSuccess(res, file)
|
||||
},
|
||||
fail(err) {
|
||||
// 上传失败处理
|
||||
handleError(err, file)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取当前文件加载的百分比
|
||||
uploadTask.onProgressUpdate((res) => {
|
||||
/**
|
||||
* res.progress: 上传进度
|
||||
* res.totalBytesSent: 已经上传的数据长度
|
||||
* res.totalBytesExpectedToSend: 预期需要上传的数据总长度
|
||||
*/
|
||||
handleProgress(res, file)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 选择文件的实际操作,将chooseFile自己用promise包了一层
|
||||
*/
|
||||
function onChooseFile() {
|
||||
const { multiple, maxSize, accept, sizeType, limit, sourceType, beforeUpload } = props
|
||||
|
||||
// 设置为只选择图片的时候使用 chooseImage 来实现
|
||||
if (accept === 'image') {
|
||||
// 文件选择
|
||||
chooseFile({
|
||||
multiple,
|
||||
sizeType,
|
||||
sourceType,
|
||||
maxCount: limit ? limit - uploadFiles.value.length : 9
|
||||
})
|
||||
.then((res: any) => {
|
||||
// 成功选择初始化file
|
||||
let files: null | Array<any> = null
|
||||
files = Array.prototype.slice.call(res.tempFiles)
|
||||
// 单选只有一个
|
||||
if (!multiple) {
|
||||
files = files.slice(0, 1)
|
||||
}
|
||||
|
||||
// 遍历列表逐个初始化上传参数
|
||||
const mapFiles = (files) => {
|
||||
files.forEach((file) => {
|
||||
file.size <= maxSize ? initFile(file) : emit('oversize', { file })
|
||||
})
|
||||
}
|
||||
|
||||
// 上传前的钩子
|
||||
if (beforeUpload) {
|
||||
// 向下兼容原来的参数写法,2.2.0 向下兼容 2.1.0
|
||||
if (beforeUpload.length === 2) {
|
||||
beforeUpload(files, (isPass) => {
|
||||
isPass && mapFiles(files)
|
||||
})
|
||||
} else {
|
||||
beforeUpload({
|
||||
files,
|
||||
fileList: uploadFiles.value,
|
||||
resolve: (isPass) => {
|
||||
isPass && mapFiles(files)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
mapFiles(files)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
emit('chooseerror', { error })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 选择文件,内置拦截选择操作
|
||||
*/
|
||||
function handleChoose() {
|
||||
if (props.disabled) return
|
||||
const { beforeChoose } = props
|
||||
// 选择图片前的钩子
|
||||
if (beforeChoose) {
|
||||
// 向下兼容原来的参数写法,2.2.0 向下兼容 2.1.0
|
||||
if (beforeChoose.length === 2) {
|
||||
beforeChoose(uploadFiles.value, (isPass) => {
|
||||
isPass && onChooseFile()
|
||||
})
|
||||
} else {
|
||||
beforeChoose({
|
||||
fileList: uploadFiles.value,
|
||||
resolve: (isPass) => {
|
||||
isPass && onChooseFile()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
onChooseFile()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 移除文件
|
||||
* @param {Object} file 上传的文件
|
||||
* @param {Number} index 删除
|
||||
*/
|
||||
function handleRemove(file, index?: number) {
|
||||
uploadFiles.value.splice(
|
||||
uploadFiles.value.findIndex((item) => item.uid === file.uid),
|
||||
1
|
||||
)
|
||||
emit('change', {
|
||||
fileList: uploadFiles.value
|
||||
})
|
||||
emit('remove', { file })
|
||||
}
|
||||
|
||||
function removeFile(index) {
|
||||
const { beforeRemove } = props
|
||||
const intIndex = parseInt(index)
|
||||
const file = uploadFiles.value[intIndex]
|
||||
if (beforeRemove) {
|
||||
// 向下兼容原来的参数写法,2.2.0 向下兼容 2.1.0
|
||||
if (beforeRemove.length === 3) {
|
||||
beforeRemove(file, uploadFiles.value, (isPass) => {
|
||||
isPass && handleRemove(file)
|
||||
})
|
||||
} else {
|
||||
beforeRemove({
|
||||
file,
|
||||
index: intIndex,
|
||||
fileList: uploadFiles.value,
|
||||
resolve: (isPass) => {
|
||||
isPass && handleRemove(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
handleRemove(file)
|
||||
}
|
||||
}
|
||||
|
||||
function onPreview(index, lists) {
|
||||
const { onPreviewFail } = props
|
||||
uni.previewImage({
|
||||
urls: lists,
|
||||
current: lists[index],
|
||||
fail() {
|
||||
if (onPreviewFail) {
|
||||
if (onPreviewFail.length === 2) {
|
||||
onPreviewFail(index, lists)
|
||||
} else {
|
||||
onPreviewFail({
|
||||
index,
|
||||
imgList: lists
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: '预览图片失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onPreviewImage(index) {
|
||||
const { beforePreview } = props
|
||||
const lists = uploadFiles.value.map((file) => file.url)
|
||||
if (beforePreview) {
|
||||
// 向下兼容原来的参数写法,2.2.0 向下兼容 2.1.0
|
||||
if (beforePreview.length === 2) {
|
||||
beforePreview({ index, lists }, (isPass) => {
|
||||
isPass && onPreview(index, lists)
|
||||
})
|
||||
} else {
|
||||
beforePreview({
|
||||
index,
|
||||
imgList: lists,
|
||||
resolve: (isPass) => {
|
||||
isPass && onPreview(index, lists)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
onPreview(index, lists)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '../common/abstracts/variable.scss';
|
||||
@import '../common/abstracts/_mixin.scss';
|
||||
|
||||
@include b(upload) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@include e(evoke) {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: $-upload-size;
|
||||
height: $-upload-size;
|
||||
font-size: $-upload-evoke-icon-size;
|
||||
background-color: $-upload-evoke-bg;
|
||||
color: $-upload-evoke-color;
|
||||
margin-bottom: 12px;
|
||||
|
||||
@include when(disabled) {
|
||||
color: $-upload-evoke-disabled-color;
|
||||
}
|
||||
|
||||
@include when(slot-default) {
|
||||
width: auto;
|
||||
height: auto;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(evoke-num) {
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@include e(evoke-icon) {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@include e(input) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@include e(preview) {
|
||||
position: relative;
|
||||
width: $-upload-size;
|
||||
height: $-upload-size;
|
||||
margin: 0 12px 12px 0;
|
||||
}
|
||||
|
||||
@include e(preview-list) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@include e(picture) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@include e(close) {
|
||||
position: absolute;
|
||||
right: -$-upload-close-icon-size / 2;
|
||||
top: -$-upload-close-icon-size / 2;
|
||||
font-size: $-upload-close-icon-size;
|
||||
z-index: 1;
|
||||
color: $-upload-close-icon-color;
|
||||
width: $-upload-close-icon-size;
|
||||
height: $-upload-close-icon-size;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: $-color-white;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(mask) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: $-upload-preview-name-bg;
|
||||
}
|
||||
|
||||
@include e(progress-txt) {
|
||||
font-size: $-upload-progress-fs;
|
||||
line-height: $-upload-progress-fs;
|
||||
margin-top: 9px;
|
||||
color: $-color-white;
|
||||
}
|
||||
|
||||
@include e(icon) {
|
||||
font-size: $-upload-preview-icon-size;
|
||||
color: $-color-white;
|
||||
}
|
||||
|
||||
@include e(status-content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -9,12 +9,8 @@
|
||||
*/
|
||||
import { defineConfig } from 'vite'
|
||||
import uni from '@dcloudio/vite-plugin-uni'
|
||||
import TransformPages from 'uni-read-pages-vite'
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [uni()],
|
||||
define: {
|
||||
ROUTES: new TransformPages().routes
|
||||
}
|
||||
plugins: [uni()]
|
||||
})
|
||||
|
||||
@ -7213,11 +7213,6 @@ unbox-primitive@^1.0.2:
|
||||
has-symbols "^1.0.3"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
uni-mini-router@^0.0.12:
|
||||
version "0.0.12"
|
||||
resolved "https://registry.npmmirror.com/uni-mini-router/-/uni-mini-router-0.0.12.tgz#3be55f5968044554b8ba1b13322d136a19865c29"
|
||||
integrity sha512-b6nvpa3RfCGhOloIALo7+jLjuDztOpfC0M8tzp4kUzlexx2etDTPDLxRFRUeu5F4kQp1YQXJtC4xa9ezK6RHjg==
|
||||
|
||||
uni-read-pages-vite@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.npmmirror.com/uni-read-pages-vite/-/uni-read-pages-vite-0.0.6.tgz#cb3f3d753acf30684a7707e80cf7eb2d8d5b17af"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user