vue3 把quill的base64变成图片地址
你可以看看https://www.kancloud.cn/liuwave/quill/1434141
或者看看别人的文章
我的项目是vu3的
template的是这样的
<el-form-item label="中文详情" prop="content"> <div ><quill-editor ref="QuillEditor" v-model:content="form.content" class="w-full" v-bind:options="options" contentType="html"></quill-editor></div> </el-form-item>
你要在script导入的是
import { Quill} from '@vueup/vue-quill'
import { ImageExtend,QuillWatch} from 'quill-image-extend-module'
Quill.register('modules/ImageExtend', ImageExtend)
script是这样的
const toolbarOptions: any = [ // 工具栏想要显示什么,就要在这里加上什么 ['bold', 'italic', 'underline', 'strike'], // 粗体、斜体、下划线、删除线 ['blockquote', 'code-block'], // 引用、代码 [{header: 1}, {header: 2}], // 一级标题、二级标题 [{list: 'ordered'}, {list: 'bullet'}], // 有序列表、无序列表 [{script: 'sub'}, {script: 'super'}], // 下标、上标 [{indent: '-1'}, {indent: '+1'}], // 左缩进、右缩进 [{direction: 'rtl'}], // 文字方向 [{size: ['small', false, 'large', 'huge']}], // 字体大小 [{header: [1, 2, 3, 4, 5, 6, false]}], // 标题大小 [{color: []}, {background: []}], // 字体颜色、背景颜色 [{font: []}], // 字体种类 [{align: []}], // 对齐方式 ['clean'], // 清除格式 ['link', 'image', 'video'] ]; const options = { // 配置 theme: 'snow', modules: { ImageExtend: { loading: true, name: 'img', action: 'api/file/goods', response: (res:any) => { // console.log(res.data.path,'ssssssssssssssssssssupdata',)
//成功后的返回值res,里面的地址根据你自己的情况
return `${baseURLImages.value}${res.data.path}` }, headers: (xhr,formData) => { // return {Authorization: `Bearer ${localStorage.getItem('userToken_ERP')}`}; xhr.setRequestHeader( "Authorization", `Bearer ${localStorage.getItem('userToken_ERP')}` ); }, // 可选参数 设置请求头部,根据自己情况 sizeError: () => { console.log('1212121212') } // 图片超过大小的回调 }, toolbar: { container: toolbarOptions, // 显示配置 handlers: { image: function() { QuillWatch.emit(this.quill.id); // console.log('ssssssssssssssssssssss11111111111111s',this,baseURLRoute.value) } } }, } };
多点调试,不懂欢迎留言

例子记录
<template>
<div>
<div class="main-sty-page mb-6">
<el-image-viewer
v-if="showViewer"
@close="
() => {
showViewer = false;
}
"
:url-list="srcList"
:hide-on-click-modal="true"
/>
<el-form :inline="true">
<el-form-item>
<el-button type="primary" :icon="Plus" @click="openAddUser('add')">添加商品</el-button>
</el-form-item>
<el-form-item>
<el-popconfirm title="确定删除吗?" @confirm="delInfoItem">
<template #reference>
<el-button :disabled="delDisabledToggle">删除</el-button>
</template>
</el-popconfirm>
</el-form-item>
<el-form-item>
<el-select v-model="valueQuery" clearable placeholder="分类查询" style="width: 240px" @clear="getDataList1" @change="getDataList2">
<el-option v-for="item in menuArr" :key="item.id" :label="item.cat_name+' ('+item.en_cat_name+')'" :value="item.id" />
</el-select>
</el-form-item>
</el-form>
</div>
<!-- 表单 -->
<global-table
:data="tableData"
:border="true"
:load-false="loadFalse"
height="calc(100vh - 280px)"
@current-func="currentFunc"
@select-delet-func="selectDeletFunc"
:totalPage="page.total"
:pagination-true-false="false"
>
<template #field>
<el-table-column prop="id" label="id" width="50" show-overflow-tooltip />
<el-table-column prop="name" label="商品中文名称" show-overflow-tooltip />
<el-table-column prop="en_name" label="商品英文名称" show-overflow-tooltip />
<el-table-column label="类别" show-overflow-tooltip>
<template #default="scope">
中:{{ scope.row.goods_category.cat_name }}
<br>
英:{{ scope.row.goods_category.en_cat_name }}
</template>
</el-table-column>
<el-table-column prop="images" label="图片">
<template #default="scope">
<el-image
@click="viewBigPicture(`${baseURL}${JSON.parse(scope.row.images)[0]}`)"
style="width: 100%; height: 80px"
:src="`${baseURL}${JSON.parse(scope.row.images)[0]}`"
fit="contain"
/>
</template>
</el-table-column>
<el-table-column prop="edit" label="操作" width="100">
<template #default="scope">
<el-button link type="primary" size="small" @click="updateUser(scope.row.id, 'update')">编辑</el-button>
</template>
</el-table-column>
</template>
</global-table>
<el-pagination
class="mt-8"
v-model:current-page="page.currentPage"
v-model:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChangePage"
/>
<!-- 添加用户弹窗 -->
<el-dialog v-model="centerDialogVisible" :title="addEditSub == 'add' ? '添加商品' : '编辑商品'" width="80%" align-center destroy-on-close="true" @close="closeDialog">
<el-scrollbar height="calc(100vh - 200px)" v-if="centerDialogVisible">
<el-form :model="form" :rules="rules" label-width="120px" ref="ruleFormRef" class="pr-10">
<el-form-item label="中文名称" prop="name">
<el-input v-model="form.name" placeholder="" />
</el-form-item>
<el-form-item label="英文名称" prop="en_name">
<el-input v-model="form.en_name" placeholder="" />
</el-form-item>
<el-form-item label="分类" prop="cid">
<!-- <el-select v-model="form.cid" placeholder="" style="width: 100%">
<el-option v-for="item in menuArr" :key="item.id" :label="`${item.strNull=='_'?'2__':item.strNull=='__'?'3__':'1__'}${item.cat_name} (${item.en_cat_name})`" :value="item.id" />
</el-select> -->
<treeselect v-model="form.cid" :options="selectMenuArr" />
</el-form-item>
<el-form-item label="图片" prop="images">
<el-upload
v-model:file-list="fileListImages"
ref="uploadRef"
:action="baseURLRoute"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-success="handleOnSuccess"
:before-upload="handleBeforeUpload"
:headers="headers"
>
<el-icon>
<Plus />
</el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_new" :false-label="0" :true-label="1">是否新品</el-checkbox>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_hot" :false-label="0" :true-label="1">是否hot</el-checkbox>
</el-form-item>
<el-form-item label="中文seo关键词" prop="seo_keyword">
<el-input v-model="form.seo_keyword" placeholder="" />
</el-form-item>
<el-form-item label="英文seo关键词" prop="en_seo_keyword">
<el-input v-model="form.en_seo_keyword" placeholder="" />
</el-form-item>
<el-form-item label="中文seo描述" prop="seo_describe">
<el-input v-model="form.seo_describe" placeholder="" />
</el-form-item>
<el-form-item label="英文seo描述" prop="en_seo_describe">
<el-input v-model="form.en_seo_describe" placeholder="" />
</el-form-item>
<el-form-item label="购买链接" prop="buy_url">
<el-input v-model="form.buy_url" placeholder="" />
</el-form-item>
<template v-for="(item,index) in attrDataArr" :key="index">
<el-form-item label="规格参数">
<el-select v-model="item.gaid" placeholder="" style="width:30%">
<el-option v-for="item in SpecArr" :key="item.id" :label="`${item.strNull}${item.cat_name} (${item.en_cat_name})`" :value="item.id" :disabled="item.parent_id==0"/>
</el-select>
<el-input v-model="item.content" style="width: 30%" placeholder="中文内容" />
<el-input v-model="item.en_content" style="width: 30%" placeholder="英文内容" />
<el-popconfirm title="确认删除?"
@confirm="confirmEvent(index)"
@cancel="cancelEvent">
<template #reference>
<el-button>删除</el-button>
</template>
</el-popconfirm>
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" @click="addAttrDataArr">新增规格</el-button>
</el-form-item>
<el-form-item label="中英文显示类型">
<el-select v-model="form.show_type" placeholder="" style="width:100%">
<el-option label="中英文都显示" :value="0"/>
<el-option label="只在中文显示" :value="1"/>
<el-option label="只在英文显示" :value="2"/>
</el-select>
</el-form-item>
<el-form-item label="中文介绍" prop="introduction">
<el-input v-model="form.introduction" placeholder="" />
</el-form-item>
<el-form-item label="英文介绍" prop="en_introduction">
<el-input v-model="form.en_introduction" placeholder="" />
</el-form-item>
<el-form-item label="中文详情" prop="content">
<div ><quill-editor ref="QuillEditor" v-model:content="form.content" class="w-full" v-bind:options="options" contentType="html"></quill-editor></div>
</el-form-item>
<el-form-item label="英文详情" prop="en_content">
<div ><quill-editor ref="QuillEditor" v-model:content="form.en_content" class="w-full" v-bind:options="options" contentType="html"></quill-editor></div>
</el-form-item>
</el-form>
<div class="mt-8 text-right pr-10" v-loading="loadingTF">
<el-button @click="resetForm()">取消</el-button>
<el-button type="primary" @click="submitForm(ruleFormRef)">确定</el-button>
</div>
</el-scrollbar>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {Plus} from '@element-plus/icons-vue';
import {reactive, ref, onMounted} from 'vue';
import type {FormInstance, FormRules, UploadProps, UploadUserFile} from 'element-plus';
// @ts-ignore
import {ElMessage} from 'element-plus';
import GlobalTable from '@components/GlobalTable/index.vue';
import goodsAPI from '@/api/website/goods';
import goodscategoryAPI from '@/api/website/goodsCategory';
import goodsAttrCategoryAPI from '@/api/website/goodsAttrCategory';
import lodash from 'lodash';
// base64转图片
import { Quill} from '@vueup/vue-quill'
import { ImageExtend,QuillWatch} from 'quill-image-extend-module'
Quill.register('modules/ImageExtend', ImageExtend)
// // import the component
import Treeselect from 'vue3-treeselect'
// import the styles
import 'vue3-treeselect/dist/vue3-treeselect.css'
// 加载
let loadingTF=ref(false)
// 预览图片
let srcList: string[] = [];
// 显示图片
let showViewer = ref(false);
const viewBigPicture = (data: any) => {
srcList = [];
srcList.push(data);
showViewer.value = true;
};
const dialogImageUrl = ref('');
const dialogVisible = ref(false);
const headers = computed(() => {
return {Authorization: `Bearer ${localStorage.getItem('userToken_ERP')}`};
});
const fileListImages = ref<UploadUserFile[]>([]);
const baseURLRoute = ref('');
const baseURLImages = ref('');
baseURLImages.value = import.meta.env.VITE_HTTPURLImages;
baseURLRoute.value = import.meta.env.VITE_HTTPURL_ROUTE + 'file/goods';
// 提交的定义内容
const form = reactive<RuleForm>({
name: '',
en_name: '',
content: '',
en_content: '',
images: '',
introduction: '',
en_introduction: '',
cid: '',
is_new: 0,
is_hot: 0,
seo_keyword: '',
en_seo_keyword: '',
seo_describe: '',
en_seo_describe: '',
buy_url: '',
attr_data:'',
show_type:0
});
const ruleFormRef = ref<FormInstance>();
interface RuleForm {
name: string;
en_name: string;
images: string;
content: string;
en_content: string;
introduction: string;
en_introduction: string;
cid: string;
is_new: number;
is_hot: number;
seo_keyword: string;
en_seo_keyword: string;
seo_describe: string;
en_seo_describe: string;
buy_url: string;
attr_data:string;
show_type:number
}
const rules = reactive<FormRules<RuleForm>>({
name: [{required: true, message: '必填', trigger: 'blur'}],
en_name: [{required: true, message: '必填', trigger: 'blur'}],
cid: [{required: true, message: '必填', trigger: 'blur'}],
images: [
{
required: true,
message: '必填',
trigger: 'blur'
}
]
});
const toolbarOptions: any = [
// 工具栏想要显示什么,就要在这里加上什么
['bold', 'italic', 'underline', 'strike'], // 粗体、斜体、下划线、删除线
['blockquote', 'code-block'], // 引用、代码
[{header: 1}, {header: 2}], // 一级标题、二级标题
[{list: 'ordered'}, {list: 'bullet'}], // 有序列表、无序列表
[{script: 'sub'}, {script: 'super'}], // 下标、上标
[{indent: '-1'}, {indent: '+1'}], // 左缩进、右缩进
[{direction: 'rtl'}], // 文字方向
[{size: ['small', false, 'large', 'huge']}], // 字体大小
[{header: [1, 2, 3, 4, 5, 6, false]}], // 标题大小
[{color: []}, {background: []}], // 字体颜色、背景颜色
[{font: []}], // 字体种类
[{align: []}], // 对齐方式
['clean'], // 清除格式
['link', 'image', 'video']
];
const options = {
// 配置
theme: 'snow',
modules: {
ImageExtend: {
loading: true,
name: 'img',
action: 'api/file/goods',
response: (res:any) => {
// console.log(res.data.path,'ssssssssssssssssssssupdata',)
return `${baseURLImages.value}${res.data.path}`
},
headers: (xhr,formData) => {
// return {Authorization: `Bearer ${localStorage.getItem('userToken_ERP')}`};
xhr.setRequestHeader(
"Authorization",
`Bearer ${localStorage.getItem('userToken_ERP')}`
);
}, // 可选参数 设置请求头部
sizeError: () => {
console.log('1212121212')
} // 图片超过大小的回调
},
toolbar: {
container: toolbarOptions, // 显示配置
handlers: {
image: function() {
QuillWatch.emit(this.quill.id);
// console.log('ssssssssssssssssssssss11111111111111s',this,baseURLRoute.value)
}
}
},
}
};
// 分页
const page = reactive({
pageSize: 10,
currentPage: 1,
total: 0
});
// { perPage: page.pageSize, currentPage: page.currentPage}
const handleSizeChange = (val: number) => {
page.pageSize = val;
getDataList1();
};
const handleCurrentChangePage = (val: number) => {
page.currentPage = val;
getDataList1();
};
// 获取API展示数据 start
let tableData = ref([]);
const getDataList = function () {
loadFalse.value = true;
goodsAPI.postGoodsList({}).then((res) => {
if (res.data.code === 200) {
tableData.value = res.data.data.data;
page.total = res.data.total;
console.log(res.data, tableData.value, '用户列表', loadFalse.value, (page.total = res.data.data.total));
loadFalse.value = false;
}
});
};
const getDataList1 = function () {
loadFalse.value = true;
goodsAPI.postGoodsList({perPage: page.pageSize, currentPage: page.currentPage}).then((res) => {
if (res.data.code === 200) {
tableData.value = res.data.data.data;
page.total = res.data.total;
console.log(res.data, tableData.value, '用户列表', loadFalse.value, (page.total = res.data.data.total));
loadFalse.value = false;
}
});
};
const getDataList2 = function () {
loadFalse.value = true;
goodsAPI.postGoodsList({perPage: page.pageSize, currentPage: page.currentPage, sqlQuery: [{FieldName: 'cid', condition: '=', values: valueQuery.value}]}).then((res) => {
if (res.data.code === 200) {
tableData.value = res.data.data.data;
page.total = res.data.total;
console.log(res.data, tableData.value, '用户列表', loadFalse.value, (page.total = res.data.data.total));
loadFalse.value = false;
}
});
};
// 获取API展示数据 end
// 定义
const loadFalse = ref(false);
let delDisabledToggle = ref(true);
let delArrayId: any = reactive([]);
let centerDialogVisible = ref(false);
// 子组件传来的方法 start
const currentFunc = function (data: number | string) {
console.log(data, '父组件当前位置');
};
const selectDeletFunc = function (data: any[]) {
delArrayId.value = data;
console.log(delArrayId.value, '删除id');
if (data.length > 0) {
delDisabledToggle.value = false;
} else {
delDisabledToggle.value = true;
}
};
// 子组件传来的方法 end
// 删除用户
const delInfoItem = function () {
let arrID = delArrayId.value.map((item) => item.id);
console.log(arrID);
goodsAPI.delGoods(arrID.join(',')).then((res) => {
ElMessage({
message: res.data.message,
type: 'success'
});
getDataList();
});
};
// 打开添加用户
let addEditSub = ref('');
const openAddUser = function (item: string) {
centerDialogVisible.value = true;
form.name = '';
form.en_name = '';
form.cid = '';
form.content = '';
form.en_content = '';
form.is_new = 0;
form.is_hot = 0;
form.seo_keyword = '';
form.en_seo_keyword = '';
form.seo_describe = '';
form.en_seo_describe = '';
form.images = '';
form.introduction = '';
form.en_introduction = '';
form.buy_url = '';
form.show_type=0;
form.attr_data='';
fileListImages.value = [];
uploadImgs.value = [];
attrDataArr.value=[{gaid:'',content:'',en_content:''}]
addEditSub.value = item;
};
// 更改用户
let updateID = ref(0);
const updateUser = function (id: number, item: string) {
addEditSub.value = item;
updateID.value = id;
fileListImages.value = [];
goodsAPI.getGoods(id).then((res) => {
if (res.data.code === 200) {
form.name = res.data.data.name;
form.en_name = res.data.data.en_name;
form.cid = res.data.data.cid;
form.is_new = res.data.data.is_new;
form.is_hot = res.data.data.is_hot;
form.seo_keyword = res.data.data.seo_keyword;
form.en_seo_keyword = res.data.data.en_seo_keyword;
form.seo_describe = res.data.data.seo_describe;
form.en_seo_describe = res.data.data.en_seo_describe;
form.content = res.data.data.content;
form.en_content = res.data.data.en_content;
form.images = res.data.data.images;
form.introduction = res.data.data.introduction;
form.en_introduction = res.data.data.en_introduction;
form.buy_url = res.data.data.buy_url;
form.show_type = res.data.data.show_type;
// form.attr_data=JSON.parse(res.data.data.attr_data);
attrDataArr.value=res.data.data.attr_data
if (res.data.data.images && JSON.parse(res.data.data.images).length > 0) {
fileListImages.value = [];
JSON.parse(res.data.data.images).forEach((item) => {
fileListImages.value.push({
name: `${item}`,
url: `${baseURLImages.value}${item}`,
originals:1
});
});
}
centerDialogVisible.value = true;
}
});
};
const closeDialog = () => {
// addForm.name = ''
// addForm.code = ''
// addForm.remark = ''
};
// 取消添加
const resetForm = () => {
centerDialogVisible.value = false;
};
// 添加用户,更改用户
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
// let imgsArr1 = uploadImgs.value.map((item:any) => item);
let imgsArr=fileListImages.value.map((item)=>{
if(item.originals && item.originals===1){
return item.name
}else if(item.response){
return item.response.data.path
}
})
// console.log(imgsArr,'imgsArr1imgsArr1imgsArr1imgsArr1')
form.images = JSON.stringify(imgsArr);
form.attr_data=JSON.stringify(attrDataArr.value);
fileListImages.value = [];
uploadImgs.value=[]
await formEl.validate((valid, fields) => {
if (valid) {
if (addEditSub.value == 'add') {
goodsAPI.addGoods(form).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: res.data.message,
type: 'success'
});
centerDialogVisible.value = false;
fileListImages.value = [];
getDataList();
}
});
} else {
goodsAPI.updateGoods(updateID.value, form).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: res.data.message,
type: 'success'
});
centerDialogVisible.value = false;
getDataList();
}
});
}
} else {
console.log('error submit!', fields);
}
});
};
let uploadImgs = ref<any>([]);
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile: any) => {
dialogImageUrl.value = uploadFile.url!;
dialogVisible.value = true;
};
const handleRemove: UploadProps['onRemove'] = (uploadFile: any, uploadFiles: any) => {
uploadImgs.value = uploadFiles.map((item: any) => item.name);
// form.images = uploadImgs.value;
console.log(uploadFiles, 'onRemove111', uploadImgs.value,form.images,fileListImages.value);
};
const handleOnSuccess: UploadProps['onSuccess'] = (uploadFile: any, uploadFiles: any) => {
// 每一次上传图片都是返回一张图片的路径,用户可能会删改,那么我们也要收集起来
if (uploadFiles.response.code === 200) {
uploadImgs.value.push(uploadFiles.response.data.path);
// form.images = uploadImgs.value;
loadingTF.value=false
console.log(uploadFile, uploadFiles, 'onSuccess', uploadFiles.response.data.path, uploadImgs.value,form.images,fileListImages.value );
}
};
// const handleOnChangeImage:UploadProps['onChange']=()=>{
// console.log('添加文件都会触发')
// }
const handleBeforeUpload: UploadProps['beforeUpload'] = () => {
// 上传之前先把已经存在的数据库的存起来
loadingTF.value=true
console.log('添加文件之前调用');
};
//分类菜单
let menuArr = ref<any>([]);
let selectMenuArr= ref<any>([]);
const getCategoryInfo = () => {
goodscategoryAPI.getGoodsCategoryList().then((res) => {
if (res.data.code == 200) {
menuArr.value = flattenTree(res.data.data, [], '');
selectMenuArr.value=flattenTreeSelect(res.data.data, [])
console.log( menuArr.value, ' menuArr.value',selectMenuArr.value);
}
});
};
// 规格菜单
let SpecArr=ref<any>([])
let attrDataArr=ref([{gaid:'',content:'',en_content:''}])
const specMenu=()=>{
goodsAttrCategoryAPI.getGoodsAttrCategoryList().then((res) => {
if (res.data.code == 200) {
// console.log(res.data.data, 'getCategoryInfo');
SpecArr.value= flattenTree(res.data.data, [], '');
}
});
}
const confirmEvent = (id: number | string) => {
attrDataArr.value.splice(id,1);
console.log('confirm!')
}
const cancelEvent = () => {
console.log('cancel!')
}
// 添加
const addAttrDataArr=()=>{
attrDataArr.value.push({gaid:'',content:'',en_content:''})
}
const flattenTree = function (tree: any, result: any, str: string) {
tree.forEach((node: any) => {
result.push({...node, strNull: str});
if (node.child && node.child.length > 0) {
flattenTree(node.child, result, str + '_');
}
});
return result;
};
// 分类 查询
// 递归原本结构
const flattenTreeSelect = function (tree, result = []) {
tree.forEach((node) => {
const nodeCopy = { ...node,label:`${node.cat_name}(${node.en_cat_name})`,children:node.child };
result.push(nodeCopy);
if (node.child && node.child.length > 0) {
nodeCopy.children = []; // 将 child 属性改为 children
flattenTreeSelect(node.child, nodeCopy.children);
}
// else{
// delete node.child
// }
});
return result;
};
const valueQuery = ref('');
// const classifyQuery = () => {
// getDataList2();
// };
const baseURL = ref('');
onMounted(() => {
getDataList();
baseURL.value = import.meta.env.VITE_HTTPURLImages;
getCategoryInfo();
specMenu();
});
</script>
<style scoped lang="scss"></style>
<!--
quill-editor
import { Quill} from '@vueup/vue-quill'
import { ImageExtend,QuillWatch} from 'quill-image-extend-module'
Quill.register('modules/ImageExtend', ImageExtend)
modules: {
ImageExtend: {
loading: true,
name: 'img',
action: 'api/file/goods',
response: (res:any) => {
//成功返回的地址,这个看你自己接口的情况
return `${baseURLImages.value}${res.data.path}`
},
headers: (xhr,formData) => {
xhr.setRequestHeader(
"Authorization",
`Bearer ${localStorage.getItem('userToken_ERP')}`
);
}, // 可选参数 设置请求头部,根据自己的情况
sizeError: () => {
} // 图片超过大小的回调
},
toolbar: {
container: toolbarOptions, // 显示配置
handlers: {
image: function() {
QuillWatch.emit(this.quill.id);
}
}
},
}
-->

浙公网安备 33010602011771号