This commit is contained in:
caoqianming 2021-05-25 20:26:26 +08:00
commit 398fa371ed
87 changed files with 2972 additions and 29 deletions

View File

@ -31,7 +31,11 @@
"pages/qtest/form",
"pages/main/start",
"pages/exam/index",
"pages/exam/note"
"pages/exam/note",
"pages/admin/index",
"pages/admin/login",
"pages/admin/exam/add",
"pages/admin/exam/add2"
],
"window": {
"backgroundTextStyle": "light",
@ -88,7 +92,7 @@
},
"plugins": {
"tencentvideo": {
"version": "1.3.15",
"version": "1.3.31",
"provider": "wxa75efa648b60994b"
}
},

View File

@ -0,0 +1,566 @@
Component({
/**
* 组件的属性列表
*/
properties: {
pickerShow: {
type: Boolean,
observer:function(val){ //弹出动画
// console.log(this.data);
if(val){
let animation = wx.createAnimation({
duration: 500,
timingFunction: "ease"
});
let animationOpacity = wx.createAnimation({
duration: 500,
timingFunction: "ease"
});
setTimeout(() => {
animation.bottom(0).step();
animationOpacity.opacity(0.7).step();
this.setData({
animationOpacity: animationOpacity.export(),
animationData: animation.export()
})
}, 0);
}else{
let animation = wx.createAnimation({
duration: 100,
timingFunction: "ease"
});
let animationOpacity = wx.createAnimation({
duration: 500,
timingFunction: "ease"
});
animation.bottom(-320).step();
animationOpacity.opacity(0).step();
this.setData({
animationOpacity: animationOpacity.export(),
animationData: animation.export()
});
}
// 在picker滚动未停止前点确定会使startValue数组各项归零发生错误这里判断并重新初始化
// 微信新增了picker滚动的回调函数已进行兼容
if(this.data.startValue&&this.data.endValue){
let s = 0, e = 0;
let conf = this.data.config;
this.data.startValue.map(val => {
if (val == 0) {
s++
}
})
this.data.endValue.map(val => {
if (val == 0) {
e++;
}
});
let tmp={
hour:4,
minute:5,
second:6
}
let n = tmp[conf.column];
if (s>=n || e>=n) {
this.initPick(this.data.config);
this.setData({
startValue: this.data.startValue,
endValue: this.data.endValue,
});
}
}
}
},
config: Object
},
/**
* 组件的初始数据
*/
data: {
// pickerShow:true
// limitStartTime: new Date().getTime()-1000*60*60*24*30,
// limitEndTime: new Date().getTime(),
// yearStart:2000,
// yearEnd:2100
},
detached: function() {
console.log("dele");
},
attached: function() {},
ready: function() {
this.readConfig();
this.initPick(this.data.config || null);
this.setData({
startValue: this.data.startValue,
endValue: this.data.endValue,
});
},
/**
* 组件的方法列表
*/
methods: {
//阻止滑动事件
onCatchTouchMove(e) {
},
//读取配置项
readConfig() {
let limitEndTime = new Date().getTime();
let limitStartTime = new Date().getTime() - 1000 * 60 * 60 * 24 * 30;
if (this.data.config) {
let conf = this.data.config;
if (typeof conf.dateLimit == "number") {
limitStartTime =
new Date().getTime() - 1000 * 60 * 60 * 24 * conf.dateLimit;
}
if(conf.limitStartTime){
limitStartTime = new Date(conf.limitStartTime.replace(/-/g,'/')).getTime();
}
if (conf.limitEndTime) {
limitEndTime = new Date(conf.limitEndTime.replace(/-/g, '/')).getTime();
}
this.setData({
yearStart: conf.yearStart || 2000,
yearEnd: conf.yearEnd || 2100,
endDate: conf.endDate || false,
dateLimit: conf.dateLimit || false,
hourColumn:
conf.column == "hour" ||
conf.column == "minute" ||
conf.column == "second",
minColumn: conf.column == "minute" || conf.column == "second",
secColumn: conf.column == "second"
});
}
let limitStartTimeArr = formatTime(limitStartTime);
let limitEndTimeArr = formatTime(limitEndTime);
this.setData({
limitStartTime,
limitStartTimeArr,
limitEndTime,
limitEndTimeArr
});
},
//滚动开始
handlePickStart:function(e){
this.setData({
isPicking:true
})
},
//滚动结束
handlePickEnd:function(e){
this.setData({
isPicking:false
})
},
onConfirm: function() {
//滚动未结束时不能确认
if(this.data.isPicking){return}
let startTime = new Date(this.data.startPickTime.replace(/-/g, "/"));
let endTime = new Date(this.data.endPickTime.replace(/-/g, "/"));
if (startTime <= endTime || !this.data.endDate) {
this.setData({
startTime,
endTime
});
let startArr = formatTime(startTime).arr;
let endArr = formatTime(endTime).arr;
let format0 = function(num){
return num<10?'0'+num:num
}
let startTimeBack =
startArr[0] +
"-" +
format0(startArr[1]) +
"-" +
format0(startArr[2]) +
" " +
(this.data.hourColumn ? format0(startArr[3]) : "00") +
":" +
(this.data.minColumn ? format0(startArr[4]) : "00") +
":" +
(this.data.secColumn ? format0(startArr[5]) : "00");
let endTimeBack =
endArr[0] +
"-" +
format0(endArr[1]) +
"-" +
format0(endArr[2]) +
" " +
(this.data.hourColumn ? format0(endArr[3]) : "00") +
":" +
(this.data.minColumn ? format0(endArr[4]) : "00") +
":" +
(this.data.secColumn ? format0(endArr[5]) : "00");
let time = {
startTime: startTimeBack,
endTime: endTimeBack
};
//触发自定义事件
this.triggerEvent("setPickerTime", time);
this.triggerEvent("hidePicker", {});
} else {
wx.showToast({
icon: "none",
title: "时间不合理"
});
}
},
hideModal: function() {
this.triggerEvent("hidePicker", {});
},
changeStartDateTime: function(e) {
let val = e.detail.value;
this.compareTime(val, "start");
},
changeEndDateTime: function(e) {
let val = e.detail.value;
this.compareTime(val, "end");
},
//比较时间是否在范围内
compareTime(val, type) {
let h = val[3] ? this.data.HourList[val[3]] : "00";
let m = val[4] ? this.data.MinuteList[val[4]] : "00";
let s = val[5] ? this.data.SecondList[val[5]] : "00";
let time =
this.data.YearList[val[0]] +
"-" +
this.data.MonthList[val[1]] +
"-" +
this.data.DayList[val[2]] +
" " +
h +
":" +
m +
":" +
s;
let start = this.data.limitStartTime;
let end = this.data.limitEndTime;
let timeNum = new Date(time.replace(/-/g, '/')).getTime();
let year, month, day, hour, min, sec, limitDate;
let tempArr = []
if (!this.data.dateLimit){
limitDate = [
this.data.YearList[val[0]],
this.data.MonthList[val[1]],
this.data.DayList[val[2]],
this.data.HourList[val[3]],
this.data.MinuteList[val[4]],
this.data.SecondList[val[5]]]
} else if (type == "start" && timeNum > new Date(this.data.endPickTime.replace(/-/g, '/')) && this.data.config.endDate) {
limitDate = formatTime(this.data.endPickTime).arr;
} else if (type == "end" && timeNum < new Date(this.data.startPickTime.replace(/-/g, '/'))) {
limitDate = formatTime(this.data.startPickTime).arr;
} else if (timeNum < start) {
limitDate = this.data.limitStartTimeArr.arr;
} else if (timeNum > end) {
limitDate = this.data.limitEndTimeArr.arr;
} else {
limitDate = [
this.data.YearList[val[0]],
this.data.MonthList[val[1]],
this.data.DayList[val[2]],
this.data.HourList[val[3]],
this.data.MinuteList[val[4]],
this.data.SecondList[val[5]]
]
}
year = limitDate[0];
month = limitDate[1];
day = limitDate[2];
hour = limitDate[3];
min = limitDate[4];
sec = limitDate[5];
if (type == "start") {
this.setStartDate(year, month, day, hour, min, sec);
} else if (type == "end") {
this.setEndDate(year, month, day, hour, min, sec);
}
},
getDays: function(year, month) {
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (month === 2) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
? 29
: 28;
} else {
return daysInMonth[month - 1];
}
},
initPick: function(initData) {
const date = initData.initStartTime ? new Date(initData.initStartTime.replace(/-/g, '/')): new Date();
const endDate = initData.initEndTime ? new Date(initData.initEndTime.replace(/-/g, '/')) : new Date();
// const startDate = new Date(date.getTime() - 1000 * 60 * 60 * 24);
const startDate = date;
const startYear = date.getFullYear();
const startMonth = date.getMonth() + 1;
const startDay = date.getDate();
const startHour = date.getHours();
const startMinute = date.getMinutes();
const startSecond = date.getSeconds();
const endYear = endDate.getFullYear();
const endMonth = endDate.getMonth() + 1;
const endDay = endDate.getDate();
const endHour = endDate.getHours();
const endMinute = endDate.getMinutes();
const endSecond = endDate.getSeconds();
let YearList = [];
let MonthList = [];
let DayList = [];
let HourList = [];
let MinuteList = [];
let SecondList = [];
//设置年份列表
for (let i = this.data.yearStart; i <= this.data.yearEnd; i++) {
YearList.push(i);
}
// 设置月份列表
for (let i = 1; i <= 12; i++) {
MonthList.push(i);
}
// 设置日期列表
for (let i = 1; i <= 31; i++) {
DayList.push(i);
}
// 设置时列表
for (let i = 0; i <= 23; i++) {
if (0 <= i && i < 10) {
i = "0" + i;
}
HourList.push(i);
}
// 分|秒
for (let i = 0; i <= 59; i++) {
if (0 <= i && i < 10) {
i = "0" + i;
}
MinuteList.push(i);
SecondList.push(i);
}
this.setData({
YearList,
MonthList,
DayList,
HourList,
MinuteList,
SecondList
});
this.setStartDate(startYear, startMonth, startDay, startHour, startMinute, startSecond);
this.setEndDate(endYear, endMonth, endDay, endHour, endMinute, endSecond);
//!!!
// setTimeout(() => {
// this.setStartDate(nowYear, nowMonth, nowDay, nowHour, nowMinute)
// this.setEndDate(nowYear, nowMonth, nowDay, nowHour, nowMinute)
// }, 0);
},
setPickerDateArr(type, year, month, day, hour, minute, second) {
let yearIdx = 0;
let monthIdx = 0;
let dayIdx = 0;
let hourIdx = 0;
let minuteIdx = 0;
let secondIdx = 0;
this.data.YearList.map((v, idx) => {
if (parseInt(v) === year) {
yearIdx = idx;
}
});
this.data.MonthList.map((v, idx) => {
if (parseInt(v) === month) {
monthIdx = idx;
}
});
// 重新设置日期列表
let DayList = [];
for (let i = 1; i <= this.getDays(year, month); i++) {
DayList.push(i);
}
DayList.map((v, idx) => {
if (parseInt(v) === day) {
dayIdx = idx;
}
});
if (type == "start") {
this.setData({ startDayList: DayList });
} else if (type == "end") {
this.setData({ endDayList: DayList });
}
this.data.HourList.map((v, idx) => {
if (parseInt(v) === parseInt(hour)) {
hourIdx = idx;
}
});
this.data.MinuteList.map((v, idx) => {
if (parseInt(v) === parseInt(minute)) {
minuteIdx = idx;
}
});
this.data.SecondList.map((v, idx) => {
if (parseInt(v) === parseInt(second)) {
secondIdx = idx;
}
});
return {
yearIdx,
monthIdx,
dayIdx,
hourIdx,
minuteIdx,
secondIdx
};
},
setStartDate: function(year, month, day, hour, minute, second) {
let pickerDateArr = this.setPickerDateArr(
"start",
year,
month,
day,
hour,
minute,
second
);
this.setData({
startYearList: this.data.YearList,
startMonthList: this.data.MonthList,
// startDayList: this.data.DayList,
startHourList: this.data.HourList,
startMinuteList: this.data.MinuteList,
startSecondList: this.data.SecondList,
startValue: [
pickerDateArr.yearIdx,
pickerDateArr.monthIdx,
pickerDateArr.dayIdx,
pickerDateArr.hourIdx,
pickerDateArr.minuteIdx,
pickerDateArr.secondIdx
],
startPickTime:
this.data.YearList[pickerDateArr.yearIdx] +
"-" +
this.data.MonthList[pickerDateArr.monthIdx] +
"-" +
this.data.DayList[pickerDateArr.dayIdx] +
" " +
this.data.HourList[pickerDateArr.hourIdx] +
":" +
this.data.MinuteList[pickerDateArr.minuteIdx] +
":" +
this.data.SecondList[pickerDateArr.secondIdx]
});
},
setEndDate: function(year, month, day, hour, minute, second) {
let pickerDateArr = this.setPickerDateArr(
"end",
year,
month,
day,
hour,
minute,
second
);
this.setData({
endYearList: this.data.YearList,
endMonthList: this.data.MonthList,
// endDayList: this.data.DayList,
endHourList: this.data.HourList,
endMinuteList: this.data.MinuteList,
endSecondList: this.data.SecondList,
endValue: [
pickerDateArr.yearIdx,
pickerDateArr.monthIdx,
pickerDateArr.dayIdx,
pickerDateArr.hourIdx,
pickerDateArr.minuteIdx,
pickerDateArr.secondIdx
],
endPickTime:
this.data.YearList[pickerDateArr.yearIdx] +
"-" +
this.data.MonthList[pickerDateArr.monthIdx] +
"-" +
this.data.DayList[pickerDateArr.dayIdx] +
" " +
this.data.HourList[pickerDateArr.hourIdx] +
":" +
this.data.MinuteList[pickerDateArr.minuteIdx] +
":" +
this.data.SecondList[pickerDateArr.secondIdx]
});
},
}
});
function formatTime(date) {
if (typeof date == 'string' || 'number') {
try {
date = date.replace(/-/g, '/')//兼容ios
} catch (error) {
}
date = new Date(date)
}
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return {
str: [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':'),
arr: [year, month, day, hour, minute, second]
}
}
function formatNumber(n) {
n = n.toString()
return n[1] ? n : '0' + n
}

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,68 @@
<!--components/timePicker/timePicker.wxml-->
<!-- 自定义时间筛选器 -->
<view hidden="{{!pickerShow}}">
<view class="picker-container {{pickerShow?'show_picker':'hide_picker'}}" animation="{{animationData}}">
<view class="btn-box" catchtouchmove="onCatchTouchMove">
<view class="pick_btn" bindtap="hideModal">取消</view>
<view class='pick_btn' style="color: #19f" bindtap="onConfirm">确定</view>
</view>
<view>
<picker-view class='sensorTypePicker' indicator-style='height: 35px;' bindchange="changeStartDateTime"
value="{{startValue}}" style="height: {{endDate?'120px':'250px'}};" bindpickstart="handlePickStart" bindpickend="handlePickEnd">
<picker-view-column style="min-width: 70px;flex-shrink: 0">
<view class='picker-item' wx:for="{{startYearList}}" wx:key='*this'>{{item}}年</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{startMonthList}}" wx:key='*this'>{{item}}月</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{startDayList}}" wx:key='*this'>{{item}}日</view>
</picker-view-column>
<picker-view-column hidden="{{!hourColumn}}">
<view class='picker-item' wx:for="{{startHourList}}" wx:key='*this'>{{item}}时</view>
</picker-view-column>
<picker-view-column hidden="{{!minColumn}}">
<view class='picker-item' wx:for="{{startMinuteList}}" wx:key='*this'>{{item}}分</view>
</picker-view-column>
<picker-view-column hidden="{{!secColumn}}">
<view class='picker-item' wx:for="{{startSecondList}}" wx:key='*this'>{{item}}秒</view>
</picker-view-column>
</picker-view>
</view>
<view wx:if="{{endDate}}">
<view class='to' style='margin-top: 4px;margin-bottom: 4px;'>至</view>
<picker-view class='sensorTypePicker' indicator-style='height: 35px;' bindchange="changeEndDateTime" bindpickstart="handlePickStart" bindpickend="handlePickEnd"
value="{{endValue}}">
<picker-view-column style="min-width: 70px;flex-shrink: 0">
<view class='picker-item' wx:for="{{endYearList}}" wx:key='*this' style="min-width: 70px;">{{item}}年</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{endMonthList}}" wx:key='*this'>{{item}}月</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{endDayList}}" wx:key='*this'>{{item}}日</view>
</picker-view-column>
<picker-view-column hidden="{{!hourColumn}}" >
<view class='picker-item' wx:for="{{endHourList}}" wx:key='*this'>{{item}}时</view>
</picker-view-column>
<picker-view-column hidden="{{!minColumn}}">
<view class='picker-item' wx:for="{{endMinuteList}}" wx:key='*this'>{{item}}分</view>
</picker-view-column>
<picker-view-column hidden="{{!secColumn}}">
<view class='picker-item' wx:for="{{startSecondList}}" wx:key='*this'>{{item}}秒</view>
</picker-view-column>
</picker-view>
</view>
<!-- <view class='sure' bindtap="onConfirm">确定</view> -->
</view>
<!-- 遮罩 -->
<view class="sensorType-screen" bindtap="hideModal" catchtouchmove="onCatchTouchMove" animation="{{animationOpacity}}"/>
</view>

