This commit is contained in:
yyy 2024-12-03 18:07:26 +08:00
parent c44b46605d
commit 6f924ed29a
21 changed files with 2414 additions and 557 deletions

View File

@ -73,4 +73,10 @@ export function getListByDeptId(data) {
return api.get('/bpm/oa/contract/getListByDeptId', data, {
login: false
})
}
}
// 获取部门类型为公司的部门信息
export function getCompanyDept(data) {
return api.get('/system/dept/getCompanyDept', data, {
login: false
})
}

View File

@ -1,32 +1,107 @@
import api from './api'
import {
VUE_APP_API_URL
} from '@/config'
import cookie from '@/utils/cookie'
export function userGetUserInfo(data) {
return api.get('/system/user/profile/get', data, { login: true })
return api.get('/system/user/profile/get', data, {
login: true
})
}
export function getSimpleUserList(data) {
return api.get('/system/user/page', data, { login: true })
return api.get('/system/user/page', data, {
login: true
})
}
export function getSimpleUserList2(data) {
return api.get('/system/user/simple-list', data, { login: true })
return api.get('/system/user/simple-list', data, {
login: true
})
}
export function getFlowUsers(data) {
return api.get('/crm/flow/flow-users', data, { login: true })
return api.get('/crm/flow/flow-users', data, {
login: true
})
}
export function updateUser(data) {
return api.put('/system/user/profile/update', data, { login: true })
return api.put('/system/user/profile/update', data, {
login: true
})
}
export function updateUserProfilePassword(data) {
return api.put('/system/user/profile/update-password', data, { login: true })
return api.put('/system/user/profile/update-password', data, {
login: true
})
}
export function getAllUserList(data) {
return api.get('/system/user/list-all', data, {
login: true
})
}
export function deleteBpmFile(data) {
return api.delete(`/infra/file/deleteBpmFile?url=${data.url}`, data, {
login: true
})
}
//上传文件 流程的
export function uploadFile(filePath, cancelLoading) {
return new Promise((resolve, reject) => {
if (!cancelLoading) {
uni.showLoading({
title: '上传中'
})
}
let url = VUE_APP_API_URL + '/infra/file/bpmUpload'
uni.showLoading({
title: '正在上传'
})
uni.uploadFile({
url,
name: 'uploadFiles',
filePath: filePath,
header: {
'Authorization': 'Bearer ' + cookie.get('accessToken'),
'tenant-id': '1'
},
success: res => {
console.log(res)
uni.hideLoading()
if (JSON.parse(res.data).code == 200 || JSON.parse(res.data).code == 0) {
resolve(JSON.parse(res.data).data)
} else if (JSON.parse(res.data).code == 401) {
uni.showToast({
title: '登录过期,请重新登录',
});
setTimeout(() => {
uni.reLaunch({
url: '/pages/components/pages/login/login'
})
}, 1000)
reject(res)
} else {
uni.showToast({
title: JSON.parse(res.data).msg
});
reject(res)
}
},
fail: err => {
uni.hideLoading()
reject(err)
}
})
})
}

View File

@ -0,0 +1,81 @@
## 适用于组织架构的基础展示
使用
```
<xtx-treeNode :list="list" @change="treeChange"></xtx-treeNode>
```
list数据结构
```
list: [{
id: '1',
name: '机构一',
children: [
{
id: '101',
name: '人事部',
children: []
},{
id: '102',
name: '总部',
children: [
{
id: '1021',
name: '选项一',
children: []
},{
id: '1022',
name: '选项二',
children: []
},{
id: '1023',
name: '选项三',
children: []
}
]
},{
id: '103',
name: '后勤部',
children: []
}
]
},{
id: '12',
name: '机构二',
children: [
{
id: '10121',
name: '选项一',
children: []
},{
id: '10122',
name: '选项二',
children: []
},{
id: '10123',
name: '选项三',
children: []
}
]
},{
id: '13',
name: '机构三',
children: [{
id: '10131',
name: '选项一',
children: []
},{
id: '10132',
name: '选项二',
children: []
},{
id: '10133',
name: '选项三',
children: []
}]
}]
```
## 事件说明
| 事件名 | 说明 | 回调 |
| ------- | ------- | ------- |
| change | 点击node节点触发 | ({ id: '10131', name: '选项一', children: []})=>{} |

View File

@ -0,0 +1,70 @@
<template>
<view>
<view
class="item--list"
v-for="(item, index) in list"
:key="index"
>
<view class="item--title" hover-class="tree__hover-class" @click="selectNode(item)">
<view>{{ item.name }}</view>
<view
v-if="item.children && item.children.length"
class="open__and--close"
@click.stop="handleOpenClose(item, index)"
>
{{ item.isOpen?'收起':'展开'}}</view>
</view>
<view v-if="item.isOpen && item.children && item.children.length" class="">
<treeItem :list="item.children" @change="selectNode"></treeItem>
</view>
</view>
</view>
</template>
<script>
import treeItem from '../xtx-treeNode/xtx-treeNode'
export default {
name: 'treeItem',
components: {
treeItem
},
props: {
list: {
item: Array,
default: () => []
}
},
methods: {
selectNode(item) {
this.$emit('change', item)
},
handleOpenClose(item, index) {
if (!item.hasOwnProperty('isOpen')) {
item.isOpen = false
}
item.isOpen = !item.isOpen
this.$forceUpdate()
}
}
}
</script>
<style scoped>
.item--list {
padding-left: 25rpx;
}
.item--title {
display: flex;
align-items: center;
font-size: 28rpx;
border-bottom: 1rpx solid #f7f7f7;
padding: 25rpx;
}
.open__and--close {
margin-left: auto;
font-size: 24rpx;
}
.tree__hover-class {
background-color: #f7f7f7;
}
</style>

View File

@ -362,12 +362,14 @@
"text": "客户",
"iconPath": "static/images/tabBar/customer.png",
"selectedIconPath": "static/images/tabBar/customer-selected.png"
}, {
"pagePath": "pages/msg/index",
"text": "消息",
"iconPath": "static/images/tabBar/msg.png",
"selectedIconPath": "static/images/tabBar/msg-select.png"
}, {
},
//{
// "pagePath": "pages/msg/index",
// "text": "消息",
// "iconPath": "static/images/tabBar/msg.png",
// "selectedIconPath": "static/images/tabBar/msg-select.png"
// },
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "static/images/tabBar/mine.png",

View File

