Browse Source

登录、权限、个人中心

devlop
邓雄飞 4 years ago
parent
commit
6dc2a5d2db
78 changed files with 10111 additions and 152 deletions
  1. 3
      .gitignore
  2. 29
      App.vue
  3. 16
      apis/clientApi.js
  4. 62
      apis/commonApi.js
  5. 49
      apis/enterpriseInfoApi.js
  6. 14
      apis/mineApi.js
  7. 32
      common/css/reset.scss
  8. 45
      components/qn-data-picker/keypress.js
  9. 566
      components/qn-data-picker/qn-data-picker.vue
  10. 563
      components/qn-data-pickerview/qn-data-picker.js
  11. 334
      components/qn-data-pickerview/qn-data-pickerview.vue
  12. 185
      components/qn-datetime-picker/calendar-item.vue
  13. 898
      components/qn-datetime-picker/calendar.vue
  14. 19
      components/qn-datetime-picker/i18n/en.json
  15. 8
      components/qn-datetime-picker/i18n/index.js
  16. 19
      components/qn-datetime-picker/i18n/zh-Hans.json
  17. 19
      components/qn-datetime-picker/i18n/zh-Hant.json
  18. 45
      components/qn-datetime-picker/keypress.js
  19. 1008
      components/qn-datetime-picker/qn-datetime-picker.vue
  20. 927
      components/qn-datetime-picker/time-picker.vue
  21. 410
      components/qn-datetime-picker/util.js
  22. 514
      components/qn-easyinput/qn-easyinput.vue
  23. 74
      components/qn-footer/qn-footer.vue
  24. 87
      components/qn-form-item/qn-form-item.vue
  25. 43
      components/qn-header/qn-header.vue
  26. 5
      components/scroll-list/images.js
  27. 668
      components/scroll-list/scroll-list.vue
  28. 80
      enums/index.js
  29. 8
      env/index.js
  30. 30
      pages.json
  31. 566
      pages/enterprise-info/index.vue
  32. 18
      pages/error/index.vue
  33. 84
      pages/login/index.vue
  34. 567
      pages/mine/index.vue
  35. 66
      pages/page-view/index.vue
  36. 17
      static/icon/iconfont.css
  37. BIN
      static/icon/iconfont.ttf
  38. BIN
      static/imgs/enterpriseInfo/location-icon.png
  39. BIN
      static/imgs/mine/certified-icon.png
  40. BIN
      static/imgs/mine/contract-icon.png
  41. BIN
      static/imgs/mine/credit-icon.png
  42. BIN
      static/imgs/mine/default-avatar.png
  43. BIN
      static/imgs/mine/finance-icon.png
  44. BIN
      static/imgs/mine/mine-top-bg.png
  45. BIN
      static/imgs/mine/money-icon.png
  46. BIN
      static/imgs/mine/msg-icon.png
  47. BIN
      static/imgs/mine/non-certified-icon.png
  48. BIN
      static/imgs/mine/order-icon.png
  49. BIN
      static/imgs/mine/setting-icon.png
  50. BIN
      static/imgs/mine/toggle-icon.png
  51. BIN
      static/imgs/mine/user-avatar.png
  52. BIN
      static/imgs/mine/vip-icon.png
  53. BIN
      static/logo.png
  54. 173
      store/index.js
  55. 44
      uni_modules/uni-popup/changelog.md
  56. 45
      uni_modules/uni-popup/components/uni-popup-dialog/keypress.js
  57. 263
      uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue
  58. 143
      uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue
  59. 187
      uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue
  60. 7
      uni_modules/uni-popup/components/uni-popup/i18n/en.json
  61. 8
      uni_modules/uni-popup/components/uni-popup/i18n/index.js
  62. 7
      uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json
  63. 7
      uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json
  64. 45
      uni_modules/uni-popup/components/uni-popup/keypress.js
  65. 26
      uni_modules/uni-popup/components/uni-popup/popup.js
  66. 403
      uni_modules/uni-popup/components/uni-popup/uni-popup.vue
  67. 90
      uni_modules/uni-popup/package.json
  68. 17
      uni_modules/uni-popup/readme.md
  69. 20
      uni_modules/uni-transition/changelog.md
  70. 128
      uni_modules/uni-transition/components/uni-transition/createAnimation.js
  71. 277
      uni_modules/uni-transition/components/uni-transition/uni-transition.vue
  72. 87
      uni_modules/uni-transition/package.json
  73. 11
      uni_modules/uni-transition/readme.md
  74. 52
      utils/hook.js
  75. 11
      utils/http/http.js
  76. 8
      utils/http/index.js
  77. 114
      utils/index.js
  78. 12
      utils/is.js

3
.gitignore

@ -1,2 +1,3 @@
/unpackage
/.vscode
/.vscode
jsconfig.json

29
App.vue

@ -2,23 +2,30 @@
import { go2 } from '@/utils/hook.js'
import store from '@/store/index.js'
export default {
onLaunch: function () {
const token = store.state.qnToken
if (!token) {
go2('login')
onLaunch: function (options) {
const { query } = options
//
if (query.share) {
let supplierId = query.id
if (!supplierId) {
go2('error')
} else {
store.commit('setSupplierId', supplierId)
}
} else {
console.log('非分享进入', store.state.supplierId)
if (!store.state.supplierId) {
go2('error')
}
}
console.log('App launched')
},
onShow: function () {
console.log('App Show')
},
onHide: function () {
console.log('App Hide')
}
onShow: function () {},
onHide: function () {}
}
</script>
<style>
/*每个页面公共css */
@import url('./common/css/reset.scss');
@import '@/static/icon/iconfont.css';
</style>

16
apis/clientApi.js

@ -1,16 +0,0 @@
import http from '../utils/http/index.js'
export const getList = (data) => {
return http.get({
url:'/trading-center/wechatapplet/get/paper-brand/brand-list',
data,
})
}
/**
* @param {Object} data :{filePath:'',fileType:'',fileName:''}
*/
export const upload = (data) => {
return http.uploadFile({
data
})
}

62
apis/commonApi.js

@ -0,0 +1,62 @@
import http from '../utils/http/index.js'
let areaCache = null
/**
* 获取省市区街道
* @returns array
*/
export function getArea(data = {}) {
return new Promise((resolve, reject) => {
if (areaCache) {
resolve(areaCache)
} else {
http.get({ url: '/yyt-uec/get/regions', data }).then((res) => {
if (res) {
areaCache = res
resolve(res)
} else {
resolve(null)
}
})
}
})
}
let baseInfo = null
/**
* 获取当前账号的基础信息
* @param {object} data 参数,暂时不需要,可以传{}
* @param {boolean} refresh 是否强制刷新,默认false拿缓存数据
* @returns object
*/
export function getBaseInfo(data = {}, refresh = false) {
return new Promise((resolve, reject) => {
if (!refresh && baseInfo) {
resolve(baseInfo)
} else {
http.get({ url: '/yyt-uec/get/base-info', data }, { hideLoading: true }).then((res) => {
if (res) {
baseInfo = res
resolve(res)
} else {
resolve(null)
}
})
}
})
}
/**
* 获取当前账号的企业实名认证地址
* @param {object} data 参数 enterpriseId
*/
export function getVerifyUrl(data = {}) {
return http.post({ url: '/yyt-uec/get/fdd-enterprise-verify-url?enterpriseId=' + data.enterpriseId, data })
}
/**
* 生成担保合同的签约地址同意纸盘商只需要签约一次即可
* @param {object} data 参数 mallSupplierId
*/
export function getGuaranteeContract(data = {}) {
return http.post({ url: '/yyt-uec/create/supplier/guarantee-contract?mallSupplierId=' + data.mallSupplierId, data })
}

49
apis/enterpriseInfoApi.js

@ -0,0 +1,49 @@
import http from '../utils/http/index.js'
/**
* 完善企业信息
* @param {*} data
* @returns
*/
export function completeInfo(data) {
return http.post({
url: '/yyt-uec/save/my/enterprise',
data
})
}
/**
* 根据名称模糊查询企业列表
* @param {*} data
* @returns
*/
export function getCompanyList(data) {
return http.get({
url: '/base-paper-trading/get/customers/enterprise/basic/list',
data
})
}
/**
* 根据id查询企业详细信息
* @param {*} data
* @returns
*/
export function getCompanyInfoById(data) {
return http.get({
url: '/yyt-uec/get/enterprise-detail',
data
})
}
/**
* 根据企业名称查询企业坐标列表
* @param {*} data
* @returns
*/
export function getCompanyLocationList(data) {
return http.get({
url: '/yyt-uec/get/enterprise/geographic/position/address',
data
})
}

14
apis/mineApi.js

@ -0,0 +1,14 @@
import http from '../utils/http/index.js'
/**
* 纸掌柜获取纸盘商订单统计
* @param {object} data
* @returns 订单统计
* swagger:http://api-ops-uec-test.qniao.cn/uec/swagger-ui/index.html?urls.primaryName=CustomerApi#/%E7%99%BB%E5%BD%95%E8%AE%A4%E8%AF%81/authorizeByCaptchaUsingPOST
*/
export const getOrderStatistics = (data) => {
return http.get({
url: '/base-paper-trading/get/supplier/order-volume-statistics',
data
})
}

32
common/css/reset.scss

@ -1,8 +1,8 @@
page {
font-size:$uni-font-size-base;
line-height: 1;
background-color: #fff;
-webkit-overflow-scrolling: touch; /* 使ios列表滑动流畅*/
font-size: $uni-font-size-base;
line-height: 1;
background-color: #f7f8fa;
-webkit-overflow-scrolling: touch; /* 使ios列表滑动流畅*/
}
page,
@ -16,32 +16,32 @@ picker,
scroll-view,
cover-view,
open-data {
box-sizing: border-box;
box-sizing: border-box;
}
rich-text,
open-data,
form {
display: block;
display: block;
}
cover-view {
line-height: 1.5;
white-space: normal;
line-height: 1.5;
white-space: normal;
}
::webkit-scrollbar {
display: none;
display: none;
}
button::after {
border: 0;
border: 0;
}
/* 清除浮动 */
.clearfix:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
content: '.';
display: block;
height: 0;
clear: both;
visibility: hidden;
}

45
components/qn-data-picker/keypress.js

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

566
components/qn-data-picker/qn-data-picker.vue

@ -0,0 +1,566 @@
<template>
<view class="uni-data-tree">
<view class="uni-data-tree-input" @click="handleInput">
<slot :options="options" :data="inputSelected" :error="errorMessage">
<view class="input-value" :class="{ 'input-value-border': border }">
<text v-if="errorMessage" class="selected-area error-text">{{ errorMessage }}</text>
<view v-else-if="loading && !isOpened" class="selected-area">
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
</view>
<scroll-view v-else-if="inputSelected.length" class="selected-area" scroll-x="true">
<view class="selected-list" :style="listStyle">
<view class="selected-item" v-for="(item, index) in inputSelected" :key="index">
<text>{{ item.text }}</text>
<text v-if="index < inputSelected.length - 1" class="input-split-line">{{ split }}</text>
</view>
</view>
</scroll-view>
<text v-else class="selected-area placeholder" :style="listStyle">{{ placeholder }}</text>
<view v-show="clearIcon && !readonly && inputSelected.length" class="icon-clear" @click.stop="clear">
<uni-icons type="clear" color="#e1e1e1" size="14"></uni-icons>
</view>
<view class="arrow-area" v-if="(!clearIcon || !inputSelected.length) && !readonly">
<view class="input-arrow"></view>
</view>
</view>
</slot>
</view>
<view class="uni-data-tree-cover" v-if="isOpened" @click="handleClose"></view>
<view class="uni-data-tree-dialog" v-if="isOpened">
<view class="uni-popper__arrow"></view>
<view class="dialog-caption">
<view class="title-area">
<text class="dialog-title">{{ popupTitle }}</text>
</view>
<view class="dialog-close" @click="handleClose">
<view class="dialog-close-plus" data-id="close"></view>
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
</view>
</view>
<data-picker-view
class="picker-view"
ref="pickerView"
v-model="dataValue"
:localdata="localdata"
:preload="preload"
:collection="collection"
:field="field"
:orderby="orderby"
:where="where"
:step-searh="stepSearh"
:self-field="selfField"
:parent-field="parentField"
:managed-mode="true"
:map="map"
:ellipsis="ellipsis"
@change="onchange"
@datachange="ondatachange"
@nodeclick="onnodeclick"
></data-picker-view>
</view>
</view>
</template>
<script>
import dataPicker from '../qn-data-pickerview/qn-data-picker.js'
import DataPickerView from '../qn-data-pickerview/qn-data-pickerview.vue'
/**
* DataPicker 级联选择
* @description 支持单列和多列级联选择列数没有限制如果屏幕显示不全顶部tab区域会左右滚动
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
* @property {String} popup-title 弹出窗口标题
* @property {Array} localdata 本地数据参考
* @property {Boolean} border = [true|false] 是否有边框
* @property {Boolean} readonly = [true|false] 是否仅读
* @property {Boolean} preload = [true|false] 是否预加载数据
* @value true 开启预加载数据点击弹出窗口后显示已加载数据
* @value false 关闭预加载数据点击弹出窗口后开始加载数据
* @property {Boolean} step-searh = [true|false] 是否分布查询
* @value true 启用分布查询仅查询当前选中节点
* @value false 关闭分布查询一次查询出所有数据
* @property {String|DBFieldString} self-field 分布查询当前字段名称
* @property {String|DBFieldString} parent-field 分布查询父字段名称
* @property {String|DBCollectionString} collection 表名
* @property {String|DBFieldString} field 查询字段多个字段用 `,` 分割
* @property {String} orderby 排序字段及正序倒叙设置
* @property {String|JQLString} where 查询条件
* @event {Function} popupshow 弹出的选择窗口打开时触发此事件
* @event {Function} popuphide 弹出的选择窗口关闭时触发此事件
*/
const styleEnum = {
left: 'flex-start',
center: 'center',
right: 'flex-end'
}
export default {
name: 'qnDataPicker',
emits: ['popupopened', 'popupclosed', 'nodeclick', 'input', 'change', 'update:modelValue'],
mixins: [dataPicker],
components: {
DataPickerView
},
props: {
options: {
type: [Object, Array],
default() {
return {}
}
},
text: {
type: String,
default: 'left'
},
popupTitle: {
type: String,
default: '请选择'
},
placeholder: {
type: String,
default: '请选择'
},
heightMobile: {
type: String,
default: ''
},
readonly: {
type: Boolean,
default: false
},
clearIcon: {
type: Boolean,
default: true
},
border: {
type: Boolean,
default: true
},
split: {
type: String,
default: '/'
},
ellipsis: {
type: Boolean,
default: true
}
},
data() {
return {
isOpened: false,
inputSelected: []
}
},
created() {
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
this.$nextTick(() => {
this.load()
})
},
methods: {
clear() {
this.inputSelected.splice(0)
this._dispatchEvent([])
},
onPropsChange() {
this._treeData = []
this.selectedIndex = 0
this.load()
},
load() {
if (this.readonly) {
this._processReadonly(this.localdata, this.dataValue)
return
}
if (this.isLocaldata) {
this.loadData()
this.inputSelected = this.selected.slice(0)
} else if (!this.parentField && !this.selfField && this.hasValue) {
this.getNodeData(() => {
this.inputSelected = this.selected.slice(0)
})
} else if (this.hasValue) {
this.getTreePath(() => {
this.inputSelected = this.selected.slice(0)
})
}
},
getForm(name = 'uniForms') {
let parent = this.$parent
let parentName = parent.$options.name
while (parentName !== name) {
parent = parent.$parent
if (!parent) return false
parentName = parent.$options.name
}
return parent
},
show() {
this.isOpened = true
this.$nextTick(() => {
this.$refs.pickerView.updateData({
treeData: this._treeData,
selected: this.selected,
selectedIndex: this.selectedIndex
})
})
this.$emit('popupopened')
},
hide() {
this.isOpened = false
this.$emit('popupclosed')
},
handleInput() {
if (this.readonly) {
return
}
this.show()
},
handleClose(e) {
this.hide()
},
onnodeclick(e) {
this.$emit('nodeclick', e)
},
ondatachange(e) {
this._treeData = this.$refs.pickerView._treeData
},
onchange(e) {
this.hide()
this.inputSelected = e
this._dispatchEvent(e)
},
_processReadonly(dataList, value) {
var isTree = dataList.findIndex((item) => {
return item.children
})
if (isTree > -1) {
let inputValue
if (Array.isArray(value)) {
inputValue = value[value.length - 1]
if (typeof inputValue === 'object' && inputValue.value) {
inputValue = inputValue.value
}
} else {
inputValue = value
}
this.inputSelected = this._findNodePath(inputValue, this.localdata)
return
}
if (!this.hasValue) {
this.inputSelected = []
return
}
let result = []
for (let i = 0; i < value.length; i++) {
var val = value[i]
var item = dataList.find((v) => {
return v.value == val
})
if (item) {
result.push(item)
}
}
if (result.length) {
this.inputSelected = result
}
},
_filterForArray(data, valueArray) {
var result = []
for (let i = 0; i < valueArray.length; i++) {
var value = valueArray[i]
var found = data.find((item) => {
return item.value == value
})
if (found) {
result.push(found)
}
}
return result
},
_dispatchEvent(selected) {
let item = {}
if (selected.length) {
var value = new Array(selected.length)
for (var i = 0; i < selected.length; i++) {
value[i] = selected[i].value
}
item = selected[selected.length - 1]
} else {
item.value = ''
}
if (this.formItem) {
this.formItem.setValue(item.value)
}
this.$emit('input', item.value)
this.$emit('update:modelValue', item.value)
this.$emit('change', {
detail: {
value: selected
}
})
}
},
computed: {
listStyle() {
const style = styleEnum[this.text]
return `justify-content:${style}`
}
}
}
</script>
<style scoped>
.uni-data-tree {
position: relative;
font-size: 14px;
}
.error-text {
color: #dd524d;
}
.input-value {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
font-size: 14px;
line-height: 38px;
padding: 0 5px;
overflow: hidden;
/* #ifdef APP-NVUE */
height: 40px;
/* #endif */
}
.input-value-border {
border: 1px solid #e5e5e5;
border-radius: 5px;
}
.selected-area {
flex: 1;
overflow: hidden;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.load-more {
/* #ifndef APP-NVUE */
margin-right: auto;
/* #endif */
/* #ifdef APP-NVUE */
width: 40px;
/* #endif */
}
.selected-list {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: nowrap;
padding: 0 5px;
}
.selected-item {
flex-direction: row;
padding: 0 1px;
/* #ifndef APP-NVUE */
white-space: nowrap;
/* #endif */
}
.placeholder {
color: grey;
}
.input-split-line {
opacity: 0.5;
}
.arrow-area {
position: relative;
width: 20px;
/* #ifndef APP-NVUE */
margin-bottom: 5px;
margin-left: auto;
display: flex;
/* #endif */
justify-content: center;
transform: rotate(-45deg);
transform-origin: center;
}
.input-arrow {
width: 7px;
height: 7px;
border-left: 1px solid #999;
border-bottom: 1px solid #999;
}
.uni-data-tree-cover {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
z-index: 100;
}
.uni-data-tree-dialog {
position: fixed;
left: 0;
top: 20%;
right: 0;
bottom: 0;
background-color: #ffffff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
z-index: 102;
overflow: hidden;
/* #ifdef APP-NVUE */
width: 750rpx;
/* #endif */
}
.dialog-caption {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
/* border-bottom: 1px solid #f0f0f0; */
}
.title-area {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
/* #ifndef APP-NVUE */
margin: auto;
/* #endif */
padding: 0 10px;
}
.dialog-title {
/* font-weight: bold; */
line-height: 44px;
}
.dialog-close {
position: absolute;
top: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 0 15px;
}
.dialog-close-plus {
width: 16px;
height: 2px;
background-color: #666;
border-radius: 2px;
transform: rotate(45deg);
}
.dialog-close-rotate {
position: absolute;
transform: rotate(-45deg);
}
.picker-view {
flex: 1;
overflow: hidden;
}
/* #ifdef H5 */
@media all and (min-width: 768px) {
.uni-data-tree-cover {
background-color: transparent;
}
.uni-data-tree-dialog {
position: absolute;
top: 55px;
height: auto;
min-height: 400px;
max-height: 50vh;
background-color: #fff;
border: 1px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
overflow: unset;
}
.dialog-caption {
display: none;
}
.icon-clear {
margin-right: 5px;
}
}
/* #endif */
/* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */
.uni-popper__arrow,
.uni-popper__arrow::after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 6px;
}
.uni-popper__arrow {
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
top: -6px;
left: 10%;
margin-right: 3px;
border-top-width: 0;
border-bottom-color: #ebeef5;
}
.uni-popper__arrow::after {
content: ' ';
top: 1px;
margin-left: -6px;
border-top-width: 0;
border-bottom-color: #fff;
}
</style>

563
components/qn-data-pickerview/qn-data-picker.js

@ -0,0 +1,563 @@
export default {
props: {
localdata: {
type: [Array, Object],
default () {
return []
}
},
spaceInfo: {
type: Object,
default () {
return {}
}
},
collection: {
type: String,
default: ''
},
action: {
type: String,
default: ''
},
field: {
type: String,
default: ''
},
orderby: {
type: String,
default: ''
},
where: {
type: [String, Object],
default: ''
},
pageData: {
type: String,
default: 'add'
},
pageCurrent: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
getcount: {
type: [Boolean, String],
default: false
},
getone: {
type: [Boolean, String],
default: false
},
gettree: {
type: [Boolean, String],
default: false
},
manual: {
type: Boolean,
default: false
},
value: {
type: [Array, String, Number],
default () {
return []
}
},
modelValue: {
type: [Array, String, Number],
default () {
return []
}
},
preload: {
type: Boolean,
default: false
},
stepSearh: {
type: Boolean,
default: true
},
selfField: {
type: String,
default: ''
},
parentField: {
type: String,
default: ''
},
multiple: {
type: Boolean,
default: false
},
map: {
type: Object,
default() {
return {
text: "text",
value: "value"
}
}
}
},
data() {
return {
loading: false,
errorMessage: '',
loadMore: {
contentdown: '',
contentrefresh: '',
contentnomore: ''
},
dataList: [],
selected: [],
selectedIndex: 0,
page: {
current: this.pageCurrent,
size: this.pageSize,
count: 0
}
}
},
computed: {
isLocaldata() {
return !this.collection.length
},
postField() {
let fields = [this.field];
if (this.parentField) {
fields.push(`${this.parentField} as parent_value`);
}
return fields.join(',');
},
dataValue() {
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null || this.modelValue !== undefined)
return isModelValue ? this.modelValue : this.value
},
hasValue() {
if (typeof this.dataValue === 'number') {
return true
}
return (this.dataValue != null) && (this.dataValue.length > 0)
}
},
created() {
this.$watch(() => {
var al = [];
['pageCurrent',
'pageSize',
'spaceInfo',
'value',
'modelValue',
'localdata',
'collection',
'action',
'field',
'orderby',
'where',
'getont',
'getcount',
'gettree'
].forEach(key => {
al.push(this[key])
});
return al
}, (newValue, oldValue) => {
let needReset = false
for (let i = 2; i < newValue.length; i++) {
if (newValue[i] != oldValue[i]) {
needReset = true
break
}
}
if (newValue[0] != oldValue[0]) {
this.page.current = this.pageCurrent
}
this.page.size = this.pageSize
this.onPropsChange()
})
this._treeData = []
},
methods: {
onPropsChange() {
this._treeData = []
},
getCommand(options = {}) {
/* eslint-disable no-undef */
let db = uniCloud.database(this.spaceInfo)
const action = options.action || this.action
if (action) {
db = db.action(action)
}
const collection = options.collection || this.collection
db = db.collection(collection)
const where = options.where || this.where
if (!(!where || !Object.keys(where).length)) {
db = db.where(where)
}
const field = options.field || this.field
if (field) {
db = db.field(field)
}
const orderby = options.orderby || this.orderby
if (orderby) {
db = db.orderBy(orderby)
}
const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current
const size = options.pageSize !== undefined ? options.pageSize : this.page.size
const getCount = options.getcount !== undefined ? options.getcount : this.getcount
const getTree = options.gettree !== undefined ? options.gettree : this.gettree
const getOptions = {
getCount,
getTree
}
if (options.getTreePath) {
getOptions.getTreePath = options.getTreePath
}
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
return db
},
getNodeData(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
where: this._pathWhere()
}).then((res) => {
this.loading = false
this.selected = res.result.data
callback && callback()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
getTreePath(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
getTreePath: {
startWith: `${this.selfField}=='${this.dataValue}'`
}
}).then((res) => {
this.loading = false
let treePath = []
this._extractTreePath(res.result.data, treePath)
this.selected = treePath
callback && callback()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
loadData() {
if (this.isLocaldata) {
this._processLocalData()
return
}
if (this.dataValue != null) {
this._loadNodeData((data) => {
this._treeData = data
this._updateBindData()
this._updateSelected()
})
return
}
if (this.stepSearh) {
this._loadNodeData((data) => {
this._treeData = data
this._updateBindData()
})
} else {
this._loadAllData((data) => {
this._treeData = []
this._extractTree(data, this._treeData, null)
this._updateBindData()
})
}
},
_loadAllData(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
gettree: true,
startwith: `${this.selfField}=='${this.dataValue}'`
}).then((res) => {
this.loading = false
callback(res.result.data)
this.onDataChange()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
_loadNodeData(callback, pw) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
where: pw || this._postWhere(),
pageSize: 500
}).then((res) => {
this.loading = false
callback(res.result.data)
this.onDataChange()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
_pathWhere() {
let result = []
let where_field = this._getParentNameByField();
if (where_field) {
result.push(`${where_field} == '${this.dataValue}'`)
}
if (this.where) {
return `(${this.where}) && (${result.join(' || ')})`
}
return result.join(' || ')
},
_postWhere() {
let result = []
let selected = this.selected
let parentField = this.parentField
if (parentField) {
result.push(`${parentField} == null || ${parentField} == ""`)
}
if (selected.length) {
for (var i = 0; i < selected.length - 1; i++) {
result.push(`${parentField} == '${selected[i].value}'`)
}
}
let where = []
if (this.where) {
where.push(`(${this.where})`)
}
if (result.length) {
where.push(`(${result.join(' || ')})`)
}
return where.join(' && ')
},
_nodeWhere() {
let result = []
let selected = this.selected
if (selected.length) {
result.push(`${this.parentField} == '${selected[selected.length - 1].value}'`)
}
if (this.where) {
return `(${this.where}) && (${result.join(' || ')})`
}
return result.join(' || ')
},
_getParentNameByField() {
const fields = this.field.split(',');
let where_field = null;
for (let i = 0; i < fields.length; i++) {
const items = fields[i].split('as');
if (items.length < 2) {
continue;
}
if (items[1].trim() === 'value') {
where_field = items[0].trim();
break;
}
}
return where_field
},
_isTreeView() {
return (this.parentField && this.selfField)
},
_updateSelected() {
var dl = this.dataList
var sl = this.selected
let textField = this.map.text
let valueField = this.map.value
for (var i = 0; i < sl.length; i++) {
var value = sl[i].value
var dl2 = dl[i]
for (var j = 0; j < dl2.length; j++) {
var item2 = dl2[j]
if (item2[valueField] === value) {
sl[i].text = item2[textField]
break
}
}
}
},
_updateBindData(node) {
const {
dataList,
hasNodes
} = this._filterData(this._treeData, this.selected)
let isleaf = this._stepSearh === false && !hasNodes
if (node) {
node.isleaf = isleaf
}
this.dataList = dataList
this.selectedIndex = dataList.length - 1
if (!isleaf && this.selected.length < dataList.length) {
this.selected.push({
value: null,
text: "请选择"
})
}
return {
isleaf,
hasNodes
}
},
_filterData(data, paths) {
let dataList = []
let hasNodes = true
dataList.push(data.filter((item) => {
return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
}))
for (let i = 0; i < paths.length; i++) {
var value = paths[i].value
var nodes = data.filter((item) => {
return item.parent_value === value
})
if (nodes.length) {
dataList.push(nodes)
} else {
hasNodes = false
}
}
return {
dataList,
hasNodes
}
},
_extractTree(nodes, result, parent_value) {
let list = result || []
let valueField = this.map.value
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let child = {}
for (let key in node) {
if (key !== 'children') {
child[key] = node[key]
}
}
if (parent_value !== null && parent_value !== undefined && parent_value !== '') {
child.parent_value = parent_value
}
result.push(child)
let children = node.children
if (children) {
this._extractTree(children, result, node[valueField])
}
}
},
_extractTreePath(nodes, result) {
let list = result || []
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let child = {}
for (let key in node) {
if (key !== 'children') {
child[key] = node[key]
}
}
result.push(child)
let children = node.children
if (children) {
this._extractTreePath(children, result)
}
}
},
_findNodePath(key, nodes, path = []) {
let textField = this.map.text
let valueField = this.map.value
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let children = node.children
let text = node[textField]
let value = node[valueField]
path.push({
value,
text
})
if (value === key) {
return path
}
if (children) {
const p = this._findNodePath(key, children, path)
if (p.length) {
return p
}
}
path.pop()
}
return []
},
_processLocalData() {
this._treeData = []
this._extractTree(this.localdata, this._treeData)
var inputValue = this.dataValue
if (inputValue === undefined) {
return
}
if (Array.isArray(inputValue)) {
inputValue = inputValue[inputValue.length - 1]
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
inputValue = inputValue[this.map.value]
}
}
this.selected = this._findNodePath(inputValue, this.localdata)
}
}
}

334
components/qn-data-pickerview/qn-data-pickerview.vue

@ -0,0 +1,334 @@
<template>
<view class="uni-data-pickerview">
<scroll-view class="selected-area" scroll-x="true" scroll-y="false" :show-scrollbar="false">
<view class="selected-list">
<template v-for="(item, index) in selected">
<view
class="selected-item"
:class="{ 'selected-item-active': index == selectedIndex, 'selected-item-text-overflow': ellipsis }"
:key="index"
v-if="item.text"
@click="handleSelect(index)"
>
<text class="">{{ item.text }}</text>
</view>
</template>
</view>
</scroll-view>
<view class="tab-c">
<template v-for="(child, i) in dataList">
<scroll-view class="list" :key="i" v-if="i == selectedIndex" :scroll-y="true">
<view class="item" :class="{ 'is-disabled': !!item.disable }" v-for="(item, j) in child" :key="j" @click="handleNodeClick(item, i, j)">
<text class="item-text item-text-overflow">{{ item[map.text] }}</text>
<view class="check" v-if="selected.length > i && item[map.value] == selected[i].value"></view>
</view>
</scroll-view>
</template>
<view class="loading-cover" v-if="loading">
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
</view>
<view class="error-message" v-if="errorMessage">
<text class="error-text">{{ errorMessage }}</text>
</view>
</view>
</view>
</template>
<script>
import dataPicker from './qn-data-picker.js'
/**
* DataPickerview
* @description uni-data-pickerview
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
* @property {Array} localdata 本地数据参考
* @property {Boolean} step-searh = [true|false] 是否分布查询
* @value true 启用分布查询仅查询当前选中节点
* @value false 关闭分布查询一次查询出所有数据
* @property {String|DBFieldString} self-field 分布查询当前字段名称
* @property {String|DBFieldString} parent-field 分布查询父字段名称
* @property {String|DBCollectionString} collection 表名
* @property {String|DBFieldString} field 查询字段多个字段用 `,` 分割
* @property {String} orderby 排序字段及正序倒叙设置
* @property {String|JQLString} where 查询条件
*/
export default {
name: 'UniDataPickerView',
emits: ['nodeclick', 'change', 'datachange', 'update:modelValue'],
mixins: [dataPicker],
props: {
managedMode: {
type: Boolean,
default: false
},
ellipsis: {
type: Boolean,
default: true
}
},
data() {
return {}
},
created() {
if (this.managedMode) {
return
}
this.$nextTick(() => {
this.load()
})
},
methods: {
onPropsChange() {
this._treeData = []
this.selectedIndex = 0
this.load()
},
load() {
if (this.isLocaldata) {
this.loadData()
} else if (this.dataValue.length) {
this.getTreePath((res) => {
this.loadData()
})
}
},
handleSelect(index) {
this.selectedIndex = index
},
handleNodeClick(item, i, j) {
if (item.disable) {
return
}
const node = this.dataList[i][j]
const text = node[this.map.text]
const value = node[this.map.value]
if (i < this.selected.length - 1) {
this.selected.splice(i, this.selected.length - i)
this.selected.push({
text,
value
})
} else if (i === this.selected.length - 1) {
this.selected.splice(i, 1, {
text,
value
})
}
if (node.isleaf) {
this.onSelectedChange(node, node.isleaf)
return
}
const { isleaf, hasNodes } = this._updateBindData()
if (!this._isTreeView() && !hasNodes) {
this.onSelectedChange(node, true)
return
}
if (this.isLocaldata && (!hasNodes || isleaf)) {
this.onSelectedChange(node, true)
return
}
if (!isleaf && !hasNodes) {
this._loadNodeData((data) => {
if (!data.length) {
node.isleaf = true
} else {
this._treeData.push(...data)
this._updateBindData(node)
}
this.onSelectedChange(node, node.isleaf)
}, this._nodeWhere())
return
}
this.onSelectedChange(node, false)
},
updateData(data) {
this._treeData = data.treeData
this.selected = data.selected
if (!this._treeData.length) {
this.loadData()
} else {
//this.selected = data.selected
this._updateBindData()
}
},
onDataChange() {
this.$emit('datachange')
},
onSelectedChange(node, isleaf) {
if (isleaf) {
this._dispatchEvent()
}
if (node) {
this.$emit('nodeclick', node)
}
},
_dispatchEvent() {
this.$emit('change', this.selected.slice(0))
}
}
}
</script>
<style scoped>
.uni-data-pickerview {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
overflow: hidden;
height: 100%;
}
.error-text {
color: #dd524d;
}
.loading-cover {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.5);
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
z-index: 1001;
}
.load-more {
/* #ifndef APP-NVUE */
margin: auto;
/* #endif */
}
.error-message {
background-color: #fff;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
padding: 15px;
opacity: 0.9;
z-index: 102;
}
/* #ifdef APP-NVUE */
.selected-area {
width: 750rpx;
}
/* #endif */
.selected-list {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: nowrap;
padding: 0 5px;
border-bottom: 1px solid #f8f8f8;
}
.selected-item {
margin-left: 10px;
margin-right: 10px;
padding: 12px 0;
text-align: center;
/* #ifndef APP-NVUE */
white-space: nowrap;
/* #endif */
}
.selected-item-text-overflow {
width: 168px;
/* fix nvue */
overflow: hidden;
/* #ifndef APP-NVUE */
width: 6em;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
/* #endif */
}
.selected-item-active {
border-bottom: 2px solid #007aff;
}
.selected-item-text {
color: #007aff;
}
.tab-c {
position: relative;
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
overflow: hidden;
}
.list {
flex: 1;
}
.item {
padding: 12px 15px;
/* border-bottom: 1px solid #f0f0f0; */
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
}
.is-disabled {
opacity: 0.5;
}
.item-text {
/* flex: 1; */
color: #333333;
text-align: left;
}
.item-text-overflow {
width: 280px;
/* fix nvue */
overflow: hidden;
/* #ifndef APP-NVUE */
width: 20em;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
/* #endif */
}
.check {
margin-right: 5px;
border: 2px solid #007aff;
border-left: 0;
border-top: 0;
height: 12px;
width: 6px;
transform-origin: center;
/* #ifndef APP-NVUE */
transition: all 0.3s;
/* #endif */
transform: rotate(45deg);
}
</style>

185
components/qn-datetime-picker/calendar-item.vue

