updated frontend

This commit is contained in:
marvzhang
2021-07-15 21:37:37 +08:00
parent db6414aa5b
commit db7920ac69
550 changed files with 27134 additions and 49444 deletions

View File

@@ -0,0 +1,178 @@
<template>
<div class="input-with-button">
<!-- Input -->
<el-input
v-model="internalValue"
:placeholder="placeholder"
:size="size"
class="input"
:disabled="disabled"
@input="onInput"
@blur="onBlur"
@focus="onFocus"
@keyup.enter="onBlur"
/>
<!-- ./Input -->
<!-- Button -->
<Button v-if="buttonLabel" :disabled="disabled" :size="size" :type="buttonType" class="button" no-margin>
<Icon v-if="buttonIcon" :icon="buttonIcon"/>
{{ buttonLabel }}
</Button>
<template v-else-if="buttonIcon">
<FaIconButton
v-if="isFaIcon"
:disabled="disabled"
:icon="buttonIcon"
:size="size"
:type="buttonType"
class="button"
/>
<IconButton
v-else
:disabled="disabled"
:icon="buttonIcon"
:size="size"
:type="buttonType"
class="button"
/>
</template>
<!-- ./Button -->
</div>
</template>
<script lang="ts">
import {defineComponent, onMounted, PropType, ref, watch} from 'vue';
import Button from '@/components/button/Button.vue';
import Icon from '@/components/icon/Icon.vue';
import FaIconButton from '@/components/button/FaIconButton.vue';
import useIcon from '@/components/icon/icon';
import IconButton from '@/components/button/IconButton.vue';
export default defineComponent({
name: 'InputWithButton',
components: {
IconButton,
FaIconButton,
Icon,
Button,
},
props: {
modelValue: {
type: String,
},
placeholder: {
type: String,
},
size: {
type: String,
default: 'mini',
},
buttonType: {
type: String as PropType<BasicType>,
default: 'primary',
},
buttonLabel: {
type: String,
default: 'Click',
},
buttonIcon: {
type: [String, Array] as PropType<string | string[]>,
},
disabled: {
type: Boolean,
default: false,
}
},
emits: [
'update:model-value',
'input',
'click',
'blur',
'focus',
'keyup.enter',
],
setup(props: InputWithButtonProps, {emit}) {
const internalValue = ref<string>();
const {
isFaIcon: _isFaIcon,
} = useIcon();
const isFaIcon = () => {
const {buttonIcon} = props;
if (!buttonIcon) return false;
return _isFaIcon(buttonIcon);
};
watch(() => props.modelValue, () => {
internalValue.value = props.modelValue;
});
const onInput = (value: string) => {
emit('update:model-value', value);
emit('input', value);
};
const onClick = () => {
emit('click');
};
const onBlur = () => {
emit('blur');
};
const onFocus = () => {
emit('focus');
};
const onKeyUpEnter = () => {
emit('keyup.enter');
};
onMounted(() => {
const {modelValue} = props;
internalValue.value = modelValue;
});
return {
internalValue,
isFaIcon,
onClick,
onInput,
onBlur,
onFocus,
onKeyUpEnter,
};
},
});
</script>
<style lang="scss" scoped>
.input-with-button {
display: inline-table;
vertical-align: middle;
//align-items: start;
.input {
display: table-cell;
}
.button {
display: table-cell;
}
}
</style>
<style scoped>
.input-with-button >>> .input.el-input .el-input__inner {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-with-button >>> .button .el-button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
height: 28px;
}
</style>

View File

