added example

This commit is contained in:
midfar 2023-03-13 16:55:51 +08:00
parent 8ccd85bc85
commit aff13ab76d
26 changed files with 1965 additions and 39 deletions

9
components.d.ts vendored
View File

@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
export interface GlobalComponents {
Breadcrumb: typeof import('./src/components/Breadcrumb/index.vue')['default']
DropdownMenu: typeof import('./src/components/Share/DropdownMenu.vue')['default']
EditorImage: typeof import('./src/components/Tinymce/components/EditorImage.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
@ -35,6 +36,7 @@ declare module '@vue/runtime-core' {
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRate: typeof import('element-plus/es')['ElRate']
@ -52,6 +54,7 @@ declare module '@vue/runtime-core' {
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
ErrorLog: typeof import('./src/components/ErrorLog/index.vue')['default']
GithubCorner: typeof import('./src/components/GithubCorner/index.vue')['default']
Hamburger: typeof import('./src/components/Hamburger/index.vue')['default']
@ -59,6 +62,7 @@ declare module '@vue/runtime-core' {
Keyboard: typeof import('./src/components/Charts/Keyboard.vue')['default']
LineMarker: typeof import('./src/components/Charts/LineMarker.vue')['default']
Mallki: typeof import('./src/components/TextHoverEffect/Mallki.vue')['default']
MDinput: typeof import('./src/components/MDinput/index.vue')['default']
MixChart: typeof import('./src/components/Charts/MixChart.vue')['default']
Pagination: typeof import('./src/components/Pagination/index.vue')['default']
PanThumb: typeof import('./src/components/PanThumb/index.vue')['default']
@ -66,8 +70,13 @@ declare module '@vue/runtime-core' {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Screenfull: typeof import('./src/components/Screenfull/index.vue')['default']
SingleImage: typeof import('./src/components/Upload/SingleImage.vue')['default']
SingleImage2: typeof import('./src/components/Upload/SingleImage2.vue')['default']
SingleImage3: typeof import('./src/components/Upload/SingleImage3.vue')['default']
SizeSelect: typeof import('./src/components/SizeSelect/index.vue')['default']
Sticky: typeof import('./src/components/Sticky/index.vue')['default']
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
Tinymce: typeof import('./src/components/Tinymce/index.vue')['default']
VueCountTo: typeof import('./src/components/vue-count-to/vue-countTo.vue')['default']
}
export interface ComponentCustomProperties {

View File

@ -10,7 +10,7 @@
<template v-slot:title>
<div class="text-title">
<span style="padding-right: 10px;">Error Log</span>
<el-button size="mini" type="primary" icon="el-icon-delete" @click="clearAll">Clear All</el-button>
<el-button size="small" type="primary" icon="el-icon-delete" @click="clearAll">Clear All</el-button>
</div>
</template>
<el-table :data="errorLogs" border>

View File

@ -0,0 +1,361 @@
<template>
<div :class="computedClasses" class="material-input__component">
<div :class="{iconClass:icon}">
<i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon" />
<input
v-if="type === 'email'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="email"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'url'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="url"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'number'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:step="step"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="number"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'password'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:required="required"
type="password"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'tel'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="tel"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'text'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="text"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<span class="material-input-bar" />
<label class="material-label">
<slot />
</label>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
export default defineComponent({
name: 'MdInput',
props: {
/* eslint-disable */
icon: String,
name: String,
type: {
type: String,
default: 'text'
},
modelValue: [String, Number],
placeholder: String,
readonly: Boolean,
disabled: Boolean,
min: String,
max: String,
step: String,
minlength: Number,
maxlength: Number,
required: {
type: Boolean,
default: true
},
autoComplete: {
type: String,
default: 'off'
},
validateEvent: {
type: Boolean,
default: true
}
},
data() {
return {
currentValue: this.modelValue,
focus: false,
fillPlaceHolder: null
}
},
computed: {
computedClasses() {
return {
'material--active': this.focus,
'material--disabled': this.disabled,
'material--raised': Boolean(this.focus || this.currentValue) // has value
}
}
},
watch: {
modelValue(newValue) {
this.currentValue = newValue
}
},
methods: {
handleModelInput(event) {
const value = event.target.value
this.$emit('update:modelValue', value)
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.change', [value])
}
}
this.$emit('change', value)
},
handleMdFocus(event) {
this.focus = true
this.$emit('focus', event)
if (this.placeholder && this.placeholder !== '') {
this.fillPlaceHolder = this.placeholder
}
},
handleMdBlur(event) {
this.focus = false
this.$emit('blur', event)
this.fillPlaceHolder = null
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.blur', [this.currentValue])
}
}
}
}
})
</script>
<style lang="scss" scoped>
// Fonts:
$font-size-base: 16px;
$font-size-small: 18px;
$font-size-smallest: 12px;
$font-weight-normal: normal;
$font-weight-bold: bold;
$apixel: 1px;
// Utils
$spacer: 12px;
$transition: 0.2s ease all;
$index: 0px;
$index-has-icon: 30px;
// Theme:
$color-white: white;
$color-grey: #9E9E9E;
$color-grey-light: #E0E0E0;
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
// Base clases:
%base-bar-pseudo {
content: '';
height: 1px;
width: 0;
bottom: 0;
position: absolute;
transition: $transition;
}
// Mixins:
@mixin slided-top() {
top: - ($font-size-base + $spacer);
left: 0;
font-size: $font-size-base;
font-weight: $font-weight-bold;
}
// Component:
.material-input__component {
margin-top: 36px;
position: relative;
* {
box-sizing: border-box;
}
.iconClass {
.material-input__icon {
position: absolute;
left: 0;
line-height: $font-size-base;
color: $color-blue;
top: $spacer;
width: $index-has-icon;
height: $font-size-base;
font-size: $font-size-base;
font-weight: $font-weight-normal;
pointer-events: none;
}
.material-label {
left: $index-has-icon;
}
.material-input {
text-indent: $index-has-icon;
}
}
.material-input {
font-size: $font-size-base;
padding: $spacer $spacer $spacer - $apixel * 10 $spacer * 0.5;
display: block;
width: 100%;
border: none;
line-height: 1;
border-radius: 0;
&:focus {
outline: none;
border: none;
border-bottom: 1px solid transparent; // fixes the height issue
}
}
.material-label {
font-weight: $font-weight-normal;
position: absolute;
pointer-events: none;
left: $index;
top: 0;
transition: $transition;
font-size: $font-size-small;
}
.material-input-bar {
position: relative;
display: block;
width: 100%;
&:before {
@extend %base-bar-pseudo;
left: 50%;
}
&:after {
@extend %base-bar-pseudo;
right: 50%;
}
}
// Disabled state:
&.material--disabled {
.material-input {
border-bottom-style: dashed;
}
}
// Raised state:
&.material--raised {
.material-label {
@include slided-top();
}
}
// Active state:
&.material--active {
.material-input-bar {
&:before,
&:after {
width: 50%;
}
}
}
}
.material-input__component {
background: $color-white;
.material-input {
background: none;
color: $color-black;
text-indent: $index;
border-bottom: 1px solid $color-grey-light;
}
.material-label {
color: $color-grey;
}
.material-input-bar {
&:before,
&:after {
background: $color-blue;
}
}
// Active state:
&.material--active {
.material-label {
color: $color-blue;
}
}
// Errors:
&.material--has-errors {
&.material--active .material-label {
color: $color-red;
}
.material-input-bar {
&:before,
&:after {
background: transparent;
}
}
}
}
</style>

View File

@ -7,6 +7,7 @@
<script>
import { defineComponent } from 'vue';
import screenfull, { bindF11, unbindF11 } from '@/utils/screenfull';
import { ElMessage } from 'element-plus';
export default defineComponent({
name: 'Screenfull',

View File

@ -24,9 +24,8 @@ export default defineComponent({
return {
sizeOptions: [
{ label: 'Default', value: 'default' },
{ label: 'Medium', value: 'medium' },
{ label: 'Small', value: 'small' },
{ label: 'Mini', value: 'mini' }
{ label: 'Large', value: 'large' },
{ label: 'small', value: 'default' }
]
};
},

View File

@ -0,0 +1,93 @@
<template>
<div :style="{height:height+'px',zIndex:zIndex}">
<div
:class="className"
:style="{top:(isSticky ? stickyTop +'px' : ''),zIndex:zIndex,position:position,width:width,height:height+'px'}"
>
<slot>
<div>sticky</div>
</slot>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Sticky',
props: {
stickyTop: {
type: Number,
default: 0
},
zIndex: {
type: Number,
default: 1
},
className: {
type: String,
default: ''
}
},
data() {
return {
active: false,
position: '',
width: undefined,
height: undefined,
isSticky: false
};
},
mounted() {
this.height = this.$el.getBoundingClientRect().height;
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleResize);
},
activated() {
this.handleScroll();
},
unmounted() {
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
},
methods: {
sticky() {
if (this.active) {
return;
}
this.position = 'fixed';
this.active = true;
this.width = this.width + 'px';
this.isSticky = true;
},
handleReset() {
if (!this.active) {
return;
}
this.reset();
},
reset() {
this.position = '';
this.width = 'auto';
this.active = false;
this.isSticky = false;
},
handleScroll() {
const width = this.$el.getBoundingClientRect().width;
this.width = width || 'auto';
const offsetTop = this.$el.getBoundingClientRect().top;
if (offsetTop < this.stickyTop) {
this.sticky();
return;
}
this.handleReset();
},
handleResize() {
if (this.isSticky) {
this.width = this.$el.getBoundingClientRect().width + 'px';
}
}
}
});
</script>

View File

@ -0,0 +1,116 @@
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="small" type="primary" @click=" dialogVisible=true">
upload
</el-button>
<el-dialog v-model="dialogVisible">
<el-upload
:multiple="true"
:file-list="fileList"
:show-file-list="true"
:on-remove="handleRemove"
:on-success="handleSuccess"
:before-upload="beforeUpload"
class="editor-slide-upload"
action="https://httpbin.org/post"
list-type="picture-card"
>
<el-button size="small" type="primary">
Click upload
</el-button>
</el-upload>
<el-button @click="dialogVisible = false">
Cancel
</el-button>
<el-button type="primary" @click="handleSubmit">
Confirm
</el-button>
</el-dialog>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { ElMessage } from 'element-plus';
// import { getToken } from 'api/qiniu'
export default defineComponent({
name: 'EditorSlideUpload',
props: {
color: {
type: String,
default: '#1890ff'
}
},
data() {
return {
dialogVisible: false,
listObj: {},
fileList: []
};
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess);
},
handleSubmit() {
const arr = Object.keys(this.listObj).map(v => this.listObj[v]);
if (!this.checkAllSuccess()) {
ElMessage({
message: 'Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!'
});
return;
}
this.$emit('successCBK', arr);
this.listObj = {};
this.fileList = [];
this.dialogVisible = false;
},
handleSuccess(response, file) {
const uid = file.uid;
const objKeyArr = Object.keys(this.listObj);
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = response.files.file;
this.listObj[objKeyArr[i]].hasSuccess = true;
return;
}
}
},
handleRemove(file) {
const uid = file.uid;
const objKeyArr = Object.keys(this.listObj);
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]];
return;
}
}
},
beforeUpload(file) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _self = this;
const _URL = window.URL || window.webkitURL;
const fileName = file.uid;
this.listObj[fileName] = {};
return new Promise((resolve, reject) => {
const img = new Image();
img.src = _URL.createObjectURL(file);
img.onload = function() {
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height };
};
resolve(true);
});
}
}
});
</script>
<style lang="scss" scoped>
.editor-slide-upload {
margin-bottom: 20px;
:deep(.el-upload--picture-card) {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,60 @@
let callbacks = [];
function loadedTinymce() {
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
// check is successfully downloaded script
return window.tinymce;
}
const dynamicLoadScript = (src, callback) => {
const existingScript = document.getElementById(src);
// eslint-disable-next-line @typescript-eslint/no-empty-function
const cb = callback || function() {};
if (!existingScript) {
const script = document.createElement('script');
script.src = src; // src url for the third-party library being loaded.
script.id = src;
document.body.appendChild(script);
callbacks.push(cb);
const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd;
onEnd(script);
}
if (existingScript && cb) {
if (loadedTinymce()) {
cb(null, existingScript);
} else {
callbacks.push(cb);
}
}
function stdOnEnd(script) {
script.onload = function() {
// this.onload = null here is necessary
// because even IE9 works not like others
this.onerror = this.onload = null;
for (const cb of callbacks) {
cb(null, script);
}
callbacks = null;
};
script.onerror = function() {
this.onerror = this.onload = null;
cb(new Error('Failed to load ' + src), script);
};
}
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return;
this.onreadystatechange = null;
for (const cb of callbacks) {
cb(null, script); // there is no way to catch loading errors in IE8
}
callbacks = null;
};
}
};
export default dynamicLoadScript;