@ -0,0 +1,185 @@
<template>
<view class="uni-calendar-item__weeks-box" :class="{
'uni-calendar-item--disable':weeks.disable,
'uni-calendar-item--before-checked-x':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked-x':weeks.afterMultiple,
}" @click="choiceDate(weeks)" @mouseenter="handleMousemove(weeks)">
<view class="uni-calendar-item__weeks-box-item" :class="{
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && (calendar.userChecked || !checkHover),
'uni-calendar-item--checked-range-text': checkHover,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
<text class="uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text">{{weeks.date}}</text>
</view>
<view :class="{'uni-calendar-item--isDay': weeks.isDay}"></view>
</view>
</template>
<script>
export default {
props: {
weeks: {
type: Object,
default () {
return {}
}
},
calendar: {
type: Object,
default: () => {
return {}
}
},
selected: {
type: Array,
default: () => {
return []
}
},
lunar: {
type: Boolean,
default: false
},
checkHover: {
type: Boolean,
default: false
}
},
methods: {
choiceDate(weeks) {
this.$emit('change', weeks)
},
handleMousemove(weeks) {
this.$emit('handleMouse', weeks)
}
}
}
</script>
<style lang="scss" scoped>
.uni-calendar-item__weeks-box {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
margin: 1px 0;
position: relative;
}
.uni-calendar-item__weeks-box-text {
font-size: 14px;
// font-family: Lato-Bold, Lato;
font-weight: bold;
color: #455997;
}
.uni-calendar-item__weeks-lunar-text {
font-size: 12px;
color: #333;
}
.uni-calendar-item__weeks-box-item {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-calendar-item__weeks-box-circle {
position: absolute;
top: 5px;
right: 5px;
width: 8px;
height: 8px;
border-radius: 8px;
background-color: #dd524d;
}
.uni-calendar-item__weeks-box .uni-calendar-item--disable {
// background-color: rgba(249, 249, 249, $uni-opacity-disabled);
cursor: default;
}
.uni-calendar-item--disable .uni-calendar-item__weeks-box-text-disable {
color: #D1D1D1;
}
.uni-calendar-item--isDay {
position: absolute;
top: 10px;
right: 17%;
background-color: #dd524d;
width:6px;
height: 6px;
border-radius: 50%;
}
.uni-calendar-item--extra {
color: #dd524d;
opacity: 0.8;
}
.uni-calendar-item__weeks-box .uni-calendar-item--checked {
background-color: #007aff;
border-radius: 50%;
box-sizing: border-box;
border: 3px solid #fff;
}
.uni-calendar-item--checked .uni-calendar-item--checked-text {
color: #fff;
}
.uni-calendar-item--multiple .uni-calendar-item--checked-range-text {
color: #333;
}
.uni-calendar-item--multiple {
background-color: #F6F7FC;
// color: #fff;
}
.uni-calendar-item--multiple .uni-calendar-item--before-checked,
.uni-calendar-item--multiple .uni-calendar-item--after-checked {
background-color: #409eff;
border-radius: 50%;
box-sizing: border-box;
border: 3px solid #F6F7FC;
}
.uni-calendar-item--before-checked .uni-calendar-item--checked-text,
.uni-calendar-item--after-checked .uni-calendar-item--checked-text {
color: #fff;
}
.uni-calendar-item--before-checked-x {
border-top-left-radius: 50px;
border-bottom-left-radius: 50px;
box-sizing: border-box;
background-color: #F6F7FC;
}
.uni-calendar-item--after-checked-x {
border-top-right-radius: 50px;
border-bottom-right-radius: 50px;
background-color: #F6F7FC;
}
</style>

898
components/qn-datetime-picker/calendar.vue

@ -0,0 +1,898 @@
<template>
<view class="uni-calendar" @mouseleave="leaveCale">
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"
@click="clean"></view>
<view v-if="insert || show" class="uni-calendar__content"
:class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}">
<view class="uni-calendar__header" :class="{'uni-calendar__header-mobile' :!insert}">
<view v-if="left" class="uni-calendar__header-btn-box" @click.stop="pre">
<view class="uni-calendar__header-btn uni-calendar--left"></view>
</view>
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
<text
class="uni-calendar__header-text">{{ (nowDate.year||'') + ' 年 ' + ( nowDate.month||'') +' 月'}}</text>
</picker>
<view v-if="right" class="uni-calendar__header-btn-box" @click.stop="next">
<view class="uni-calendar__header-btn uni-calendar--right"></view>
</view>
<view v-if="!insert" class="dialog-close" @click="clean">
<view class="dialog-close-plus" data-id="close"></view>
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
</view>
<!-- <text class="uni-calendar__backtoday" @click="backtoday">回到今天</text> -->
</view>
<view class="uni-calendar__box">
<view v-if="showMonth" class="uni-calendar__box-bg">
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
</view>
<view class="uni-calendar__weeks" style="padding-bottom: 7px;">
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
</view>
</view>
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar"
:selected="selected" :lunar="lunar" :checkHover="range" @change="choiceDate"
@handleMouse="handleMouse">
</calendar-item>
</view>
</view>
</view>
<view v-if="!insert && !range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top"
style="padding: 0 80px;">
<view class="uni-date-changed--time-date">{{tempSingleDate ? tempSingleDate : selectDateText}}</view>
<time-picker type="time" :start="reactStartTime" :end="reactEndTime" v-model="time"
:disabled="!tempSingleDate" :border="false" :hide-second="hideSecond" class="time-picker-style">
</time-picker>
</view>
<view v-if="!insert && range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top">
<view class="uni-date-changed--time-start">
<view class="uni-date-changed--time-date">{{tempRange.before ? tempRange.before : startDateText}}
</view>
<time-picker type="time" :start="reactStartTime" v-model="timeRange.startTime" :border="false"
:hide-second="hideSecond" :disabled="!tempRange.before" class="time-picker-style">
</time-picker>
</view>
<uni-icons type="arrowthinright" color="#999" style="line-height: 50px;"></uni-icons>
<view class="uni-date-changed--time-end">
<view class="uni-date-changed--time-date">{{tempRange.after ? tempRange.after : endDateText}}</view>
<time-picker type="time" :end="reactEndTime" v-model="timeRange.endTime" :border="false"
:hide-second="hideSecond" :disabled="!tempRange.after" class="time-picker-style">
</time-picker>
</view>
</view>
<view v-if="!insert" class="uni-date-changed uni-date-btn--ok">
<!-- <view class="uni-calendar__header-btn-box">
<text class="uni-calendar__button-text uni-calendar--fixed-width">{{okText}}</text>
</view> -->
<view class="uni-datetime-picker--btn" @click="confirm">确认</view>
</view>
</view>
</view>
</template>
<script>
import Calendar from './util.js';
import calendarItem from './calendar-item.vue'
import timePicker from './time-picker.vue'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const {
t
} = initVueI18n(messages)
/**
* Calendar 日历
* @description 日历组件可以查看日期选择任意范围内的日期打点操作常用场景如酒店日期预订火车机票选择购买日期上下班打卡等
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
* @property {String} date 自定义当前时间默认为今天
* @property {Boolean} lunar 显示农历
* @property {String} startDate 日期选择范围-开始日期
* @property {String} endDate 日期选择范围-结束日期
* @property {Boolean} range 范围选择
* @property {Boolean} insert = [true|false] 插入模式,默认为false
* @value true 弹窗模式
* @value false 插入模式
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
* @property {Array} selected 打点期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
* @property {Boolean} showMonth 是否选择月份为背景
* @event {Function} change 日期改变`insert :ture` 时生效
* @event {Function} confirm 确认选择`insert :false` 时生效
* @event {Function} monthSwitch 切换月份时触发
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
*/
export default {
components: {
calendarItem,
timePicker
},
props: {
date: {
type: String,
default: ''
},
defTime: {
type: [String, Object],
default: ''
},
selectableTimes: {
type: [Object],
default () {
return {}
}
},
selected: {
type: Array,
default () {
return []
}
},
lunar: {
type: Boolean,
default: false
},
startDate: {
type: String,
default: ''
},
endDate: {
type: String,
default: ''
},
range: {
type: Boolean,
default: false
},
typeHasTime: {
type: Boolean,
default: false
},
insert: {
type: Boolean,
default: true
},
showMonth: {
type: Boolean,
default: true
},
clearDate: {
type: Boolean,
default: true
},
left: {
type: Boolean,
default: true
},
right: {
type: Boolean,
default: true
},
checkHover: {
type: Boolean,
default: true
},
hideSecond: {
type: [Boolean],
default: false
},
pleStatus: {
type: Object,
default () {
return {
before: '',
after: '',
data: [],
fulldate: ''
}
}
}
},
data() {
return {
show: false,
weeks: [],
calendar: {},
nowDate: '',
aniMaskShow: false,
firstEnter: true,
time: '',
timeRange: {
startTime: '',
endTime: ''
},
tempSingleDate: '',
tempRange: {
before: '',
after: ''
}
}
},
watch: {
date: {
immediate: true,
handler(newVal, oldVal) {
if (!this.range) {
this.tempSingleDate = newVal
setTimeout(() => {
this.init(newVal)
}, 100)
}
}
},
defTime: {
immediate: true,
handler(newVal, oldVal) {
if (!this.range) {
this.time = newVal
} else {
// console.log('-----', newVal);
this.timeRange.startTime = newVal.start
this.timeRange.endTime = newVal.end
}
}
},
startDate(val) {
this.cale.resetSatrtDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
endDate(val) {
this.cale.resetEndDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
selected(newVal) {
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
this.weeks = this.cale.weeks
},
pleStatus: {
immediate: true,
handler(newVal, oldVal) {
const {
before,
after,
fulldate,
which
} = newVal
this.tempRange.before = before
this.tempRange.after = after
setTimeout(() => {
if (fulldate) {
this.cale.setHoverMultiple(fulldate)
if (before && after) {
this.cale.lastHover = true
if (this.rangeWithinMonth(after, before)) return
this.setDate(before)
} else {
this.cale.setMultiple(fulldate)
this.setDate(this.nowDate.fullDate)
this.calendar.fullDate = ''
this.cale.lastHover = false
}
} else {
this.cale.setDefaultMultiple(before, after)
if (which === 'left') {
this.setDate(before)
this.weeks = this.cale.weeks
} else {
this.setDate(after)
this.weeks = this.cale.weeks
}
this.cale.lastHover = true
}
}, 16)
}
}
},
computed: {
reactStartTime() {
const activeDate = this.range ? this.tempRange.before : this.calendar.fullDate
const res = activeDate === this.startDate ? this.selectableTimes.start : ''
return res
},
reactEndTime() {
const activeDate = this.range ? this.tempRange.after : this.calendar.fullDate
const res = activeDate === this.endDate ? this.selectableTimes.end : ''
return res
},
/**
* for i18n
*/
selectDateText() {
return t("uni-datetime-picker.selectDate")
},
startDateText() {
return this.startPlaceholder || t("uni-datetime-picker.startDate")
},
endDateText() {
return this.endPlaceholder || t("uni-datetime-picker.endDate")
},
okText() {
return t("uni-datetime-picker.ok")
},
monText() {
return t("uni-calender.MON")
},
TUEText() {
return t("uni-calender.TUE")
},
WEDText() {
return t("uni-calender.WED")
},
THUText() {
return t("uni-calender.THU")
},
FRIText() {
return t("uni-calender.FRI")
},
SATText() {
return t("uni-calender.SAT")
},
SUNText() {
return t("uni-calender.SUN")
},
},
created() {
//
this.cale = new Calendar({
// date: new Date(),
selected: this.selected,
startDate: this.startDate,
endDate: this.endDate,
range: this.range,
// multipleStatus: this.pleStatus
})
//
// this.cale.setDate(this.date)
this.init(this.date)
// this.setDay
},
methods: {
leaveCale() {
this.firstEnter = true
},
handleMouse(weeks) {
if (weeks.disable) return
if (this.cale.lastHover) return
let {
before,
after
} = this.cale.multipleStatus
if (!before) return
this.calendar = weeks
//
this.cale.setHoverMultiple(this.calendar.fullDate)
this.weeks = this.cale.weeks
// hover
if (this.firstEnter) {
this.$emit('firstEnterCale', this.cale.multipleStatus)
this.firstEnter = false
}
},
rangeWithinMonth(A, B) {
const [yearA, monthA] = A.split('-')
const [yearB, monthB] = B.split('-')
return yearA === yearB && monthA === monthB
},
// 穿
clean() {
this.close()
},
clearCalender() {
if (this.range) {
this.timeRange.startTime = ''
this.timeRange.endTime = ''
this.tempRange.before = ''
this.tempRange.after = ''
this.cale.multipleStatus.before = ''
this.cale.multipleStatus.after = ''
this.cale.multipleStatus.data = []
this.cale.lastHover = false
} else {
this.time = ''
this.tempSingleDate = ''
}
this.calendar.fullDate = ''
this.setDate()
},
bindDateChange(e) {
const value = e.detail.value + '-1'
this.init(value)
},
/**
* 初始化日期显示
* @param {Object} date
*/
init(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.calendar = this.cale.getInfo(date)
},
// choiceDate(weeks) {
// if (weeks.disable) return
// this.calendar = weeks
// //
// this.cale.setMultiple(this.calendar.fullDate, true)
// this.weeks = this.cale.weeks
// this.tempSingleDate = this.calendar.fullDate
// this.tempRange.before = this.cale.multipleStatus.before
// this.tempRange.after = this.cale.multipleStatus.after
// this.change()
// },
/**
* 打开日历弹窗
*/
open() {
//
if (this.clearDate && !this.insert) {
this.cale.cleanMultipleStatus()
// this.cale.setDate(this.date)
this.init(this.date)
}
this.show = true
this.$nextTick(() => {
setTimeout(() => {
this.aniMaskShow = true
}, 50)
})
},
/**
* 关闭日历弹窗
*/
close() {
this.aniMaskShow = false
this.$nextTick(() => {
setTimeout(() => {
this.show = false
this.$emit('close')
}, 300)
})
},
/**
* 确认按钮
*/
confirm() {
this.setEmit('confirm')
this.close()
},
/**
* 变化触发
*/
change() {
if (!this.insert) return
this.setEmit('change')
},
/**
* 选择月份触发
*/
monthSwitch() {
let {
year,
month
} = this.nowDate
this.$emit('monthSwitch', {
year,
month: Number(month)
})
},
/**
* 派发事件
* @param {Object} name
*/
setEmit(name) {
let {
year,
month,
date,
fullDate,
lunar,
extraInfo
} = this.calendar
this.$emit(name, {
range: this.cale.multipleStatus,
year,
month,
date,
time: this.time,
timeRange: this.timeRange,
fulldate: fullDate,
lunar,
extraInfo: extraInfo || {}
})
},
/**
* 选择天触发
* @param {Object} weeks
*/
choiceDate(weeks) {
if (weeks.disable) return
this.calendar = weeks
this.calendar.userChecked = true
//
this.cale.setMultiple(this.calendar.fullDate, true)
this.weeks = this.cale.weeks
this.tempSingleDate = this.calendar.fullDate
this.tempRange.before = this.cale.multipleStatus.before
this.tempRange.after = this.cale.multipleStatus.after
this.change()
},
/**
* 回到今天
*/
backtoday() {
let date = this.cale.getDate(new Date()).fullDate
// this.cale.setDate(date)
this.init(date)
this.change()
},
/**
* 比较时间大小
*/
dateCompare(startDate, endDate) {
//
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
//
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
if (startDate <= endDate) {
return true
} else {
return false
}
},
/**
* 上个月
*/
pre() {
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
this.setDate(preDate)
this.monthSwitch()
},
/**
* 下个月
*/
next() {
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
this.setDate(nextDate)
this.monthSwitch()
},
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.cale.getInfo(date)
}
}
}
</script>
<style lang="scss" scoped>
.uni-calendar {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.uni-calendar__mask {
position: fixed;
bottom: 0;
top: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.4);
transition-property: opacity;
transition-duration: 0.3s;
opacity: 0;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-calendar--mask-show {
opacity: 1
}
.uni-calendar--fixed {
position: fixed;
bottom: calc(var(--window-bottom));
left: 0;
right: 0;
transition-property: transform;
transition-duration: 0.3s;
transform: translateY(460px);
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-calendar--ani-show {
transform: translateY(0);
}
.uni-calendar__content {
background-color: #fff;
}
.uni-calendar__content-mobile {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.1);
}
.uni-calendar__header {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
height: 50px;
}
.uni-calendar__header-mobile {
padding: 10px;
padding-bottom: 0;
}
.uni-calendar--fixed-top {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
border-top-color: rgba(0, 0, 0, 0.4);
border-top-style: solid;
border-top-width: 1px;
}
.uni-calendar--fixed-width {
width: 50px;
}
.uni-calendar__backtoday {
position: absolute;
right: 0;
top: 25rpx;
padding: 0 5px;
padding-left: 10px;
height: 25px;
line-height: 25px;
font-size: 12px;
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
color: #fff;
background-color: #f1f1f1;
}
.uni-calendar__header-text {
text-align: center;
width: 100px;
font-size: 15px;
color: #666;
}
.uni-calendar__button-text {
text-align: center;
width: 100px;
font-size: 14px;
color: #007aff;
/* #ifndef APP-NVUE */
letter-spacing: 3px;
/* #endif */
}
.uni-calendar__header-btn-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
}
.uni-calendar__header-btn {
width: 9px;
height: 9px;
border-left-color: #808080;
border-left-style: solid;
border-left-width: 1px;
border-top-color: #555555;
border-top-style: solid;
border-top-width: 1px;
}
.uni-calendar--left {
transform: rotate(-45deg);
}
.uni-calendar--right {
transform: rotate(135deg);
}
.uni-calendar__weeks {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-calendar__weeks-item {
flex: 1;
}
.uni-calendar__weeks-day {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
height: 40px;
border-bottom-color: #F5F5F5;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.uni-calendar__weeks-day-text {
font-size: 12px;
color: #B2B2B2;
}
.uni-calendar__box {
position: relative;
// padding: 0 10px;
padding-bottom: 7px;
}
.uni-calendar__box-bg {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.uni-calendar__box-bg-text {
font-size: 200px;
font-weight: bold;
color: #999;
opacity: 0.1;
text-align: center;
/* #ifndef APP-NVUE */
line-height: 1;
/* #endif */
}
.uni-date-changed {
padding: 0 10px;
// line-height: 50px;
text-align: center;
color: #333;
border-top-color: #DCDCDC;
;
border-top-style: solid;
border-top-width: 1px;
flex: 1;
}
.uni-date-btn--ok {
padding: 20px 15px;
}
.uni-date-changed--time-start {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
}
.uni-date-changed--time-end {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
}
.uni-date-changed--time-date {
color: #999;
line-height: 50px;
margin-right: 5px;
// opacity: 0.6;
}
.time-picker-style {
// width: 62px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center
}
.mr-10 {
margin-right: 10px;
}
.dialog-close {
position: absolute;
top: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 0 25px;
margin-top: 10px;
}
.dialog-close-plus {
width: 16px;
height: 2px;
background-color: #737987;
border-radius: 2px;
transform: rotate(45deg);
}
.dialog-close-rotate {
position: absolute;
transform: rotate(-45deg);
}
.uni-datetime-picker--btn {
border-radius: 100px;
height: 40px;
line-height: 40px;
background-color: #007aff;
color: #fff;
font-size: 16px;
letter-spacing: 5px;
}
/* #ifndef APP-NVUE */
.uni-datetime-picker--btn:active {
opacity: 0.7;
}
/* #endif */
</style>

19
components/qn-datetime-picker/i18n/en.json

@ -0,0 +1,19 @@
{
"uni-datetime-picker.selectDate": "select date",
"uni-datetime-picker.selectTime": "select time",
"uni-datetime-picker.selectDateTime": "select datetime",
"uni-datetime-picker.startDate": "start date",
"uni-datetime-picker.endDate": "end date",
"uni-datetime-picker.startTime": "start time",
"uni-datetime-picker.endTime": "end time",
"uni-datetime-picker.ok": "ok",
"uni-datetime-picker.clear": "clear",
"uni-datetime-picker.cancel": "cancel",
"uni-calender.MON": "MON",
"uni-calender.TUE": "TUE",
"uni-calender.WED": "WED",
"uni-calender.THU": "THU",
"uni-calender.FRI": "FRI",
"uni-calender.SAT": "SAT",
"uni-calender.SUN": "SUN"
}

8
components/qn-datetime-picker/i18n/index.js

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

19
components/qn-datetime-picker/i18n/zh-Hans.json

@ -0,0 +1,19 @@
{
"uni-datetime-picker.selectDate": "选择日期",
"uni-datetime-picker.selectTime": "选择时间",
"uni-datetime-picker.selectDateTime": "选择日期时间",
"uni-datetime-picker.startDate": "开始日期",
"uni-datetime-picker.endDate": "结束日期",
"uni-datetime-picker.startTime": "开始时间",
"uni-datetime-picker.endTime": "结束时间",
"uni-datetime-picker.ok": "确定",
"uni-datetime-picker.clear": "清除",
"uni-datetime-picker.cancel": "取消",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}

19
components/qn-datetime-picker/i18n/zh-Hant.json

@ -0,0 +1,19 @@
{
"uni-datetime-picker.selectDate": "選擇日期",
"uni-datetime-picker.selectTime": "選擇時間",
"uni-datetime-picker.selectDateTime": "選擇日期時間",
"uni-datetime-picker.startDate": "開始日期",
"uni-datetime-picker.endDate": "結束日期",
"uni-datetime-picker.startTime": "開始时间",
"uni-datetime-picker.endTime": "結束时间",
"uni-datetime-picker.ok": "確定",
"uni-datetime-picker.clear": "清除",
"uni-datetime-picker.cancel": "取消",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}

45
components/qn-datetime-picker/keypress.js

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

1008
components/qn-datetime-picker/qn-datetime-picker.vue
File diff suppressed because it is too large
View File

927
components/qn-datetime-picker/time-picker.vue

@ -0,0 +1,927 @@
<template>
<view class="uni-datetime-picker">
<view @click="initTimePicker">
<slot>
<view class="uni-datetime-picker-timebox-pointer"
:class="{'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border}">
<text class="uni-datetime-picker-text">{{time}}</text>
<view v-if="!time" class="uni-datetime-picker-time">
<text class="uni-datetime-picker-text">{{selectTimeText}}</text>
</view>
</view>
</slot>
</view>
<view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view>
<view v-if="visible" class="uni-datetime-picker-popup" :class="[dateShow && timeShow ? '' : 'fix-nvue-height']"
:style="fixNvueBug">
<view class="uni-title">
<text class="uni-datetime-picker-text">{{selectTimeText}}</text>
</view>
<view v-if="dateShow" class="uni-datetime-picker__container-box">
<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd"
@change="bindDateChange">
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
</picker-view>
<!-- 兼容 nvue 不支持伪类 -->
<text class="uni-datetime-picker-sign sign-left">-</text>
<text class="uni-datetime-picker-sign sign-right">-</text>
</view>
<view v-if="timeShow" class="uni-datetime-picker__container-box">
<picker-view class="uni-datetime-picker-view" :class="[hideSecond ? 'time-hide-second' : '']"
:indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange">
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
<picker-view-column v-if="!hideSecond">
<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
</view>
</picker-view-column>
</picker-view>
<!-- 兼容 nvue 不支持伪类 -->
<text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text>
<text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text>
</view>
<view class="uni-datetime-picker-btn">
<view @click="clearTime">
<text class="uni-datetime-picker-btn-text">{{clearText}}</text>
</view>
<view class="uni-datetime-picker-btn-group">
<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">
<text class="uni-datetime-picker-btn-text">{{cancelText}}</text>
</view>
<view @click="setTime">
<text class="uni-datetime-picker-btn-text">{{okText}}</text>
</view>
</view>
</view>
</view>
<!-- #ifdef H5 -->
<!-- <keypress v-if="visible" @esc="tiggerTimePicker" @enter="setTime" /> -->
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress'
// #endif
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const { t } = initVueI18n(messages)
/**
* DatetimePicker 时间选择器
* @description 可以同时选择日期和时间的选择器
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
* @property {String} type = [datetime | date | time] 显示模式
* @property {Boolean} multiple = [true|false] 是否多选
* @property {String|Number} value 默认值
* @property {String|Number} start 起始日期或时间
* @property {String|Number} end 起始日期或时间
* @property {String} return-type = [timestamp | string]
* @event {Function} change 选中发生变化触发
*/
export default {
name: 'UniDatetimePicker',
components: {
// #ifdef H5
keypress
// #endif
},
data() {
return {
indicatorStyle: `height: 50px;`,
visible: false,
fixNvueBug: {},
dateShow: true,
timeShow: true,
title: '日期和时间',
//
time: '',
//
year: 1920,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
//
startYear: 1920,
startMonth: 1,
startDay: 1,
startHour: 0,
startMinute: 0,
startSecond: 0,
//
endYear: 2120,
endMonth: 12,
endDay: 31,
endHour: 23,
endMinute: 59,
endSecond: 59,
}
},
props: {
type: {
type: String,
default: 'datetime'
},
value: {
type: [String, Number],
default: ''
},
modelValue: {
type: [String, Number],
default: ''
},
start: {
type: [Number, String],
default: ''
},
end: {
type: [Number, String],
default: ''
},
returnType: {
type: String,
default: 'string'
},
disabled: {
type: [Boolean, String],
default: false
},
border: {
type: [Boolean, String],
default: true
},
hideSecond: {
type: [Boolean, String],
default: false
}
},
watch: {
value: {
handler(newVal, oldVal) {
if (newVal) {
this.parseValue(this.fixIosDateFormat(newVal)) // iOSsafari
this.initTime(false)
} else {
this.time = ''
this.parseValue(Date.now())
}
},
immediate: true
},
type: {
handler(newValue) {
if (newValue === 'date') {
this.dateShow = true
this.timeShow = false
this.title = '日期'
} else if (newValue === 'time') {
this.dateShow = false
this.timeShow = true
this.title = '时间'
} else {
this.dateShow = true
this.timeShow = true
this.title = '日期和时间'
}
},
immediate: true
},
start: {
handler(newVal) {
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') // iOSsafari
},
immediate: true
},
end: {
handler(newVal) {
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') // iOSsafari
},
immediate: true
},
//
months(newVal) {
this.checkValue('month', this.month, newVal)
},
days(newVal) {
this.checkValue('day', this.day, newVal)
},
hours(newVal) {
this.checkValue('hour', this.hour, newVal)
},
minutes(newVal) {
this.checkValue('minute', this.minute, newVal)
},
seconds(newVal) {
this.checkValue('second', this.second, newVal)
}
},
computed: {
//
years() {
return this.getCurrentRange('year')
},
months() {
return this.getCurrentRange('month')
},
days() {
return this.getCurrentRange('day')
},
hours() {
return this.getCurrentRange('hour')
},
minutes() {
return this.getCurrentRange('minute')
},
seconds() {
return this.getCurrentRange('second')
},
// picker
ymd() {
return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay]
},
hms() {
return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond]
},
// date start
currentDateIsStart() {
return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay
},
// date end
currentDateIsEnd() {
return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay
},
//
minYear() {
return this.startYear
},
maxYear() {
return this.endYear
},
minMonth() {
if (this.year === this.startYear) {
return this.startMonth
} else {
return 1
}
},
maxMonth() {
if (this.year === this.endYear) {
return this.endMonth
} else {
return 12
}
},
minDay() {
if (this.year === this.startYear && this.month === this.startMonth) {
return this.startDay
} else {
return 1
}
},
maxDay() {
if (this.year === this.endYear && this.month === this.endMonth) {
return this.endDay
} else {
return this.daysInMonth(this.year, this.month)
}
},
minHour() {
if (this.type === 'datetime') {
if (this.currentDateIsStart) {
return this.startHour
} else {
return 0
}
}
if (this.type === 'time') {
return this.startHour
}
},
maxHour() {
if (this.type === 'datetime') {
if (this.currentDateIsEnd) {
return this.endHour
} else {
return 23
}
}
if (this.type === 'time') {
return this.endHour
}
},
minMinute() {
if (this.type === 'datetime') {
if (this.currentDateIsStart && this.hour === this.startHour) {
return this.startMinute
} else {
return 0
}
}
if (this.type === 'time') {
if (this.hour === this.startHour) {
return this.startMinute
} else {
return 0
}
}
},
maxMinute() {
if (this.type === 'datetime') {
if (this.currentDateIsEnd && this.hour === this.endHour) {
return this.endMinute
} else {
return 59
}
}
if (this.type === 'time') {
if (this.hour === this.endHour) {
return this.endMinute
} else {
return 59
}
}
},
minSecond() {
if (this.type === 'datetime') {
if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {
return this.startSecond
} else {
return 0
}
}
if (this.type === 'time') {
if (this.hour === this.startHour && this.minute === this.startMinute) {
return this.startSecond
} else {
return 0
}
}
},
maxSecond() {
if (this.type === 'datetime') {
if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {
return this.endSecond
} else {
return 59
}
}
if (this.type === 'time') {
if (this.hour === this.endHour && this.minute === this.endMinute) {
return this.endSecond
} else {
return 59
}
}
},
/**
* for i18n
*/
selectTimeText() {
return t("uni-datetime-picker.selectTime")
},
okText() {
return t("uni-datetime-picker.ok")
},
clearText() {
return t("uni-datetime-picker.clear")
},
cancelText() {
return t("uni-datetime-picker.cancel")
}
},
mounted() {
// #ifdef APP-NVUE
const res = uni.getSystemInfoSync();
this.fixNvueBug = {
top: res.windowHeight / 2,
left: res.windowWidth / 2
}
// #endif
},
methods: {
/**
* @param {Object} item
* 小于 10 在前面加个 0
*/
lessThanTen(item) {
return item < 10 ? '0' + item : item
},
/**
* 解析时分秒字符串例如00:00:00
* @param {String} timeString
*/
parseTimeType(timeString) {
if (timeString) {
let timeArr = timeString.split(':')
this.hour = Number(timeArr[0])
this.minute = Number(timeArr[1])
this.second = Number(timeArr[2])
}
},
/**
* 解析选择器初始值类型可以是字符串时间戳例如2000-10-02'08:30:00' 1610695109000
* @param {String | Number} datetime
*/
initPickerValue(datetime) {
let defaultValue = null
if (datetime) {
defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end)
} else {
defaultValue = Date.now()
defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end)
}
this.parseValue(defaultValue)
},
/**
* 初始值规则
* - 用户设置初始值 value
* - 设置了起始时间 start终止时间 end start < value < end初始值为 value 否则初始值为 start
* - 只设置了起始时间 start start < value初始值为 value否则初始值为 start
* - 只设置了终止时间 end value < end初始值为 value否则初始值为 end
* - 无起始终止时间则初始值为 value
* - 无初始值 value则初始值为当前本地时间 Date.now()
* @param {Object} value
* @param {Object} dateBase
*/
compareValueWithStartAndEnd(value, start, end) {
let winner = null
value = this.superTimeStamp(value)
start = this.superTimeStamp(start)
end = this.superTimeStamp(end)
if (start && end) {
if (value < start) {
winner = new Date(start)
} else if (value > end) {
winner = new Date(end)
} else {
winner = new Date(value)
}
} else if (start && !end) {
winner = start <= value ? new Date(value) : new Date(start)
} else if (!start && end) {
winner = value <= end ? new Date(value) : new Date(end)
} else {
winner = new Date(value)
}
return winner
},
/**
* 转换为可比较的时间戳接受日期时分秒时间戳
* @param {Object} value
*/
superTimeStamp(value) {
let dateBase = ''
if (this.type === 'time' && value && typeof value === 'string') {
const now = new Date()
const year = now.getFullYear()
const month = now.getMonth() + 1
const day = now.getDate()
dateBase = year + '/' + month + '/' + day + ' '
}
if (Number(value) && typeof value !== NaN) {
value = parseInt(value)
dateBase = 0
}
return this.createTimeStamp(dateBase + value)
},
/**
* 解析默认值 value字符串时间戳
* @param {Object} defaultTime
*/
parseValue(value) {
if (!value) {
return
}
if (this.type === 'time' && typeof value === "string") {
this.parseTimeType(value)
} else {
let defaultDate = null
defaultDate = new Date(value)
if (this.type !== 'time') {
this.year = defaultDate.getFullYear()
this.month = defaultDate.getMonth() + 1
this.day = defaultDate.getDate()
}
if (this.type !== 'date') {
this.hour = defaultDate.getHours()
this.minute = defaultDate.getMinutes()
this.second = defaultDate.getSeconds()
}
}
if (this.hideSecond) {
this.second = 0
}
},
/**
* 解析可选择时间范围 startend年月日字符串时间戳
* @param {Object} defaultTime
*/
parseDatetimeRange(point, pointType) {
//
if (!point) {
if (pointType === 'start') {
this.startYear = 1920
this.startMonth = 1
this.startDay = 1
this.startHour = 0
this.startMinute = 0
this.startSecond = 0
}
if (pointType === 'end') {
this.endYear = 2120
this.endMonth = 12
this.endDay = 31
this.endHour = 23
this.endMinute = 59
this.endSecond = 59
}
return
}
if (this.type === 'time') {
const pointArr = point.split(':')
this[pointType + 'Hour'] = Number(pointArr[0])
this[pointType + 'Minute'] = Number(pointArr[1])
this[pointType + 'Second'] = Number(pointArr[2])
} else {
if (!point) {
pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60
return
}
if (Number(point) && Number(point) !== NaN) {
point = parseInt(point)
}
// datetime end ,
const hasTime = /[0-9]:[0-9]/
if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test(
point)) {
point = point + ' 23:59:59'
}
const pointDate = new Date(point)
this[pointType + 'Year'] = pointDate.getFullYear()
this[pointType + 'Month'] = pointDate.getMonth() + 1
this[pointType + 'Day'] = pointDate.getDate()
if (this.type === 'datetime') {
this[pointType + 'Hour'] = pointDate.getHours()
this[pointType + 'Minute'] = pointDate.getMinutes()
this[pointType + 'Second'] = pointDate.getSeconds()
}
}
},
//
getCurrentRange(value) {
const range = []
for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) {
range.push(i)
}
return range
},
//
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
},
//
checkValue(name, value, values) {
if (values.indexOf(value) === -1) {
this[name] = values[0]
}
},
//
daysInMonth(year, month) { // Use 1 for January, 2 for February, etc.
return new Date(year, month, 0).getDate();
},
// iOSsafari
fixIosDateFormat(value) {
if (typeof value === 'string') {
value = value.replace(/-/g, '/')
}
return value
},
/**
* 生成时间戳
* @param {Object} time
*/
createTimeStamp(time) {
if (!time) return
if (typeof time === "number") {
return time
} else {
time = time.replace(/-/g, '/')
if (this.type === 'date') {
time = time + ' ' + '00:00:00'
}
return Date.parse(time)
}
},
/**
* 生成日期或时间的字符串
*/
createDomSting() {
const yymmdd = this.year +
'-' +
this.lessThanTen(this.month) +
'-' +
this.lessThanTen(this.day)
let hhmmss = this.lessThanTen(this.hour) +
':' +
this.lessThanTen(this.minute)
if (!this.hideSecond) {
hhmmss = hhmmss + ':' + this.lessThanTen(this.second)
}
if (this.type === 'date') {
return yymmdd
} else if (this.type === 'time') {
return hhmmss
} else {
return yymmdd + ' ' + hhmmss
}
},
/**
* 初始化返回值并抛出 change 事件
*/
initTime(emit = true) {
this.time = this.createDomSting()
if (!emit) return
if (this.returnType === 'timestamp' && this.type !== 'time') {
this.$emit('change', this.createTimeStamp(this.time))
this.$emit('input', this.createTimeStamp(this.time))
this.$emit('update:modelValue', this.createTimeStamp(this.time))
} else {
this.$emit('change', this.time)
this.$emit('input', this.time)
this.$emit('update:modelValue', this.time)
}
},
/**
* 用户选择日期或时间更新 data
* @param {Object} e
*/
bindDateChange(e) {
const val = e.detail.value
this.year = this.years[val[0]]
this.month = this.months[val[1]]
this.day = this.days[val[2]]
},
bindTimeChange(e) {
const val = e.detail.value
this.hour = this.hours[val[0]]
this.minute = this.minutes[val[1]]
this.second = this.seconds[val[2]]
},
/**
* 初始化弹出层
*/
initTimePicker() {
if (this.disabled) return
const value = this.fixIosDateFormat(this.value)
this.initPickerValue(value)
this.visible = !this.visible
},
/**
* 触发或关闭弹框
*/
tiggerTimePicker(e) {
this.visible = !this.visible
},
/**
* 用户点击清空按钮清空当前值
*/
clearTime() {
this.time = ''
this.$emit('change', this.time)
this.$emit('input', this.time)
this.$emit('update:modelValue', this.time)
this.tiggerTimePicker()
},
/**
* 用户点击确定按钮
*/
setTime() {
this.initTime()
this.tiggerTimePicker()
}
}
}
</script>
<style>
.uni-datetime-picker {
/* #ifndef APP-NVUE */
/* width: 100%; */
/* #endif */
}
.uni-datetime-picker-view {
height: 130px;
width: 270px;
/* #ifndef APP-NVUE */
cursor: pointer;
/* #endif */
}
.uni-datetime-picker-item {
height: 50px;
line-height: 50px;
text-align: center;
font-size: 14px;
}
.uni-datetime-picker-btn {
margin-top: 60px;
/* #ifndef APP-NVUE */
display: flex;
cursor: pointer;
/* #endif */
flex-direction: row;
justify-content: space-between;
}
.uni-datetime-picker-btn-text {
font-size: 14px;
color: #007AFF;
}
.uni-datetime-picker-btn-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-datetime-picker-cancel {
margin-right: 30px;
}
.uni-datetime-picker-mask {
position: fixed;
bottom: 0px;
top: 0px;
left: 0px;
right: 0px;
background-color: rgba(0, 0, 0, 0.4);
transition-duration: 0.3s;
z-index: 998;
}
.uni-datetime-picker-popup {
border-radius: 8px;
padding: 30px;
width: 270px;
/* #ifdef APP-NVUE */
height: 500px;
/* #endif */
/* #ifdef APP-NVUE */
width: 330px;
/* #endif */
background-color: #fff;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition-duration: 0.3s;
z-index: 999;
}
.fix-nvue-height {
/* #ifdef APP-NVUE */
height: 330px;
/* #endif */
}
.uni-datetime-picker-time {
color: grey;
}
.uni-datetime-picker-column {
height: 50px;
}
.uni-datetime-picker-timebox {
border: 1px solid #E5E5E5;
border-radius: 5px;
padding: 7px 10px;
/* #ifndef APP-NVUE */
box-sizing: border-box;
cursor: pointer;
/* #endif */
}
.uni-datetime-picker-timebox-pointer {
/* #ifndef APP-NVUE */
cursor: pointer;
/* #endif */
}
.uni-datetime-picker-disabled {
opacity: 0.4;
/* #ifdef H5 */
cursor: not-allowed !important;
/* #endif */
}
.uni-datetime-picker-text {
font-size: 14px;
}
.uni-datetime-picker-sign {
position: absolute;
top: 53px;
/* 减掉 10px 的元素高度,兼容nvue */
color: #999;
/* #ifdef APP-NVUE */
font-size: 16px;
/* #endif */
}
.sign-left {
left: 86px;
}
.sign-right {
right: 86px;
}
.sign-center {
left: 135px;
}
.uni-datetime-picker__container-box {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-top: 40px;
}
.time-hide-second {
width: 180px;
}
</style>