View File

@ -0,0 +1,96 @@
/* components/timePicker/timePicker.wxss */
.picker-item{
line-height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
}
/* 自定义时间 */
.picker-container {
display: flex;
flex-direction: column;
/* justify-content: center; */
align-items: center;
width: 100%;
overflow: hidden;
position: fixed;
bottom: -640rpx;
left: 0;
/* height: 0; */
transition: height 0.5s;
z-index: 2000;
background: white;
border-top: 1px solid #EFEFF4;
}
.sensorType-screen{
width: 100vw;
/* height:400rpx; */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #000;
opacity: 0;
overflow: hidden;
z-index: 1999;
color: #fff;
}
.sensorTypePicker{
width: 690rpx;
height: 240rpx;
/* padding: 45px 0; */
}
.picker-item{
line-height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
/* overflow: hidden; */
}
.box{
padding: 0 20rpx;
}
/* 至 */
.to{
width:100%;
display: flex;
justify-content: center;align-items: center;
color:rgb(138,138,138);
/* font-size:30rpx; */
}
/* 确定 */
.sure{
width:100%;
height:90rpx;
border-top: 2rpx solid #EFEFF4;
display: flex;justify-content: center;align-items: center;
color: rgb(36,123,255);
font-size:16px;
}
.btn-box{
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2rpx solid #eee;
}
.pick_btn{
padding: 14rpx 30rpx;
color: #ccc;
/* background-color: #159; */
}
.show_picker{
/* height: 320px; */
}
.hide_picker{
/* height: 0; */
}

View File