@@ -0,0 +1,228 @@
<template>
<div class="tag-input">
<template v-for="(item, $index) in selectedValue" :key="$index">
<TagInputItem
v-if="item.isEdit"
ref="inputItemRef"
v-model="selectedValue[$index]"
:disabled="disabled"
placeholder="Tag Name"
size="mini"
@blur="onBlur($index, $event)"
@check="onCheck($index, $event)"
@close="onClose($index, $event)"
@delete="onDelete($index, $event)"
@focus="onFocus($index, $event)"
/>
<Tag
v-else
:closable="!disabled"
:color="item.color"
:disabled="disabled"
:label="item.name"
clickable
size="small"
type="plain"
@click="onEdit($index, $event)"
@close="onDelete($index, $event)"
/>
</template>
<el-tooltip :content="addButtonTooltip" :disabled="!addButtonTooltip">
<Tab
:icon="['fa', 'plus']"
:show-close="false"
:show-title="false"
class="add-btn"
:class="disabled ? 'disabled' : ''"
@click="onAdd"
/>
</el-tooltip>
</div>
</template>
<script lang="ts">
import {computed, defineComponent, PropType, ref, watch} from 'vue';
import TagComp from '@/components/tag/Tag.vue';
import Tab from '@/components/tab/Tab.vue';
import TagInputItem from '@/components/input/TagInputItem.vue';
import {cloneArray} from '@/utils/object';
import {getNewTag} from '@/components/tag/tag';
export default defineComponent({
name: 'TagInput',
components: {
TagInputItem,
Tag: TagComp,
Tab,
},
props: {
modelValue: {
type: Array as PropType<Tag[]>,
default: () => {
return [];
}
},
disabled: {
type: Boolean,
default: false,
}
},
emits: [
'change',
'update:model-value',
],
setup(props: TagInputProps, {emit}) {
const activeIndex = ref<number>(-1);
const inputItemRef = ref<typeof TagInputItem>();
const selectedValue = ref<TagInputOption[]>([]);
const emitValue = () => {
emit('change', selectedValue.value);
emit('update:model-value', selectedValue.value.map(d => {
return {
_id: d._id,
name: d.name,
color: d.color,
} as Tag;
}));
};
const disabled = computed<boolean>(() => props.disabled);
const addButtonTooltip = computed<string>(() => disabled.value ? '' : 'Add Tag');
const onEdit = (index: number, ev?: Event) => {
// check disabled
if (disabled.value) return;
ev?.stopPropagation();
const item = selectedValue.value[index];
item.isEdit = true;
// auto focus
setTimeout(() => inputItemRef.value?.focus(), 0);
};
const onDelete = (index: number, ev?: Event) => {
// check disabled
if (disabled.value) return;
ev?.stopPropagation();
selectedValue.value.splice(index, 1);
// commit change
emitValue();
};
const onFocus = (index: number, ev?: Event) => {
ev?.stopPropagation();
activeIndex.value = index;
};
const onBlur = (index: number, ev?: Event) => {
ev?.stopPropagation();
activeIndex.value = -1;
};
const onCheck = (index: number, value?: Tag, ev?: Event) => {
ev?.stopPropagation();
const item = selectedValue.value[index];
if (!item) return;
item.isEdit = false;
if (!value) return;
const {name, hex} = value;
item.name = name;
item.hex = hex;
// commit change
emitValue();
};
const onClose = (index: number, ev?: Event) => {
ev?.stopPropagation();
const item = selectedValue.value[index];
if (!item) return;
item.isEdit = false;
if (!item.name) {
selectedValue.value.splice(index, 1);
}
};
const onAdd = () => {
// check disabled
if (disabled.value) return;
// add value to array
selectedValue.value.push({
...getNewTag(),
isEdit: true,
});
// auto focus
setTimeout(() => inputItemRef.value?.focus(), 0);
};
watch(() => props.modelValue, () => {
const modelValue = props.modelValue || [];
selectedValue.value = cloneArray(modelValue);
});
return {
inputItemRef,
selectedValue,
addButtonTooltip,
onFocus,
onBlur,
onAdd,
onEdit,
onDelete,
onCheck,
onClose,
};
},
});
</script>
<style lang="scss" scoped>
@import "../../styles/variables.scss";
.tag-input {
display: flex;
flex-wrap: wrap;
align-items: center;
min-height: 28px;
.tag-input-item {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
.el-input {
width: 100px;
}
}
.add-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
&:not(.disabled) {
background-color: $white;
color: $infoMediumColor;
}
}
}
</style>
<style scoped>
.tag-input >>> .tag {
margin-right: 10px;
}
</style>

View File

