feat: 增加message-box组件

This commit is contained in:
xuqingkai 2023-06-19 12:31:04 +08:00
parent 62d3c53575
commit 26c5bc9074
49 changed files with 3260 additions and 411 deletions

View File

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

View File

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

View File

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

View File

@ -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": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {}

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

View File

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

View File

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

View File

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

View File

@ -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%;
}
}

View File

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

View File

@ -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
// (nullundefinedundefinedvoid (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() {
// passwordfalse
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)
})
}
// changeblur 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>

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,9 +16,9 @@ VueComponent({
customStyle: String
},
methods: {
handleClick () {
handleClick() {
this.$emit('click')
},
noop () {}
noop() {}
}
})
})

View File

@ -1,6 +1,7 @@
@import "./../common/abstracts/_mixin.scss";
@import "./../common/abstracts/variable.scss";
@include b(modal) {
position: fixed;
left: 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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']
})

View File

@ -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()
}

View File

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

View File

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

View File

@ -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()]
})

View File

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