@ -0,0 +1,291 @@
// components/dynamicForm/index.js
import formatTime from './utils/formatTime';
Component({
/**
* 组件的属性列表
*/
properties: {
formData: Array,
showSubmitBtn: {
type: Boolean,
value: true
},
toSubmit: Number
},
//监听数据变化, 当toSubmit 值变化时, 代表父组件点击提交案例事件
observers: {
'toSubmit': function (e) {
if (e) {
this.formSubmit();
}
},
'formData': function () {
this.formInit();
}
},
/**
* 组件的初始数据
*/
data: {
pickerMap: {},
fileMap: {},
inputMap: {}
},
lifetimes: {
attached: function () {
this.formInit();
},
moved: function () { },
detached: function () { },
},
pageLifetimes: {
// 组件所在页面的生命周期函数
show: function () { },
hide: function () { },
resize: function () { },
},
/**
* 组件的方法列表
*/
methods: {
//表单初始化
formInit() {
const pickerMap = {}, fileMap = {}, inputMap = {}, dateMap = {};//存储各表单变化后的值,表单id为索引
const pickers = [], files = [], inputs = [], datePickers = [];
this.data.formData.forEach(val => {
switch (val.type) {
case 'picker':
pickers.push(val);
break;
case 'file':
files.push(val);
break;
case 'input':
case 'textarea':
inputs.push(val);
break;
case 'date':
datePickers.push(val);
break;
default:
break;
}
});
pickers.forEach(val => {
pickerMap[val.id] = {
original: val,
hasChoose: val.defaultIdx != 'undefined',
error:null,
idx: val.defaultIdx || 0
};
});
files.forEach(val => {
fileMap[val.id] = {
original: val,
error: null,
list: val.fileList
};
});
inputs.forEach(val => {
inputMap[val.id] = {
original: val,
value: val.defaultValue || '',
placeholder: val.placeholder,
error: null,
rules: val.rules ? val.rules.map(val => {
val.regular = new RegExp(val.regular);
return val;
}) : []
};
});
datePickers.forEach(val => {
dateMap[val.id] = {
original: val,
config: val.config,
completeTime: val.completeTime,
show: false,
hasChoose: !!val.config.initStartTime,
error: null,
startDate: val.config.initStartTime || formatTime(),
endDate: val.config.initEndTime || formatTime()
};
if (!val.completeTime){
dateMap[val.id].startDate = dateMap[val.id].startDate.split(' ')[0];
dateMap[val.id].endDate = dateMap[val.id].endDate.split(' ')[0];
}
});
this.setData({
pickers,
inputs,
datePickers,
files,
pickerMap,
inputMap,
fileMap,
dateMap
});
},
//提交表单
formSubmit() {
let formData = {};
const { pickerMap, inputMap, dateMap, fileMap } = this.data;
for (let i in this.data) { //获取表单数据后缀为Map
if (i.match(/Map$/)) {
formData = Object.assign({}, formData, this.data[i]);
}
}
let hasError = false;
for (let i in formData) {//循环验证所有表单数据规则
let info = formData[i];
if (info.original.type === 'input' || info.original.type === 'textarea') {
if (!info.value){
if (info.original.isRequired){
info.error = info.original.lable + '不可为空';
hasError = true;
}
} else if (info.rules){
for (let val of info.rules) {
if (!info.value.match(val.regular)) {
info.error = val.tips || '格式有误';
hasError = true;
break;
}
}
}
this.setData({
[`inputMap.${i}`]: info
});
} else if (info.original.type === 'file') {
if (info.list.length === 0 && info.original.isRequired) {
let error = '请选择文件';
if (info.original.accept === 'video') {
error = '请选择视频';
} else if (info.original.accept === 'image') {
error = '请选择图片';
}
info.error = error;
hasError = true;
this.setData({
[`fileMap.${i}`]: info
});
}
} else if (info.original.type === 'picker' || info.original.type === 'date'){
if (!info.hasChoose && info.original.isRequired){
info.error = '请选择' + info.original.lable;
hasError = true;
this.setData({
[`${info.original.type}Map.${i}`]: info
});
}
}
}
if (hasError) {
wx.showToast({
title: '表单填写有误',
icon: 'none'
});
return;
}
this.triggerEvent('dynamicFormSubmit', formData);
console.log(formData);
},
//更新数据劫持
updateData(key,val){
this.setData({
[key]: val
});
this.triggerEvent('dynamicFormChange', { key, val});
},
//显示选择器
datePickerShow(e) {
if (e.target.dataset.disabled) {
return;
}
this.setData({
[`dateMap.${e.target.dataset.id}.show`]: true
});
},
//隐藏时间选择器
datePickerHide(id) {
if (typeof id === 'object') {
id = id.target.id;
}
this.setData({
[`dateMap.${id}.show`]: false
});
},
//设置选择器时间
setPickerTime(e) {
const {dateMap} = this.data;
const { startTime, endTime } = e.detail;
const date = dateMap[e.target.id];
if (!date.hasChoose){
date.hasChoose = true;
date.error = null;
}
date.show = false;
date.startDate = date.completeTime ? startTime :startTime.split(' ')[0];
date.endDate = date.completeTime ?endTime :endTime.split(' ')[0];
this.updateData(`dateMap.${e.target.id}`,date);
},
//输入框
onInput(e) {
const { value } = e.detail;
const info = this.data.inputMap[e.target.id] || {};
if (!info) {
return;
}
info.value = e.detail.value;
info.error = null;
if (info.rules && info.value) {
for (let val of info.rules) {
if (!info.value.match(val.regular)) {
info.error = val.tips || '格式有误';
break;
}
}
}
this.updateData(`inputMap.${e.target.id}`, info);
},
//picker选择
onPickerChange(e) {
const { id } = e.target;
const picker = this.data.pickerMap[id];
if(!picker.hasChoose){
picker.hasChoose = true;
picker.error = null;
}
picker.idx = e.detail.value;
picker.data = this.data.pickers.filter(val => val.id === id)[0].range[e.detail.value];
this.updateData(`pickerMap.${e.target.id}`, picker);
},
// 选择文件
onFileRead(e) {
console.log(e);
for (let val of e.detail.file) {
const size = this.data.fileMap[e.target.id].original.maxSize;
if (val.size > size * 1024 * 1024) {
wx.showToast({
title: `请选择${size}MB以内的文件`,
icon: 'none'
});
return;
}
}
const files = this.data.fileMap[e.target.id];
files.error = null;
files.list = files.list.concat(e.detail.file);
this.updateData(`fileMap.${e.target.id}`, files);
},
//删除文件
onFileDelete(e) {
console.log(e);
const files = this.data.fileMap[e.target.id].list;
files.splice(e.detail.index, 1);
this.updateData(`fileMap.${e.target.id}.list`, files);
}
}
});

View File

@ -0,0 +1,8 @@
{
"component": true,
"usingComponents": {
"van-icon": "./vant/icon/index",
"van-uploader": "./vant/uploader/index",
"time-picker": "./components/timePicker/timePicker"
}
}

View File

@ -0,0 +1,72 @@
<!--components/dynamicForm/index.wxml-->
<view class="form-box">
<block wx:for="{{formData}}" wx:key="id">
<!-- input输入框 -->
<view class="form-row ipt-row " wx:if="{{item.type==='input'}}">
<view class="form-lable {{item.isRequired?'required':'' +''}}">{{item.lable}}</view>
<view style="width:100%">
<input wx:if="{{!item.disabled}}" class="field {{item.disabled?'disabled':''}}"
type="{{inputMap[item.id].original.inputType || 'text'}}" maxlength="{{item.maxLength || -1}}"
bindinput="onInput" id="{{item.id}}" value="{{item.defaultValue || ''}}"
disabled="{{item.disabled}}" placeholder="{{inputMap[item.id].placeholder || '请填写内容'}}" />
<view class="field disabled" wx:else>{{item.defaultValue || ''}}</view>
<view class="error-info" wx:if="{{inputMap[item.id].error}}">{{inputMap[item.id].error}}</view>
</view>
</view>
<!-- picker选择器 -->
<view class="form-row flex-start" wx:elif="{{item.type==='picker'}}">
<view class="form-lable {{item.isRequired?'required':''}}">{{item.lable}}</view>
<view style="width:100%">
<view wx:if="{{!item.disabled}}" class="picker-row {{item.disabled?'disabled':''}}">
<picker class="field" range="{{item.range}}" disabled="{{item.disabled}}" id="{{item.id}}" value="{{pickerMap[item.id].idx}}"
range-key="name" bindchange="onPickerChange">
{{pickerMap[item.id].hasChoose?item.range[pickerMap[item.id].idx].name:'请选择'}}
</picker>
<van-icon class="row-icon" name="arrow"></van-icon>
</view>
<view class="field disabled" wx:else>{{pickerMap[item.id].hasChoose?item.range[pickerMap[item.id].idx].name:''}}</view>
<view class="error-info" wx:if="{{pickerMap[item.id].error}}">{{pickerMap[item.id].error}}</view>
</view>
</view>
<!-- 日期选择器 -->
<view class="form-row flex-start " wx:elif="{{item.type==='date'}}">
<view class="form-lable {{item.isRequired?'required':''}}">{{item.lable}}</view>
<view style="width:100%">
<view class="picker-row">
<view class="field {{item.disabled?'disabled':''}}" bindtap="datePickerShow" data-disabled="{{item.disabled}}" data-id="{{item.id}}">{{dateMap[item.id].hasChoose?(dateMap[item.id].config.endDate?dateMap[item.id].startDate+' ~ ' + dateMap[item.id].endDate: dateMap[item.id].startDate):'请选择'}}</view>
<time-picker pickerShow="{{dateMap[item.id].show}}" id="{{item.id}}" wx:if="{{!isPickerRender}}" bind:hidePicker="datePickerHide"
bind:setPickerTime="setPickerTime" config="{{dateMap[item.id].original.config}}"></time-picker>
<van-icon class="row-icon" name="arrow"></van-icon>
</view>
<view class="error-info" wx:if="{{dateMap[item.id].error}}">{{dateMap[item.id].error}}</view>
</view>
</view>
<!-- 文本框 -->
<view class="textarea-box" wx:elif="{{item.type==='textarea'}}">
<view class="flex mb-24">
<view class="area-lable {{item.isRequired?'required':''}}">{{item.lable}}</view>
<view class="error-info" wx:if="{{inputMap[item.id].error}}">{{inputMap[item.id].error}}</view>
</view>
<view class="text-area {{item.disabled?'disabled':''}}">
<textarea style="width:100%" value="{{item.defaultValue || ''}}" disabled="{{item.disabled}}" placeholder="{{inputMap[item.id].placeholder || '请填写内容'}}"
id="{{item.id}}" bindinput="onInput" auto-height="{{true}}" maxlength="{{item.maxLength || -1}}" name=""
cols="30" rows="10"></textarea>
<view wx:if="{{item.maxLength}}" class="text-num">{{inputMap[item.id].value.length||0}}/{{item.maxLength}}
</view>
</view>
</view>
<!-- 文件上传 -->
<view class="img-box" wx:elif="{{item.type==='file'}}">
<!-- <view class="area-lable mb-24 {{item.isRequired?'required':''}}">{{item.lable}}</view> -->
<view class="flex mb-24">
<view class="area-lable {{item.isRequired?'required':''}}">{{item.lable}}</view>
<view class="error-info" wx:if="{{fileMap[item.id].error}}">{{fileMap[item.id].error}}</view>
</view>
<van-uploader file-list="{{ fileMap[item.id].list }}" preview-size="196rpx" max-count="{{fileMap[item.id].original.maxCount || 9}}"
disabled="{{fileMap[item.id].original.disabled || false}}" accept="{{fileMap[item.id].original.accept || 'image'}}" id="{{item.id}}" multiple image-fit="aspectFill"
bind:after-read="onFileRead" bind:delete="onFileDelete" />
</view>
</block>
</view>
<button wx:if="{{showSubmitBtn}}" class='form-btn' bindtap="formSubmit">提交</button>

View File