410
components/qn-datetime-picker/util.js

@ -0,0 +1,410 @@
class Calendar {
constructor({
date,
selected,
startDate,
endDate,
range,
// multipleStatus
} = {}) {
// 当前日期
this.date = this.getDate(new Date()) // 当前初入日期
// 打点信息
this.selected = selected || [];
// 范围开始
this.startDate = startDate
// 范围结束
this.endDate = endDate
this.range = range
// 多选状态
this.cleanMultipleStatus()
// 每周日期
this.weeks = {}
// this._getWeek(this.date.fullDate)
// this.multipleStatus = multipleStatus
this.lastHover = false
}
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.selectDate = this.getDate(date)
this._getWeek(this.selectDate.fullDate)
}
/**
* 清理多选状态
*/
cleanMultipleStatus() {
this.multipleStatus = {
before: '',
after: '',
data: []
}
}
/**
* 重置开始日期
*/
resetSatrtDate(startDate) {
// 范围开始
this.startDate = startDate
}
/**
* 重置结束日期
*/
resetEndDate(endDate) {
// 范围结束
this.endDate = endDate
}
/**
* 获取任意时间
*/
getDate(date, AddDayCount = 0, str = 'day') {
if (!date) {
date = new Date()
}
if (typeof date !== 'object') {
date = date.replace(/-/g, '/')
}
const dd = new Date(date)
switch (str) {
case 'day':
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
break
case 'month':
if (dd.getDate() === 31) {
dd.setDate(dd.getDate() + AddDayCount)
} else {
dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
}
break
case 'year':
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
break
}
const y = dd.getFullYear()
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
return {
fullDate: y + '-' + m + '-' + d,
year: y,
month: m,
date: d,
day: dd.getDay()
}
}
/**
* 获取上月剩余天数
*/
_getLastMonthDays(firstDay, full) {
let dateArr = []
for (let i = firstDay; i > 0; i--) {
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
dateArr.push({
date: beforeDate,
month: full.month - 1,
disable: true
})
}
return dateArr
}
/**
* 获取本月天数
*/
_currentMonthDys(dateData, full) {
let dateArr = []
let fullDate = this.date.fullDate
for (let i = 1; i <= dateData; i++) {
let isinfo = false
let nowDate = full.year + '-' + (full.month < 10 ?
full.month : full.month) + '-' + (i < 10 ?
'0' + i : i)
// 是否今天
let isDay = fullDate === nowDate
// 获取打点信息
let info = this.selected && this.selected.find((item) => {
if (this.dateEqual(nowDate, item.date)) {
return item
}
})
// 日期禁用
let disableBefore = true
let disableAfter = true
if (this.startDate) {
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
disableBefore = this.dateCompare(this.startDate, nowDate)
}
if (this.endDate) {
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
disableAfter = this.dateCompare(nowDate, this.endDate)
}
let multiples = this.multipleStatus.data
let checked = false
let multiplesStatus = -1
if (this.range) {
if (multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, nowDate)
})
}
if (multiplesStatus !== -1) {
checked = true
}
}
let data = {
fullDate: nowDate,
year: full.year,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.isLogicBefore(nowDate, this.multipleStatus.before, this.multipleStatus.after),
afterMultiple: this.isLogicAfter(nowDate, this.multipleStatus.before, this.multipleStatus.after),
month: full.month,
disable: !(disableBefore && disableAfter),
isDay,
userChecked: false
}
if (info) {
data.extraInfo = info
}
dateArr.push(data)
}
return dateArr
}
/**
* 获取下月天数
*/
_getNextMonthDays(surplus, full) {
let dateArr = []
for (let i = 1; i < surplus + 1; i++) {
dateArr.push({
date: i,
month: Number(full.month) + 1,
disable: true
})
}
return dateArr
}
/**
* 获取当前日期详情
* @param {Object} date
*/
getInfo(date) {
if (!date) {
date = new Date()
}
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
return dateInfo
}
/**
* 比较时间大小
*/
dateCompare(startDate, endDate) {
// 计算截止时间
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
if (startDate <= endDate) {
return true
} else {
return false
}
}
/**
* 比较时间是否相等
*/
dateEqual(before, after) {
// 计算截止时间
before = new Date(before.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
after = new Date(after.replace('-', '/').replace('-', '/'))
if (before.getTime() - after.getTime() === 0) {
return true
} else {
return false
}
}
/**
* 比较真实起始日期
*/
isLogicBefore(currentDay, before, after) {
let logicBefore = before
if (before && after) {
logicBefore = this.dateCompare(before, after) ? before : after
}
return this.dateEqual(logicBefore, currentDay)
}
isLogicAfter(currentDay, before, after) {
let logicAfter = after
if (before && after) {
logicAfter = this.dateCompare(before, after) ? after : before
}
return this.dateEqual(logicAfter, currentDay)
}
/**
* 获取日期范围内所有日期
* @param {Object} begin
* @param {Object} end
*/
geDateAll(begin, end) {
var arr = []
var ab = begin.split('-')
var ae = end.split('-')
var db = new Date()
db.setFullYear(ab[0], ab[1] - 1, ab[2])
var de = new Date()
de.setFullYear(ae[0], ae[1] - 1, ae[2])
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
for (var k = unixDb; k <= unixDe;) {
k = k + 24 * 60 * 60 * 1000
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
}
return arr
}
/**
* 获取多选状态
*/
setMultiple(fullDate) {
let {
before,
after
} = this.multipleStatus
if (!this.range) return
if (before && after) {
if (!this.lastHover) {
this.lastHover = true
return
}
this.multipleStatus.before = fullDate
this.multipleStatus.after = ''
this.multipleStatus.data = []
this.multipleStatus.fulldate = ''
this.lastHover = false
} else {
if (!before) {
this.multipleStatus.before = fullDate
this.lastHover = false
} else {
this.multipleStatus.after = fullDate
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus
.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus
.before);
}
this.lastHover = true
}
}
this._getWeek(fullDate)
}
/**
* 鼠标 hover 更新多选状态
*/
setHoverMultiple(fullDate) {
let {
before,
after
} = this.multipleStatus
if (!this.range) return
if (this.lastHover) return
if (!before) {
this.multipleStatus.before = fullDate
} else {
this.multipleStatus.after = fullDate
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
}
}
this._getWeek(fullDate)
}
/**
* 更新默认值多选状态
*/
setDefaultMultiple(before, after) {
this.multipleStatus.before = before
this.multipleStatus.after = after
if (before && after) {
if (this.dateCompare(before, after)) {
this.multipleStatus.data = this.geDateAll(before, after);
this._getWeek(after)
} else {
this.multipleStatus.data = this.geDateAll(after, before);
this._getWeek(before)
}
}
}
/**
* 获取每周数据
* @param {Object} dateData
*/
_getWeek(dateData) {
const {
fullDate,
year,
month,
date,
day
} = this.getDate(dateData)
let firstDay = new Date(year, month - 1, 1).getDay()
let currentDay = new Date(year, month, 0).getDate()
let dates = {
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
nextMonthDays: [], // 下个月开始几天
weeks: []
}
let canlender = []
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
let weeks = {}
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
for (let i = 0; i < canlender.length; i++) {
if (i % 7 === 0) {
weeks[parseInt(i / 7)] = new Array(7)
}
weeks[parseInt(i / 7)][i % 7] = canlender[i]
}
this.canlender = canlender
this.weeks = weeks
}
//静态方法
// static init(date) {
// if (!this.instance) {
// this.instance = new Calendar(date);
// }
// return this.instance;
// }
}
export default Calendar

514
components/qn-easyinput/qn-easyinput.vue

@ -0,0 +1,514 @@
<template>
<view class="uni-easyinput" :class="{ 'uni-easyinput-error': msg }" :style="{ color: inputBorder && msg ? '#e43d33' : styles.color }">
<view
class="uni-easyinput__content"
:class="{ 'is-input-border': inputBorder, 'is-input-error-border': inputBorder && msg, 'is-textarea': type === 'textarea', 'is-disabled': disabled }"
:style="{
'border-color': inputBorder && msg ? '#dd524d' : styles.borderColor,
'background-color': disabled ? styles.disableColor : ''
}"
>
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')"></uni-icons>
<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"
@input="onInput"
@blur="onBlur"
@focus="onFocus"
@confirm="onConfirm"
></textarea>
<input
v-else
:type="type === 'password' ? 'text' : type"
class="uni-easyinput__content-input"
:style="{
'padding-right': type === 'password' || clearable || prefixIcon ? '' : '10px',
'padding-left': prefixIcon ? '' : '10px',
'text-align': text
}"
: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"
@focus="onFocus"
@blur="onBlur"
@input="onInput"
@confirm="onConfirm"
/>
<template v-if="type === 'password' && passwordIcon">
<uni-icons
v-if="val != ''"
class="content-clear-icon"
:class="{ 'is-textarea-icon': type === 'textarea' }"
:type="showPassword ? 'eye-slash-filled' : 'eye-filled'"
:size="18"
color="#c0c4cc"
@click="onEyes"
></uni-icons>
</template>
<template v-else-if="suffixIcon">
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')"></uni-icons>
</template>
<template v-else>
<uni-icons
class="content-clear-icon"
:class="{ 'is-textarea-icon': type === 'textarea' }"
type="clear"
:size="clearSize"
v-if="clearable && val && !disabled"
color="#c0c4cc"
@click="onClear"
></uni-icons>
</template>
<slot name="right"></slot>
</view>
</view>
</template>
<script>
// import {
// debounce,
// throttle
// } from './common.js'
/**
* 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 {Boolean} trim 是否自动去除两端的空格
* @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>
*/
export default {
name: 'uni-easyinput',
emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm'],
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
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: String,
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: 15
},
inputBorder: {
type: Boolean,
default: true
},
prefixIcon: {
type: String,
default: ''
},
suffixIcon: {
type: String,
default: ''
},
trim: {
type: [Boolean, String],
default: true
},
passwordIcon: {
type: Boolean,
default: true
},
styles: {
type: Object,
default() {
return {
color: '#333',
disableColor: '#F7F6F6',
borderColor: '#e5e5e5'
}
}
},
errorMessage: {
type: [String, Boolean],
default: ''
},
// left|center|right
text: {
type: String,
default: 'left'
}
},
data() {
return {
focused: false,
errMsg: '',
val: '',
showMsg: '',
border: false,
isFirstBorder: false,
showClearIcon: false,
showPassword: false
}
},
computed: {
msg() {
return this.errorMessage || this.errMsg
},
// uniappinputmaxlength
inputMaxlength() {
return Number(this.maxlength)
}
},
watch: {
value(newVal) {
if (this.errMsg) this.errMsg = ''
this.val = newVal
// fix by mehaotian is_reset uni-forms
if (this.form && this.formItem && !this.is_reset) {
this.is_reset = false
this.formItem.setValue(newVal)
}
},
modelValue(newVal) {
if (this.errMsg) this.errMsg = ''
this.val = newVal
if (this.form && this.formItem && !this.is_reset) {
this.is_reset = false
this.formItem.setValue(newVal)
}
},
focus(newVal) {
this.$nextTick(() => {
this.focused = this.focus
})
}
},
created() {
if (!this.value) {
this.val = this.modelValue
}
if (!this.modelValue) {
this.val = this.value
}
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.form && this.formItem) {
if (this.formItem.name) {
if (!this.is_reset) {
this.is_reset = false
this.formItem.setValue(this.val)
}
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
},
mounted() {
this.$nextTick(() => {
this.focused = this.focus
})
},
methods: {
/**
* 初始化变量值
*/
init() {},
onClickIcon(type) {
this.$emit('iconClick', type)
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent
let parentName = parent.$options.name
while (parentName !== name) {
parent = parent.$parent
if (!parent) return false
parentName = parent.$options.name
}
return parent
},
onEyes() {
this.showPassword = !this.showPassword
},
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)
},
onFocus(event) {
this.$emit('focus', event)
},
onBlur(event) {
let value = event.detail.value
this.$emit('blur', event)
},
onConfirm(e) {
this.$emit('confirm', e.detail.value)
},
onClear(event) {
this.val = ''
// TODO vue2
this.$emit('input', '')
// TODO vue2
// TODO  vue3
this.$emit('update:modelValue', '')
},
fieldClick() {
this.$emit('click')
},
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" scoped>
$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;
}
.uni-easyinput__content-input {
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
position: relative;
overflow: hidden;
flex: 1;
line-height: 1;
font-size: 14px;
}
.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;
padding-top: 6px;
padding-bottom: 10px;
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;
}
.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 {
border-color: red;
background-color: #f7f6f6;
color: #d5d5d5;
.uni-easyinput__placeholder-class {
color: #d5d5d5;
font-size: 12px;
}
}
</style>

74
components/qn-footer/qn-footer.vue

@ -0,0 +1,74 @@
<template>
<view class="qn-footer">
<view
:class="{ 'qn-footer--fixed': fixed, 'qn-footer--shadow': shadow, 'qn-footer--border': border }"
class="qn-footer__content"
:style="{ height: height }"
>
<slot />
</view>
<view class="qn-footer__placeholder" v-if="fixed">
<view class="qn-footer__placeholder-view" :style="{ height: height }" />
</view>
</view>
</template>
<script>
export default {
props: {
fixed: {
type: Boolean,
default: false
},
height: {
type: String,
default: '120rpx'
},
shadow: {
type: Boolean,
default: false
},
border: {
type: Boolean,
default: true
}
}
}
</script>
<style lang="scss" scoped>
.qn-footer__content {
position: relative;
background-color: #fff;
overflow: hidden;
padding: 16rpx 0;
margin-top: 24rpx;
}
.qn-footer--fixed {
position: fixed;
z-index: 10;
bottom: 0;
/* #ifdef H5 */
left: var(--window-left);
right: var(--window-right);
/* #endif */
/* #ifndef H5 */
left: 0;
right: 0;
/* #endif */
}
.qn-footer--shadow {
/* #ifndef APP-NVUE */
box-shadow: 0 -1px 6px #ccc;
/* #endif */
}
.qn-footer--border {
border-top-width: 1rpx;
border-top-style: solid;
border-top-color: #eee;
}
.qn-footer__placeholder-view {
margin-top: 24rpx;
}
</style>

87
components/qn-form-item/qn-form-item.vue