View File

@ -0,0 +1,255 @@
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { ElMessage } from 'element-plus';
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import editorImage from './components/EditorImage';
import plugins from './plugins';
import toolbar from './toolbar';
import load from './dynamicLoadScript';
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN = 'https://unpkg.com/tinymce-all-in-one@4.9.3/tinymce.min.js';
export default defineComponent({
name: 'Tinymce',
components: { editorImage },
props: {
id: {
type: String,
default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '');
}
},
modelValue: {
type: String,
default: ''
},
toolbar: {
type: Array,
required: false,
default() {
return [];
}
},
menubar: {
type: String,
default: 'file edit insert view format table'
},
height: {
type: [Number, String],
required: false,
default: 360
},
width: {
type: [Number, String],
required: false,
default: 'auto'
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN',
'es': 'es_MX',
'ja': 'ja'
}
};
},
computed: {
containerWidth() {
const width = this.width;
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
return `${width}px`;
}
return width;
}
},
watch: {
modelValue(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || ''));
}
}
},
mounted() {
this.init();
},
activated() {
if (window.tinymce) {
this.initTinymce();
}
},
deactivated() {
this.destroyTinymce();
},
unmounted() {
this.destroyTinymce();
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) => {
if (err) {
ElMessage({
message: err.message,
type: 'error'
});
return;
}
this.initTinymce();
});
},
initTinymce() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList['en'],
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: editor => {
if (that.modelValue) {
editor.setContent(that.modelValue);
}
that.hasInit = true;
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true;
this.$emit('update:modelValue', editor.getContent());
});
},
setup(editor) {
editor.on('FullscreenStateChanged', (e) => {
that.fullscreen = e.state;
});
},
// it will try to keep these URLs intact
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
convert_urls: false
//
// images_dataimg_filter(img) {
// setTimeout(() => {
// const $image = $(img);
// $image.removeAttr('width');
// $image.removeAttr('height');
// if ($image[0].height && $image[0].width) {
// $image.attr('data-wscntype', 'image');
// $image.attr('data-wscnh', $image[0].height);
// $image.attr('data-wscnw', $image[0].width);
// $image.addClass('wscnph');
// }
// }, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = that.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('')
// console.log(err);
// });
// },
});
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId);
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen');
}
if (tinymce) {
tinymce.destroy();
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value);
},
getContent() {
window.tinymce.get(this.tinymceId).getContent();
},
imageSuccessCBK(arr) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
arr.forEach(v => {
window.tinymce.get(that.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`);
});
}
}
});
</script>
<style lang="scss" scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container {
:deep(.mce-fullscreen) {
z-index: 10000;
}
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>

View File

@ -0,0 +1,7 @@
// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount'];
export default plugins;

View File

@ -0,0 +1,6 @@
// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'];
export default toolbar;

View File

@ -0,0 +1,136 @@
<template>
<div class="upload-container">
<el-upload
:data="dataObj"
:multiple="false"
:show-file-list="false"
:on-success="handleImageSuccess"
class="image-uploader"
drag
action="https://httpbin.org/post"
>
<i class="el-icon-upload" />
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
</el-upload>
<div class="image-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl+'?imageView2/1/w/200/h/200'">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { getToken } from '@/api/qiniu';
export default defineComponent({
name: 'SingleImageUpload',
props: {
modelValue: {
type: String,
default: ''
}
},
data() {
return {
tempUrl: '',
dataObj: { token: '', key: '' }
};
},
computed: {
imageUrl() {
return this.modelValue;
}
},
methods: {
rmImage() {
this.emitInput('');
},
emitInput(val) {
this.$emit('update:modelValue', val);
},
handleImageSuccess() {
this.emitInput(this.tempUrl);
},
beforeUpload() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _self = this;
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key;
const token = response.data.qiniu_token;
_self._data.dataObj.token = token;
_self._data.dataObj.key = key;
this.tempUrl = response.data.qiniu_url;
resolve(true);
}).catch(err => {
console.log(err);
reject(false);
});
});
}
}
});
</script>
<style lang="scss" scoped>
@import "@/styles/mixin.scss";
.upload-container {
width: 100%;
position: relative;
@include clearfix;
.image-uploader {
width: 60%;
float: left;
}
.image-preview {
width: 200px;
height: 200px;
position: relative;
border: 1px dashed #d9d9d9;
float: left;
margin-left: 50px;
.image-preview-wrapper {
position: relative;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
.image-preview-action {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, .5);
transition: opacity .3s;
cursor: pointer;
text-align: center;
line-height: 200px;
.el-icon-delete {
font-size: 36px;
}
}
&:hover {
.image-preview-action {
opacity: 1;
}
}
}
}
</style>

View File

@ -0,0 +1,132 @@
<template>
<div class="singleImageUpload2 upload-container">
<el-upload
:data="dataObj"
:multiple="false"
:show-file-list="false"
:on-success="handleImageSuccess"
class="image-uploader"
drag
action="https://httpbin.org/post"
>
<i class="el-icon-upload" />
<div class="el-upload__text">
Drag或<em>点击上传</em>
</div>
</el-upload>
<div v-show="imageUrl.length>0" class="image-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { getToken } from '@/api/qiniu';
export default defineComponent({
name: 'SingleImageUpload2',
props: {
modelValue: {
type: String,
default: ''
}
},
data() {
return {
tempUrl: '',
dataObj: { token: '', key: '' }
};
},
computed: {
imageUrl() {
return this.modelValue;
}
},
methods: {
rmImage() {
this.emitInput('');
},
emitInput(val) {
this.$emit('update:modelValue', val);
},
handleImageSuccess() {
this.emitInput(this.tempUrl);
},
beforeUpload() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _self = this;
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key;
const token = response.data.qiniu_token;
_self._data.dataObj.token = token;
_self._data.dataObj.key = key;
this.tempUrl = response.data.qiniu_url;
resolve(true);
}).catch(() => {
reject(false);
});
});
}
}
});
</script>
<style lang="scss" scoped>
.upload-container {
width: 100%;
height: 100%;
position: relative;
.image-uploader {
height: 100%;
}
.image-preview {
width: 100%;
height: 100%;
position: absolute;
left: 0px;
top: 0px;
border: 1px dashed #d9d9d9;
.image-preview-wrapper {
position: relative;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
.image-preview-action {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, .5);
transition: opacity .3s;
cursor: pointer;
text-align: center;
line-height: 200px;
.el-icon-delete {
font-size: 36px;
}
}
&:hover {
.image-preview-action {
opacity: 1;
}
}
}
}
</style>

View File

@ -0,0 +1,159 @@
<template>
<div class="upload-container">
<el-upload
:data="dataObj"
:multiple="false"
:show-file-list="false"
:on-success="handleImageSuccess"
class="image-uploader"
drag
action="https://httpbin.org/post"
>
<i class="el-icon-upload" />
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
</el-upload>
<div class="image-preview image-app-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
<div class="image-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { getToken } from '@/api/qiniu';
export default defineComponent({
name: 'SingleImageUpload3',
props: {
modelValue: {
type: String,
default: ''
}
},
data() {
return {
tempUrl: '',
dataObj: { token: '', key: '' }
};
},
computed: {
imageUrl() {
return this.modelValue;
}
},
methods: {
rmImage() {
this.emitInput('');
},
emitInput(val) {
this.$emit('update:modelValue', val);
},
handleImageSuccess(file) {
this.emitInput(file.files.file);
},
beforeUpload() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _self = this;
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key;
const token = response.data.qiniu_token;
_self._data.dataObj.token = token;
_self._data.dataObj.key = key;
this.tempUrl = response.data.qiniu_url;
resolve(true);
}).catch(err => {
console.log(err);
reject(false);
});
});
}
}
});
</script>
<style lang="scss" scoped>
@import "@/styles/mixin.scss";
.upload-container {
width: 100%;
position: relative;
@include clearfix;
.image-uploader {
width: 35%;
float: left;
}
.image-preview {
width: 200px;
height: 200px;
position: relative;
border: 1px dashed #d9d9d9;
float: left;
margin-left: 50px;
.image-preview-wrapper {
position: relative;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
.image-preview-action {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, .5);
transition: opacity .3s;
cursor: pointer;
text-align: center;
line-height: 200px;
.el-icon-delete {
font-size: 36px;
}
}
&:hover {
.image-preview-action {
opacity: 1;
}
}
}
.image-app-preview {
width: 320px;
height: 180px;
position: relative;
border: 1px dashed #d9d9d9;
float: left;
margin-left: 50px;
.app-fake-conver {
height: 44px;
position: absolute;
width: 100%; // background: rgba(0, 0, 0, .1);
text-align: center;
line-height: 64px;
color: #fff;
}
}
}
</style>

View File

@ -171,37 +171,36 @@ export const asyncRoutes:RouteRecordRaw[] = [
nestedRouter,
tableRouter,
// {
// path: '/example',
// component: Layout,
// redirect: '/example/list',
// name: 'Example',
// meta: {
// title: 'Example',
// icon: 'el-icon-s-help'
// },
// children: [
// {
// path: 'create',
// component: () => import('@/views/example/create'),
// name: 'CreateArticle',
// meta: { title: 'Create Article', icon: 'edit' }
// },
// {
// path: 'edit/:id(\\d+)',
// component: () => import('@/views/example/edit'),
// name: 'EditArticle',
// meta: { title: 'Edit Article', noCache: true, activeMenu: '/example/list' },
// hidden: true
// },
// {
// path: 'list',
// component: () => import('@/views/example/list'),
// name: 'ArticleList',
// meta: { title: 'Article List', icon: 'list' }
// }
// ]
// },
{
path: '/example',
component: Layout,
redirect: '/example/list',
name: 'Example',
meta: {
title: 'Example',
icon: 'el-icon-s-help'
},
children: [
{
path: 'create',
component: () => import('@/views/example/create.vue'),
name: 'CreateArticle',
meta: { title: 'Create Article', icon: 'edit' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/example/edit.vue'),
name: 'EditArticle',
meta: { hidden: true, title: 'Edit Article', noCache: true, activeMenu: '/example/list' }
},
{
path: 'list',
component: () => import('@/views/example/list.vue'),
name: 'ArticleList',
meta: { title: 'Article List', icon: 'list' }
}
]
},
{
path: '/tab',

View File

@ -0,0 +1,297 @@
<template>
<div class="createPost-container">
<el-form ref="postForm" :model="postForm" :rules="rules" class="form-container">
<sticky :z-index="10" :class-name="'sub-navbar '+postForm.status">
<CommentDropdown v-model="postForm.comment_disabled" />
<PlatformDropdown v-model="postForm.platforms" />
<SourceUrlDropdown v-model="postForm.source_uri" />
<el-button v-loading="loading" style="margin-left: 10px;" type="success" @click="submitForm">
Publish
</el-button>
<el-button v-loading="loading" type="warning" @click="draftForm">
Draft
</el-button>
</sticky>
<div class="createPost-main-container">
<el-row>
<Warning />
<el-col :span="24">
<el-form-item style="margin-bottom: 40px;" prop="title">
<MDinput v-model="postForm.title" :maxlength="100" name="name" required>
Title
</MDinput>
</el-form-item>
<div class="postInfo-container">
<el-row>
<el-col :span="8">
<el-form-item label-width="60px" label="Author:" class="postInfo-container-item">
<el-select v-model="postForm.author" :remote-method="getRemoteUserList" filterable default-first-option remote placeholder="Search user">
<el-option v-for="(item,index) in userListOptions" :key="item+index" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label-width="120px" label="Publish Time:" class="postInfo-container-item">
<el-date-picker v-model="displayTime" type="datetime" format="yyyy-MM-dd HH:mm:ss" placeholder="Select date and time" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="90px" label="Importance:" class="postInfo-container-item">
<el-rate
v-model="postForm.importance"
:max="3"
:colors="['#99A9BF', '#F7BA2A', '#FF9900']"
:low-threshold="1"
:high-threshold="3"
style="display:inline-block"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<el-form-item style="margin-bottom: 40px;" label-width="70px" label="Summary:">
<el-input v-model="postForm.content_short" :rows="1" type="textarea" class="article-textarea" autosize placeholder="Please enter the content" />
<span v-show="contentShortLength" class="word-counter">{{ contentShortLength }}words</span>
</el-form-item>
<el-form-item prop="content" style="margin-bottom: 30px;">
<Tinymce ref="editor" v-model="postForm.content" :height="400" />
</el-form-item>
<el-form-item prop="image_uri" style="margin-bottom: 30px;">
<Upload v-model="postForm.image_uri" />
</el-form-item>
</div>
</el-form>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { ElMessage, ElNotification } from 'element-plus';
import Tinymce from '@/components/Tinymce';
import Upload from '@/components/Upload/SingleImage3';
import MDinput from '@/components/MDinput';
import Sticky from '@/components/Sticky'; // header
import { validURL } from '@/utils/validate';
import { fetchArticle } from '@/api/article';
import { searchUser } from '@/api/remote-search';
import Warning from './Warning';
import { CommentDropdown, PlatformDropdown, SourceUrlDropdown } from './Dropdown';
const defaultForm = {
status: 'draft',
title: '', //
content: '', //
content_short: '', //
source_uri: '', //
image_uri: '', //
display_time: undefined, //
id: undefined,
platforms: ['a-platform'],
comment_disabled: false,
importance: 0
};
export default defineComponent({
name: 'ArticleDetail',
components: { Tinymce, MDinput, Upload, Sticky, Warning, CommentDropdown, PlatformDropdown, SourceUrlDropdown },
props: {
isEdit: {
type: Boolean,
default: false
}
},
data() {
const validateRequire = (rule, value, callback) => {
if (value === '') {
ElMessage({
message: rule.field + 'xxx为必传项',
type: 'error'
});
callback(new Error(rule.field + '为必传项'));
} else {
callback();
}
};
const validateSourceUri = (rule, value, callback) => {
if (value) {
if (validURL(value)) {
callback();
} else {
ElMessage({
message: '外链url填写不正确',
type: 'error'
});
callback(new Error('外链url填写不正确'));
}
} else {
callback();
}
};
return {
postForm: Object.assign({}, defaultForm),
loading: false,
userListOptions: [],
rules: {
image_uri: [{ validator: validateRequire }],
title: [{ validator: validateRequire }],
content: [{ validator: validateRequire }],
source_uri: [{ validator: validateSourceUri, trigger: 'blur' }]
},
tempRoute: {}
};
},
computed: {
contentShortLength() {
return this.postForm.content_short.length;
},
displayTime: {
// set and get is useful when the data
// returned by the back end api is different from the front end
// back end return => "2013-06-25 06:59:25"
// front end need timestamp => 1372114765000
get() {
return (+new Date(this.postForm.display_time));
},
set(val) {
this.postForm.display_time = new Date(val);
}
}
},
created() {
if (this.isEdit) {
const id = this.$route.params && this.$route.params.id;
this.fetchData(id);
}
// Why need to make a copy of this.$route here?
// Because if you enter this page and quickly switch tag, may be in the execution of the setTagsViewTitle function, this.$route is no longer pointing to the current page
// https://github.com/PanJiaChen/vue-element-admin/issues/1221
this.tempRoute = Object.assign({}, this.$route);
},
methods: {
fetchData(id) {
fetchArticle(id).then(response => {
this.postForm = response.data;
// just for test
this.postForm.title += ` Article Id:${this.postForm.id}`;
this.postForm.content_short += ` Article Id:${this.postForm.id}`;
// set tagsview title
this.setTagsViewTitle();
// set page title
this.setPageTitle();
}).catch(err => {
console.log(err);
});
},
setTagsViewTitle() {
const title = 'Edit Article';
const route = Object.assign({}, this.tempRoute, { title: `${title}-${this.postForm.id}` });
this.$store.dispatch('tagsView/updateVisitedView', route);
},
setPageTitle() {
const title = 'Edit Article';
document.title = `${title} - ${this.postForm.id}`;
},
submitForm() {
console.log(this.postForm);
this.$refs.postForm.validate(valid => {
if (valid) {
this.loading = true;
ElNotification({
title: '成功',
message: '发布文章成功',
type: 'success',
duration: 2000
});
this.postForm.status = 'published';
this.loading = false;
} else {
console.log('error submit!!');
return false;
}
});
},
draftForm() {
if (this.postForm.content.length === 0 || this.postForm.title.length === 0) {
ElMessage({
message: '请填写必要的标题和内容',
type: 'warning'
});
return;
}
ElMessage({
message: '保存成功',
type: 'success',
showClose: true,
duration: 1000
});
this.postForm.status = 'draft';
},
getRemoteUserList(query) {
searchUser(query).then(response => {
if (!response.data.items) return;
this.userListOptions = response.data.items.map(v => v.name);
});
}
}
});
</script>
<style lang="scss" scoped>
@import "@/styles/mixin.scss";
.createPost-container {
position: relative;
:deep(.sub-navbar) {
display: flex;
align-items: center;
justify-content: right;
}
.createPost-main-container {
padding: 40px 45px 20px 50px;
.postInfo-container {
position: relative;
@include clearfix;
margin-bottom: 10px;
.postInfo-container-item {
float: left;
}
}
}
.word-counter {
width: 40px;
position: absolute;
right: 10px;
top: 0px;
}
}
.article-textarea {
:deep(textarea) {
padding-right: 40px;
resize: none;
border: none;
border-radius: 0px;
border-bottom: 1px solid #bfcbd9;
}
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<el-dropdown :show-timeout="100" trigger="click">
<el-button plain>
{{ !comment_disabled?'Comment: opened':'Comment: closed' }}
<i class="el-icon-caret-bottom el-icon--right" />
</el-button>
<template v-slot:dropdown>
<el-dropdown-menu class="no-padding">
<el-dropdown-item>
<el-radio-group v-model="comment_disabled" style="padding: 10px;">
<el-radio :label="true">
Close comment
</el-radio>
<el-radio :label="false">
Open comment
</el-radio>
</el-radio-group>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
modelValue: {
type: Boolean,
default: false
}
},
computed: {
comment_disabled: {
get() {
return this.modelValue;
},
set(val) {
this.$emit('update:modelValue', val);
}
}
}
});
</script>

View File

@ -0,0 +1,50 @@
<template>
<el-dropdown :hide-on-click="false" :show-timeout="100" trigger="click">
<el-button plain>
Platfroms({{ platforms.length }})
<i class="el-icon-caret-bottom el-icon--right" />
</el-button>
<template v-slot:dropdown>
<el-dropdown-menu class="no-border">
<el-checkbox-group v-model="platforms" style="padding: 5px 15px;">
<el-checkbox v-for="item in platformsOptions" :key="item.key" :label="item.key">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
modelValue: {
required: true,
default: () => [],
type: Array
}
},
data() {
return {
platformsOptions: [
{ key: 'a-platform', name: 'a-platform' },
{ key: 'b-platform', name: 'b-platform' },
{ key: 'c-platform', name: 'c-platform' }
]
};
},
computed: {
platforms: {
get() {
return this.modelValue;
},
set(val) {
this.$emit('update:modelValue', val);
}
}
}
});
</script>

View File

@ -0,0 +1,42 @@
<template>
<el-dropdown :show-timeout="100" trigger="click">
<el-button plain>
Link
<i class="el-icon-caret-bottom el-icon--right" />
</el-button>
<template v-slot:dropdown>
<el-dropdown-menu class="no-padding no-border" style="width:400px">
<el-form-item label-width="0px" style="margin-bottom: 0px" prop="source_uri">
<el-input v-model="source_uri" placeholder="Please enter the content">
<template v-slot:prepend>
URL
</template>
</el-input>
</el-form-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
modelValue: {
type: String,
default: ''
}
},
computed: {
source_uri: {
get() {
return this.modelValue;
},
set(val) {
this.$emit('update:modelValue', val);
}
}
}
});
</script>

View File

@ -0,0 +1,3 @@
export { default as CommentDropdown } from './Comment';
export { default as PlatformDropdown } from './Platform';
export { default as SourceUrlDropdown } from './SourceUrl';

View File

@ -0,0 +1,13 @@
<template>
<aside>
Creating and editing pages cannot be cached by keep-alive because keep-alive include does not currently support
caching based on routes, so it is currently cached based on component name. If you want to achieve a similar caching
effect, you can use a browser caching scheme such as localStorage. Or do not use keep-alive include to cache all
pages directly. See details
<a
href="https://panjiachen.github.io/vue-element-admin-site/guide/essentials/tags-view.html"
target="_blank"
>Document</a>
</aside>
</template>

View File

@ -0,0 +1,14 @@
<template>
<article-detail :is-edit="false" />
</template>
<script>
import { defineComponent } from 'vue';
import ArticleDetail from './components/ArticleDetail';
export default defineComponent({
name: 'CreateArticle',
components: { ArticleDetail }
});
</script>

View File

@ -0,0 +1,14 @@
<template>
<article-detail :is-edit="true" />
</template>
<script>
import { defineComponent } from 'vue';
import ArticleDetail from './components/ArticleDetail';
export default defineComponent({
name: 'EditForm',
components: { ArticleDetail }
});
</script>

113
src/views/example/list.vue Normal file
View File

@ -0,0 +1,113 @@
<template>
<div class="app-container">
<el-table v-loading="listLoading" :data="list" border fit highlight-current-row style="width: 100%">
<el-table-column align="center" label="ID" width="80">
<template v-slot="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column width="180px" align="center" label="Date">
<template v-slot="scope">
<span>{{ parseTime(scope.row.timestamp, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column width="120px" align="center" label="Author">
<template v-slot="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column width="100px" label="Importance">
<template v-slot="scope">
<svg-icon v-for="n in scope.row.importance" :key="n" icon-class="star" class="meta-item__icon" />
</template>
</el-table-column>
<el-table-column class-name="status-col" label="Status" width="110">
<template v-slot="{row}">
<el-tag :type="statusFilter(row.status)">
{{ row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column min-width="300px" label="Title">
<template v-slot="{row}">
<router-link :to="'/example/edit/'+row.id" class="link-type">
<span>{{ row.title }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column align="center" label="Actions" width="120">
<template v-slot="scope">
<router-link :to="'/example/edit/'+scope.row.id">
<el-button type="primary" size="small" icon="el-icon-edit">
Edit
</el-button>
</router-link>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" v-model:page="listQuery.page" v-model:limit="listQuery.limit" @pagination="getList" />
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { parseTime } from '@/utils';
import { fetchList } from '@/api/article';
import Pagination from '@/components/Pagination'; // Secondary package based on el-pagination
export default defineComponent({
name: 'ArticleList',
components: { Pagination },
data() {
return {
list: null,
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 20
}
};
},
created() {
this.getList();
},
methods: {
parseTime,
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'info',
deleted: 'danger'
};
return statusMap[status];
},
getList() {
this.listLoading = true;
fetchList(this.listQuery).then(response => {
this.list = response.data.items;
this.total = response.data.total;
this.listLoading = false;
});
}
}
});
</script>
<style scoped>
.edit-input {
padding-right: 100px;
}
.cancel-btn {
position: absolute;
right: 15px;
top: 10px;
}
</style>

View File

@ -158,6 +158,7 @@ import { fetchList, fetchPv, createArticle, updateArticle } from '@/api/article'
import waves from '@/directive/waves'; // waves directive
import { parseTime } from '@/utils';
import Pagination from '@/components/Pagination'; // secondary package based on el-pagination
import { ElNotification } from 'element-plus';
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
@ -303,7 +304,7 @@ export default defineComponent({
createArticle(this.temp).then(() => {
this.list.unshift(this.temp);
this.dialogFormVisible = false;
this.$notify({
ElNotification({
title: 'Success',
message: 'Created Successfully',
type: 'success',
@ -331,7 +332,7 @@ export default defineComponent({
const index = this.list.findIndex(v => v.id === this.temp.id);
this.list.splice(index, 1, this.temp);
this.dialogFormVisible = false;
this.$notify({
ElNotification({
title: 'Success',
message: 'Update Successfully',
type: 'success',
@ -342,7 +343,7 @@ export default defineComponent({
});
},
handleDelete(row, index) {
this.$notify({
ElNotification({
title: 'Success',
message: 'Delete Successfully',
type: 'success',

View File

@ -36,7 +36,8 @@
"src/**/*.tsx",
"src/**/*.vue",
"types/**/*.d.ts",
"types/**/*.ts"
"types/**/*.ts",
"auto-imports.d.ts",
],
"exclude": ["node_modules", "dist", "**/*.js", "**/*.md", "src/**/*.md"]
}