@ -0,0 +1,119 @@
/* components/dynamicForm/index.wxss */
.page{
background-color: #eeeeef;
padding-top: 1px;
padding-bottom: 1px;
}
.flex{
display: flex;
align-items: center;
}
.form-box{
padding: 0 30rpx 0px;
padding-right: 0;
margin: 30rpx;
box-sizing: border-box;
border-top: 1px solid #eee;
border-radius: 10rpx;
background-color: #fff;
}
.form-row{
position: relative;
display: flex;
align-items: center;
padding: 25rpx 0;
font-size: 30rpx;
border-bottom: 1px solid #eee;
}
.ipt-row{
align-items: flex-start;
}
.flex-start{
align-items: flex-start;
}
.picker-row{
display: flex;
justify-content: space-between;
align-items: center;
}
.form-row:last-child{
border-bottom: none;
}
.form-lable{
position: relative;
min-width: 120rpx;
max-width: 200rpx;
word-break: break-all;
padding-right: 30rpx;
flex-shrink: 0;
flex-grow: 0;
font-weight: 700;
}
.field{
width: 100%;
flex-grow: 1;
min-height: 45rpx;
padding-right: 30rpx;
box-sizing: border-box;
line-height: 45rpx;
}
.error-info{
font-size: 24rpx;
color: #f00;
}
.required::before{
position: absolute;
top: 50%;
left: -18rpx;
transform: translateY(-35%);
content: '*';
color: #f00;
font-size: 36rpx;
}
.form-btn{
margin: 60rpx auto;
width: 400rpx;
background-color: #17e;
color: #fff;
}
.row-icon{
padding-right: 20rpx;
}
.img-box{
padding-top: 25rpx;
/* padding-bottom: 25rpx; */
}
.mb-24{
margin-bottom: 24rpx;
}
.area-lable{
position: relative;
font-size: 30rpx;
margin-right: 20rpx;
font-weight: 700;
}
.textarea-box{
padding-top: 20rpx;
}
.text-area{
position: relative;
width: 630rpx;
padding: 20rpx;
padding-bottom: 60rpx;
font-size: 30rpx;
min-height: 200rpx;
box-sizing: border-box;
border-radius: 8rpx;
border: 1px solid #eee;
}
.text-num{
position: absolute;
right: 20rpx;
bottom: 10px;
text-align: right;
color: #999;
}
.disabled{
opacity: 0.3;
}

View File

@ -0,0 +1,29 @@
const formatTime = date => {
if (!date) {
date = new Date();
}
if(typeof date === 'string'){
date = new Date(date);
if(!date){
date = new Date(date.replace(/-/g, '/'));//兼容IOS new Date()
}
}
if(typeof date === 'number'){
date = new Date(date);
}
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':');
};
const formatNumber = n => {
n = n.toString();
return n[1] ? n : '0' + n;
};
export default formatTime;

View File

@ -0,0 +1,7 @@
export declare const RED = "#ee0a24";
export declare const BLUE = "#1989fa";
export declare const WHITE = "#fff";
export declare const GREEN = "#07c160";
export declare const ORANGE = "#ff976a";
export declare const GRAY = "#323233";
export declare const GRAY_DARK = "#969799";

View File

@ -0,0 +1,7 @@
export const RED = '#ee0a24';
export const BLUE = '#1989fa';
export const WHITE = '#fff';
export const GREEN = '#07c160';
export const ORANGE = '#ff976a';
export const GRAY = '#323233';
export const GRAY_DARK = '#969799';

View File

@ -0,0 +1,3 @@
import { VantComponentOptions, CombinedComponentInstance } from '../definitions/index';
declare function VantComponent<Data, Props, Methods>(vantOptions?: VantComponentOptions<Data, Props, Methods, CombinedComponentInstance<Data, Props, Methods>>): void;
export { VantComponent };

View File

@ -0,0 +1,48 @@
import { basic } from '../mixins/basic';
import { observe } from '../mixins/observer/index';
function mapKeys(source, target, map) {
Object.keys(map).forEach(key => {
if (source[key]) {
target[map[key]] = source[key];
}
});
}
function VantComponent(vantOptions = {}) {
const options = {};
mapKeys(vantOptions, options, {
data: 'data',
props: 'properties',
mixins: 'behaviors',
methods: 'methods',
beforeCreate: 'created',
created: 'attached',
mounted: 'ready',
relations: 'relations',
destroyed: 'detached',
classes: 'externalClasses'
});
const { relation } = vantOptions;
if (relation) {
options.relations = Object.assign(options.relations || {}, {
[`../${relation.name}/index`]: relation
});
}
// add default externalClasses
options.externalClasses = options.externalClasses || [];
options.externalClasses.push('custom-class');
// add default behaviors
options.behaviors = options.behaviors || [];
options.behaviors.push(basic);
// map field to form-field behavior
if (vantOptions.field) {
options.behaviors.push('wx://form-field');
}
// add default options
options.options = {
multipleSlots: true,
addGlobalClass: true
};
observe(vantOptions, options);
Component(options);
}
export { VantComponent };

View File

@ -0,0 +1 @@
.van-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.van-multi-ellipsis--l2{-webkit-line-clamp:2}.van-multi-ellipsis--l2,.van-multi-ellipsis--l3{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical}.van-multi-ellipsis--l3{-webkit-line-clamp:3}.van-clearfix:after{display:table;clear:both;content:""}.van-hairline,.van-hairline--bottom,.van-hairline--left,.van-hairline--right,.van-hairline--surround,.van-hairline--top,.van-hairline--top-bottom{position:relative}.van-hairline--bottom:after,.van-hairline--left:after,.van-hairline--right:after,.van-hairline--surround:after,.van-hairline--top-bottom:after,.van-hairline--top:after,.van-hairline:after{position:absolute;box-sizing:border-box;-webkit-transform-origin:center;transform-origin:center;content:" ";pointer-events:none;top:-50%;right:-50%;bottom:-50%;left:-50%;border:0 solid #eee;-webkit-transform:scale(.5);transform:scale(.5)}.van-hairline--top:after{border-top-width:1px}.van-hairline--left:after{border-left-width:1px}.van-hairline--right:after{border-right-width:1px}.van-hairline--bottom:after{border-bottom-width:1px}.van-hairline--top-bottom:after{border-width:1px 0}.van-hairline--surround:after{border-width:1px}

View File

@ -0,0 +1 @@
.van-clearfix:after{display:table;clear:both;content:""}

View File

@ -0,0 +1 @@
.van-ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.van-multi-ellipsis--l2{-webkit-line-clamp:2}.van-multi-ellipsis--l2,.van-multi-ellipsis--l3{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical}.van-multi-ellipsis--l3{-webkit-line-clamp:3}

View File

@ -0,0 +1 @@
.van-hairline,.van-hairline--bottom,.van-hairline--left,.van-hairline--right,.van-hairline--surround,.van-hairline--top,.van-hairline--top-bottom{position:relative}.van-hairline--bottom:after,.van-hairline--left:after,.van-hairline--right:after,.van-hairline--surround:after,.van-hairline--top-bottom:after,.van-hairline--top:after,.van-hairline:after{position:absolute;box-sizing:border-box;-webkit-transform-origin:center;transform-origin:center;content:" ";pointer-events:none;top:-50%;right:-50%;bottom:-50%;left:-50%;border:0 solid #eee;-webkit-transform:scale(.5);transform:scale(.5)}.van-hairline--top:after{border-top-width:1px}.van-hairline--left:after{border-left-width:1px}.van-hairline--right:after{border-right-width:1px}.van-hairline--bottom:after{border-bottom-width:1px}.van-hairline--top-bottom:after{border-width:1px 0}.van-hairline--surround:after{border-width:1px}

View File

@ -0,0 +1,8 @@
/// <reference types="miniprogram-api-typings" />
export declare function isDef(value: any): boolean;
export declare function isObj(x: any): boolean;
export declare function isNumber(value: any): boolean;
export declare function range(num: number, min: number, max: number): number;
export declare function nextTick(fn: Function): void;
export declare function getSystemInfoSync(): WechatMiniprogram.GetSystemInfoSuccessCallbackResult;
export declare function addUnit(value?: string | number): string | undefined;

View File

@ -0,0 +1,32 @@
export function isDef(value) {
return value !== undefined && value !== null;
}
export function isObj(x) {
const type = typeof x;
return x !== null && (type === 'object' || type === 'function');
}
export function isNumber(value) {
return /^\d+(\.\d+)?$/.test(value);
}
export function range(num, min, max) {
return Math.min(Math.max(num, min), max);
}
export function nextTick(fn) {
setTimeout(() => {
fn();
}, 1000 / 30);
}
let systemInfo = null;
export function getSystemInfoSync() {
if (systemInfo == null) {
systemInfo = wx.getSystemInfoSync();
}
return systemInfo;
}
export function addUnit(value) {
if (!isDef(value)) {
return undefined;
}
value = String(value);
return isNumber(value) ? `${value}px` : value;
}

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,27 @@
import { VantComponent } from '../common/component';
VantComponent({
props: {
dot: Boolean,
info: null,
size: null,
color: String,
customStyle: String,
classPrefix: {
type: String,
value: 'van-icon'
},
name: {
type: String,
observer(val) {
this.setData({
isImageName: val.indexOf('/') !== -1
});
}
}
},
methods: {
onClick() {
this.$emit('click');
}
}
});

View File

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"van-info": "../info/index"
}
}

View File

@ -0,0 +1,20 @@
<!-- <wxs src="../wxs/utils.wxs" module="utils" /> -->
<view
class="custom-class {{ classPrefix }} {{ isImageName ? 'van-icon--image' : classPrefix + '-' + name }}"
style="color: {{ color }};font-size: {{ size }}rpx;{{ customStyle }}"
bind:tap="onClick"
>
<!-- <van-info
wx:if="{{ info !== null || dot }}"
dot="{{ dot }}"
info="{{ info }}"
custom-class="van-icon__info"
/> -->
<image
wx:if="{{ isImageName }}"
src="{{ name }}"
mode="aspectFit"
class="van-icon__image"
/>
</view>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,12 @@
import { VantComponent } from '../common/component';
VantComponent({
props: {
dot: Boolean,
info: null,
customStyle: String
},
data: {
// classBem: utils.bem('info', { dot })
}
});

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -0,0 +1,8 @@
<!-- <wxs src="../wxs/utils.wxs" module="utils" /> -->
<view
wx:if="{{ info !== null && info !== '' || dot }}"
class="custom-class van-info "
style="{{ customStyle }}"
>{{ dot ? '' : info }}</view>
<!-- <view>{{ utils.bem('info', { dot }) }}</view> -->

View File