@ -0,0 +1,87 @@
<template>
<view>
<view v-if="type === 'title'" class="qn-form-item qn-form-item__title">
<text class="title">{{ label }}</text>
</view>
<view v-if="type === 'item'" class="qn-form-item qn-form-item" :style="{ flexWrap: size === 'large' ? 'wrap' : 'nowrap' }">
<view class="label" :style="{ marginTop: size === 'large' ? '18rpx' : '' }">
<uni-icons v-if="required" custom-prefix="iconfont" type="icon-required" size="14" color="#F5222D"></uni-icons>
<text class="label__text">{{ label }}</text>
</view>
<view class="value" :style="{ marginTop: size === 'large' ? '10rpx' : '' }">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
/**
* @value {string} type 表单类型 title:标题 item:表单项
* @value label 表单项名称
* @value size 表单项大小 默认normal ,可选值:normal,large
* @value required 是否必填 默认false
*
*/
export default {
props: {
type: {
type: String,
default: 'item'
},
label: {
type: String,
default: ''
},
size: {
type: String,
default: 'normal'
},
required: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="scss" scoped>
.qn-form-item {
width: 750rpx;
padding: 0rpx 32rpx;
background-color: #fff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: 2rpx solid #d8d8d8;
min-height: 80rpx;
.label {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
margin-right: 20rpx;
.label__text {
font-size: 28rpx;
color: #000000;
}
}
.value {
flex-grow: 1;
flex-shrink: 1;
text-align: right;
}
}
.qn-form-item__title {
background-color: #f7f8fa;
padding: 20rpx 32rpx;
border: none;
.title {
font-size: 30rpx;
color: #888888;
}
}
</style>

43
components/qn-header/qn-header.vue

@ -0,0 +1,43 @@
<template>
<view
class="header"
:style="{
height: `${statusBarHeight + 44}px`,
paddingTop: `${statusBarHeight}px`
}"
>
<slot name="icon">
<uni-icons type="back" size="24" @click="back" />
</slot>
<slot></slot>
</view>
</template>
<script>
import { back } from '@/utils/hook.js'
export default {
data() {
return {
statusBarHeight: 20
}
},
mounted() {
this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight - 0
},
methods: {
back
}
}
</script>
<style lang="scss">
.header {
width: 750rpx;
padding: 0 32rpx 10rpx 20rpx;
background-color: #fff;
display: flex;
flex-direction: row;
align-items: center;
border-bottom: 2rpx solid #d8d8d8;
}
</style>

5
components/scroll-list/images.js

@ -0,0 +1,5 @@
export default {
empty: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHoAAABoCAYAAAA+R+R0AAAG50lEQVR4Xu2df1PURhjHn71fOQ8ZsKIoWioWsHcOLU49O53OqbwDfQm+AugraN+B+grad1D7CkDPzrSeHbAM54+2YLHiyGAL43DclSTb2UgwQC7Zu01ySfbJPzdym2ef5/vJdzeX7KwE8JBCASJFlVgkIGhJLgIEjaAlUUCSMtHRCFoSBSQpEx2NoCVRQJIy0dEBgi5XqtOlYmEiwC53u0LQAalersxfBUhOA2gTpeLYTEDdIuighUbQQSsecH/vAJvHO0eb/wrS2Th0+wiezckAYAF9oLOZoOZsBO0jaBYaHe2zwGELj3N02Ij4lA+Ctgg7PTvbO3Hhwrr56ZPmHQuLv6MBgMFNqcocAPkOgN4oFQtnOkYkhh2H6masXFn4FoB8Qym5cflS/vsY6t2xkkIDmjk6qSrPgZIpAvpU6dL58Y6pEsOOQwM6htqGqiQEHSoc/iWDoP3TNlSREXSocPiXDIL2T9tQRUbQocLhXzII2j9tQxUZQYcKh3/JIGj/tA1VZGlAsydvaS1zk1JiPEMnhD7fTv73NXuJEioiPiUjBeifHiyM64TMJBKkK3com2Ja1rbqqq7TzQSlV7+6dH7OJ31DEzZ2oO89rF4jFCatChOAz0kikTt3diB5KKsYX23VG/B0cUWjul6jAL9a21MCty9fLNwJDSUPEokV6B3IP6TTSVXJZAznskNJp+B4Xw+YkM2/M9iraxvQ2FZ3pdyqN3RN0xOUwPU4wY4U6PKD6iQQuOZwgY9n0qme/PCHJJlMtO2Dueoi6Dplc3fTIZ0CzGkquT3xZf552x0FeGJkQN9/+Pg3SulYJp1SD2Xfu9Wq1WatDtmsAqNDA0ISPltagXq9AV25rG2czVpDVzUtAUDXVTVxIQqwIwH6fqU6RQFuDg70weDAsaYQ55/+BVuNbch/fBpEHP3o8RL0dndBfvh0075WXv8Diy9eAxC4XbpYmBK6sgI42TPQd39hq0MArnxx3vj08ihXqjNKJl0qfjrsOB6/WX8Lj//4GzKZtDEvt3PU6g3QNN2AfLS32zHEz7PPmLPLpWLBae12O2l4fo5noHcWq4MfC9IZ6J7u3JWxcx+5CsBgM7eJHAP9H7hCZvHZCLLxtnYXQYuobTm3FdAedckVRhrQbLhOJMgVO1V0nd71ahhH0FzXnWMjoaEbQePQLX4J4tDtqYZCjrZmEpabMU/VcQkmzRxt1cHvn1e8d90I2l4Bzxztp8B4MyauLoIW0FDKoVtAL9dTvXJ0vbENvy+9goH+I3D0iPNTL9ekZH1gwiNMu228Ar38cg2WV9bAeGZ+qq/ddHbPQ0cLS7g3AIIWF1SqOboVR2+8rUFPd85RYXS0+AW4JwKPo2cXluBwLgtDg8chlUzaZsADWtU0mH/yAti77fzwKce5HEEHDJrBqTz603i9yBYLjA6dsF008OZf9hrzJRztPQz5kYPvmhlcBpnFUzJpGPtkELJKumk1CDpg0Kw7BunZ0ivYrDUMR48MnTjgRjYczz9ZNu66zw7278ny9doGLC2vGpC7cooBudnIYJ6IoDsAmnXJIC0urxoL/tgxMnQS+vt69mTD2uwHaDqdNWSLCEeHTnJVgKC5ZOJvxDNHW6MZy3yWV22da9er6XS7C8MpSwTNz5CrZaugWVD2cMRpfuXq2KURgvZCRUuMdkB7nIJtOATtscoIWlxQqR6YiMu1NwI62mNF0dHigqKjBTQUcbSfCzXsSkLQHQLt59IrBC0A1e5UEUcjaBtF4zJHB7U8Gh3dYUcjaBcAcXG0tUwcumM8dCNoCR2NP68kcbTHtwuu4QL7HX2vUr1DgNwCoJOlYuG6a2b4UqMViVzbBgba3J6CAvx4uVhw2nDmQNLsZiyrpEsXx5x3PHCt1uMGUu544KZh+cHCHBCyTgHGtVTjTCs79r3fw+SYsSY7DIe0e5i4ic9272M79Jmfbu33f79zoXymZFJqVrHflajVmO22N3clogAbmkrGcVeidpVsct6Os1sa9j1O4X04AnPqNrkVBcgs6cDmaN8Ex8BcCiBoLpmi3whBR58hVwUImkum6DdC0NFnyFWBEOi9/9s5V39SNCoVx2ashVp12v+d2Y5Hy2bn8ojaNuid12yh3wOTRwQf2syYW2Ue1EmbOHghVKcBgEfL3bit5tw2aNYRz1XYakJxaB8rR8cBiEw1CDlaJqGiXiuCjjpBzvwRNKdQUW+GoKNOkDP/UIJ2WhbL6tq/F7i5opKzZk+bebkvuaeJ7QuGoAXVRdCCAuLp3ioQSkd7WyJGYwogaEmuAwSNoCVRQJIy0dEIWhIFJCkTHY2gJVFAkjLR0QhaEgUkKRMdjaAlUUCSMtHRkoD+H39JyZZ6zcs+AAAAAElFTkSuQmCC',
success: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC4wMSIvPjxwYXRoIGQ9Ik0xMCAyNEwyMCAzNEw0MCAxNCIgc3Ryb2tlPSIjMzAzMTMzIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==',
error: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgdmlld0JveD0iMCAwIDQ4IDQ4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC4wMSIvPjxwYXRoIGQ9Ik0xNCAxNEwzNCAzNCIgc3Ryb2tlPSIjMzAzMTMzIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xNCAzNEwzNCAxNCIgc3Ryb2tlPSIjMzAzMTMzIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg=='
}

668
components/scroll-list/scroll-list.vue

@ -0,0 +1,668 @@
<template>
<view class="scroll-list-wrap" :style="[scrollListWrapStyle]">
<scroll-view
class="scroll-view"
:class="[elClass]"
:style="[listWrapStyle]"
scroll-y
scroll-anchoring
enable-back-to-top
:scroll-top="scrollTop"
:lower-threshold="defaultOption.lowerThreshold"
@scroll="handleScroll"
@touchend="handleTouchEnd"
@touchmove.prevent.stop="handleTouchMove"
@touchstart="handleTouchStart"
@scrolltolower="handleScrolltolower"
>
<view class="scroll-content" :style="[scrollContentStyle]">
<view class="pull-down-wrap">
<slot name="pulldown" v-if="$slots.pulldown"></slot>
<view class="refresh-view" :style="[refreshViewStyle]" v-else>
<view class="pull-down-animation" :class="{ refreshing: refreshing }" :style="[pullDownAnimationStyle]"></view>
<text class="pull-down-text" :style="[pullDownTextStyle]">{{ refreshStateText }}</text>
</view>
</view>
<view class="empty-wrap" v-if="showEmpty">
<slot name="empty" v-if="$slots.empty"></slot>
<view class="empty-view" v-else>
<image class="empty-image" :src="defaultOption.emptyImage || images.empty" mode="aspectFit"></image>
<text class="empty-text" :style="[emptyTextStyle]">{{ emptyText }}</text>
</view>
</view>
<view class="list-content"><slot></slot></view>
<view class="pull-up-wrap" v-if="showPullUp">
<slot name="pullup" v-if="$slots.pullup"></slot>
<view class="load-view" v-else>
<view class="pull-up-animation" v-if="loading" :style="[pullUpAnimationStyle]"></view>
<text class="pull-up-text" :style="[pullUpTextStyle]">{{ loadStateText }}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import images from './images.js'
export default {
name: 'scroll-list',
props: {
//
option: {
type: Object,
default: () => ({})
}
},
data() {
return {
defaultOption: {
page: 1, //
size: 15, //
auto: true, //
height: null, //
disabled: false, //
background: '', //
emptyImage: '', //
offsetBottom: 0, //
pullDownSpeed: 0.5, //
lowerThreshold: 40, //
refresherThreshold: 80, //
refreshDelayed: 800, //
refreshFinishDelayed: 800, //
safeArea: false, //
emptyTextColor: '#82848a', //
loadTextColor: '#82848a', //
loadIconColor: '#82848a', //
refresherTextColor: '#82848a', //
refresherIconColor: '#82848a', //
emptyText: '暂无列表~', //
loadingText: '正在加载中~', //
loadFailText: '加载失败啦~', //
noMoreText: '没有更多啦~', //
refreshingText: '正在刷新~', //
refreshFailText: '刷新失败~', //
refreshSuccessText: '刷新成功~', //
pulldownText: '下拉刷新~', //
pulldownFinishText: '松开刷新~' //
},
images, //
elClass: '', // class
windowInfo: {}, //
scrollTop: 0, //
scrollViewTop: -1, //
scrollViewHeight: 0, //
currentPage: 1, //
currentSize: 15, //
currentScrollTop: 0, //
emptyText: '暂无列表~',
loadStateText: '正在加载中~', //
refreshStateText: '下拉刷新~', //
loadDisabled: false, //
loading: false, //
refreshing: false, //
refreshFinish: false, //
pulldowning: false, //
pullDownHeight: 0, //
showEmpty: false, //
showPullUp: false, //
showPullDown: false //
}
},
methods: {
//
handleInit() {
//
this.defaultOption = Object.assign(this.defaultOption, this.option)
this.showEmpty = !this.defaultOption.auto
this.currentPage = this.defaultOption.page
this.currentSize = this.defaultOption.size
this.emptyText = this.defaultOption.emptyText
this.loadStateText = this.defaultOption.loadingText
this.refreshStateText = this.defaultOption.pulldownText
//
this.queryRect('.' + this.elClass).then((rect) => {
//
this.scrollViewTop = rect.top
//
if (this.defaultOption.auto) this.load()
})
},
//
load() {
if (this.defaultOption.disabled || this.loading || this.loadDisabled) return
//
this.loading = true
//
this.loadStateText = this.defaultOption.loadingText
//
this.showPullUp = true
//
let paging = { page: this.currentPage, size: this.currentSize }
// load
this.$emit('load', paging)
},
//
loadSuccess(data = {}) {
//
const { list, total } = data
//
if (Array.isArray(list)) {
//
if (list.length) {
//
if (list.length >= total) {
//
this.loadDisabled = true
//
this.loadStateText = this.defaultOption.noMoreText
} else {
//
this.loadDisabled = false
//
this.currentPage++
//
this.loadStateText = this.defaultOption.loadingText
//
this.loadCompute()
}
//
this.showPullUp = true
//
this.showEmpty = false
} else {
//
this.loadDisabled = true
//
this.showPullUp = false
//
this.showPullUp = false
//
this.showEmpty = true
}
//
this.loading = false
//
this.$emit('loadSuccess', list)
} else {
//
this.loadFail()
console.error('the list must be a array')
}
},
//
loadFail() {
//
this.loading = false
//
this.showEmpty = false
//
this.showPullUp = true
//
this.loadStateText = this.defaultOption.loadFailText
//
this.$emit('loadFail')
},
//
refresh() {
//
if (this.pullDownHeight == this.defaultOption.refresherThreshold) {
//
this.loading = false
//
this.showPullUp = false
} else {
//
this.loading = true
//
this.showEmpty = false
//
this.showPullUp = true
//
this.loadStateText = this.defaultOption.refreshingText
}
//
this.refreshFinish = false
//
this.refreshing = true
//
this.refreshStateText = this.defaultOption.refreshingText
//
this.currentPage = 1
this.currentSize = this.defaultOption.size
let paging = { page: this.currentPage, size: this.currentSize }
// refresh
setTimeout(() => {
this.$emit('refresh', paging)
}, this.defaultOption.refreshDelayed)
},
//
refreshSuccess(data) {
//
const { list, total } = data
//
if (Array.isArray(list)) {
//
if (list.length) {
//
if (list.length >= total) {
//
this.loadDisabled = true
//
this.loadStateText = this.defaultOption.noMoreText
} else {
//
this.currentPage++
//
this.loadDisabled = false
//
this.loadStateText = this.defaultOption.loadingText
//
this.defaultOption.auto = true
//
this.loadCompute()
}
//
this.showEmpty = false
//
this.showPullUp = true
} else {
//
this.loadDisabled = true
//
this.showPullUp = false
//
this.showEmpty = true
//
this.loadStateText = this.defaultOption.noMoreText
}
//
this.loading = false
//
this.refreshStateText = this.defaultOption.refreshSuccessText
//
this.refreshing = false
//
this.pulldowning = false
//
this.$emit('refreshSuccess', list)
setTimeout(() => {
//
this.refreshFinish = true
//
this.pullDownHeight = 0
//
this.showPullDown = false
this.$emit('refreshSuccess')
}, this.defaultOption.refreshFinishDelayed)
} else {
//
this.refreshFail()
console.error('the list must be a array')
}
},
//
refreshFail() {
//
this.loadStateText = this.defaultOption.refreshFailText
//
this.refreshStateText = this.defaultOption.refreshFailText
//
this.loading = false
//
this.showPullUp = true
//
this.refreshing = false
//
this.pulldowning = false
//
setTimeout(() => {
//
this.refreshFinish = true
//
this.pullDownHeight = 0
//
this.showPullDown = false
//
this.$emit('refreshError')
}, this.defaultOption.refreshFinishDelayed)
},
//
loadCompute() {
//
if (this.defaultOption.auto) {
//
setTimeout(() => {
this.$nextTick(() => {
this.queryRect('.list-content').then((rect) => {
if (rect.height <= this.scrollViewHeight) {
this.load()
}
})
})
}, 100)
}
},
//
handleScrolltolower(e) {
if (this.loadDisabled) return
this.$emit('scrolltolower', e)
this.load()
},
//
handleScroll(event) {
this.currentScrollTop = event.detail.scrollTop
this.$emit('scroll', event.detail)
},
//
handleTouchStart(event) {
if (this.defaultOption.disabled) return
this.currentTouchStartY = event.touches[0].clientY
this.$emit('touchStart', event)
},
//
handleTouchMove(event) {
if (this.defaultOption.disabled || this.currentScrollTop) return
if (event.touches[0].clientY >= this.currentTouchStartY) {
this.pulldowning = true
this.showPullDown = true
let pullDownDistance = (event.touches[0].clientY - this.currentTouchStartY) * this.defaultOption.pullDownSpeed
this.pullDownHeight = pullDownDistance > this.defaultOption.refresherThreshold ? this.defaultOption.refresherThreshold : pullDownDistance
this.refreshStateText =
this.pullDownHeight >= this.defaultOption.refresherThreshold ? this.defaultOption.pulldownFinishText : this.defaultOption.pulldownText
this.$emit('touchMove', event)
}
},
//
handleTouchEnd(event) {
if (this.defaultOption.disabled) return
//
if (this.pullDownHeight < this.defaultOption.refresherThreshold) {
//
this.pulldowning = false
//
this.pullDownHeight = 0
//
this.showPullDown = false
//
this.$emit('refreshStop')
} else {
this.refresh()
}
//
this.$emit('touchEnd', event)
},
//
updateScrollView() {
if (this.defaultOption.height) {
this.scrollViewHeight = uni.upx2px(this.defaultOption.height)
} else {
this.scrollViewHeight = this.windowInfo.windowHeight - this.scrollViewTop
}
this.scrollViewObserve()
},
//
listContentObserve() {
this.disconnectObserve('_listContentObserve')
const listContentObserve = this.createIntersectionObserver({
thresholds: [0, 0.5, 1]
})
listContentObserve.relativeToViewport({
// #ifdef H5
top: -(this.windowInfo.windowTop + rect.top),
// #endif
// #ifndef H5
top: -rect.top
// #endif
})
},
//
scrollViewObserve() {
this.disconnectObserve('_scrollViewObserve')
this.$nextTick(() => {
this.queryRect('.' + this.elClass).then((rect) => {
const scrollViewObserve = this.createIntersectionObserver({
thresholds: [0, 0.5, 1]
})
scrollViewObserve.relativeToViewport({
// #ifdef H5
top: -(this.windowInfo.windowTop + rect.top),
// #endif
// #ifndef H5
top: -rect.top
// #endif
})
scrollViewObserve.observe('.' + this.elClass, (position) => {
// #ifdef H5
this.scrollViewTop = position.boundingClientRect.top - this.windowInfo.windowTop
// #endif
// #ifndef H5
this.scrollViewTop = position.boundingClientRect.top
// #endif
})
this._scrollViewObserve = scrollViewObserve
})
})
},
//
disconnectObserve(observerName) {
const observer = this[observerName]
observer && observer.disconnect()
},
// dom
queryRect(selector, all) {
return new Promise((resolve) => {
uni
.createSelectorQuery()
.in(this)
[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect) => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect)
}
if (!all && rect) {
resolve(rect)
}
})
.exec()
})
},
// 16RGB
hexToRgb(hex) {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hex = hex.replace(shorthandRegex, (m, r, g, b) => {
return r + r + g + g + b + b
})
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
: null
}
},
computed: {
scrollListWrapStyle() {
let style = {}
style.background = this.defaultOption.background
return style
},
//
listWrapStyle() {
let style = {}
const { offsetBottom } = this.defaultOption
style.height = this.scrollViewHeight - uni.upx2px(offsetBottom) + 'px'
if (this.defaultOption.safeArea) style.paddingBottom = 'env(safe-area-inset-bottom) !important'
return style
},
//
scrollContentStyle() {
const style = {}
const { pullDownHeight, pulldowning, showPullDown } = this
style.transform = showPullDown ? `translateY(${pullDownHeight}px)` : `translateY(0px)`
style.transition = pulldowning ? `transform 100ms ease-out` : `transform 200ms cubic-bezier(0.19,1.64,0.42,0.72)`
return style
},
//
refreshViewStyle() {
const style = {}
const { showPullDown } = this
style.opacity = showPullDown ? 1 : 0
return style
},
//
pullDownAnimationStyle() {
const style = {}
const { refresherIconColor, refresherThreshold } = this.defaultOption
const { refreshing, pullDownHeight } = this
const { r, g, b } = this.hexToRgb(refresherIconColor)
const rate = pullDownHeight / refresherThreshold
style.borderColor = `rgba(${r},${g},${b},0.2)`
style.borderTopColor = refresherIconColor
if (!refreshing) {
style.transform = `rotate(${360 * rate}deg)`
style.transition = 'transform 100ms linear'
}
return style
},
pullDownTextStyle() {
const style = {}
const { refresherTextColor } = this.defaultOption
style.color = refresherTextColor
return style
},
//
pullUpAnimationStyle() {
const style = {}
const { loadIconColor } = this.defaultOption
const { r, g, b } = this.hexToRgb(loadIconColor)
style.borderColor = `rgba(${r},${g},${b},0.2)`
style.borderTopColor = loadIconColor
return style
},
//
pullUpTextStyle() {
const style = {}
const { loadTextColor } = this.defaultOption
style.color = loadTextColor
return style
},
//
emptyTextStyle() {
const style = {}
const { emptyTextColor } = this.defaultOption
style.color = emptyTextColor
return style
}
},
watch: {
scrollViewTop(val) {
this.updateScrollView()
}
},
created() {
this.elClass = 'scroll-view-' + this._uid
this.windowInfo = uni.getSystemInfoSync()
},
mounted() {
this.handleInit()
}
}
</script>
<style scoped lang="scss">
.scroll-list-wrap {
box-sizing: border-box;
.scroll-view {
position: relative;
.scroll-content {
height: 100%;
display: flex;
will-change: transform;
flex-direction: column;
.pull-down-wrap {
left: 0;
width: 100%;
display: flex;
padding: 30rpx 0;
position: absolute;
align-items: flex-end;
justify-content: center;
transform: translateY(-100%);
.refresh-view {
display: flex;
justify-content: center;
.pull-down-animation {
width: 32rpx;
height: 32rpx;
border-width: 4rpx;
border-style: solid;
border-radius: 50%;
&.refreshing {
animation: spin 0.5s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
}
.pull-down-text {
margin-left: 10rpx;
}
}
}
.empty-wrap {
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
position: absolute;
align-items: center;
flex-direction: column;
.empty-view {
margin: auto;
display: flex;
align-items: center;
flex-direction: column;
.empty-image {
width: 200rpx;
height: 200rpx;
}
.empty-text {
color: #606266;
margin-top: 20rpx;
}
}
}
.list-content {
}
.pull-up-wrap {
display: flex;
align-items: center;
justify-content: center;
.load-view {
padding: 20rpx 0;
display: flex;
align-items: center;
justify-content: center;
.pull-up-animation {
width: 32rpx;
height: 32rpx;
border-width: 4rpx;
border-style: solid;
border-radius: 50%;
animation: spin 0.5s linear infinite;
}
.pull-up-text {
margin-left: 10rpx;
}
}
}
}
}
}
</style>

80
enums/index.js

@ -1,3 +1,6 @@
import env from '@/env/index.js'
const urlEnv = env === 'production' ? '' : `-${env}`
/**
* 账号类型
*/
@ -27,3 +30,80 @@ export const verificationType = {
PHONE: 1,
EMAIL: 2
}
/**
* 法大大企业认证状态 1:未认证 2:认证中 3:已认证 4:认证失败
*/
export const fddEnterpriseStatus = {
UNCERTIFIED: 1,
CERTIFIED_ING: 2,
CERTIFIED_SUCCESS: 3,
CERTIFIED_FAIL: 4
}
/**
* 上传地址
*/
export const uploadUrl = {
image: `https://api-ops-yyt${urlEnv}.qniao.cn/yyt-uec/file-uploading/upload/image`,
file: `https://api-ops-yyt${urlEnv}.qniao.cn/yyt-uec/file-uploading/upload/file`
}
/**
* 结算周期1月结30飞算1期2月结453月结60飞算2期4月结755月结90飞算3期
*/
export const settlementPeriodEnum = [
{
value: 1,
label: '月结30'
},
{
value: 2,
label: '月结45'
},
{
value: 3,
label: '月结60'
},
{
value: 4,
label: '月结75'
},
{
value: 5,
label: '月结90'
}
]
/**
* 结算周期112233
*/
export const fsSettlementPeriodEnum = [
{
value: 1,
label: '1期'
},
{
value: 2,
label: '2期'
},
{
value: 3,
label: '3期'
}
]
/**
* 飞算结算方式 1: 先息后本
*/
export const fsSettlementMethodEnum = [
{
value: 1,
label: '先息后本'
}
]
/**
* 飞算授信审核状态 0: 待客户申请 1: 审核中 2: 通过 3: 拒绝
*/
export const fsAuditStatus = {
WAIT_APPLY: 0,
AUDITING: 1,
PASS: 2,
REJECT: 3
}

8
env/index.js

@ -0,0 +1,8 @@
/**
* @description 唯一环境变量
*/
const env = 'test'
// const env = 'dev'
// const env = 'production'
export default env

30
pages.json

@ -3,18 +3,21 @@
{
"path": "pages/store/index",
"style": {
"navigationBarTitleText": "店铺首页"
"navigationBarTitleText": "店铺首页",
"navigationStyle": "custom"
}
},
{
"path": "pages/cart/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "购物车"
}
},
{
"path": "pages/mine/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "个人中心"
}
},
@ -22,14 +25,39 @@
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/agreement/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "用户与隐私协议"
}
},
{
"path": "pages/page-view/index",
"style": {
"navigationBarTitleText": "统一第三方页面",
"enablePullDownRefresh": false
}
},
{
"path": "pages/enterprise-info/index",
"style": {
"navigationBarTitleText": "完善企业信息",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/error/index",
"style": {
"navigationBarTitleText": "页面不存在",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}
],
"globalStyle": {

566
pages/enterprise-info/index.vue

@ -0,0 +1,566 @@
<template>
<view class="content">
<uni-nav-bar left-icon="back" @clickLeft="back" statusBar fixed title="完善信息"></uni-nav-bar>
<view>
<qn-form-item label="基础信息" type="title"></qn-form-item>
<qn-form-item label="企业名称" required>
<qn-easyinput :maxlength="20" @blur="showCompany" v-model="form.name" :inputBorder="false" text="right" placeholder="请输入企业名称"></qn-easyinput>
</qn-form-item>
<qn-form-item label="企业简称">
<qn-easyinput :maxlength="20" v-model="form.shortName" :inputBorder="false" text="right" placeholder="请输入企业简称"></qn-easyinput>
</qn-form-item>
<qn-form-item label="联系人">
<qn-easyinput :maxlength="20" v-model="form.contactName" :inputBorder="false" text="right" placeholder="请输入用户名称"></qn-easyinput>
</qn-form-item>
<qn-form-item label="联系手机">
<qn-easyinput
:maxlength="11"
type="number"
v-model="form.contactMobile"
:inputBorder="false"
text="right"
placeholder="请输入用户手机号"
></qn-easyinput>
</qn-form-item>
<qn-form-item label="担任职务">
<qn-easyinput :maxlength="20" v-model="form.contactTitle" :inputBorder="false" text="right" placeholder="请输入用户职务"></qn-easyinput>
</qn-form-item>
<qn-form-item label="点击定位" required v-if="!hasSelected">
<image @click="showLocationList" style="width: 32rpx; height: 32rpx" src="/static/imgs/enterpriseInfo/location-icon.png"></image>
</qn-form-item>
<qn-form-item label="所在区域" required>
<qn-data-picker
:readonly="true"
text="right"
:border="false"
class="qn-picker"
placeholder="区域"
popup-title="请选择城市"
:map="{ text: 'name', value: 'id' }"
@change="onAreaChange"
:clear-icon="true"
:localdata="items"
>
<text v-if="form.locDistrictId">
{{ `${form.locProvinceName || ''}/${form.locCityName || ''}/${form.locDistrictName || ''}/${form.locStreetName || ''}` }}
</text>
</qn-data-picker>
</qn-form-item>
<qn-form-item label="详细地址" required>
<qn-easyinput
:maxlength="20"
:disabled="true"
:styles="{ disableColor: '#fff' }"
v-model="form.locDetail"
:inputBorder="false"
text="right"
placeholder="请定位详细地址"
></qn-easyinput>
</qn-form-item>
<qn-form-item label="工商信息" type="title"></qn-form-item>
<qn-form-item label="信用代码" required>
<qn-easyinput
:maxlength="18"
:disabled="hasSelected"
:styles="{ disableColor: '#fff' }"
v-model="form.uniformSocialCreditCode"
:inputBorder="false"
text="right"
placeholder="请输入信用代码"
></qn-easyinput>
</qn-form-item>
<qn-form-item label="法人/实控人" required>
<qn-easyinput
:maxlength="20"
:disabled="hasSelected"
:styles="{ disableColor: '#fff' }"
v-model="form.legalPersonName"
:inputBorder="false"
text="right"
placeholder="请输入法人/实控人"
></qn-easyinput>
</qn-form-item>
<qn-form-item label="法人/实控人手机" required>
<qn-easyinput
:maxlength="11"
type="number"
:disabled="hasSelected"
:styles="{ disableColor: '#fff' }"
v-model="form.legalPersonMobile"
:inputBorder="false"
text="right"
placeholder="请输入法人/实控人手机"
></qn-easyinput>
</qn-form-item>
<qn-form-item label="法人/实控人身份证号" required>
<qn-easyinput
:maxlength="18"
:disabled="hasSelected"
:styles="{ disableColor: '#fff' }"
v-model="form.legalPersonIdCardNo"
:inputBorder="false"
text="right"
placeholder="请输入身份证号"
></qn-easyinput>
</qn-form-item>
<qn-form-item label="身份证照片" size="large" required>
<view class="upload-area">
<image class="idCard" @click="!hasSelected && selectedImage('legalPersonIdCardFrontImg')" :src="frontIDCard" />
<image class="idCard" @click="!hasSelected && selectedImage('legalPersonIdCardBackImg')" :src="backIDCard" />
</view>
</qn-form-item>
<qn-form-item label="营业执照" required>
<text
v-if="!form.businessLicenseImg"
@click="!hasSelected && selectedImage('businessLicenseImg')"
:style="`font-size: 28rpx; color: ${hasSelected ? '#eee' : '#007aff'}`"
>
点击上传
</text>
<text
v-if="form.businessLicenseImg"
@click="!hasSelected && selectedImage('businessLicenseImg')"
:style="`font-size: 28rpx;margin-right: 16rpx; color: ${hasSelected ? '#eee' : '#007aff'}`"
>
重新上传
</text>
<text v-if="form.businessLicenseImg" @click="!hasSelected && showImage()" style="font-size: 28rpx; color: #007aff">预览</text>
</qn-form-item>
<qn-form-item label="注册资本(万)" required>
<qn-easyinput
type="digit"
maxlength="10"
:disabled="hasSelected"
:styles="{ disableColor: '#fff' }"
v-model="form.registeredCapital"
:inputBorder="false"
text="right"
placeholder="请输入注册资本"
></qn-easyinput>
</qn-form-item>
<qn-form-item label="成立日期" required>
<qn-datetime-picker v-model="form.foundDate" type="date" :border="false" :disabled="hasSelected"></qn-datetime-picker>
</qn-form-item>
</view>
<uni-popup ref="popup" type="bottom">
<view class="popup_modal">
<slot name="title">
<view class="popup_modal-title">可选择已录入公司</view>
</slot>
<scroll-view scroll-y="true" class="popup_modal-scroll">
<view @click="selectCompany(item.enterpriseId)" class="popup_modal-scroll-item" v-for="item in searchList" :key="item.enterpriseId">
{{ item.enterpriseName }}
</view>
</scroll-view>
</view>
</uni-popup>
<uni-popup ref="locationPopup" type="bottom">
<view class="popup_modal">
<slot name="title">
<view class="popup_modal-title">可选择以下定位公司</view>
</slot>
<scroll-view scroll-y="true" class="popup_modal-scroll">
<view @click="selectLocation(item)" style="height: 100rpx" class="popup_modal-scroll-item" v-for="(item, index) in locationList" :key="index">
<view class="location-item">
<text class="title">{{ item.enterpriseName }}</text>
<text class="address">{{ `${item.provinceName}/${item.cityName}/${item.districtName}/${item.address}` }}</text>
</view>
</view>
</scroll-view>
</view>
</uni-popup>
<qn-footer fixed height="120rpx">
<view class="button-area">
<view class="button button__cancel" @click="back">
<text class="text">取消</text>
</view>
<view class="button button__submit" @click="saveInfo">
<text class="text" style="color: white">保存信息</text>
</view>
</view>
</qn-footer>
</view>
</template>
<script>
import { back, uploadImage, go2 } from '@/utils/hook.js'
import { getArea } from '@/apis/commonApi.js'
import { completeInfo, getCompanyList, getCompanyInfoById, getCompanyLocationList } from '@/apis/enterpriseInfoApi.js'
const validateFields = [
'name',
'uniformSocialCreditCode',
'legalPersonName',
'locProvinceId',
'locCityId',
'locDistrictId',
'locStreetId',
'locProvinceName',
'locCityName',
'locDistrictName',
'locStreetName',
'locDetail',
'legalPersonIdCardNo',
'legalPersonIdCardFrontImg',
'legalPersonIdCardBackImg',
'businessLicenseImg',
'registeredCapital',
'foundDate'
]
export default {
data() {
return {
form: {
id: null,
name: null,
shortName: null,
contactName: null,
contactMobile: null,
contactTitle: null,
uniformSocialCreditCode: null,
locProvinceId: null,
locCityId: null,
locDistrictId: null,
locStreetId: null,
locProvinceName: null,
locCityName: null,
locDistrictName: null,
locStreetName: null,
locDetail: null,
legalPersonName: null,
legalPersonMobile: null,
legalPersonIdCardNo: null,
legalPersonIdCardFrontImg: null,
legalPersonIdCardBackImg: null,
businessLicenseImg: null,
registeredCapital: null,
foundDate: null
},
searchList: [],
locationList: [],
hasSelected: false,
items: []
}
},
mounted() {
getArea().then((res) => {
if (res) {
this.items = res
}
})
},
methods: {
back,
showLocationList() {
if (!this.form.name || !this.form.name.trim()) {
uni.showToast({
title: '请先填写公司名称',
icon: 'none'
})
return
}
getCompanyLocationList({ enterpriseName: this.form.name }).then((res) => {
if (res) {
this.locationList = res
this.$refs.locationPopup.open('bottom')
} else {
uni.showToast({
title: '暂无定位公司',
icon: 'warn'
})
}
})
},
selectLocation(location) {
this.form.locProvinceId = location.provinceCode
this.form.locCityId = location.cityCode
this.form.locDistrictId = location.districtCode
this.form.locProvinceName = location.provinceName
this.form.locCityName = location.cityName
this.form.locDistrictName = location.districtName
this.form.locDetail = location.address
this.$refs.locationPopup.close()
},
showCompany(e) {
let enterpriseName = e.detail.value.trim()
if (enterpriseName) {
getCompanyList({ enterpriseName }).then((res) => {
if (res) {
this.searchList = res.records
if (this.searchList.length > 0) {
this.$refs.popup.open('bottom')
}
}
})
}
},
selectCompany(enterpriseId) {
this.$refs.popup.close()
getCompanyInfoById({ enterpriseId }).then((res) => {
if (res) {
this.form.id = enterpriseId
this.form.name = res.name
//
this.reflectiveCompany(res)
setTimeout(() => {
this.hasSelected = true
}, 0)
}
})
},
reflectiveCompany(info) {
this.form.uniformSocialCreditCode = info.uniformSocialCreditCode
this.form.locProvinceId = info.locProvinceId
this.form.locCityId = info.locCityId
this.form.locStreetId = info.locStreetId
this.form.locDistrictId = info.locDistrictId
this.form.locProvinceName = info.locProvinceName
this.form.locCityName = info.locCityName
this.form.locStreetName = info.locStreetName
this.form.locDistrictName = info.locDistrictName
this.form.locDetail = info.locDetail
this.form.registeredCapital = info.registeredCapital
this.form.foundDate = info.foundDate
},
onAreaChange(e) {
if (e.detail.value && e.detail.value.length > 0) {
const [province, city, district, street] = e.detail.value
this.form.locProvinceId = province.value
this.form.locProvinceName = province.text
this.form.locCityId = city.value
this.form.locCityName = city.text
this.form.locDistrictId = district.value
this.form.locDistrictName = district.text
this.form.locStreetId = street.value
this.form.locStreetName = street.text
} else {
this.form.locProvinceId = null
this.form.locProvinceName = null
this.form.locCityId = null
this.form.locCityName = null
this.form.locDistrictId = null
this.form.locDistrictName = null
this.form.locStreetId = null
this.form.locStreetName = null
}
},
selectedImage(type) {
uploadImage()
.then((url) => {
if (url) {
this.form[type] = url
}
})
.catch((e) => {
uni.showToast({
title: '上传失败',
icon: 'fail'
})
})
},
showImage() {
uni.previewImage({
urls: [this.form.businessLicenseImg]
})
},
saveInfo() {
if (!this.form.id) {
for (let i = 0; i < validateFields.length; i++) {
if (this.form[validateFields[i]] === null || this.form[validateFields[i]] === '') {
uni.showToast({
title: '请完善信息',
icon: 'none'
})
return
}
}
if (!/^1[3456789]\d{9}$/.test(this.form['legalPersonMobile'])) {
uni.showToast({
title: '请输入正确法人手机号',
icon: 'none'
})
return
}
}
if (this.form.contactMobile && !/^1[3456789]\d{9}$/.test(this.form['contactMobile'])) {
uni.showToast({
title: '请输入正确联系人手机号',
icon: 'none'
})
return
}
completeInfo(this.form).then((res) => {
if (res) {
uni.showToast({
title: '添加成功',
icon: 'success'
})
setTimeout(() => {
go2('store')
}, 1000)
}
})
}
},
watch: {
['form.name'](val) {
this.hasSelected = false
this.form.id = null
this.reflectiveCompany({})
}
},
computed: {
frontIDCard() {
let url = 'https://qncloud.oss-cn-shenzhen.aliyuncs.com/paper_shopkeeper/frontIDCard.png'
if (this.hasSelected || !this.form.legalPersonIdCardFrontImg) {
return url
}
return this.form.legalPersonIdCardFrontImg
},
backIDCard() {
let url = 'https://qncloud.oss-cn-shenzhen.aliyuncs.com/paper_shopkeeper/backDCard.png'
if (this.hasSelected || !this.form.legalPersonIdCardBackImg) {
return url
}
return this.form.legalPersonIdCardBackImg
}
}
}
</script>
<style lang="scss" scoped>
.content {
width: 750rpx;
display: flex;
flex-direction: column;
background-color: #f7f8fa;
}
.qn-form-item {
width: 750rpx;
padding: 0rpx 32rpx;
background-color: #fff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: 2rpx solid #d8d8d8;
min-height: 80rpx;
.label {
flex-grow: 0;
flex-shrink: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
margin-right: 20rpx;
.label__text {
font-size: 28rpx;
color: #000000;
}
}
.value {
flex-grow: 1;
flex-shrink: 1;
text-align: right;
}
}
.qn-form-item__title {
background-color: #f7f8fa;
padding: 20rpx 32rpx;
border: none;
.title {
font-size: 30rpx;
color: #888888;
}
}
.popup_modal {
width: 750rpx;
height: 600rpx;
background-color: #fff;
border-radius: 10px 10px 0 0;
.popup_modal-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 750rpx;
height: 88rpx;
font-weight: 600;
border-bottom: 2rpx solid #d8d8d8;
}
.popup_modal-scroll {
width: 750rpx;
height: 600rpx;
.popup_modal-scroll-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 750rpx;
height: 88rpx;
padding: 0rpx 32rpx;
border-bottom: 2rpx solid #d8d8d8;
}
}
}
.upload-area {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 10rpx;
margin-bottom: 24rpx;
.idCard {
width: 324rpx;
height: 280rpx;
flex-grow: 0;
flex-shrink: 0;
}
}
.button-area {
width: 750rpx;
padding: 0 32rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.button {
flex-grow: 0;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10rpx;
.text {
font-size: 30rpx;
font-weight: 500;
text-align: center;
}
}
.button__cancel {
width: 270rpx;
height: 88rpx;
border: 2rpx solid #979797;
}
.button__submit {
width: 400rpx;
height: 88rpx;
background: #007aff;
}
}
.location-item {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
.title {
font-size: 32rpx;
color: #000000;
margin-bottom: 10rpx;
}
.address {
font-size: 24rpx;
color: #888888;
word-break: break-all;
}
}
</style>

