Merge branch 'develop' of ***REMOVED*** into develop

This commit is contained in:
07akioni 2019-07-25 10:11:18 +08:00
commit 9da46498ab
16 changed files with 1445 additions and 8 deletions

View File

@ -24,7 +24,7 @@ Run `npm run test` to test all components.
Run `npm run test-cov` to test all components and see detailed test coverage report.
## Want to see how component works
1. Run `npm run build`
1. Run `npm run dev`
2. Open `http://localhost:8086/` in browser.
## Want to add your own component
There is no guideline for now. If you want to know how to do it, you can explore by yourself or ask `lecong.zhang@tusimple.ai`.

View File

@ -0,0 +1,908 @@
<template>
<div
ref="doc"
class="n-doc"
>
<div class="n-doc-header">
<n-gradient-text :font-size="20">
Form / n-form
</n-gradient-text>
</div>
<div class="n-doc-body">
<div class="n-doc-section">
<div class="n-doc-section__header">
Inline Form
</div>
<div class="n-doc-section__view">
<n-form
inline
:label-width="80"
>
<n-form-item label="name">
<n-input placeholder="Input your name" />
</n-form-item>
<n-form-item label="age">
<n-input placeholder="Input your age" />
</n-form-item>
<n-form-item label="phone">
<n-input placeholder="Input your phone number" />
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea>
<n-form inline
:label-width="80">
<n-form-item :model="form" label="name">
<n-input v-model="form.name" placeholder="Input your name" />
</n-form-item>
<n-form-item label="age">
<n-input v-model="form.age" placeholder="Input your age" />
</n-form-item>
<n-form-item label="phone">
<n-input v-model="form.phone" placeholder="Input your phone number" />
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
form: {
name: '',
age: '',
phone: ''
}
}
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form Item
</div>
<div class="n-doc-section__view">
<n-form :model="form">
<n-form-item label="Input">
<n-input
v-model="form.input"
placeholder="Enter sth"
/>
</n-form-item>
<n-form-item label="Select">
<n-select
v-model="form.select"
size="small"
placeholder="Please Select Type"
:items="items"
/>
</n-form-item>
<n-form-item label="Switch">
<n-switch v-model="form.switch" />
</n-form-item>
<n-form-item label="DatePicker">
<n-date-picker
v-model="form.dateTimeTimestamp"
type="datetime"
/>
</n-form-item>
<n-form-item label="Switch">
<n-radio
v-model="form.radio"
value="Definitely Maybe"
>
Definitely Maybe
</n-radio>
<n-radio
v-model="form.radio"
value="Be Here Now"
>
Be Here Now
</n-radio>
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea>
<n-form :model="form">
<n-form-item label="Input">
<n-input v-model="form.input" placeholder="Enter sth" />
</n-form-item>
<n-form-item label="Select">
<n-select size="small" v-model="form.select"
placeholder="Please Select Type" :items="items" />
</n-form-item>
<n-form-item label="Switch">
<n-switch v-model="form.switch" />
</n-form-item>
<n-form-item label="DatePicker">
<n-date-picker v-model="form.dateTimeTimestamp"
type="datetime" />
</n-form-item>
<n-form-item label="Switch">
<n-radio v-model="form.radio" value="Definitely Maybe">
Definitely Maybe</n-radio>
<n-radio v-model="form.radio" value="Be Here Now">
Be Here Now</n-radio>
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
items: [
{
label: "ArtifactoryLabel",
value: "Artifactory"
},
{
label: "Registry",
value: "Registry"
},
{
label: "Public",
value: "Public"
},
{
label: "Custom",
value: "Custom"
}
],
form: {
input: '',
select: '',
datepicker: 0,
switch: false,
radio: ''
}
}
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form of Label Position
</div>
<div class="n-doc-section__view">
<n-form
:label-width="200"
label-position="top"
>
<n-form-item label="Top1">
<n-input />
</n-form-item>
<n-form-item label="Top2">
<n-input />
</n-form-item>
<n-form-item
label-position="left"
label="Left"
>
<n-input />
</n-form-item>
<n-form-item
label-position="center"
label="Center"
>
<n-input />
</n-form-item>
<n-form-item
label-position="right"
label="Right"
>
<n-input />
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
<n-form-item
label="Action"
label-position="right"
>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea>
<n-form
:label-width="200"
label-position="top"
>
<n-form-item label="Top1">
<n-input/>
</n-form-item>
<n-form-item label="Top2">
<n-input/>
</n-form-item>
<n-form-item label-position="left" label="Left">
<n-input/>
</n-form-item>
<n-form-item label-position="center" label="Center">
<n-input/>
</n-form-item>
<n-form-item label-position="right" label="Right">
<n-input/>
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
<n-form-item label="Action" label-position="right">
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Validate Form
</div>
<div class="n-doc-section__view">
<n-form
ref="form-validate"
:model="validateForm"
:rules="validateRules"
>
<n-form-item
label="Warning"
label-position="top"
>
The key in form-model does not support the form with ' . '.<br>
And does not init the value of parameters with 'undefined'.<br>
ResetForm Method: only can reset the item with prop.<br>
ValidateForm Method: support validate specified items by the second parameter in form of array.<br>
</n-form-item>
<n-form-item
:required-logo="false"
prop="input"
label="Input"
>
<n-input
v-model="validateForm.input"
placeholder="Enter string"
/>
</n-form-item>
<n-form-item
prop="muti.deep.select"
label="Select"
>
<n-select
v-model="validateForm.muti.deep.select"
placeholder="Please Select Type"
:items="items"
/>
</n-form-item>
<n-form-item
label="Switch"
prop="switch"
>
<n-switch v-model="validateForm.switch" />
</n-form-item>
<n-form-item
label="DatePicker"
prop="datepicker"
>
<n-date-picker
v-model="validateForm.datepicker"
type="date"
/>
</n-form-item>
<n-form-item
prop="radio"
label="Radio"
>
<n-radio
v-model="validateForm.radio"
value="Definitely Maybe"
>
Definitely Maybe
</n-radio>
<n-radio
v-model="validateForm.radio"
value="Be Here Now"
>
Be Here Now
</n-radio>
</n-form-item>
<n-form-item>
<n-button @click="formValidate('form-validate')">
Submit
</n-button>
<n-button @click="formReset('form-validate')">
Reset
</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-form ref="form-validate" disabled :model="validateForm" :rules="validateRules">
<n-form-item prop="input" label="Input">
<n-input v-model="validateForm.input" placeholder="Enter string" />
</n-form-item>
<n-form-item prop="muti.deep.select" label="Select">
<n-select
size="small"
v-model="validateForm.muti.deep.select"
placeholder="Please Select Type"
:items="items"
/>
</n-form-item>
<n-form-item label="Switch" prop="switch">
<n-switch v-model="validateForm.switch" />
</n-form-item>
<n-form-item label="DatePicker" prop="datepicker">
<n-date-picker v-model="validateForm.datepicker" type="date" />
</n-form-item>
<n-form-item
prop="radio"
label="Radio">
<n-radio v-model="validateForm.radio" value="Definitely Maybe">
Definitely Maybe
</n-radio>
<n-radio v-model="validateForm.radio" value="Be Here Now">
Be Here Now
</n-radio>
</n-form-item>
<n-form-item>
<n-button @click="formValidate('form-validate')">
Submit
</n-button>
<n-button @click="formReset('form-validate')">
Reset
</n-button>
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
validateForm: {
input: "",
muti: {
deep: {
select: ""
}
},
datepicker: "",
switch: false,
radio: ""
},
validateRules: {
input: [
{ required: true, message: "input cannot be empty", trigger: "blur" }
],
"muti.deep.select": [
{
required: true,
message: "select cannot be empty",
trigger: "change"
}
],
radio: [
{
required: true,
message: "radio please choose some",
trigger: "change"
}
],
datepicker: [
{
required: true,
type: "date",
message: "Please select the date",
trigger: "change"
}
]
}
}
},
methods: {
formValidate(ref) {
// two ways to use, the first used most
// this.$refs[ref].validate((flag, res) => {
// console.log("validate all result", flag, res);
// });
new Promise((resolve, reject) => {
this.$refs[ref].validate((valid, filed) => {
if (valid) {
resolve(filed);
} else {
reject(filed);
}
});
})
.then(f => {
console.log("pass", f);
})
.catch(e => {
console.log("unpass", e);
});
},
formReset (ref) {
this.$refs[ref].resetForm();
},
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form of Custom validate rules
</div>
<div class="n-doc-section__view">
<n-form
ref="custom"
:model="custom"
:rules="customRules"
:label-width="80"
>
<n-form-item
prop="age"
label="Age"
>
<n-input v-model.number="custom.age" />
</n-form-item>
<n-form-item
prop="pass"
label="Password"
>
<n-input
v-model="custom.pass"
/>
</n-form-item>
<n-form-item
prop="checkPass"
label="CheckPass"
>
<n-input v-model="custom.checkPass" />
</n-form-item>
<n-form-item>
<n-button @click="formValidate('custom')">
Commit
</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-form ref="custom" :model="custom" :rules="customRules" :label-width="80">
<n-form-item prop="age" label="Age">
<n-input v-model.number="custom.age"></n-input>
</n-form-item>
<n-form-item prop="pass" label="Password">
<n-input v-model="custom.pass"></n-input>
</n-form-item>
<n-form-item prop="checkPass" label="CheckPass">
<n-input v-model="custom.checkPass"></n-input>
</n-form-item>
<n-form-item>
<n-button @click="formValidate('custom')">Commit</n-button>
</n-form-item>
</n-form>
<script>
export default {
data() {
return {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error("Input age"));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error("Number required"));
} else {
if (value < 18) {
callback(new Error("Age should over 18"));
} else {
callback();
}
}
}, 1000);
};
var validatePass = (rule, value, callback) => {
if (value === "") {
callback(new Error("Input password"));
} else {
if (this.custom.checkPass !== "") {
this.$refs.custom.validate("", ["checkPass"]);
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === "") {
callback(new Error("Input password again"));
} else if (value !== this.custom.pass) {
callback(new Error("ent input password twice!"));
} else {
callback();
}
};
custom: {
pass: "",
checkPass: "",
age: ""
},
customRules: {
pass: [{ validator: validatePass, trigger: "blur" }],
checkPass: [{ validator: validatePass2, trigger: "blur" }],
age: [{ validator: checkAge, trigger: "blur" }]
}
}
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form of Dynamic
</div>
<div class="n-doc-section__view">
<n-form
ref="custom"
:model="dynamic"
:rules="dynamicRules"
:label-width="80"
>
<n-form-item
v-for="(item, k) in dynamic.email"
:key="k"
:prop="'email.' + k"
:label="k"
>
<n-input v-model="dynamic['email'][k]" />
</n-form-item>
<n-form-item>
<n-button @click="dynamicAddItem">
Add
</n-button>
<n-button @click="dynamicPopItem">
Delete
</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-form ref="dynamic" :model="dynamic" :rules="dynamicRules" :label-width="80">
<n-form-item
:key="k"
:prop="'email.' + k"
:label="k"
v-for="(item, k) in dynamic.email"
>
<n-input v-model="dynamic['email'][k]"></n-input>
</n-form-item>
<n-form-item>
<n-button @click="dynamicAddItem">Add item</n-button>
<n-button @click="dynamicPopItem">Delete</n-button>
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
dynamic: {
name: "",
email: {
email0: ""
}
},
dynamicRules: {
"email.email0": [
{
required: true,
message: "Mailbox cannot be empty",
trigger: "blur"
},
{ type: "email", message: "Incorrect email format", trigger: "blur" }
]
}
}
},
methods: {
dynamicAddItem() {
let i = Object.keys(this.dynamic.email).length;
this.$set(this.dynamic.email, "email" + i, "");
// dynamicRules
this.$set(
this.dynamicRules,
"email.email" + i,
this.dynamicRules["email.email0"]
);
},
dynamicPopItem () {
let keys = Object.keys(this.dynamic.email)
let k = keys[keys.length - 1]
if (k) {
delete this.dynamic.email[k]
}
}
}
}
</script>
</textarea>
</div>
</div>
</div>
</div>
</template>
<script>
import docCodeEditorMixin from './docCodeEditorMixin'
export default {
mixins: [docCodeEditorMixin],
data () {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('Input age'))
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('Number required'))
} else {
if (value < 18) {
callback(new Error('Age should over 18'))
} else {
callback()
}
}
}, 1000)
}
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('Input password'))
} else {
if (this.custom.checkPass !== '') {
this.$refs.custom.validate('', ['checkPass'])
}
callback()
}
}
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('Input password again'))
} else if (value !== this.custom.pass) {
callback(new Error('ent input password twice!'))
} else {
callback()
}
}
return {
form: {
name: '',
age: '',
phone: '',
// second form
input: '',
select: '',
datepicker: '',
switch: false,
radio: ''
},
items: [
{
label: 'ArtifactoryLabel',
value: 'Artifactory'
},
{
label: 'Registry',
value: 'Registry'
},
{
label: 'Public',
value: 'Public'
},
{
label: 'Custom',
value: 'Custom'
}
],
validateForm: {
input: 'input',
muti: {
deep: {
select: 'Public'
}
},
datepicker: 0,
switch: false,
radio: ''
},
validateRules: {
input: [
{ required: true, message: 'input cannot be empty', trigger: 'blur' }
],
'muti.deep.select': [
{
required: true,
message: 'select cannot be empty',
trigger: 'change'
}
],
radio: [
{
required: true,
message: 'radio please choose some',
trigger: 'change'
}
],
datepicker: [
{
required: true,
type: 'date',
message: 'Please select the date',
trigger: 'change'
}
]
},
ruleValidate: {
name: [
{
required: true,
message: 'The name cannot be empty',
trigger: 'blur'
}
],
mail: [
{
required: true,
message: 'Mailbox cannot be empty',
trigger: 'blur'
},
{ type: 'email', message: 'Incorrect email format', trigger: 'blur' }
],
city: [
{
required: true,
message: 'Please select the city',
trigger: 'change'
}
],
gender: [
{ required: true, message: 'Please select gender', trigger: 'change' }
],
interest: [
{
required: true,
type: 'array',
min: 1,
message: 'Choose at least one hobby',
trigger: 'change'
},
{
type: 'array',
max: 2,
message: 'Choose two hobbies at best',
trigger: 'change'
}
],
date: [
{
required: true,
type: 'date',
message: 'Please select the date',
trigger: 'change'
}
],
time: [
{
required: true,
type: 'string',
message: 'Please select time',
trigger: 'change'
}
],
desc: [
{
required: true,
message: 'Please enter a personal introduction',
trigger: 'blur'
},
{
type: 'string',
min: 20,
message: 'Introduce no less than 20 words',
trigger: 'blur'
}
]
},
custom: {
pass: '',
checkPass: '',
age: ''
},
customRules: {
pass: [{ validator: validatePass, trigger: 'blur' }],
checkPass: [{ validator: validatePass2, trigger: 'blur' }],
age: [{ validator: checkAge, trigger: 'blur' }]
},
dynamic: {
name: '',
email: {
email0: ''
}
},
dynamicRules: {
'email.email0': [
{
required: true,
message: 'Mailbox cannot be empty',
trigger: 'blur'
},
{ type: 'email', message: 'Incorrect email format', trigger: 'blur' }
]
}
}
},
methods: {
formValidate (ref) {
// this.$refs[ref].validate((flag, res) => {
// console.log('validate all result', flag, res);
// })
new Promise((resolve, reject) => {
this.$refs[ref].validate((valid, filed) => {
if (valid) {
resolve(filed)
} else {
reject(filed)
}
})
})
.then(f => {
console.log('pass', f)
})
.catch(e => {
console.log('unpass', e)
})
},
formReset (ref) {
this.$refs[ref].resetForm()
},
dynamicAddItem () {
let i = Object.keys(this.dynamic.email).length
this.$set(this.dynamic.email, 'email' + i, '')
// dynamicRules
this.$set(
this.dynamicRules,
'email.email' + i,
this.dynamicRules['email.email0']
)
},
dynamicPopItem () {
let keys = Object.keys(this.dynamic.email)
let k = keys.pop()
if (k) {
this.$delete(this.dynamic.email, k)
}
}
}
}
</script>

