feat: 新增 Form 表单组件

This commit is contained in:
xuqingkai 2023-12-23 16:37:52 +08:00
parent ed56bcdb03
commit c8086d624c
41 changed files with 2469 additions and 857 deletions

View File

@ -1,7 +1,7 @@
/* /*
* @Author: weisheng * @Author: weisheng
* @Date: 2023-07-27 10:26:09 * @Date: 2023-07-27 10:26:09
* @LastEditTime: 2023-12-07 15:08:32 * @LastEditTime: 2023-12-21 22:27:02
* @LastEditors: weisheng * @LastEditors: weisheng
* @Description: * @Description:
* @FilePath: \wot-design-uni\docs\.vitepress\config.ts * @FilePath: \wot-design-uni\docs\.vitepress\config.ts
@ -231,10 +231,13 @@ export default defineConfig({
text: "DatetimePickerView 时间选择器视图" text: "DatetimePickerView 时间选择器视图"
}, { }, {
link: "/component/form", link: "/component/form",
text: "Form 表单组合" text: "Form 表单"
}, { }, {
link: "/component/input", link: "/component/input",
text: "Input 输入框" text: "Input 输入框"
}, {
link: "/component/textarea",
text: "Textarea 文本域"
}, { }, {
link: "/component/input-number", link: "/component/input-number",
text: "InputNumber 计数器" text: "InputNumber 计数器"

View File

@ -397,6 +397,17 @@ function handleConfirm4({ value }) {
| close-on-click-modal | 点击遮罩是否关闭 | boolean | - | true | - | | close-on-click-modal | 点击遮罩是否关闭 | boolean | - | true | - |
| z-index | 弹窗层级 | number | - | 15 | - | | z-index | 弹窗层级 | number | - | 15 | - |
| safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - | | safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## Events ## Events

View File

@ -35,10 +35,10 @@
```scss ```scss
.cell-icon { .cell-icon {
display: block; display: block;
box-sizing: border-box;
width: 16px; width: 16px;
height: 16px; height: 16px;
margin-top: 2px; margin-right: 4px;
margin-right: 15px;
background: url('https://img10.360buyimg.com/jmadvertisement/jfs/t1/71075/7/3762/1820/5d1f26d1E0d600b9e/a264c901943080ac.png') no-repeat; background: url('https://img10.360buyimg.com/jmadvertisement/jfs/t1/71075/7/3762/1820/5d1f26d1E0d600b9e/a264c901943080ac.png') no-repeat;
background-size: cover; background-size: cover;
} }
@ -198,10 +198,11 @@ function handleSwitchChange({ value }) {
```scss ```scss
.cell-icon { .cell-icon {
display: block; display: block;
box-sizing: border-box;
padding: 4px 0;
width: 16px; width: 16px;
height: 16px; height: 24px;
margin-top: 2px; margin-right: 4px;
margin-right: 15px;
background: url('https://img10.360buyimg.com/jmadvertisement/jfs/t1/71075/7/3762/1820/5d1f26d1E0d600b9e/a264c901943080ac.png') no-repeat; background: url('https://img10.360buyimg.com/jmadvertisement/jfs/t1/71075/7/3762/1820/5d1f26d1E0d600b9e/a264c901943080ac.png') no-repeat;
background-size: cover; background-size: cover;
} }
@ -251,6 +252,18 @@ function handleSwitchChange({ value }) {
| center | 是否垂直居中,默认顶部居中 | boolean | - | false | - | | center | 是否垂直居中,默认顶部居中 | boolean | - | false | - |
| required | 表单属性,必填 | boolean | - | false | - | | required | 表单属性,必填 | boolean | - | false | - |
| vertical | 表单属性,上下结构 | boolean | - | false | - | | vertical | 表单属性,上下结构 | boolean | - | false | - |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## Cell Events ## Cell Events

View File

@ -525,6 +525,17 @@ const columnChange = ({ selectedItem, resolve, finish }) => {
| z-index | 弹窗层级 | number | - | 15 | - | | z-index | 弹窗层级 | number | - | 15 | - |
| safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - | | safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - |
| ellipsis | 是否超出隐藏 | boolean | - | false | - | | ellipsis | 是否超出隐藏 | boolean | - | false | - |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## 选项数据结构 ## 选项数据结构

View File

@ -284,6 +284,17 @@ const displayFormatTabLabel = (items) => {
| z-index | 弹窗层级 | number | - | 15 | - | | z-index | 弹窗层级 | number | - | 15 | - |
| safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - | | safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - |
| ellipsis | 是否超出隐藏 | boolean | - | false | - | | ellipsis | 是否超出隐藏 | boolean | - | false | - |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## Events ## Events

View File

@ -1,141 +1,727 @@
<frame/> <frame/>
# Form 表单组合 # Form 表单 <el-tag text style="vertical-align: middle;margin-left:8px;" effect="plain">0.2.0</el-tag>
本章节主要讲如何将多个 form 表单组件进行组合,形成一个完整的表单页面 用于数据录入、校验,支持输入框、单选框、复选框、文件上传等类型,常见的 form 表单为`单元格`形式的展示,即左侧为表单的标题描述,右侧为表单的输入
常见的 form 表单为`单元格`形式的展示,即左侧为表单的标题描述,右侧为表单的输入 其中,`Input 输入框``Textarea 输入框``Picker 选择器``Calendar 日历选择器``ColPicker 多列选择器``SelectPicker 单复选选择器``Cell 单元格``DatetimePicker 日期时间选择器`具有`单元格`的展示形式,同时也支持 `prop``rules` 属性,我们称之为`表单项组件`,而 `InputNumber 计数器``Switch 开关``Upload 上传` 等组件则需要使用 `Cell 单元格` 进行包裹使用
其中,`Input 输入框``Picker 选择器``Calendar 日历选择器`, `ColPicker 多列选择器``SelectPicker 单复选选择器``DatetimePicker 日期时间选择器`具有`单元格`的展示形式,而 `InputNumber 计数器``Switch 开关`需要使用 `Cell 单元格`进行包裹使用。 结合 `wd-form` 组件,可以实现对以上组件的规则校验。
所有的表单组件都支持 `name` 属性,可以结合小程序原生的 `form` 组件,监听 `submit` 事件,统一获取到所有表单组件的 `value`,也可以单独对每个表单组件监听 `change` 事件来获取单个表单组件的 `value`
> 对于表单组件,建议对 wd-cell-group 开启 border 属性,这样每条 cell 就会有边框线隔离开,这样表单的划分比较清晰。 > 对于表单组件,建议对 wd-cell-group 开启 border 属性,这样每条 cell 就会有边框线隔离开,这样表单的划分比较清晰。
下面是 Demo 示例: ## 基础用法
html 文件代码: 在表单中,使用 `model` 指定表单数据对象,每个 `表单项组件` 代表一个表单项,使用 `prop` 指定表单项字段 ,使用 `rules` 属性定义校验规则。
```html ::: details 查看基础用法示例
::: code-group
```html [vue]
<wd-form ref="form" :model="model">
<wd-cell-group border>
<wd-input
label="用户名"
label-width="100px"
prop="value1"
clearable
v-model="model.value1"
placeholder="请输入用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<wd-input
label="密码"
label-width="100px"
prop="value2"
show-password
clearable
v-model="model.value2"
placeholder="请输入密码"
:rules="[{ required: true, message: '请填写密码' }]"
/>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
</view>
</wd-form>
```
```typescript [typescript]
<script lang="ts" setup>
const { success: showSuccess } = useToast()
const model = reactive<{
value1: string
value2: string
}>({
value1: '',
value2: ''
})
const form = ref()
function handleSubmit1() {
form.value
.validate()
.then(({ valid, errors }) => {
if (valid) {
showSuccess({
msg: '校验通过'
})
}
})
.catch((error) => {
console.log(error, 'error')
})
}
</script>
```
```css [css]
.footer {
padding: 12px;
}
```
:::
## 校验规则
本章节演示四种自定义校验及提示规则:`正则校验``函数校验``函数返回错误提示``异步函数校验`
::: details 查看校验规则示例
::: code-group
```html [vue]
<wd-form ref="form2" :model="model">
<wd-cell-group border>
<wd-input
label="校验"
label-width="100px"
prop="value1"
clearable
v-model="model.value1"
placeholder="正则校验"
:rules="[{ required: false, pattern: /\d{6}/, message: '请输入6位字符' }]"
/>
<wd-input
label="校验"
label-width="100px"
prop="value2"
clearable
v-model="model.value2"
placeholder="函数校验"
:rules="[
{
required: false,
validator: validatorMessage,
message: '请输入正确的手机号'
}
]"
/>
<wd-input
label="校验"
label-width="100px"
prop="value3"
clearable
v-model="model.value3"
placeholder="校验函数返回错误提示"
:rules="[
{
required: false,
message: '请输入内容',
validator: validator
}
]"
/>
<wd-input
label="校验"
label-width="100px"
prop="value4"
clearable
v-model="model.value4"
placeholder="异步函数校验"
:rules="[{ required: false, validator: asyncValidator, message: '请输入1234' }]"
/>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
</view>
</wd-form>
```
```typescript [typescript]
<script lang="ts" setup>
const model = reactive<{
value1: string
value2: string
value3: string
value4: string
}>({
value1: '',
value2: '',
value3: '',
value4: ''
})
const { success: showSuccess } = useToast()
const form = ref()
const validatorMessage = (val) => {
return /1\d{10}/.test(val)
}
const validator = (val) => {
if (String(val).length >= 4) {
return Promise.resolve()
} else {
return Promise.reject('长度不得小于4')
}
}
// 校验函数可以返回 Promise实现异步校验
const asyncValidator = (val) =>
new Promise((resolve) => {
showLoading('验证中...')
setTimeout(() => {
closeToast()
resolve(val === '1234')
}, 1000)
})
function handleSubmit() {
form.value
.validate()
.then(({ valid, errors }) => {
if (valid) {
showSuccess({
msg: '提交成功'
})
}
})
.catch((error) => {
console.log(error, 'error')
})
}
</script>
```
```css [css]
.footer {
padding: 12px;
}
```
:::
## 动态表单
表单项动态增减。
::: details 查看动态表单示例
::: code-group
```html [vue]
<wd-form ref="form" :model="model">
<wd-cell-group border>
<wd-input
label="用户名"
label-width="100px"
prop="name"
clearable
v-model="model.name"
placeholder="请输入用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<wd-input
v-for="(item, index) in model.phoneNumbers"
:key="item.key"
:label="'联系方式' + index"
:prop="'phoneNumbers.' + index + '.value'"
label-width="100px"
clearable
v-model="item.value"
placeholder="联系方式"
:rules="[{ required: true, message: '请填写联系方式' + index }]"
/>
<wd-cell title-width="0px">
<view class="footer">
<wd-button size="small" type="info" plain @click="addPhone">添加</wd-button>
<wd-button size="small" type="info" plain @click="removePhone">删除</wd-button>
<wd-button size="small" type="info" plain @click="reset">重置</wd-button>
<wd-button type="primary" size="small" @click="submit">提交</wd-button>
</view>
</wd-cell>
</wd-cell-group>
</wd-form>
```
```typescript [typescript]
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { reactive, ref } from 'vue'
interface PhoneItem {
key: number
value: string
}
const model = reactive<{
name: string
phoneNumbers: PhoneItem[]
}>({
name: '',
phoneNumbers: [
{
key: Date.now(),
value: ''
}
]
})
const { success: showSuccess } = useToast()
const form = ref()
const removePhone = () => {
model.phoneNumbers.splice(model.phoneNumbers.length - 1, 1)
}
const addPhone = () => {
model.phoneNumbers.push({
key: Date.now(),
value: ''
})
}
const reset = () => {
form.value.reset()
}
const submit = () => {
form.value.validate().then(({ valid, errors }) => {
if (valid) {
showSuccess('校验通过')
}
})
}
</script>
```
```css [css]
.footer {
text-align: left;
:deep(.wd-button) {
&:not(:last-child) {
margin-right: 12px;
}
}
}
```
:::
## 指定字段校验
`validate` 方法可以传入一个 `prop` 参数,指定校验的字段,可以实现在表单组件的`blur``change`等事件触发时对该字段的校验。
::: details 查看指定字段校验示例
::: code-group
```html [vue]
<wd-form ref="form" :model="model">
<wd-cell-group border>
<wd-input
label="用户名"
label-width="100px"
prop="name"
clearable
v-model="model.name"
placeholder="请输入用户名"
@blur="handleBlur('name')"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<wd-input
label="联系方式"
prop="phoneNumber"
label-width="100px"
clearable
@blur="handleBlur('phoneNumber')"
v-model="model.phoneNumber"
placeholder="联系方式"
:rules="[{ required: true, message: '请填写联系方式' }]"
/>
</wd-cell-group>
</wd-form>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
```
```typescript [typescript]
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { reactive, ref } from 'vue'
const model = reactive<{
name: string
phoneNumber: string
}>({
name: '',
phoneNumber: ''
})
const { success: showSuccess } = useToast()
const form = ref()
function handleBlur(prop: string) {
form.value.validate(prop)
}
function handleSubmit() {
form.value
.validate()
.then(({ valid }) => {
if (valid) {
showSuccess('校验通过')
}
})
.catch((error) => {
console.log(error, 'error')
})
}
</script>
```
```css [css]
.footer {
padding: 12px;
}
```
:::
## 复杂表单
结合`Input 输入框``Textarea 输入框``Picker 选择器``Calendar 日历选择器``ColPicker 多列选择器``SelectPicker 单复选选择器``Cell 单元格``DatetimePicker 日期时间选择器`实现一个复杂表单。
::: details 查看复杂表单示例
::: code-group
```html [vue]
<view>
<wd-message-box /> <wd-message-box />
<wd-toast /> <wd-toast />
<form @submit="formSubmit"> <wd-form ref="form" :model="model" :rules="rules">
<wd-cell-group custom-class="group" title="基础信息" border> <wd-cell-group custom-class="group" title="基础信息" border>
<wd-input <wd-input
label="优惠券名称" label="优惠券名称"
label-width="100px" label-width="100px"
:maxlength="20" :maxlength="20"
show-word-limit show-word-limit
name="couponName" prop="couponName"
required required
suffix-icon="warn-bold" suffix-icon="warn-bold"
clearable clearable
v-model="couponName" v-model="model.couponName"
placeholder="请输入优惠券名称" placeholder="请输入优惠券名称"
@change="handleCouponName"
@clicksuffixicon="handleIconClick" @clicksuffixicon="handleIconClick"
/> />
<wd-select-picker <wd-select-picker
label="推广平台" label="推广平台"
label-width="100px" label-width="100px"
name="platform" prop="platform"
v-model="platform" v-model="model.platform"
:columns="platformList" :columns="platformList"
placeholder="请选择推广平台" placeholder="请选择推广平台"
@confirm="handlePlatform"
/> />
<wd-picker label="优惠方式" label-width="100px" name="promotion" align-right v-model="promotion" :columns="promotionlist" /> <wd-picker
<wd-cell title="券面额" required title-width="100px" custom-value-class="cell-left"> label="优惠方式"
placeholder="请选择优惠方式"
label-width="100px"
prop="promotion"
v-model="model.promotion"
:columns="promotionlist"
/>
<wd-cell prop="threshold" title="券面额" required title-width="100px" custom-value-class="cell-left">
<view style="text-align: left"> <view style="text-align: left">
<view class="inline-txt" style="margin-left: 0"></view> <view class="inline-txt" style="margin-left: 0"></view>
<wd-input <wd-input
no-border no-border
custom-style="display: inline-block; width: 70px; vertical-align: middle" custom-style="display: inline-block; width: 70px; vertical-align: middle"
placeholder="请输入金额" placeholder="请输入金额"
v-model="threshold" v-model="model.threshold"
name="threshold"
@change="handleThreshold"
/> />
<view class="inline-txt"></view> <view class="inline-txt"></view>
<wd-input <wd-input
no-border no-border
custom-style="display: inline-block; width: 70px; vertical-align: middle" custom-style="display: inline-block; width: 70px; vertical-align: middle"
placeholder="请输入金额" placeholder="请输入金额"
v-model="price" v-model="model.price"
name="price"
@change="handlePrice"
/> />
</view> </view>
</wd-cell> </wd-cell>
</wd-cell-group> </wd-cell-group>
<wd-cell-group custom-class="group" title="时间和地址" border> <wd-cell-group custom-class="group" title="时间和地址" border>
<wd-datetime-picker label="时间" label-width="100px" name="date" v-model="date" @confirm="handleDate" /> <wd-datetime-picker label="时间" label-width="100px" placeholder="请选择时间" prop="time" v-model="model.time" />
<wd-calendar label="日期" label-width="100px" placeholder="请选择日期" prop="date" v-model="model.date" />
<wd-col-picker <wd-col-picker
label="地址" label="地址"
placeholder="请选择地址"
label-width="100px" label-width="100px"
name="address" prop="address"
v-model="address" v-model="model.address"
:columns="area" :columns="area"
:column-change="areaChange" :column-change="areaChange"
@confirm="handleAddress"
/> />
</wd-cell-group> </wd-cell-group>
<wd-cell-group custom-class="group" title="其他信息" border> <wd-cell-group custom-class="group" title="其他信息" border>
<wd-input <wd-textarea
label="活动细则" label="活动细则"
label-width="100px" label-width="100px"
type="textarea" type="textarea"
v-model="content" v-model="model.content"
:maxlength="300" :maxlength="300"
show-word-limit show-word-limit
placeholder="请输入活动细则信息" placeholder="请输入活动细则信息"
clearable clearable
name="content" prop="content"
@change="handleContent"
/> />
<wd-cell title="发货数量" center> <wd-cell title="发货数量" title-width="100px" prop="count">
<wd-input-number v-model="count" name="count" @change="handleCount" /> <view style="text-align: left">
<wd-input-number v-model="model.count" />
</view>
</wd-cell> </wd-cell>
<wd-cell title="这里显示的是多文字标题包含非常的文字" title-width="240px" center> <wd-cell title="开启折扣" title-width="100px" prop="switchVal" center>
<wd-switch v-model="switchVal" name="switchVal" @change="handleSwitch" /> <view style="text-align: left">
<wd-switch v-model="model.switchVal" />
</view>
</wd-cell>
<wd-input label="卡号" label-width="100px" prop="cardId" suffix-icon="camera" placeholder="请输入卡号" clearable v-model="model.cardId" />
<wd-input label="手机号" label-width="100px" prop="phone" placeholder="请输入手机号" clearable v-model="model.phone" />
<wd-cell title="活动图片" title-width="100px" prop="fileList">
<wd-upload :file-list="model.fileList" action="https://ftf.jd.com/api/uploadImg" @change="handleFileChange"></wd-upload>
</wd-cell> </wd-cell>
<wd-input
label="卡号"
label-width="100px"
name="cardId"
suffix-icon="camera"
placeholder="请输入卡号"
clearable
v-model="cardId"
@change="handleCardId"
/>
<wd-input label="手机号" label-width="100px" name="phone" placeholder="请输入手机号" clearable v-model="phone" @change="handlePhone" />
</wd-cell-group> </wd-cell-group>
<view class="tip"> <view class="tip">
<wd-checkbox v-model="read" name="read" @change="handleRead" custom-label-class="label-class"> <wd-checkbox v-model="model.read" prop="read" custom-label-class="label-class">
已阅读并同意 已阅读并同意
<text style="color: #4d80f0">《借款额度合同及相关授权》</text> <text style="color: #4d80f0">《借款额度合同及相关授权》</text>
</wd-checkbox> </wd-checkbox>
</view> </view>
<view class="footer"> <view class="footer">
<button class="wd-button is-primary is-block is-round is-large" form-type="submit">提交</button> <wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
</view>
</wd-form>
</view> </view>
</form>
``` ```
> 自定义按钮组件的 form-type 无法触发小程序官方form组件的submit事件微信要求小程序基础库 2.10.3 才支持,京东小程序的则暂时不支持,因此需要用小程序自带的官方 button 组件,样式上可以引入 wot-design 中 button 的样式文件,使用 `wd-button` , `is-primary` , `is-suck` , `is-block`, `is-plain` , `is-disabled` 等类名进行组合使用来展示 wot-design 组件库的按钮样式。 ```typescript [typescript]
<script lang="ts" setup>
index.js 文件代码:
```typescript
import { useMessage } from '@/uni_modules/wot-design-uni/components/wd-message-box'
import { useToast } from '@/uni_modules/wot-design-uni' import { useToast } from '@/uni_modules/wot-design-uni'
import { isArray } from '@/uni_modules/wot-design-uni/components/common/util'
import { FormRules } from '@/uni_modules/wot-design-uni/components/wd-form/types'
import { areaData } from '@/utils/area' import { areaData } from '@/utils/area'
import { ref } from 'vue' import { reactive, ref } from 'vue'
const model = reactive<{
couponName: string
platform: any[]
promotion: string
threshold: string
price: string
time: number | string
date: null | number
address: string[]
count: number
content: string
switchVal: boolean
cardId: string
phone: string
read: boolean
fileList: Record<string, string>[]
}>({
couponName: '',
platform: [],
promotion: '',
threshold: '',
price: '',
date: null,
time: '',
address: [],
count: 1,
content: '',
switchVal: true,
cardId: '',
phone: '',
read: false,
fileList: []
})
const rules: FormRules = {
couponName: [
{
required: true,
pattern: /\d{6}/,
message: '优惠券名称6个字以上',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请输入优惠券名称')
}
}
}
],
content: [
{
required: true,
message: '请输入活动细则信息',
validator: (value) => {
if (value && value.length > 2) {
return Promise.resolve()
} else {
return Promise.reject('请输入活动细则信息')
}
}
}
],
threshold: [
{
required: true,
message: '请输入满减金额',
validator: (value) => {
if (value && model.price) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
],
platform: [
{
required: true,
message: '请选择推广平台',
validator: (value) => {
if (value && isArray(value) && value.length) {
return Promise.resolve()
} else {
return Promise.reject('请选择推广平台')
}
}
}
],
promotion: [
{
required: true,
message: '请选择推广平台',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请选择推广平台')
}
}
}
],
time: [
{
required: true,
message: '请选择时间',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请选择时间')
}
}
}
],
date: [
{
required: true,
message: '请选择日期',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
],
address: [
{
required: true,
message: '请选择地址',
validator: (value) => {
if (isArray(value) && value.length) {
return Promise.resolve()
} else {
return Promise.reject('请选择地址')
}
}
}
],
count: [
{
required: true,
message: '发货数量需要大于1',
validator: (value) => {
if (Number(value) > 1) {
return Promise.resolve()
} else {
return Promise.reject('发货数量需要大于1')
}
}
}
],
cardId: [
{
required: true,
message: '请输入卡号',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请输入卡号')
}
}
}
],
phone: [
{
required: true,
message: '请输入手机号',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
],
fileList: [
{
required: true,
message: '请选择活动图片',
validator: (value) => {
if (isArray(value) && value.length) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
]
}
const couponName = ref<string>('')
const couponNameErr = ref<boolean>(false)
const platform = ref<any>([])
const platformList = ref<any>([ const platformList = ref<any>([
{ {
value: '1', value: '1',
@ -166,7 +752,6 @@ const platformList = ref<any>([
label: '京东极速版' label: '京东极速版'
} }
]) ])
const promotion = ref<string>('1')
const promotionlist = ref<any[]>([ const promotionlist = ref<any[]>([
{ {
value: '1', value: '1',
@ -177,12 +762,6 @@ const promotionlist = ref<any[]>([
label: '无门槛' label: '无门槛'
} }
]) ])
const threshold = ref<string>('')
const price = ref<string>('')
const date = ref<number>(Date.now())
const address = ref<any[]>([])
const count = ref<number>(1)
const area = ref<any[]>([ const area = ref<any[]>([
Object.keys(areaData[86]).map((key) => { Object.keys(areaData[86]).map((key) => {
@ -206,69 +785,32 @@ const areaChange = ({ selectedItem, resolve, finish }) => {
finish() finish()
} }
} }
const content = ref<string>('')
const coun = ref<number>(1)
const read = ref<boolean>(false)
const switchVal = ref<boolean>(true)
const cardId = ref<string>('')
const phone = ref<string>('')
const toast = useToast() const toast = useToast()
const messageBox = useMessage() const form = ref()
function handleCouponName({ value }) { function handleFileChange({ fileList }) {
console.log(value) model.fileList = fileList
}
couponNameErr.value = false function handleSubmit() {
} form.value
function handlePlatform({ value }) { .validate()
console.log(value) .then(({ valid, errors }) => {
} console.log(valid)
function handleThreshold({ value }) { console.log(errors)
console.log(value) })
} .catch((error) => {
function handlePrice({ value }) { console.log(error, 'error')
console.log(value) })
}
function handleAddress({ value }) {
console.log(value)
}
function handleContent({ value }) {
console.log(value)
}
function handleCount({ value }) {
console.log(value)
}
function handleSwitch({ value }) {
console.log(value)
}
function handleRead({ value }) {
read.value = value
}
function handleCardId({ value }) {
console.log(value)
}
function handlePhone({ value }) {
console.log(value)
}
function formSubmit() {
if (!couponName.value) {
toast.error('请填写优惠券名称')
return
}
messageBox.alert('提交成功')
} }
function handleIconClick() { function handleIconClick() {
toast.info('优惠券提示信息') toast.info('优惠券提示信息')
} }
function handleDate({ value }) { </script>
console.log(value)
}
``` ```
css 文件代码: ```css [css]
```scss
.inline-txt { .inline-txt {
display: inline-block; display: inline-block;
font-size: 14px; font-size: 14px;
@ -292,3 +834,34 @@ css 文件代码:
font-size: 12px !important; font-size: 12px !important;
} }
``` ```
:::
## Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
| ----- | ------------ | --------------------- | ------ | ------ | -------- |
| model | 表单数据对象 | `Record<string, any>` | - | - | 0.2.0 |
| rules | 表单验证规则 | `FormRules` | - | - | 0.2.0 |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --------- | ------------------------------------------------------- | ------------------------------------- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## Events
| 事件名称 | 说明 | 参数 | 最低版本 |
| -------- | ------------------------------------------------------------------------------ | --------------- | -------- |
| validate | 验证表单,支持传入一个 prop 来验证单个表单项,不传入 prop 时,会验证所有表单项 | `prop?: string` | 0.2.0 |
| reset | 重置校验结果 | - | 0.2.0 |
## 外部样式类
| 类名 | 说明 | 最低版本 |
| ------------ | ---------- | -------- |
| custom-class | 根节点样式 | 0.2.0 |

View File

@ -69,36 +69,6 @@ function handleChange(event) {
<wd-input v-model="value" :maxlength="20" show-word-limit @change="handleChange"/> <wd-input v-model="value" :maxlength="20" show-word-limit @change="handleChange"/>
``` ```
## 文本域
设置 `type` 为 'textarea`。
:::warning
`wd-input``type` 为 'textarea' ,并嵌入 `wd-message-box``wd-popup``wd-action-sheet` 这类弹层组件时textarea 的 placeholder 样式会失效,需要手动给 `wd-message-box``wd-popup``wd-action-sheet` 组件设置 `:lazy-render="false"` 属性textarea 原生组件在这块实现有些问题,对于页面非立即渲染的 textarea 无法成功设置 placeholder 样式
:::
```html
<wd-input type="textarea" v-model="value" placeholder="请输入..." @change="handleChange"/>
```
设置清空,字数限制。
```html
<wd-input
type="textarea"
v-model="value"
placeholder="请输入..."
:maxlength="120"
clearable
show-word-limit
@change="handleChange"/>
```
也可以设置`auto-height`使高度自增加。
```html
<wd-input v-model="value" auto-height @change="handleChange" clearable/>
```
## 设置label标题 ## 设置label标题
设置 `label` 标题,可以和 `cell-group` 组合使用,形成 `cell` 展示类型。可以通过 `label-width` 设置标题宽度,默认为 '33%'。 设置 `label` 标题,可以和 `cell-group` 组合使用,形成 `cell` 展示类型。可以通过 `label-width` 设置标题宽度,默认为 '33%'。
@ -143,7 +113,7 @@ function handleChange(event) {
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
|-----|------|-----|-------|-------|---------| |-----|------|-----|-------|-------|---------|
| type | 类型 | string | text / textarea / number / digit / idcard | text | - | | type | 类型 | string | text / number / digit / idcard | text | - |
| v-model | 绑定值 | string / number | - | - | - | | v-model | 绑定值 | string / number | - | - | - |
| placeholder | 占位文本 | string | - | 请输入... | - | | placeholder | 占位文本 | string | - | 请输入... | - |
| clearable | 显示清空按钮 | boolean | - | false | - | | clearable | 显示清空按钮 | boolean | - | false | - |
@ -157,17 +127,14 @@ function handleChange(event) {
| confirm-type | 设置键盘右下角按钮的文字仅在type='text'时生效 | string | done / go / next / search / send | done | - | | confirm-type | 设置键盘右下角按钮的文字仅在type='text'时生效 | string | done / go / next / search / send | done | - |
| confirm-hold | 点击键盘右下角按钮时是否保持键盘不收起 | Boolean | - | false | - | | confirm-hold | 点击键盘右下角按钮时是否保持键盘不收起 | Boolean | - | false | - |
| always-embed | 微信小程序原生属性,强制 input 处于同层状态,默认 focus 时 input 会切到非同层状态 (仅在 iOS 下生效) | Boolean | - | false | - | | always-embed | 微信小程序原生属性,强制 input 处于同层状态,默认 focus 时 input 会切到非同层状态 (仅在 iOS 下生效) | Boolean | - | false | - |
| placeholderStyle | 原生属,指定 placeholder 的样式目前仅支持color,font-size和font-weight | string | - | - | - | | placeholderStyle | 原生属,指定 placeholder 的样式目前仅支持color,font-size和font-weight | string | - | - | - |
| placeholderClass | textarea指定 placeholder 的样式类 | string | - | textarea-placeholder | - | | placeholderClass | 原生属性,指定 placeholder 的样式类 | string | - | - | - |
| focus | 原生属性,获取焦点 | boolean | - | false | - | | focus | 原生属性,获取焦点 | boolean | - | false | - |
| cursorSpacing | 原生属性指定光标与键盘的距离。取textarea距离底部的距离和cursor-spacing指定的距离的最小值作为光标与键盘的距离 | number | - | 0 | - | | cursorSpacing | 原生属性,指定光标与键盘的距离。取 input 距离底部的距离和cursor-spacing指定的距离的最小值作为光标与键盘的距离 | number | - | 0 | - |
| fixed | textarea原生属性如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true | boolean | - | false | - |
| cursor | 原生属性指定focus时的光标位置 | number | - | -1 | - | | cursor | 原生属性指定focus时的光标位置 | number | - | -1 | - |
| showConfirmBar | 原生属性,是否显示键盘上方带有”完成“按钮那一栏 | boolean | - | true | - |
| selectionStart | 原生属性光标起始位置自动聚集时有效需与selection-end搭配使用 | number | - | -1 | - | | selectionStart | 原生属性光标起始位置自动聚集时有效需与selection-end搭配使用 | number | - | -1 | - |
| selectionEnd | 原生属性光标结束位置自动聚集时有效需与selection-start搭配使用 | number | - | -1 | - | | selectionEnd | 原生属性光标结束位置自动聚集时有效需与selection-start搭配使用 | number | - | -1 | - |
| adjustPosition | 原生属性,键盘弹起时,是否自动上推页面 | boolean | - | true | - | | adjustPosition | 原生属性,键盘弹起时,是否自动上推页面 | boolean | - | true | - |
| autoHeight | textarea原生属性textarea 行数自适应从1行开始显示 | boolean | - | false | - |
| label | 设置左侧标题 | string | - | - | - | | label | 设置左侧标题 | string | - | - | - |
| size | 设置输入框大小 | string | - | - | - | | size | 设置输入框大小 | string | - | - | - |
| error | 设置输入框错误状态,错误状态时为红色 | boolean | - | false | - | | error | 设置输入框错误状态,错误状态时为红色 | boolean | - | false | - |
@ -177,8 +144,20 @@ function handleChange(event) {
| use-suffix-slot | 使用 后置图标 插槽 | boolean | - | false | - | | use-suffix-slot | 使用 后置图标 插槽 | boolean | - | false | - |
| use-prefix-slot | 使用 前置图标 插槽 | boolean | - | false | - | | use-prefix-slot | 使用 前置图标 插槽 | boolean | - | false | - |
| required | cell 类型下必填样式 | boolean | - | false | - | | required | cell 类型下必填样式 | boolean | - | false | - |
| name | form 表单中的字段名 | string | - | - | - |
| no-border | 非 cell 类型下是否隐藏下划线 | boolean | - | false | - | - | | no-border | 非 cell 类型下是否隐藏下划线 | boolean | - | false | - | - |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## Events ## Events
@ -186,10 +165,9 @@ function handleChange(event) {
|---------|-----|-----|---------| |---------|-----|-----|---------|
| input | 监听输入框input事件 | ` {value, cursor, keyCode}` | - | | input | 监听输入框input事件 | ` {value, cursor, keyCode}` | - |
| focus | 监听输入框focus事件 | ` { value, height }`, height 为键盘高度 | - | | focus | 监听输入框focus事件 | ` { value, height }`, height 为键盘高度 | - |
| blur | 监听输入框blur事件 | ` { value, cursor }`仅在type="textarea"时存在cursor | - | | blur | 监听输入框blur事件 | ` { value }` | - |
| change | 监听输入框修改事件 | ` { value }` | - | | change | 监听输入框修改事件 | ` { value }` | - |
| clear | 监听输入框清空按钮事件 | - | - | | clear | 监听输入框清空按钮事件 | - | - |
| linechange | 监听输入框行数变化(仅限textarea) | ` { height: 0, heightRpx: 0, lineCount: 0 }` | - |
| confirm | 点击完成时, 触发 confirm 事件 | ` { value }` | - | | confirm | 点击完成时, 触发 confirm 事件 | ` { value }` | - |
| keyboardheightchange | 键盘高度发生变化的时候触发此事件 | ` { height, duration }` | - | | keyboardheightchange | 键盘高度发生变化的时候触发此事件 | ` { height, duration }` | - |
| clickprefixicon | 点击前置图标时触发 | - | - | | clickprefixicon | 点击前置图标时触发 | - | - |
@ -211,7 +189,5 @@ function handleChange(event) {
| 类名 | 说明 | 最低版本 | | 类名 | 说明 | 最低版本 |
|-----|------|--------| |-----|------|--------|
| custom-class | 根节点样式 | - | | custom-class | 根节点样式 | - |
| custom-textarea-container-class | textarea 容器外部自定义样式 | - |
| custom-textarea-class | textarea 外部自定义样式 | - |
| custom-input-class | input 外部自定义样式 | - | | custom-input-class | input 外部自定义样式 | - |
| custom-label-class | label 外部自定义样式 | - | | custom-label-class | label 外部自定义样式 | - |

View File

@ -261,6 +261,17 @@ function handleConfirm({ value }) {
| z-index | 弹窗层级 | number | - | 15 | - | | z-index | 弹窗层级 | number | - | 15 | - |
| safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - | | safe-area-inset-bottom | 弹出面板是否设置底部安全距离iphone X 类型的机型) | boolean | - | true | - |
| ellipsis | 是否超出隐藏 | boolean | - | false | - | | ellipsis | 是否超出隐藏 | boolean | - | false | - |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## Events ## Events

View File

@ -342,6 +342,17 @@ function handleConfirm({ value, selectedItems }) {
| filter-placeholder | 搜索框占位符 | string | - | 搜索 | - | | filter-placeholder | 搜索框占位符 | string | - | 搜索 | - |
| ellipsis | 是否超出隐藏 | boolean | - | false | - | | ellipsis | 是否超出隐藏 | boolean | - | false | - |
| scroll-into-view | 重新打开是否滚动到选中项 | boolean | - | true | 0.1.34 | | scroll-into-view | 重新打开是否滚动到选中项 | boolean | - | true | 0.1.34 |
| prop | 表单域 `model` 字段名,在使用表单校验功能的情况下,该属性是必填的 | string | - | - | - |
| rules | 表单验证规则 | `FormItemRule []` | - | `[]` | - |
### FormItemRule 数据结构
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| required | 是否为必选字段 | `boolean` |
| message | 错误提示文案 | `string` |
| validator | 通过函数进行校验,可以返回一个 `Promise` 来进行异步校验 | `(value, rule) => boolean \| Promise` |
| pattern | 通过正则表达式进行校验,正则无法匹配表示校验不通过 | `RegExp` |
## 选项数据结构 ## 选项数据结构

View File

@ -1,14 +1,6 @@
<!--
* @Author: weisheng
* @Date: 2023-08-07 18:49:03
* @LastEditTime: 2023-10-17 15:14:09
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\components\page-wraper\page-wraper.vue
* 记得注释
-->
<template> <template>
<wd-config-provider :theme="theme"> <wd-config-provider :theme="theme">
<wd-toast />
<view class="page-wraper"> <view class="page-wraper">
<wd-cell title="切换暗黑" title-width="240px" center v-if="showDarkMode"> <wd-cell title="切换暗黑" title-width="240px" center v-if="showDarkMode">
<wd-switch v-model="isDark" /> <wd-switch v-model="isDark" />

View File

@ -160,6 +160,16 @@
"navigationBarTitleText": "Input 输入框" "navigationBarTitleText": "Input 输入框"
} }
}, },
{
"path": "pages/textarea/Index",
"name": "textarea",
"style": {
"mp-alipay": {
"allowsBounceVertical": "NO"
},
"navigationBarTitleText": "Textarea 文本域"
}
},
{ {
"path": "pages/messageBox/Index", "path": "pages/messageBox/Index",
"name": "messageBox", "name": "messageBox",
@ -528,7 +538,37 @@
"mp-alipay": { "mp-alipay": {
"allowsBounceVertical": "NO" "allowsBounceVertical": "NO"
}, },
"navigationBarTitleText": "Form 表单组合" "navigationBarTitleText": "Form 表单"
}
},
{
"path": "pages/form/demo1",
"name": "formDemo1",
"style": {
"mp-alipay": {
"allowsBounceVertical": "NO"
},
"navigationBarTitleText": "复杂表单"
}
},
{
"path": "pages/form/demo2",
"name": "formDemo2",
"style": {
"mp-alipay": {
"allowsBounceVertical": "NO"
},
"navigationBarTitleText": "失焦校验"
}
},
{
"path": "pages/form/demo3",
"name": "formDemo3",
"style": {
"mp-alipay": {
"allowsBounceVertical": "NO"
},
"navigationBarTitleText": "复杂表单"
} }
}, },
{ {

View File

@ -128,10 +128,10 @@ function showToast() {
<style lang="scss" scoped> <style lang="scss" scoped>
.cell-icon { .cell-icon {
display: block; display: block;
box-sizing: border-box;
width: 16px; width: 16px;
height: 16px; height: 16px;
margin-top: 2px; margin: 4px 4px 4px 0;
margin-right: 15px;
background: url('https://img10.360buyimg.com/jmadvertisement/jfs/t1/71075/7/3762/1820/5d1f26d1E0d600b9e/a264c901943080ac.png') no-repeat; background: url('https://img10.360buyimg.com/jmadvertisement/jfs/t1/71075/7/3762/1820/5d1f26d1E0d600b9e/a264c901943080ac.png') no-repeat;
background-size: cover; background-size: cover;
} }

View File

@ -1,279 +1,211 @@
<template> <template>
<view>
<page-wraper> <page-wraper>
<wd-message-box /> <demo-block title="基础表单" transparent>
<wd-toast /> <wd-form ref="form1" :model="model1">
<form @submit="formSubmit"> <wd-cell-group border>
<wd-cell-group custom-class="group" title="基础信息" border>
<wd-input <wd-input
label="优惠券名称" label="用户名"
label-width="100px" label-width="100px"
:maxlength="20" prop="value1"
show-word-limit
name="couponName"
required
suffix-icon="warn-bold"
clearable clearable
v-model="couponName" v-model="model1.value1"
placeholder="请输入优惠券名称" placeholder="请输入用户名"
@change="handleCouponName" :rules="[{ required: true, message: '请填写用户名' }]"
@clicksuffixicon="handleIconClick"
/> />
<wd-select-picker
label="推广平台"
label-width="100px"
name="platform"
v-model="platform"
:columns="platformList"
placeholder="请选择推广平台"
@confirm="handlePlatform"
/>
<wd-picker label="优惠方式" label-width="100px" name="promotion" align-right v-model="promotion" :columns="promotionlist" />
<wd-cell title="券面额" required title-width="100px" custom-value-class="cell-left">
<view style="text-align: left">
<view class="inline-txt" style="margin-left: 0"></view>
<wd-input <wd-input
no-border label="密码"
custom-style="display: inline-block; width: 70px; vertical-align: middle"
placeholder="请输入金额"
v-model="threshold"
name="threshold"
@change="handleThreshold"
/>
<view class="inline-txt"></view>
<wd-input
no-border
custom-style="display: inline-block; width: 70px; vertical-align: middle"
placeholder="请输入金额"
v-model="price"
name="price"
@change="handlePrice"
/>
</view>
</wd-cell>
</wd-cell-group>
<wd-cell-group custom-class="group" title="时间和地址" border>
<wd-datetime-picker label="时间" label-width="100px" name="date" v-model="date" @confirm="handleDate" />
<wd-col-picker
label="地址"
label-width="100px" label-width="100px"
name="address" prop="value2"
v-model="address" show-password
:columns="area" clearable
:column-change="areaChange" v-model="model1.value2"
@confirm="handleAddress" placeholder="请输入密码"
:rules="[{ required: true, message: '请填写密码' }]"
/> />
</wd-cell-group> </wd-cell-group>
<wd-cell-group custom-class="group" title="其他信息" border>
<wd-input
label="活动细则"
label-width="100px"
type="textarea"
v-model="content"
:maxlength="300"
show-word-limit
placeholder="请输入活动细则信息"
clearable
name="content"
@change="handleContent"
/>
<wd-cell title="发货数量" center>
<wd-input-number v-model="count" name="count" @change="handleCount" />
</wd-cell>
<wd-cell title="这里显示的是多文字标题包含非常的文字" title-width="240px" center>
<wd-switch v-model="switchVal" name="switchVal" @change="handleSwitch" />
</wd-cell>
<wd-input
label="卡号"
label-width="100px"
name="cardId"
suffix-icon="camera"
placeholder="请输入卡号"
clearable
v-model="cardId"
@change="handleCardId"
/>
<wd-input label="手机号" label-width="100px" name="phone" placeholder="请输入手机号" clearable v-model="phone" @change="handlePhone" />
</wd-cell-group>
<view class="tip">
<wd-checkbox v-model="read" name="read" @change="handleRead" custom-label-class="label-class">
已阅读并同意
<text style="color: #4d80f0">借款额度合同及相关授权</text>
</wd-checkbox>
</view>
<view class="footer"> <view class="footer">
<button class="wd-button is-primary is-block is-round is-large" form-type="submit">提交</button> <wd-button type="primary" size="large" @click="handleSubmit1" block>提交</wd-button>
</view> </view>
</form> </wd-form>
</demo-block>
<demo-block title="校验规则" transparent>
<wd-form ref="form2" :model="model2">
<wd-cell-group border>
<wd-input
label="校验"
label-width="100px"
prop="value1"
clearable
v-model="model2.value1"
placeholder="正则校验"
:rules="[{ required: false, pattern: /\d{6}/, message: '请输入6位字符' }]"
/>
<wd-input
label="校验"
label-width="100px"
prop="value2"
clearable
v-model="model2.value2"
placeholder="函数校验"
:rules="[
{
required: false,
validator: validatorMessage,
message: '请输入正确的手机号'
}
]"
/>
<wd-input
label="校验"
label-width="100px"
prop="value3"
clearable
v-model="model2.value3"
placeholder="校验函数返回错误提示"
:rules="[
{
required: false,
message: '请输入内容',
validator: validator
}
]"
/>
<wd-input
label="校验"
label-width="100px"
prop="value4"
clearable
v-model="model2.value4"
placeholder="异步函数校验"
:rules="[{ required: false, validator: asyncValidator, message: '请输入1234' }]"
/>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" @click="handleSubmit2" block>提交</wd-button>
</view>
</wd-form>
</demo-block>
<demo-block title="动态表单" transparent>
<view class="demo-button">
<wd-button @click="handleClick1" :round="false" block size="large">动态表单</wd-button>
</view>
</demo-block>
<demo-block title="失焦校验" transparent>
<view class="demo-button">
<wd-button @click="handleClick2" :round="false" block size="large">失焦校验</wd-button>
</view>
</demo-block>
<demo-block title="复杂表单" transparent>
<view class="demo-button">
<wd-button @click="handleClick3" :round="false" block size="large">复杂表单</wd-button>
</view>
</demo-block>
</page-wraper> </page-wraper>
</view>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useMessage } from '@/uni_modules/wot-design-uni/components/wd-message-box'
import { useToast } from '@/uni_modules/wot-design-uni' import { useToast } from '@/uni_modules/wot-design-uni'
import { areaData } from '@/utils/area' import { reactive, ref } from 'vue'
import { ref } from 'vue'
const couponName = ref<string>('') const model1 = reactive<{
const couponNameErr = ref<boolean>(false) value1: string
const platform = ref<any>([]) value2: string
const platformList = ref<any>([ }>({
{ value1: '',
value: '1', value2: ''
label: '京东'
},
{
value: '2',
label: '开普勒'
},
{
value: '3',
label: '手Q'
},
{
value: '4',
label: '微信'
},
{
value: '5',
label: '1号店'
},
{
value: '6',
label: '十元街'
},
{
value: '7',
label: '京东极速版'
}
])
const promotion = ref<string>('1')
const promotionlist = ref<any[]>([
{
value: '1',
label: '满减'
},
{
value: '2',
label: '无门槛'
}
])
const threshold = ref<string>('')
const price = ref<string>('')
const date = ref<number>(Date.now())
const address = ref<any[]>([])
const count = ref<number>(1)
const area = ref<any[]>([
Object.keys(areaData[86]).map((key) => {
return {
value: key,
label: areaData[86][key]
}
}) })
])
const areaChange = ({ selectedItem, resolve, finish }) => { const model2 = reactive<{
if (areaData[selectedItem.value]) { value1: string
resolve( value2: string
Object.keys(areaData[selectedItem.value]).map((key) => { value3: string
return { value4: string
value: key, }>({
label: areaData[selectedItem.value][key] value1: '',
} value2: '',
value3: '',
value4: ''
}) })
)
const { success: showSuccess, loading: showLoading, close: closeToast } = useToast()
const form1 = ref()
const form2 = ref()
const validatorMessage = (val) => {
return /1\d{10}/.test(val)
}
const validator = (val) => {
if (String(val).length >= 4) {
return Promise.resolve()
} else { } else {
finish() return Promise.reject('长度不得小于4')
} }
} }
const content = ref<string>('')
const coun = ref<number>(1)
const read = ref<boolean>(false)
const switchVal = ref<boolean>(true)
const cardId = ref<string>('')
const phone = ref<string>('')
const toast = useToast() // Promise
const messageBox = useMessage() const asyncValidator = (val) =>
new Promise((resolve) => {
showLoading('验证中...')
function handleCouponName({ value }) { setTimeout(() => {
console.log(value) closeToast()
resolve(val === '1234')
}, 1000)
})
couponNameErr.value = false function handleSubmit1() {
form1.value
.validate()
.then(({ valid, errors }) => {
if (valid) {
showSuccess({
msg: '提交成功'
})
} }
function handlePlatform({ value }) { })
console.log(value) .catch((error) => {
console.log(error, 'error')
})
} }
function handleThreshold({ value }) {
console.log(value)
}
function handlePrice({ value }) {
console.log(value)
}
function handleAddress({ value }) {
console.log(value)
}
function handleContent({ value }) {
console.log(value)
}
function handleCount({ value }) {
console.log(value)
}
function handleSwitch({ value }) {
console.log(value)
}
function handleRead({ value }) {
read.value = value
}
function handleCardId({ value }) {
console.log(value)
}
function handlePhone({ value }) {
console.log(value)
}
function formSubmit(event) {
console.log(event)
if (!couponName.value) { function handleSubmit2() {
toast.error('请填写优惠券名称') form2.value
return .validate()
.then(({ valid, errors }) => {
if (valid) {
showSuccess({
msg: '提交成功'
})
} }
messageBox.alert('提交成功') })
.catch((error) => {
console.log(error, 'error')
})
} }
function handleIconClick() {
toast.info('优惠券提示信息') function handleClick1() {
uni.navigateTo({ url: '/pages/form/demo1' })
} }
function handleDate({ value }) {
console.log(value) function handleClick2() {
uni.navigateTo({ url: '/pages/form/demo2' })
}
function handleClick3() {
uni.navigateTo({ url: '/pages/form/demo3' })
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.wot-theme-dark { .demo-button {
.inline-txt { width: 100%;
color: $-dark-color3; box-sizing: border-box;
} padding: 0 24rpx;
}
.inline-txt {
display: inline-block;
font-size: 14px;
margin: 0 8px;
color: rgba(0, 0, 0, 0.45);
vertical-align: middle;
}
:deep(.group) {
margin-top: 12px;
}
.tip {
margin: 10px 15px 21px;
color: #999;
font-size: 12px;
} }
.footer { .footer {
padding: 0 25px 21px; padding: 16px;
}
:deep(.label-class) {
color: #999 !important;
font-size: 12px !important;
} }
</style> </style>

95
src/pages/form/demo1.vue Normal file
View File

@ -0,0 +1,95 @@
<template>
<page-wraper>
<wd-form ref="form" :model="model">
<wd-cell-group border>
<wd-input
label="用户名"
label-width="100px"
prop="name"
clearable
v-model="model.name"
placeholder="请输入用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<wd-input
v-for="(item, index) in model.phoneNumbers"
:key="item.key"
:label="'联系方式' + index"
:prop="'phoneNumbers.' + index + '.value'"
label-width="100px"
clearable
v-model="item.value"
placeholder="联系方式"
:rules="[{ required: true, message: '请填写联系方式' + index }]"
/>
<wd-cell title-width="0px">
<view class="footer">
<wd-button size="small" type="info" plain @click="addPhone">添加</wd-button>
<wd-button size="small" type="info" plain @click="removePhone">删除</wd-button>
<wd-button size="small" type="info" plain @click="reset">重置</wd-button>
<wd-button type="primary" size="small" @click="submit">提交</wd-button>
</view>
</wd-cell>
</wd-cell-group>
</wd-form>
</page-wraper>
</template>
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { reactive, ref } from 'vue'
interface PhoneItem {
key: number
value: string
}
const model = reactive<{
name: string
phoneNumbers: PhoneItem[]
}>({
name: '',
phoneNumbers: [
{
key: Date.now(),
value: ''
}
]
})
const { success: showSuccess } = useToast()
const form = ref()
const removePhone = () => {
model.phoneNumbers.splice(model.phoneNumbers.length - 1, 1)
}
const addPhone = () => {
model.phoneNumbers.push({
key: Date.now(),
value: ''
})
}
const reset = () => {
form.value.reset()
}
const submit = () => {
form.value.validate().then(({ valid, errors }) => {
if (valid) {
showSuccess('校验通过')
}
})
}
</script>
<style lang="scss" scoped>
.footer {
text-align: left;
:deep(.wd-button) {
&:not(:last-child) {
margin-right: 12px;
}
}
}
</style>

69
src/pages/form/demo2.vue Normal file
View File

@ -0,0 +1,69 @@
<template>
<page-wraper>
<wd-form ref="form" :model="model">
<wd-cell-group border>
<wd-input
label="用户名"
label-width="100px"
prop="name"
clearable
v-model="model.name"
placeholder="请输入用户名"
@blur="handleBlur('name')"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<wd-input
label="联系方式"
prop="phoneNumber"
label-width="100px"
clearable
@blur="handleBlur('phoneNumber')"
v-model="model.phoneNumber"
placeholder="联系方式"
:rules="[{ required: true, message: '请填写联系方式' }]"
/>
</wd-cell-group>
</wd-form>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</page-wraper>
</template>
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { reactive, ref } from 'vue'
const model = reactive<{
name: string
phoneNumber: string
}>({
name: '',
phoneNumber: ''
})
const { success: showSuccess } = useToast()
const form = ref()
function handleBlur(prop: string) {
form.value.validate(prop)
}
function handleSubmit() {
form.value
.validate()
.then(({ valid }) => {
if (valid) {
showSuccess('校验通过')
}
})
.catch((error) => {
console.log(error, 'error')
})
}
</script>
<style lang="scss" scoped>
.footer {
padding: 12px;
}
</style>

426
src/pages/form/demo3.vue Normal file
View File

@ -0,0 +1,426 @@
<template>
<view>
<page-wraper>
<wd-message-box />
<wd-toast />
<wd-form ref="form" :model="model" :rules="rules">
<wd-cell-group custom-class="group" title="基础信息" border>
<wd-input
label="优惠券名称"
label-width="100px"
:maxlength="20"
show-word-limit
prop="couponName"
required
suffix-icon="warn-bold"
clearable
v-model="model.couponName"
placeholder="请输入优惠券名称"
@clicksuffixicon="handleIconClick"
/>
<wd-select-picker
label="推广平台"
label-width="100px"
prop="platform"
v-model="model.platform"
:columns="platformList"
placeholder="请选择推广平台"
/>
<wd-picker
label="优惠方式"
placeholder="请选择优惠方式"
label-width="100px"
prop="promotion"
v-model="model.promotion"
:columns="promotionlist"
/>
<wd-cell prop="threshold" title="券面额" required title-width="100px" custom-value-class="cell-left">
<view style="text-align: left">
<view class="inline-txt" style="margin-left: 0"></view>
<wd-input
no-border
custom-style="display: inline-block; width: 70px; vertical-align: middle"
placeholder="请输入金额"
v-model="model.threshold"
/>
<view class="inline-txt"></view>
<wd-input
no-border
custom-style="display: inline-block; width: 70px; vertical-align: middle"
placeholder="请输入金额"
v-model="model.price"
/>
</view>
</wd-cell>
</wd-cell-group>
<wd-cell-group custom-class="group" title="时间和地址" border>
<wd-datetime-picker label="时间" label-width="100px" placeholder="请选择时间" prop="time" v-model="model.time" />
<wd-calendar label="日期" label-width="100px" placeholder="请选择日期" prop="date" v-model="model.date" />
<wd-col-picker
label="地址"
placeholder="请选择地址"
label-width="100px"
prop="address"
v-model="model.address"
:columns="area"
:column-change="areaChange"
/>
</wd-cell-group>
<wd-cell-group custom-class="group" title="其他信息" border>
<wd-textarea
label="活动细则"
label-width="100px"
type="textarea"
v-model="model.content"
:maxlength="300"
show-word-limit
placeholder="请输入活动细则信息"
clearable
prop="content"
/>
<wd-cell title="发货数量" title-width="100px" prop="count">
<view style="text-align: left">
<wd-input-number v-model="model.count" />
</view>
</wd-cell>
<wd-cell title="开启折扣" title-width="100px" prop="switchVal" center>
<view style="text-align: left">
<wd-switch v-model="model.switchVal" />
</view>
</wd-cell>
<wd-input label="卡号" label-width="100px" prop="cardId" suffix-icon="camera" placeholder="请输入卡号" clearable v-model="model.cardId" />
<wd-input label="手机号" label-width="100px" prop="phone" placeholder="请输入手机号" clearable v-model="model.phone" />
<wd-cell title="活动图片" title-width="100px" prop="fileList">
<wd-upload :file-list="model.fileList" action="https://ftf.jd.com/api/uploadImg" @change="handleFileChange"></wd-upload>
</wd-cell>
</wd-cell-group>
<view class="tip">
<wd-checkbox v-model="model.read" prop="read" custom-label-class="label-class">
已阅读并同意
<text style="color: #4d80f0">借款额度合同及相关授权</text>
</wd-checkbox>
</view>
<view class="footer">
<wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
</view>
</wd-form>
</page-wraper>
</view>
</template>
<script lang="ts" setup>
import { useToast } from '@/uni_modules/wot-design-uni'
import { isArray } from '@/uni_modules/wot-design-uni/components/common/util'
import { FormRules } from '@/uni_modules/wot-design-uni/components/wd-form/types'
import { areaData } from '@/utils/area'
import { reactive, ref } from 'vue'
const model = reactive<{
couponName: string
platform: any[]
promotion: string
threshold: string
price: string
time: number | string
date: null | number
address: string[]
count: number
content: string
switchVal: boolean
cardId: string
phone: string
read: boolean
fileList: Record<string, string>[]
}>({
couponName: '',
platform: [],
promotion: '',
threshold: '',
price: '',
date: null,
time: '',
address: [],
count: 1,
content: '',
switchVal: true,
cardId: '',
phone: '',
read: false,
fileList: []
})
const rules: FormRules = {
couponName: [
{
required: true,
pattern: /\d{6}/,
message: '优惠券名称6个字以上',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请输入优惠券名称')
}
}
}
],
content: [
{
required: true,
message: '请输入活动细则信息',
validator: (value) => {
if (value && value.length > 2) {
return Promise.resolve()
} else {
return Promise.reject('请输入活动细则信息')
}
}
}
],
threshold: [
{
required: true,
message: '请输入满减金额',
validator: (value) => {
if (value && model.price) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
],
platform: [
{
required: true,
message: '请选择推广平台',
validator: (value) => {
if (value && isArray(value) && value.length) {
return Promise.resolve()
} else {
return Promise.reject('请选择推广平台')
}
}
}
],
promotion: [
{
required: true,
message: '请选择推广平台',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请选择推广平台')
}
}
}
],
time: [
{
required: true,
message: '请选择时间',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请选择时间')
}
}
}
],
date: [
{
required: true,
message: '请选择日期',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
],
address: [
{
required: true,
message: '请选择地址',
validator: (value) => {
if (isArray(value) && value.length) {
return Promise.resolve()
} else {
return Promise.reject('请选择地址')
}
}
}
],
count: [
{
required: true,
message: '发货数量需要大于1',
validator: (value) => {
if (Number(value) > 1) {
return Promise.resolve()
} else {
return Promise.reject('发货数量需要大于1')
}
}
}
],
cardId: [
{
required: true,
message: '请输入卡号',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject('请输入卡号')
}
}
}
],
phone: [
{
required: true,
message: '请输入手机号',
validator: (value) => {
if (value) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
],
fileList: [
{
required: true,
message: '请选择活动图片',
validator: (value) => {
if (isArray(value) && value.length) {
return Promise.resolve()
} else {
return Promise.reject()
}
}
}
]
}
const platformList = ref<any>([
{
value: '1',
label: '京东'
},
{
value: '2',
label: '开普勒'
},
{
value: '3',
label: '手Q'
},
{
value: '4',
label: '微信'
},
{
value: '5',
label: '1号店'
},
{
value: '6',
label: '十元街'
},
{
value: '7',
label: '京东极速版'
}
])
const promotionlist = ref<any[]>([
{
value: '1',
label: '满减'
},
{
value: '2',
label: '无门槛'
}
])
const area = ref<any[]>([
Object.keys(areaData[86]).map((key) => {
return {
value: key,
label: areaData[86][key]
}
})
])
const areaChange = ({ selectedItem, resolve, finish }) => {
if (areaData[selectedItem.value]) {
resolve(
Object.keys(areaData[selectedItem.value]).map((key) => {
return {
value: key,
label: areaData[selectedItem.value][key]
}
})
)
} else {
finish()
}
}
const toast = useToast()
const form = ref()
function handleFileChange({ fileList }) {
model.fileList = fileList
}
function handleSubmit() {
form.value
.validate()
.then(({ valid, errors }) => {
console.log(valid)
console.log(errors)
})
.catch((error) => {
console.log(error, 'error')
})
}
function handleIconClick() {
toast.info('优惠券提示信息')
}
</script>
<style lang="scss" scoped>
.wot-theme-dark {
.inline-txt {
color: $-dark-color3;
}
}
.inline-txt {
display: inline-block;
font-size: 14px;
margin: 0 8px;
color: rgba(0, 0, 0, 0.45);
vertical-align: middle;
}
:deep(.group) {
margin-top: 12px;
}
.tip {
margin: 10px 15px 21px;
color: #999;
font-size: 12px;
}
.footer {
padding: 0 25px 21px;
}
:deep(.label-class) {
color: #999 !important;
font-size: 12px !important;
}
</style>

View File

@ -151,6 +151,10 @@ const list = ref([
id: 'input', id: 'input',
name: 'Input 输入框' name: 'Input 输入框'
}, },
{
id: 'textarea',
name: 'Textarea 文本域'
},
{ {
id: 'inputNumber', id: 'inputNumber',
name: 'InputNumber 计数器' name: 'InputNumber 计数器'
@ -189,7 +193,7 @@ const list = ref([
}, },
{ {
id: 'form', id: 'form',
name: 'Form 表单组件组合' name: 'Form 表单'
}, },
{ {
id: 'upload', id: 'upload',

View File

@ -16,7 +16,7 @@
<wd-input type="text" v-model="value4" clearable @change="handleChange1" /> <wd-input type="text" v-model="value4" clearable @change="handleChange1" />
</demo-block> </demo-block>
<demo-block title="密码框"> <demo-block title="密码框">
<wd-input type="text" v-model="value5" clearable show-password @change="handleChange2" /> <wd-input type="text" v-model="value5" disabled clearable show-password @change="handleChange2" />
</demo-block> </demo-block>
<demo-block title="设置前后Icon"> <demo-block title="设置前后Icon">
<wd-input type="text" v-model="value6" prefix-icon="dong" suffix-icon="list" clearable @change="handleChange3" /> <wd-input type="text" v-model="value6" prefix-icon="dong" suffix-icon="list" clearable @change="handleChange3" />
@ -26,16 +26,7 @@
</demo-block> </demo-block>
<demo-block title="取消底部边框,自定义使用"> <demo-block title="取消底部边框,自定义使用">
<wd-input v-model="value8" no-border placeholder="请输入价格" custom-style="display: inline-block; width: 70px; vertical-align: middle;" /> <wd-input v-model="value8" no-border placeholder="请输入价格" custom-style="display: inline-block; width: 70px; vertical-align: middle;" />
<text class="custom-txt" style="display: inline-block; vertical-align: middle; font-size: 14px"></text> <text class="custom-txt"></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 clearable></wd-input>
</demo-block> </demo-block>
<demo-block title="cell 类型" transparent> <demo-block title="cell 类型" transparent>
<wd-cell-group border> <wd-cell-group border>
@ -45,12 +36,12 @@
<wd-input type="text" label="错误状态" v-model="value15" placeholder="请输入用户名" error /> <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="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="value17" placeholder="请输入..." prefix-icon="dong" suffix-icon="list" />
<wd-input type="text" label="自定义插槽" v-model="value18" placeholder="请输入..." use-suffix-slot clearable> <wd-input type="text" label="自定义插槽" center v-model="value18" placeholder="请输入..." use-suffix-slot clearable>
<template #suffix> <template #suffix>
<wd-button size="small" custom-class="button">获取验证码</wd-button> <wd-button size="small" custom-class="button">获取验证码</wd-button>
</template> </template>
</wd-input> </wd-input>
<wd-input type="text" label="大尺寸" size="large" v-model="value19" placeholder="请输入..." /> <wd-input type="text" label="大尺寸" clearable size="large" v-model="value19" placeholder="请输入..." />
</wd-cell-group> </wd-cell-group>
</demo-block> </demo-block>
</page-wraper> </page-wraper>
@ -101,6 +92,12 @@ function handleBlur(event) {
color: $-dark-color; color: $-dark-color;
} }
} }
.custom-txt {
display: inline-block;
vertical-align: middle;
font-size: 14px;
line-height: 24px;
}
.flex { .flex {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@ -1,6 +1,18 @@
<template> <template>
<page-wraper> <page-wraper>
<wd-toast /> <wd-toast />
<view class="main">
<wd-tabs v-model="tab1" @change="handleChange">
<block v-for="item in 4" :key="item">
<wd-tab :title="`标签${item}`">
<view class="content">
内容{{ tab1 + 1 }}
<wd-button @click="tab1 < 3 ? tab1++ : (tab1 = 0)">下一个</wd-button>
</view>
</wd-tab>
</block>
</wd-tabs>
</view>
<demo-block title="基本用法" transparent> <demo-block title="基本用法" transparent>
<wd-tabs v-model="tab1" @change="handleChange"> <wd-tabs v-model="tab1" @change="handleChange">
<block v-for="item in 4" :key="item"> <block v-for="item in 4" :key="item">
@ -129,4 +141,8 @@ function handleChange(event) {
line-height: 320px; line-height: 320px;
text-align: center; text-align: center;
} }
.main {
:deep(.wd-tabs) {
}
}
</style> </style>

View File

@ -52,13 +52,14 @@
@include e(cell) { @include e(cell) {
position: relative; position: relative;
display: flex; display: flex;
padding: 0 $-cell-padding; padding: $-cell-wrapper-padding $-cell-padding;
align-items: flex-start;
background-color: $-color-white; background-color: $-color-white;
text-decoration: none; text-decoration: none;
color: $-cell-title-color; color: $-cell-title-color;
font-size: $-cell-title-fs; font-size: $-cell-title-fs;
overflow: hidden; overflow: hidden;
line-height: $-cell-ling-height; line-height: $-cell-line-height;
} }
@include e(cell) { @include e(cell) {
@ -101,11 +102,19 @@
} }
} }
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
}
@include e(label) { @include e(label) {
position: relative; position: relative;
width: $-input-cell-label-width; width: $-input-cell-label-width;
padding: $-cell-wrapper-padding 0;
margin-right: $-cell-padding; margin-right: $-cell-padding;
color: $-cell-title-color;
box-sizing: border-box; box-sizing: border-box;
@include when(required) { @include when(required) {
@ -114,7 +123,7 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc($-cell-wrapper-padding + 2px); top: 2px;
content: '*'; content: '*';
font-size: $-cell-required-size; font-size: $-cell-required-size;
line-height: 1.1; line-height: 1.1;
@ -123,12 +132,14 @@
} }
} }
@include e(value-wraper) {
display: flex;
}
@include e(value) { @include e(value) {
flex: 1; flex: 1;
padding: $-cell-wrapper-padding 0;
margin-right: 10px; margin-right: 10px;
color: $-cell-value-color; color: $-cell-value-color;
white-space: pre-wrap;
@include when(ellipsis) { @include when(ellipsis) {
@include lineEllipsis; @include lineEllipsis;
@ -139,12 +150,15 @@
} }
} }
@include e(body) {
flex: 1;
}
@include edeep(arrow) { @include edeep(arrow) {
display: block; display: block;
margin-top: $-cell-wrapper-padding;
font-size: $-cell-icon-size; font-size: $-cell-icon-size;
color: $-cell-arrow-color; color: $-cell-arrow-color;
line-height: 1.25; line-height: $-cell-line-height;
} }
@include e(header) { @include e(header) {

View File

@ -10,12 +10,14 @@
> >
<view <view
v-if="label || useLabelSlot" v-if="label || useLabelSlot"
:class="`wd-calendar__label ${required ? 'is-required' : ''} ${customLabelClass}`" :class="`wd-calendar__label ${isRequired ? 'is-required' : ''} ${customLabelClass}`"
:style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''" :style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''"
> >
<block v-if="label">{{ label }}</block> <block v-if="label">{{ label }}</block>
<slot v-else name="label"></slot> <slot v-else name="label"></slot>
</view> </view>
<view class="wd-calendar__body">
<view class="wd-calendar__value-wraper">
<view <view
:class="`wd-calendar__value ${ellipsis ? 'is-ellipsis' : ''} ${customValueClass} ${showValue ? '' : 'wd-calendar__value--placeholder'}`" :class="`wd-calendar__value ${ellipsis ? 'is-ellipsis' : ''} ${customValueClass} ${showValue ? '' : 'wd-calendar__value--placeholder'}`"
> >
@ -23,6 +25,9 @@
</view> </view>
<wd-icon v-if="!disabled && !readonly" custom-class="wd-calendar__arrow" name="arrow-right" /> <wd-icon v-if="!disabled && !readonly" custom-class="wd-calendar__arrow" name="arrow-right" />
</view> </view>
<view v-if="errorMessage" class="wd-calendar__error-message">{{ errorMessage }}</view>
</view>
</view>
</view> </view>
<wd-action-sheet <wd-action-sheet
v-model="pickerShow" v-model="pickerShow"
@ -109,9 +114,11 @@ export default {
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { dayjs } from '../common/dayjs' import { dayjs } from '../common/dayjs'
import { debounce, deepClone, isArray, isEqual, padZero } from '../common/util' import { deepClone, isArray, isEqual, padZero } from '../common/util'
import { getWeekNumber, isRange } from '../wd-calendar-view/utils' import { getWeekNumber, isRange } from '../wd-calendar-view/utils'
import { useCell } from '../composables/useCell' import { useCell } from '../composables/useCell'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
const defaultDisplayFormat = (value, type) => { const defaultDisplayFormat = (value, type) => {
switch (type) { switch (type) {
@ -229,6 +236,8 @@ interface Props {
safeAreaInsetBottom?: boolean safeAreaInsetBottom?: boolean
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
beforeConfirm?: Function beforeConfirm?: Function
prop?: string
rules?: FormItemRule[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
customClass: '', customClass: '',
@ -255,7 +264,8 @@ const props = withDefaults(defineProps<Props>(), {
ellipsis: false, ellipsis: false,
showTypeSwitch: false, showTypeSwitch: false,
shortcuts: () => [], shortcuts: () => [],
safeAreaInsetBottom: true safeAreaInsetBottom: true,
rules: () => []
}) })
const pickerShow = ref<boolean>(false) const pickerShow = ref<boolean>(false)
@ -329,6 +339,31 @@ watch(
} }
) )
const { parent: form } = useParent(FORM_KEY)
//
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
const range = computed(() => { const range = computed(() => {
return (type) => { return (type) => {
return isRange(type) return isRange(type)

View File

@ -1,12 +1,3 @@
<!--
* @Author: weisheng
* @Date: 2023-08-01 11:12:05
* @LastEditTime: 2023-08-15 16:12:36
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-cell-group\wd-cell-group.vue
* 记得注释
-->
<template> <template>
<view :class="['wd-cell-group', border ? 'is-border' : '', customClass]"> <view :class="['wd-cell-group', border ? 'is-border' : '', customClass]">
<view v-if="title || value || useSlot" class="wd-cell-group__title"> <view v-if="title || value || useSlot" class="wd-cell-group__title">

View File

@ -36,7 +36,7 @@
background-color: $-color-white; background-color: $-color-white;
text-decoration: none; text-decoration: none;
color: $-cell-title-color; color: $-cell-title-color;
line-height: $-cell-ling-height; line-height: $-cell-line-height;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
@include when(border) { @include when(border) {
@ -62,6 +62,10 @@
.wd-cell__value { .wd-cell__value {
text-align: left; text-align: left;
} }
.wd-cell__left {
margin-right: 0;
}
} }
@include when(label) { @include when(label) {
@ -73,10 +77,9 @@
position: relative; position: relative;
flex: 1; flex: 1;
display: flex; display: flex;
align-items: center;
margin-right: $-cell-padding;
font-size: $-cell-title-fs; font-size: $-cell-title-fs;
box-sizing: border-box; box-sizing: border-box;
margin-right: $-cell-padding;
@include when(required) { @include when(required) {
padding-left: 12px; padding-left: 12px;
@ -94,7 +97,6 @@
@include e(right) { @include e(right) {
position: relative; position: relative;
display: flex;
flex: 1; flex: 1;
} }
@ -102,7 +104,6 @@
flex: 1; flex: 1;
width: 100%; width: 100%;
font-size: $-cell-title-fs; font-size: $-cell-title-fs;
margin-right: $-cell-padding;
} }
@include e(label) { @include e(label) {
@ -115,11 +116,14 @@
@include edeep(icon) { @include edeep(icon) {
display: block; display: block;
position: relative; position: relative;
width: $-cell-icon-size;
height: $-cell-icon-size;
line-height: 1.25;
margin-right: $-cell-icon-right; margin-right: $-cell-icon-right;
font-size: $-cell-icon-size; font-size: $-cell-icon-size;
height: $-cell-line-height;
line-height: $-cell-line-height;
}
@include e(body){
display: flex;
} }
@include e(value) { @include e(value) {
@ -128,18 +132,25 @@
font-size: $-cell-value-fs; font-size: $-cell-value-fs;
color: $-cell-value-color; color: $-cell-value-color;
text-align: right; text-align: right;
line-height: $-cell-value-line-height; vertical-align: middle;
vertical-align: top;
} }
@include edeep(arrow-right) { @include edeep(arrow-right) {
display: inline-block; display: block;
margin-left: 8px; margin-left: 8px;
width: $-cell-arrow-size; width: $-cell-arrow-size;
line-height: 1.22;
font-size: $-cell-arrow-size; font-size: $-cell-arrow-size;
color: $-cell-arrow-color; color: $-cell-arrow-color;
vertical-align: top; height: $-cell-line-height;
line-height: $-cell-line-height;
}
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
} }
@include when(link) { @include when(link) {
@ -155,14 +166,17 @@
font-size: $-cell-title-fs-large; font-size: $-cell-title-fs-large;
} }
.wd-cell__wrapper {
padding-top: $-cell-wrapper-padding-large;
padding-bottom: $-cell-wrapper-padding-large;
}
.wd-cell__label { .wd-cell__label {
font-size: $-cell-label-fs-large; font-size: $-cell-label-fs-large;
} }
.wd-cell__icon { :deep(.wd-cell__icon) {
font-size: $-cell-icon-size-large; font-size: $-cell-icon-size-large;
width: $-cell-icon-size-large;
height: $-cell-icon-size-large;
} }
} }

View File

@ -1,13 +1,13 @@
<template> <template>
<view <view
:class="['wd-cell', cell.border.value ? 'is-border' : '', size ? 'is-' + size : '', center ? 'is-center' : '', customClass]" :class="['wd-cell', isBorder ? 'is-border' : '', size ? 'is-' + size : '', center ? 'is-center' : '', customClass]"
:hover-class="isLink || clickable ? 'is-hover' : 'none'" :hover-class="isLink || clickable ? 'is-hover' : 'none'"
hover-stay-time="70" hover-stay-time="70"
@click="onClick" @click="onClick"
> >
<view :class="['wd-cell__wrapper', vertical ? 'is-vertical' : '']"> <view :class="['wd-cell__wrapper', vertical ? 'is-vertical' : '']">
<view <view
:class="['wd-cell__left', required ? 'is-required' : '']" :class="['wd-cell__left', isRequired ? 'is-required' : '']"
:style="titleWidth ? 'min-width:' + titleWidth + ';max-width:' + titleWidth + ';' : ''" :style="titleWidth ? 'min-width:' + titleWidth + ';max-width:' + titleWidth + ';' : ''"
> >
<!--左侧icon部位--> <!--左侧icon部位-->
@ -16,22 +16,19 @@
<view class="wd-cell__title"> <view class="wd-cell__title">
<!--title BEGIN--> <!--title BEGIN-->
<view>
<view v-if="title" :class="customTitleClass">{{ title }}</view> <view v-if="title" :class="customTitleClass">{{ title }}</view>
<slot v-else name="title"></slot> <slot v-else name="title"></slot>
</view>
<!--title END--> <!--title END-->
<!--label BEGIN--> <!--label BEGIN-->
<view>
<view v-if="label" :class="`wd-cell__label ${customLabelClass}`">{{ label }}</view> <view v-if="label" :class="`wd-cell__label ${customLabelClass}`">{{ label }}</view>
<slot v-else name="label" /> <slot v-else name="label" />
</view>
<!--label END--> <!--label END-->
</view> </view>
</view> </view>
<!--right content BEGIN--> <!--right content BEGIN-->
<view class="wd-cell__right"> <view class="wd-cell__right">
<view class="wd-cell__body">
<!--文案内容--> <!--文案内容-->
<view :class="`wd-cell__value ${customValueClass}`"> <view :class="`wd-cell__value ${customValueClass}`">
<template v-if="value">{{ value }}</template> <template v-if="value">{{ value }}</template>
@ -39,6 +36,9 @@
</view> </view>
<!--箭头--> <!--箭头-->
<wd-icon v-if="isLink" custom-class="wd-cell__arrow-right" name="arrow-right" /> <wd-icon v-if="isLink" custom-class="wd-cell__arrow-right" name="arrow-right" />
<slot v-else name="right-icon" />
</view>
<view v-if="errorMessage" class="wd-cell__error-message">{{ errorMessage }}</view>
</view> </view>
<!--right content END--> <!--right content END-->
</view> </view>
@ -57,7 +57,10 @@ export default {
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'
import { useCell } from '../composables/useCell' import { useCell } from '../composables/useCell'
import { useParent } from '../composables/useParent'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
interface Props { interface Props {
title?: string title?: string
@ -69,10 +72,13 @@ interface Props {
replace?: boolean replace?: boolean
clickable?: boolean clickable?: boolean
size?: string size?: string
border?: boolean
titleWidth?: string titleWidth?: string
center?: boolean center?: boolean
required?: boolean required?: boolean
vertical?: boolean vertical?: boolean
prop?: string
rules?: FormItemRule[]
customClass?: string customClass?: string
customIconClass?: string customIconClass?: string
customLabelClass?: string customLabelClass?: string
@ -91,11 +97,40 @@ const props = withDefaults(defineProps<Props>(), {
replace: false, replace: false,
center: false, center: false,
required: false, required: false,
vertical: false vertical: false,
rules: () => []
}) })
const cell = useCell() const cell = useCell()
const isBorder = computed(() => {
return cell.border.value
})
const { parent: form } = useParent(FORM_KEY)
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
const emit = defineEmits(['click']) const emit = defineEmits(['click'])
/** /**

View File

@ -52,14 +52,14 @@
@include e(cell) { @include e(cell) {
position: relative; position: relative;
display: flex; display: flex;
padding: 0 $-cell-padding; padding: $-cell-wrapper-padding $-cell-padding;
align-items: flex-start; align-items: flex-start;
background-color: $-color-white; background-color: $-color-white;
text-decoration: none; text-decoration: none;
color: $-cell-title-color; color: $-cell-title-color;
font-size: $-cell-title-fs; font-size: $-cell-title-fs;
overflow: hidden; overflow: hidden;
line-height: $-cell-ling-height; line-height: $-cell-line-height;
} }
@include e(cell) { @include e(cell) {
@include when(disabled) { @include when(disabled) {
@ -88,11 +88,18 @@
} }
} }
} }
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
}
@include e(label) { @include e(label) {
position: relative; position: relative;
width: $-input-cell-label-width; width: $-input-cell-label-width;
padding: $-cell-wrapper-padding 0;
margin-right: $-cell-padding; margin-right: $-cell-padding;
color: $-cell-title-color;
box-sizing: border-box; box-sizing: border-box;
@include when(required) { @include when(required) {
@ -101,7 +108,7 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc($-cell-wrapper-padding + 2px); top: 2px;
content: '*'; content: '*';
font-size: $-cell-required-size; font-size: $-cell-required-size;
line-height: 1.1; line-height: 1.1;
@ -109,9 +116,11 @@
} }
} }
} }
@include e(value-wraper) {
display: flex;
}
@include e(value) { @include e(value) {
flex: 1; flex: 1;
padding: $-cell-wrapper-padding 0;
margin-right: 10px; margin-right: 10px;
color: $-cell-value-color; color: $-cell-value-color;
@ -122,12 +131,14 @@
color: $-input-placeholder-color; color: $-input-placeholder-color;
} }
} }
@include e(body) {
flex: 1;
}
@include edeep(arrow) { @include edeep(arrow) {
display: block; display: block;
margin-top: $-cell-wrapper-padding;
font-size: $-cell-icon-size; font-size: $-cell-icon-size;
color: $-cell-arrow-color; color: $-cell-arrow-color;
line-height: 1.25; line-height: $-cell-line-height;
} }
@include e(selected) { @include e(selected) {
height: $-col-picker-selected-height; height: $-col-picker-selected-height;

View File

@ -10,12 +10,14 @@
> >
<view <view
v-if="label || useLabelSlot" v-if="label || useLabelSlot"
:class="`wd-col-picker__label ${required && 'is-required'} ${customLabelClass}`" :class="`wd-col-picker__label ${isRequired && 'is-required'} ${customLabelClass}`"
:style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''" :style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''"
> >
<block v-if="label">{{ label }}</block> <block v-if="label">{{ label }}</block>
<slot v-else name="label"></slot> <slot v-else name="label"></slot>
</view> </view>
<view class="wd-col-picker__body">
<view class="wd-col-picker__value-wraper">
<view <view
:class="`wd-col-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${showValue ? '' : 'wd-col-picker__value--placeholder'}`" :class="`wd-col-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${showValue ? '' : 'wd-col-picker__value--placeholder'}`"
> >
@ -23,6 +25,9 @@
</view> </view>
<wd-icon v-if="!disabled && !readonly" custom-class="wd-col-picker__arrow" name="arrow-right" /> <wd-icon v-if="!disabled && !readonly" custom-class="wd-col-picker__arrow" name="arrow-right" />
</view> </view>
<view v-if="errorMessage" class="wd-col-picker__error-message">{{ errorMessage }}</view>
</view>
</view>
</view> </view>
<wd-action-sheet <wd-action-sheet
v-model="pickerShow" v-model="pickerShow"
@ -89,9 +94,11 @@ export default {
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { getCurrentInstance, onMounted, ref, watch } from 'vue' import { computed, getCurrentInstance, onMounted, ref, watch } from 'vue'
import { debounce, getRect, getType } from '../common/util' import { debounce, getRect, getType } from '../common/util'
import { useCell } from '../composables/useCell' import { useCell } from '../composables/useCell'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
const $container = '.wd-col-picker__selected-container' const $container = '.wd-col-picker__selected-container'
const $item = '.wd-col-picker__selected-item' const $item = '.wd-col-picker__selected-item'
@ -132,6 +139,8 @@ interface Props {
zIndex?: number zIndex?: number
safeAreaInsetBottom?: boolean safeAreaInsetBottom?: boolean
ellipsis?: boolean ellipsis?: boolean
prop?: string
rules?: FormItemRule[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -157,7 +166,8 @@ const props = withDefaults(defineProps<Props>(), {
zIndex: 15, zIndex: 15,
safeAreaInsetBottom: true, safeAreaInsetBottom: true,
ellipsis: false, ellipsis: false,
labelWidth: '33%' labelWidth: '33%',
rules: () => []
}) })
const pickerShow = ref<boolean>(false) const pickerShow = ref<boolean>(false)
@ -268,6 +278,31 @@ watch(
} }
) )
const { parent: form } = useParent(FORM_KEY)
//
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
const emit = defineEmits(['close', 'update:modelValue', 'confirm']) const emit = defineEmits(['close', 'update:modelValue', 'confirm'])
onMounted(() => { onMounted(() => {

View File

@ -3,7 +3,7 @@
.wot-theme-dark { .wot-theme-dark {
@include b(picker) { @include b(picker) {
@include e(field) { @include e(cell) {
background-color: $-dark-background2; background-color: $-dark-background2;
color: $-dark-color; color: $-dark-color;
} }
@ -21,7 +21,7 @@
@include when(border) { @include when(border) {
.wd-picker__field { .wd-picker__cell {
@include halfPixelBorder('top', $-cell-padding, $-dark-border-color); @include halfPixelBorder('top', $-cell-padding, $-dark-border-color);
} }
} }
@ -53,17 +53,17 @@
border-radius: 16px 16px 0px 0px; border-radius: 16px 16px 0px 0px;
} }
@include e(body) { @include e(wraper) {
padding-bottom: var(--window-bottom); padding-bottom: var(--window-bottom);
} }
@include when(border) { @include when(border) {
.wd-picker__field { .wd-picker__cell {
@include halfPixelBorder("top", $-cell-padding); @include halfPixelBorder("top", $-cell-padding);
} }
} }
@include when(large) { @include when(large) {
.wd-picker__field { .wd-picker__cell {
font-size: $-cell-title-fs-large; font-size: $-cell-title-fs-large;
} }
:deep(.wd-picker__arrow) { :deep(.wd-picker__arrow) {
@ -85,17 +85,17 @@
text-align: right; text-align: right;
} }
} }
@include e(field) { @include e(cell) {
position: relative; position: relative;
display: flex; display: flex;
padding: 0 $-cell-padding; padding: $-cell-wrapper-padding $-cell-padding;
align-items: flex-start; align-items: flex-start;
background-color: $-color-white; background-color: $-color-white;
text-decoration: none; text-decoration: none;
color: $-cell-title-color; color: $-cell-title-color;
font-size: $-cell-title-fs; font-size: $-cell-title-fs;
overflow: hidden; overflow: hidden;
line-height: $-cell-ling-height; line-height: $-cell-line-height;
@include when(disabled) { @include when(disabled) {
.wd-picker__value { .wd-picker__value {
@ -110,10 +110,17 @@
} }
} }
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
}
@include e(label) { @include e(label) {
position: relative; position: relative;
width: $-input-cell-label-width; width: $-input-cell-label-width;
padding: $-cell-wrapper-padding 0;
margin-right: $-cell-padding; margin-right: $-cell-padding;
color: $-cell-title-color; color: $-cell-title-color;
box-sizing: border-box; box-sizing: border-box;
@ -124,7 +131,7 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc($-cell-wrapper-padding + 2px); top: 2px;
content: "*"; content: "*";
font-size: $-cell-required-size; font-size: $-cell-required-size;
line-height: 1.1; line-height: 1.1;
@ -133,10 +140,12 @@
} }
} }
@include e(value-wraper) {
display: flex;
}
@include e(value) { @include e(value) {
width: 0;
flex: 1; flex: 1;
padding: $-cell-wrapper-padding 0;
margin-right: 10px; margin-right: 10px;
color: $-cell-value-color; color: $-cell-value-color;
@ -145,16 +154,19 @@
} }
} }
@include e(body) {
flex: 1;
}
@include e(placeholder) { @include e(placeholder) {
color: $-input-placeholder-color; color: $-input-placeholder-color;
} }
@include edeep(arrow) { @include edeep(arrow) {
display: block; display: block;
margin-top: $-cell-wrapper-padding;
font-size: $-cell-icon-size; font-size: $-cell-icon-size;
color: $-cell-arrow-color; color: $-cell-arrow-color;
line-height: 1.25; line-height: $-cell-line-height;
} }
@include e(toolbar) { @include e(toolbar) {

View File

@ -5,17 +5,19 @@
} ${error ? 'is-error' : ''} ${customClass}`" } ${error ? 'is-error' : ''} ${customClass}`"
> >
<!--文案--> <!--文案-->
<view @click="showPopup"> <view class="wd-picker__field" @click="showPopup">
<slot v-if="useDefaultSlot"></slot> <slot v-if="useDefaultSlot"></slot>
<view v-else class="wd-picker__field"> <view v-else class="wd-picker__cell">
<view <view
v-if="label || useLabelSlot" v-if="label || useLabelSlot"
:class="`wd-picker__label ${customLabelClass} ${required ? 'is-required' : ''}`" :class="`wd-picker__label ${customLabelClass} ${isRequired ? 'is-required' : ''}`"
:style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''" :style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''"
> >
<block v-if="label">{{ label }}</block> <block v-if="label">{{ label }}</block>
<slot v-else name="label"></slot> <slot v-else name="label"></slot>
</view> </view>
<view class="wd-picker__body">
<view class="wd-picker__value-wraper">
<view :class="`wd-picker__value ${customValueClass}`"> <view :class="`wd-picker__value ${customValueClass}`">
<view v-if="region"> <view v-if="region">
<text :class="showValue[0] ? '' : 'wd-picker__placeholder'">{{ showValue[0] ? showValue[0] : placeholder }}</text> <text :class="showValue[0] ? '' : 'wd-picker__placeholder'">{{ showValue[0] ? showValue[0] : placeholder }}</text>
@ -28,6 +30,9 @@
</view> </view>
<wd-icon v-if="!disabled && !readonly" custom-class="wd-picker__arrow" name="arrow-right" /> <wd-icon v-if="!disabled && !readonly" custom-class="wd-picker__arrow" name="arrow-right" />
</view> </view>
<view v-if="errorMessage" class="wd-picker__error-message">{{ errorMessage }}</view>
</view>
</view>
</view> </view>
<!--弹出层picker-view 在隐藏时修改值会触发多次change事件从而导致所有列选中第一项因此picker在关闭时不隐藏 --> <!--弹出层picker-view 在隐藏时修改值会触发多次change事件从而导致所有列选中第一项因此picker在关闭时不隐藏 -->
<wd-popup <wd-popup
@ -40,7 +45,7 @@
@close="onCancel" @close="onCancel"
custom-class="wd-picker__popup" custom-class="wd-picker__popup"
> >
<view class="wd-picker__body"> <view class="wd-picker__wraper">
<!--toolBar--> <!--toolBar-->
<view class="wd-picker__toolbar" @touchmove="noop"> <view class="wd-picker__toolbar" @touchmove="noop">
<!--取消按钮--> <!--取消按钮-->
@ -135,10 +140,12 @@ export default {
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { getCurrentInstance, nextTick, onBeforeMount, onMounted, ref, watch } from 'vue' import { computed, getCurrentInstance, nextTick, onBeforeMount, onMounted, ref, watch } from 'vue'
import { deepClone, getType, isArray, isDef, isEqual, padZero } from '../common/util' import { deepClone, getType, isArray, isDef, isEqual, padZero } from '../common/util'
import { useCell } from '../composables/useCell' import { useCell } from '../composables/useCell'
import { type DateTimeType, getPickerValue } from '../wd-datetime-picker-view/type' import { type DateTimeType, getPickerValue } from '../wd-datetime-picker-view/type'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
interface Props { interface Props {
customClass?: string customClass?: string
customViewClass?: string customViewClass?: string
@ -211,6 +218,8 @@ interface Props {
displayFormatTabLabel?: Function displayFormatTabLabel?: Function
defaultValue?: string | number | Date | Array<string | number | Date> defaultValue?: string | number | Date | Array<string | number | Date>
zIndex?: number zIndex?: number
prop?: string
rules?: FormItemRule[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -262,7 +271,8 @@ const props = withDefaults(defineProps<Props>(), {
minMinute: 0, minMinute: 0,
// //
maxMinute: 59, maxMinute: 59,
zIndex: 15 zIndex: 15,
rules: () => []
}) })
const datetimePickerView = ref() const datetimePickerView = ref()
const datetimePickerView1 = ref() const datetimePickerView1 = ref()
@ -383,6 +393,31 @@ watch(
} }
) )
const { parent: form } = useParent(FORM_KEY)
//
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
const emit = defineEmits(['change', 'open', 'toggle', 'cancel', 'confirm', 'update:modelValue']) const emit = defineEmits(['change', 'open', 'toggle', 'cancel', 'confirm', 'update:modelValue'])
/** /**

View File

@ -0,0 +1,18 @@
@import '../common/abstracts/variable';
@import '../common/abstracts/mixin';
.wot-theme-dark {
@include b(form-item) {
}
}
@include bdeep(form-item) {
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
}
}

View File

@ -0,0 +1,82 @@
<!--
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2023-12-17 15:16:03
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-form-item\wd-form-item.vue
* 记得注释
-->
<template>
<wd-cell
custom-class="wd-form-item"
:required="required"
:title="label"
:center="center"
:border="border"
:title-width="labelWidth"
:is-link="isLink"
>
<slot></slot>
<view v-if="errorMessage" class="wd-form-item__error-message">{{ errorMessage }}</view>
</wd-cell>
</template>
<script lang="ts">
export default {
name: 'wd-form-item',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { useParent } from '../composables/useParent'
import WdCell from '../wd-cell/wd-cell.vue'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
interface Props {
prop: string
rules?: FormItemRule[]
required?: boolean
center?: boolean
label?: string
labelWidth?: string
isLink?: boolean
customClass?: string
customStyle?: string
}
const props = withDefaults(defineProps<Props>(), {
rules: () => [],
center: false,
labelWidth: '100px',
customClass: '',
customStyle: ''
})
const { parent: form, index } = useParent(FORM_KEY)
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
const border = computed(() => {
if (index.value > 0 && form && form.border) {
return true
} else {
return false
}
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -0,0 +1,10 @@
@import "../common/abstracts/variable";
@import "../common/abstracts/mixin";
.wot-theme-dark {
@include b(form) {
}
}
@include b(form) {
}

View File

@ -0,0 +1,38 @@
/*
* @Author: weisheng
* @Date: 2023-12-14 11:21:58
* @LastEditTime: 2023-12-23 16:20:20
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-form\types.ts
*
*/
import { type InjectionKey } from 'vue'
export type FormProvide = {
model: Record<string, any>
rules?: FormRules
border?: boolean
errorMessages?: Record<string, string>
}
export const FORM_KEY: InjectionKey<FormProvide> = Symbol('wd-form')
export type FormRules = {
[key: string]: FormItemRule[]
}
export type ErrorMessage = {
prop: string
message: string
}
export interface FormItemRule {
[key: string]: any
required: boolean
message: string
pattern?: RegExp
validator?: (value: any, rule: FormItemRuleWithoutValidator) => boolean | Promise<string> | Promise<boolean> | Promise<void> | Promise<unknown>
}
export type FormItemRuleWithoutValidator = Omit<FormItemRule, 'validator'>