@ -9,16 +9,16 @@
<uv-input v-model="form.contractNo" :disabled="isCheck" />
</uv-form-item> -->
<uv-form-item required label="合同名称:" prop="contractName">
<uv-input v-model="form.contractName" :disabled="isCheck" />
<uv-input v-model="form.contractName" :disabled="isCheck" placeholder="请输入合同名称" />
</uv-form-item>
<uv-form-item label="选择客户:" @click="type != 'check' ? selectShow() : ''" prop="customerName" required>
<uv-input disabledColor="#ffffff" disabled v-model="form.customerName" placeholder="选择客户">
<uv-form-item label="选择客户:" @click="type !== 'detail' ? selectShow() : ''" prop="customerName" required>
<uv-input disabledColor="#ffffff" disabled="true" v-model="form.customerName" placeholder="选择客户">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
</template>
</uv-input>
</uv-form-item>
<uv-form-item label="选择商机:" @click="type != 'check' ? selectShow4() : ''">
<uv-form-item label="选择商机:" @click="type !== 'detail' ? selectShow4() : ''">
<uv-input disabledColor="#ffffff" disabled v-model="form.businessName" placeholder="选择商机">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
@ -26,38 +26,45 @@
</uv-input>
</uv-form-item>
<uv-form-item label="合同金额:" prop="contractMoney" required>
<uv-input type="number" v-model="form.contractMoney" :disabled="isCheck" />
<uv-input type="number" v-model="form.contractMoney" :disabled="isCheck" placeholder="请输入合同金额" />
</uv-form-item>
<uv-form-item label="下单时间 :" @click="type != 'check' ? createTimeShow() : ''">
<uv-input v-model="orderTime" placeholder="选择下单时间 " disabled disabledColor="#ffffff">
<uv-form-item label="签约时间 :" @click="type !== 'detail' ? createTimeShow() : ''">
<uv-input v-model="form.signingDate" placeholder="选择签约时间 " disabled disabledColor="#ffffff">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
</template>
</uv-input>
</uv-form-item>
<uv-form-item label="开始时间 :" @click="type != 'check' ? createTimeShow2() : ''">
<uv-input v-model="startTime" placeholder="选择开始时间 " disabled disabledColor="#ffffff">
<uv-form-item label="开始时间 :" @click="type !== 'detail' ? createTimeShow2() : ''">
<uv-input v-model="form.startDate" placeholder="选择开始时间 " disabled disabledColor="#ffffff">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
</template>
</uv-input>
</uv-form-item>
<uv-form-item label="结束时间 :" @click="type != 'check' ? createTimeShow3() : ''">
<uv-input v-model="endTime" placeholder="选择结束时间 " disabled disabledColor="#ffffff">
<uv-form-item label="结束时间 :" @click="type !== 'detail' ? createTimeShow3() : ''">
<uv-input v-model="form.endDate" placeholder="选择结束时间 " disabled disabledColor="#ffffff">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
</template>
</uv-input>
</uv-form-item>
<uv-form-item label="客户签约人:" @click="type != 'check' ? selectShow2() : ''" required>
<uv-form-item label="客户签约人:" @click="type !== 'detail' ? selectShow2() : ''" required>
<uv-input disabledColor="#ffffff" disabled v-model="form.contactsName" placeholder="选择签约人">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
</template>
</uv-input>
</uv-form-item>
<uv-form-item label="公司签约人:" @click="type != 'check' ? selectShow3() : ''" required>
<uv-input disabledColor="#ffffff" disabled v-model="form.orderAdminName" placeholder="选择签约人">
<uv-form-item label="签约公司:" @click="type !== 'detail' ? selectShow6() : ''" required>
<uv-input disabledColor="#ffffff" disabled v-model="form.companyName" placeholder="选择签约公司">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
</template>
</uv-input>
</uv-form-item>
<uv-form-item label="公司签约人:" @click="type !== 'detail' ? selectShow3() : ''" required>
<uv-input disabledColor="#ffffff" disabled v-model="form.signatoryName" placeholder="选择签约人">
<template v-slot:suffix>
<uv-icon name="arrow-right"></uv-icon>
</template>
@ -71,7 +78,7 @@
</uv-form-item>
</uv-form>
<!-- 产品 -->
<view class="flex cif-title" @click="selectProduct">
<view class="flex cif-title" @click="type !== 'detail' ? selectProduct() : ''">
<view class="flex-1 text">产品关联</view>
<view class="flex align-center" style="color:#2979ff">
选择产品<uv-icon name="arrow-right" color="#909399" size="20"></uv-icon>
@ -82,15 +89,13 @@
<view class="item" v-for="(item,index) in selectList" :key="index">
<view class="number flex">
<view class="flex-1">序号: {{index+1}}</view>
<uv-number-box :min="1" v-model="item.nums" :longPress="false"
@change="isCheck ? valChange() : ''"></uv-number-box>
<uni-number-box :min="1" v-model="item.nums" @change="valChange" :disabled="isCheck" />
</view>
<view class="content flex align-center">
<view class="flex-1 storeName">{{item.storeName ? item.storeName : item.name}}({{item.sku}})</view>
<view class="price flex align-center">
<view class="pr-1" style="font-weight: 600;"></view>
<uv-input :disabled="true" type="number" :clearable="false" placeholder="请输入价格" v-model="item.price"
@input="isCheck ? inputChang : ''" />
<uv-input :disabled="true" type="number" :clearable="false" placeholder="请输入价格" v-model="item.price" />
</view>
</view>
<view class="brief flex align-center">
@ -98,7 +103,7 @@
<uv-input v-model="item.remarks" :disabled="isCheck" />
</view>
<!-- 删除按钮 -->
<view class="close" @click="type != 'check' ? remove(index) : ''">
<view class="close" @click="type !== 'detail' ? remove(index) : ''">
<uv-icon name="close" color="#909399" size="20"></uv-icon>
</view>
</view>
@ -107,25 +112,62 @@
</view>
<view class="flex mb-1 mt-1 align-center">
<view class="mr-1" style="width:150rpx">优惠金额:</view>
<view class="flex-1"><uv-input type="number" v-model="form.discountRate" :disabled="isCheck"
placeholder="请填写0到100的数" @input="inputChang" /></view>
<view class="flex-1">
<uni-easyinput type="number" :disabled="isCheck" placeholder="请填写优惠金额" @blur="inputChang"
v-model="form.discountRate"></uni-easyinput>
</view>
</view>
<view class="flex mb-1 mt-3 align-center">
<view class="mr-1" style="width: 150rpx">总额:</view>
<view class="flex-1"><uv-input type="number" v-model="form.totalPrice" :disabled="isCheck" /></view>
<view class="flex-1"><uv-input type="number" v-model="form.totalPrice" :disabled="true" /></view>
</view>
<view class="mt-4" style="text-align: center;" v-if="!isCheck">
<uv-button color="#09b4f1" @click="submit" :disabled="form.checkStatus == 1 || form.checkStatus == 2"
:ripple="true">确定提交</uv-button>
<view class="container-item" style="flex-direction: column;align-items: flex-start;border-bottom: none;">
<view class="container-item-upload">
<view class="container-item-label">图片</view>
</view>
<view class="item-photo-list">
<view class="item-photo-list-item" style="font-size: 0; position: relative;margin-bottom: 60rpx;"
:style="{marginRight:(index+1)%3==0?'0rpx':'20rpx'}" v-for="(item, index) in imgFiles" :key="item"
@click="replaceImage(item, index)">
<image v-if="!isCheck" src="@/static/greyguanbi.png" mode="aspectFit" class="item-photo-list-item-close"
@click.stop="deleteItem(item, index)"></image>
<image v-if="!isCheck" :src="item.url" mode="" style="width: 100%; height: 100%"></image>
</view>
<image src="http://sys.znkjfw.com/imgs/process/uploadzp.png" mode="aspectFill" class="item-photo-list-item"
v-if="imgFiles.length<9 && !isCheck" @click="chooseImage"></image>
</view>
</view>
<view class="mt-4 flex justify-end" style="text-align: center;" v-else>
<view class="container-item" style="flex-direction: column;align-items: flex-start;border-bottom: none;">
<view class="container-item-upload">
<view class="container-item-label">附件 pdf</view>
</view>
<view class="item-photo-list">
<view class="item-photo-list-item" style="font-size: 0; position: relative;margin-bottom: 60rpx;"
:style="{marginRight:(index+1)%3==0?'0rpx':'20rpx'}" v-for="(item, index) in pdfList" :key="item">
<image v-if="!isCheck" src="@/static/greyguanbi.png" mode="aspectFit" class="item-photo-list-item-close"
@click.stop="deleteFiles(item, index)"></image>
<image v-if="!isCheck" src="http://sys.znkjfw.com/imgs/process/pdfIcon.png" mode=""
style="width: 100%; height: 100%">
</image>
<view class="item-photo-list-item-name">
{{item.name || ''}}
</view>
</view>
<image v-if="!isCheck" src="http://sys.znkjfw.com/imgs/process/uploadzp.png" mode="aspectFill"
class="item-photo-list-item" @click="chooseFiles"></image>
</view>
</view>
<view class="mt-4" style="text-align: center;">
<uv-button color="#09b4f1" @click="submit" :disabled="isCheck" :ripple="true">确定提交</uv-button>
</view>
<!-- <view class="mt-4 flex justify-end" style="text-align: center;" v-else>
<view style="width: 25%;margin-right: 10rpx;">
<uv-button color="#09b4f1" @click="submitCheck(1)">审核</uv-button>
</view>
<view style="width: 25%;">
<uv-button type="error" @click="selectShow5()">拒绝</uv-button>
</view>
</view>
</view> -->
</view>
<uv-popup mode="bottom" round="38" ref="selectShowRef5">
<view class="popup-content">
@ -134,7 +176,7 @@
</view>
<text class="">输入驳回理由</text>
<view class="" @click="selectShowClose5" style="width: 45px;">
<uv-icon name="close" color="#909399" size="30"></uv-icon>
<uv-icon name="close" color="#909399" size="24"></uv-icon>
</view>
</view>
<view style="height: 500rpx;padding: 20rpx;">
@ -143,7 +185,6 @@
<view style="padding: 20rpx;">
<uv-button color="#09b4f1" @click="submitCheck(2)">确定</uv-button>
</view>
</view>
</uv-popup>
<!-- 选择客户弹窗 -->
@ -154,7 +195,7 @@
</view>
<text class="">选择客户</text>
<view class="" @click="selectShowClose" style="width: 45px;">
<uv-icon name="close" color="#909399" size="30"></uv-icon>
<uv-icon name="close" color="#909399" size="24"></uv-icon>
</view>
</view>
<uv-search margin="30rpx 20rpx" shape="square" v-model="keyword" :show-action="false" :clearabled="true"
@ -184,7 +225,7 @@
</view>
<text class="">选择客户签约人</text>
<view class="" @click="selectShowClose2" style="width: 45px;">
<uv-icon name="close" color="#909399" size="30"></uv-icon>
<uv-icon name="close" color="#909399" size="24"></uv-icon>
</view>
</view>
<scroll-view scroll-y style="height: 760rpx;width: 100%;">
@ -212,17 +253,17 @@
</view>
<text class="">选择公司签约人</text>
<view class="" @click="selectShowClose3" style="width: 45px;">
<uv-icon name="close" color="#909399" size="30"></uv-icon>
<uv-icon name="close" color="#909399" size="24"></uv-icon>
</view>
</view>
<uv-search margin="30rpx 20rpx" shape="square" v-model="keyword3" :show-action="false" :clearabled="true"
placeholder="输入用户名称搜索" @change="onSearch3"></uv-search>
<scroll-view scroll-y style="height: 760rpx;width: 100%;" @scrolltolower="reachBottom3">
<uv-search margin="10rpx 26rpx 30rpx 26rpx" shape="square" v-model="keyword3" :show-action="false"
:clearabled="true" placeholder="输入用户名称搜索" @change="onSearch3"></uv-search>
<scroll-view scroll-y style="height: 760rpx;width: 100%;">
<view class="list">
<block v-if="userList.length > 0">
<view class="mb-4">
<view class="item flex" v-for="(item,index) in userList" :key="index" @click="onItem3(item,index)">
<view class="title">{{item.username}}</view>
<view class="title">{{item.nickname}}</view>
<view class="check-icon">
<uv-icon v-if="item.checked" name="checkmark" color="#09b4f1" size="28"></uv-icon>
</view>
@ -242,10 +283,10 @@
</view>
<text class="">选择商机</text>
<view class="" @click="selectShowClose4" style="width: 45px;">
<uv-icon name="close" color="#909399" size="30"></uv-icon>
<uv-icon name="close" color="#909399" size="24"></uv-icon>
</view>
</view>
<scroll-view scroll-y style="height: 760rpx;width: 100%;" @scrolltolower="reachBottom3">
<scroll-view scroll-y style="height: 760rpx;width: 100%;">
<view class="list">
<block v-if="businessList.length > 0">
<view class="mb-4">
@ -262,11 +303,40 @@
</scroll-view>
</view>
</uv-popup>
<uv-datetime-picker ref="datetimePicker" v-model="timeValue" mode="datetime"
<!-- 签约公司 -->
<uv-popup mode="bottom" round="38" ref="selectShowRef6">
<view class="popup-content">
<view class="popup-title">
<view class="" style="width: 45px;">
</view>
<text class="">选择签约公司</text>
<view class="" @click="selectShowClose6" style="width: 45px;">
<uv-icon name="close" color="#909399" size="24"></uv-icon>
</view>
</view>
<scroll-view scroll-y style="height: 760rpx;width: 100%;">
<view class="list">
<block v-if="companyList.length > 0">
<view class="mb-4">
<view class="item flex" v-for="(item,index) in companyList" :key="index" @click="onItem6(item,index)">
<view class="title">{{item.name}}</view>
<view class="check-icon">
<uv-icon v-if="item.checked" name="checkmark" color="#09b4f1" size="28"></uv-icon>
</view>
</view>
</view>
</block>
<uv-empty text="暂无数据" v-else mode="list"></uv-empty>
</view>
</scroll-view>
</view>
</uv-popup>
<uv-datetime-picker ref="datetimePicker" v-model="timeValue" mode="date"
@confirm="createTimeChange"></uv-datetime-picker>
<uv-datetime-picker ref="datetimePicker2" v-model="timeValue2" mode="datetime"
<uv-datetime-picker ref="datetimePicker2" v-model="timeValue2" mode="date"
@confirm="createTimeChange2"></uv-datetime-picker>
<uv-datetime-picker ref="datetimePicker3" v-model="timeValue2" mode="datetime"
<uv-datetime-picker ref="datetimePicker3" v-model="timeValue2" mode="date"
@confirm="createTimeChange3"></uv-datetime-picker>
</view>
</template>
@ -283,8 +353,7 @@
} from '@dcloudio/uni-app'
import {
getCustomerListPage,
getContactsPage,
createOAContract
getContactsPage
} from '@/api/customer'
import {
getBusinessProductListByBusinessId,
@ -296,17 +365,24 @@
getContract,
getContractProductListByContractId,
updateContract,
check
check,
getCompanyDept,
createOAContract,
getOAContract
} from '@/api/contract'
import {
getSimpleUserList,
getFlowUsers,
getSimpleUserList2
getSimpleUserList2,
getAllUserList,
uploadFile,
deleteBpmFile
} from '@/api/user'
import AddressSelect from '@/components/AddressSelect/index.vue'
import {
formatDateTime,
prePage
timestampToDate,
prePage,
getFileType
} from '@/utils/util'
import {
useMainStore
@ -314,6 +390,9 @@
import {
storeToRefs
} from 'pinia'
import {
deepClone
} from '../../../../uni_modules/uv-ui-tools/libs/function'
const main = useMainStore()
const {
selectProductList
@ -327,7 +406,7 @@
const goodsName = ref('')
const page = ref(1)
const page3 = ref(1)
const pageSize = ref(10)
const pageSize = ref(15)
const lastPage = ref(false)
const lastPage3 = ref(false)
const selectList = ref([])
@ -335,33 +414,30 @@
const type = ref('add')
const contractId = ref(0)
const customerId = ref(0)
const nextTime = ref('')
const orderTime = ref(formatDateTime(new Date()))
const startTime = ref(formatDateTime(new Date()))
const endTime = ref(formatDateTime(new Date()))
const content = ref('')
const customerName = ref('')
const form = ref({
fileItems: [], //
contractType: 1, //
contractNo: '', //
contractName: '', //
customerId: '', //ID
customerName: '', //
nextTime: '', //
startDate: new Date(), //
orderTime: new Date(), //
endDate: new Date(), //
startDate: timestampToDate(new Date()), //
signingDate: timestampToDate(new Date()), //
endDate: timestampToDate(new Date()), //
remark: '', //
discountRate: '', //
totalPrice: '', //
discountRate: 0, //
totalPrice: 0, //
contactsName: '', //
contactsId: '', //id
orderAdminName: '', //
companyId: '', //
companyName: '', //
businessId: '', //ID
invoiceMoney: '', //
returnMoney: '', // /
contractMoney: '', //
signatoryId: '', //
signatoryName: '', //
})
const timeText = ref('')
const errorType = ref('message')
@ -381,55 +457,147 @@
})
onLoad((e) => {
if (e.type) {
type.value = e.type
}
if (type.value == 'check') {
type.value = e.type
if (type.value == 'detail') {
isCheck.value = true
title.value = '审核合同'
title.value = '合同详情'
contractId.value = e.id
getContractInfo()
}
if (type.value == "edit") {
title.value = '编辑合同'
contractId.value = e.id
getContractInfo()
} else {
// getNumber()
}
getUserList() //
getCustomerList()
getUserList()
// getFlowUsersList()
getCompanyList() //
})
onShow(() => {
selectList.value = selectProductList.value
selectList.value = selectProductList.value || []
count_price()
})
const getFlowUsersList = async () => {
let data = await getFlowUsers({
flowType: 'contract'
//
const imgFiles = ref([]) //
const pdfList = ref([]) //pdf
//
const deleteFiles = (val, i) => {
deleteBpmFile({
url: val.url
}).then(res => {
pdfList.value.splice(i, 1)
})
let users = await getSimpleUserList2()
let userIdArr = []
let userArr = []
for (var i = 0; i <= data.length; i++) {
users.forEach((item, index) => {
if (data[i] == item.id) {
userIdArr.push(item.id)
userArr.push(item.nickname)
}
//
const uploadFileApi = (url) => {
return new Promise((reslove, reject) => {
uploadFile(url, true)
.then((res) => {
console.log(res, 'success')
reslove(res);
})
.catch((err) => {
console.log(err, 'err')
reject(err);
});
});
}
//
const getExtension = (fileName) => {
let arr = fileName.split('.');
return arr[arr.length - 1];
}
//
const chooseImage = () => {
uni.chooseImage({
count: 9 - imgFiles.value.length, //9
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success: (res) => {
let promise = Promise.resolve();
let arr = [];
uni.showLoading({
title: '上传中'
})
res.tempFilePaths.forEach((item) => {
promise = promise.then(() => uploadFileApi(item));
promise
.then((ress) => {
arr.push(1)
imgFiles.value.push({
name: ress.name,
url: ress.url
})
if (arr.length >= res.tempFilePaths.length) {
uni.hideLoading()
}
})
.catch((err) => {
uni.hideLoading()
});
});
}
});
}
// pdf
const chooseFiles = () => {
wx.chooseMessageFile({
count: 9 - pdfList.value.length, //100
extension: ['pdf', '.pdf'],
success: (res) => {
let isUploadFile = true
res.tempFiles.forEach(item => {
if (getExtension(item.name) != 'pdf') {
isUploadFile = false
}
})
if (!isUploadFile) {
uni.showToast({
title: '请您上传pdf格式文件',
});
return
}
let promise = Promise.resolve();
let arr = [];
uni.showLoading({
title: '上传中'
})
res.tempFiles.forEach((item) => {
promise = promise.then(() => uploadFileApi(item.path));
promise
.then((ress) => {
arr.push(1)
pdfList.value.push({
name: ress.name,
url: ress.url
})
if (arr.length >= res.tempFilePaths.length) {
uni.hideLoading()
}
})
.catch((err) => {
uni.hideLoading()
});
});
}
});
}
//
const companyList = ref([])
const getCompanyList = async () => {
companyList.value = await getCompanyDept()
if (form.value.companyId) {
companyList.value.forEach((item, index) => {
if (form.value.companyId == item.id) {
item.checked = true
} else {
item.checked = false
}
})
}
form.value.flowAdminIdName = userArr.join(',')
form.value.flowAdminId = userIdArr
}
//
const getNumber = async () => {
form.value.contractNo = await getContractNo()
}
const selectShowRef = ref()
const selectShow = () => {
@ -446,8 +614,7 @@
//
const createTimeChange = (e) => {
form.value.orderTime = e.value
orderTime.value = formatDateTime(e.value)
form.value.signingDate = timestampToDate(e.value)
}
const datetimePicker2 = ref()
@ -457,8 +624,19 @@
//
const createTimeChange2 = (e) => {
form.value.startDate = e.value
startTime.value = formatDateTime(e.value)
form.value.startDate = timestampToDate(e.value)
if (form.value.endDate) {
//
let startDate = new Date(form.value.startDate);
let endDate = new Date(form.value.endDate);
if (endDate < startDate) {
form.value.endDate = ''
uni.showToast({
icon: 'none',
title: '结束时间不能大于开始时间'
})
}
}
}
const datetimePicker3 = ref()
@ -468,15 +646,36 @@
//
const createTimeChange3 = (e) => {
form.value.endDate = e.value
endTime.value = formatDateTime(e.value)
form.value.endDate = timestampToDate(e.value)
if (form.value.startDate) {
//
let startDate = new Date(form.value.startDate);
let endDate = new Date(form.value.endDate);
if (endDate < startDate) {
form.value.endDate = ''
uni.showToast({
icon: 'none',
title: '结束时间不能大于开始时间'
})
}
}
}
//
const getContractInfo = async () => {
form.value = await getContract({
form.value = await getOAContract({
id: contractId.value
})
form.value.flowAdminId = form.value.flowAdminId2
if (form.value.fileItems.length) {
form.value.fileItems.forEach(item => {
if (getFileType(item.name) == 'image') {
imgFiles.value.push(item)
}
if (getFileType(item.name) == 'pdf') {
pdfList.value.push(item)
}
})
}
selectList.value = await getContractProductListByContractId({
contractId: contractId.value
})
@ -493,7 +692,6 @@
uni.navigateTo({
url: '/pages/components/pages/business/selectProduct'
});
}
const getCustomerList = async (isNextPage, pages) => {
@ -505,7 +703,7 @@
}).then(res => {
if (res) {
//
if (res.list.length < 10) {
if (res.list.length < pageSize.value) {
listStatus.value = 'nomore'
}
//
@ -534,37 +732,20 @@
}
const getUserList = async (isNextPage, pages) => {
await getSimpleUserList({
pageNo: page.value,
pageSize: pageSize.value,
await getAllUserList({
username: keyword3.value,
deptId: form.value.companyId
}).then(res => {
if (res) {
//
if (res.list.length < 10) {
listStatus3.value = 'nomore'
}
//
if (res.list.length == 0) {
lastPage3.value = true
}
//
if (isNextPage) {
userList.value = userList.value.concat(res.list)
return
}
userList.value = res.list
if (form.value.orderAdminId) {
userList.value.forEach((item, index) => {
if (form.value.orderAdminId == item.id) {
item.checked = true
username.value = item.username
} else {
item.checked = false
}
})
}
userList.value = res
if (form.value.signatoryId) {
userList.value.forEach((item, index) => {
if (form.value.signatoryId == item.id) {
item.checked = true
username.value = item.nickname
} else {
item.checked = false
}
})
}
})
}
@ -574,28 +755,16 @@
const reachBottom = () => {
if (lastPage.value || listStatus.value == 'loading') return
listStatus.value = 'loading'
setTimeout(() => {
getCustomerList(true, ++page.value)
if (customerList.value.length >= 10) {
listStatus.value = 'loadmore';
} else {
listStatus.values = 'loading';
}
}, 1200)
page.value++
getCustomerList(true, page.value)
if (customerList.value.length >= pageSize.value) {
listStatus.value = 'loadmore';
} else {
listStatus.values = 'loading';
}
}
const reachBottom3 = () => {
if (lastPage3.value || listStatus3.value == 'loading') return
listStatus3.value = 'loading'
setTimeout(() => {
getUserList(true, ++page3.value)
if (userList.value.length >= 10) {
listStatus3.value = 'loadmore';
} else {
listStatus3.values = 'loading';
}
}, 1200)
}
//
const onItem = async (val, i) => {
customerList.value.forEach((item, index) => {
@ -607,7 +776,6 @@
}
})
form.value.customerId = val.id
form.value.customerName = val.name ? val.name : ''
//
let data = await getContactsPage({
@ -640,7 +808,6 @@
}
})
form.value.contactsId = val.id
form.value.contactsName = val.name ? val.name : ''
selectShowClose2()
}
@ -658,13 +825,12 @@
userList.value.forEach((item, index) => {
if (val.id == item.id) {
item.checked = true
val.username = val.username ? val.username : item.username;
} else {
item.checked = false
}
})
form.value.orderAdminId = val.id
form.value.orderAdminName = val.username ? val.username : ''
form.value.signatoryId = val.id
form.value.signatoryName = val.nickname ? val.nickname : ''
selectShowClose3()
}
@ -690,7 +856,6 @@
selectList.value = await getBusinessProductListByBusinessId({
businessId: val.id
})
selectShowClose4()
}
@ -702,12 +867,52 @@
unref(selectShowRef5).close()
}
//
const selectShowRef6 = ref()
const selectShow6 = async () => {
unref(selectShowRef6).open()
}
const selectShowClose6 = () => {
unref(selectShowRef6).close()
}
const onItem6 = (val, i) => {
companyList.value.forEach((item, index) => {
if (val.id == item.id) {
item.checked = true
} else {
item.checked = false
}
})
form.value.companyId = val.id
form.value.companyName = val.name
userList.value = []
page3.value = 1
getUserList()
selectShowClose6()
}
//
const valChange = () => {
const valChange = (e) => {
count_price()
}
//
const inputChang = () => {
const inputChang = (e) => {
if (e.detail.value > form.value.totalPrice) {
form.value.discountRate = 0
uni.showToast({
icon: 'none',
title: '折扣金额不能大于总金额'
})
}
if (e.detail.value < 0) {
form.value.discountRate = 0
uni.showToast({
icon: 'none',
title: '折扣金额不能小于0'
})
}
count_price()
}
//
@ -717,16 +922,18 @@
}
//
const count_price = () => {
let total = 0;
setTimeout(() => {
for (let i = 0; i < selectList.value.length; i++) {
total += selectList.value[i].nums * selectList.value[i].price
}
if (form.value.discountRate > 0) {
total = total - (form.value.discountRate / 100) * total
let list = deepClone(selectList.value)
if (list.length > 0) {
let total = 0;
for (let i = 0; i < list.length; i++) {
total = total + (list[i].nums * list[i].price)
}
total = total - Number(form.value.discountRate || 0)
form.value.totalPrice = total.toFixed(2)
}, 500)
} else {
form.value.totalPrice = 0
}
}
const submitCheck = async (type) => {
@ -785,7 +992,7 @@
})
return
}
if (form.value.orderAdminIdName == '') {
if (form.value.signatoryId == '') {
uni.showToast({
icon: 'error',
title: '请选择公司签约人'
@ -819,11 +1026,10 @@
obj.subtotal = item.nums * item.price
newArr[index] = obj
})
if (type.value == 'add') {
if (type.value !== 'detail') {
form.value.contractProducts = newArr
await createContract(form.value)
form.value.fileItems = [...imgFiles.value, ...pdfList.value]
await createOAContract(form.value)
uni.showToast({
title: '添加成功',
icon: 'success',
@ -834,9 +1040,6 @@
uni.navigateBack();
}, 1000);
} else {
// selectList.value.forEach((item,index)=>{
// item.subtotal = item.nums * item.price
// })
form.value.contractProducts = newArr
await updateContract(form.value)
uni.showToast({
@ -855,7 +1058,7 @@
<style lang="scss" scoped>
.set-box {
padding: 0rpx 22rpx 34rpx 22rpx;
padding: 0rpx 22rpx 30rpx 34rpx;
margin-bottom: 80rpx;
.cif-title {
@ -924,7 +1127,7 @@
font-size: 35rpx;
font-weight: 600;
text-align: center;
height: 50px;
height: 64px;
padding-right: 25rpx;
}
@ -932,19 +1135,17 @@
margin-bottom: 45rpx;
.item {
padding: 0 25rpx;
padding: 0 20rpx 30rpx 30rpx;
justify-content: space-between;
height: 45px;
.title {
flex: 1;
font-size: 28rpx;
font-weight: 600;
font-size: 29rpx;
}
.check-icon {
text-align: center;
width: 100rpx;
// width: 100rpx;
}
}
}
@ -959,4 +1160,69 @@
.storeName {
font-size: 30rpx;
}
.container-item {
width: 100%;
padding: 25rpx 0;
border-bottom: 1rpx solid #e8e8e8;
display: flex;
align-items: center;
justify-content: space-between;
}
.container-item-label {
flex-shrink: 0;
color: #020202;
font-weight: 400;
font-face: PingFang SC;
font-size: 30rpx;
margin-right: 15rpx;
}
.item-photo-list {
display: flex;
flex-wrap: wrap;
padding: 0 30rpx;
margin-top: 20rpx;
}
.item-photo-list-item-name {
position: absolute;
width: 100%;
left: 0;
bottom: -40rpx;
font-size: 22rpx;
text-align: center;
overflow: hidden;
/* 确保超出容器的文本被裁剪 */
white-space: nowrap;
/* 确保文本在一行内显示 */
text-overflow: ellipsis;
/* 超出部分显示为省略号 */
}
.item-photo-list-item-close {
width: 40rpx;
height: 40rpx;
position: absolute;
right: -20rpx;
top: -20rpx;
}
.item-photo-list-item {
width: 180rpx;
height: 180rpx;
flex-shrink: 0;
border: 2rpx solid #e5e5e5;
border-radius: 10rpx;
margin-bottom: 20rpx;
/* margin-right: 20rpx; */
}
.container-item-upload {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
</style>

View File

@ -19,35 +19,30 @@
@scrolltolower="reachBottom">
<view class="page-box">
<block v-if="dataList.length > 0">
<view class="client" v-for="(items, index) in dataList" :key="index" @click="onItem(items.id)">
<view class="client" v-for="(items, index) in dataList" :key="index">
<view class="top">
<view class="left uv-line-1">
<view class="store uv-line-1">{{items.name}}</view>
<view class="store uv-line-1">{{items.contractName}}</view>
</view>
<view class="flex">
<uv-text v-if="items.checkStatus === 1" type="warning" text="审核中"></uv-text>
<uv-text v-else-if="items.checkStatus === 2" type="success" text="审核通过"></uv-text>
<uv-text v-else-if="items.checkStatus === 3" type="info" text="审核未通过"></uv-text>
<uv-text v-else type="error" text="待审核"></uv-text>
<uv-icon name="arrow-right" :size="20"></uv-icon>
<view class="flex align-center">
<text
:style="{color:items.result==1?'#4687fc':items.result==2?'#4cd964':items.result==3?'#dd524d':'#999999'}">{{fillterStatus(items.result)}}</text>
<uv-icon name="arrow-right" :size="16"
:color="items.result==1?'#4687fc':items.result==2?'#4cd964':items.result==3?'#dd524d':'#999999'"></uv-icon>
</view>
</view>
<view class="item align-center">
<view class="content">
<view class="title uv-line-2">{{items.money}} <text class="pl-1">回款:{{items.returnMoney}}</text>
<view class="title uv-line-2">{{items.contractMoney}} <text
class="pl-1">回款:{{items.returnMoney}}</text>
</view>
</view>
<view class="right">{{items.orderAdminName ? items.orderAdminName : '--'}}</view>
<view class="right">{{items.signatoryName ? items.signatoryName : '暂无'}}</view>
</view>
<view class="bottom">
<view class="client_time">到期{{timeFormats(items.endTime)}}</view>
<view class="client_time">到期{{timestampToDate(items.endDate)}}</view>
<view class="flex">
<block v-if="items.isCheck">
<view class="btn ml-1 entity" @click.stop="onCheck(items.id)">审核</view>
</block>
<block v-else>
<view class="btn ml-1 primary" @click.stop="onCheck(items.id,'look')">详情</view>
</block>
<view class="btn ml-1 primary" @click.stop="onCheck(items.id)">详情</view>
</view>
</view>
</view>
@ -76,10 +71,13 @@
} from '@dcloudio/uni-app'
import {
getContractPage,
getOAContractPage
getOAContractPage,
} from '@/api/contract'
import {
formatDateTime
getDictData,
} from '@/api/customer.js'
import {
timestampToDate
} from '@/utils/util'
const title = ref('合同')
const keyword = ref('')
@ -131,8 +129,30 @@
scrollTop.value = 0
dataList.value = []
getList()
getStatusList()
})
const statusList = ref([])
const getStatusList = () => {
getDictData({
type: 'bpm_process_instance_result'
}).then(res => {
statusList.value = res
})
}
//
const fillterStatus = (status) => {
if (statusList.value.length) {
let obj = statusList.value.find(item => {
return item.value == status
})
return obj == undefined ? '' : obj.name
} else {
return status
}
}
const timeFormats = (val) => {
if (val) {
return formatDateTime(val)
@ -140,24 +160,12 @@
return '--'
}
}
const handleSelect = (key) => {
relation.value = key.value
getList()
}
//
const onCheck = (id, type) => {
if (type == 'look') {
uni.navigateTo({
url: '/pages/components/pages/contract/add?id=' + id + '&type=edit'
});
} else {
uni.navigateTo({
url: '/pages/components/pages/contract/add?id=' + id + '&type=check'
});
}
}
const getList = async (isNextPage, pages) => {
await getOAContractPage({
@ -205,9 +213,10 @@
getList()
}
const onItem = (val) => {
//
const onCheck = (id, type) => {
uni.navigateTo({
url: '/pages/components/pages/contract/add?id=' + val + '&type=edit'
url: '/pages/components/pages/contract/add?id=' + id + '&type=detail'
});
}

View File

@ -1,133 +1,146 @@
<template>
<uv-navbar
:fixed="false"
@leftClick="$onClickLeft"
bgColor="#09b4f1"
>
<template v-slot:left>
<uv-icon name="arrow-left" size="19" color="#ffffff"></uv-icon>
</template>
<template v-slot:center>
<text style="color:#ffffff">{{title}}</text>
</template>
</uv-navbar>
<view class="bg-white">
<uv-cell-group>
<uv-cell customStyle="padding:10rpx 0" title="修改个人信息" :isLink="true" arrow-direction="right" @click="gopage('/pages/components/pages/mine/edit')"></uv-cell>
<uv-cell customStyle="padding:10rpx 0" title="修改密码" :isLink="true" arrow-direction="right" @click="gopage('/pages/components/pages/mine/pass')"></uv-cell>
<uv-cell customStyle="padding:10rpx 0" title="退出登录" :isLink="true" arrow-direction="right" @click="logout"></uv-cell>
</uv-cell-group>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow} from '@dcloudio/uni-app'
import { formatDateTime } from '@/utils/util'
import {
userLogout
} from '@/api/auth'
import { VUE_APP_UPLOAD_URL,TENANT_ID } from '@/config';
const main = useMainStore()
const { openid, lang } = storeToRefs(main)
const title = ref('设置')
const date = ref('')
const member = ref({})
onLoad(() => {
member.value = main.member;
})
const logout = () => {
uni.showModal({
title: '提示',
content: '确定退出吗?',
success: async function (res) {
if (res.confirm) {
await userLogout()
uni.showToast({
title: '退出成功',
icon: 'success',
duration: 2000
})
setTimeout(() => {
gopage('/pages/components/pages/login/login')
}, 1500);
}
}
});
}
const gopage = (url) => {
uni.navigateTo({
url
})
}
</script>
<style lang="scss" scoped>
page {
height: 100%;
}
.container {
padding: 20rpx 30rpx;
}
.form {
border-radius: 8rpx;
}
.form-input {
.label {
width: 160rpx;
font-size: $font-size-base;
color: $text-color-base;
}
.input {
}
.radio-group {
display: flex;
justify-content: flex-start;
.radio {
padding: 10rpx 30rpx;
border-radius: 6rpx;
border: 2rpx solid $text-color-assist;
color: $text-color-assist;
font-size: $font-size-base;
&.checked {
background-color: $color-primary;
color: $text-color-white;
border: 2rpx solid $color-primary;
}
}
}
}
.btn-box {
// height: calc((100vh - 40rpx) / 2);
}
.save-btn {
width: 90%;
border-radius: 50rem !important;
font-size: $font-size-lg;
}
.logout-btn {
width: 90%;
border-radius: 50rem !important;
font-size: $font-size-lg;
background-color:rgb(237, 81, 29);
}
</style>
<template>
<uv-navbar :fixed="false" @leftClick="$onClickLeft" bgColor="#09b4f1">
<template v-slot:left>
<uv-icon name="arrow-left" size="19" color="#ffffff"></uv-icon>
</template>
<template v-slot:center>
<text style="color:#ffffff">{{title}}</text>
</template>
</uv-navbar>
<view class="bg-white">
<uv-cell-group :border='false'>
<!-- <uv-cell customStyle="padding:10rpx 0" title="修改个人信息" :isLink="true" arrow-direction="right"
@click="gopage('/pages/components/pages/mine/edit')"></uv-cell>
<uv-cell customStyle="padding:10rpx 0" title="修改密码" :isLink="true" arrow-direction="right"
@click="gopage('/pages/components/pages/mine/pass')"></uv-cell> -->
<uv-cell size="large" title="退出登录" :isLink="true" arrow-direction="right" @click="logout"></uv-cell>
</uv-cell-group>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import {
useMainStore
} from '@/store/store'
import {
storeToRefs
} from 'pinia'
import {
onLoad,
onShow
} from '@dcloudio/uni-app'
import {
formatDateTime
} from '@/utils/util'
import {
userLogout
} from '@/api/auth'
import {
VUE_APP_UPLOAD_URL,
TENANT_ID
} from '@/config';
const main = useMainStore()
const {
openid,
lang
} = storeToRefs(main)
const title = ref('设置')
const date = ref('')
const member = ref({})
onLoad(() => {
member.value = main.member;
})
const logout = () => {
uni.showModal({
title: '提示',
content: '确定退出吗?',
success: async function(res) {
if (res.confirm) {
await userLogout()
uni.showToast({
title: '退出成功',
icon: 'success',
duration: 2000
})
setTimeout(() => {
gopage('/pages/components/pages/login/login')
}, 1500);
}
}
});
}
const gopage = (url) => {
uni.navigateTo({
url
})
}
</script>
<style lang="scss" scoped>
page {
height: 100%;
}
.container {
padding: 20rpx 30rpx;
}
.form {
border-radius: 8rpx;
}
.form-input {
.label {
width: 160rpx;
font-size: $font-size-base;
color: $text-color-base;
}
.input {}
.radio-group {
display: flex;
justify-content: flex-start;
.radio {
padding: 10rpx 30rpx;
border-radius: 6rpx;
border: 2rpx solid $text-color-assist;
color: $text-color-assist;
font-size: $font-size-base;
&.checked {
background-color: $color-primary;
color: $text-color-white;
border: 2rpx solid $color-primary;
}
}
}
}
.btn-box {
// height: calc((100vh - 40rpx) / 2);
}
.save-btn {
width: 90%;
border-radius: 50rem !important;
font-size: $font-size-lg;
}
.logout-btn {
width: 90%;
border-radius: 50rem !important;
font-size: $font-size-lg;
background-color: rgb(237, 81, 29);
}
</style>

View File

@ -8,7 +8,6 @@
</template>
</uv-navbar>
<view class="bg-white">
<view class="font-size-sm px-5 py-2">2024-10-07</view>
<uv-cell-group>
<uv-cell @click="gopage('/pages/customer/index')" icon="/static/images/19.png" title="待跟进客户"
iconStyle="height:70rpx;width:70rpx;margin-right:10rpx">

View File

@ -12,9 +12,9 @@
<text class="id-text">{{roleName}}</text>
</view>
</view>
<!-- <view class="mt-2 mr-4" @tap="gopage('/pages/components/pages/mine/index')">
<view class="mt-2 mr-4" @tap="gopage('/pages/components/pages/mine/index')">
<image src="/static/images/mine/set.png" class="setting-icon-img"></image>
</view> -->
</view>
</view>
<view style="padding: 0 30rpx;">
<view class="user-box">

BIN
static/greyguanbi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,115 @@
## 1.1.192024-07-18
- 修复 初始值传入 null 导致input报错的bug
## 1.1.182024-04-11
- 修复 easyinput组件双向绑定问题
## 1.1.172024-03-28
- 修复 在头条小程序下丢失事件绑定的问题
## 1.1.162024-03-20
- 修复 在密码输入情况下 清除和小眼睛覆盖bug 在edge浏览器下显示双眼睛bug
## 1.1.152024-02-21
- 新增 左侧插槽left
## 1.1.142024-02-19
- 修复 onBlur的emit传值错误
## 1.1.122024-01-29
- 补充 adjust-position文档属性补充
## 1.1.112024-01-29
- 补充 adjust-position属性传递值Boolean当键盘弹起时是否自动上推页面
## 1.1.102024-01-22
- 去除 移除无用的log输出
## 1.1.92023-04-11
- 修复 vue3 下 keyboardheightchange 事件报错的bug
## 1.1.82023-03-29
- 优化 trim 属性默认值
## 1.1.72023-03-29
- 新增 cursor-spacing 属性
## 1.1.62023-01-28
- 新增 keyboardheightchange 事件,可监听键盘高度变化
## 1.1.52022-11-29
- 优化 主题样式
## 1.1.42022-10-27
- 修复 props 中背景颜色无默认值的bug
## 1.1.02022-06-30
- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
- 新增 clear 事件,点击右侧叉号图标触发
- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
## 1.0.52022-06-07
- 优化 clearable 显示策略
## 1.0.42022-06-07
- 优化 clearable 显示策略
## 1.0.32022-05-20
- 修复 关闭图标某些情况下无法取消的 bug
## 1.0.22022-04-12
- 修复 默认值不生效的 bug
## 1.0.12022-04-02
- 修复 value 不能为 0 的 bug
## 1.0.02021-11-19
- 优化 组件 UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
## 0.1.42021-08-20
- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
## 0.1.32021-08-11
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
## 0.1.22021-07-30
- 优化 vue3 下事件警告的问题
## 0.1.1
- 优化 errorMessage 属性支持 Boolean 类型
## 0.1.02021-07-13
- 组件兼容 vue3如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.0.162021-06-29
- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug
## 0.0.152021-06-21
- 修复 passwordIcon 属性拼写错误的 bug
## 0.0.142021-06-18
- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标
- 修复 confirmType 属性不生效的问题
## 0.0.132021-06-04
- 修复 disabled 状态可清出内容的 bug
## 0.0.122021-05-12
- 新增 组件示例地址
## 0.0.112021-05-07
- 修复 input-border 属性不生效的问题
## 0.0.102021-04-30
- 修复 ios 遮挡文字、显示一半的问题
## 0.0.92021-02-05
- 调整为 uni_modules 目录规范
- 优化 兼容 nvue 页面

View File

@ -0,0 +1,54 @@
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行 false - 延迟执行
*/
export const debounce = function(func, wait = 1000, immediate = true) {
let timer;
return function() {
let context = this,
args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply(context, args);
}, wait)
}
}
}
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 使用表时间戳在时间段开始的时候触发 2 使用表定时器在时间段结束的时候触发
*/
export const throttle = (func, wait = 1000, type = 1) => {
let previous = 0;
let timeout;
return function() {
let context = this;
let args = arguments;
if (type === 1) {
let now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}

View File

@ -0,0 +1,676 @@
<template>
<view class="uni-easyinput" :class="{ 'uni-easyinput-error': msg }" :style="boxStyle">
<view class="uni-easyinput__content" :class="inputContentClass" :style="inputContentStyle">
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')" size="22"></uni-icons>
<slot name="left">
</slot>
<!-- #ifdef MP-ALIPAY -->
<textarea :enableNative="enableNative" v-if="type === 'textarea'" class="uni-easyinput__content-textarea" :class="{ 'input-padding': inputBorder }" :name="name" :value="val" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" placeholder-class="uni-easyinput__placeholder-class" :maxlength="inputMaxlength" :focus="focused" :autoHeight="autoHeight" :cursor-spacing="cursorSpacing" :adjust-position="adjustPosition" @input="onInput" @blur="_Blur" @focus="_Focus" @confirm="onConfirm" @keyboardheightchange="onkeyboardheightchange"></textarea>
<input :enableNative="enableNative" v-else :type="type === 'password' ? 'text' : type" class="uni-easyinput__content-input" :style="inputStyle" :name="name" :value="val" :password="!showPassword && type === 'password'" :placeholder="placeholder" :placeholderStyle="placeholderStyle" placeholder-class="uni-easyinput__placeholder-class" :disabled="disabled" :maxlength="inputMaxlength" :focus="focused" :confirmType="confirmType" :cursor-spacing="cursorSpacing" :adjust-position="adjustPosition" @focus="_Focus" @blur="_Blur" @input="onInput" @confirm="onConfirm" @keyboardheightchange="onkeyboardheightchange" />
<!-- #endif -->
<!-- #ifndef MP-ALIPAY -->
<textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea" :class="{ 'input-padding': inputBorder }" :name="name" :value="val" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" placeholder-class="uni-easyinput__placeholder-class" :maxlength="inputMaxlength" :focus="focused" :autoHeight="autoHeight" :cursor-spacing="cursorSpacing" :adjust-position="adjustPosition" @input="onInput" @blur="_Blur" @focus="_Focus" @confirm="onConfirm" @keyboardheightchange="onkeyboardheightchange"></textarea>
<input v-else :type="type === 'password' ? 'text' : type" class="uni-easyinput__content-input" :style="inputStyle" :name="name" :value="val" :password="!showPassword && type === 'password'" :placeholder="placeholder" :placeholderStyle="placeholderStyle" placeholder-class="uni-easyinput__placeholder-class" :disabled="disabled" :maxlength="inputMaxlength" :focus="focused" :confirmType="confirmType" :cursor-spacing="cursorSpacing" :adjust-position="adjustPosition" @focus="_Focus" @blur="_Blur" @input="onInput" @confirm="onConfirm" @keyboardheightchange="onkeyboardheightchange" />
<!-- #endif -->
<template v-if="type === 'password' && passwordIcon">
<!-- 开启密码时显示小眼睛 -->
<uni-icons v-if="isVal" class="content-clear-icon" :class="{ 'is-textarea-icon': type === 'textarea' }" :type="showPassword ? 'eye-slash-filled' : 'eye-filled'" :size="22" :color="focusShow ? primaryColor : '#c0c4cc'" @click="onEyes"></uni-icons>
</template>
<template v-if="suffixIcon">
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')" size="22"></uni-icons>
</template>
<template v-else>
<uni-icons v-if="clearable && isVal && !disabled && type !== 'textarea'" class="content-clear-icon" :class="{ 'is-textarea-icon': type === 'textarea' }" type="clear" :size="clearSize" :color="msg ? '#dd524d' : focusShow ? primaryColor : '#c0c4cc'" @click="onClear"></uni-icons>
</template>
<slot name="right"></slot>
</view>
</view>
</template>
<script>
/**
* Easyinput 输入框
* @description 此组件可以实现表单的输入与校验包括 "text" "textarea" 类型
* @tutorial https://ext.dcloud.net.cn/plugin?id=3455
* @property {String} value 输入内容
* @property {String } type 输入框的类型默认text password/text/textarea/..
* @value text 文本输入键盘
* @value textarea 多行文本输入键盘
* @value password 密码输入键盘
* @value number 数字输入键盘注意iOS上app-vue弹出的数字键盘并非9宫格方式
* @value idcard 身份证输入键盘支付宝百度QQ小程序
* @value digit 带小数点的数字键盘 App的nvue页面微信支付宝百度头条QQ小程序支持
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件点击可清空输入框内容默认true
* @property {Boolean} autoHeight 是否自动增高输入区域type为textarea时有效默认true
* @property {String } placeholder 输入框的提示文字
* @property {String } placeholderStyle placeholder的样式(内联样式字符串)"color: #ddd"
* @property {Boolean} focus 是否自动获得焦点默认false
* @property {Boolean} disabled 是否禁用默认false
* @property {Number } maxlength 最大输入长度设置为 -1 的时候不限制最大长度默认140
* @property {String } confirmType 设置键盘右下角按钮的文字仅在type="text"时生效默认done
* @property {Number } clearSize 清除图标的大小单位px默认15
* @property {String} prefixIcon 输入框头部图标
* @property {String} suffixIcon 输入框尾部图标
* @property {String} primaryColor 设置主题色默认#2979ff
* @property {Boolean} trim 是否自动去除两端的空格
* @property {Boolean} cursorSpacing 指定光标与键盘的距离单位 px
* @property {Boolean} ajust-position 当键盘弹起时是否上推内容默认值true
* @value both 去除两端空格
* @value left 去除左侧空格
* @value right 去除右侧空格
* @value start 去除左侧空格
* @value end 去除右侧空格
* @value all 去除全部空格
* @value none 不去除空格
* @property {Boolean} inputBorder 是否显示input输入框的边框默认true
* @property {Boolean} passwordIcon type=password时是否显示小眼睛图标
* @property {Object} styles 自定义颜色
* @event {Function} input 输入框内容发生变化时触发
* @event {Function} focus 输入框获得焦点时触发
* @event {Function} blur 输入框失去焦点时触发
* @event {Function} confirm 点击完成按钮时触发
* @event {Function} iconClick 点击图标时触发
* @example <uni-easyinput v-model="mobile"></uni-easyinput>
*/
function obj2strClass(obj) {
let classess = '';
for (let key in obj) {
const val = obj[key];
if (val) {
classess += `${key} `;
}
}
return classess;
}
function obj2strStyle(obj) {
let style = '';
for (let key in obj) {
const val = obj[key];
style += `${key}:${val};`;
}
return style;
}
export default {
name: 'uni-easyinput',
emits: [
'click',
'iconClick',
'update:modelValue',
'input',
'focus',
'blur',
'confirm',
'clear',
'eyes',
'change',
'keyboardheightchange'
],
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
options: {
// #ifdef MP-TOUTIAO
virtualHost: false,
// #endif
// #ifndef MP-TOUTIAO
virtualHost: true
// #endif
},
inject: {
form: {
from: 'uniForm',
default: null
},
formItem: {
from: 'uniFormItem',
default: null
}
},
props: {
name: String,
value: [Number, String],
modelValue: [Number, String],
type: {
type: String,
default: 'text'
},
clearable: {
type: Boolean,
default: true
},
autoHeight: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: ' '
},
placeholderStyle: String,
focus: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
maxlength: {
type: [Number, String],
default: 140
},
confirmType: {
type: String,
default: 'done'
},
clearSize: {
type: [Number, String],
default: 24
},
inputBorder: {
type: Boolean,
default: true
},
prefixIcon: {
type: String,
default: ''
},
suffixIcon: {
type: String,
default: ''
},
trim: {
type: [Boolean, String],
default: false
},
cursorSpacing: {
type: Number,
default: 0
},
passwordIcon: {
type: Boolean,
default: true
},
adjustPosition: {
type: Boolean,
default: true
},
primaryColor: {
type: String,
default: '#2979ff'
},
styles: {
type: Object,
default () {
return {
color: '#333',
backgroundColor: '#fff',
disableColor: '#F7F6F6',
borderColor: '#e5e5e5'
};
}
},
errorMessage: {
type: [String, Boolean],
default: ''
},
// #ifdef MP-ALIPAY
enableNative: {
type: Boolean,
default: false
}
// #endif
},
data() {
return {
focused: false,
val: '',
showMsg: '',
border: false,
isFirstBorder: false,
showClearIcon: false,
showPassword: false,
focusShow: false,
localMsg: '',
isEnter: false // 使
};
},
computed: {
//
isVal() {
const val = this.val;
// fixed by mehaotian 00
if (val || val === 0) {
return true;
}
return false;
},
msg() {
// console.log('computed', this.form, this.formItem);
// if (this.form) {
// return this.errorMessage || this.formItem.errMsg;
// }
// TODO formItem errMsg
return this.localMsg || this.errorMessage;
},
// uniappinputmaxlength
inputMaxlength() {
return Number(this.maxlength);
},
// style
boxStyle() {
return `color:${
this.inputBorder && this.msg ? '#e43d33' : this.styles.color
};`;
},
// input
inputContentClass() {
return obj2strClass({
'is-input-border': this.inputBorder,
'is-input-error-border': this.inputBorder && this.msg,
'is-textarea': this.type === 'textarea',
'is-disabled': this.disabled,
'is-focused': this.focusShow
});
},
inputContentStyle() {
const focusColor = this.focusShow ?
this.primaryColor :
this.styles.borderColor;
const borderColor =
this.inputBorder && this.msg ? '#dd524d' : focusColor;
return obj2strStyle({
'border-color': borderColor || '#e5e5e5',
'background-color': this.disabled ?
this.styles.disableColor : this.styles.backgroundColor
});
},
// input
inputStyle() {
const paddingRight =
this.type === 'password' || this.clearable || this.prefixIcon ?
'' :
'10px';
return obj2strStyle({
'padding-right': paddingRight,
'padding-left': this.prefixIcon ? '' : '10px'
});
}
},
watch: {
value(newVal) {
// fix by mehaotian nullinputbug
if (newVal === null) {
this.val = '';
return
}
this.val = newVal;
},
modelValue(newVal) {
if (newVal === null) {
this.val = '';
return
}
this.val = newVal;
},
focus(newVal) {
this.$nextTick(() => {
this.focused = this.focus;
this.focusShow = this.focus;
});
}
},
created() {
this.init();
// TODO vue3 computed inject formItem.errMsg
if (this.form && this.formItem) {
this.$watch('formItem.errMsg', newVal => {
this.localMsg = newVal;
});
}
},
mounted() {
this.$nextTick(() => {
this.focused = this.focus;
this.focusShow = this.focus;
});
},
methods: {
/**
* 初始化变量值
*/
init() {
if (this.value || this.value === 0) {
this.val = this.value;
} else if (
this.modelValue ||
this.modelValue === 0 ||
this.modelValue === ''
) {
this.val = this.modelValue;
} else {
// fix by ht nullinput
this.val = '';
}
},
/**
* 点击图标时触发
* @param {Object} type
*/
onClickIcon(type) {
this.$emit('iconClick', type);
},
/**
* 显示隐藏内容密码框时生效
*/
onEyes() {
this.showPassword = !this.showPassword;
this.$emit('eyes', this.showPassword);
},
/**
* 输入时触发
* @param {Object} event
*/
onInput(event) {
let value = event.detail.value;
//
if (this.trim) {
if (typeof this.trim === 'boolean' && this.trim) {
value = this.trimStr(value);
}
if (typeof this.trim === 'string') {
value = this.trimStr(value, this.trim);
}
}
if (this.errMsg) this.errMsg = '';
this.val = value;
// TODO vue2
this.$emit('input', value);
// TODO  vue3
this.$emit('update:modelValue', value);
},
/**
* 外部调用方法
* 获取焦点时触发
* @param {Object} event
*/
onFocus() {
this.$nextTick(() => {
this.focused = true;
});
this.$emit('focus', null);
},
_Focus(event) {
this.focusShow = true;
this.$emit('focus', event);
},
/**
* 外部调用方法
* 失去焦点时触发
* @param {Object} event
*/
onBlur() {
this.focused = false;
this.$emit('blur', null);
},
_Blur(event) {
let value = event.detail.value;
this.focusShow = false;
this.$emit('blur', event);
// eventstring
if (this.isEnter === false) {
this.$emit('change', this.val);
}
//
if (this.form && this.formItem) {
const { validateTrigger } = this.form;
if (validateTrigger === 'blur') {
this.formItem.onFieldChange();
}
}
},
/**
* 按下键盘的发送键
* @param {Object} e
*/
onConfirm(e) {
this.$emit('confirm', this.val);
this.isEnter = true;
this.$emit('change', this.val);
this.$nextTick(() => {
this.isEnter = false;
});
},
/**
* 清理内容
* @param {Object} event
*/
onClear(event) {
this.val = '';
// TODO vue2
this.$emit('input', '');
// TODO vue2
// TODO  vue3
this.$emit('update:modelValue', '');
//
this.$emit('clear');
},
/**
* 键盘高度发生变化的时候触发此事件
* 兼容性微信小程序2.7.0+App 3.1.0+
* @param {Object} event
*/
onkeyboardheightchange(event) {
this.$emit('keyboardheightchange', event);
},
/**
* 去除空格
*/
trimStr(str, pos = 'both') {
if (pos === 'both') {
return str.trim();
} else if (pos === 'left') {
return str.trimLeft();
} else if (pos === 'right') {
return str.trimRight();
} else if (pos === 'start') {
return str.trimStart();
} else if (pos === 'end') {
return str.trimEnd();
} else if (pos === 'all') {
return str.replace(/\s+/g, '');
} else if (pos === 'none') {
return str;
}
return str;
}
}
};
</script>
<style lang="scss">
$uni-error: #e43d33;
$uni-border-1: #dcdfe6 !default;
.uni-easyinput {
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
flex: 1;
position: relative;
text-align: left;
color: #333;
font-size: 14px;
}
.uni-easyinput__content {
flex: 1;
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
box-sizing: border-box;
// min-height: 36px;
/* #endif */
flex-direction: row;
align-items: center;
// border
border-color: #fff;
transition-property: border-color;
transition-duration: 0.3s;
}
.uni-easyinput__content-input {
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
position: relative;
overflow: hidden;
flex: 1;
line-height: 1;
font-size: 14px;
height: 35px;
// min-height: 36px;
/*ifdef H5*/
& ::-ms-reveal {
display: none;
}
& ::-ms-clear {
display: none;
}
& ::-o-clear {
display: none;
}
/*endif*/
}
.uni-easyinput__placeholder-class {
color: #999;
font-size: 12px;
// font-weight: 200;
}
.is-textarea {
align-items: flex-start;
}
.is-textarea-icon {
margin-top: 5px;
}
.uni-easyinput__content-textarea {
position: relative;
overflow: hidden;
flex: 1;
line-height: 1.5;
font-size: 14px;
margin: 6px;
margin-left: 0;
height: 80px;
min-height: 80px;
/* #ifndef APP-NVUE */
min-height: 80px;
width: auto;
/* #endif */
}
.input-padding {
padding-left: 10px;
}
.content-clear-icon {
padding: 0 5px;
}
.label-icon {
margin-right: 5px;
margin-top: -1px;
}
//
.is-input-border {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-direction: row;
align-items: center;
border: 1px solid $uni-border-1;
border-radius: 4px;
/* #ifdef MP-ALIPAY */
overflow: hidden;
/* #endif */
}
.uni-error-message {
position: absolute;
bottom: -17px;
left: 0;
line-height: 12px;
color: $uni-error;
font-size: 12px;
text-align: left;
}
.uni-error-msg--boeder {
position: relative;
bottom: 0;
line-height: 22px;
}
.is-input-error-border {
border-color: $uni-error;
.uni-easyinput__placeholder-class {
color: mix(#fff, $uni-error, 50%);
}
}
.uni-easyinput--border {
margin-bottom: 0;
padding: 10px 15px;
// padding-bottom: 0;
border-top: 1px #eee solid;
}
.uni-easyinput-error {
padding-bottom: 0;
}
.is-first-border {
/* #ifndef APP-NVUE */
border: none;
/* #endif */
/* #ifdef APP-NVUE */
border-width: 0;
/* #endif */
}
.is-disabled {
background-color: #f7f6f6;
color: #d5d5d5;
.uni-easyinput__placeholder-class {
color: #d5d5d5;
font-size: 12px;
}
}
</style>

View File

@ -0,0 +1,88 @@
{
"id": "uni-easyinput",
"displayName": "uni-easyinput 增强输入框",
"version": "1.1.19",
"description": "Easyinput 组件是对原生input组件的增强",
"keywords": [
"uni-ui",
"uniui",
"input",
"uni-easyinput",
"输入框"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,11 @@
### Easyinput 增强输入框
> **组件名uni-easyinput**
> 代码块: `uEasyinput`
easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的easyinput 内置了边框,图标等,同时包含 input 所有功能
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,39 @@
## 1.2.82024-04-26
- 修复 在vue2下H5黑边的bug
## 1.2.72024-04-26
- 修复 在vue2手动输入后失焦导致清空数值的严重bug
## 1.2.62024-02-22
- 新增 设置宽度属性width(单位px)
## 1.2.52024-02-21
- 修复 step步长小于1时键盘类型为number的bug
## 1.2.42024-02-02
- 修复 加减号垂直位置偏移样式问题
## 1.2.32023-05-23
- 更新示例工程
## 1.2.22023-05-08
- 修复 change 事件执行顺序错误的问题
## 1.2.12021-11-22
- 修复 vue3中某些scss变量无法找到的问题
## 1.2.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-number-box](https://uniapp.dcloud.io/component/uniui/uni-number-box)
## 1.1.22021-11-09
- 新增 提供组件设计资源,组件样式调整
## 1.1.12021-07-30
- 优化 vue3下事件警告的问题
## 1.1.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.72021-05-12
- 新增 组件示例地址
## 1.0.62021-04-20
- 修复 uni-number-box 浮点数运算不精确的 bug
- 修复 uni-number-box change 事件触发不正确的 bug
- 新增 uni-number-box v-model 双向绑定
## 1.0.52021-02-05
- 调整为uni_modules目录规范
## 1.0.72021-02-05
- 调整为uni_modules目录规范
- 新增 支持 v-model
- 新增 支持 focus、blur 事件
- 新增 支持 PC 端

View File

@ -0,0 +1,232 @@
<template>
<view class="uni-numbox">
<view @click="_calcValue('minus')" class="uni-numbox__minus uni-numbox-btns" :style="{background}">
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }"
:style="{color}">-</text>
</view>
<input :disabled="disabled" @focus="_onFocus" @blur="_onBlur" class="uni-numbox__value"
:type="step<1?'digit':'number'" v-model="inputValue" :style="{background, color, width:widthWithPx}" />
<view @click="_calcValue('plus')" class="uni-numbox__plus uni-numbox-btns" :style="{background}">
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }"
:style="{color}">+</text>
</view>
</view>
</template>
<script>
/**
* NumberBox 数字输入框
* @description 带加减按钮的数字输入框
* @tutorial https://ext.dcloud.net.cn/plugin?id=31
* @property {Number} value 输入框当前值
* @property {Number} min 最小值
* @property {Number} max 最大值
* @property {Number} step 每次点击改变的间隔大小
* @property {String} background 背景色
* @property {String} color 字体颜色前景色
* @property {Number} width 输入框宽度(单位:px)
* @property {Boolean} disabled = [true|false] 是否为禁用状态
* @event {Function} change 输入框值改变时触发的事件参数为输入框当前的 value
* @event {Function} focus 输入框聚焦时触发的事件参数为 event 对象
* @event {Function} blur 输入框失焦时触发的事件参数为 event 对象
*/
export default {
name: "UniNumberBox",
emits: ['change', 'input', 'update:modelValue', 'blur', 'focus'],
props: {
value: {
type: [Number, String],
default: 1
},
modelValue: {
type: [Number, String],
default: 1
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
background: {
type: String,
default: '#f5f5f5'
},
color: {
type: String,
default: '#333'
},
disabled: {
type: Boolean,
default: false
},
width: {
type: Number,
default: 40,
}
},
data() {
return {
inputValue: 0
};
},
watch: {
value(val) {
this.inputValue = +val;
},
modelValue(val) {
this.inputValue = +val;
}
},
computed: {
widthWithPx() {
return this.width + 'px';
}
},
created() {
if (this.value === 1) {
this.inputValue = +this.modelValue;
}
if (this.modelValue === 1) {
this.inputValue = +this.value;
}
},
methods: {
_calcValue(type) {
if (this.disabled) {
return;
}
const scale = this._getDecimalScale();
let value = this.inputValue * scale;
let step = this.step * scale;
if (type === "minus") {
value -= step;
if (value < (this.min * scale)) {
return;
}
if (value > (this.max * scale)) {
value = this.max * scale
}
}
if (type === "plus") {
value += step;
if (value > (this.max * scale)) {
return;
}
if (value < (this.min * scale)) {
value = this.min * scale
}
}
this.inputValue = (value / scale).toFixed(String(scale).length - 1);
// TODO vue2
this.$emit("input", +this.inputValue);
// TODO vue3
this.$emit("update:modelValue", +this.inputValue);
this.$emit("change", +this.inputValue);
},
_getDecimalScale() {
let scale = 1;
//
if (~~this.step !== this.step) {
scale = Math.pow(10, String(this.step).split(".")[1].length);
}
return scale;
},
_onBlur(event) {
this.$emit('blur', event)
let value = event.detail.value;
if (isNaN(value)) {
this.inputValue = this.value;
return;
}
value = +value;
if (value > this.max) {
value = this.max;
} else if (value < this.min) {
value = this.min;
}
const scale = this._getDecimalScale();
this.inputValue = value.toFixed(String(scale).length - 1);
this.$emit("input", +this.inputValue);
this.$emit("update:modelValue", +this.inputValue);
this.$emit("change", +this.inputValue);
},
_onFocus(event) {
this.$emit('focus', event)
}
}
};
</script>
<style lang="scss">
$box-height: 26px;
$bg: #f5f5f5;
$br: 2px;
$color: #333;
.uni-numbox {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-numbox-btns {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 8px;
background-color: $bg;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-numbox__value {
margin: 0 2px;
background-color: $bg;
width: 40px;
height: $box-height;
text-align: center;
font-size: 14px;
border-width: 0;
color: $color;
}
.uni-numbox__minus {
border-top-left-radius: $br;
border-bottom-left-radius: $br;
}
.uni-numbox__plus {
border-top-right-radius: $br;
border-bottom-right-radius: $br;
}
.uni-numbox--text {
// fix nvue
line-height: 20px;
margin-bottom: 2px;
font-size: 20px;
font-weight: 300;
color: $color;
}
.uni-numbox .uni-numbox--disabled {
color: #c0c0c0 !important;
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
</style>

View File

@ -0,0 +1,83 @@
{
"id": "uni-number-box",
"displayName": "uni-number-box 数字输入框",
"version": "1.2.8",
"description": "NumberBox 带加减按钮的数字输入框组件,用户可以控制每次点击增加的数值,支持小数。",
"keywords": [
"uni-ui",
"uniui",
"数字输入框"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,13 @@
## NumberBox 数字输入框
> **组件名uni-number-box**
> 代码块: `uNumberBox`
带加减按钮的数字输入框。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-number-box)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -1,191 +1,216 @@
// 验证手机号码
export function validatePhoneNumber(data) {
var reg = /^1[3-9]\d{9}$/
return reg.test(data)
}
export function lastfour(num) {
// 截取后4位
let lastFourDigits = num.slice(-4);
return lastFourDigits
}
export function isValidBankCard(cardNumber) {
const reg = /^(\d{16}|\d{19})$/
// 使用正则表达式测试银行卡号
return reg.test(cardNumber)
}
export function formatTime(time) {
if (typeof time !== 'number' || time < 0) {
return time
}
var hour = parseInt(time / 3600)
time = time % 3600
var minute = parseInt(time / 60)
time = time % 60
var second = time
return ([hour, minute, second]).map(function(n) {
n = n.toString()
return n[1] ? n : '0' + n
}).join(':')
}
export function formatDateTime(date, fmt = 'yyyy-MM-dd hh:mm:ss') {
if(!date) {
return ''
}
if (typeof (date) === 'number') {
date = new Date(date)
}
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
}
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt))
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
return fmt
}
export function formatLocation(longitude, latitude) {
if (typeof longitude === 'string' && typeof latitude === 'string') {
longitude = parseFloat(longitude)
latitude = parseFloat(latitude)
}
longitude = longitude.toFixed(2)
latitude = latitude.toFixed(2)
return {
longitude: longitude.toString().split('.'),
latitude: latitude.toString().split('.')
}
}
var dateUtils = {
UNITS: {
'年': 31557600000,
'月': 2629800000,
'天': 86400000,
'小时': 3600000,
'分钟': 60000,
'秒': 1000
},
humanize: function(milliseconds) {
var humanize = '';
for (var key in this.UNITS) {
if (milliseconds >= this.UNITS[key]) {
humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
break;
}
}
return humanize || '刚刚';
},
format: function(dateStr) {
var date = this.parse(dateStr)
var diff = Date.now() - date.getTime();
if (diff < this.UNITS['天']) {
return this.humanize(diff);
}
var _format = function(number) {
return (number < 10 ? ('0' + number) : number);
};
return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +
_format(date.getHours()) + ':' + _format(date.getMinutes());
},
parse: function(str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串转化为一个Date对象
var a = str.split(/[^0-9]/);
return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
}
};
// 返回上一页
export function prePage(page = null){
let pages = getCurrentPages();
//console.log('pages:',pages);
let prePage = pages[pages.length - 2];
if (page !== null) {
prePage = pages[page];
}
// #ifdef H5
//return prePage;
// #endif
return prePage;
}
export function kmUnit(m){
var v;
if(typeof m === 'number' && !isNaN(m)){
if (m >= 1000) {
v = (m / 1000).toFixed(2) + 'km'
} else {
v = m + 'm'
}
}else{
v = '0m'
}
return v;
}
export function isWeixin() {
if (navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('micromessenger') !== -1) {
return true
}
return false
}
// 验证手机号码
export function validatePhoneNumber(data) {
var reg = /^1[3-9]\d{9}$/
return reg.test(data)
}
export function lastfour(num) {
// 截取后4位
let lastFourDigits = num.slice(-4);
return lastFourDigits
}
export function isValidBankCard(cardNumber) {
const reg = /^(\d{16}|\d{19})$/
// 使用正则表达式测试银行卡号
return reg.test(cardNumber)
}
export function formatTime(time) {
if (typeof time !== 'number' || time < 0) {
return time
}
var hour = parseInt(time / 3600)
time = time % 3600
var minute = parseInt(time / 60)
time = time % 60
var second = time
return ([hour, minute, second]).map(function(n) {
n = n.toString()
return n[1] ? n : '0' + n
}).join(':')
}
export function formatDateTime(date, fmt = 'yyyy-MM-dd hh:mm:ss') {
if (!date) {
return ''
}
if (typeof(date) === 'number') {
date = new Date(date)
}
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
}
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt))
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
return fmt
}
export function timestampToDate(timestamp) {
let date = new Date(timestamp);
let year = date.getFullYear();
let month = ("0" + (date.getMonth() + 1)).slice(-2);
let day = ("0" + date.getDate()).slice(-2);
return `${year}-${month}-${day}`;
}
export function formatLocation(longitude, latitude) {
if (typeof longitude === 'string' && typeof latitude === 'string') {
longitude = parseFloat(longitude)
latitude = parseFloat(latitude)
}
longitude = longitude.toFixed(2)
latitude = latitude.toFixed(2)
return {
longitude: longitude.toString().split('.'),
latitude: latitude.toString().split('.')
}
}
var dateUtils = {
UNITS: {
'年': 31557600000,
'月': 2629800000,
'天': 86400000,
'小时': 3600000,
'分钟': 60000,
'秒': 1000
},
humanize: function(milliseconds) {
var humanize = '';
for (var key in this.UNITS) {
if (milliseconds >= this.UNITS[key]) {
humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
break;
}
}
return humanize || '刚刚';
},
format: function(dateStr) {
var date = this.parse(dateStr)
var diff = Date.now() - date.getTime();
if (diff < this.UNITS['天']) {
return this.humanize(diff);
}
var _format = function(number) {
return (number < 10 ? ('0' + number) : number);
};
return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +
_format(date.getHours()) + ':' + _format(date.getMinutes());
},
parse: function(str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串转化为一个Date对象
var a = str.split(/[^0-9]/);
return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
}
};
// 返回上一页
export function prePage(page = null) {
let pages = getCurrentPages();
//console.log('pages:',pages);
let prePage = pages[pages.length - 2];
if (page !== null) {
prePage = pages[page];
}
// #ifdef H5
//return prePage;
// #endif
return prePage;
}
export function kmUnit(m) {
var v;
if (typeof m === 'number' && !isNaN(m)) {
if (m >= 1000) {
v = (m / 1000).toFixed(2) + 'km'
} else {
v = m + 'm'
}
} else {
v = '0m'
}
return v;
}
export function isWeixin() {
if (navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('micromessenger') !== -1) {
return true
}
return false
}
export function parseQuery() {
let res = {}
// #ifdef H5
const query = (location.href.split('?')[1] || '').trim().replace(/^(\?|#|&)/, '')
if (!query) {
return res
}
query.split('&').forEach(param => {
const parts = param.replace(/\+/g, ' ').split('=')
const key = decodeURIComponent(parts.shift())
const val = parts.length > 0 ? decodeURIComponent(parts.join('=')) : null
if (res[key] === undefined) {
res[key] = val
} else if (Array.isArray(res[key])) {
res[key].push(val)
} else {
res[key] = [res[key], val]
}
})
// #endif
// #ifndef H5
var pages = getCurrentPages() //获取加载的页面
var currentPage = pages[pages.length - 1] //获取当前页面的对象
var url = currentPage.route //当前页面url
res = currentPage.options //如果要获取url中所带的参数可以查看options
// #endif
return res
}
export function urlDecode(query) {
if (!query) return null
let hash, object = {}
const hashes = query.slice(query.indexOf('?') + 1).split('&')
for (let i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=')
object[hash[0]] = hash[1]
}
return object;
let res = {}
// #ifdef H5
const query = (location.href.split('?')[1] || '').trim().replace(/^(\?|#|&)/, '')
if (!query) {
return res
}
query.split('&').forEach(param => {
const parts = param.replace(/\+/g, ' ').split('=')
const key = decodeURIComponent(parts.shift())
const val = parts.length > 0 ? decodeURIComponent(parts.join('=')) : null
if (res[key] === undefined) {
res[key] = val
} else if (Array.isArray(res[key])) {
res[key].push(val)
} else {
res[key] = [res[key], val]
}
})
// #endif
// #ifndef H5
var pages = getCurrentPages() //获取加载的页面
var currentPage = pages[pages.length - 1] //获取当前页面的对象
var url = currentPage.route //当前页面url
res = currentPage.options //如果要获取url中所带的参数可以查看options
// #endif
return res
}
export function urlDecode(query) {
if (!query) return null
let hash, object = {}
const hashes = query.slice(query.indexOf('?') + 1).split('&')
for (let i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=')
object[hash[0]] = hash[1]
}
return object;
}
//根据名称返回是图片还是pdf
export function getFileType(filename) {
const lowercaseFilename = filename.toLowerCase();
if (lowercaseFilename.endsWith('.jpg') || lowercaseFilename.endsWith('.jpeg') || lowercaseFilename.endsWith(
'.png') || lowercaseFilename.endsWith('.gif')) {
return 'image';
} else if (lowercaseFilename.endsWith('.pdf')) {
return 'pdf';
} else if (lowercaseFilename.endsWith('.xls') || lowercaseFilename.endsWith('.xlsx')) {
return 'excel';
} else {
return 'other';
}
}