Commit 4d9430fb authored by June's avatar June

feature: login

parent 5fbbf2c4
<script>
import { mapActions } from 'vuex'
import { checkLoginStatus } from '@/apis/user.js'
// import { wxLogin } from '@/apis/user.js'
import { login_wx } from '@/utils/modules/login.js'
export default {
onLaunch: async function() {
onLaunch: function() {
this.setSystemInfo() // 获取系统信息
this.setLocation() // 获取经纬度
this.setMenuButtonInfo() // 获取胶囊信息
this.checkUpdate() // 检查更新
// await login_wx()
checkLoginStatus() // 检查登录状态
.then(res => {
console.log(res)
if(res.status) {
this.setLoginStatus(true)
this.setCount()
} else {
this.setLoginStatus(false)
}
})
.catch(err => console.log(err))
// wx.login
login_wx(async () => {
const token = await this.$getStorage('token')
if(!token) {
uni.navigateTo({
url: '/pages/login/index'
})
} else {
this.setToken(token)
}
})
},
methods: {
...mapActions('systemInfo', ['setSystemInfo', 'setMenuButtonInfo']),
...mapActions('user', ['setLocation', 'setLoginStatus']),
...mapActions('user', ['setLocation', 'setToken', 'setLoginStatus']),
...mapActions('cart', ['setCount']),
// 检查更新
checkUpdate() {
......
// 用户相关
import { request } from "@/lib/service"
import { baseUrl } from '@/config' // 将请求的baseUrl写这里,为了开发时调试请求地址
const RSA = require('@/utils/wx_rsa.js')
import { publicKey } from '@/config/index.js'
import md5 from 'md5'
/**
* @desc 检查登录态
*/
export function checkLoginStatus() {
return request({
url: `${baseUrl}/wxxcx/isLogin.htm`,
url: `zsxcx/isLogin.htm`,
method: 'post',
needMask: true
})
......@@ -20,54 +18,37 @@ export function checkLoginStatus() {
*/
export function checkLoginTime() {
return request({
url: `${baseUrl}/wxxcx/getFwqTime.htm`,
url: `zsxcx/getFwqTime.htm`,
method: 'post'
})
}
/**
* @desc 微信登录 wx.login 获取openid unionid等信息
* @desc wx.login 获取openid unionid等信息
* @param { String } code
*/
export function wxLogin(code) {
return request({
url: `${baseUrl}/wxxcx/getInfo.htm`,
url: `zsxcx/getInfo.htm`,
data: {
js_code: code
}
})
}
/**
* @desc 静默登录
* @param { Number } userPhone
* @param { String } time
*/
export function slelentLogin(userPhone, time) {
const encrypt_rsa = RSA.KEYUTIL.getKey(publicKey)
return request({
url: `${baseUrl}/wxxcx/silentLogin.htm`,
method: 'post',
data: {
un: RSA.hex2b64(encrypt_rsa.encrypt(userPhone)),
tm: RSA.hex2b64(encrypt_rsa.encrypt(time))
}
})
}
/**
* @desc 退出登录
*/
export function logout() {
return request({
url: `${baseUrl}/wxxcx/loginOut.htm`,
url: `zsxcx/loginOut.htm`,
method: 'post',
needMask: true
})
}
/**
* @desc 注册
* @desc 登录
* @param {Object} params
*
* lgtype 必填 登录方式:1微信登录 2短信验证码登录 3账号密码登录
......@@ -78,22 +59,53 @@ export function logout() {
* iv 加密算法的初始向量(lgtype=1时必填)
* session_key 会话密钥(lgtype=1时必填)
*/
export function register(params) {
export function login(params) {
return request({
url: `${baseUrl}/wxxcx/login.htm`,
url: `zsxcx/login.htm`,
method: 'post',
data: params
})
}
/**
* @desc 忘记密码
* @param { string } f_code 输入验证码
* @param { string } upwd 新密码
* @param { string } upwd2 新密码二次输入
*/
export function forgetPwd(params) {
return request({
url: `zsxcx/forgetPwd.htm`,
method: 'post',
data: params
})
}
/**
* @desc 发送登录验证码接口
* @param { String } phone
*/
export function loginSms(phone) {
return request({
url: `zsxcx/send_lgmessage.htm`,
method: 'post',
data: {
phone,
sign: md5('glsms' + phone)
}
})
}
/**
* @desc 获取手机验证码
* @desc 发送忘记密码验证码接口
*/
export function smsCode() {
export function forgetSms(phone) {
return request({
url:`${baseUrl}/a`
url: `zsxcx/send_fgmessage.htm`,
method: 'post',
data: {
phone,
sign: md5('glsms' + phone)
}
})
}
\ No newline at end of file
......@@ -13,5 +13,3 @@ export const baseUrl = env[__wxConfig.envVersion]
// 商品图片的域名地址
export const imgUrl = process.env.NODE_ENV === 'development' ? 'https://d.gelifood.com/' : 'https://www.gelifood.com/'
export const publicKey = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp3Rit6cNlpOa9dbTx2ddt6ZblmMO04zdY+UIHMvsAnjBHYxEYfm7hx1yNkf1ohZNg47K+Ox/z8WcTRYpGnSka5UHDUTeBEQp4BGD25PsJLkc5YUk0PXjqRq5m+vuB3mEn7r6DaTwCxX5n2G4ky77xhzmZbG7MDP85RQHBVsvqYwIDAQAB-----END PUBLIC KEY-----'
\ No newline at end of file
import { baseUrl } from '@/config'
import Toast from "../toast/index.js"
import { isObeject, isFunction } from "@/utils/types.js"
import Validator from "@/utils/validate.js"
......@@ -16,7 +17,7 @@ import store from '@/store/index.js'
function validateOps(options = {}) {
const validator = new Validator()
validator.add(Object.keys(options), [{strategy: 'minLength:1', errorMsg: '参数格式不正确'}])
validator.add(options.url, [{strategy: 'notEmpty', errorMsg: '请求地址不能为空'}, {strategy: 'isHttp', errorMsg: '请求地址不正确'}])
validator.add(options.url, [{strategy: 'notEmpty', errorMsg: '请求地址不能为空'}])
return validator.validate()
}
......@@ -25,20 +26,22 @@ export function request (options) {
const valite_err = validateOps(options)
if(valite_err) return Toast({title: valite_err})
options.method = options.method ? options.method.toUpperCase() : "GET" // 默认get
options.needMask && uni.showLoading({title: '加载中...', mask: true})
options.url = `${baseUrl}/${options.url}`
if(options.needMask) {
uni.showLoading({title: '加载中...', mask: true})
}
return new Promise((resolve, reject) => {
const token = store.state.user.token
uni.request({
...options,
header: {
'content-type': 'application/x-www-form-urlencoded',
'cookie': store.state.user.cookieKey
'token': token
},
success: async res => {
try{
const statusCode = res.statusCode
// console.log(statusCode)
switch(statusCode) {
case 200:
switch(res.statusCode) {
case 200:
options.successCb && typeof options.successCb === 'function' && options.successCb()
// 处理某些接口返回String类型的数据
const data = (res.data && typeof res.data === 'string') ? JSON.parse(res.data) : res.data
......@@ -47,39 +50,29 @@ export function request (options) {
case 10:
resolve({
status: true,
data: res.data.list || res.data.data,
res: res.data,
data: data.data,
msg: data.rep_msg
})
break;
case -1:
Toast({title: data.rep_msg})
resolve({
status: true,
data: res.data.list || res.data.data,
res: res.data,
status: false,
data: data.data,
msg: data.rep_msg
})
break;
case -3:
const loginRes = await login_slilen()
console.log('刷新登录 重新执行上一次请求=======')
if(loginRes) { // 重新执行上一次的操作
const againRes = await request(options)
resolve({
status: true,
data: againRes.list || againRes.data,
res: againRes.res,
msg: againRes.msg
})
}
Toast({title: '登录已失效,请重新登录', cb: () => uni.navigateTo({
url: '/pages/login/index'
})})
break;
default:
Toast({title: data.rep_msg})
resolve({
status: false,
code: status, // 正常不返回为了好找 后端调试 -1 => 业务错误(一般失败情况返回此项);-2 => 系统异常; -3 => 请先登录; -4 => 请求失败; -10 => 签名失败
data: data.list,
res: res.data.list || res.data.data,
data: data.data,
msg: data.rep_msg
})
break;
......
/**
* getStorage
* @desc 获取存储的本地数据
* @param { keyName }
* @param { String } key
* @return { storageVal }
*/
export function getStorage(key){
......@@ -9,6 +9,22 @@ export function getStorage(key){
return val ? JSON.parse(val) : ''
}
/**
* getStorage 异步 用处不大
* @param { String } key
* @param { function } cb
* @return no
*/
// export function getStorageAsync(key, cb) {
// uni.getStorage({
// key,
// success: res => {
// cb && typeOf cb === 'function' && cb()
// console.log(res.data)
// }
// })
// }
/**
* setStorage
* @desc 设置本地存储数据(同步)
......
......@@ -10,6 +10,8 @@ import uniPopup from '@/components/uni-popup/index.vue'
Vue.component('empty-view', emptyView)
Vue.component('uni-popup', uniPopup)
Vue.prototype.$store = store
Vue.config.productionTip = false
App.mpType = 'app'
......
......@@ -50,7 +50,7 @@
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "wxd170058f4ad8fecd",
"appid" : "wxe4c065e2234c470b",
"setting" : {
"urlCheck" : false,
"es6" : true,
......
......@@ -65,6 +65,12 @@
"subPackages": [{
"root": "subPages",
"pages": [
{
"path": "resetPwd/index",
"style": {
"navigationBarTitleText": "重置密码"
}
},
{
"path": "userManage/index",
"style": {
......
......@@ -67,13 +67,10 @@
import searchBar from '@/components/search-bar/index.vue'
import goodsModule from './components/goods-module.vue'
import goodsPopup from '@/components/goods-popup/index.vue'
import cityPicker from '@/components/city-picker/index.vue'
import { mapState, mapActions } from 'vuex'
import { checkLogin } from '@/utils/common.js'
import { logout } from '@/apis/user.js'
import cityPicker from '@/components/city-picker/index.vue'
import { checkLogin } from '@/utils/common.js'
export default {
data() {
......@@ -112,9 +109,9 @@ export default {
methods: {
...mapActions('cart', ['setCount']),
...mapActions('user', ['logout']),
handleLogout() {
logout()
.then(res => console.log(res))
this.logout()
},
handleCart() {
......
import Validator from "@/utils/validate.js"
import { register } from '@/apis/user.js'
function phoneValidate() {
const phoneValidator = new Validator()
const form = this.form
phoneValidator.add(form.user, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
return phoneValidator.validate()
}
function smsValidate() {
const smsValidator = new Validator()
const form = this.form
smsValidator.add(form.phone, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
smsValidator.add(form.smsCode, [{strategy: 'notEmpty', errorMsg: '验证码不能为空'}])
smsValidator.add(form.user, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
smsValidator.add(form.yzm, [{strategy: 'notEmpty', errorMsg: '验证码不能为空'}])
return smsValidator.validate()
}
function accoutValidate() {
const smsValidator = new Validator()
const form = this.form
smsValidator.add(form.phone, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
smsValidator.add(form.password, [{strategy: 'notEmpty', errorMsg: '密码不能为空'}])
// 用户名不检验手机号码
if(form.lgtype === 2) {
smsValidator.add(form.user, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
} else if(form.lgtype === 3) {
smsValidator.add(form.user, [{strategy: 'notEmpty', errorMsg: '用户名不能为空'}])
}
smsValidator.add(form.pwd, [{strategy: 'notEmpty', errorMsg: '密码不能为空'}])
return smsValidator.validate()
}
async function registerCommon(params) {
try{
const res = await register(params)
console.log(res)
}catch(e){
this.$toast({title: e.msg || '程序错误'})
}
async function loginCommon(params) {
params.openid = this.openid
this.login(params)
}
export default {
smsValidate,
accoutValidate,
registerCommon
phoneValidate,
loginCommon
}
\ No newline at end of file
......@@ -2,10 +2,9 @@
<view>
<view>
<input
type="text"
placeholder="请输入手机号码"
data-type="phone"
@input="inputForm"
:type="form.lgtype === 3 ? 'text' : 'number'"
:placeholder="userPlachelder"
v-model="form.user"
/>
</view>
......@@ -13,78 +12,82 @@
<input
type="text"
placeholder="请输入验证码"
data-type="smsCode"
@input="inputForm"
v-model="form.yzm"
/>
<view>SMS</view>
<button @click="handleSms">{{smsMsg}}</button>
</view>
<view v-show="form.lgtype === 3">
<input
type="password"
placeholder="请输入密码"
data-type="password"
@input="inputForm"
v-model="form.pwd"
/>
</view>
<view>
<button type="default" @click="handleLogin(2)">短信验证码注册登录</button>
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">微信快捷登录</button>
<button type="default" @click="handleLogin(3)">密码登录</button>
<button :class="form.lgtype === 2 ? 'btn_active' : null" type="default" @click="handleLogin(2)">短信验证码注册登录</button>
<button :class="form.lgtype === 1 ? 'btn_active' : null" open-type="getPhoneNumber" @click="handleLogin(1)" @getphonenumber="getPhoneNumber">微信快捷登录</button>
<button :class="form.lgtype === 3 ? 'btn_active' : null" type="default" @click="handleLogin(3)">密码登录</button>
</view>
<button type="default" @click="handleForget">忘记密码</button>
</view>
</template>
<script>
import loginCommon from './common.js'
import common from './common.js'
import { timerFn } from '@/utils/common.js'
import { mapState } from 'vuex'
import { loginSms } from '@/apis/user.js'
import { mapState, mapActions } from 'vuex'
const userPlachelderStr = {
1: '',
2: '请输入手机号码',
3: '请输入用户名'
}
export default {
data() {
return {
login_type: "smsLogin", // smsLogin wxLogin accoutLogin
smsDisabled: false, // sms按钮是否禁用
smsMsg: '获取验证码',
form: {
lgtype: 2, // 1微信登录 2短信验证码登录 3账号密码登录
phone: null,
smsCode: null,
password: null
user: '',
yzm: '',
pwd: ''
}
}
},
computed: {
...mapState({
session_key: state => state.user.session_key
})
session_key: state => state.user.session_key,
openid: state => state.user.openid
}),
userPlachelder() {
return userPlachelderStr[this.form.lgtype]
}
},
// components: {
// uniPopup
// },
methods: {
...loginCommon,
...common,
...mapActions('user', ['login']),
inputForm(e) {
const { type } = e.currentTarget.dataset
this.form[type] = e.detail.value
},
handleLogin(type) {
if(type !== this.form.lgtype) return this.$set(this.form, 'lgtype', type)
async handleLogin(type) {
if(type !== this.form.lgtype) {
this.$set(this.form, 'lgtype', type)
}
switch (type) {
case 'smsLogin':
case 2:
const smsValidaErr = this.smsValidate()
if(smsValidaErr) return this.$toast({title: smsValidaErr})
console.log('login api')
uni.navigateBack()
this.loginCommon(this.form)
break;
case 'accoutLogin':
case 3:
const accoutValidaErr = this.accoutValidate()
if(accoutValidaErr) return this.$toast({title: accoutValidaErr})
console.log('login api')
uni.navigateBack()
this.loginCommon(this.form)
break;
default:
break;
......@@ -95,27 +98,50 @@ export default {
getPhoneNumber (e) {
if(e.detail.errMsg === 'getPhoneNumber:fail user deny') return
const { iv, encryptedData } = e.detail
console.log(iv, encryptedData)
this.registerCommon({
this.loginCommon({
lgtype: 1,
encryptedData,
iv,
session_key: this.session_key
})
},
// 获取验证码
async handleSms() {
try{
if(this.smsDisabled) return console.log('验证码倒计时中')
const valiErr = this.phoneValidate()
if(valiErr) return this.$toast({title: valiErr})
const res = await loginSms(this.form.user)
timerFn(60, num => {
if(num <= 0) {
this.smsDisabled = false
this.smsMsg = '重新获取'
} else {
this.smsDisabled = true
this.smsMsg = `${num}S重试`
}
})
// console.log(res)
}catch(e){
console.log(e)
this.$toast({title: e.msg || '程序错误'})
}
},
// testTime() {
// timerFn(10, num => {
// this.str = num
// })
// },
handleForget() {
uni.navigateTo({
url: `/subPages/resetPwd/index`
})
}
}
}
</script>
<style scoped>
.btn_active {
background-color: yellow;
}
.test {
position: fixed;
right: 20rpx;
......
......@@ -12,18 +12,19 @@ const mutations = {
const actions = {
setCount: async ({ commit }) => {
const res = await cartCount()
const { status, data } = await cartCount()
console.log(status, data)
const count = res.res.count
console.log('vuex actions success ==========================================')
if(res.status && count > 0) {
commit('SETCOUNT', count)
uni.setTabBarBadge({
index: 2,
text: count > 99 ? '99+' : JSON.stringify(count)
})
} else {
wx.removeTabBarBadge({index: 2})
}
// if(res.status && count > 0) {
// commit('SETCOUNT', count)
// uni.setTabBarBadge({
// index: 2,
// text: count > 99 ? '99+' : JSON.stringify(count)
// })
// } else {
// wx.removeTabBarBadge({index: 2})
// }
}
}
......
import { getStorage, setStorage, removeStorage } from '@/lib/storage'
import { setStorageAsync, removeStorage } from '@/lib/storage'
// import store from '@/store/index.js'
import { login, logout } from '@/apis/user.js'
import Toast from '@/lib/toast/index.js'
const state = {
isLogin: true,
token: null,
openid: null,
unionid: null,
session_key: '',
cookieKey: 'JSESSIONID=aaaD4X0GrEplMbVRPm5Nx; path=/',
userInfo: {},
location: {},
isOverdue: false, // 服务是否过期
userInfo: null,
location: {}
isLogin: true,
}
const mutations = {
SETLOGINSTATUS(state, status) {
state.isLogin = status
},
SETTOKEN(state, token){
state.token = token
},
SETLOGINSTATUS(state, status) {
state.isLogin = status
},
SETUSERINFO(state, userInfo){
state.userInfo = userInfo
},
LOGIN(state, params) {
state.token = params.token
state.userInfo.user = params.user
},
LOGOUT(state){
state.token = ''
state.userInfo = ''
state.userInfo = {}
}
}
const actions = {
setLoginStatus: ({commit}, status) => {
commit('SETLOGINSTATUS', status)
},
setToken: ({commit}, token) => {
commit('SETTOKEN', token)
setStorage('token',token)
setStorageAsync('token',token)
},
setLoginStatus: ({commit}, status) => {
commit('SETLOGINSTATUS', status)
},
setLocation: async ({state}) => {
uni.getLocation({
......@@ -49,10 +57,29 @@ const actions = {
commit('SETUSERINFO', userInfo)
setStorage('userInfo',userInfo)
},
logout: ({commit})=>{
commit('LOGOUT')
removeStorage('token')
removeStorage('userInfo')
login: async ({commit}, form) => {
try{
const { status, data } = await login(form)
if(status) {
setStorageAsync('token', data.token)
commit('LOGIN', data)
uni.navigateBack()
}
}catch(e){
Toast({title: e.msg || '登录失败'})
}
},
logout: async ({commit})=>{
try{
const { status, data } = await logout()
if(status) {
commit('LOGOUT')
removeStorage('token')
}
}catch(e){
Toast({title: e.msg || '退出登录失败'})
}
}
}
......
import Validator from "@/utils/validate.js"
function phoneValidate() {
const phoneValidator = new Validator()
const form = this.form
phoneValidator.add(form.phone, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
return phoneValidator.validate()
}
function validateForm() {
const smsValidator = new Validator()
const form = this.form
smsValidator.add(form.phone, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
smsValidator.add(form.f_code, [{strategy: 'notEmpty', errorMsg: '验证码不能为空'}])
smsValidator.add(form.upwd, [{strategy: 'notEmpty', errorMsg: '密码不能为空'}, {strategy: 'minLength:8', errorMsg: '密码不能少于8位数字或字母'}],
smsValidator.add(form.upwd2, [{strategy: 'notEmpty', errorMsg: '确认密码不能为空'}, {strategy: `unique:${form.upwd}`, errorMsg: '两次输入的密码不一致'}]))
return smsValidator.validate()
}
export default {
phoneValidate,
validateForm
}
\ No newline at end of file
<template>
<view>
<view>
<input
type="number"
placeholder="请输入手机号码"
maxlength="11"
v-model="form.phone"
/>
</view>
<view>
<input
type="text"
placeholder="请输入验证码"
v-model="form.f_code"
/>
</view>
<view>
<input
type="text"
placeholder="请输入新密码"
v-model="form.upwd"
/>
</view>
<view>
<input
type="text"
placeholder="请再次输入密码"
v-model="form.upwd2"
/>
</view>
<button type="default" @click="handleSms">{{smsMsg}}</button>
<button type="default" @click="handleReset">重置</button>
</view>
</template>
<script>
import { mapState } from 'vuex'
import common from './common.js'
import { forgetPwd, forgetSms } from '@/apis/user.js'
import { timerFn } from '@/utils/common.js'
export default {
data() {
return {
smsMsg: '获取验证码',
smsDisabled: false,
form: {
phone: '',
f_code: '',
upwd: '',
upwd2: ''
}
}
},
methods: {
...common,
// 获取验证码
async handleSms() {
try{
if(this.smsDisabled) return console.log('验证码倒计时中')
const valiErr = this.phoneValidate()
if(valiErr) return this.$toast({title: valiErr})
const res = await forgetSms(this.form.phone)
console.log(res)
timerFn(60, num => {
if(num <= 0) {
this.smsDisabled = false
this.smsMsg = '重新获取'
} else {
this.smsDisabled = true
this.smsMsg = `${num}S重试`
}
})
// console.log(res)
}catch(e){
console.log(e)
this.$toast({title: e.msg || '程序错误'})
}
},
async handleReset() {
try{
const validateErr = this.validateForm()
console.log(validateErr)
if(validateErr) return this.$toast({title: validateErr})
const { status, msg } = await forgetPwd(this.form)
if(status) {
this.$toast({title: msg, cb: () => {uni.navigateBack()}})
}
}catch(e){
this.$toast({title: e.msg || '程序错误'})
}
}
}
}
</script>
<style>
</style>
<template>
<view>1</view>
</template>
<script>
</script>
<style>
</style>
import { checkLoginTime, slelentLogin, wxLogin } from '@/apis/user.js'
import { wxLogin } from '@/apis/user.js'
import store from '@/store/index.js'
import { isFunction } from '@/utils/types.js'
export async function login_slilen(cb) {
const checkRes = await checkLoginTime()
if(checkRes.status) {
const res = await slelentLogin('13143340532', checkRes.res.time)
store.state.user.userInfo = res.userInfo
return true
} else {
return false
}
}
export async function login_wx() {
return new Promise(()=> {
uni.login({
success: async res => {
const loginRes = await wxLogin(res.code)
console.log(store)
if(loginRes.status) {
store.state.user.openid = loginRes.res.openid
store.state.user.unionid = loginRes.res.unionid
store.state.user.session_key = loginRes.res.session_key
}
// wx.login
export function login_wx(cb) {
uni.login({
success: async res => {
const { status, data } = await wxLogin(res.code)
cb && isFunction(cb) && cb()
if(status) {
store.state.user.openid = data.openid
store.state.user.unionid = data.unionid
store.state.user.session_key = data.session_key
}
})
}
})
}
\ No newline at end of file
......@@ -10,6 +10,9 @@ const validate_strategies = {
},
isHttp: (value, errorMsg) => {
if(!value.startsWith("http")) return errorMsg;
},
unique: (value, preValue, errorMsg) => {
if(preValue !== value) return errorMsg
}
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment