学习vue——图片、富文本的新增与修改回显
方法一
1 <el-upload 2 ref="RefUpload" 3 class="avatar-uploader" 4 :auto-upload="false" 5 :show-file-list="false" 6 :on-change="handleChange" 7 > 8 <img v-if="imageUrl" :src="imageUrl" class="avatar" /> 9 <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon> 10 </el-upload> 11 12 <el-button type="primary" style="margin-top: 20px;" :icon="Plus" 13 @click="RefUpload.$el.querySelector('input').click()" <--!--点击按钮调出文件选择框--> 14 >选择图片</el-button>
<el-button type="primary" style="margin-top: 20px;" :icon="Upload" @click='uploadImage'>上传头像</el-button>
1 const imageUrl = ref() 4 const RefUpload = ref() // 用于按钮调出文件选择框 5 const submitImage = ref() 6 // 预览图片 7 const handleChange = (file) => { 8 imageUrl.value = URL.createObjectURL(file.raw) 9 submitImage.value = file.raw 10 } 11 // 上传图片到后端 12 const uploadImage = async () => { 13 const formData = new FormData() 14 formData.append('cover_img', submitImage.value) 15 const { data } = await artAddChannelService(formData) 16 console.log(data) 17 }
django
1 from django.conf import settings 2 import os 3 import base64 4 5 file = request.FILES.get("image") 6 path = os.path.join(settings.STATICFILES_DIRS[0], 'xx3.jpeg') 7 if file: 8 with open(path, 'wb') as f: 9 f.write(file.open())
方法二(byte)
1 // 方法二
const imageUrl = ref()
2 const handleChange = (uploadFile) => { 3 // 基于 FileReader 读取图片做预览 4 const reader = new FileReader() 5 reader.readAsDataURL(uploadFile.raw) 6 reader.onload = () => { 7 imageUrl.value = reader.result 8 } 9 } 10 const uploadImage = async () => { 11 console.log(imageUrl.value) 12 const { data } = await artAddChannelService(imageUrl.value) 13 console.log(data) 14 }
django
1 path = os.path.join(settings.STATICFILES_DIRS[0], 'xx3.jpeg') 2 image_base64 = request.body.decode() 3 if image_base64: 4 # 去掉 base64 图片数据中的前缀(如果有) 5 image_base64 = image_base64.split(',')[1] 6 print(image_base64) 7 # 解码 base64 数据 8 file = base64.b64decode(image_base64) 9 if file: 10 with open(path, 'wb') as f: 11 f.write(file)
css
1 </template> 2 <style lang="scss" scoped> 3 .avatar-uploader { 4 :deep() { 5 .avatar { 6 width: 278px; 7 height: 278px; 8 display: block; 9 } 10 .el-upload { 11 border: 1px dashed var(--el-border-color); 12 border-radius: 6px; 13 cursor: pointer; 14 position: relative; 15 overflow: hidden; 16 transition: var(--el-transition-duration-fast); 17 } 18 .el-upload:hover { 19 border-color: var(--el-color-primary); 20 } 21 .el-icon.avatar-uploader-icon { 22 font-size: 28px; 23 color: #8c939d; 24 width: 278px; 25 height: 278px; 26 text-align: center; 27 } 28 } 29 } 30 </style>
提要
富文本使用链接:https://vueup.github.io/vue-quill/
父组件
1 <tempalte> 2 <!-- 抽屉 --> 3 <drawer-page ref="open" @tijiao = EmitData></drawer-page> 4 </template>
1 <script lang="ts" setup> 2 const open = ref() 3 4 const EmitData = (data) => { 5 // .get 是FromDate 的获取方式 6 console.log("cover_img:",data.get("cover_img")) 7 // 子组件传来的数据,父组件负责新增、修改 8 artAddChannelService(data) 9 } 10 </script>
// 新增 const pubulic = () => { open.value.open() } // 修改 const Edit = (row) => { console.log(row) // 调用子组件的方法open open.value.open(row) }
子组件
1 <template> 2 <!-- 图片 --> 3 <el-form-item label="cover_img" prop="cover_img"> 4 <el-upload 5 class="avatar-uploader" 6 :show-file-list="false" 7 :auto-upload="false" 8 :on-change="upImage" 9 > 10 <img v-if="imageUrl" :src="imageUrl" class="avatar" /> 11 <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon> 12 </el-upload> 13 </el-form-item> 14 <!-- 富文本 --> 15 <el-form-item label="content"> 16 <div class="editor"> 17 <quill-editor ref="qedit" theme="snow" 18 v-model:content="ruleForm.content" content-type="html"> 19 </quill-editor> 20 </div> 21 </el-form-item> 22 </template>
1 <script lang="ts" setup> 2 import { ref } from 'vue' 3 import { ElMessageBox } from 'element-plus' 4 import SearchSelect from './SearchSelect.vue'; 5 import { Plus } from '@element-plus/icons-vue' 6 import { QuillEditor } from '@vueup/vue-quill' 7 import '@vueup/vue-quill/dist/vue-quill.snow.css'; 8 9 10 const defaultForm = {date:'',name:'',address:'',cover_img:'',content:''} 11 const ruleForm = ref({ ...defaultForm }) 12 // 抽屉 13 const drawer = ref(false) 14 const cancelClick = () => { 15 drawer.value = false 16 } 17 18 // 富文本,作用为了清空富文本 19 const qedit = ref() 20 // 本地预览图片 21 const imageUrl = ref('') 22 const upImage = (image) => { 23 console.log(image.raw) 24 // 本地预览 25 imageUrl.value = URL.createObjectURL(image.raw) 26 // 添加到要提交的数据里 27 ruleForm.value.cover_img = image.raw 28 } 29 30 31 // 父组件打开抽屉 32 const open = (row) => { 33 drawer.value = true 34 console.log(row) 35 if(row){ 36 console.log("编辑") 37 // 回显数据 38 ruleForm.value = {...row} 39 // 回显图片 40 imageUrl.value = ruleForm.value.cover_img 41 42 }else{ 43 console.log("新建") 44 // 置空 45 ruleForm.value = { ...defaultForm } 46 imageUrl.value = '' 47 qedit.value.setHTML('') 48 } 49 } 50 defineExpose({ 51 open 52 }) 53 54 // 子组件触发提交 55 const emit = defineEmits(['tijiao']) 56 const confirmClick = () => { 57 ElMessageBox.confirm(`Are you confirm ?`) 58 .then(() => { 59 drawer.value = false 60 // 把图片做成对象的形式 61 const fd = new FormData() 62 for (const key in ruleForm.value) { 63 console.log(`key:${key},${ruleForm.value[key]}`) 64 fd.append(key, ruleForm.value[key]) 65 } 66 // artAddChannelService(fd) 可以在子组件提交 67 // 交给父组件提交 68 emit('tijiao',fd) 69 70 }) 71 .catch(() => { 72 // catch error 73 }) 74 } 75 76 </script>
上传图片的css
1 <style lang="scss" scoped> 2 .avatar-uploader { 3 :deep() { 4 .avatar { 5 width: 178px; 6 height: 178px; 7 display: block; 8 } 9 .el-upload { 10 border: 1px dashed var(--el-border-color); 11 border-radius: 6px; 12 cursor: pointer; 13 position: relative; 14 overflow: hidden; 15 transition: var(--el-transition-duration-fast); 16 } 17 .el-upload:hover { 18 border-color: var(--el-color-primary); 19 } 20 .el-icon.avatar-uploader-icon { 21 font-size: 28px; 22 color: #8c939d; 23 width: 178px; 24 height: 178px; 25 text-align: center; 26 } 27 } 28 } 29 30 .editor { 31 width: 100%; 32 :deep(.ql-editor) { 33 min-height: 200px; 34 } 35 } 36 </style>
完整版本
父组件
1 <script lang="ts" setup> 2 import { ref } from 'vue' 3 import DrawerPage from './components/DrawerPage.vue'; 4 import SearchSelect from './components/SearchSelect.vue'; 5 import { formatTime } from '@/utils/formdate'; 6 import { artAddChannelService } from '@/api/article.js' 7 import { useRouter } from 'vue-router' 8 const router = useRouter() 9 const cat = () => { 10 console.log(value.value) 11 } 12 const open = ref() 13 14 const tableData = [ 15 { 16 id: 1, 17 date: '2016-05-03 12:23:00', 18 name: 'Tom', 19 address: 'No. 189, Grove St, Los Angeles', 20 cover_img:"http://127.0.0.1:8000/static/xx.jpeg", 21 content:'<p style="color: red">1233</p>' 22 }, 23 { 24 id: 2, 25 date: '2016-05-02', 26 name: 'Tom', 27 address: 'No. 189, Grove St, Los Angeles', 28 cover_img:"http://127.0.0.1:8000/static/xx.jpeg", 29 content:'<p style="color: red">1233</p>' 30 }, 31 { 32 id: 3, 33 date: '2016-05-04', 34 name: 'Tom', 35 address: 'No. 189, Grove St, Los Angeles', 36 cover_img:"http://127.0.0.1:8000/static/xx.jpeg", 37 content:'<p style="color: red">1233</p>' 38 }, 39 { 40 id: 4, 41 date: '2016-05-01', 42 name: 'Tom', 43 address: 'No. 189, Grove St, Los Angeles', 44 cover_img:"http://127.0.0.1:8000/static/xx.jpeg", 45 content:'<p style="color: red">1233</p>' 46 }, 47 ] 48 // 新增 49 const pubulic = () => { 50 open.value.open() 51 } 52 // 修改 53 const Edit = (row) => { 54 console.log(row) 55 // 调用子组件的方法open 56 open.value.open(row) 57 } 58 59 const EmitData = (data) => { 60 // .get 是FromDate 的获取方式 61 console.log("cover_img:",data.get("cover_img")) 62 // 子组件传来的数据,父组件负责新增、修改 63 artAddChannelService(data) 64 } 65 const value = ref({ 66 page: 1, 67 size: 2, 68 selectId : "" 69 }) 70 </script> 71 <template> 72 <title-page title="文章管理"> 73 <template #extra> 74 <div> 75 <el-button @click="pubulic">发布文章</el-button> 76 </div> 77 </template> 78 79 <el-form inline > 80 <el-form-item label="文章标题" > 81 <search-select v-model="value.selectId"></search-select> 82 </el-form-item> 83 <el-form-item label="发布状态"> 84 <el-select label="发布状态" > 85 <el-option label="已发布" value=1>新闻</el-option> 86 <el-option label="未发布" value=0>新闻</el-option> 87 </el-select> 88 89 </el-form-item> 90 <el-form-item> 91 <el-button style="margin-left: 25px"type="primary">搜索</el-button> 92 <el-button plan>重置</el-button> 93 <el-button plan @click="cat">cat</el-button> 94 </el-form-item> 95 </el-form> 96 97 98 99 100 <el-table :data="tableData" style="width: 100%"> 101 <el-table-column type="index" label="index" width="180" /> 102 <el-table-column prop="date" label="Date" width="180" > 103 <template #default="{ row }"> 104 {{ formatTime(row.date) }} 105 </template> 106 </el-table-column> 107 108 <el-table-column prop="name" label="Name" width="180" > 109 <template #default="{ row }"> 110 <el-link @click="router.push(`/article/detail/?aid=${row.id}`)" type="primary" :underline="false">{{ row.name }}</el-link> 111 <!-- <el-link @click="router.push(`/article/detail/${row.id}`)" type="primary" :underline="false">{{ row.name }}</el-link> --> 112 </template> 113 </el-table-column> 114 <el-table-column prop="address" label="Address" /> 115 116 <el-table-column label="Operations"> 117 <template #default="scope"> 118 <el-button size="small" circle icon="Edit" @click="Edit(scope.row)"></el-button> 119 <el-button 120 size="small" 121 type="danger" 122 circle 123 icon="Delete" 124 125 > 126 127 </el-button> 128 </template> 129 </el-table-column> 130 131 <template #empty> 132 <el-empty description="暂无数据"></el-empty> 133 </template> 134 135 </el-table> 136 137 <el-pagination 138 v-if="tableData.length>0" 139 v-model:current-page=value.page 140 v-model:page-size=value.size 141 :page-sizes="[5, 10, 20, 100]" 142 143 144 :background="true" 145 layout="jumper,total, sizes, prev, pager, next " 146 :total="10" 147 style="justify-content: flex-end;" 148 next-text="下一页" 149 prev-text="上一页" 150 151 /> 152 </title-page> 153 154 <!-- 抽屉 --> 155 <drawer-page ref="open" @tijiao = EmitData></drawer-page> 156 </template> 157 <style scoped> 158 .sear{ 159 margin-bottom: 10px; 160 } 161 </style>
子组件
1 <script lang="ts" setup> 2 import { ref } from 'vue' 3 import { ElMessageBox } from 'element-plus' 4 import SearchSelect from './SearchSelect.vue'; 5 import { Plus } from '@element-plus/icons-vue' 6 import { QuillEditor } from '@vueup/vue-quill' 7 import '@vueup/vue-quill/dist/vue-quill.snow.css'; 8 9 10 const defaultForm = {date:'',name:'',address:'',cover_img:'',content:''} 11 const ruleForm = ref({ ...defaultForm }) 12 // 抽屉 13 const drawer = ref(false) 14 const cancelClick = () => { 15 drawer.value = false 16 } 17 18 // 富文本,作用为了清空富文本 19 const qedit = ref() 20 // 本地预览图片 21 const imageUrl = ref('') 22 const upImage = (image) => { 23 console.log(image.raw) 24 // 本地预览 25 imageUrl.value = URL.createObjectURL(image.raw) 26 // 添加到要提交的数据里 27 ruleForm.value.cover_img = image.raw 28 } 29 30 31 // 父组件打开抽屉 32 const open = (row) => { 33 drawer.value = true 34 console.log(row) 35 if(row){ 36 console.log("编辑") 37 // 回显数据 38 ruleForm.value = {...row} 39 // 回显图片 40 imageUrl.value = ruleForm.value.cover_img 41 42 }else{ 43 console.log("新建") 44 // 置空 45 ruleForm.value = { ...defaultForm } 46 imageUrl.value = '' 47 qedit.value.setHTML('') 48 } 49 } 50 defineExpose({ 51 open 52 }) 53 54 // 子组件触发提交 55 const emit = defineEmits(['tijiao']) 56 const confirmClick = () => { 57 ElMessageBox.confirm(`Are you confirm ?`) 58 .then(() => { 59 drawer.value = false 60 // 把图片做成对象的形式 61 const fd = new FormData() 62 for (const key in ruleForm.value) { 63 console.log(`key:${key},${ruleForm.value[key]}`) 64 fd.append(key, ruleForm.value[key]) 65 } 66 // artAddChannelService(fd) 可以在子组件提交 67 // 交给父组件提交 68 emit('tijiao',fd) 69 70 }) 71 .catch(() => { 72 // catch error 73 }) 74 } 75 // 富文本:https://vueup.github.io/vue-quill/ 76 </script> 77 <template> 78 <div> 79 <el-drawer 80 v-model="drawer" 81 :title="ruleForm.name ? '编辑' : '新增' " 82 direction="rtl" 83 size=40% 84 > 85 <el-form label-width="auto" label-position="right" :model="ruleForm"> 86 <!-- <el-form-item label="name" prop="name"> 87 <search-select v-model="articleId" width="100%" ></search-select> 88 </el-form-item> --> 89 <!-- 图片 --> 90 <el-form-item label="cover_img" prop="cover_img"> 91 <el-upload 92 class="avatar-uploader" 93 :show-file-list="false" 94 :auto-upload="false" 95 :on-change="upImage" 96 > 97 <img v-if="imageUrl" :src="imageUrl" class="avatar" /> 98 <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon> 99 </el-upload> 100 </el-form-item> 101 <!-- 富文本 --> 102 <el-form-item label="content"> 103 <div class="editor"> 104 <quill-editor ref="qedit" theme="snow" v-model:content="ruleForm.content" content-type="html"> 105 </quill-editor> 106 </div> 107 </el-form-item> 108 <el-form-item label="name" prop="name" > 109 <el-input v-model="ruleForm.name" /> 110 </el-form-item> 111 <el-form-item label="date" prop="date" > 112 <el-input v-model="ruleForm.date" /> 113 </el-form-item> 114 <el-form-item label="address" prop="address" > 115 <el-input v-model="ruleForm.address" /> 116 </el-form-item> 117 </el-form> 118 <template #footer> 119 <div style="flex: auto"> 120 <el-button @click="cancelClick">cancel</el-button> 121 <el-button type="primary" @click="confirmClick">confirm</el-button> 122 </div> 123 </template> 124 </el-drawer> 125 </div> 126 </template> 127 <style lang="scss" scoped> 128 .avatar-uploader { 129 :deep() { 130 .avatar { 131 width: 178px; 132 height: 178px; 133 display: block; 134 } 135 .el-upload { 136 border: 1px dashed var(--el-border-color); 137 border-radius: 6px; 138 cursor: pointer; 139 position: relative; 140 overflow: hidden; 141 transition: var(--el-transition-duration-fast); 142 } 143 .el-upload:hover { 144 border-color: var(--el-color-primary); 145 } 146 .el-icon.avatar-uploader-icon { 147 font-size: 28px; 148 color: #8c939d; 149 width: 178px; 150 height: 178px; 151 text-align: center; 152 } 153 } 154 } 155 156 .editor { 157 width: 100%; 158 :deep(.ql-editor) { 159 min-height: 200px; 160 } 161 } 162 </style>
django
1 setting.py 2 STATIC_URL = '/static/' 3 STATICFILES_DIRS = ( 4 os.path.join(BASE_DIR, 'static'), 5 ) 6 7 url.py 8 from book import views 9 from django.conf.urls import url 10 from django.conf import settings 11 from django.conf.urls.static import static 12 urlpatterns = [ 13 # 大事件项目 14 url('/reg', views.reg), 15 url('/login', views.login), 16 url('/cate/list', views.articleList), 17 url('/cate/add', views.addAticle), 18 19 ]+static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)