View File

@ -130,6 +130,10 @@ export default {
{
name: 'Tooltip',
path: '/n-tooltip'
},
{
name: 'Form',
path: '/n-form'
}
]
},

View File

@ -26,6 +26,7 @@ import datePickerDemo from './components/datePickerDemo'
import inputNumberDemo from './components/inputNumberDemo'
import nimbusIconDemo from './components/nimbusIconDemo'
import radioDemo from './components/radioDemo'
import formDemo from './components/formDemo'
import timePickerDemo from './components/timePickerDemo'
import notificationDemo from './components/notificationDemo'
@ -79,6 +80,7 @@ const routes = [
{ path: '/n-input-number', component: inputNumberDemo },
{ path: '/n-nimbus-icon', component: nimbusIconDemo },
{ path: '/n-radio', component: radioDemo },
{ path: '/n-form', component: formDemo },
{ path: '/n-time-picker', component: timePickerDemo }
]
},

View File

@ -22,7 +22,7 @@ You <strong>MUST</strong> fix all lint warnings and errors before you push your
Run <code>npm run test-cov</code> to test all components and see detailed test coverage report.</p>
<h2 id="wanttoseehowcomponentworks">Want to see how component works</h2>
<ol>
<li>Run <code>npm run build</code></li>
<li>Run <code>npm run dev</code></li>
<li>Open <code>http://localhost:8086/</code> in browser.</li>
</ol>
<h2 id="wanttoaddyourowncomponent">Want to add your own component</h2>