@@ -0,0 +1,353 @@
<template>
<div
:class="[
isFocus ? 'is-focus' : '',
isNew ? 'is-new' : '',
]"
class="tag-input-item"
>
<!-- Input -->
<div class="input-wrapper">
<el-autocomplete
ref="inputRef"
v-model="internalValue.name"
:disabled="disabled"
:fetch-suggestions="fetchSuggestions"
:placeholder="placeholder"
:size="size"
popper-class="tag-input-item-popper"
class="input"
value-key="name"
@blur="onBlur"
@focus="onFocus"
@select="onSelect"
@keyup.enter="onCheck"
/>
<div class="actions">
<font-awesome-icon
:class="[isDisabled('check') ? 'disabled' : '']"
:icon="['fa', 'check']"
class="action-btn check"
@click="onCheck"
/>
<font-awesome-icon
:class="[isDisabled('close') ? 'disabled' : '']"
:icon="['fa', 'times']"
class="action-btn close"
@click="onClose"
/>
<font-awesome-icon
:class="[isDisabled('delete') ? 'disabled' : '']"
:icon="['fa', 'trash']"
class="action-btn delete"
@click="onDelete"
/>
</div>
</div>
<!-- ./Input -->
<!-- Color Picker -->
<ColorPicker
v-model="internalValue.color"
:disabled="!isNew"
:predefine="predefinedColors"
class="color-picker"
show-alpha
/>
<!-- <el-color-picker-->
<!-- v-model="internalValue.color"-->
<!-- :disabled="!isNew"-->
<!-- :predefine="predefinedColors"-->
<!-- class="color-picker"-->
<!-- show-alpha-->
<!-- />-->
<!-- ./Color Picker -->
</div>
</template>
<script lang="ts">
import {computed, defineComponent, inject, onMounted, PropType, readonly, ref, watch} from 'vue';
import {ElInput} from 'element-plus';
import {plainClone} from '@/utils/object';
import useTagService from '@/services/tag/tagService';
import {useStore} from 'vuex';
import {FILTER_OP_CONTAINS, FILTER_OP_EQUAL} from '@/constants/filter';
import {getNewTag} from '@/components/tag/tag';
import {getPredefinedColors} from '@/utils/color';
import ColorPicker from '@/components/color/ColorPicker.vue';
export default defineComponent({
name: 'TagInputItem',
components: {
ColorPicker,
},
props: {
modelValue: {
type: Object as PropType<Tag>,
},
placeholder: {
type: String,
},
size: {
type: String as PropType<BasicSize>,
default: 'mini',
},
disabled: {
type: Boolean,
default: false,
}
},
emits: [
'update:model-value',
'input',
'click',
'blur',
'focus',
'keyup.enter',
'close',
'check',
'delete',
],
setup(props: TagInputItemProps, {emit}) {
const store = useStore();
const internalValue = ref<Tag>(getNewTag());
const isFocus = ref<boolean>(false);
const inputRef = ref<typeof ElInput>();
const isNew = computed<boolean>(() => !internalValue.value._id);
// predefined colors
const predefinedColors = readonly<string[]>(getPredefinedColors());
watch(() => props.modelValue, () => {
if (!props.modelValue) {
internalValue.value = getNewTag();
} else {
internalValue.value = plainClone(props.modelValue);
}
});
const isDisabled = (key: string) => {
switch (key) {
case 'check':
return !internalValue.value.name;
case 'close':
return false;
case 'delete':
return false;
default:
return false;
}
};
const onInput = (name: string) => {
const value = {...props.modelValue, name};
emit('input', value);
};
const onClick = () => {
emit('click');
};
const onBlur = () => {
isFocus.value = false;
emit('blur');
};
const onFocus = () => {
isFocus.value = true;
emit('focus');
};
const focus = () => {
inputRef.value?.focus();
};
const onSelect = (value: Tag) => {
internalValue.value = value;
};
const onCheck = () => {
if (isDisabled('check')) return;
emit('update:model-value', internalValue.value);
emit('check', internalValue.value);
};
const onClose = () => {
if (isDisabled('close')) return;
emit('close');
};
const onDelete = () => {
if (isDisabled('delete')) return;
emit('delete');
};
const ctx = inject<ListStoreContext<BaseModel>>('store-context');
const fetchSuggestions = async (queryString: string, callback: (data: Tag[]) => void) => {
const {
getList,
} = useTagService(store);
const params = {
page: 1,
size: 50,
conditions: [
{key: 'col', op: FILTER_OP_EQUAL, value: `${ctx?.namespace}s`}
]
} as ListRequestParams;
if (queryString) {
const conditions = params.conditions as FilterConditionData[];
conditions.push({key: 'name', op: FILTER_OP_CONTAINS, value: queryString});
}
try {
const res = await getList(params);
return callback(res.data || []);
} catch (e) {
console.error(e);
callback([]);
}
};
onMounted(() => {
if (!props.modelValue) {
internalValue.value = getNewTag();
} else {
internalValue.value = plainClone(props.modelValue);
}
});
return {
predefinedColors,
internalValue,
isFocus,
inputRef,
isNew,
onClick,
onInput,
onBlur,
onFocus,
focus,
onCheck,
onClose,
onDelete,
onSelect,
isDisabled,
fetchSuggestions,
};
},
});
</script>
<style lang="scss" scoped>
@import "../../styles/variables.scss";
.tag-input-item {
display: flex;
align-items: center;
//height: 28px;
.input-wrapper {
display: inherit;
border: none;
position: relative;
height: 28px;
.actions {
position: absolute;
top: 0;
right: 5px;
.action-btn {
width: 14px;
height: 14px;
padding: 3px;
color: $infoMediumColor;
cursor: pointer;
&:hover:not(.disabled) {
&.check {
color: $successColor;
}
&.close {
color: $infoColor;
}
&.delete {
color: $dangerColor;
}
}
&.disabled {
color: $infoMediumLightColor;
cursor: not-allowed;
}
}
}
}
}
</style>
<style scoped>
.tag-input-item >>> .input,
.tag-input-item >>> .actions,
.tag-input-item >>> .color-picker,
.tag-input-item >>> .color-picker .el-color-picker {
margin: 0;
padding: 0;
height: 28px;
line-height: 28px;
}
.tag-input-item >>> .input {
display: inherit;
}
.tag-input-item >>> .input .el-input__inner {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
transition: none;
}
.tag-input-item >>> .color-picker .el-color-picker__trigger {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: none;
border-top: 1px solid #DCDFE6;
border-right: 1px solid #DCDFE6;
border-bottom: 1px solid #DCDFE6;
padding: 0;
}
.tag-input-item.is-focus >>> .color-picker .el-color-picker__trigger {
border-color: #409eff;
}
.tag-input-item >>> .color-picker .el-color-picker__color {
border: none;
}
.tag-input-item >>> .color-picker .el-color-picker__mask {
background: transparent;
border-radius: 0;
left: 0;
height: 28px;
width: 28px;
}
.tag-input-item >>> .el-autocomplete-suggestion__list > li {
height: 28px;
}
</style>
<style>
.tag-input-item-popper >>> .el-autocomplete-suggestion__list > li {
height: 28px;
}
</style>