fix
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<span
|
||||
class="badge"
|
||||
:class="[`badge--${variant}`, `badge--${size}`, { 'badge--clickable': clickable }]"
|
||||
:role="clickable ? 'button' : undefined"
|
||||
@click="onClick"
|
||||
>
|
||||
<span v-if="dot" class="badge__dot" />
|
||||
<SvgIcon v-if="icon" :name="icon" :size="iconSize" class="badge__icon" :color="iconColor" />
|
||||
<slot name="prepend" />
|
||||
<span v-if="label" class="badge__label">{{ label }}</span>
|
||||
<span v-if="$slots.default || value" class="badge__value">
|
||||
<slot>{{ value }}</slot>
|
||||
</span>
|
||||
<slot name="append" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import SvgIcon from '@/components/icons/SvgIcon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
/** @type {import('vue').PropType<'neutral' | 'success' | 'danger' | 'warning' | 'primary' | 'info' | 'cyan'>} */
|
||||
variant: { type: String, default: 'neutral' },
|
||||
/** @type {import('vue').PropType<'sm' | 'md' | 'lg'>} */
|
||||
size: { type: String, default: 'md' },
|
||||
label: { type: String, default: '' },
|
||||
value: { type: [String, Number], default: '' },
|
||||
/** @type {import('vue').PropType<import('@/components/icons/icon-names').IconName | ''>} */
|
||||
icon: { type: String, default: '' },
|
||||
dot: { type: Boolean, default: false },
|
||||
clickable: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const ICON_SIZE_MAP = { sm: 11, md: 14, lg: 16 }
|
||||
const iconSize = computed(() => ICON_SIZE_MAP[props.size] ?? 14)
|
||||
const iconColor = computed(() => 'currentColor')
|
||||
|
||||
const onClick = (event) => {
|
||||
if (!props.clickable) return
|
||||
emit('click', event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
border-radius: 0.75rem;
|
||||
font-family: var(--font-family-fa);
|
||||
white-space: nowrap;
|
||||
line-height: 1.5;
|
||||
transition: filter 0.15s ease;
|
||||
|
||||
&--clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 0.4em;
|
||||
height: 0.4em;
|
||||
border-radius: 9999px;
|
||||
background: currentcolor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-weight: 300;
|
||||
color: inherit;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&--sm {
|
||||
padding: 0.2rem 0.625rem;
|
||||
font-size: 0.65rem;
|
||||
gap: 0.3rem;
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
&--md {
|
||||
padding: 0.3rem 0.875rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&--lg {
|
||||
padding: 0.4rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 0.875rem;
|
||||
}
|
||||
|
||||
&--neutral {
|
||||
background: rgba(107, 107, 107, 5%);
|
||||
color: #535353;
|
||||
}
|
||||
|
||||
&--success {
|
||||
background: rgba(0, 153, 76, 10%);
|
||||
color: #00994c;
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background: rgba(243, 102, 117, 8%);
|
||||
color: #cc2831;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background: rgba(204, 154, 40, 10%);
|
||||
color: #cc6f00;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background: rgba(0, 112, 116, 8%);
|
||||
color: #007074;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background: rgba(104, 104, 104, 10%);
|
||||
color: #686868;
|
||||
}
|
||||
|
||||
&--cyan {
|
||||
background: rgba(104, 104, 104, 10%);
|
||||
color: #686868;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="my-tinymce">
|
||||
<Editor v-model="contentValue" :init="myInit" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import axios from 'axios'
|
||||
import tinymce from 'tinymce/tinymce'
|
||||
import '@/plugins/tinymce/importTinymce'
|
||||
import Editor from '@tinymce/tinymce-vue'
|
||||
import { initTiny } from '@/plugins/tinymce/tinymce'
|
||||
import { onMounted, toRefs, ref, reactive, defineProps, defineEmits, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// placeholder
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Please enter content',
|
||||
},
|
||||
// Default style
|
||||
style: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return { width: '100%', heigth: '400' }
|
||||
},
|
||||
},
|
||||
// image upload server address
|
||||
imgUploadUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/media',
|
||||
},
|
||||
batchId: {
|
||||
type: String,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
// Parameter custom initialization
|
||||
const customer = (init) => {
|
||||
// Allow the outside world to pass in height and placeholder
|
||||
init.height = props.style.heigth
|
||||
init.placeholder = props.placeholder
|
||||
|
||||
// Paste pictures and automatically process base64
|
||||
init.urlconverter_callback = (url, node, onSave, name) => {
|
||||
if (node === 'img' && url.startsWith('blob:')) {
|
||||
tinymce.activeEditor && tinymce.activeEditor.uploadImages()
|
||||
}
|
||||
return url
|
||||
}
|
||||
// upload picture
|
||||
init.images_upload_handler = (blobInfo, success, failure) => {
|
||||
imgUploadFn(blobInfo, success, failure)
|
||||
}
|
||||
return init
|
||||
}
|
||||
// const myInit = ref(customer(initTiny(props.batchId)));
|
||||
const state = reactive({
|
||||
myInit: ref(customer(initTiny(props.batchId))),
|
||||
contentValue: props.modelValue, // binding text
|
||||
timeout: null,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
tinymce.init({})
|
||||
})
|
||||
|
||||
// Listen for text changes and pass them to the outside world
|
||||
watch(
|
||||
() => state.contentValue,
|
||||
(n) => {
|
||||
debounce(() => {
|
||||
emit('update:modelValue', state.contentValue)
|
||||
})
|
||||
}
|
||||
)
|
||||
// Listen to the default value. The first time a v-model is passed in from the outside world, it is assigned to contentValue.
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(n) => {
|
||||
if (n && n !== state.contentValue) {
|
||||
state.contentValue = n
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const debounce = (fn, wait = 400) => {
|
||||
if (state.timeout !== null) {
|
||||
clearTimeout(state.timeout)
|
||||
}
|
||||
state.timeout = setTimeout(fn, wait)
|
||||
}
|
||||
|
||||
const imgUploadFn = async (blobInfo, success, failure) => {
|
||||
// Can limit image size
|
||||
// if (blobInfo.blob().size / 1024 / 1024 > 2) {
|
||||
// failure('Upload failed, please control the image size within 2M')
|
||||
// } else {}
|
||||
const formData = new FormData()
|
||||
formData.append('file', blobInfo.blob(), blobInfo.filename())
|
||||
formData.append('batch_id', props.batchId)
|
||||
const response = await axios.post('https://api.madomotor.ir'.props.imgUploadUrl, formData)
|
||||
|
||||
if (response && response.status == 200) {
|
||||
return success(response.data.data.url)
|
||||
}
|
||||
return failure(`HTTP Error: ${response.status}`)
|
||||
}
|
||||
|
||||
const { myInit, contentValue, timeout } = toRefs(state)
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
Reference in New Issue
Block a user