View File

@ -23,8 +23,8 @@ import Alert from './packages/common/Alert'
import DatePicker from './packages/common/DatePicker'
import InputNumber from './packages/common/InputNumber'
import Radio from './packages/common/Radio'
import Form from './packages/common/Form'
import TimePicker from './packages/common/TimePicker'
import ServiceCard from './packages/nimbus/ServiceCard'
import HomeLayout from './packages/nimbus/HomeLayout'
import Navbar from './packages/nimbus/Navbar'
@ -66,6 +66,7 @@ function install (Vue) {
InputNumber.install(Vue)
NimbusIcon.install(Vue)
Radio.install(Vue)
Form.install(Vue)
TimePicker.install(Vue)
}

View File

@ -0,0 +1,10 @@
/* istanbul ignore file */
import Form from './src/main.vue'
import FormItem from './src/form-item.vue'
Form.install = function (Vue) {
Vue.component(Form.name, Form)
Vue.component(FormItem.name, FormItem)
}
export default Form

View File

@ -0,0 +1,194 @@
<template>
<div
:class="getFormClass()"
>
<label
v-if="label !== undefined"
:class="`${prefix}__label`"
:style="style"
>
{{ label }}
</label>
<div
class="n-form-item__content"
:class="validateFlag ? `${prefix}__content--error` : `${prefix}__content--pass`"
>
<slot />
<div :class="`${prefix}__validate`">
{{ validateInfo }}
</div>
</div>
</div>
</template>
<script>
import AsyncValidator from 'async-validator'
import { getObjValue } from '../../../utils/index'
export default {
name: 'NFormItem',
props: {
label: {
type: String,
default: undefined
},
labelWidth: {
type: Number,
default: undefined
},
labelStyle: {
type: String,
default: undefined
},
labelPosition: {
type: String,
default: undefined
},
prop: {
type: String,
default: undefined
},
required: {
type: Boolean,
default: undefined
},
requiredLogo: {
type: Boolean,
default: undefined
}
},
inject: ['form'],
provide () {
return {
formItem: this
}
},
data () {
return {
prefix: 'n-form-item',
validateInfo: '',
validateFlag: false
}
},
computed: {
style () {
let s = {
width: null
}
let lW = this.getValue('labelWidth')
s.width = lW ? lW + 'px' : 'auto'
return s
},
requiredRule () {
let rule = []
if (this.required) {
rule.push({
trigger: 'blur',
required: true
})
}
return rule
}
},
watch: {
// disabled (n) {
// this.$children.forEach(i => {
// if (getObjValue(i, '$options.name.0'.split('.')) === 'N') {
// // dirctly modify the prop value is forbbiden by vue,
// // neither definePropery or proxy pass
// // we need to update every components we want to influence
// // i.disabled = n
// }
// })
// }
},
created () {
this.validateEventListener()
},
beforeDestroy () {
this.$off('on-form-blur', this.onFormBlur)
this.$off('on-form-change', this.onFormChange)
},
methods: {
getValue (key) {
return this[key] === undefined ? this.form[key] : this[key]
},
getFormClass () {
let cls = ['n-form-item']
let pre = 'n-form-item__label--'
cls.push(pre + this.getValue('labelPosition'))
if (!this.getValue('requiredLogo')) {
return cls
}
if (this.required) {
cls.push(pre + 'require')
} else if (this.form && this.form.rules && this.prop) {
let flag = (this.form.rules[this.prop] || []).some(i => {
return i.required
})
if (flag) {
cls.push(pre + 'require')
}
}
return cls
},
getRules () {
let rules = []
if (this.required) {
rules.push(this.requiredRule)
}
if (this.form && this.form.rules && this.prop) {
(this.form.rules[this.prop] || []).forEach(i => {
rules.push(i)
})
}
return rules
},
onFormBlur () {
this.validate('blur')
},
onFormChange () {
this.validate('change')
},
/**
* form item validation, can specify rules
*/
validate (trigger = '', cb = () => {}) {
if (!this.prop || typeof this.prop !== 'string') {
return
}
let rules = this.getRules()
//
let prop = this.prop
let value = getObjValue(this.form.model, this.prop.split('.'))
if (this.prop.indexOf('.') > -1) {
prop = prop.slice(prop.lastIndexOf('.') + 1)
}
let activeRule = trigger === '' ? rules : rules.filter(i => {
return i.trigger === trigger
})
if (activeRule.length === 0) return
const asyncValidator = new AsyncValidator({ [prop]: activeRule })
asyncValidator.validate({ [prop]: value }, (errors, fields) => {
if (errors) {
this.validateInfo = errors[0].message
this.validateFlag = true
} else {
this.clearValidateClass()
}
cb(errors[0].message || false, fields)
})
},
clearValidateClass () {
this.validateInfo = ''
this.validateFlag = false
},
validateEventListener () {
const rules = this.getRules()
if (rules.length >= 0) {
this.$on('on-form-blur', this.onFormBlur)
this.$on('on-form-change', this.onFormChange)
}
}
}
}
</script>