View File

@ -0,0 +1,174 @@
<template>
<view :class="`wd-form ${customClass}`" :style="customStyle">
<slot></slot>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-form',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { reactive, watch } from 'vue'
import { deepClone, getPropByPath, isDef, isPromise } from '../common/util'
import { useChildren } from '../composables/useChildren'
import { FormRules, FORM_KEY, ErrorMessage, FormItemRule } from './types'
interface Props {
//
model: Record<string, any>
//
rules?: FormRules
customClass?: string
customStyle?: string
}
const props = withDefaults(defineProps<Props>(), {
rules: () => ({}),
customClass: '',
customStyle: ''
})
const { children, linkChildren } = useChildren(FORM_KEY)
let errorMessages = reactive<Record<string, string>>({})
linkChildren({ ...props, errorMessages: errorMessages })
watch(
() => props.model,
() => {
clearMessage()
},
{ immediate: true, deep: true }
)
/**
* 表单校验
* @param prop 指定校验字段
*/
async function validate(prop?: string): Promise<{ valid: boolean; errors: ErrorMessage[] }> {
const errors: ErrorMessage[] = []
let valid: boolean = true
const promises: Promise<void>[] = []
const formRules: FormRules = getMergeRules()
const rulesToValidate: FormRules = prop ? { [prop]: formRules[prop] } : formRules
for (const prop in rulesToValidate) {
const rules = rulesToValidate[prop]
const value = getPropByPath(props.model, prop)
if (rules && rules.length > 0) {
for (const rule of rules) {
if (rule.required && (!isDef(value) || value === '')) {
errors.push({
prop,
message: rule.message
})
valid = false
break
}
if (rule.pattern && !rule.pattern.test(props.model[prop])) {
errors.push({
prop,
message: rule.message
})
valid = false
break
}
const { validator, ...ruleWithoutValidator } = rule
if (validator) {
const result = validator(props.model[prop], ruleWithoutValidator)
if (isPromise(result)) {
promises.push(
result
.then((res: any) => {
if (typeof res === 'string') {
errors.push({
prop,
message: res
})
valid = false
} else if (typeof res === 'boolean' && !res) {
errors.push({
prop,
message: rule.message
})
valid = false
}
})
.catch((error) => {
errors.push({
prop,
message: error || rule.message
})
valid = false
})
)
} else {
if (!result) {
errors.push({
prop,
message: rule.message
})
valid = false
}
}
}
}
}
}
await Promise.all(promises)
errors.forEach((error) => {
showMessage(error)
})
return {
valid,
errors
}
}
// rulesrules
function getMergeRules() {
const mergedRules: FormRules = deepClone(props.rules)
children.forEach((item) => {
if (isDef(item.prop) && isDef(item.rules) && item.rules.length) {
if (mergedRules[item.prop]) {
mergedRules[item.prop] = [...mergedRules[item.prop], ...item.rules]
} else {
mergedRules[item.prop] = item.rules
}
}
})
return mergedRules
}
function showMessage(errorMsg: ErrorMessage) {
if (errorMsg.message) {
errorMessages[errorMsg.prop] = errorMsg.message
}
}
function clearMessage() {
Object.keys(errorMessages).forEach((key) => {
errorMessages[key] = ''
})
}
function reset() {
clearMessage()
}
defineExpose({ validate, reset })
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -28,7 +28,6 @@
@include b(input-number) { @include b(input-number) {
display: inline-block; display: inline-block;
font-size: 0;
user-select: none; user-select: none;
line-height: 1.15; line-height: 1.15;

View File

@ -3,6 +3,7 @@
.wot-theme-dark { .wot-theme-dark {
@include b(input) { @include b(input) {
background: $-dark-background2;
&::after { &::after {
background: $-dark-color-gray; background: $-dark-color-gray;
@ -16,9 +17,6 @@
} }
} }
@include e(textarea) {
background: $-dark-background2;
}
@include e(inner) { @include e(inner) {
color: $-dark-color; color: $-dark-color;
@ -28,14 +26,6 @@
} }
} }
@include e(textarea-inner) {
color: $-dark-color;
&::-webkit-input-placeholder {
color: $-dark-color3;
}
}
@include e(placeholder) { @include e(placeholder) {
color: $-dark-color3; color: $-dark-color3;
} }
@ -49,19 +39,8 @@
color: $-dark-color; color: $-dark-color;
} }
@include e(textarea-count) {
color: $-dark-color3;
background: transparent;
}
@include e(textarea-count-current) {
color: $-dark-color;
}
:deep(.wd-input__icon), :deep(.wd-input__icon),
:deep(.wd-input__clear), :deep(.wd-input__clear) {
:deep(.wd-input__textarea-icon) {
color: $-dark-color; color: $-dark-color;
background: transparent; background: transparent;
} }
@ -76,8 +55,7 @@
@include when(disabled) { @include when(disabled) {
.wd-input__inner, .wd-input__inner {
.wd-input__textarea-inner {
color: $-dark-color-gray; color: $-dark-color-gray;
background: transparent; background: transparent;
} }
@ -94,6 +72,7 @@
position: relative; position: relative;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
text-align: left; text-align: left;
background: $-input-bg;
&::after { &::after {
position: absolute; position: absolute;
@ -115,12 +94,6 @@
} }
} }
@include when(textarea) {
&::after {
display: none;
}
}
@include e(label) { @include e(label) {
position: relative; position: relative;
width: $-input-cell-label-width; width: $-input-cell-label-width;
@ -129,7 +102,6 @@
box-sizing: border-box; box-sizing: border-box;
font-size: $-input-fs; font-size: $-input-fs;
flex-shrink: 0; flex-shrink: 0;
line-height: $-cell-ling-height;
@include when(required) { @include when(required) {
padding-left: 12px; padding-left: 12px;
@ -137,7 +109,7 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc($-cell-wrapper-padding + 2px); top: 2px;
content: "*"; content: "*";
font-size: $-cell-required-size; font-size: $-cell-required-size;
line-height: 1.1; line-height: 1.1;
@ -148,17 +120,22 @@
@include e(label-inner) { @include e(label-inner) {
display: inline-block; display: inline-block;
padding: $-cell-wrapper-padding 0;
font-size: $-input-fs; font-size: $-input-fs;
line-height: $-cell-line-height;
} }
@include e(block) { @include e(body) {
flex: 1;
}
@include e(value) {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
} }
@include e(prefix) { @include e(prefix) {
margin-right: $-input-icon-margin; margin-right: $-input-icon-margin;
font-size: $-input-fs; font-size: $-input-fs;
@ -172,12 +149,20 @@
@include e(suffix) { @include e(suffix) {
flex-shrink: 0; flex-shrink: 0;
margin-left: $-input-icon-margin; margin-left: $-input-icon-margin;
line-height: initial;
}
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
} }
@include when(disabled) { @include when(disabled) {
.wd-input__inner, .wd-input__inner {
.wd-input__textarea-inner {
color: $-input-disabled-color; color: $-input-disabled-color;
background: transparent; background: transparent;
} }
@ -185,8 +170,7 @@
@include when(error) { @include when(error) {
.wd-input__inner, .wd-input__inner {
.wd-input__textarea-inner {
color: $-input-error-color; color: $-input-error-color;
background: transparent; background: transparent;
} }
@ -207,18 +191,13 @@
@include when(cell) { @include when(cell) {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
padding: 0 $-input-cell-padding; padding: $-input-cell-padding $-input-padding;
background-color: $-input-cell-bg; background-color: $-input-cell-bg;
&.is-error::after { &.is-error::after {
background: $-input-cell-border-color; background: $-input-cell-border-color;
} }
.wd-input__textarea,
.wd-input__block {
flex: 1;
}
:deep(.wd-input__icon), :deep(.wd-input__icon),
:deep(.wd-input__clear) { :deep(.wd-input__clear) {
height: $-input-cell-height; height: $-input-cell-height;
@ -238,17 +217,6 @@
display: none; display: none;
} }
.wd-input__textarea {
padding: 13px 0 36px 0;
}
.wd-input__textarea-inner {
padding-right: 24px;
}
.wd-input__textarea-suffix {
right: 0;
}
@include when(center) { @include when(center) {
align-items: center; align-items: center;
@ -260,6 +228,8 @@
} }
@include when(large) { @include when(large) {
padding: $-input-cell-padding-large;
.wd-input__prefix { .wd-input__prefix {
font-size: $-input-fs-large; font-size: $-input-fs-large;
} }
@ -270,47 +240,20 @@
.wd-input__inner { .wd-input__inner {
font-size: $-input-fs-large; font-size: $-input-fs-large;
height: $-input-cell-height-large;
} }
.wd-input__textarea-inner {
font-size: $-input-fs-large;
}
.wd-input__count { .wd-input__count {
font-size: $-input-count-fs-large; font-size: $-input-count-fs-large;
} }
.wd-input__textarea-count {
font-size: $-input-count-fs-large;
}
:deep(.wd-input__icon), :deep(.wd-input__icon),
:deep(.wd-input__clear) { :deep(.wd-input__clear) {
height: $-input-cell-height-large;
font-size: $-input-icon-size-large;
line-height: $-input-cell-height-large;
}
:deep(.wd-input__textarea-icon) {
font-size: $-input-icon-size-large; font-size: $-input-icon-size-large;
} }
} }
@include when(auto-height) {
&::after {
display: block;
}
.wd-input__textarea {
padding: $-input-inner-padding;
}
.wd-input__textarea-suffix {
top: 8px;
right: 0;
}
}
@include e(inner) { @include e(inner) {
flex: 1; flex: 1;
@ -330,16 +273,18 @@
} }
} }
@include e(readonly) { @include e(readonly-mask) {
padding: $-input-inner-padding; position: absolute;
top: 0;
left: 0;
z-index: 2;
width: 100%;
height: 100%;
} }
@include e(textarea-inner) {
width: 100%;
}
@include edeep(icon) { @include edeep(icon) {
margin-left: 8px; margin-left: $-input-icon-margin;
font-size: $-input-icon-size; font-size: $-input-icon-size;
color: $-input-icon-color; color: $-input-icon-color;
vertical-align: middle; vertical-align: middle;
@ -347,7 +292,7 @@
} }
@include edeep(clear) { @include edeep(clear) {
margin-left: 8px; margin-left: $-input-icon-margin;
font-size: $-input-icon-size; font-size: $-input-icon-size;
color: $-input-clear-color; color: $-input-clear-color;
vertical-align: middle; vertical-align: middle;
@ -370,79 +315,6 @@
} }
} }
@include e(textarea) {
position: relative;
padding: $-input-textarea-padding;
font-size: 0;
display: flex;
background: $-input-bg;
@include when(show-limit) {
padding-bottom: 36px;
}
}
@include edeep(textarea-icon) {
margin-left: 8px;
font-size: $-input-icon-size;
line-height: 20px;
color: $-input-icon-color;
background: #fff;
}
@include e(textarea-inner) {
flex: 1;
// width: 1px;
padding: 0;
font-size: $-input-fs;
line-height: 1.43;
color: $-input-color;
outline: none;
box-sizing: border-box;
border: none;
word-break: break-word;
&::-webkit-input-placeholder {
color: $-input-placeholder-color;
}
@include when(suffix) {
padding-right: calc($-input-textarea-padding + $-input-icon-size);
}
}
@include e(textarea-suffix) {
position: absolute;
right: 14px;
top: $-input-textarea-padding;
bottom: 0;
}
@include edeep(textarea-icon) {
margin-left: 8px;
font-size: $-input-icon-size;
line-height: 20px;
color: $-input-clear-color;
background: $-input-bg;
}
@include e(textarea-count) {
position: absolute;
bottom: 8px;
right: 0;
font-size: $-input-count-fs;
color: $-input-count-color;
background: $-input-bg;
line-height: 20px;
}
@include e(textarea-count-current) {
color: $-input-count-current-color;
@include when(error) {
color: $-input-error-color;
}
}
@include e(placeholder) { @include e(placeholder) {
color: $-input-placeholder-color; color: $-input-placeholder-color;
@ -452,18 +324,8 @@
} }
} }
:deep(.wd-input__textarea-count) {
display: inline-flex;
}
.wd-input__count, .wd-input__count,
.wd-input__count-current { .wd-input__count-current {
display: inline-flex; display: inline-flex;
} }
@include e(textarea-map) {
position: absolute;
width: 100%;
height: 100%;
}
} }

View File

@ -1,74 +1,22 @@
<template> <template>
<view :class="rootClass" :style="customStyle"> <view :class="rootClass" :style="customStyle" @click="handleClick">
<view v-if="label || useLabelSlot" :class="labelClass" :style="labelStyle"> <view v-if="label || useLabelSlot" :class="labelClass" :style="labelStyle">
<view v-if="prefixIcon || usePrefixSlot" class="wd-input__prefix"> <view v-if="prefixIcon || usePrefixSlot" class="wd-input__prefix">
<wd-icon v-if="prefixIcon && !usePrefixSlot" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" /> <wd-icon v-if="prefixIcon && !usePrefixSlot" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" />
<slot v-else name="prefix"></slot> <slot v-else name="prefix"></slot>
</view> </view>
<view style="display: inline-flex">
<view class="wd-input__label-inner"> <view class="wd-input__label-inner">
<template v-if="label">{{ label }}</template> <template v-if="label">{{ label }}</template>
<slot v-else name="label"></slot> <slot v-else name="label"></slot>
</view> </view>
</view> </view>
</view>
<!-- 文本域 -->
<view v-if="type === 'textarea'" :class="`wd-input__textarea ${customTextareaContainerClass} ${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-bar="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 class="wd-input__body">
<view class="wd-input__value">
<view v-if="(prefixIcon || usePrefixSlot) && !label" class="wd-input__prefix"> <view v-if="(prefixIcon || usePrefixSlot) && !label" class="wd-input__prefix">
<wd-icon v-if="prefixIcon" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" /> <wd-icon v-if="prefixIcon" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" />
<slot name="prefix"></slot> <slot name="prefix"></slot>
</view> </view>
<!-- readonly -->
<view v-if="readonly" class="wd-input__inner wd-input__readonly">{{ inputValue }}</view>
<template v-else>
<input <input
:class="[ :class="[
'wd-input__inner', 'wd-input__inner',
@ -81,9 +29,7 @@
:password="showPassword && !isPwdVisible" :password="showPassword && !isPwdVisible"
v-model="inputValue" v-model="inputValue"
:placeholder="placeholder" :placeholder="placeholder"
:readonly="readonly"
:disabled="disabled" :disabled="disabled"
:minlength="minlength"
:maxlength="maxlength" :maxlength="maxlength"
:focus="isFocus" :focus="isFocus"
:confirm-type="confirmType" :confirm-type="confirmType"
@ -103,6 +49,7 @@
@confirm="handleConfirm" @confirm="handleConfirm"
@keyboardheightchange="handleKeyboardheightchange" @keyboardheightchange="handleKeyboardheightchange"
/> />
<view v-if="readonly" class="wd-input__readonly-mask" />
<view v-if="showClear || showPassword || suffixIcon || showWordCount || useSuffixSlot" class="wd-input__suffix"> <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="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" /> <wd-icon v-if="showPassword" custom-class="wd-input__icon" :name="isPwdVisible ? 'view' : 'eye-close'" @click="togglePwdVisible" />
@ -120,7 +67,8 @@
<wd-icon v-if="suffixIcon" custom-class="wd-input__icon" :name="suffixIcon" @click="onClickSuffixIcon" /> <wd-icon v-if="suffixIcon" custom-class="wd-input__icon" :name="suffixIcon" @click="onClickSuffixIcon" />
<slot name="suffix"></slot> <slot name="suffix"></slot>
</view> </view>
</template> </view>
<view v-if="errorMessage" class="wd-input__error-message">{{ errorMessage }}</view>
</view> </view>
</view> </view>
</template> </template>
@ -140,10 +88,10 @@ export default {
import { computed, onBeforeMount, ref, watch } from 'vue' import { computed, onBeforeMount, ref, watch } from 'vue'
import { objToStyle, requestAnimationFrame } from '../common/util' import { objToStyle, requestAnimationFrame } from '../common/util'
import { useCell } from '../composables/useCell' import { useCell } from '../composables/useCell'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
interface Props { interface Props {
customTextareaContainerClass?: string
customTextareaClass?: string
customInputClass?: string customInputClass?: string
customLabelClass?: string customLabelClass?: string
customClass?: string customClass?: string
@ -152,11 +100,8 @@ interface Props {
placeholder?: string placeholder?: string
placeholderStyle?: string placeholderStyle?: string
placeholderClass?: string placeholderClass?: string
autoHeight?: boolean
fixed?: boolean
cursorSpacing?: number cursorSpacing?: number
cursor?: number cursor?: number
showConfirmBar?: boolean
selectionStart?: number selectionStart?: number
selectionEnd?: number selectionEnd?: number
adjustPosition?: boolean adjustPosition?: boolean
@ -167,11 +112,10 @@ interface Props {
type?: string type?: string
maxlength?: number maxlength?: number
disabled?: boolean disabled?: boolean
alignRight?: boolean
alwaysEmbed?: boolean alwaysEmbed?: boolean
// //
alignRight?: boolean
modelValue: string | number modelValue: string | number
minlength?: number
showPassword?: boolean showPassword?: boolean
clearable?: boolean clearable?: boolean
readonly?: boolean readonly?: boolean
@ -180,8 +124,6 @@ interface Props {
prefixIcon?: string prefixIcon?: string
suffixIcon?: string suffixIcon?: string
showWordLimit?: boolean showWordLimit?: boolean
suffix?: string
suffixCount?: number
label?: string label?: string
labelWidth?: string labelWidth?: string
useLabelSlot?: boolean useLabelSlot?: boolean
@ -190,11 +132,11 @@ interface Props {
center?: boolean center?: boolean
noBorder?: boolean noBorder?: boolean
required?: boolean required?: boolean
prop?: string
rules?: FormItemRule[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
customTextareaContainerClass: '',
customTextareaClass: '',
customInputClass: '', customInputClass: '',
customLabelClass: '', customLabelClass: '',
customClass: '', customClass: '',
@ -203,7 +145,6 @@ const props = withDefaults(defineProps<Props>(), {
maxlength: -1, maxlength: -1,
modelValue: '', modelValue: '',
placeholder: '请输入...', placeholder: '请输入...',
autoHeight: false,
clearable: false, clearable: false,
showPassword: false, showPassword: false,
disabled: false, disabled: false,
@ -215,12 +156,10 @@ const props = withDefaults(defineProps<Props>(), {
showWordLimit: false, showWordLimit: false,
confirmType: 'done', confirmType: 'done',
confirmHold: false, confirmHold: false,
placeholderClass: 'textarea-placeholder', placeholderClass: '',
focus: false, focus: false,
cursorSpacing: 0, cursorSpacing: 0,
fixed: false,
cursor: -1, cursor: -1,
showConfirmBar: true,
selectionStart: -1, selectionStart: -1,
selectionEnd: -1, selectionEnd: -1,
adjustPosition: true, adjustPosition: true,
@ -230,7 +169,8 @@ const props = withDefaults(defineProps<Props>(), {
labelWidth: '33%', labelWidth: '33%',
useLabelSlot: false, useLabelSlot: false,
required: false, required: false,
noBorder: false noBorder: false,
rules: () => []
}) })
const showClear = ref<boolean>(false) const showClear = ref<boolean>(false)
@ -263,18 +203,40 @@ watch(
{ immediate: true, deep: true } { immediate: true, deep: true }
) )
const { parent: form } = useParent(FORM_KEY)
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
const rootClass = computed(() => { const rootClass = computed(() => {
return `wd-input ${props.type === 'textarea' ? 'is-textarea' : ''} ${props.label || props.useLabelSlot ? 'is-cell' : ''} ${ return `wd-input ${props.label || props.useLabelSlot ? 'is-cell' : ''} ${props.center ? 'is-center' : ''} ${
props.center ? 'is-center' : '' cell.border.value ? 'is-border' : ''
} ${cell.border.value ? 'is-border' : ''} ${props.size ? 'is-' + props.size : ''} ${props.error ? 'is-error' : ''} ${ } ${props.size ? 'is-' + props.size : ''} ${props.error ? 'is-error' : ''} ${props.disabled ? 'is-disabled' : ''} ${
props.disabled ? 'is-disabled' : '' inputValue.value && String(inputValue.value).length > 0 ? 'is-not-empty' : ''
} ${props.autoHeight ? 'is-auto-height' : ''} ${inputValue.value && String(inputValue.value).length > 0 ? 'is-not-empty' : ''} ${ } ${props.noBorder ? 'is-no-border' : ''} ${props.customClass}`
props.noBorder ? 'is-no-border' : ''
} ${props.customClass}`
}) })
const labelClass = computed(() => { const labelClass = computed(() => {
return `wd-input__label ${props.customLabelClass} ${props.required ? 'is-required' : ''}` return `wd-input__label ${props.customLabelClass} ${isRequired.value ? 'is-required' : ''}`
}) })
const inputPlaceholderClass = computed(() => { const inputPlaceholderClass = computed(() => {
@ -301,7 +263,8 @@ const emit = defineEmits([
'confirm', 'confirm',
'linechange', 'linechange',
'clicksuffixicon', 'clicksuffixicon',
'clickprefixicon' 'clickprefixicon',
'click'
]) ])
onBeforeMount(() => { onBeforeMount(() => {
@ -346,9 +309,7 @@ function handleBlur({ detail }) {
}) })
emit('update:modelValue', inputValue.value) emit('update:modelValue', inputValue.value)
emit('blur', { emit('blur', {
value: inputValue.value, value: inputValue.value
// textarea cursor
cursor: detail.cursor ? detail.cursor : null
}) })
} }
function handleFocus({ detail }) { function handleFocus({ detail }) {
@ -370,15 +331,15 @@ function handleKeyboardheightchange(event) {
function handleConfirm({ detail }) { function handleConfirm({ detail }) {
emit('confirm', detail) emit('confirm', detail)
} }
function handleLineChange(event) {
emit('linechange', event.detail)
}
function onClickSuffixIcon() { function onClickSuffixIcon() {
emit('clicksuffixicon') emit('clicksuffixicon')
} }
function onClickPrefixIcon() { function onClickPrefixIcon() {
emit('clickprefixicon') emit('clickprefixicon')
} }
function handleClick(event: MouseEvent) {
emit('click', event)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -5,7 +5,7 @@
.wot-theme-dark { .wot-theme-dark {
@include b(picker) { @include b(picker) {
@include when(border) { @include when(border) {
.wd-picker__field { .wd-picker__cell {
@include halfPixelBorder('top', $-cell-padding, $-dark-border-color); @include halfPixelBorder('top', $-cell-padding, $-dark-border-color);
} }
} }
@ -26,7 +26,7 @@
color: $-dark-color; color: $-dark-color;
} }
@include e(field) { @include e(cell) {
background-color: $-dark-background2; background-color: $-dark-background2;
color: $-dark-color; color: $-dark-color;
@include when(disabled) { @include when(disabled) {
@ -67,12 +67,12 @@
} }
@include when(border) { @include when(border) {
.wd-picker__field { .wd-picker__cell {
@include halfPixelBorder('top', $-cell-padding); @include halfPixelBorder('top', $-cell-padding);
} }
} }
@include when(large) { @include when(large) {
.wd-picker__field { .wd-picker__cell {
font-size: $-cell-title-fs-large; font-size: $-cell-title-fs-large;
} }
:deep(.wd-picker__arrow) { :deep(.wd-picker__arrow) {
@ -90,17 +90,17 @@
text-align: right; text-align: right;
} }
} }
@include e(field) { @include e(cell) {
position: relative; position: relative;
display: flex; display: flex;
padding: 0 $-cell-padding; padding: $-cell-wrapper-padding $-cell-padding;
align-items: flex-start; align-items: flex-start;
background-color: $-color-white; background-color: $-color-white;
text-decoration: none; text-decoration: none;
color: $-cell-title-color; color: $-cell-title-color;
font-size: $-cell-title-fs; font-size: $-cell-title-fs;
overflow: hidden; overflow: hidden;
line-height: $-cell-ling-height; line-height: $-cell-line-height;
@include when(disabled) { @include when(disabled) {
.wd-picker__value { .wd-picker__value {
@ -115,10 +115,17 @@
} }
} }
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
}
@include e(label) { @include e(label) {
position: relative; position: relative;
width: $-input-cell-label-width; width: $-input-cell-label-width;
padding: $-cell-wrapper-padding 0;
margin-right: $-cell-padding; margin-right: $-cell-padding;
color: $-cell-title-color; color: $-cell-title-color;
box-sizing: border-box; box-sizing: border-box;
@ -129,7 +136,7 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc($-cell-wrapper-padding + 2px); top: 2px;
content: '*'; content: '*';
font-size: $-cell-required-size; font-size: $-cell-required-size;
line-height: 1.1; line-height: 1.1;
@ -138,10 +145,12 @@
} }
} }
@include e(value-wraper) {
display: flex;
}
@include e(value) { @include e(value) {
width: 0;
flex: 1; flex: 1;
padding: $-cell-wrapper-padding 0;
margin-right: 10px; margin-right: 10px;
color: $-cell-value-color; color: $-cell-value-color;
@ -150,19 +159,22 @@
} }
} }
@include e(body) {
flex: 1;
}
@include e(placeholder) { @include e(placeholder) {
color: $-input-placeholder-color; color: $-input-placeholder-color;
} }
@include edeep(arrow) { @include edeep(arrow) {
display: block; display: block;
margin-top: $-cell-wrapper-padding;
font-size: $-cell-icon-size; font-size: $-cell-icon-size;
color: $-cell-arrow-color; color: $-cell-arrow-color;
line-height: 1.25; line-height: $-cell-line-height;
} }
@include e(body) { @include e(wraper) {
padding-bottom: var(--window-bottom); padding-bottom: var(--window-bottom);
} }

View File

@ -5,22 +5,27 @@
} ${error ? 'is-error' : ''} ${customClass}`" } ${error ? 'is-error' : ''} ${customClass}`"
> >
<!--文案--> <!--文案-->
<view @click="showPopup"> <view class="wd-picker__field" @click="showPopup">
<slot v-if="useDefaultSlot"></slot> <slot v-if="useDefaultSlot"></slot>
<view v-else class="wd-picker__field"> <view v-else class="wd-picker__cell">
<view <view
v-if="label || useLabelSlot" v-if="label || useLabelSlot"
:class="`wd-picker__label ${customLabelClass} ${required ? 'is-required' : ''}`" :class="`wd-picker__label ${customLabelClass} ${isRequired ? 'is-required' : ''}`"
:style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''" :style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''"
> >
<template v-if="label">{{ label }}</template> <template v-if="label">{{ label }}</template>
<slot v-else name="label"></slot> <slot v-else name="label"></slot>
</view> </view>
<view class="wd-picker__body">
<view class="wd-picker__value-wraper">
<view :class="`wd-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${showValue ? '' : 'wd-picker__placeholder'}`"> <view :class="`wd-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${showValue ? '' : 'wd-picker__placeholder'}`">
{{ showValue ? showValue : placeholder }} {{ showValue ? showValue : placeholder }}
</view> </view>
<wd-icon v-if="!disabled && !readonly" custom-class="wd-picker__arrow" name="arrow-right" /> <wd-icon v-if="!disabled && !readonly" custom-class="wd-picker__arrow" name="arrow-right" />
</view> </view>
<view v-if="errorMessage" class="wd-picker__error-message">{{ errorMessage }}</view>
</view>
</view>
</view> </view>
<!--弹出层picker-view 在隐藏时修改值会触发多次change事件从而导致所有列选中第一项因此picker在关闭时不隐藏 --> <!--弹出层picker-view 在隐藏时修改值会触发多次change事件从而导致所有列选中第一项因此picker在关闭时不隐藏 -->
<wd-popup <wd-popup
@ -33,7 +38,7 @@
@close="onCancel" @close="onCancel"
custom-class="wd-picker__popup" custom-class="wd-picker__popup"
> >
<view class="wd-picker__body"> <view class="wd-picker__wraper">
<!--toolBar--> <!--toolBar-->
<view class="wd-picker__toolbar" @touchmove="noop"> <view class="wd-picker__toolbar" @touchmove="noop">
<!--取消按钮--> <!--取消按钮-->
@ -84,6 +89,8 @@ import { getCurrentInstance, onBeforeMount, ref, watch, computed, onMounted, nex
import { deepClone, defaultDisplayFormat, getType } from '../common/util' import { deepClone, defaultDisplayFormat, getType } from '../common/util'
import { useCell } from '../composables/useCell' import { useCell } from '../composables/useCell'
import { type ColumnItem, formatArray } from '../wd-picker-view/type' import { type ColumnItem, formatArray } from '../wd-picker-view/type'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
interface Props { interface Props {
customClass?: string customClass?: string
@ -139,6 +146,8 @@ interface Props {
displayFormat?: Function displayFormat?: Function
// //
zIndex?: number zIndex?: number
prop?: string
rules?: FormItemRule[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -168,12 +177,12 @@ const props = withDefaults(defineProps<Props>(), {
closeOnClickModal: true, closeOnClickModal: true,
safeAreaInsetBottom: true, safeAreaInsetBottom: true,
ellipsis: false, ellipsis: false,
columnsHeight: 217, columnsHeight: 217,
valueKey: 'value', valueKey: 'value',
labelKey: 'label', labelKey: 'label',
columns: () => [], columns: () => [],
zIndex: 15 zIndex: 15,
rules: () => []
}) })
const pickerViewWd = ref<any>(null) const pickerViewWd = ref<any>(null)
@ -275,6 +284,31 @@ watch(
} }
) )
const { parent: form } = useParent(FORM_KEY)
//
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
const { proxy } = getCurrentInstance() as any const { proxy } = getCurrentInstance() as any
const emit = defineEmits(['confirm', 'open', 'cancel', 'update:modelValue']) const emit = defineEmits(['confirm', 'open', 'cancel', 'update:modelValue'])

View File

@ -47,14 +47,14 @@
@include e(cell) { @include e(cell) {
position: relative; position: relative;
display: flex; display: flex;
padding: 0 $-cell-padding; padding: $-cell-wrapper-padding $-cell-padding;
align-items: flex-start; align-items: flex-start;
background-color: $-color-white; background-color: $-color-white;
text-decoration: none; text-decoration: none;
color: $-cell-title-color; color: $-cell-title-color;
font-size: $-cell-title-fs; font-size: $-cell-title-fs;
overflow: hidden; overflow: hidden;
line-height: $-cell-ling-height; line-height: $-cell-line-height;
} }
@include e(cell) { @include e(cell) {
@include when(disabled) { @include when(disabled) {
@ -83,10 +83,17 @@
} }
} }
} }
@include e(error-message){
color: $-form-item-error-message-color;
font-size: $-form-item-error-message-font-size;
line-height: $-form-item-error-message-line-height;
text-align: left;
vertical-align: middle;
}
@include e(label) { @include e(label) {
position: relative; position: relative;
width: $-input-cell-label-width; width: $-input-cell-label-width;
padding: $-cell-wrapper-padding 0; color: $-cell-title-color;
margin-right: $-cell-padding; margin-right: $-cell-padding;
box-sizing: border-box; box-sizing: border-box;
@ -96,7 +103,7 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0; left: 0;
top: calc($-cell-wrapper-padding + 2px); top: 2px;
content: '*'; content: '*';
font-size: $-cell-required-size; font-size: $-cell-required-size;
line-height: 1.1; line-height: 1.1;
@ -104,9 +111,11 @@
} }
} }
} }
@include e(value-wraper) {
display: flex;
}
@include e(value) { @include e(value) {
flex: 1; flex: 1;
padding: $-cell-wrapper-padding 0;
margin-right: 10px; margin-right: 10px;
color: $-cell-value-color; color: $-cell-value-color;
@ -117,12 +126,14 @@
color: $-input-placeholder-color; color: $-input-placeholder-color;
} }
} }
@include e(body) {
flex: 1;
}
@include edeep(arrow) { @include edeep(arrow) {
display: block; display: block;
margin-top: $-cell-wrapper-padding;
font-size: $-cell-icon-size; font-size: $-cell-icon-size;
color: $-cell-arrow-color; color: $-cell-arrow-color;
line-height: 1.25; line-height: $-cell-line-height;
} }
@include e(selected) { @include e(selected) {
height: $-col-picker-selected-height; height: $-col-picker-selected-height;

View File

@ -10,12 +10,14 @@
> >
<view <view
v-if="label || useLabelSlot" v-if="label || useLabelSlot"
:class="`wd-select-picker__label ${required && 'is-required'} ${customLabelClass}`" :class="`wd-select-picker__label ${isRequired && 'is-required'} ${customLabelClass}`"
:style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''" :style="labelWidth ? 'min-width:' + labelWidth + ';max-width:' + labelWidth + ';' : ''"
> >
<block v-if="label">{{ label }}</block> <block v-if="label">{{ label }}</block>
<slot v-else name="label"></slot> <slot v-else name="label"></slot>
</view> </view>
<view class="wd-select-picker__body">
<view class="wd-select-picker__value-wraper">
<view <view
:class="`wd-select-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${ :class="`wd-select-picker__value ${ellipsis && 'is-ellipsis'} ${customValueClass} ${
showValue ? '' : 'wd-select-picker__value--placeholder' showValue ? '' : 'wd-select-picker__value--placeholder'
@ -25,6 +27,10 @@
</view> </view>
<wd-icon v-if="!disabled && !readonly" custom-class="wd-select-picker__arrow" name="arrow-right" /> <wd-icon v-if="!disabled && !readonly" custom-class="wd-select-picker__arrow" name="arrow-right" />
</view> </view>
<view v-if="errorMessage" class="wd-select-picker__error-message">{{ errorMessage }}</view>
</view>
</view>
</view> </view>
<wd-action-sheet <wd-action-sheet
v-model="pickerShow" v-model="pickerShow"
@ -102,9 +108,11 @@ export default {
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { getCurrentInstance, onBeforeMount, ref, watch, nextTick } from 'vue' import { getCurrentInstance, onBeforeMount, ref, watch, nextTick, computed } from 'vue'
import { useCell } from '../composables/useCell' import { useCell } from '../composables/useCell'
import { getRect, getType, isArray, isDef, requestAnimationFrame } from '../common/util' import { getRect, getType, isArray, isDef, requestAnimationFrame } from '../common/util'
import { useParent } from '../composables/useParent'
import { FORM_KEY, FormItemRule } from '../wd-form/types'
type SelectPickerType = 'checkbox' | 'radio' type SelectPickerType = 'checkbox' | 'radio'
@ -149,6 +157,8 @@ interface Props {
filterPlaceholder?: string filterPlaceholder?: string
ellipsis?: boolean ellipsis?: boolean
scrollIntoView?: boolean scrollIntoView?: boolean
prop?: string
rules?: FormItemRule[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -181,7 +191,8 @@ const props = withDefaults(defineProps<Props>(), {
filterable: false, filterable: false,
filterPlaceholder: '搜索', filterPlaceholder: '搜索',
ellipsis: false, ellipsis: false,
scrollIntoView: true scrollIntoView: true,
rules: () => []
}) })
const pickerShow = ref<boolean>(false) const pickerShow = ref<boolean>(false)
@ -249,6 +260,31 @@ watch(
} }
) )
const { parent: form } = useParent(FORM_KEY)
//
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
//
const isRequired = computed(() => {
let formRequired = false
if (form && form.rules) {
const rules = form.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
onBeforeMount(() => { onBeforeMount(() => {
selectList.value = valueFormat(props.modelValue) selectList.value = valueFormat(props.modelValue)
filterColumns.value = props.columns filterColumns.value = props.columns

View File

@ -1,7 +1,7 @@
/* /*
* @Author: weisheng * @Author: weisheng
* @Date: 2023-09-25 17:28:12 * @Date: 2023-09-25 17:28:12
* @LastEditTime: 2023-12-06 16:42:32 * @LastEditTime: 2023-12-23 16:26:58
* @LastEditors: weisheng * @LastEditors: weisheng
* @Description: * @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\global.d.ts * @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\global.d.ts
@ -93,6 +93,8 @@ declare module '@vue/runtime-core' {
WdCountDown: typeof import('./components/wd-count-down/wd-count-down.vue')['default'] WdCountDown: typeof import('./components/wd-count-down/wd-count-down.vue')['default']
WdNumberKeyboard: typeof import('./components/wd-number-keyboard/wd-number-keyboard.vue')['default'] WdNumberKeyboard: typeof import('./components/wd-number-keyboard/wd-number-keyboard.vue')['default']
WdGap: typeof import('./components/wd-gap/wd-gap.vue')['default'] WdGap: typeof import('./components/wd-gap/wd-gap.vue')['default']
WdForm: typeof import('./components/wd-form/wd-form.vue')['default']
WdTextarea: typeof import('./components/wd-textarea/wd-textarea.vue')['default']
} }
} }