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); } } }, } -->