@ -0,0 +1 @@
@import '../common/index.wxss';.van-info{position:absolute;top:0;right:0;box-sizing:border-box;white-space:nowrap;text-align:center;-webkit-transform:translate(50%,-50%);transform:translate(50%,-50%);-webkit-transform-origin:100%;transform-origin:100%;min-width:16px;min-width:var(--info-size,16px);padding:0 3px;padding:var(--info-padding,0 3px);color:#fff;color:var(--info-color,#fff);font-weight:500;font-weight:var(--info-font-weight,500);font-size:12px;font-size:var(--info-font-size,12px);font-family:PingFang SC,Helvetica Neue,Arial,sans-serif;font-family:var(--info-font-family,PingFang SC,Helvetica Neue,Arial,sans-serif);line-height:14px;line-height:calc(var(--info-size, 16px) - var(--info-border-width, 1px)*2);background-color:#ee0a24;background-color:var(--info-background-color,#ee0a24);border:1px solid #fff;border:var(--info-border-width,1px) solid var(--white,#fff);border-radius:16px;border-radius:var(--info-size,16px)}.van-info--dot{min-width:0;border-radius:100%;width:8px;width:var(--info-dot-size,8px);height:8px;height:var(--info-dot-size,8px);background-color:#ee0a24;background-color:var(--info-dot-color,#ee0a24)}

View File

@ -0,0 +1,22 @@
export var basic = Behavior({
methods: {
$emit: function $emit() {
this.triggerEvent.apply(this, arguments);
},
getRect: function getRect(selector, all) {
var _this = this;
return new Promise(function (resolve) {
wx.createSelectorQuery().in(_this)[all ? 'selectAll' : 'select'](selector).boundingClientRect(function (rect) {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect);
}
if (!all && rect) {
resolve(rect);
}
}).exec();
});
}
}
});

View File

@ -0,0 +1,15 @@
export var button = Behavior({
properties: {
id: String,
sessionFrom: String,
appParameter: String,
sendMessageImg: String,
sendMessagePath: String,
showMessageCard: String,
sendMessageTitle: String,
lang: {
type: String,
value: 'en'
}
}
});

View File

@ -0,0 +1,39 @@
var isIPhoneX = null;
function getIsIPhoneX() {
return new Promise(function (resolve, reject) {
if (isIPhoneX !== null) {
resolve(isIPhoneX);
} else {
wx.getSystemInfo({
success: function success(_ref) {
var model = _ref.model,
screenHeight = _ref.screenHeight;
var iphoneX = /iphone x/i.test(model);
var iphoneNew = /iPhone11/i.test(model) && screenHeight === 812;
isIPhoneX = iphoneX || iphoneNew;
resolve(isIPhoneX);
},
fail: reject
});
}
});
}
export var iphonex = Behavior({
properties: {
safeAreaInsetBottom: {
type: Boolean,
value: true
}
},
created: function created() {
var _this = this;
getIsIPhoneX().then(function (isIPhoneX) {
_this.set({
isIPhoneX: isIPhoneX
});
});
}
});

View File

@ -0,0 +1,24 @@
export var link = Behavior({
properties: {
url: String,
linkType: {
type: String,
value: 'navigateTo'
}
},
methods: {
jumpLink: function jumpLink(urlKey) {
if (urlKey === void 0) {
urlKey = 'url';
}
var url = this.data[urlKey];
if (url) {
wx[this.data.linkType]({
url: url
});
}
}
}
});

View File

@ -0,0 +1,43 @@
export var behavior = Behavior({
created: function created() {
var _this = this;
if (!this.$options) {
return;
}
var cache = {};
var _this$$options = this.$options(),
computed = _this$$options.computed;
var keys = Object.keys(computed);
this.calcComputed = function () {
var needUpdate = {};
keys.forEach(function (key) {
var value = computed[key].call(_this);
if (cache[key] !== value) {
cache[key] = needUpdate[key] = value;
}
});
return needUpdate;
};
},
attached: function attached() {
this.set();
},
methods: {
// set data and set computed data
set: function set(data, callback) {
if (data) {
this.setData(data, callback);
}
if (this.calcComputed) {
this.setData(this.calcComputed());
}
}
}
});

View File

@ -0,0 +1,38 @@
import { behavior } from './behavior';
import { observeProps } from './props';
export function observe(vantOptions, options) {
var watch = vantOptions.watch,
computed = vantOptions.computed;
options.behaviors.push(behavior);
if (watch) {
var props = options.properties || {};
Object.keys(watch).forEach(function (key) {
if (key in props) {
var prop = props[key];
if (prop === null || !('type' in prop)) {
prop = {
type: prop
};
}
prop.observer = watch[key];
props[key] = prop;
}
});
options.properties = props;
}
if (computed) {
options.methods = options.methods || {};
options.methods.$options = function () {
return vantOptions;
};
if (options.properties) {
observeProps(options.properties);
}
}
}

View File

@ -0,0 +1,32 @@
export function observeProps(props) {
if (!props) {
return;
}
Object.keys(props).forEach(function (key) {
var prop = props[key];
if (prop === null || !('type' in prop)) {
prop = {
type: prop
};
}
var _prop = prop,
observer = _prop.observer;
prop.observer = function () {
if (observer) {
if (typeof observer === 'string') {
observer = this[observer];
}
observer.apply(this, arguments);
}
this.set();
};
props[key] = prop;
});
}

View File

@ -0,0 +1,22 @@
export var openType = Behavior({
properties: {
openType: String
},
methods: {
bindGetUserInfo: function bindGetUserInfo(event) {
this.$emit('getuserinfo', event.detail);
},
bindContact: function bindContact(event) {
this.$emit('contact', event.detail);
},
bindGetPhoneNumber: function bindGetPhoneNumber(event) {
this.$emit('getphonenumber', event.detail);
},
bindOpenSetting: function bindOpenSetting(event) {
this.$emit('opensetting', event.detail);
},
bindError: function bindError(event) {
this.$emit('error', event.detail);
}
}
});

View File

@ -0,0 +1,21 @@
export var touch = Behavior({
methods: {
touchStart: function touchStart(event) {
this.direction = '';
this.deltaX = 0;
this.deltaY = 0;
this.offsetX = 0;
this.offsetY = 0;
this.startX = event.touches[0].clientX;
this.startY = event.touches[0].clientY;
},
touchMove: function touchMove(event) {
var touch = event.touches[0];
this.deltaX = touch.clientX - this.startX;
this.deltaY = touch.clientY - this.startY;
this.offsetX = Math.abs(this.deltaX);
this.offsetY = Math.abs(this.deltaY);
this.direction = this.offsetX > this.offsetY ? 'horizontal' : this.offsetX < this.offsetY ? 'vertical' : '';
}
}
});

View File

@ -0,0 +1,73 @@
export var transition = function transition(showDefaultValue) {
return Behavior({
properties: {
customStyle: String,
show: {
type: Boolean,
value: showDefaultValue,
observer: 'observeShow'
},
duration: {
type: Number,
value: 300
}
},
data: {
type: '',
inited: false,
display: false,
supportAnimation: true
},
attached: function attached() {
if (this.data.show) {
this.show();
}
this.detectSupport();
},
methods: {
detectSupport: function detectSupport() {
var _this = this;
wx.getSystemInfo({
success: function success(info) {
if (info && info.system && info.system.indexOf('iOS 8') === 0) {
_this.set({
supportAnimation: false
});
}
}
});
},
observeShow: function observeShow(value) {
if (value) {
this.show();
} else {
if (this.data.supportAnimation) {
this.set({
type: 'leave'
});
} else {
this.set({
display: false
});
}
}
},
show: function show() {
this.set({
inited: true,
display: true,
type: 'enter'
});
},
onAnimationEnd: function onAnimationEnd() {
if (!this.data.show) {
this.set({
display: false
});
}
}
}
});
};

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,180 @@
import { VantComponent } from '../common/component';
import { isImageFile } from './utils';
VantComponent({
props: {
disabled: Boolean,
multiple: Boolean,
uploadText: String,
useBeforeRead: Boolean,
previewSize: {
type: null,
value: 90
},
name: {
type: [Number, String],
value: ''
},
accept: {
type: String,
value: 'image'
},
sizeType: {
type: Array,
value: ['original', 'compressed']
},
capture: {
type: Array,
value: ['album', 'camera']
},
fileList: {
type: Array,
value: [],
observer: 'formatFileList'
},
maxSize: {
type: Number,
value: Number.MAX_VALUE
},
maxCount: {
type: Number,
value: 100
},
deletable: {
type: Boolean,
value: true
},
previewImage: {
type: Boolean,
value: true
},
previewFullImage: {
type: Boolean,
value: true
},
imageFit: {
type: String,
value: 'scaleToFill'
}
},
data: {
lists: [],
computedPreviewSize: '',
isInCount: true
},
methods: {
formatFileList() {
const { fileList = [], maxCount } = this.data;
const lists = fileList.map(item => (Object.assign(Object.assign({}, item), { isImage: typeof item.isImage === 'undefined' ? isImageFile(item) : item.isImage })));
this.setData({ lists, isInCount: lists.length < maxCount });
},
startUpload() {
if (this.data.disabled)
return;
const { name = '', capture, maxCount, multiple, maxSize, accept, sizeType,videoCfg={}, lists, useBeforeRead = false // 是否定义了 beforeRead
} = this.data;
let chooseFile = null;
const newMaxCount = maxCount - lists.length;
// 设置为只选择图片的时候使用 chooseImage 来实现
if (accept === 'image') {
chooseFile = new Promise((resolve, reject) => {
wx.chooseImage({
count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
sourceType: capture,
sizeType,
success: resolve,
fail: reject
});
});
}
else if (accept === 'video'){
chooseFile = new Promise((resolve, reject) => {
wx.chooseMedia({
count: videoCfg.count || 9,
mediaType: ['video'],
sourceType: videoCfg.sourceType || ['album', 'camera'],
maxDuration: videoCfg.maxDuration || 10,
camera: videoCfg.camera || 'back',
success: (res)=>{
res.isVideo = true;
resolve(res);
},
fail: reject
});
});
}
else {
chooseFile = new Promise((resolve, reject) => {
wx.chooseMessageFile({
count: multiple ? newMaxCount : 1,
type: 'file',
success: resolve,
fail: reject
});
});
}
chooseFile
.then((res) => {
const file = multiple ? res.tempFiles : res.tempFiles[0];
// 检查文件大小
if (file instanceof Array) {
const sizeEnable = file.every(item => item.size <= maxSize);
if (!sizeEnable) {
this.$emit('oversize', { name });
return;
}
}
else if (file.size > maxSize) {
this.$emit('oversize', { name });
return;
}
let upData = { file, name };
if(res.isVideo){
file.map(val=>{
val.isVideo = true;
return val;
});
}
// 触发上传之前的钩子函数
if (useBeforeRead) {
this.$emit('before-read', {
file,
name,
callback: (result) => {
if (result) {
// 开始上传
this.$emit('after-read', upData);
}
}
});
}
else {
this.$emit('after-read', upData);
}
})
.catch(error => {
this.$emit('error', error);
});
},
deleteItem(event) {
const { index } = event.currentTarget.dataset;
this.$emit('delete', { index, name: this.data.name });
},
doPreviewImage(event) {
if (!this.data.previewFullImage)
return;
const curUrl = event.currentTarget.dataset.url;
const images = this.data.lists
.filter(item => item.isImage)
.map(item => item.url || item.path);
this.$emit('click-preview', { url: curUrl, name: this.data.name });
wx.previewImage({
urls: images,
current: curUrl,
fail() {
wx.showToast({ title: '预览图片失败', icon: 'none' });
}
});
}
}
});

