added example
This commit is contained in:
parent
8ccd85bc85
commit
aff13ab76d
9
components.d.ts
vendored
9
components.d.ts
vendored
@ -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 {
|
||||
|
@ -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>
|
||||
|
361
src/components/MDinput/index.vue
Normal file
361
src/components/MDinput/index.vue
Normal 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>
|
@ -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',
|
||||
|
@ -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' }
|
||||
]
|
||||
};
|
||||
},
|
||||
|
93
src/components/Sticky/index.vue
Normal file
93
src/components/Sticky/index.vue
Normal 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>
|
116
src/components/Tinymce/components/EditorImage.vue
Normal file
116
src/components/Tinymce/components/EditorImage.vue
Normal 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>
|
60
src/components/Tinymce/dynamicLoadScript.js
Normal file
60
src/components/Tinymce/dynamicLoadScript.js
Normal 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;
|
255
src/components/Tinymce/index.vue
Normal file
255
src/components/Tinymce/index.vue
Normal 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 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>
|
7
src/components/Tinymce/plugins.js
Normal file
7
src/components/Tinymce/plugins.js
Normal 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;
|
6
src/components/Tinymce/toolbar.js
Normal file
6
src/components/Tinymce/toolbar.js
Normal 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;
|
136
src/components/Upload/SingleImage.vue
Normal file
136
src/components/Upload/SingleImage.vue
Normal 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>
|
132
src/components/Upload/SingleImage2.vue
Normal file
132
src/components/Upload/SingleImage2.vue
Normal 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>
|
159
src/components/Upload/SingleImage3.vue
Normal file
159
src/components/Upload/SingleImage3.vue
Normal 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>
|
@ -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',
|
||||
|
297
src/views/example/components/ArticleDetail.vue
Normal file
297
src/views/example/components/ArticleDetail.vue
Normal 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>
|
45
src/views/example/components/Dropdown/Comment.vue
Normal file
45
src/views/example/components/Dropdown/Comment.vue
Normal 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>
|
50
src/views/example/components/Dropdown/Platform.vue
Normal file
50
src/views/example/components/Dropdown/Platform.vue
Normal 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>
|
42
src/views/example/components/Dropdown/SourceUrl.vue
Normal file
42
src/views/example/components/Dropdown/SourceUrl.vue
Normal 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>
|
3
src/views/example/components/Dropdown/index.js
Normal file
3
src/views/example/components/Dropdown/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as CommentDropdown } from './Comment';
|
||||
export { default as PlatformDropdown } from './Platform';
|
||||
export { default as SourceUrlDropdown } from './SourceUrl';
|
13
src/views/example/components/Warning.vue
Normal file
13
src/views/example/components/Warning.vue
Normal 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>
|
||||
|
14
src/views/example/create.vue
Normal file
14
src/views/example/create.vue
Normal 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>
|
||||
|
14
src/views/example/edit.vue
Normal file
14
src/views/example/edit.vue
Normal 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
113
src/views/example/list.vue
Normal 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>
|
@ -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',
|
||||
|
@ -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"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user