18
pages/error/index.vue

@ -0,0 +1,18 @@
<template>
<view>error</view>
</template>
<script>
import { go2, back } from '@/utils/hook.js'
export default {
data() {
return {}
},
methods: {
go2,
back
}
}
</script>
<style lang="scss" scoped></style>

84
pages/login/index.vue

@ -1,14 +1,6 @@
<template>
<view>
<uni-nav-bar
:fixed="true"
color="#ffffff"
background-color="#ffffff"
:status-bar="true"
:border="false"
left-icon="closeempty"
@clickLeft="close"
/>
<view style="background-color: white">
<uni-nav-bar :fixed="true" color="#ffffff" background-color="#ffffff" :status-bar="true" :border="false" left-icon="closeempty" @clickLeft="close" />
<view class="logo_area">
<image class="logo" src="/static/logo.png"></image>
<text class="title">纸掌柜</text>
@ -25,44 +17,22 @@
/>
</view>
<view class="captcha">
<uni-easyinput
v-model="captcha"
:inputBorder="false"
:placeholderStyle="'font-size:32rpx;'"
placeholder="请输入验证码"
:maxlength="6"
type="number"
/>
<uni-easyinput v-model="captcha" :inputBorder="false" :placeholderStyle="'font-size:32rpx;'" placeholder="请输入验证码" :maxlength="6" type="number" />
<view v-show="timer > 0" class="timer_area">
<text class="timer">{{ `(${timer}S后) ` }}</text>
<text class="timer_text">重新获取</text>
</view>
<text v-show="timer <= 0" class="code" @click="getCode">
获取验证码
</text>
</view>
<view
class="login-button"
@click="login"
:style="{ opacity: disabled ? '0.5' : '' }"
>
登录
<text v-show="timer <= 0" class="code" @click="getCode">获取验证码</text>
</view>
<view class="login-button" @click="login" :style="{ opacity: disabled ? '0.5' : '' }">登录</view>
</view>
<view class="agreement_area">
<checkbox-group @change="onCheck">
<checkbox
value="cb"
:checked="checked"
color="#000000"
style="transform: scale(0.7)"
/>
<checkbox value="cb" :checked="checked" color="#000000" style="transform: scale(0.7)" />
</checkbox-group>
<view class="agreement">
已阅读并同意纸掌柜
<text class="agreement_text" @click="jumpAgreement">
用户与隐私协议
</text>
<text class="agreement_text" @click="jumpAgreement">用户与隐私协议</text>
</view>
</view>
<!--
@ -74,6 +44,7 @@
<script>
import { getAuthCaptcha, loginByPhone, getQnToken } from '@/apis/loginApi'
import { getBaseInfo } from '@/apis/commonApi'
import { accountType, verificationType, codePurpose } from '@/enums/index.js'
import store from '@/store/index.js'
import { go2, back } from '@/utils/hook.js'
@ -123,7 +94,10 @@ export default {
verifiableAccount: this.phoneNumber,
verifiableAccountType: verificationType.PHONE
}).then((res) => {
console.log(res)
uni.showToast({
title: '验证码已发送',
icon: 'none'
})
})
},
//
@ -166,9 +140,10 @@ export default {
//
const nextPage = store.state.nextPage
if (nextPage.name) {
go2(nextPage.name, nextPage.data)
go2(nextPage.name, nextPage.data, true)
} else {
go2('mine')
//
this.setAccountInfo()
}
store.commit('removeNextPage')
}, 1000)
@ -178,15 +153,34 @@ export default {
})
}
})
},
setAccountInfo() {
getBaseInfo({}, true).then((res) => {
if (res) {
if (!res.enterpriseList || res.enterpriseList.length === 0) {
go2('enterprise-info', { operation: 'add' }, true)
} else {
let companyInfo = res.enterpriseList[0]
store.commit('setCompanyInfo', {
id: companyInfo.id,
name: companyInfo.name,
fddEnterpriseStatus: companyInfo.fddEnterpriseStatus
})
store.commit('setUserInfo', {
name: companyInfo.employeeName,
userId: res.userId,
mobile: res.mobile,
avatar: null
})
go2('store')
}
}
})
}
},
computed: {
disabled() {
return (
this.phoneNumber.trim() === '' ||
this.phoneNumber.trim().length != 11 ||
this.captcha.trim() === ''
)
return this.phoneNumber.trim() === '' || this.phoneNumber.trim().length != 11 || this.captcha.trim() === ''
}
}
}

567
pages/mine/index.vue

@ -1,40 +1,543 @@
<template>
<view class="content">
mine
</view>
<view class="content">
<view class="top-area">
<view class="reset">
<image class="bg" src="/static/imgs/mine/mine-top-bg.png"></image>
<view class="operation-area">
<view class="user">
<image class="avatar" :src="curAvatar"></image>
<view v-if="!hasLogin" @click="go2('login')">
<view>
<text style="font-size: 40rpx; color: #fff; font-weight: 600">点击登录</text>
</view>
<view style="margin-top: 10rpx">
<text style="font-size: 26rpx; color: #fff; font-weight: 400">登录解锁更全功能</text>
</view>
</view>
<view v-else>
<view class="user__name">
<text class="name">{{ userInfo.name }}</text>
<image v-if="userInfo.fddEnterpriseStatus === fddStatus.UNCERTIFIED" class="image" src="/static/imgs/mine/certified-icon.png"></image>
<image v-else class="image" @click="certifyCompany()" src="/static/imgs/mine/non-certified-icon.png"></image>
</view>
<view style="margin-top: 10rpx">
<text style="font-size: 26rpx; color: #fff; font-weight: 400; word-break: break-all">
{{ userInfo.companyName }}
</text>
</view>
</view>
</view>
<view class="operation">
<view class="box">
<view class="container" @click="go2('message')">
<image class="icon" src="/static/imgs/mine/msg-icon.png"></image>
<view v-if="messageNum > 0" class="number">
<text class="text">{{ messageNum }}</text>
</view>
</view>
<image class="icon" @click="go2('setting')" src="/static/imgs/mine/setting-icon.png"></image>
</view>
<view v-if="companyNum > 1" class="box" @click="go2('toggle-supplier')">
<text style="font-size: 24rpx; color: #fff; flex-shrink: 0">切换账号</text>
<image class="icon" style="width: 24rpx; height: 24rpx; margin-left: 8rpx" src="/static/imgs/mine/toggle-icon.png"></image>
</view>
</view>
</view>
<view class="vip-area">
<view class="vip-content">
<view class="left">
<image class="icon" src="/static/imgs/mine/vip-icon.png"></image>
<text class="text">VIP会员</text>
</view>
<text class="center">立即开通会员 尊享特权</text>
<view class="right">
<text class="text">开通会员</text>
</view>
</view>
</view>
</view>
</view>
<view class="card-area">
<view class="header">
<view class="item" style="justify-content: flex-start">
<image class="icon" style="width: 32rpx; height: 32rpx" src="/static/imgs/mine/money-icon.png"></image>
<text class="text">掌柜收入</text>
</view>
<view class="item" style="justify-content: flex-end">
<qn-data-picker
v-model="tradeDate"
text="right"
:border="false"
placeholder="交易区间"
popup-title="请选择时间"
:localdata="tradeRange"
:clear-icon="false"
v-slot:default="{ data }"
>
<view class="time-range">
<text style="font-size: 24rpx; color: rgba(0, 0, 0, 0.65); margin-right: 8rpx">
{{ data[0] ? data[0].value : '' }}
</text>
<text style="font-size: 24rpx; color: #333333; font-weight: 500; margin-right: 8rpx">
{{ data[0] ? data[0].text : '' }}
</text>
<uni-icons type="bottom" size="14" color="rgba(0,0,0,0.65)"></uni-icons>
</view>
</qn-data-picker>
</view>
</view>
<view class="order-area">
<view class="order-item">
<text class="value">{{ tradeData.tradingVolume }}</text>
<text class="label">交易额()</text>
</view>
<view class="order-item">
<text class="value">{{ tradeData.volumeOfBusiness }}</text>
<text class="label">交易量()</text>
</view>
<view class="order-item">
<text class="value">{{ tradeData.orderQuantity }}</text>
<text class="label">成交订单()</text>
</view>
</view>
</view>
<view class="card-area">
<view class="header">
<text style="font-size: 30rpx; color: rgba(0, 0, 0, 0.85); font-weight: 500">其他工具</text>
</view>
<view class="icon-area">
<view class="icon-item">
<image class="icon" src="/static/imgs/mine/order-icon.png"></image>
<text class="label">订单管理</text>
</view>
<view class="icon-item">
<image class="icon" src="/static/imgs/mine/finance-icon.png"></image>
<text class="label">账期订单融资</text>
</view>
<view class="icon-item">
<image class="icon" src="/static/imgs/mine/contract-icon.png"></image>
<text class="label">合同管理</text>
</view>
<view class="icon-item">
<image class="icon" src="/static/imgs/mine/credit-icon.png"></image>
<text class="label">征信管理</text>
</view>
<view class="icon-item" @click="go2('client-credit-list')">
<image class="icon" src="/static/imgs/mine/credit-icon.png"></image>
<text class="label">授信记录</text>
</view>
<!-- <view class="icon-item">
<image class="icon" src="/static/imgs/mine/contract-icon.png"></image>
<text class="label">服务区域</text>
</view> -->
</view>
</view>
<view @click="logout">mine</view>
<view @click="go2('client-credit')">client-credit</view>
<view @click="go2('enterprise-info')">enterprise-info</view>
</view>
</template>
<script>
import { exit, go2 } from '@/utils/hook.js'
import { fddEnterpriseStatus } from '@/enums/index.js'
import { getBaseInfo } from '@/apis/commonApi.js'
import qnDataPicker from '@/components/qn-data-picker/qn-data-picker.vue'
import { dateTimeFormat } from '@/utils/index.js'
import { getOrderStatistics } from '@/apis/mineApi.js'
//
const currentMonth = (() => {
let endDate = new Date()
let beginDate = new Date(endDate.getFullYear(), endDate.getMonth(), 1)
return [dateTimeFormat(beginDate, 'yyyy-mm-dd'), dateTimeFormat(endDate, 'yyyy-mm-dd')]
})()
export default {
}
//
const lastMonth = (() => {
let now = new Date()
let endDate = new Date(now.getFullYear(), now.getMonth(), 0)
let beginDate = new Date(now.getFullYear(), now.getMonth() - 1, 1)
return [dateTimeFormat(beginDate, 'yyyy-mm-dd'), dateTimeFormat(endDate, 'yyyy-mm-dd')]
})()
export default {
components: { qnDataPicker },
data() {
return {
fddStatus: Object.freeze(fddEnterpriseStatus),
messageNum: 0,
companyNum: 0,
tradeRange: [
{
text: '本月',
value: currentMonth.join('~')
},
{
text: '上月',
value: lastMonth.join('~')
},
{
text: '总计',
value: ''
}
],
tradeDate: currentMonth.join('~'),
tradeData: {
tradingVolume: 0,
volumeOfBusiness: 0,
orderQuantity: 0
}
}
},
methods: {
logout() {
exit()
},
//
certifyCompany() {
if (this.userInfo.fddEnterpriseStatus === fddEnterpriseStatus.UNCERTIFIED || this.userInfo.fddEnterpriseStatus === fddEnterpriseStatus.CERTIFIED_FAIL) {
getVerifyUrl({ enterpriseId: this.$store.state.companyInfo.id }).then((res) => {
if (res) {
// #ifdef APP-PLUS
go2('page-view', {
title: '实名认证',
url: encodeURIComponent(res)
})
// #endif
// #ifdef H5
window ? (window.location.href = res) : ''
// #endif
}
})
}
if (this.userInfo.fddEnterpriseStatus === fddEnterpriseStatus.UNCERTIFIED) {
uni.showToast({
title: '认证中,请稍后~',
icon: 'none'
})
}
},
go2,
//
getStatistics() {}
},
watch: {
tradeDate(val) {
console.log('val:', val)
},
'$store.state.supplierInfo.supplierId': {
handler(val) {
console.log('切换了供应商:', val)
if (val) {
this.getStatistics()
}
},
immediate: true
},
tradeDate() {
this.getStatistics()
}
},
computed: {
hasLogin() {
console.log('token:', this.$store.state.qnToken)
return this.$store.state.qnToken != ''
},
curAvatar() {
if (!this.hasLogin) {
return '/static/imgs/mine/user-avatar.png'
}
if (this.userInfo.avatar) {
return this.userInfo.avatar
} else {
return '/static/imgs/mine/default-avatar.png'
}
},
userInfo() {
return {
avatar: this.$store.state.userInfo.avatar || '',
name: this.$store.state.userInfo.name || this.$store.state.userInfo.mobile || '',
companyName: this.$store.state.companyInfo.name || '',
fddEnterpriseStatus: this.$store.state.companyInfo.fddEnterpriseStatus || 1
}
}
},
onShow() {
getBaseInfo().then((res) => {
if (res) {
this.companyNum = res.enterpriseList.length
}
})
},
created() {
this.getStatistics()
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
<style lang="scss" scoped>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.top-area {
width: 750rpx;
height: 380rpx;
overflow: hidden;
position: relative;
border-radius: 0 0 350px 350px;
transform: scaleX(2.5);
.reset {
width: 750rpx;
height: 380rpx;
transform: scaleX(0.4); //
position: relative;
overflow: hidden;
}
.bg {
width: 750rpx;
height: 380rpx;
z-index: 0;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.operation-area {
width: 750rpx;
margin: 96rpx 0 36rpx;
padding: 0 32rpx;
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
.user {
z-index: 5;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
.avatar {
width: 156rpx;
height: 156rpx;
border-radius: 50%;
flex-grow: 0;
flex-shrink: 0;
overflow: hidden;
margin-right: 16rpx;
}
.user__name {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
.name {
font-size: 40rpx;
color: #fff;
font-weight: 600;
margin-right: 16rpx;
word-break: break-all;
}
.image {
width: 100rpx;
height: 32rpx;
flex-grow: 0;
flex-shrink: 0;
}
}
}
.operation {
z-index: 5;
margin-top: 18rpx;
margin-left: 20rpx;
flex-grow: 0;
flex-shrink: 0;
.box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
margin-bottom: 36rpx;
.container {
width: 40rpx;
height: 40rpx;
flex-grow: 0;
flex-shrink: 0;
margin-right: 26rpx;
position: relative;
.number {
position: absolute;
top: -12rpx;
right: -12rpx;
// width:32rpx;
// height: 32rpx;
padding: 6rpx 10rpx;
background-color: #f5222d;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
.text {
font-size: 20rpx;
color: #fff;
font-weight: 500;
}
}
}
.icon {
width: 40rpx;
height: 40rpx;
flex-grow: 0;
flex-shrink: 0;
}
}
}
}
.vip-area {
z-index: 5;
position: absolute;
bottom: 0;
left: 0;
width: 686rpx;
height: 90rpx;
margin: 0 32rpx;
background-image: linear-gradient(90deg, #333333 0%, #696c6b 99%);
border-radius: 10rpx;
.vip-content {
width: 686rpx;
padding: 18rpx 24rpx 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.left {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
.icon {
width: 28rpx;
height: 28rpx;
flex-grow: 0;
flex-shrink: 0;
}
.text {
font-size: 26rpx;
color: #dbc189;
font-weight: 500;
}
}
.center {
font-size: 24rpx;
color: #e7e1be;
font-weight: 600;
}
.right {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 128rpx;
height: 44rpx;
border-radius: 14rpx;
background-image: linear-gradient(90deg, #f4edce 1%, #f3d99e 98%);
.text {
font-size: 24rpx;
color: #443015;
}
}
}
}
}
.card-area {
width: 686rpx;
margin: 20rpx 32rpx 0;
background-color: #fff;
box-shadow: 0 2rpx 14rpx 0 rgba(220, 220, 220, 0.5);
border-radius: 10px;
.header {
padding: 20rpx 24rpx 14rpx;
border-bottom: 2rpx solid #f8f8f8;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.item {
display: flex;
flex-direction: row;
align-items: center;
.icon {
flex-grow: 0;
flex-shrink: 0;
margin-right: 10rpx;
}
.text {
font-size: 30rpx;
color: rgba(0, 0, 0, 0.85);
letter-spacing: 1.5rpx;
font-weight: 500;
}
}
}
}
.order-area {
padding: 20rpx 20rpx 30rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.order-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.value {
font-size: 36rpx;
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
}
.label {
font-size: 26rpx;
color: #666666;
margin-top: 10rpx;
}
}
}
.icon-area {
padding: 40rpx 32rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
.icon-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 140rpx;
margin-bottom: 36rpx;
.icon {
width: 68rpx;
height: 68rpx;
flex-grow: 0;
flex-shrink: 0;
}
.label {
font-size: 26rpx;
color: #666666;
margin-top: 10rpx;
}
}
}
.time-range {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
}
</style>

66
pages/page-view/index.vue

@ -0,0 +1,66 @@
<template>
<view>
<web-view :src="url"></web-view>
</view>
</template>
<script>
import { go2, back, exit } from '@/utils/hook.js'
import { makeSocket } from '@/utils/index.js'
export default {
data() {
return {
url: '',
socket: null
}
},
onLoad(option) {
if (option) {
this.url = decodeURIComponent(option.url)
console.log(this.url, option)
} else {
uni.showToast({
title: '参数错误',
icon: 'none',
complete: () => {
setTimeout(() => {
back()
}, 1000)
}
})
}
},
created() {
makeSocket({ pageInfo: 'page-view', retry: true }).then((res) => {
this.socket = res
this.socket.onMessage(this.getMessage)
})
},
methods: {
getMessage(data) {
//
if (data.type == 'signSuccess') {
// go2('paper-mall-order-detail', { id: data.orderId }, true)
}
//
if (data.type == 'certificatedSuccess') {
back()
}
},
destroySocket() {
if (this.socket) {
this.socket.close()
this.socket = null
}
}
},
destroyed() {
this.destroySocket()
},
onHide() {
this.destroySocket()
}
}
</script>
<style></style>

17
static/icon/iconfont.css

@ -0,0 +1,17 @@
@font-face {
font-family: "iconfont"; /* Project id 3120523 */
src: url('/static/icon/iconfont.ttf') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-required:before {
content: "\e60e";
}

BIN
static/icon/iconfont.ttf

BIN
static/imgs/enterpriseInfo/location-icon.png

Before After
Width: 32  |  Height: 32  |  Size: 1.5 KiB

BIN
static/imgs/mine/certified-icon.png

Before After
Width: 100  |  Height: 32  |  Size: 4.9 KiB

BIN
static/imgs/mine/contract-icon.png

Before After
Width: 74  |  Height: 74  |  Size: 8.6 KiB

BIN
static/imgs/mine/credit-icon.png

Before After
Width: 68  |  Height: 73  |  Size: 5.9 KiB

BIN
static/imgs/mine/default-avatar.png

Before After
Width: 156  |  Height: 156  |  Size: 17 KiB

BIN
static/imgs/mine/finance-icon.png

Before After
Width: 74  |  Height: 73  |  Size: 8.4 KiB

BIN
static/imgs/mine/mine-top-bg.png

Before After
Width: 750  |  Height: 377  |  Size: 336 KiB

BIN
static/imgs/mine/money-icon.png

Before After
Width: 32  |  Height: 32  |  Size: 1.6 KiB

BIN
static/imgs/mine/msg-icon.png

Before After
Width: 40  |  Height: 40  |  Size: 1.2 KiB

BIN
static/imgs/mine/non-certified-icon.png

Before After
Width: 100  |  Height: 32  |  Size: 4.5 KiB

BIN
static/imgs/mine/order-icon.png

Before After
Width: 68  |  Height: 68  |  Size: 5.2 KiB

BIN
static/imgs/mine/setting-icon.png

Before After
Width: 40  |  Height: 40  |  Size: 1.2 KiB

BIN
static/imgs/mine/toggle-icon.png

Before After
Width: 24  |  Height: 24  |  Size: 382 B

BIN
static/imgs/mine/user-avatar.png

Before After
Width: 156  |  Height: 156  |  Size: 5.1 KiB

BIN
static/imgs/mine/vip-icon.png

Before After
Width: 28  |  Height: 26  |  Size: 1.3 KiB

BIN
static/logo.png

Before After
Width: 72  |  Height: 72  |  Size: 3.9 KiB Width: 72  |  Height: 72  |  Size: 6.6 KiB

173
store/index.js

@ -1,21 +1,44 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { isObject } from '@/utils/is'
import { isObject, isArray } from '@/utils/is'
let qnToken = null,
/**
* @value avatar 头像
* @value name 当前账号职员名称
* @value userId 用户id
* @value mobile 手机号
*/
userInfo = null,
supplierInfo = null,
uecToken = null
/**
* @value id 企业id
* @value name 企业名称
* @value fddEnterpriseStatus 法大大认证状态 1未认证2认证进行中3认证成功4认证失败
*/
companyInfo = null,
uecToken = null,
searchHistory = null,
/**
* @value supplierId 当前被分析的供应商id
*/
supplierId = null
const companyInfoParams = ['id', 'name', 'fddEnterpriseStatus']
const userInfoParams = ['name', 'userId', 'mobile', 'avatar']
try {
uecToken = uni.getStorageSync('uecToken')
qnToken = uni.getStorageSync('qnToken')
supplierId = uni.getStorageSync('supplierId')
userInfo = uni.getStorageSync('userInfo')
searchHistory = uni.getStorageSync('searchHistory')
if (searchHistory) {
searchHistory = JSON.parse(searchHistory)
}
if (userInfo) {
userInfo = JSON.parse(userInfo)
}
supplierInfo = uni.getStorageSync('supplierInfo')
if (supplierInfo) {
supplierInfo = JSON.parse(supplierInfo)
companyInfo = uni.getStorageSync('companyInfo')
if (companyInfo) {
companyInfo = JSON.parse(companyInfo)
}
} catch (e) {
console.error('初始化错误:', e)
@ -26,12 +49,14 @@ const store = new Vuex.Store({
state: {
uecToken: uecToken || '',
qnToken: qnToken || '', // token
supplierId: supplierId || '', // 供应商id
userInfo: userInfo || {}, // 用户信息
supplierInfo: supplierInfo || {}, // 纸盘商信息
companyInfo: companyInfo || {}, // 纸盘商信息
nextPage: {
name: '',
data: {}
}
},
searchHistory: searchHistory || []
},
mutations: {
setUecToken(state, token) {
@ -71,6 +96,37 @@ const store = new Vuex.Store({
console.error('userInfo必须是对象')
return
}
for (let i = 0; i < userInfoParams.length; i++) {
if (userInfo[userInfoParams[i]] === undefined) {
console.error('userInfo必须包含' + userInfoParams[i] + '属性')
return
}
}
try {
uni.setStorageSync('userInfo', JSON.stringify(userInfo))
state.userInfo = userInfo
} catch (e) {
console.error('设置userInfo失败:', e)
}
},
/**
* 更改当前用户信息
* @param {*} state 状态
* @param {arr} map 以key-value形式存储的数组
* @value key 需要更改的key
* @value value 更改后的值
*/
changeUserInfo(state, map) {
if (!isArray(map)) {
console.error('map必须是数组')
return
}
let userInfo = state.userInfo
map.forEach((item) => {
if (userInfoParams.includes(item.key)) {
userInfo[item.key] = item.value
}
})
try {
uni.setStorageSync('userInfo', JSON.stringify(userInfo))
state.userInfo = userInfo
@ -86,24 +142,55 @@ const store = new Vuex.Store({
console.error('删除userInfo失败:', e)
}
},
setSupplierInfo(state, supplierInfo) {
if (!isObject(supplierInfo)) {
console.error('supplierInfo必须是对象')
setCompanyInfo(state, companyInfo) {
if (!isObject(companyInfo)) {
console.error('companyInfo必须是对象')
return
}
for (let i = 0; i < companyInfoParams.length; i++) {
if (companyInfo[companyInfoParams[i]] === undefined) {
console.error(`companyInfo必须包含${companyInfoParams[i]}`)
return
}
}
try {
uni.setStorageSync('companyInfo', JSON.stringify(companyInfo))
state.companyInfo = companyInfo
} catch (e) {
console.error('设置companyInfo失败:', e)
}
},
/**
* 更改当前供应商信息
* @param {*} state 状态
* @param {arr} map 以key-value形式存储的数组
* @value key 需要更改的key
* @value value 更改后的值
*/
changeCompanyInfo(state, map) {
if (!isArray(map)) {
console.error('map必须是数组')
return
}
let companyInfo = state.companyInfo
map.forEach((item) => {
if (companyInfoParams.includes(item.key)) {
companyInfo[item.key] = item.value
}
})
try {
uni.setStorageSync('supplierInfo', JSON.stringify(supplierInfo))
state.supplierInfo = supplierInfo
uni.setStorageSync('companyInfo', JSON.stringify(companyInfo))
state.companyInfo = companyInfo
} catch (e) {
console.error('更改supplierInfo失败:', e)
console.error('更改companyInfo失败:', e)
}
},
removeSupplierInfo(state) {
removeCompanyInfo(state) {
try {
uni.removeStorageSync('supplierInfo')
state.supplierInfo = {}
uni.removeStorageSync('companyInfo')
state.companyInfo = {}
} catch (e) {
console.error('删除supplierInfo失败:', e)
console.error('删除companyInfo失败:', e)
}
},
setNextPage(state, nextPage) {
@ -117,6 +204,42 @@ const store = new Vuex.Store({
removeNextPage(state) {
state.nextPage.name = ''
state.nextPage.data = {}
},
setSearchHistory(state, searchHistory) {
if (!isArray(searchHistory)) {
console.error('searchHistory必须是数组')
return
}
try {
uni.setStorageSync('searchHistory', JSON.stringify(searchHistory))
state.searchHistory = searchHistory
} catch (e) {
console.error('更改searchHistory失败:', e)
}
},
clearSearchHistory(state) {
try {
uni.removeStorageSync('searchHistory')
state.searchHistory = []
} catch (e) {
console.error('删除searchHistory失败:', e)
}
},
setSupplierId(state, id) {
try {
uni.setStorageSync('supplierId', id)
state.supplierId = id
} catch (e) {
console.error('更改supplierId失败:', e)
}
},
removeSupplierId(state) {
try {
uni.removeStorageSync('supplierId')
state.supplierId = ''
} catch (e) {
console.error('删除supplierId失败:', e)
}
}
},
actions: {
@ -124,7 +247,19 @@ const store = new Vuex.Store({
commit('removeUecToken')
commit('removeToken')
commit('removeUserInfo')
commit('removeSupplierInfo')
commit('removeCompanyInfo')
},
addSearchHistory({ commit, state }, searchHistory) {
const arr = [...state.searchHistory] // 单层数组直接解构
let index = arr.findIndex((item) => item === searchHistory)
if (index > -1) {
arr.splice(index, 1)
}
arr.unshift(searchHistory)
if (arr.length > 10) {
arr = arr.slice(0, 10)
}
commit('setSearchHistory', arr)
}
}
})

44
uni_modules/uni-popup/changelog.md

@ -0,0 +1,44 @@
## 1.7.2(2021-11-26)
- 优化 组件示例
## 1.7.1(2021-11-26)
- 修复 vuedoc 文字错误
## 1.7.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
## 1.6.2(2021-08-24)
- 新增 支持国际化
## 1.6.1(2021-07-30)
- 优化 vue3下事件警告的问题
## 1.6.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.5.0(2021-06-23)
- 新增 mask-click 遮罩层点击事件
## 1.4.5(2021-06-22)
- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
## 1.4.4(2021-06-18)
- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
## 1.4.3(2021-06-08)
- 修复 错误的 watch 字段
- 修复 safeArea 属性不生效的问题
- 修复 点击内容,再点击遮罩无法关闭的Bug
## 1.4.2(2021-05-12)
- 新增 组件示例地址
## 1.4.1(2021-04-29)
- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
## 1.4.0 (2021-04-29)
- 新增 type 属性的 left\right 值,支持左右弹出
- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
- 新增 safeArea 属性,是否适配底部安全区
- 修复 App\h5\微信小程序底部安全区占位不对的Bug
- 修复 App 端弹出等待的Bug
- 优化 提升低配设备性能,优化动画卡顿问题
- 优化 更简单的组件自定义方式
## 1.2.9(2021-02-05)
- 优化 组件引用关系,通过uni_modules引用组件
## 1.2.8(2021-02-05)
- 调整为uni_modules目录规范
## 1.2.7(2021-02-05)
- 调整为uni_modules目录规范
- 新增 支持 PC 端
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端

45
uni_modules/uni-popup/components/uni-popup-dialog/keypress.js

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

263
uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue

@ -0,0 +1,263 @@
<template>
<view class="uni-popup-dialog">
<view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
</view>
<view v-if="mode === 'base'" class="uni-dialog-content">
<slot>
<text class="uni-dialog-content-text">{{content}}</text>
</slot>
</view>
<view v-else class="uni-dialog-content">
<slot>
<input class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholderText" :focus="focus" >
</slot>
</view>
<view class="uni-dialog-button-group">
<view class="uni-dialog-button" @click="closeDialog">
<text class="uni-dialog-button-text">{{cancelText}}</text>
</view>
<view class="uni-dialog-button uni-border-left" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
</view>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
/**
* PopUp 弹出层-对话框样式
* @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} value input 模式下的默认值
* @property {String} placeholder input 模式下输入提示
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} mode = [base|input] 模式
* @value base 基础对话框
* @value input 可输入对话框
* @property {String} content 对话框内容
* @property {Boolean} beforeClose 是否拦截取消事件
* @event {Function} confirm 点击确认按钮触发
* @event {Function} close 点击取消按钮触发
*/
export default {
name: "uniPopupDialog",
mixins: [popup],
emits:['confirm','close'],
props: {
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'error'
},
mode: {
type: String,
default: 'base'
},
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
dialogType: 'error',
focus: false,
val: ""
}
},
computed: {
okText() {
return t("uni-popup.ok")
},
cancelText() {
return t("uni-popup.cancel")
},
placeholderText() {
return this.placeholder || t("uni-popup.placeholder")
},
titleText() {
return this.title || t("uni-popup.title")
}
},
watch: {
type(val) {
this.dialogType = val
},
mode(val) {
if (val === 'input') {
this.dialogType = 'info'
}
},
value(val) {
this.val = val
}
},
created() {
//
this.popup.disableMask()
// this.popup.closeMask()
if (this.mode === 'input') {
this.dialogType = 'info'
this.val = this.value
} else {
this.dialogType = this.type
}
},
mounted() {
this.focus = true
},
methods: {
/**
* 点击确认按钮
*/
onOk() {
if (this.mode === 'input'){
this.$emit('confirm', this.val)
}else{
this.$emit('confirm')
}
if(this.beforeClose) return
this.popup.close()
},
/**
* 点击取消按钮
*/
closeDialog() {
this.$emit('close')
if(this.beforeClose) return
this.popup.close()
},
close(){
this.popup.close()
}
}
}
</script>
<style lang="scss" scoped>
.uni-popup-dialog {
width: 300px;
border-radius: 11px;
background-color: #fff;
}
.uni-dialog-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 25px;
}
.uni-dialog-title-text {
font-size: 16px;
font-weight: 500;
}
.uni-dialog-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
padding: 20px;
}
.uni-dialog-content-text {
font-size: 14px;
color: #6C6C6C;
}
.uni-dialog-button-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
border-top-color: #f5f5f5;
border-top-style: solid;
border-top-width: 1px;
}
.uni-dialog-button {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
}
.uni-border-left {
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}
.uni-dialog-button-text {
font-size: 16px;
color: #333;
}
.uni-button-color {
color: #007aff;
}
.uni-dialog-input {
flex: 1;
font-size: 14px;
border: 1px #eee solid;
height: 40px;
padding: 0 10px;
border-radius: 5px;
color: #555;
}
.uni-popup__success {
color: #4cd964;
}
.uni-popup__warn {
color: #f0ad4e;
}
.uni-popup__error {
color: #dd524d;
}
.uni-popup__info {
color: #909399;
}
</style>

143
uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue

@ -0,0 +1,143 @@
<template>
<view class="uni-popup-message">
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
<slot>
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
</slot>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
/**
* PopUp 弹出层-消息提示
* @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} message 消息提示文字
* @property {String} duration 显示时间设置为 0 则不会自动关闭
*/
export default {
name: 'uniPopupMessage',
mixins:[popup],
props: {
/**
* 主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'success'
},
/**
* 消息文字
*/
message: {
type: String,
default: ''
},
/**
* 显示时间设置为 0 则不会自动关闭
*/
duration: {
type: Number,
default: 3000
},
maskShow:{
type:Boolean,
default:false
}
},
data() {
return {}
},
created() {
this.popup.maskShow = this.maskShow
this.popup.messageChild = this
},
methods: {
timerClose(){
if(this.duration === 0) return
clearTimeout(this.timer)
this.timer = setTimeout(()=>{
this.popup.close()
},this.duration)
}
}
}
</script>
<style lang="scss" scoped>
.uni-popup-message {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
}
.uni-popup-message__box {
background-color: #e1f3d8;
padding: 10px 15px;
border-color: #eee;
border-style: solid;
border-width: 1px;
flex: 1;
}
@media screen and (min-width: 500px) {
.fixforpc-width {
margin-top: 20px;
border-radius: 4px;
flex: none;
min-width: 380px;
/* #ifndef APP-NVUE */
max-width: 50%;
/* #endif */
/* #ifdef APP-NVUE */
max-width: 500px;
/* #endif */
}
}
.uni-popup-message-text {
font-size: 14px;
padding: 0;
}
.uni-popup__success {
background-color: #e1f3d8;
}
.uni-popup__success-text {
color: #67C23A;
}
.uni-popup__warn {
background-color: #faecd8;
}
.uni-popup__warn-text {
color: #E6A23C;
}
.uni-popup__error {
background-color: #fde2e2;
}
.uni-popup__error-text {
color: #F56C6C;
}
.uni-popup__info {
background-color: #F2F6FC;
}
.uni-popup__info-text {
color: #909399;
}
</style>

187
uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue

@ -0,0 +1,187 @@
<template>
<view class="uni-popup-share">
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{item.text}}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">{{cancelText}}</button>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
export default {
name: 'UniPopupShare',
mixins:[popup],
emits:['select'],
props: {
title: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
bottomData: [{
text: '微信',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
name: 'wx'
},
{
text: '支付宝',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
name: 'wx'
},
{
text: 'QQ',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
name: 'qq'
},
{
text: '新浪',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
name: 'sina'
},
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
// name: 'copy'
// },
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
// name: 'more'
// }
]
}
},
created() {},
computed: {
cancelText() {
return t("uni-popup.cancel")
},
shareTitleText() {
return this.title || t("uni-popup.shareTitle")
}
},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit('select', {
item,
index
})
this.close()
},
/**
* 关闭窗口
*/
close() {
if(this.beforeClose) return
this.popup.close()
}
}
}
</script>
<style lang="scss" scoped>
.uni-popup-share {
background-color: #fff;
border-top-left-radius: 11px;
border-top-right-radius: 11px;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 90px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 30px;
height: 30px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3B4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>

7
uni_modules/uni-popup/components/uni-popup/i18n/en.json

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "cancel",
"uni-popup.ok": "ok",
"uni-popup.placeholder": "pleace enter",
"uni-popup.title": "Hint",
"uni-popup.shareTitle": "Share to"
}

8
uni_modules/uni-popup/components/uni-popup/i18n/index.js

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

7
uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "确定",
"uni-popup.placeholder": "请输入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

7
uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "確定",
"uni-popup.placeholder": "請輸入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

45
uni_modules/uni-popup/components/uni-popup/keypress.js

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif

26
uni_modules/uni-popup/components/uni-popup/popup.js

@ -0,0 +1,26 @@
export default {
data() {
return {
}
},
created(){
this.popup = this.getParent()
},
methods:{
/**
* 获取父元素实例
*/
getParent(name = 'uniPopup') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
}
}

403
uni_modules/uni-popup/components/uni-popup/uni-popup.vue

@ -0,0 +1,403 @@
<template>
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']" @touchmove.stop.prevent="clear">
<view @touchstart="touchstart" >
<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass" :duration="duration" :show="showTrans" @click="onTap" />
<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap">
<view class="uni-popup__wrapper" :style="{ backgroundColor: bg }" :class="[popupstyle]" @click="clear"><slot /></view>
</uni-transition>
</view>
<!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" />
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress.js'
// #endif
/**
* PopUp 弹出层
* @description 弹出层组件为了解决遮罩弹层的问题
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value left 左侧弹出
* @value right 右侧弹出
* @value message 消息提示
* @value dialog 对话框
* @value share 底部分享示例
* @property {Boolean} animation = [true|false] 是否开启动画
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗
* @property {String} backgroundColor 主窗口背景色
* @property {Boolean} safeArea 是否适配底部安全区
* @event {Function} change 打开关闭弹窗触发e={show: false}
* @event {Function} maskClick 点击遮罩触发
*/
export default {
name: 'uniPopup',
components: {
// #ifdef H5
keypress
// #endif
},
emits:['change','maskClick'],
props: {
//
animation: {
type: Boolean,
default: true
},
// top: bottomcenter
// message: ; dialog :
type: {
type: String,
default: 'center'
},
// maskClick
maskClick: {
type: Boolean,
default: true
},
backgroundColor: {
type: String,
default: 'none'
},
safeArea:{
type: Boolean,
default: true
}
},
watch: {
/**
* 监听type类型
*/
type: {
handler: function(type) {
if (!this.config[type]) return
this[this.config[type]](true)
},
immediate: true
},
isDesktop: {
handler: function(newVal) {
if (!this.config[newVal]) return
this[this.config[this.type]](true)
},
immediate: true
},
/**
* 监听遮罩是否可点击
* @param {Object} val
*/
maskClick: {
handler: function(val) {
this.mkclick = val
},
immediate: true
}
},
data() {
return {
duration: 300,
ani: [],
showPopup: false,
showTrans: false,
popupWidth: 0,
popupHeight: 0,
config: {
top: 'top',
bottom: 'bottom',
center: 'center',
left: 'left',
right: 'right',
message: 'top',
dialog: 'center',
share: 'bottom'
},
maskClass: {
position: 'fixed',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)'
},
transClass: {
position: 'fixed',
left: 0,
right: 0
},
maskShow: true,
mkclick: true,
popupstyle: this.isDesktop ? 'fixforpc-top' : 'top'
}
},
computed: {
isDesktop() {
return this.popupWidth >= 500 && this.popupHeight >= 500
},
bg() {
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
return 'transparent'
}
return this.backgroundColor
}
},
mounted() {
const fixSize = () => {
const { windowWidth, windowHeight, windowTop, safeAreaInsets } = uni.getSystemInfoSync()
this.popupWidth = windowWidth
this.popupHeight = windowHeight + windowTop
//
if(this.safeArea){
this.safeAreaInsets = safeAreaInsets
}else{
this.safeAreaInsets = 0
}
}
fixSize()
// #ifdef H5
// window.addEventListener('resize', fixSize)
// this.$once('hook:beforeDestroy', () => {
// window.removeEventListener('resize', fixSize)
// })
// #endif
},
created() {
this.mkclick = this.maskClick
if (this.animation) {
this.duration = 300
} else {
this.duration = 0
}
// TODO message
this.messageChild = null
// TODO
this.clearPropagation = false
},
methods: {
/**
* 公用方法不显示遮罩层
*/
closeMask() {
this.maskShow = false
},
/**
* 公用方法遮罩层禁止点击
*/
disableMask() {
this.mkclick = false
},
// TODO nvue
clear(e) {
// #ifndef APP-NVUE
e.stopPropagation()
// #endif
this.clearPropagation = true
},
open(direction) {
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
if (!(direction && innerType.indexOf(direction) !== -1)) {
direction = this.type
}
if (!this.config[direction]) {
console.error('缺少类型:', direction)
return
}
this[this.config[direction]]()
this.$emit('change', {
show: true,
type: direction
})
},
close(type) {
this.showTrans = false
this.$emit('change', {
show: false,
type: this.type
})
clearTimeout(this.timer)
// //
// this.customOpen && this.customClose()
this.timer = setTimeout(() => {
this.showPopup = false
}, 300)
},
// TODO
touchstart(){
this.clearPropagation = false
},
onTap() {
if (this.clearPropagation) {
// fix by mehaotian nvue
this.clearPropagation = false
return
}
this.$emit('maskClick')
if (!this.mkclick) return
this.close()
},
/**
* 顶部弹出样式处理
*/
top(type) {
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
this.ani = ['slide-top']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
backgroundColor: this.bg
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
this.$nextTick(() => {
if (this.messageChild && this.type === 'message') {
this.messageChild.timerClose()
}
})
},
/**
* 底部弹出样式处理
*/
bottom(type) {
this.popupstyle = 'bottom'
this.ani = ['slide-bottom']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
paddingBottom: (this.safeAreaInsets && this.safeAreaInsets.bottom) || 0,
backgroundColor: this.bg
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
/**
* 中间弹出样式处理
*/
center(type) {
this.popupstyle = 'center'
this.ani = ['zoom-out', 'fade']
this.transClass = {
position: 'fixed',
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center'
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
left(type) {
this.popupstyle = 'left'
this.ani = ['slide-left']
this.transClass = {
position: 'fixed',
left: 0,
bottom: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
right(type) {
this.popupstyle = 'right'
this.ani = ['slide-right']
this.transClass = {
position: 'fixed',
bottom: 0,
right: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
}
}
}
</script>
<style lang="scss" scoped>
.uni-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
&.top,
&.left,
&.right {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
.uni-popup__wrapper {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
/* iphonex 等安全区设置,底部安全区适配 */
/* #ifndef APP-NVUE */
// padding-bottom: constant(safe-area-inset-bottom);
// padding-bottom: env(safe-area-inset-bottom);
/* #endif */
&.left,
&.right {
/* #ifdef H5 */
padding-top: var(--window-top);
/* #endif */
/* #ifndef H5 */
padding-top: 0;
/* #endif */
flex: 1;
}
}
}
.fixforpc-z-index {
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.fixforpc-top {
top: 0;
}
</style>

90
uni_modules/uni-popup/package.json

@ -0,0 +1,90 @@
{
"id": "uni-popup",
"displayName": "uni-popup 弹出层",
"version": "1.7.2",
"description": " Popup 组件,提供常用的弹层",
"keywords": [
"uni-ui",
"弹出层",
"弹窗",
"popup",
"弹框"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-transition"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"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"
}
}
}
}
}

17
uni_modules/uni-popup/readme.md

@ -0,0 +1,17 @@
## Popup 弹出层
> **组件名:uni-popup**
> 代码块: `uPopup`
> 关联组件:`uni-transition`
弹出层组件,在应用中弹出一个消息提示窗口、提示框等
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839

20
uni_modules/uni-transition/changelog.md

@ -0,0 +1,20 @@
## 1.3.1(2021-11-23)
- 修复 init 方法初始化问题
## 1.3.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition)
## 1.2.1(2021-09-27)
- 修复 init 方法不生效的 Bug
## 1.2.0(2021-07-30)
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.1(2021-05-12)
- 新增 示例地址
- 修复 示例项目缺少组件的 Bug
## 1.1.0(2021-04-22)
- 新增 通过方法自定义动画
- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
- 优化 动画触发逻辑,使动画更流畅
- 优化 支持单独的动画类型
- 优化 文档示例
## 1.0.2(2021-02-05)
- 调整为 uni_modules 目录规范

128
uni_modules/uni-transition/components/uni-transition/createAnimation.js

@ -0,0 +1,128 @@
// const defaultOption = {
// duration: 300,
// timingFunction: 'linear',
// delay: 0,
// transformOrigin: '50% 50% 0'
// }
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
// #endif
class MPAnimation {
constructor(options, _this) {
this.options = options
this.animation = uni.createAnimation(options)
this.currentStepAnimates = {}
this.next = 0
this.$ = _this
}
_nvuePushAnimates(type, args) {
let aniObj = this.currentStepAnimates[this.next]
let styles = {}
if (!aniObj) {
styles = {
styles: {},
config: {}
}
} else {
styles = aniObj
}
if (animateTypes1.includes(type)) {
if (!styles.styles.transform) {
styles.styles.transform = ''
}
let unit = ''
if(type === 'rotate'){
unit = 'deg'
}
styles.styles.transform += `${type}(${args+unit}) `
} else {
styles.styles[type] = `${args}`
}
this.currentStepAnimates[this.next] = styles
}
_animateRun(styles = {}, config = {}) {
let ref = this.$.$refs['ani'].ref
if (!ref) return
return new Promise((resolve, reject) => {
nvueAnimation.transition(ref, {
styles,
...config
}, res => {
resolve()
})
})
}
_nvueNextAnimate(animates, step = 0, fn) {
let obj = animates[step]
if (obj) {
let {
styles,
config
} = obj
this._animateRun(styles, config).then(() => {
step += 1
this._nvueNextAnimate(animates, step, fn)
})
} else {
this.currentStepAnimates = {}
typeof fn === 'function' && fn()
this.isEnd = true
}
}
step(config = {}) {
// #ifndef APP-NVUE
this.animation.step(config)
// #endif
// #ifdef APP-NVUE
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
// #endif
return this
}
run(fn) {
// #ifndef APP-NVUE
this.$.animationData = this.animation.export()
this.$.timer = setTimeout(() => {
typeof fn === 'function' && fn()
}, this.$.durationTime)
// #endif
// #ifdef APP-NVUE
this.isEnd = false
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
if(!ref) return
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
this.next = 0
// #endif
}
}
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 = ['opacity', 'backgroundColor']
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
MPAnimation.prototype[type] = function(...args) {
// #ifndef APP-NVUE
this.animation[type](...args)
// #endif
// #ifdef APP-NVUE
this._nvuePushAnimates(type, args)
// #endif
return this
}
})
export function createAnimation(option, _this) {
if(!_this) return
clearTimeout(_this.timer)
return new MPAnimation(option, _this)
}

277
uni_modules/uni-transition/components/uni-transition/uni-transition.vue

@ -0,0 +1,277 @@
<template>
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
</template>
<script>
import { createAnimation } from './createAnimation'
/**
* Transition 过渡动画
* @description 简单过渡动画组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
* @value fade 渐隐渐出过渡
* @value slide-top 由上至下过渡
* @value slide-right 由右至左过渡
* @value slide-bottom 由下至上过渡
* @value slide-left 由左至右过渡
* @value zoom-in 由小到大过渡
* @value zoom-out 由大到小过渡
* @property {Number} duration 过渡动画持续时间
* @property {Object} styles 组件样式 css 样式注意带-连接符的属性需要使用小驼峰写法如`backgroundColor:red`
*/
export default {
name: 'uniTransition',
emits:['click','change'],
props: {
show: {
type: Boolean,
default: false
},
modeClass: {
type: [Array, String],
default() {
return 'fade'
}
},
duration: {
type: Number,
default: 300
},
styles: {
type: Object,
default() {
return {}
}
},
customClass:{
type: String,
default: ''
}
},
data() {
return {
isShow: false,
transform: '',
opacity: 1,
animationData: {},
durationTime: 300,
config: {}
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.open()
} else {
// close,
if (this.isShow) {
this.close()
}
}
},
immediate: true
}
},
computed: {
//
stylesObject() {
let styles = {
...this.styles,
'transition-duration': this.duration / 1000 + 's'
}
let transform = ''
for (let i in styles) {
let line = this.toLine(i)
transform += line + ':' + styles[i] + ';'
}
return transform
},
//
transformStyles() {
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
}
},
created() {
//
this.config = {
duration: this.duration,
timingFunction: 'ease',
transformOrigin: '50% 50%',
delay: 0
}
this.durationTime = this.duration
},
methods: {
/**
* ref 触发 初始化动画
*/
init(obj = {}) {
if (obj.duration) {
this.durationTime = obj.duration
}
this.animation = createAnimation(Object.assign(this.config, obj),this)
},
/**
* 点击组件触发回调
*/
onClick() {
this.$emit('click', {
detail: this.isShow
})
},
/**
* ref 触发 动画分组
* @param {Object} obj
*/
step(obj, config = {}) {
if (!this.animation) return
for (let i in obj) {
try {
if(typeof obj[i] === 'object'){
this.animation[i](...obj[i])
}else{
this.animation[i](obj[i])
}
} catch (e) {
console.error(`方法 ${i} 不存在`)
}
}
this.animation.step(config)
return this
},
/**
* ref 触发 执行动画
*/
run(fn) {
if (!this.animation) return
this.animation.run(fn)
},
//
open() {
clearTimeout(this.timer)
this.transform = ''
this.isShow = true
let { opacity, transform } = this.styleInit(false)
if (typeof opacity !== 'undefined') {
this.opacity = opacity
}
this.transform = transform
// nextTick wx
this.$nextTick(() => {
// TODO
this.timer = setTimeout(() => {
this.animation = createAnimation(this.config, this)
this.tranfromInit(false).step()
this.animation.run()
this.$emit('change', {
detail: this.isShow
})
}, 20)
})
},
//
close(type) {
if (!this.animation) return
this.tranfromInit(true)
.step()
.run(() => {
this.isShow = false
this.animationData = null
this.animation = null
let { opacity, transform } = this.styleInit(false)
this.opacity = opacity || 1
this.transform = transform
this.$emit('change', {
detail: this.isShow
})
})
},
//
styleInit(type) {
let styles = {
transform: ''
}
let buildStyle = (type, mode) => {
if (mode === 'fade') {
styles.opacity = this.animationType(type)[mode]
} else {
styles.transform += this.animationType(type)[mode] + ' '
}
}
if (typeof this.modeClass === 'string') {
buildStyle(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildStyle(type, mode)
})
}
return styles
},
//
tranfromInit(type) {
let buildTranfrom = (type, mode) => {
let aniNum = null
if (mode === 'fade') {
aniNum = type ? 0 : 1
} else {
aniNum = type ? '-100%' : '0'
if (mode === 'zoom-in') {
aniNum = type ? 0.8 : 1
}
if (mode === 'zoom-out') {
aniNum = type ? 1.2 : 1
}
if (mode === 'slide-right') {
aniNum = type ? '100%' : '0'
}
if (mode === 'slide-bottom') {
aniNum = type ? '100%' : '0'
}
}
this.animation[this.animationMode()[mode]](aniNum)
}
if (typeof this.modeClass === 'string') {
buildTranfrom(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildTranfrom(type, mode)
})
}
return this.animation
},
animationType(type) {
return {
fade: type ? 1 : 0,
'slide-top': `translateY(${type ? '0' : '-100%'})`,
'slide-right': `translateX(${type ? '0' : '100%'})`,
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
'slide-left': `translateX(${type ? '0' : '-100%'})`,
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
}
},
//
animationMode() {
return {
fade: 'opacity',
'slide-top': 'translateY',
'slide-right': 'translateX',
'slide-bottom': 'translateY',
'slide-left': 'translateX',
'zoom-in': 'scale',
'zoom-out': 'scale'
}
},
// 线
toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
}
}
}
</script>
<style></style>