View File

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"van-icon": "../icon/index"
}
}

View File

@ -0,0 +1,65 @@
<!-- <wxs src="../wxs/utils.wxs" module="utils" /> -->
<view class="van-uploader">
<view class="van-uploader__wrapper">
<!-- 预览样式 -->
<view
wx:if="{{ previewImage }}"
wx:for="{{ lists }}"
wx:key="index"
class="van-uploader__preview"
>
<image
wx:if="{{ item.isImage }}"
mode="{{ imageFit }}"
src="{{ item.url || item.path }}"
alt="{{ item.name || ('图片' + index) }}"
class="van-uploader__preview-image"
style="width: {{ previewSize }}; height: {{ previewSize }};"
data-url="{{ item.url || item.path }}"
bind:tap="doPreviewImage"
/>
<view
wx:elif="{{ item.isVideo }}"
class="van-uploader__preview"
style="width:280rpx; height:280rpx;"
class="van-uploader__preview"
>
<video class="van-uploader__preview-video" data-url="{{ item.url || item.path }}" style="width:280rpx; height:280rpx;" src="{{ item.url || item.tempFilePath }}" controls></video>
</view>
<view
wx:else
class="van-uploader__file"
style="width: {{ previewSize }}; height: {{ previewSize }};"
>
<van-icon name="description" class="van-uploader__file-icon" />
<view class="van-uploader__file-name van-ellipsis">{{ item.name || item.url || item.path }}</view>
</view>
<van-icon
wx:if="{{ deletable && !disabled }}"
name="clear"
size='40'
class="van-uploader__preview-delete"
data-index="{{ index }}"
bind:tap="deleteItem"
/>
</view>
<!-- 上传样式 -->
<block wx:if="{{ isInCount && !disabled }}">
<view class="van-uploader__slot" bind:tap="startUpload">
<slot />
</view>
<!-- 默认上传样式 -->
<view
class="van-uploader__upload"
style="width: {{ previewSize }}; height: {{ previewSize }};"
bind:tap="startUpload"
>
<van-icon name="plus" size='40' class="van-uploader__upload-icon" />
<text wx:if="{{ uploadText }}" class="van-uploader__upload-text">{{ uploadText }}</text>
</view>
</block>
</view>
</view>

View File