View File

@ -0,0 +1,124 @@
<template>
<form
class="n-form"
:class="{
'n-form--inline': inline
}"
>
<slot />
</form>
</template>
<script>
import { deepClone, getObjValue } from '../../../utils/index'
export default {
name: 'NForm',
provide () {
return {
form: this
}
},
props: {
inline: {
type: Boolean,
default: false
},
labelWidth: {
type: Number,
default: 80
},
labelPosition: {
type: String,
default: 'right' // ['top', 'right', 'left', 'center']
},
model: {
type: Object,
default: function () {
return {}
}
},
rules: {
type: Object,
default: function () {
return {}
}
},
requiredLogo: {
type: Boolean,
default: true
}
},
data () {
return {
initialValue: ''
}
},
created () {
this.initialValue = deepClone(this.model)
},
methods: {
getLabelPosClass (labelPosition) {
return 'n-form--lable-' + labelPosition
},
/**
* form validation, validate all prop-elements by default,
* can use specify the scope of validation by param part.
*
* @param {Funtion} cb callback
* @param {Array} scope to specify the scope of validation
* @return {Boolean} validation passed or not
*/
validate (cb, scope = []) {
let promise
let isCallback = typeof cb === 'function'
if (!isCallback && window.Promise) {
promise = new Promise((resolve, reject) => {
cb = valid => valid ? resolve(valid) : reject(valid)
})
}
let valid = true
let fields = {}
this.$children.forEach((child, i) => {
let flag = scope.length > 0 ? scope.indexOf(child.prop) > -1 : true
if (child.prop && flag) {
child.validate('', (errors, field) => {
if (errors) {
valid = false
}
fields = Object.assign({}, fields, field)
})
}
if (++i === this.$children.length && isCallback) {
cb(valid, fields)
}
})
if (promise) {
return promise
}
},
/**
* just can reset the value with prop in form-item
*/
resetForm () {
this.$children.forEach(child => {
if (child.prop) {
let keys = child.prop.split('.')
let obj = this.model
let j = 0
keys.forEach((k, i) => {
if (i !== keys.length - 1) {
obj = obj[k]
}
j = i
})
obj[keys[j]] = getObjValue(this.initialValue, keys)
if (child.validateFlag) {
child.clearValidateClass()
}
}
})
}
}
}
</script>

View File

@ -66,12 +66,22 @@
<script>
import nIcon from '../../Icon'
import Emitter from '../../../mixins/emitter'
export default {
name: 'NInput',
components: {
nIcon
},
mixins: [ Emitter ],
inject: {
form: {
default: null
},
formItem: {
default: null
}
},
model: {
prop: 'value',
event: 'input'
@ -119,6 +129,11 @@ export default {
isComposing: false
}
},
computed: {
validateState () {
return this.formItem ? this.formItem.validateState : ''
}
},
methods: {
handleCompositionStart () {
this.isComposing = true
@ -133,6 +148,10 @@ export default {
},
handleBlur (e) {
this.$emit('blur', e)
// ,
if (this.formItem) {
this.dispatch('NFormItem', 'on-form-blur', e.target.value)
}
},
handleFocus (e) {
this.$emit('focus', e)
@ -142,6 +161,9 @@ export default {
this.$emit('keyup', e)
},
handleChange (e) {
if (this.formItem) {
this.dispatch('NFormItem', 'on-form-change', e.target.value)
}
this.$emit('change', e.target.value)
},
handleClick (e) {

View File

@ -20,12 +20,20 @@
</template>
<script>
import Emitter from '../../../mixins/emitter'
export default {
name: 'NRadio',
mixins: [ Emitter ],
model: {
prop: 'privateValue',
event: 'input'
},
inject: {
formItem: {
default: null
}
},
props: {
value: {
type: [Boolean, String, Number],
@ -50,6 +58,9 @@ export default {
if (this.disabled) return
if (this.privateValue !== this.value) {
this.$emit('input', this.value)
if (this.formItem) {
this.dispatch('NFormItem', 'on-form-change', this.value)
}
}
}
}

View File

@ -85,17 +85,23 @@
</template>
<script>
import Emitter from '../../../mixins/emitter'
import detachable from '../../../mixins/detachable'
import placeable from '../../../mixins/placeable'
import toggleable from '../../../mixins/toggleable'
export default {
name: 'NSingleSelect',
mixins: [detachable, toggleable, placeable],
mixins: [detachable, toggleable, placeable, Emitter],
model: {
prop: 'selectedValue',
event: 'input'
},
inject: {
formItem: {
default: null
}
},
props: {
items: {
type: Array,
@ -159,9 +165,12 @@ export default {
}
},
watch: {
selectedItem () {
selectedItem (n, o) {
if (this.selectedItem !== null) {
this.label = this.selectedItem.label
if (n !== o && this.formItem) {
this.dispatch('NFormItem', 'on-form-change', n.value)
}
} else {
this.label = ''
}

View File

@ -0,0 +1,33 @@
function broadcast (componentName, eventName, params) {
this.$children.forEach(child => {
let name = child.$options.componentName
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params))
} else {
broadcast.apply(child, [componentName, eventName].concat([params]))
}
})
}
export default {
methods: {
dispatch (componentName, eventName, params) {
let parent = this.$parent || this.$root
let name = parent.$options.name
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.name
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
},
broadcast (componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params)
}
}
}

View File

@ -1,4 +1,34 @@
import getScrollParent from './getScrollParent'
export {
getScrollParent
import getScrollParent from './dom/getScrollParent'
const isObject = (o) => {
let type = Object.prototype.toString.call(o)
return type === '[object Array]' || type === '[object Object]'
}
const _isObject = (o) => {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
const deepClone = (obj) => {
if (!isObject(obj)) {
return obj
}
let isArray = Array.isArray(obj)
let cloneObj = isArray ? [] : {}
for (let key in obj) {
cloneObj[key] = _isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
}
return cloneObj
}
const getObjValue = (obj, keys) => {
return keys.reduce((res, n) => (res !== undefined && res[n] !== undefined ? res[n] : null), obj)
}
export {
getScrollParent,
deepClone,
getObjValue
}

88
styles/Form.scss Normal file
View File

@ -0,0 +1,88 @@
@import './mixins/mixins.scss';
@import './theme/default.scss';
@include b(form) {
width: 100%;
font-size: 14px;
color: rgba(255, 255, 255, 1);
&.n-form--inline {
display: flex;
align-items: center;
align-content: space-around;
flex-wrap: nowrap;
.n-form-item:not(:last-child) {
margin-right: 10px;
}
}
.n-form-item__label--top {
display: block;
.n-form-item__label {
display: block;
overflow-wrap: unset;
align-items: center;
text-align: left;
}
}
.n-form-item__label--require .n-form-item__label::before {
float: right;
content: '*';
color:rgba(255, 146, 164, 1);
margin-left: 5px;
}
.n-form-item__label--left .n-form-item__label {
text-align: left;
}
.n-form-item__label--right .n-form-item__label {
text-align: right;
}
.n-form-item__label--center .n-form-item__label {
text-align: center;
}
.n-form--disable {
box-shadow: inset 0 0 0px 1px rgb(99, 96, 96);
background-color: rgba(255, 255, 255, 0.1);
cursor: not-allowed;
&:hover {
box-shadow: inset 0 0 0px 1px rgb(99, 96, 96);
background-color: rgba(255, 255, 255, 0.1);
}
}
}
@include b(form-item) {
display: flex;
width: 100%;
align-items: center;
margin-bottom: 24px;
.n-form-item__label {
flex-grow: 0;
padding: 10px 10px 10px 0;
overflow-wrap: break-word;
}
.n-form-item__content {
position: relative;
flex-grow: 1;
}
.n-form-item__validate {
position: absolute;
bottom: -18px;
margin-top: 6px;
color: rgba(255, 146, 164, 1);
font-size: 14px;
}
.n-form-item__content--pass {
.n-form-item__validate {
visibility: hidden;
}
}
.n-form-item__content--error {
.n-form-item__validate {
visibility: visible;
}
// 后期可以通过属性优化, 精确定位
input, select, textarea {
box-shadow: inset 0 0 0px 1px rgba(255, 146, 164, 1);
background-color: rgba(255, 255, 255, 0.1);
}
}
}

View File

@ -21,6 +21,7 @@
@import './DatePicker.scss';
@import './InputNumber.scss';
@import './Radio.scss';
@import './Form.scss';
@import './TimePicker.scss';
@import "./NimbusServiceLayout.scss";