87
uni_modules/uni-transition/package.json

@ -0,0 +1,87 @@
{
"id": "uni-transition",
"displayName": "uni-transition 过渡动画",
"version": "1.3.1",
"description": "元素的简单过渡动画",
"keywords": [
"uni-ui",
"uniui",
"动画",
"过渡",
"过渡动画"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"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"
}
}
}
}
}

11
uni_modules/uni-transition/readme.md

@ -0,0 +1,11 @@
## Transition 过渡动画
> **组件名:uni-transition**
> 代码块: `uTransition`
元素过渡动画
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839

52
utils/hook.js

@ -1,4 +1,5 @@
import store from '@/store/index'
import { uploadUrl } from '@/enums/index.js'
// 框架方法封装
const tabList = ['store', 'cart', 'mine']
@ -23,7 +24,7 @@ export function back() {
delta: 1
})
} else {
go2('client')
go2('store')
}
}
@ -80,3 +81,52 @@ export function loginGo2(url, data = {}, isRedirect) {
go2('login')
}
}
/**
* 退出登录并跳转到登录页面
* @return {null}
*/
export function exit() {
store.dispatch('logout')
go2('login')
}
/**
* 文件上传
* @param {array} sourceType 上传的方式 album:相册 camera:相机
* @return {Promise} 以数组的形式返回对应的文件地址
*/
export function uploadImage(sourceType = ['album', 'camera']) {
return new Promise((resolve, reject) => {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: sourceType,
success: (res) => {
const tempFilePaths = res.tempFilePaths
uni.uploadFile({
url: uploadUrl.image,
filePath: tempFilePaths[0],
name: 'image',
fileType: 'image',
success: (res) => {
let result = JSON.parse(res.data)
if (result.data) {
resolve(result.data)
} else {
reject(result.message)
}
},
fail: (err) => {
console.error('uploadFile error:', err)
reject(err)
}
})
},
fail: (err) => {
console.error('chooseImage error:', err)
reject(err)
}
})
})
}

11
utils/http/http.js

@ -1,6 +1,7 @@
import { isFunction } from '../is.js'
const urlEnv = process.env.NODE_ENV === 'production' ? '' : '-test'
const uplaodUrl = `https://api-ops-yyt${urlEnv}.qniao.cn/cloud-print-user-center/utils/uploadImage`
import env from '@/env/index.js'
const urlEnv = env === 'production' ? '' : `-${env}`
const uploadUrl = `https://api-ops-yyt${urlEnv}.qniao.cn/cloud-print-user-center/utils/uploadImage`
export default class Http {
constructor(
config = {},
@ -68,14 +69,14 @@ export default class Http {
}).catch((err) => {
// 吃掉请求产生的异常
// 后期可以记录
console.log('native response error', err)
console.error('native response error', err)
})
}
// config:{}
uploadFile(config, options) {
return new Promise((resolve, rejetc) => {
let conf = Object.assign({}, config)
conf.url = uplaodUrl
conf.url = uploadUrl
const { reqInterceptor } = this
let opt = Object.assign({}, this.requestOption, options)
if (reqInterceptor && isFunction(reqInterceptor)) {
@ -103,7 +104,7 @@ export default class Http {
}
})
}).catch((err) => {
console.log('upload native err', err)
console.error('upload native err', err)
})
}
}

8
utils/http/index.js

@ -1,8 +1,9 @@
import Http from './http.js'
import env from '@/env/index.js'
// 请求封装文件
const urlEnv = process.env.NODE_ENV === 'production' ? '' : '-test'
const xappid = '503258978847966403'
const urlEnv = env === 'production' ? '' : `-${env}`
const xappid = '503258978847966404'
// 请求url列表
const prefixList = {
'/yyt-uec': `https://api-client-yyt${urlEnv}.qniao.cn`,
@ -79,7 +80,8 @@ const resInterceptor = (response, options) => {
const res = response.data
if (statusCode >= 200 && statusCode < 300) {
if (res.code == 0) {
return res.data
// 将成功的null置位1
return res.data || 1
} else {
uni.showToast({
title: res.message,

114
utils/index.js

@ -0,0 +1,114 @@
import env from '@/env/index.js'
import store from '@/store/index.js'
/**
* 日期格式化样例 yyyy-mm-dd hh:MM:ss
* @param date Date 需要转换的日期
* @param fmt string 转化的格式 yyyy-mm-dd hh:MM:ss
*/
export const dateTimeFormat = (date, fmt) => {
if (!date) {
throw new Error('日期不正确')
}
let ret
const opt = {
'y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月
'd+': date.getDate().toString(), // 日
'h+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
's+': date.getSeconds().toString() // 秒
}
for (let k in opt) {
ret = new RegExp('(' + k + ')').exec(fmt)
if (ret) {
fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'))
}
}
return fmt
}
/**
* 创建websocket
* @param {*} data
* @value {string} pageInfo 页面信息
* @value {true} retry 是否重连默认false
* @returns 一个websocket实例
*/
export const makeSocket = async ({ pageInfo = '', retry = false }) => {
const socket = {
sockTask: null,
close: function () {
this.sockTask.close({ code: 1000 })
closeFlag = true
},
onMessage: function () {}
}
let limitedNum = 0
let closeFlag = false
let timer = null
async function createSocket() {
let url = ''
// if (env == 'production') {
// url = 'wss://api-client-yyt.qniao.cn/qn-websocket-service/wechatwebsock?token='
// } else if (env == 'test') {
// url = 'wss://api-client-yyt-test.qniao.cn/qn-websocket-service/wechatwebsock?token='
// }
if (env == 'production') {
url = 'wss://api-client-yyt.qniao.cn/trading-center/wechatwebsock?token='
} else if (env == 'test') {
url = 'wss://api-client-yyt-test.qniao.cn/trading-center/wechatwebsock?token='
}
const token = store.state.qnToken
const socketTask = await uni.connectSocket({
url: `${url}${token}`,
header: {
'content-type': 'application/json'
},
success: () => {
console.log('websocket连接成功')
},
fail: () => {
console.log('websocket连接失败')
}
})
socketTask.onOpen(() => {
console.log(pageInfo + ' onOpen')
timer = setInterval(() => {
socketTask.send({ data: 'ping' })
}, 10000)
})
socketTask.onClose(() => {
console.log(pageInfo + ' onClose')
clearInterval(timer)
timer = null
if (!closeFlag && retry && limitedNum < 20) {
limitedNum++
console.log('重连次数:' + limitedNum)
createSocket()
}
})
socketTask.onError(() => {
console.log(pageInfo + ' error')
})
socket.sockTask = socketTask
}
await createSocket()
if (socket) {
socket.onMessage = (fn) => {
socket.sockTask.onMessage((res) => {
let data = JSON.parse(res.data)
console.log(pageInfo + '接收到消息:', data)
if (data.type != 'heartbeat') {
fn(data)
}
})
}
}
return socket
}

12
utils/is.js

@ -5,5 +5,13 @@ export function is(val, type) {
}
export function isFunction(val) {
return is(val,'Function')
}
return is(val, 'Function')
}
export function isObject(val) {
return is(val, 'Object')
}
export function isArray(val) {
return is(val, 'Array')
}
Loading…
Cancel
Save