@ -0,0 +1,3 @@
@import '../common/index.wxss';.van-uploader{position:relative;display:inline-block}.van-uploader__wrapper{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap}.van-uploader__slot:empty{display:none}.van-uploader__slot:not(:empty)+.van-uploader__upload{display:none!important}.van-uploader__upload{position:relative;display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;box-sizing:border-box;width:80px;height:80px;margin:0 20rpx 20rpx 0;background-color:#fff;border:2px dashed #e0e0f0;border-radius:8px}.van-uploader__upload-icon{display:inline-block;width:48rpx;height:48rpx;color:#969799;font-size:24px}.van-uploader__upload-text{margin-top:8px;color:#969799;font-size:12px}.van-uploader__preview{position:relative;margin:0 20rpx 20rpx 0}.van-uploader__preview-image{display:block;width:80px;height:80px;border-radius:8px}
.van-uploader__preview-video{display:block;width:80px;height:80px;border-radius:8px;overflow: hidden}
.van-uploader__preview-delete{position:absolute;top:-8px;right:-8px;color:#969799;font-size:18px;background-color:#fff;border-radius:100%}.van-uploader__file{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;width:80px;height:80px;background-color:#f7f8fa;border-radius:8px}.van-uploader__file-icon{display:inline-block;width:20px;height:20px;color:#646566;font-size:20px}.van-uploader__file-name{box-sizing:border-box;width:100%;margin-top:8px;padding:0 5px;color:#646566;font-size:12px;text-align:center}

View File

@ -0,0 +1,12 @@
interface File {
path: string;
url: string;
size: number;
name: string;
type: string;
time: number;
image: boolean;
}
export declare function isImageUrl(url: string): boolean;
export declare function isImageFile(item: File): boolean;
export {};

View File

@ -0,0 +1,16 @@
const IMAGE_EXT = ['jpeg', 'jpg', 'gif', 'png', 'svg'];
export function isImageUrl(url) {
return IMAGE_EXT.some(ext => url.indexOf(`.${ext}`) !== -1);
}
export function isImageFile(item) {
if (item.type) {
return item.type.indexOf('image') === 0;
}
if (item.path) {
return isImageUrl(item.path);
}
if (item.url) {
return isImageUrl(item.url);
}
return false;
}

View File

@ -0,0 +1,14 @@
/* eslint-disable */
var REGEXP = getRegExp('^\d+(\.\d+)?$');
function addUnit(value) {
if (value == null) {
return undefined;
}
return REGEXP.test('' + value) ? value + 'px' : value;
}
module.exports = {
addUnit: addUnit
};

View File

@ -0,0 +1,5 @@
function isArray(array) {
return array && array.constructor === 'Array';
}
module.exports.isArray = isArray;

View File

@ -0,0 +1,38 @@
var array = require('./array.wxs');
var object = require('./object.wxs');
var PREFIX = 'van-';
function join(name, mods) {
name = PREFIX + name;
mods = mods.map(function(mod) {
return name + '--' + mod;
});
mods.unshift(name);
return mods.join(' ');
}
function traversing(mods, conf) {
if (!conf) {
return;
}
if (typeof conf === 'string' || typeof conf === 'number') {
mods.push(conf);
} else if (array.isArray(conf)) {
conf.forEach(function(item) {
traversing(mods, item);
});
} else if (typeof conf === 'object') {
object.keys(conf).forEach(function(key) {
conf[key] && mods.push(key);
});
}
}
function bem(name, conf) {
var mods = [];
traversing(mods, conf);
return join(name, mods);
}
module.exports.bem = bem;

View File

@ -0,0 +1,54 @@
/**
* Simple memoize
* wxs doesn't support fn.apply, so this memoize only support up to 2 args
*/
function isPrimitive(value) {
var type = typeof value;
return (
type === 'boolean' ||
type === 'number' ||
type === 'string' ||
type === 'undefined' ||
value === null
);
}
// mock simple fn.call in wxs
function call(fn, args) {
if (args.length === 2) {
return fn(args[0], args[1]);
}
if (args.length === 1) {
return fn(args[0]);
}
return fn();
}
function serializer(args) {
if (args.length === 1 && isPrimitive(args[0])) {
return args[0];
}
var obj = {};
for (var i = 0; i < args.length; i++) {
obj['key' + i] = args[i];
}
return JSON.stringify(obj);
}
function memoize(fn) {
var cache = {};
return function() {
var key = serializer(arguments);
if (cache[key] === undefined) {
cache[key] = call(fn, arguments);
}
return cache[key];
};
}
module.exports.memoize = memoize;

View File

@ -0,0 +1,13 @@
/* eslint-disable */
var REGEXP = getRegExp('{|}|"', 'g');
function keys(obj) {
return JSON.stringify(obj)
.replace(REGEXP, '')
.split(',')
.map(function(item) {
return item.split(':')[0];
});
}
module.exports.keys = keys;

View File

@ -0,0 +1,10 @@
/* eslint-disable */
var bem = require('./bem.wxs').bem;
var memoize = require('./memoize.wxs').memoize;
var addUnit = require('./add-unit.wxs').addUnit;
module.exports = {
bem: memoize(bem),
memoize: memoize,
addUnit: addUnit
};

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621915089686" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2807" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M762.78272 154.624h-44.032a96.81408 96.81408 0 0 0-46.92992-51.31776A98.06848 98.06848 0 0 0 578.92352 35.84H445.07648a98.06848 98.06848 0 0 0-92.88192 67.49184A96.81408 96.81408 0 0 0 305.25952 154.624h-44.032C176.47104 154.624 107.52 226.26816 107.52 314.31168v519.30624C107.52 921.6512 176.47104 993.28 261.21728 993.28h501.56544C847.52896 993.28 916.48 921.6512 916.48 833.61792V314.31168c0-88.04352-68.95104-159.68768-153.69728-159.68768z m-372.736 8.86784a34.92864 34.92864 0 0 0 27.21792-31.744 27.96544 27.96544 0 0 1 27.80672-26.0352h133.84704a27.96032 27.96032 0 0 1 27.80672 26.0352 34.93376 34.93376 0 0 0 27.21792 31.744 27.93984 27.93984 0 0 1-6.144 55.1936H396.19072a27.93984 27.93984 0 0 1-6.13888-55.168z m456.59648 670.12608c0 49.50016-37.62176 89.77408-83.86048 89.77408H261.21728c-46.23872 0-83.86048-40.27392-83.86048-89.77408V314.31168c0-49.50528 37.62176-89.7792 83.86048-89.7792h43.32544a97.84832 97.84832 0 0 0 91.648 64.06144h231.62368a97.8432 97.8432 0 0 0 91.648-64.06144h43.33056c46.23872 0 83.86048 40.27392 83.86048 89.7792v519.30624zM739.84 375.65952H279.79264a34.944 34.944 0 0 0 0 69.888H739.84a34.944 34.944 0 0 0 0-69.888z m-460.04736 254.64832h324.74112a34.944 34.944 0 0 0 0-69.88288H279.79264a34.944 34.944 0 0 0 0 69.88288z m383.24736 114.87744H279.79264a34.944 34.944 0 0 0 0 69.88288H663.04a34.944 34.944 0 0 0 0-69.88288z" fill="#595BB3" p-id="2808"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621915136519" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4543" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M692.949333 838.101333c30.933333 17.408 61.653333 26.197333 92.117334 26.197334 30.464 0 61.184-8.789333 92.117333-26.197334V960l-83.712-44.202667a15.530667 15.530667 0 0 0-14.250667-0.128l-86.4 44.202667v-121.770667h0.128zM810.666667 106.666667a42.666667 42.666667 0 0 1 42.666666 42.666666v298.666667h-128a170.666667 170.666667 0 0 0-170.453333 162.133333l-0.213333 8.533334v298.666666H213.333333a42.666667 42.666667 0 0 1-42.666666-42.666666v-725.333334a42.666667 42.666667 0 0 1 42.666666-42.666666h597.333334z m-25.6 426.666666c84.906667 0 153.6 68.138667 153.6 152.405334 0 84.224-68.693333 152.362667-153.6 152.362666s-153.6-68.138667-153.6-152.362666c0-84.266667 68.693333-152.405333 153.6-152.405334z m0 60.885334l-32.554667 47.018666-55.04 16.213334 35.114667 45.226666-1.536 57.002667 54.016-19.029333 54.186666 19.029333-1.536-57.002667 35.114667-45.226666-55.210667-16.213334-32.554666-47.018666zM490.666667 405.333333h-170.666667a21.333333 21.333333 0 0 0-20.992 17.493334L298.666667 426.666667v21.333333a21.333333 21.333333 0 0 0 17.493333 20.992L320 469.333333h170.666667a21.333333 21.333333 0 0 0 20.992-17.493333L512 448V426.666667a21.333333 21.333333 0 0 0-21.333333-21.333334z m128-170.666666h-298.666667a21.333333 21.333333 0 0 0-20.992 17.493333L298.666667 256v21.333333a21.333333 21.333333 0 0 0 17.493333 20.992L320 298.666667h298.666667a21.333333 21.333333 0 0 0 20.992-17.493334L640 277.333333V256a21.333333 21.333333 0 0 0-21.333333-21.333333z" fill="#d81e06" p-id="4544"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,68 @@
// pages/admin/exam/add.js
Page({
/**
* 页面的初始数据
*/
data: {
form:{
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

View File

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

View File

@ -0,0 +1,81 @@
<!--pages/admin/exam/add.wxml-->
<view class="page__title" style="text-align:center">新建考试</view>
<view class="weui-cells__title">基本信息</view>
<view class="weui-cells weui-cells_form">
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd"><label class="weui-label">考试名称</label></view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="填写考试名称" placeholder-class="weui-input__placeholder"
value="{{form.name}}" />
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">考试地点</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="填写考试地点" placeholder-class="weui-input__placeholder" type="number"
value="{{form.place}}" />
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">工作类别</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="填写您的快递单号" placeholder-class="weui-input__placeholder"
bindinput="kdInput" />
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">参考机会</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="填写可参考次数" placeholder-class="weui-input__placeholder"
value="3" />
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">开启时间</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="选择考试开启时间" placeholder-class="weui-input__placeholder"
bindinput="addressInput" />
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">关闭时间</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="选择考试关闭时间" placeholder-class="weui-input__placeholder"
bindinput="addressInput" />
</view>
</view>
</view>
<view class="weui-cells__title">监考人信息</view>
<view class="weui-cells weui-cells_form">
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">姓名</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="填写监考人姓名" placeholder-class="weui-input__placeholder"
value="{{form.proctor_name}}" />
</view>
</view>
<view class="weui-cell weui-cell_active">
<view class="weui-cell__hd">
<label class="weui-label">联系方式</label>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="填写监考人联系方式" placeholder-class="weui-input__placeholder"
value="{{form.proctor_phone}}" />
</view>
</view>
</view>
<view style="margin-top:16rpx">
<a class="weui-btn weui-btn_primary" bindtap="submit">确定</a>
</view>

View File

@ -0,0 +1 @@
/* pages/admin/exam/add.wxss */

View File

@ -0,0 +1,146 @@
const app = getApp()
const api = require("../../../utils/request.js");
Page({
data: {
formData: [
{
type: 'input',
id:'name',
lable:'考试名称',
isRequired: true,//是否必填
maxLength: 20,//最大长度
defaultValue:'',//初始值
rules:[//规则验证数组
{
regular: '^\\S*$',//正则字符串
tips: '不能有空格'//错误提示
},
]
},
{
type: 'input',
id:'place',
lable:'考试地点',
isRequired: true,//是否必填
maxLength: 50,//最大长度
defaultValue:'',//初始值
rules:[//规则验证数组
{
regular: '^\\S*$',//正则字符串
tips: '不能有空格'//错误提示
},
]
},
{
type: 'input',
id: 'chance',
lable: '考试机会',
defaultValue: 3,
inputType: 'digit', //对应input组件type值(text,number)
placeholder: '请填写数字',
isRequired: true,//是否必填
//disabled:true,
rules: [
]
},
{
type: 'picker',
id: 'workscope',
lable: '工作类别',
defaultIdx:0,//默认选择索引
// disabled:true,
isRequired:true,
range:[
{
id: 0,
name: '正常'
},
{
id: 1,
name: '异常'
},
]
},
{
type: 'date',
id: 'daterange',
lable: '开关时间',
isRequired: true,
/* 使endDate,,
与config中的colum属性共同设置
*/
completeTime:true, //显示完整时间, 包含时分秒
config: {
endDate: true,
dateLimit: true,
// initStartTime: "2020-01-01 12:32:44",
// initEndTime: "2020-12-01 12:32:44",
column: "minute",//day、hour、minute、secend
limitStartTime: "2000-01-01 00:00:59",
limitEndTime: "2100-01-01 00:00:59"
}
},
{
type: 'input',
id:'proctor_name',
lable:'监考人姓名',
isRequired: true,//是否必填
maxLength: 50,//最大长度
defaultValue:'',//初始值
rules:[//规则验证数组
{
regular: '^\\S*$',//正则字符串
tips: '不能有空格'//错误提示
},
]
},
{
type: 'input',
id:'proctor_phone',
lable:'监考人联系方式',
isRequired: true,//是否必填
maxLength: 50,//最大长度
defaultValue:'',//初始值
rules:[//规则验证数组
{
regular: '^\\S*$',//正则字符串
tips: '不能有空格'//错误提示
},
]
},
],
toSubmit: Math.random()
},
onFormSubmit(e){
console.log('表单提交: ', e);
let x = {};
x.opentime = e.daterange.startDate;
x.endtime = e.daterange.endDate;
x.name = e.name
x.place = e.place
x.chance = e.chance
x.workscope = e.workscope
x.proctor_name = e.proctor_name
x.proctor_phone = e.proctor_phone
console.log(x)
},
onFormChange(e){
console.log('表单变化: ',e);
},
//变更数值, 触发表单提交事件
toSubmitChange(){
this.setData({
toSubmit: Math.random()
})
},
onLoad: function () {
api.requesta('/examtest/workscope/?can_exam=true', 'GET').then(res=>{
this.data.formData[3].range=res.data
this.setData({
formData: this.data.formData
})
})
},
})

View File

@ -0,0 +1,5 @@
{
"usingComponents": {
"d-form": "/components/dynamicForm/index"
}
}

View File

@ -0,0 +1,4 @@
<!--pages/admin/exam/add2.wxml-->
<d-form formData="{{formData}}" showSubmitBtn="{{false}}" toSubmit="{{toSubmit}}" bind:dynamicFormChange="onFormChange" bind:dynamicFormSubmit="onFormSubmit"></d-form>
<button bindtap="toSubmitChange" type="primary">确认</button>

View File

@ -0,0 +1 @@
/* pages/admin/exam/add2.wxss */

View File

@ -0,0 +1,86 @@
// pages/admin/index.js
const api = require("../../utils/request.js");
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.setData({
admininfo:getApp().globalData.admininfo
})
},
logout: function(){
wx.redirectTo({
url: '/pages/admin/login?type=nologin',
})
},
bindmp: function(){
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
api.requesta('/rbac/user/bindmp/','POST', {code:res.code}).then(res=>{
getApp().globalData.admininfo.mpopenid = res.data.mpopenid
wx.redirectTo({
url: '/pages/admin/index',
})
})
}
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

View File

@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "管理员控制台"
}

View File

@ -0,0 +1,53 @@
<!--pages/admin/index.wxml-->
<view class="page">
<view class="page__bd">
<view class="weui-cells weui-cells_after-title" style="margin-top:0px">
<view class="weui-panel__bd">
<view class="weui-media-box weui-media-box_appmsg">
<view class="weui-media-box__bd weui-media-box__bd_in-appmsg">
<view class="weui-media-box__title">
<span>账号:</span>
<span style="font-weight:bold;">{{admininfo.username}}</span>
</view>
<view class="weui-media-box__desc">昵称:
<span>{{ admininfo.name }}</span>
</view>
<view class="weui-media-box__desc">角色:
<span>{{ admininfo.roles }}</span>
</view>
<view class="weui-media-box__desc" wx:if="{{admininfo.mpopenid}}">
已绑定微信
</view>
</view>
</view>
<view style="text-align:right;padding-right:8rpx">
<button type="primary" size="mini" bindtap="bindmp" wx:if="{{!admininfo.mpopenid}}">绑定微信</button>
<button type="warn" size="mini" bindtap="logout" style="margin-left:4rpx">退出</button>
</view>
</view>
</view>
<view class="weui-cells__title">功能列表</view>
<view class="weui-cells weui-cells_after-title">
<view class="weui-grids">
<navigator class="weui-grid" url="/pages/admin/exam/add2">
<view class="weui-grid__icon">
<image src="/images/suiji.svg" alt></image>
</view>
<view class="weui-grid__label">发布考试</view>
</navigator>
<a class="weui-grid" bindtap="goYati">
<view class="weui-grid__icon">
<image src="/images/examtest.svg" alt></image>
</view>
<view class="weui-grid__label">考试记录</view>
</a>
<navigator class="weui-grid" url="/pages/cuoti/index">
<view class="weui-grid__icon">
<image src="/images/candidate.svg" alt></image>
</view>
<view class="weui-grid__label">出证记录</view>
</navigator>
</view>
</view>
</view>
</view>

View File

@ -0,0 +1,9 @@
/* pages/admin/index.wxss */
.weui-grids {
border-top:none;
}
.weui-grid {
width: 25%;
border-right:none;
border-bottom:none;
}

View File

@ -0,0 +1,116 @@
// pages/admin/login.js
const api = require("../../utils/request.js");
Page({
/**
* 页面的初始数据
*/
data: {
form:{
username:'',
password:''
}
},
usernameChange: function (e) {
this.data.form.username = e.detail.value
},
passwordChange: function (e) {
this.data.form.password = e.detail.value
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
if(options.type=='nologin'){
}else{
this.mplogin()
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
},
denglu: function ( ) {
var form = this.data.form
api.requesta('/token/', 'POST', form).then(res => {
getApp().globalData.admintoken = res.data.token;
api.requesta('/rbac/user/info/', 'GET').then(res=>{
getApp().globalData.admininfo = res.data
wx.showToast({
title: '登录成功',
icon: 'none'
})
wx.redirectTo({
url: '/pages/admin/index',
})
})
})
},
mplogin: function(){
wx.showLoading({
title: '自动登陆中',
mask:true
})
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
api.requesta('/rbac/user/mplogin/','POST', {code:res.code}).then(res=>{
getApp().globalData.admintoken = res.data.token;
api.requesta('/rbac/user/info/', 'GET').then(res=>{
getApp().globalData.admininfo = res.data
wx.redirectTo({
url: '/pages/admin/index',
})
})
})
}
})
}
})

View File

@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "管理员登录"
}

View File

@ -0,0 +1,33 @@
<!--pages/admin/login.wxml-->
<view class="page">
<view class="page__hd" >
<view class="page__title" style="text-align:center">中科辐射学堂</view>
<view class="page__desc" style="text-align:center">管理员登录</view>
</view>
<view class="page__bd">
<view class="weui-cells weui-cells_after-title">
<view class="weui-cell weui-cell_input">
<view class="weui-cell__hd">
<view class="weui-label">账号</view>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="请输入管理员账号" bindinput="usernameChange" />
</view>
</view>
<view class="weui-cell weui-cell_input">
<view class="weui-cell__hd">
<view class="weui-label">密码</view>
</view>
<view class="weui-cell__bd">
<input class="weui-input" placeholder="请输入密码" bindinput="passwordChange" type="password"/>
</view>
</view>
</view>
</view>
<button class="weui-btn" type="primary" bindtap="denglu">登陆</button>
</view>
<view class="weui-footer weui-footer_fixed-bottom">
<!-- <view class="weui-footer__text" bindtap="intro">点击下载系统/小程序文档介绍</view> -->
<view class="weui-footer__text">Copyright © 2018-2021 国检集团</view>
<view class="weui-footer__text">中存大数据提供技术支持</view>
</view>

View File

@ -0,0 +1 @@
/* pages/admin/login.wxss */

View File

@ -107,6 +107,11 @@ Page({
url: '/pages/main/main',
})
},
goadmin: function(){
wx.redirectTo({
url: '/pages/admin/login',
})
},
denglu: function (data) {
api.request('/crm/consumer/register/', 'POST', data).then(res => {
getApp().onLaunch()

View File

@ -33,6 +33,7 @@
</view>
<view class="weui-footer weui-footer_fixed-bottom">
<!-- <view class="weui-footer__text" bindtap="intro">点击下载系统/小程序文档介绍</view> -->
<view class="weui-footer__text">Copyright © 2018-2020 国检集团</view>
<view style="color:blue" bindtap="goadmin">管理员入口</view>
<view class="weui-footer__text">Copyright © 2018-2021 国检集团</view>
<view class="weui-footer__text">中存大数据提供技术支持</view>
</view>

View File

@ -72,6 +72,13 @@
<view class="weui-cell__ft weui-cell__ft_in-access" style="color:red"></view>
</navigator>
</view>
<view class="weui-cells__title">管理员操作台</view>
<view class="weui-cells weui-cells_after-title">
<navigator url="/pages/admin/login" class="weui-cell weui-cell_access">
<view class="weui-cell__bd">管理员入口</view>
<view class="weui-cell__ft weui-cell__ft_in-access" style="color:red"></view>
</navigator>
</view>
<view class="weui-footer weui-footer_fixed-bottom">
<!-- <view class="weui-footer__text" bindtap="intro">点击下载系统/小程序文档介绍</view> -->
<view class="weui-footer__text">更多服务请联系课程顾问</view>

View File

@ -21,13 +21,15 @@
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": false,
"useMultiFrameRuntime": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": false,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"enableEngineNative": false,
"bundle": false,
"useIsolateContext": true,
"useCompilerModule": true,
@ -38,7 +40,7 @@
"minifyWXSS": true
},
"compileType": "miniprogram",
"libVersion": "2.14.1",
"libVersion": "2.16.1",
"appid": "wxf1e9471c93f05ad6",
"projectname": "test_mini",
"debugOptions": {

View File

@ -51,6 +51,57 @@ function request(url, method, data) {
return promise;
}
function requesta(url, method, data) {
let promise = new Promise((resolve, reject) => {
wx.showNavigationBarLoading();
wx.request({
url: getApp().globalData.host + url,
method: method,
data: data,
header:{
'Authorization': 'JWT ' + getApp().globalData.admintoken
},
success: (res => {
if (res.data.code >= 200 && res.data.code < 400) {
resolve(res.data);
}else if(res.data.code == 401){
}
else {
var msg = '请求错误'
if(res.data.msg){
msg = res.data.msg
}
if (msg.indexOf('该操作的权限')!=-1){
msg = '权限不足或账户过期,请联系课程顾问'
}
wx.showToast({
title: msg,
icon: 'none',
duration: 1000
})
}
}),
fail: (res => {
wx.showToast({
title: '请求出错',
icon: 'none',
duration: 1500
})
console.log(res)
reject('网络出错');
}),
complete: function () {
wx.hideNavigationBarLoading();
}
})
})
return promise;
}
module.exports = {
request: request
request: request,
requesta: requesta
}

View File

@ -16,8 +16,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.serializers import (jwt_encode_handler,
jwt_payload_handler)
from rest_framework_jwt.serializers import jwt_encode_handler
from rest_framework_jwt.settings import api_settings
from crm.zhenzismsclient import ZhenziSmsClient
@ -35,16 +34,12 @@ from rbac.models import UserProfile
from django.http import Http404
from .spider import getZs
import time
appid = 'wxf1e9471c93f05ad6'
secret = '4bf7f9bd6c52634586bbe792a1f0a834'
sms_appid = '100172'
sms_appsecret = '00b8681c-0ce6-41c8-a867-904c1891c78a'
sms_url = 'https://sms.zhenzikj.com'
from server.config import *
def jwt_payload_handler(user):
def my_payload_handler(user, dtype="admin"):
payload = {
'user_id': user.pk,
'type':'consumer',
'type':dtype,
'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
}
if api_settings.JWT_ALLOW_REFRESH:
@ -571,7 +566,7 @@ class ConsumerMPLoginView(APIView):
session_key = info['session_key']
consumer = Consumer.objects.get_or_create(openid = openid)[0]
serializer = ConsumerDetailSerializer(instance=consumer)
payload = jwt_payload_handler(consumer)
payload = my_payload_handler(consumer, 'consumer')
token = jwt_encode_handler(payload)
return Response({"token":token,"session_key":session_key, "openid":openid, "userinfo":serializer.data})

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2021-05-25 02:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rbac', '0005_userprofile_bcompany'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='mpopenid',
field=models.CharField(blank=True, max_length=100, null=True, unique=True, verbose_name='小程序openid'),
),
]

View File

@ -152,6 +152,7 @@ class UserProfile(AbstractUser):
pname = models.CharField('所属省份', max_length=100, null=True, blank=True)
bcompany = models.ForeignKey('crm.company', verbose_name='所属公司', null=True, blank=True, on_delete=models.SET_NULL)
mpopenid = models.CharField(max_length=100, verbose_name='小程序openid', unique=True, null=True, blank=True)
class Meta:
verbose_name = "用户信息"

View File

@ -1,28 +1,35 @@
# -*- coding: utf-8 -*-
import json
from operator import itemgetter
import requests
# import jwt
from django.conf import settings
from django.contrib.auth import authenticate,login,logout
from django.contrib.auth.hashers import check_password
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.hashers import check_password, make_password
from django_filters.rest_framework import DjangoFilterBackend
from django.contrib.auth.hashers import make_password
from rest_framework.decorators import action
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework import status
from rest_framework.decorators import action, authentication_classes
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework import status
from utils.custom import CommonPagination
from utils.child import get_child_queryset
from ..models import UserProfile, Menu, Organization
from utils.custom import CommonPagination
from ..models import Menu, Organization, UserProfile
from ..permission import (RbacPermission, get_all_menu_queryset,
get_permission_list)
from ..serializers.menu_serializer import MenuSerializer
from ..serializers.user_serializer import UserListSerializer, UserCreateSerializer, UserModifySerializer, \
UserInfoListSerializer
from ..permission import get_all_menu_queryset,get_permission_list,RbacPermission
from ..serializers.user_serializer import (UserCreateSerializer,
UserInfoListSerializer,
UserListSerializer,
UserModifySerializer)
from server.config import *
from rest_framework_jwt.serializers import jwt_encode_handler
from crm.views import my_payload_handler
class UserLogoutView(APIView):
authentication_classes = ()
@ -47,6 +54,8 @@ class UserInfoView(APIView):
# 'avatar': request._request._current_scheme_host + '/media/' + str(user.image),
'avatar': user.avatar,
'perms': perms,
'roles':user.roles.values_list('name', flat=True),
'mpopenid': user.mpopenid
}
return Response(data)
@ -59,6 +68,7 @@ class UserInfoView(APIView):
from utils.pagination import PageOrNot
class UserViewSet(PageOrNot, ModelViewSet):
"""
用户管理增删改查
@ -135,4 +145,39 @@ class UserViewSet(PageOrNot, ModelViewSet):
return Response({'error': '新密码两次输入不一致!'})
else:
return Response({'error':'旧密码错误!'})
@action(methods=['post'], detail=False, permission_classes=[], authentication_classes=[])
def mplogin(self, request, pk=None):
"""
小程序登录
"""
code = request.data['code']
info = requests.get('https://api.weixin.qq.com/sns/jscode2session?appid='+appid+'&secret='+secret+'&js_code=' +
code+'&grant_type=authorization_code').content.decode('utf-8')
info = json.loads(info)
openid = info['openid']
session_key = info['session_key']
try:
user = UserProfile.objects.get(mpopenid = openid)
payload = my_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({"token":token,"session_key":session_key, "openid":openid})
except:
return Response({'error':'自动登录失败!'})
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
def bindmp(self, request, pk=None):
"""
绑定微信
"""
user = request.user
code = request.data['code']
info = requests.get('https://api.weixin.qq.com/sns/jscode2session?appid='+appid+'&secret='+secret+'&js_code=' +
code+'&grant_type=authorization_code').content.decode('utf-8')
info = json.loads(info)
openid = info['openid']
user.mpopenid = openid
user.save()
return Response({'mpopenid':openid})

View File

@ -0,0 +1,5 @@
appid = 'wxf1e9471c93f05ad6'
secret = '4bf7f9bd6c52634586bbe792a1f0a834'
sms_appid = '100172'
sms_appsecret = '00b8681c-0ce6-41c8-a867-904c1891c78a'
sms_url = 'https://sms.zhenzikj.com'

View File

@ -45,7 +45,11 @@ class FitJSONRenderer(JSONRenderer):
response = renderer_context.get("response")
response_body.code = response.status_code
if response_body.code >= 400: # drf异常
response_body.msg = data['detail'] if 'detail' in data else data
if isinstance(data, dict):
data = data[list(data.keys())[0]]
if isinstance(data, list):
data = data[0]
response_body.msg = data
elif data and 'error' in data and data['error']:# 自传异常,key为error
response_body.code = data.get("code",400)
response_body.msg = data.get("error", "")