mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2024-12-15 04:42:23 +08:00
Merge branch 'develop' of ***REMOVED*** into develop
This commit is contained in:
commit
6eb8c9651c
@ -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`.
|
||||
|
@ -9,13 +9,13 @@
|
||||
>
|
||||
<!--EXAMPLE_START-->
|
||||
<n-date-picker
|
||||
v-model="dateTimeTimestamp"
|
||||
v-model="ts1"
|
||||
type="datetime"
|
||||
style="margin-right: 12px;"
|
||||
@change="onDateTimeChange"
|
||||
/>
|
||||
<n-date-picker
|
||||
v-model="dateTimestamp"
|
||||
v-model="ts2"
|
||||
type="date"
|
||||
@change="onDateChange"
|
||||
/>
|
||||
@ -26,18 +26,20 @@
|
||||
style="flex-wrap: nowrap;"
|
||||
>
|
||||
<n-input
|
||||
v-model="dateTimeTimestamp"
|
||||
placeholder="dateTimeTimestamp"
|
||||
:value="ts1"
|
||||
placeholder="ts1"
|
||||
type="text"
|
||||
style="margin-right: 12px;"
|
||||
@input="handleInputTs1"
|
||||
/>
|
||||
<n-input
|
||||
v-model="dateTimestamp"
|
||||
placeholder="dateTimestamp"
|
||||
:value="ts2"
|
||||
placeholder="ts2"
|
||||
type="text"
|
||||
@input="handleInputTs2"
|
||||
/>
|
||||
</div>
|
||||
<pre class="n-doc-section__inspect">datetime v-model: {{ dateTimeTimestamp }}, date v-model: {{ dateTimestamp }}</pre>
|
||||
<pre class="n-doc-section__inspect">datetime v-model: {{ JSON.stringify(ts1) }}, date v-model: {{ JSON.stringify(ts2) }}</pre>
|
||||
<n-doc-source-block>
|
||||
<!--SOURCE-->
|
||||
</n-doc-source-block>
|
||||
@ -48,8 +50,8 @@
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
dateTimeTimestamp: 891360258000,
|
||||
dateTimestamp: 891360258000
|
||||
ts1: null,
|
||||
ts2: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -58,6 +60,13 @@ export default {
|
||||
},
|
||||
onDateChange (timestamp, dateString) {
|
||||
this.$NMessage.success(`${timestamp}, ${dateString}`)
|
||||
},
|
||||
handleInputTs1 (v) {
|
||||
v = Number(v)
|
||||
this.ts1 = Number.isNaN(v) ? null : v
|
||||
},
|
||||
handleInputTs2 (v) {
|
||||
this.ts2 = Number.isNaN(v) ? null : v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
908
demo/components/formDemo.vue
Normal file
908
demo/components/formDemo.vue
Normal 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>
|
@ -15,7 +15,6 @@
|
||||
</div>
|
||||
<div class="n-doc-section__view">
|
||||
<n-nimbus-service-layout
|
||||
v-model="currentFileld"
|
||||
icon="md-musical-notes"
|
||||
name="Oasis"
|
||||
:items="[
|
||||
@ -65,7 +64,6 @@
|
||||
<textarea><n-nimbus-service-layout
|
||||
icon="md-musical-notes"
|
||||
name="Oasis"
|
||||
v-model="'Hello'"
|
||||
:items="[
|
||||
{
|
||||
name: 'Definitely Maybe',
|
||||
@ -120,7 +118,7 @@ export default {
|
||||
mixins: [docCodeEditorMixin],
|
||||
data () {
|
||||
return {
|
||||
currentFileld: 'Hello'
|
||||
currentField: 'Hello'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
34
demo/components/timePickerDemo/basicUsage.demo.vue
Normal file
34
demo/components/timePickerDemo/basicUsage.demo.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="n-doc-section">
|
||||
<div class="n-doc-section__header">
|
||||
Basic Usage
|
||||
</div>
|
||||
<div
|
||||
class="n-doc-section__view"
|
||||
style="flex-wrap: nowrap;"
|
||||
>
|
||||
<!--EXAMPLE_START-->
|
||||
<n-time-picker
|
||||
v-model="time0"
|
||||
style="margin-right: 14px;"
|
||||
/>
|
||||
<n-time-picker v-model="time1" />
|
||||
<!--EXAMPLE_END-->
|
||||
</div>
|
||||
<pre class="n-doc-section__inspect">time0: {{ JSON.stringify(time0) }}, time1: {{ JSON.stringify(time1) }}</pre>
|
||||
<n-doc-source-block>
|
||||
<!--SOURCE-->
|
||||
</n-doc-source-block>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
time0: null,
|
||||
time1: 1563937380000
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
32
demo/components/timePickerDemo/index.vue
Normal file
32
demo/components/timePickerDemo/index.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div
|
||||
ref="doc"
|
||||
class="n-doc"
|
||||
>
|
||||
<div class="n-doc-header">
|
||||
<n-gradient-text :font-size="20">
|
||||
TimePicker / n-time-picker
|
||||
</n-gradient-text>
|
||||
</div>
|
||||
<div class="n-doc-body">
|
||||
<basicUsage />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import basicUsage from './basicUsage.demo.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
basicUsage
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
23
demo/debugComponents/popoverDebug.vue
Normal file
23
demo/debugComponents/popoverDebug.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div style="height: 3000px;">
|
||||
<div style="height: 400px;" />
|
||||
<div style="margin-left: 400px;">
|
||||
<n-popover trigger="click">
|
||||
<template v-slot:activator>
|
||||
<button>Activator</button>
|
||||
</template>
|
||||
<span>Out Out Out</span>
|
||||
</n-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
17
demo/debugComponents/routerDebug.vue
Normal file
17
demo/debugComponents/routerDebug.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div style="padding: 100px;">
|
||||
<button @click="f">
|
||||
router debug
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
f () {
|
||||
this.$router.push('/n-popover?999')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -50,7 +50,6 @@ export default {
|
||||
},
|
||||
{
|
||||
name: 'Common',
|
||||
path: '/',
|
||||
childItems: [
|
||||
{
|
||||
name: 'AdvanceTable',
|
||||
@ -124,9 +123,30 @@ export default {
|
||||
name: 'Table',
|
||||
path: '/n-table'
|
||||
},
|
||||
{
|
||||
name: 'TimePicker',
|
||||
path: '/n-time-picker'
|
||||
},
|
||||
{
|
||||
name: 'Tooltip',
|
||||
path: '/n-tooltip'
|
||||
},
|
||||
{
|
||||
name: 'Form',
|
||||
path: '/n-form'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Debug',
|
||||
childItems: [
|
||||
{
|
||||
name: 'PopoverDebug',
|
||||
path: '/n-popover-debug'
|
||||
},
|
||||
{
|
||||
name: 'RouterDebug',
|
||||
path: '/n-router-debug'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ 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'
|
||||
import nimbusConfirmCardDemo from './components/nimbusConfirmCardDemo'
|
||||
@ -33,6 +35,9 @@ import paginationDemo from './components/paginationDemo'
|
||||
import startPage from './components/startPage'
|
||||
import demo from './demo'
|
||||
|
||||
import popoverDebug from './debugComponents/popoverDebug'
|
||||
import routerDebug from './debugComponents/routerDebug'
|
||||
|
||||
Vue.use(NaiveUI)
|
||||
Vue.use(VueRouter)
|
||||
|
||||
@ -43,6 +48,10 @@ const routes = [
|
||||
path: '/home-demo',
|
||||
component: homeDemo
|
||||
},
|
||||
{
|
||||
path: '/n-popover-debug',
|
||||
component: popoverDebug
|
||||
},
|
||||
{
|
||||
path: '/start',
|
||||
component: demo,
|
||||
@ -71,7 +80,10 @@ const routes = [
|
||||
{ path: '/n-date-picker', component: datePickerDemo },
|
||||
{ path: '/n-input-number', component: inputNumberDemo },
|
||||
{ path: '/n-nimbus-icon', component: nimbusIconDemo },
|
||||
{ path: '/n-radio', component: radioDemo }
|
||||
{ path: '/n-radio', component: radioDemo },
|
||||
{ path: '/n-form', component: formDemo },
|
||||
{ path: '/n-time-picker', component: timePickerDemo },
|
||||
{ path: '/n-router-debug', component: routerDebug }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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>
|
||||
|
5
index.js
5
index.js
@ -23,7 +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'
|
||||
@ -65,6 +66,8 @@ function install (Vue) {
|
||||
InputNumber.install(Vue)
|
||||
NimbusIcon.install(Vue)
|
||||
Radio.install(Vue)
|
||||
Form.install(Vue)
|
||||
TimePicker.install(Vue)
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "naive-ui",
|
||||
"version": "0.2.29",
|
||||
"version": "0.2.33",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -6,16 +6,15 @@
|
||||
[`n-date-picker--${size}-size`]: true,
|
||||
'n-date-picker--disabled': disabled
|
||||
}"
|
||||
@click="handleCalendarClick"
|
||||
>
|
||||
<input
|
||||
v-model="displayDateTimeString"
|
||||
class="n-date-picker__input"
|
||||
:placeholder="placeholder"
|
||||
:readonly="disabled ? 'disabled' : false"
|
||||
@click.stop="openCalendar"
|
||||
@click="handleActivatorClick"
|
||||
@blur="handleDateTimeInputBlur"
|
||||
@keyup.enter="handleDateTimeInputEnter"
|
||||
@input="handleDateTimeInputInput"
|
||||
>
|
||||
<div class="n-date-picker__icon">
|
||||
<n-icon
|
||||
@ -29,9 +28,17 @@
|
||||
>
|
||||
<div ref="content">
|
||||
<datetime-panel
|
||||
v-model="panelValue"
|
||||
v-if="type === 'datetime'"
|
||||
:value="value"
|
||||
:active="active"
|
||||
@change="handlePanelValueChange"
|
||||
@input="handlePanelInput"
|
||||
@close="closeCalendar"
|
||||
/>
|
||||
<date-panel
|
||||
v-if="type === 'date'"
|
||||
:value="value"
|
||||
:active="active"
|
||||
@input="handlePanelInput"
|
||||
@close="closeCalendar"
|
||||
/>
|
||||
</div>
|
||||
@ -45,6 +52,7 @@ import NIcon from '../../Icon'
|
||||
import detachable from '../../../mixins/detachable'
|
||||
import placeable from '../../../mixins/placeable'
|
||||
import DatetimePanel from './panel/datetime'
|
||||
import DatePanel from './panel/date'
|
||||
import clickoutside from '../../../directives/clickoutside'
|
||||
|
||||
const DATE_FORMAT = {
|
||||
@ -69,16 +77,13 @@ export default {
|
||||
},
|
||||
components: {
|
||||
NIcon,
|
||||
DatetimePanel
|
||||
DatetimePanel,
|
||||
DatePanel
|
||||
},
|
||||
mixins: [
|
||||
detachable,
|
||||
placeable
|
||||
],
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
@ -89,7 +94,7 @@ export default {
|
||||
default: 'bottom-start'
|
||||
},
|
||||
value: {
|
||||
type: [Number, String],
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
@ -112,21 +117,14 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
displayDateTimeString: '',
|
||||
displayDateString: '',
|
||||
displayTimeString: '',
|
||||
rightDisplayDateTimeString: '',
|
||||
rightDisplayDateString: '',
|
||||
rightDisplayTimeString: '',
|
||||
calendarDateTime: moment(),
|
||||
rightCalendarDateTime: moment(),
|
||||
currentDateTime: moment(),
|
||||
active: false,
|
||||
showTimeSelector: false,
|
||||
showRightTimeSelector: false,
|
||||
calendar: [],
|
||||
rightCalendar: [],
|
||||
...TIME_CONST,
|
||||
panelValue: this.value
|
||||
...TIME_CONST
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -182,84 +180,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlePanelValueChange (value) {
|
||||
this.$emit('change', value)
|
||||
handlePanelInput (value, valueString) {
|
||||
this.$emit('input', value, 'unavailable for now')
|
||||
this.refreshSelectedDateTimeString()
|
||||
},
|
||||
/**
|
||||
* If new datetime is null or undefined, emit null to value.
|
||||
* Else adjust new datetime by props.type and emit it to value.
|
||||
*/
|
||||
setValue (newSelectedDateTime) {
|
||||
if (newSelectedDateTime === null || newSelectedDateTime === undefined) {
|
||||
this.$emit('change', null)
|
||||
return
|
||||
}
|
||||
if (newSelectedDateTime.isValid()) {
|
||||
const adjustedDateTime = this.adjustDateTimeAccrodingToType(newSelectedDateTime)
|
||||
if (this.computedSelectedDateTime === null || adjustedDateTime.valueOf() !== this.computedSelectedDateTime.valueOf()) {
|
||||
this.$emit('change', adjustedDateTime.valueOf(), adjustedDateTime.format(this.format))
|
||||
}
|
||||
}
|
||||
},
|
||||
adjustDateTimeAccrodingToType (datetime) {
|
||||
if (this.type === 'datetime') {
|
||||
return moment(datetime).startOf('second')
|
||||
} else {
|
||||
return moment(datetime).startOf('date').hour(0)
|
||||
}
|
||||
},
|
||||
justifySelectedDateTimeAfterChangeTimeString () {
|
||||
if (this.computedSelectedDateTime === null) {
|
||||
// case here is impossible for now, because you can't clear time for now
|
||||
} else {
|
||||
const newDisplayDateTimeString = this.displayDateString + ' ' + this.displayTimeString
|
||||
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
}
|
||||
},
|
||||
handleTimeInput (e) {
|
||||
const newDisplayDateTimeString = this.displayDateString + ' ' + e.target.value
|
||||
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
},
|
||||
handleDateInput (e) {
|
||||
const newDisplayDateTimeString = e.target.value + ' ' + this.displayTimeString
|
||||
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
},
|
||||
handleTimeInputBlur () {
|
||||
this.refreshSelectedDateTimeString()
|
||||
},
|
||||
handleDateInputBlur () {
|
||||
this.refreshSelectedDateTimeString()
|
||||
},
|
||||
handleTimeConfirmClick () {
|
||||
this.justifySelectedDateTimeAfterChangeTimeString()
|
||||
this.refreshSelectedDateTimeString()
|
||||
this.closeTimeSelector()
|
||||
},
|
||||
handleTimeCancelClick () {
|
||||
this.closeTimeSelector()
|
||||
},
|
||||
openTimeSelector () {
|
||||
if (this.computedSelectedDateTime === null) {
|
||||
this.setValue(moment())
|
||||
}
|
||||
this.showTimeSelector = true
|
||||
},
|
||||
closeTimeSelector () {
|
||||
this.showTimeSelector = false
|
||||
},
|
||||
/**
|
||||
* To close timeSelector
|
||||
*/
|
||||
handleCalendarClick (e) {
|
||||
if (!this.$refs.timeSelector) return
|
||||
if (!this.$refs.timeSelector.contains(e.target) && this.$refs.timeSelector !== e.target) {
|
||||
this.closeTimeSelector()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* If not selected, display nothing,
|
||||
* else update datetime related string
|
||||
@ -270,16 +194,6 @@ export default {
|
||||
return
|
||||
}
|
||||
this.displayDateTimeString = this.computedSelectedDateTime.format(this.format)
|
||||
this.displayDateString = this.computedSelectedDateTime.format('YYYY-MM-DD')
|
||||
this.displayTimeString = this.computedSelectedDateTime.format('HH:mm:ss')
|
||||
},
|
||||
/**
|
||||
* If new time is invalid, do nothing.
|
||||
* If valid, update.
|
||||
*/
|
||||
handleDateTimeInputEnter () {
|
||||
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
},
|
||||
/**
|
||||
* If new SelectedDateTime is valid, update `selectedDateTime` and `calendarTime`
|
||||
@ -287,16 +201,23 @@ export default {
|
||||
*/
|
||||
handleDateTimeInputBlur () {
|
||||
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
/**
|
||||
* If newSelectedDateTime is invalid, display string need to be restored
|
||||
*/
|
||||
this.refreshSelectedDateTimeString()
|
||||
if (newSelectedDateTime.isValid()) {
|
||||
this.$emit('input', newSelectedDateTime.valueOf())
|
||||
} else {
|
||||
this.refreshSelectedDateTimeString()
|
||||
}
|
||||
},
|
||||
handleActivatorClick (e) {
|
||||
if (this.active) {
|
||||
e.stopPropagation()
|
||||
} else {
|
||||
this.openCalendar()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Calendar view related methods
|
||||
*/
|
||||
openCalendar () {
|
||||
openCalendar (e) {
|
||||
/**
|
||||
* May leak memory here if change disabled from false to true
|
||||
*/
|
||||
@ -310,6 +231,12 @@ export default {
|
||||
},
|
||||
toggleCalendar () {
|
||||
|
||||
},
|
||||
handleDateTimeInputInput (v) {
|
||||
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
|
||||
if (newSelectedDateTime.isValid()) {
|
||||
this.$emit('input', newSelectedDateTime.valueOf())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<transition name="n-date-picker-calendar--transition">
|
||||
<div
|
||||
v-if="active"
|
||||
v-clickoutside.lazy="handleClickOutside"
|
||||
class="n-date-picker-calendar"
|
||||
>
|
||||
<div style="width: 100%; height: 12px" />
|
||||
<div class="n-date-picker-calendar__month-modifier">
|
||||
<div
|
||||
class="n-date-picker-calendar__fast-prev"
|
||||
@click="prevYear"
|
||||
>
|
||||
<n-icon
|
||||
type="ios-arrow-back"
|
||||
size="14"
|
||||
/>
|
||||
<n-icon
|
||||
type="ios-arrow-back"
|
||||
size="14"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n-date-picker-calendar__prev"
|
||||
@click="prevMonth"
|
||||
>
|
||||
<n-icon
|
||||
type="ios-arrow-back"
|
||||
size="14"
|
||||
/>
|
||||
</div>
|
||||
<div class="n-date-picker-calendar__month-year">
|
||||
{{ calendarDateTime.format('MMMM') }} {{ calendarDateTime.year() }}
|
||||
</div>
|
||||
<div
|
||||
class="n-date-picker-calendar__next"
|
||||
@click="nextMonth"
|
||||
>
|
||||
<n-icon
|
||||
type="ios-arrow-forward"
|
||||
size="14"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="n-date-picker-calendar__fast-next"
|
||||
@click="nextYear"
|
||||
>
|
||||
<n-icon
|
||||
type="ios-arrow-forward"
|
||||
size="14"
|
||||
/>
|
||||
<n-icon
|
||||
type="ios-arrow-forward"
|
||||
size="14"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-date-picker-calendar__weekdays">
|
||||
<div
|
||||
v-for="weekday in weekdays"
|
||||
:key="weekday"
|
||||
class="n-date-picker-calendar__weekday"
|
||||
>
|
||||
{{ weekday }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-date-picker-calendar__divider" />
|
||||
<div class="n-date-picker-calendar__dates">
|
||||
<div
|
||||
v-for="dateItem in dateArray(calendarDateTime, valueAsMoment, currentDateTime)"
|
||||
:key="dateItem.timestamp"
|
||||
class="n-date-picker-calendar__date"
|
||||
:class="{
|
||||
'n-date-picker-calendar__date--current': dateItem.isCurrentDate,
|
||||
'n-date-picker-calendar__date--selected': dateItem.isSelectedDate,
|
||||
'n-date-picker-calendar__date--in-display-month': dateItem.isDateOfDisplayMonth
|
||||
}"
|
||||
@click="handleDateClick(dateItem)"
|
||||
>
|
||||
{{ dateItem.date }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-date-picker-calendar__actions">
|
||||
<n-button
|
||||
size="tiny"
|
||||
round
|
||||
@click="setSelectedDateTimeToNow"
|
||||
>
|
||||
Now
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
round
|
||||
auto-text-color
|
||||
type="primary"
|
||||
@click="handleConfirmClick"
|
||||
>
|
||||
Confirm
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { dateArray, setDate } from '../utils'
|
||||
import NIcon from '../../../Icon'
|
||||
import clickoutside from '../../../../directives/clickoutside'
|
||||
|
||||
import NButton from '../../../Button'
|
||||
|
||||
const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
|
||||
const DATE_FORMAT = 'YYYY-MM-DD'
|
||||
const DATE_VALIDATE_FORMAT = ['YYYY-MM-DD', 'YYYY-MM-D', 'YYYY-M-D', 'YYYY-M-DD']
|
||||
|
||||
const PLACEHOLDER = 'Select date and time'
|
||||
|
||||
const TIME_CONST = {
|
||||
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
hours: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
|
||||
minutes: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59'],
|
||||
seconds: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59']
|
||||
}
|
||||
export default {
|
||||
components: {
|
||||
NButton,
|
||||
NIcon
|
||||
},
|
||||
directives: {
|
||||
clickoutside
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
debug: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: PLACEHOLDER
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: DATETIME_FORMAT
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
displayDateString: '',
|
||||
calendarDateTime: moment(),
|
||||
currentDateTime: moment(),
|
||||
calendar: [],
|
||||
...TIME_CONST
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedHour () {
|
||||
if (this.valueAsMoment) return this.valueAsMoment.format('HH')
|
||||
else return null
|
||||
},
|
||||
computedMinute () {
|
||||
if (this.valueAsMoment) return this.valueAsMoment.format('mm')
|
||||
else return null
|
||||
},
|
||||
computedSecond () {
|
||||
if (this.valueAsMoment) return this.valueAsMoment.format('ss')
|
||||
else return null
|
||||
},
|
||||
/**
|
||||
* If value is valid return null.
|
||||
* If value is not valid, return moment(value)
|
||||
*/
|
||||
valueAsMoment () {
|
||||
if (this.value === null || this.value === undefined) return null
|
||||
const newSelectedDateTime = moment(this.value)
|
||||
if (newSelectedDateTime.isValid()) {
|
||||
return newSelectedDateTime
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
valueAsMoment (newValue) {
|
||||
if (newValue !== null) {
|
||||
this.displayDateString = newValue.format(DATE_FORMAT)
|
||||
} else {
|
||||
this.displayDateString = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.valueAsMoment !== null) {
|
||||
this.displayDateString = this.valueAsMoment.format(DATE_FORMAT)
|
||||
} else {
|
||||
this.displayDateString = ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dateArray,
|
||||
handleClickOutside () {
|
||||
this.closeCalendar()
|
||||
},
|
||||
setValue (newSelectedDateTime) {
|
||||
if (newSelectedDateTime === null || newSelectedDateTime === undefined) {
|
||||
this.$emit('input', null)
|
||||
} else if (newSelectedDateTime.isValid()) {
|
||||
const adjustedDateTime = this.adjustValue(newSelectedDateTime)
|
||||
if (this.valueAsMoment === null || adjustedDateTime.valueOf() !== this.valueAsMoment.valueOf()) {
|
||||
this.refreshDisplayDateString(adjustedDateTime)
|
||||
this.$emit('input', adjustedDateTime.valueOf())
|
||||
}
|
||||
}
|
||||
},
|
||||
adjustValue (value) {
|
||||
return moment(value).startOf('day')
|
||||
},
|
||||
handleDateInput (value) {
|
||||
const date = moment(value, DATE_FORMAT, true)
|
||||
if (date.isValid()) {
|
||||
if (!this.valueAsMoment) {
|
||||
const newValue = moment()
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
} else {
|
||||
const newValue = this.valueAsMoment
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
}
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
},
|
||||
handleDateInputBlur () {
|
||||
const date = moment(this.displayDateString, DATE_VALIDATE_FORMAT, true)
|
||||
if (date.isValid()) {
|
||||
if (!this.valueAsMoment) {
|
||||
const newValue = moment()
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
} else {
|
||||
const newValue = this.valueAsMoment
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
}
|
||||
} else {
|
||||
this.refreshDisplayDateString()
|
||||
}
|
||||
},
|
||||
clearSelectedDateTime () {
|
||||
this.$emit('input', null)
|
||||
this.displayDateString = ''
|
||||
},
|
||||
setSelectedDateTimeToNow () {
|
||||
this.$emit('input', this.adjustValue(moment()).valueOf())
|
||||
this.calendarDateTime = moment()
|
||||
},
|
||||
handleDateClick (dateItem) {
|
||||
let newSelectedDateTime = moment()
|
||||
if (this.valueAsMoment !== null) {
|
||||
newSelectedDateTime = moment(this.valueAsMoment)
|
||||
}
|
||||
newSelectedDateTime = setDate(newSelectedDateTime, dateItem)
|
||||
this.$emit('input', this.adjustValue(newSelectedDateTime).valueOf())
|
||||
},
|
||||
/**
|
||||
* If not selected, display nothing,
|
||||
* else update datetime related string
|
||||
*/
|
||||
refreshDisplayDateString (time) {
|
||||
if (this.valueAsMoment === null) {
|
||||
this.displayDateString = ''
|
||||
return
|
||||
}
|
||||
if (time === undefined) {
|
||||
time = this.valueAsMoment
|
||||
}
|
||||
this.displayDateString = time.format(DATE_FORMAT)
|
||||
},
|
||||
handleConfirmClick () {
|
||||
this.$emit('confirm')
|
||||
this.closeCalendar()
|
||||
},
|
||||
closeCalendar () {
|
||||
if (this.active) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
nextYear () {
|
||||
this.calendarDateTime.add(1, 'year')
|
||||
this.$forceUpdate()
|
||||
},
|
||||
prevYear () {
|
||||
this.calendarDateTime.subtract(1, 'year')
|
||||
this.$forceUpdate()
|
||||
},
|
||||
nextMonth () {
|
||||
this.calendarDateTime.add(1, 'month')
|
||||
this.$forceUpdate()
|
||||
},
|
||||
prevMonth () {
|
||||
this.calendarDateTime.subtract(1, 'month')
|
||||
this.$forceUpdate()
|
||||
},
|
||||
handleTimePickerInput (value) {
|
||||
this.$emit('input', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -2,106 +2,26 @@
|
||||
<transition name="n-date-picker-calendar--transition">
|
||||
<div
|
||||
v-if="active"
|
||||
v-clickoutside="handleClickOutside"
|
||||
v-clickoutside.lazy="handleClickOutside"
|
||||
class="n-date-picker-calendar"
|
||||
>
|
||||
<div
|
||||
class="n-date-picker-calendar__date-time-input-wrapper"
|
||||
>
|
||||
<input
|
||||
<n-input
|
||||
v-model="displayDateString"
|
||||
class="n-date-picker-calendar__date-input"
|
||||
placeholder="Select date"
|
||||
@blur="handleDateInputBlur"
|
||||
@input="handleDateInput"
|
||||
>
|
||||
<div
|
||||
ref="timeSelector"
|
||||
class="n-date-picker-calendar__time-input-wrapper"
|
||||
>
|
||||
<input
|
||||
v-model="displayTimeString"
|
||||
class="n-date-picker-calendar__time-input"
|
||||
placeholder="Select time"
|
||||
@click="openTimeSelector"
|
||||
@input="handleTimeInput"
|
||||
@blur="handleTimeInputBlur"
|
||||
>
|
||||
<transition name="n-date-picker-time-selector--transition">
|
||||
<div
|
||||
v-if="showTimeSelector"
|
||||
class="n-date-picker-time-selector"
|
||||
>
|
||||
<div class="n-date-picker-time-selector__selection-wrapper">
|
||||
<div class="n-date-picker-time-selector__hour">
|
||||
<div
|
||||
v-for="hour in hours"
|
||||
:key="hour"
|
||||
class="n-date-picker-time-selector__item"
|
||||
:class="{
|
||||
'n-date-picker-time-selector__item--active':
|
||||
hour === computedHour
|
||||
}"
|
||||
@click="setHour(hour)"
|
||||
>
|
||||
{{ hour }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-date-picker-time-selector__minute">
|
||||
<div
|
||||
v-for="minute in minutes"
|
||||
:key="minute"
|
||||
class="n-date-picker-time-selector__item"
|
||||
:class="{
|
||||
'n-date-picker-time-selector__item--active':
|
||||
minute === computedMinute
|
||||
}"
|
||||
@click="setMinute(minute)"
|
||||
>
|
||||
{{ minute }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-date-picker-time-selector__hour">
|
||||
<div
|
||||
v-for="second in seconds"
|
||||
:key="second"
|
||||
class="n-date-picker-time-selector__item"
|
||||
:class="{
|
||||
'n-date-picker-time-selector__item--active':
|
||||
second === computedSecond
|
||||
}"
|
||||
@click="setSecond(second)"
|
||||
>
|
||||
{{ second }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-date-picker-time-selector__actions">
|
||||
<n-button
|
||||
size="tiny"
|
||||
round
|
||||
@click="handleTimeCancelClick"
|
||||
>
|
||||
Cancel
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
round
|
||||
auto-text-color
|
||||
type="primary"
|
||||
@click="handleTimeConfirmClick"
|
||||
>
|
||||
Confirm
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
/>
|
||||
<n-time-picker
|
||||
class="n-date-picker-calendar__time-input"
|
||||
:value="value"
|
||||
stop-selector-bubble
|
||||
@input="handleTimePickerInput"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div
|
||||
v-else
|
||||
style="width: 100%; height: 12px;"
|
||||
/>-->
|
||||
<div class="n-date-picker-calendar__month-modifier">
|
||||
<div
|
||||
class="n-date-picker-calendar__fast-prev"
|
||||
@ -163,7 +83,7 @@
|
||||
<div class="n-date-picker-calendar__divider" />
|
||||
<div class="n-date-picker-calendar__dates">
|
||||
<div
|
||||
v-for="dateItem in dateArray(calendarDateTime, computedSelectedDateTime, currentDateTime)"
|
||||
v-for="dateItem in dateArray(calendarDateTime, valueAsMoment, currentDateTime)"
|
||||
:key="dateItem.timestamp"
|
||||
class="n-date-picker-calendar__date"
|
||||
:class="{
|
||||
@ -189,7 +109,7 @@
|
||||
round
|
||||
auto-text-color
|
||||
type="primary"
|
||||
@click="handleDateInputAndTimeInputConfirmClick"
|
||||
@click="handleConfirmClick"
|
||||
>
|
||||
Confirm
|
||||
</n-button>
|
||||
@ -205,15 +125,15 @@ import NIcon from '../../../Icon'
|
||||
import clickoutside from '../../../../directives/clickoutside'
|
||||
|
||||
import NButton from '../../../Button'
|
||||
import NTimePicker from '../../../TimePicker'
|
||||
import NInput from '../../../Input'
|
||||
|
||||
const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
|
||||
const DATE_FORMAT = 'YYYY-MM-DD'
|
||||
const DATE_VALIDATE_FORMAT = ['YYYY-MM-DD', 'YYYY-MM-D', 'YYYY-M-D', 'YYYY-M-DD']
|
||||
|
||||
const PLACEHOLDER = 'Select date and time'
|
||||
|
||||
const DATE_FORMAT = {
|
||||
date: 'YYYY-MM-DD',
|
||||
datetime: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
const PLACEHOLDER = {
|
||||
date: 'Select date',
|
||||
datetime: 'Select date and time'
|
||||
}
|
||||
const TIME_CONST = {
|
||||
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
hours: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
|
||||
@ -224,74 +144,65 @@ const TIME_CONST = {
|
||||
export default {
|
||||
components: {
|
||||
NButton,
|
||||
NIcon
|
||||
NIcon,
|
||||
NTimePicker,
|
||||
NInput
|
||||
},
|
||||
directives: {
|
||||
clickoutside
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
type: [Number, String],
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
debug: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: PLACEHOLDER
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: DATETIME_FORMAT
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
displayDateTimeString: '',
|
||||
displayDateString: '',
|
||||
displayTimeString: '',
|
||||
rightDisplayDateTimeString: '',
|
||||
rightDisplayDateString: '',
|
||||
rightDisplayTimeString: '',
|
||||
calendarDateTime: moment(),
|
||||
rightCalendarDateTime: moment(),
|
||||
currentDateTime: moment(),
|
||||
showTimeSelector: false,
|
||||
showRightTimeSelector: false,
|
||||
calendar: [],
|
||||
rightCalendar: [],
|
||||
...TIME_CONST
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeholder () {
|
||||
return PLACEHOLDER[this.type]
|
||||
},
|
||||
format () {
|
||||
return DATE_FORMAT[this.type]
|
||||
},
|
||||
computedHour () {
|
||||
if (this.computedSelectedDateTime) return this.computedSelectedDateTime.format('HH')
|
||||
if (this.valueAsMoment) return this.valueAsMoment.format('HH')
|
||||
else return null
|
||||
},
|
||||
computedMinute () {
|
||||
if (this.computedSelectedDateTime) return this.computedSelectedDateTime.format('mm')
|
||||
if (this.valueAsMoment) return this.valueAsMoment.format('mm')
|
||||
else return null
|
||||
},
|
||||
computedSecond () {
|
||||
if (this.computedSelectedDateTime) return this.computedSelectedDateTime.format('ss')
|
||||
if (this.valueAsMoment) return this.valueAsMoment.format('ss')
|
||||
else return null
|
||||
},
|
||||
/**
|
||||
* If value is valid return null.
|
||||
* If value is not valid, return moment(value)
|
||||
*/
|
||||
computedSelectedDateTime () {
|
||||
valueAsMoment () {
|
||||
if (this.value === null || this.value === undefined) return null
|
||||
const newSelectedDateTime = moment(Number(this.value))
|
||||
const newSelectedDateTime = moment(this.value)
|
||||
if (newSelectedDateTime.isValid()) {
|
||||
return newSelectedDateTime
|
||||
} else {
|
||||
@ -299,171 +210,113 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
valueAsMoment (newValue) {
|
||||
if (newValue !== null) {
|
||||
this.displayDateString = newValue.format(DATE_FORMAT)
|
||||
} else {
|
||||
this.displayDateString = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.valueAsMoment !== null) {
|
||||
this.displayDateString = this.valueAsMoment.format(DATE_FORMAT)
|
||||
} else {
|
||||
this.displayDateString = ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dateArray,
|
||||
handleClickOutside () {
|
||||
this.closeCalendar()
|
||||
console.log('clickoutside')
|
||||
},
|
||||
dateArray,
|
||||
/**
|
||||
* If new datetime is null or undefined, emit null to value.
|
||||
* Else adjust new datetime by props.type and emit it to value.
|
||||
*/
|
||||
setValue (newSelectedDateTime) {
|
||||
if (newSelectedDateTime === null || newSelectedDateTime === undefined) {
|
||||
this.$emit('change', null)
|
||||
return
|
||||
}
|
||||
if (newSelectedDateTime.isValid()) {
|
||||
const adjustedDateTime = this.adjustDateTime(newSelectedDateTime)
|
||||
if (this.computedSelectedDateTime === null || adjustedDateTime.valueOf() !== this.computedSelectedDateTime.valueOf()) {
|
||||
this.$emit('change', adjustedDateTime.valueOf(), adjustedDateTime.format(this.format))
|
||||
this.$emit('input', null)
|
||||
} else if (newSelectedDateTime.isValid()) {
|
||||
const adjustedDateTime = this.adjustValue(newSelectedDateTime)
|
||||
if (this.valueAsMoment === null || adjustedDateTime.valueOf() !== this.valueAsMoment.valueOf()) {
|
||||
this.refreshDisplayDateString(adjustedDateTime)
|
||||
this.$emit('input', adjustedDateTime.valueOf())
|
||||
}
|
||||
}
|
||||
},
|
||||
adjustDateTime (datetime) {
|
||||
adjustValue (datetime) {
|
||||
return moment(datetime).startOf('second')
|
||||
},
|
||||
justifySelectedDateTimeAfterChangeTimeString () {
|
||||
if (this.computedSelectedDateTime === null) {
|
||||
// case here is impossible for now, because you can't clear time for now
|
||||
handleDateInput (value) {
|
||||
const date = moment(value, DATE_FORMAT, true)
|
||||
if (date.isValid()) {
|
||||
if (!this.valueAsMoment) {
|
||||
const newValue = moment()
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
} else {
|
||||
const newValue = this.valueAsMoment
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
}
|
||||
} else {
|
||||
const newDisplayDateTimeString = this.displayDateString + ' ' + this.displayTimeString
|
||||
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
// do nothing
|
||||
}
|
||||
},
|
||||
handleTimeInput (e) {
|
||||
const newDisplayDateTimeString = this.displayDateString + ' ' + e.target.value
|
||||
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
},
|
||||
handleDateInput (e) {
|
||||
const newDisplayDateTimeString = e.target.value + ' ' + this.displayTimeString
|
||||
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
},
|
||||
handleTimeInputBlur () {
|
||||
this.refreshSelectedDateTimeString()
|
||||
},
|
||||
handleDateInputBlur () {
|
||||
this.refreshSelectedDateTimeString()
|
||||
},
|
||||
handleTimeConfirmClick () {
|
||||
this.justifySelectedDateTimeAfterChangeTimeString()
|
||||
this.refreshSelectedDateTimeString()
|
||||
this.closeTimeSelector()
|
||||
},
|
||||
handleTimeCancelClick () {
|
||||
this.closeTimeSelector()
|
||||
},
|
||||
setHour (hour) {
|
||||
try {
|
||||
const timeArray = this.displayTimeString.split(':')
|
||||
timeArray[0] = hour
|
||||
this.displayTimeString = timeArray.join(':')
|
||||
} catch (err) {
|
||||
|
||||
} finally {
|
||||
this.justifySelectedDateTimeAfterChangeTimeString()
|
||||
}
|
||||
},
|
||||
setMinute (minute) {
|
||||
try {
|
||||
const timeArray = this.displayTimeString.split(':')
|
||||
timeArray[1] = minute
|
||||
this.displayTimeString = timeArray.join(':')
|
||||
} catch (err) {
|
||||
|
||||
} finally {
|
||||
this.justifySelectedDateTimeAfterChangeTimeString()
|
||||
}
|
||||
},
|
||||
setSecond (second) {
|
||||
try {
|
||||
const timeArray = this.displayTimeString.split(':')
|
||||
timeArray[2] = second
|
||||
this.displayTimeString = timeArray.join(':')
|
||||
} catch (err) {
|
||||
|
||||
} finally {
|
||||
this.justifySelectedDateTimeAfterChangeTimeString()
|
||||
}
|
||||
},
|
||||
openTimeSelector () {
|
||||
if (this.computedSelectedDateTime === null) {
|
||||
this.setValue(moment())
|
||||
}
|
||||
this.showTimeSelector = true
|
||||
},
|
||||
closeTimeSelector () {
|
||||
this.showTimeSelector = false
|
||||
},
|
||||
nativeCloseCalendar (e) {
|
||||
if (!this.$refs.activator.contains(e.target) && !this.$refs.content.contains(e.target)) {
|
||||
this.closeCalendar()
|
||||
const date = moment(this.displayDateString, DATE_VALIDATE_FORMAT, true)
|
||||
if (date.isValid()) {
|
||||
if (!this.valueAsMoment) {
|
||||
const newValue = moment()
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
} else {
|
||||
const newValue = this.valueAsMoment
|
||||
newValue.year(date.year())
|
||||
newValue.month(date.month())
|
||||
newValue.date(date.date())
|
||||
this.$emit('input', this.adjustValue(newValue).valueOf())
|
||||
}
|
||||
} else {
|
||||
this.refreshDisplayDateString()
|
||||
}
|
||||
},
|
||||
clearSelectedDateTime () {
|
||||
this.setValue(null)
|
||||
this.displayDateTimeString = ''
|
||||
this.$emit('input', null)
|
||||
this.displayDateString = ''
|
||||
this.displayTimeString = ''
|
||||
},
|
||||
setSelectedDateTimeToNow () {
|
||||
this.setValue(moment())
|
||||
this.$emit('input', this.adjustValue(moment()).valueOf())
|
||||
this.calendarDateTime = moment()
|
||||
},
|
||||
handleDateClick (dateItem) {
|
||||
let newSelectedDateTime = moment()
|
||||
if (this.computedSelectedDateTime !== null) {
|
||||
newSelectedDateTime = moment(this.computedSelectedDateTime)
|
||||
if (this.valueAsMoment !== null) {
|
||||
newSelectedDateTime = moment(this.valueAsMoment)
|
||||
}
|
||||
newSelectedDateTime = setDate(newSelectedDateTime, dateItem)
|
||||
this.setValue(newSelectedDateTime)
|
||||
this.$emit('input', this.adjustValue(newSelectedDateTime).valueOf())
|
||||
},
|
||||
/**
|
||||
* If not selected, display nothing,
|
||||
* else update datetime related string
|
||||
*/
|
||||
refreshSelectedDateTimeString () {
|
||||
if (this.computedSelectedDateTime === null) {
|
||||
this.displayDateTimeString = ''
|
||||
refreshDisplayDateString (time) {
|
||||
if (this.valueAsMoment === null) {
|
||||
this.displayDateString = ''
|
||||
return
|
||||
}
|
||||
this.displayDateTimeString = this.computedSelectedDateTime.format(this.format)
|
||||
this.displayDateString = this.computedSelectedDateTime.format('YYYY-MM-DD')
|
||||
this.displayTimeString = this.computedSelectedDateTime.format('HH:mm:ss')
|
||||
},
|
||||
/**
|
||||
* If new time is invalid, do nothing.
|
||||
* If valid, update.
|
||||
*/
|
||||
handleDateTimeInputEnter () {
|
||||
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
},
|
||||
/**
|
||||
* If new SelectedDateTime is valid, update `selectedDateTime` and `calendarTime`
|
||||
* Whatever happened, refresh selectedDateTime
|
||||
*/
|
||||
handleDateTimeInputBlur () {
|
||||
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
|
||||
this.setValue(newSelectedDateTime)
|
||||
/**
|
||||
* If newSelectedDateTime is invalid, display string need to be restored
|
||||
*/
|
||||
this.refreshSelectedDateTimeString()
|
||||
},
|
||||
handleDateInputAndTimeInputConfirmClick () {
|
||||
const newDisplayDateTimeString = `${this.displayDateString.trim()} ${this.displayTimeString.trim()}`
|
||||
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
|
||||
if (this.computedSelectedDateTime === null) {
|
||||
this.setValue(moment())
|
||||
} else {
|
||||
this.setValue(newSelectedDateTime)
|
||||
this.refreshSelectedDateTimeString()
|
||||
if (time === undefined) {
|
||||
time = this.valueAsMoment
|
||||
}
|
||||
this.displayDateString = time.format(DATE_FORMAT)
|
||||
},
|
||||
handleConfirmClick () {
|
||||
this.$emit('confirm')
|
||||
this.closeCalendar()
|
||||
},
|
||||
closeCalendar () {
|
||||
@ -486,6 +339,9 @@ export default {
|
||||
prevMonth () {
|
||||
this.calendarDateTime.subtract(1, 'month')
|
||||
this.$forceUpdate()
|
||||
},
|
||||
handleTimePickerInput (value) {
|
||||
this.$emit('input', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
packages/common/Form/index.js
Normal file
10
packages/common/Form/index.js
Normal 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
|
194
packages/common/Form/src/form-item.vue
Normal file
194
packages/common/Form/src/form-item.vue
Normal 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>
|
124
packages/common/Form/src/main.vue
Normal file
124
packages/common/Form/src/main.vue
Normal 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>
|
@ -19,6 +19,7 @@
|
||||
@blur="handleBlur"
|
||||
@focus="handleFocus"
|
||||
@input="handleInput"
|
||||
@click="handleClick"
|
||||
@change="handleChange"
|
||||
@keyup="handleKeyUp"
|
||||
@compositionstart="handleCompositionStart"
|
||||
@ -48,6 +49,7 @@
|
||||
@blur="handleBlur"
|
||||
@focus="handleFocus"
|
||||
@input="handleInput"
|
||||
@click="handleClick"
|
||||
@change="handleChange"
|
||||
@keyup="handleKeyUp"
|
||||
@compositionstart="handleCompositionStart"
|
||||
@ -64,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'
|
||||
@ -117,6 +129,11 @@ export default {
|
||||
isComposing: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
validateState () {
|
||||
return this.formItem ? this.formItem.validateState : ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCompositionStart () {
|
||||
this.isComposing = true
|
||||
@ -131,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)
|
||||
@ -140,7 +161,13 @@ 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) {
|
||||
this.$emit('click', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.n-modal-content {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.n-modal-content--transition-enter-active {
|
||||
opacity: 1;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 = ''
|
||||
}
|
||||
|
8
packages/common/TimePicker/index.js
Normal file
8
packages/common/TimePicker/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
/* istanbul ignore file */
|
||||
import Scaffold from './src/main.vue'
|
||||
|
||||
Scaffold.install = function (Vue) {
|
||||
Vue.component(Scaffold.name, Scaffold)
|
||||
}
|
||||
|
||||
export default Scaffold
|
307
packages/common/TimePicker/src/main.vue
Normal file
307
packages/common/TimePicker/src/main.vue
Normal file
@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<div class="n-time-picker">
|
||||
<div ref="activator">
|
||||
<n-input
|
||||
v-model="displayTimeString"
|
||||
class="n-date-picker-calendar__time-input"
|
||||
placeholder="Select time"
|
||||
@click="handleActivatorClick"
|
||||
@input="handleTimeInput"
|
||||
@blur="handleTimeInputBlur"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref="contentWrapper"
|
||||
class="n-content-wrapper"
|
||||
@click="handleContentClick"
|
||||
>
|
||||
<div
|
||||
ref="content"
|
||||
>
|
||||
<transition name="n-time-picker--transition">
|
||||
<div
|
||||
v-if="active"
|
||||
v-clickoutside.lazy="closeTimeSelector"
|
||||
class="n-time-picker-selector"
|
||||
>
|
||||
<div class="n-time-picker__selection-wrapper">
|
||||
<div
|
||||
ref="hours"
|
||||
class="n-time-picker__hour"
|
||||
>
|
||||
<div
|
||||
v-for="hour in hours"
|
||||
:key="hour"
|
||||
class="n-time-picker__item"
|
||||
:class="{
|
||||
'n-time-picker__item--active':
|
||||
hour === computedHour
|
||||
}"
|
||||
@click="setHour(hour)"
|
||||
>
|
||||
{{ hour }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="minutes"
|
||||
class="n-time-picker__minute"
|
||||
>
|
||||
<div
|
||||
v-for="minute in minutes"
|
||||
:key="minute"
|
||||
class="n-time-picker__item"
|
||||
:class="{
|
||||
'n-time-picker__item--active':
|
||||
minute === computedMinute
|
||||
}"
|
||||
@click="setMinute(minute)"
|
||||
>
|
||||
{{ minute }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref="seconds"
|
||||
class="n-time-picker__hour"
|
||||
>
|
||||
<div
|
||||
v-for="second in seconds"
|
||||
:key="second"
|
||||
class="n-time-picker__item"
|
||||
:class="{
|
||||
'n-time-picker__item--active':
|
||||
second === computedSecond,
|
||||
'n-time-picker__item--disabled':
|
||||
validator &&
|
||||
!validator(computedHour, computedMinute, second)
|
||||
}"
|
||||
@click="setSecond(second)"
|
||||
>
|
||||
{{ second }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-time-picker__actions">
|
||||
<n-button
|
||||
size="tiny"
|
||||
round
|
||||
@click="handleCancelClick"
|
||||
>
|
||||
Cancel
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
round
|
||||
auto-text-color
|
||||
type="primary"
|
||||
@click="handleConfirmClick"
|
||||
>
|
||||
Confirm
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NInput from '../../Input'
|
||||
import moment from 'moment'
|
||||
import detachable from '../../../mixins/detachable'
|
||||
import placeable from '../../../mixins/placeable'
|
||||
import clickoutside from '../../../directives/clickoutside'
|
||||
|
||||
const DEFAULT_FORMAT = 'HH:mm:ss'
|
||||
const TIME_CONST = {
|
||||
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
hours: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
|
||||
minutes: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59'],
|
||||
seconds: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59']
|
||||
}
|
||||
|
||||
// function ranges () {
|
||||
|
||||
// }
|
||||
|
||||
// function validateRange (ranges) {
|
||||
// const rangeReg = /((\d\d):(\d\d):(\d\d))\s*-\s*((\d\d):(\d\d):(\d\d))/
|
||||
// if (typeof ranges === 'string') {
|
||||
// const result = ranges.match(rangeReg)
|
||||
// if (!result) return false
|
||||
// else {
|
||||
// const [time1, hour1, min1, sec1, time2, hour2, min2, sec2] = result.slice(1)
|
||||
// if ()
|
||||
// }
|
||||
// } else if (Array.isArray(ranges)) {
|
||||
|
||||
// } else {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Use range to disabled time since validator will need time picker to loop all available options.
|
||||
* Warning: this component shouldn't change v-model's timestamps' date
|
||||
*/
|
||||
export default {
|
||||
name: 'NTimePicker',
|
||||
components: {
|
||||
NInput
|
||||
},
|
||||
directives: {
|
||||
clickoutside
|
||||
},
|
||||
mixins: [detachable, placeable],
|
||||
props: {
|
||||
stopSelectorBubble: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-start'
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: DEFAULT_FORMAT
|
||||
},
|
||||
validator: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
active: false,
|
||||
displayTimeString: this.value === null ? null : moment(this.value).format(this.format),
|
||||
...TIME_CONST,
|
||||
memorizedValue: this.value
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedTime () {
|
||||
if (this.value === null) return null
|
||||
else return moment(this.value)
|
||||
},
|
||||
computedHour () {
|
||||
if (this.computedTime) return this.computedTime.format('HH')
|
||||
else return null
|
||||
},
|
||||
computedMinute () {
|
||||
if (this.computedTime) return this.computedTime.format('mm')
|
||||
else return null
|
||||
},
|
||||
computedSecond () {
|
||||
if (this.computedTime) return this.computedTime.format('ss')
|
||||
else return null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
computedTime (time) {
|
||||
this.refreshTimeString(time)
|
||||
this.$nextTick().then(this.scrollTimer)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
justifyValueAfterChangeDisplayTimeString () {
|
||||
const time = moment(this.displayTimeString, this.format, true)
|
||||
if (time.isValid()) {
|
||||
if (this.computedTime !== null) {
|
||||
const newTime = this.computedTime
|
||||
newTime.hour(time.hour())
|
||||
newTime.minute(time.minute())
|
||||
newTime.second(time.second())
|
||||
this.$emit('input', newTime.valueOf())
|
||||
} else {
|
||||
this.$emit('input', time.valueOf())
|
||||
}
|
||||
}
|
||||
},
|
||||
setHour (hour) {
|
||||
if (this.value === null) {
|
||||
this.$emit('input', moment().hour(hour).startOf('hour').valueOf())
|
||||
} else {
|
||||
this.$emit('input', moment(this.value).hour(hour).valueOf())
|
||||
}
|
||||
},
|
||||
setMinute (minute) {
|
||||
if (this.value === null) {
|
||||
this.$emit('input', moment().minute(minute).startOf('minute').valueOf())
|
||||
} else {
|
||||
this.$emit('input', moment(this.value).minute(minute).valueOf())
|
||||
}
|
||||
},
|
||||
setSecond (second) {
|
||||
if (this.value === null) {
|
||||
this.$emit('input', moment().second(second).startOf('second').valueOf())
|
||||
} else {
|
||||
this.$emit('input', moment(this.value).second(second).valueOf())
|
||||
}
|
||||
},
|
||||
refreshTimeString (time) {
|
||||
if (time === undefined) time = this.computedTime
|
||||
if (time === null) this.displayTimeString = ''
|
||||
else this.displayTimeString = time.format(this.format)
|
||||
},
|
||||
handleTimeInputBlur () {
|
||||
this.refreshTimeString()
|
||||
},
|
||||
scrollTimer () {
|
||||
if (this.$refs.hours) {
|
||||
const hour = this.$refs.hours.querySelector('.n-time-picker__item--active')
|
||||
if (hour) {
|
||||
this.$refs.hours.scrollTo(0, hour.offsetTop)
|
||||
}
|
||||
}
|
||||
if (this.$refs.minutes) {
|
||||
const minute = this.$refs.minutes.querySelector('.n-time-picker__item--active')
|
||||
if (minute) {
|
||||
this.$refs.minutes.scrollTo(0, minute.offsetTop)
|
||||
}
|
||||
}
|
||||
if (this.$refs.seconds) {
|
||||
const second = this.$refs.seconds.querySelector('.n-time-picker__item--active')
|
||||
if (second) {
|
||||
this.$refs.seconds.scrollTo(0, second.offsetTop)
|
||||
}
|
||||
}
|
||||
},
|
||||
openTimeSelector () {
|
||||
this.memorizedValue = this.value
|
||||
this.active = true
|
||||
this.$nextTick().then(this.scrollTimer)
|
||||
},
|
||||
handleActivatorClick (e) {
|
||||
if (this.active) {
|
||||
e.stopPropagation()
|
||||
} else {
|
||||
this.openTimeSelector()
|
||||
}
|
||||
},
|
||||
closeTimeSelector () {
|
||||
this.active = false
|
||||
},
|
||||
handleTimeInput () {
|
||||
this.justifyValueAfterChangeDisplayTimeString()
|
||||
},
|
||||
handleCancelClick () {
|
||||
this.$emit('input', this.memorizedValue)
|
||||
this.active = false
|
||||
},
|
||||
handleConfirmClick () {
|
||||
this.refreshTimeString()
|
||||
this.active = false
|
||||
},
|
||||
handleContentClick (e) {
|
||||
if (this.stopSelectorBubble) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -2,25 +2,41 @@ import clickoutsideDelegate from '../utils/clickoutsideDelegate'
|
||||
|
||||
const ctx = '@@clickoutsideContext'
|
||||
|
||||
function lazyHandler (handler) {
|
||||
let called = false
|
||||
return function () {
|
||||
if (called) {
|
||||
console.debug('[clickoutside] called')
|
||||
handler()
|
||||
} else {
|
||||
console.debug('[clickoutside] lazy called')
|
||||
called = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const clickoutside = {
|
||||
bind (el, bindings) {
|
||||
console.debug('[clickoutside]: bind', el)
|
||||
},
|
||||
inserted (el, bindings) {
|
||||
console.log('[clickoutside]: inserted')
|
||||
console.debug('[clickoutside]: inserted')
|
||||
if (typeof bindings.value === 'function') {
|
||||
el[ctx] = {
|
||||
handler: bindings.value
|
||||
handler: bindings.modifiers.lazy ? lazyHandler(bindings.value) : bindings.value
|
||||
}
|
||||
clickoutsideDelegate.registerHandler(el, el[ctx].handler, false)
|
||||
}
|
||||
},
|
||||
update (el, bindings) {
|
||||
if (typeof bindings.value === 'function') {
|
||||
clickoutsideDelegate.unregisterHandler(el, el[ctx].handler)
|
||||
clickoutsideDelegate.unregisterHandler(el[ctx].handler)
|
||||
el[ctx].handler = bindings.value
|
||||
clickoutsideDelegate.registerHandler(el, el[ctx].handler, false)
|
||||
}
|
||||
},
|
||||
unbind (el) {
|
||||
console.log('[clickoutside]: unbind')
|
||||
console.debug('[clickoutside]: unbind')
|
||||
clickoutsideDelegate.unregisterHandler(el[ctx].handler)
|
||||
}
|
||||
}
|
||||
|
33
packages/mixins/emitter.js
Normal file
33
packages/mixins/emitter.js
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$refs.content.style = 'position: absolute;'
|
||||
this.$nextTick().then(() => {
|
||||
this.registerScrollListeners()
|
||||
this.registerResizeListener()
|
||||
@ -86,15 +87,14 @@ export default {
|
||||
resizeDelegate.registerHandler(this.updatePosition)
|
||||
},
|
||||
registerScrollListeners () {
|
||||
let currentElement = this.$el
|
||||
let currentElement = getParentNode(this.$el)
|
||||
while (true) {
|
||||
currentElement = getScrollParent(currentElement)
|
||||
if (currentElement === null) break
|
||||
this.scrollListeners.push([currentElement, this.updatePosition])
|
||||
currentElement = getParentNode(currentElement)
|
||||
if (currentElement === document.body || currentElement.nodeName === 'HTML') {
|
||||
break
|
||||
}
|
||||
}
|
||||
// console.log(this.scrollListeners)
|
||||
for (const [el, handler] of this.scrollListeners) {
|
||||
scrollDelegate.registerHandler(el, handler)
|
||||
}
|
||||
|
@ -104,10 +104,6 @@ export default {
|
||||
paddingBody: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
@ -121,31 +117,31 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.activeItemName = this.value
|
||||
$route (to, from) {
|
||||
this.syncActiveItemWithPath(to.path)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.activeItemName = this.value
|
||||
const path = this.$route.path
|
||||
for (const item of this.items) {
|
||||
if (item.path === path) {
|
||||
this.activeItemName = item.name
|
||||
this.$emit('input', item.name)
|
||||
return
|
||||
}
|
||||
if (item.childItems) {
|
||||
for (const childItem of item.childItems) {
|
||||
if (childItem.path === path) {
|
||||
this.activeItemName = childItem.name
|
||||
this.$emit('input', item.name)
|
||||
return
|
||||
this.syncActiveItemWithPath(path)
|
||||
},
|
||||
methods: {
|
||||
syncActiveItemWithPath (path) {
|
||||
for (const item of this.items) {
|
||||
if (item.path === path) {
|
||||
this.activeItemName = item.name
|
||||
return
|
||||
}
|
||||
if (item.childItems) {
|
||||
for (const childItem of item.childItems) {
|
||||
if (childItem.path === path) {
|
||||
this.activeItemName = childItem.name
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
toggle () {
|
||||
this.isCollapsed = !this.isCollapsed
|
||||
},
|
||||
|
@ -45,7 +45,7 @@ class ClickOutsideDelegate {
|
||||
}
|
||||
if (!this.handlerCount) {
|
||||
console.debug('[ClickOutsideDelegate]: remove handler from window')
|
||||
window.removeEventListener('click', this.handleClickOutside, true)
|
||||
window.removeEventListener('click', this.handleClickOutside)
|
||||
this.handlers = new Map()
|
||||
}
|
||||
}
|
||||
@ -57,11 +57,11 @@ class ClickOutsideDelegate {
|
||||
if (!el) throw new Error('[ClickOutsideDelegate.registerHandler]: make sure `el` is an HTMLElement')
|
||||
}
|
||||
if (this.handlers.get(handler)) {
|
||||
throw new Error('[ClickOutsideDelegate.registerHandler]: don\'t register duplicate event handler')
|
||||
throw new Error('[ClickOutsideDelegate.registerHandler]: don\'t register duplicate event handler, if you want to do it, unregister this handler and reregister it.')
|
||||
}
|
||||
if (!this.handlerCount) {
|
||||
console.debug('[ClickOutsideDelegate]: add handler to window')
|
||||
window.addEventListener('click', this.handleClickOutside, true)
|
||||
window.addEventListener('click', this.handleClickOutside)
|
||||
}
|
||||
++this.handlerCount
|
||||
this.handlers.set(handler, { els, once })
|
||||
|
@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Returns the parentNode or the host of the element
|
||||
* Returns the parentNode or the host of the element until document
|
||||
* @method
|
||||
* @param {Element} element
|
||||
* @returns {Element} parent
|
||||
*/
|
||||
export default function getParentNode (element) {
|
||||
if (element.nodeName === 'HTML') {
|
||||
if (element.nodeName === '#document') {
|
||||
return element
|
||||
}
|
||||
return element.parentNode || element.host
|
||||
|
@ -5,20 +5,20 @@ import getParentNode from './getParentNode'
|
||||
* Returns the scrolling parent of the given element
|
||||
* @method
|
||||
* @param {Element} element
|
||||
* @returns {Element} scroll parent
|
||||
* @returns {Element|null} scroll parent
|
||||
*/
|
||||
export default function getScrollParent (element) {
|
||||
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
|
||||
if (!element) {
|
||||
return document.body
|
||||
return null
|
||||
}
|
||||
|
||||
switch (element.nodeName) {
|
||||
case 'HTML':
|
||||
case 'BODY':
|
||||
return element.ownerDocument.body
|
||||
return document
|
||||
case '#document':
|
||||
return element.body
|
||||
return null
|
||||
}
|
||||
|
||||
// Firefox want us to check `-x` and `-y` variations as well
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
||||
color: rgba(221, 238, 247, 0.3);
|
||||
height: 20px;
|
||||
}
|
||||
.n-date-picker__input, .n-date-picker-calendar__date-input, .n-date-picker-calendar__time-input {
|
||||
.n-date-picker__input, .n-date-picker-calendar__date-input {
|
||||
-webkit-appearance: none;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
|
88
styles/Form.scss
Normal file
88
styles/Form.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -123,8 +123,13 @@
|
||||
transition: opacity 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||
opacity: 1;
|
||||
}
|
||||
&.n-nimbus-service-layout-drawer__item--active span{
|
||||
opacity: 1;
|
||||
&.n-nimbus-service-layout-drawer__item--active {
|
||||
&:not(.n-nimbus-service-layout-drawer__item--is-group-item) {
|
||||
color: #63E2B7;
|
||||
}
|
||||
span {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:hover span {
|
||||
opacity: 1;
|
||||
|
@ -14,6 +14,8 @@ $popover-distance: $popover-arrow-width - 1 + 14;
|
||||
}
|
||||
|
||||
.n-popover__content-container {
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: fixed;
|
||||
|
51
styles/TimePicker.scss
Normal file
51
styles/TimePicker.scss
Normal file
@ -0,0 +1,51 @@
|
||||
@import "./mixins/mixins.scss";
|
||||
@import "./theme/default.scss";
|
||||
|
||||
@include b(time-picker-selector) {
|
||||
background-color: rgba(97, 104, 132, 1);
|
||||
box-shadow:0px 2px 20px 0px rgba(0,0,0,0.16);
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
margin-top: 4px;
|
||||
width: 180px;
|
||||
overflow: hidden;
|
||||
@include fade-in-transition(time-picker, top left);
|
||||
.n-time-picker__selection-wrapper {
|
||||
position: relative;
|
||||
height: 244px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, .07);
|
||||
display: flex;
|
||||
}
|
||||
.n-time-picker__hour, .n-time-picker__minute, .n-time-picker__second {
|
||||
@include scrollbar;
|
||||
width: 60px;
|
||||
height: 244px;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
.n-time-picker__item {
|
||||
cursor: pointer;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color .2s $default-cubic-bezier, color .2s $default-cubic-bezier;
|
||||
background: transparent;
|
||||
&:hover {
|
||||
background-color: rgba(99,226,183,0.12);
|
||||
}
|
||||
&.n-time-picker__item--active {
|
||||
background-color: rgba(99,226,183,0.12);
|
||||
color: rgba(99, 226, 183, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.n-time-picker__actions {
|
||||
// margin will boom! I don't know why
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.n-button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,4 +7,5 @@
|
||||
|
||||
@include b(tooltip__content) {
|
||||
padding: 8px 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -21,6 +21,8 @@
|
||||
@import './DatePicker.scss';
|
||||
@import './InputNumber.scss';
|
||||
@import './Radio.scss';
|
||||
@import './Form.scss';
|
||||
@import './TimePicker.scss';
|
||||
|
||||
@import "./NimbusServiceLayout.scss";
|
||||
@import "./Popover.scss";
|
||||
|
@ -182,4 +182,24 @@ $default-font-family: 'Lato';
|
||||
&.#{$namespace}-#{$block}--transition-leave-to {
|
||||
transition: opacity .2s $slow-out-cubic-bezier, transform .2s $slow-out-cubic-bezier;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin fade-in-transition($block, $origin: left top) {
|
||||
&.#{$namespace}-#{$block}--transition-enter-active,
|
||||
&.#{$namespace}-#{$block}--transition-leave-active {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
&.#{$namespace}-#{$block}--transition-enter-active {
|
||||
transform-origin: $origin;
|
||||
transition: opacity .2s $fast-in-cubic-bezier, transform .2s $fast-in-cubic-bezier;
|
||||
}
|
||||
&.#{$namespace}-#{$block}--transition-enter, &.#{$namespace}-#{$block}--transition-leave-to {
|
||||
opacity: 0;
|
||||
transform-origin: $origin;
|
||||
transform: scale(.9);
|
||||
}
|
||||
&.#{$namespace}-#{$block}--transition-leave-to {
|
||||
transition: opacity .2s $slow-out-cubic-bezier, transform .2s $slow-out-cubic-bezier;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user