Commit 7d59a964 authored by yaya's avatar yaya

feat:添加信息设置以及图片裁剪、去水印以及合成功能

parent 891da704
// https://github.com/michael-ciniawsky/postcss-load-config
/*
module.exports = {
"plugins": {
"postcss-import": {},
......@@ -7,4 +7,4 @@ module.exports = {
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}
}*/
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"http://quanku-system-dev.wanwudezhi.com/"'
BASE_API: '"http://quanku-system-dev.wanwudezhi.com/admin"'
}
......@@ -26,8 +26,8 @@ export function publishGoods(data) {
export function detailGoods(id) {
return request({
url: '/goods/detail',
method: 'get',
url: '/item/findDetail',
method: 'post',
params: { id }
})
}
......
......@@ -22,7 +22,11 @@ import './permission' // permission control
import * as filters from './filters' // global filters
import permission from '@/directive/permission/index.js' // 权限判断指令
import 'vue-picture-cut/lib/vue-picture-cut.css'
import VuePictureCut from 'vue-picture-cut'
Vue.use(VuePictureCut)
import VueCropper from 'vue-cropper'
Vue.use(VueCropper)
Vue.use(Element, {
size: Cookies.get('size') || 'medium', // set element-ui default size
i18n: (key, value) => i18n.t(key, value)
......
......@@ -157,6 +157,18 @@ export const asyncRouterMap = [
icon: 'tab'
},
children: [
{
path: 'infoSet',
component: () => import('@/views/goods/infoSet'),
name: 'category',
meta: {
perms: ['GET /admin/category/list', 'POST /admin/category/create', 'GET /admin/category/read', 'POST /admin/category/update', 'POST /admin/category/delete', 'POST /admin/category/getSubCategory', 'POST /admin/bidrecord/list'],
title: '信息设置',
icon: 'list',
noCache: true
},
hidden: true
},
{
path: 'category',
component: () => import('@/views/mall/category'),
......
<template>
<el-dialog
:visible.sync="showLamaDialog"
title="去水印"
width="1000px">
<div >
<iframe
ref="iframe"
:src="iframeSrc"
width="900px"
height="600px"
frameborder="0"
scrolling="no"/>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'LamaImageDialog',
props: {
showLamaDialog: {
type: Boolean,
default: false
}
},
data() {
return {
iframeSrc: ' http://lama.wanwudezhi.work/'
}
},
mounted() {
window.addEventListener('message', this.handleMessage)
},
// vue实例销毁时销毁一些监听事件
methods: {
handleMessage(event) {
console.log('===从子应用的传递的===', event.data)
const data = event.data
switch (data.cmd) {
case 'base64Str':
// 业务逻辑
console.log('===从子应用的传递的returnHeight===', data.params)
this.$emit('imageBase64', data.params && data.params.imgUrlBase64 || '')
break
}
}
}
}
</script>
<style scoped>
</style>
<template>
<div>
<el-dialog
:visible.sync="selectImageDialogVisible"
:before-close="handleClose"
width="800px"
height="800px"
title="选择图片">
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSelectSuccess">确定</el-button>
</div>
<div class="list">
<div v-for="(item, index) in imageList" :key="item + index" class="select_item" @click="handleCheckImg(index)">
<img :src="item" class="gallery">
<img :src="index === currentPosition ? checkedImg : checkImg" class="check_img">
</div>
</div>
</el-dialog>
<LamaImageDialog
:show-lama-dialog="showLamaImageDialog"
@imageBase64="handleGetImageBase64"/>
</div>
</template>
<script>
import LamaImageDialog from './lamaImageDialog'
export default {
name: 'SelectImageDialog',
components: { LamaImageDialog },
props: {
selectImageDialogVisible: {
type: Boolean,
default: false
},
selectType: { // 1去水印
type: Number,
default: 0
},
imageList: {
type: Array,
default: () => {
return []
}
}
},
data() {
return {
showLamaImageDialog: false,
currentPosition: -1,
checked: false,
checkImg: 'https://cdn.wanwudezhi.com/static/web-static/image/830b6dba5de0ad1cf4840df24d13b176_128x128.png',
checkedImg: 'https://cdn.wanwudezhi.com/static/web-static/image/efc07a5868f35cb0fb66c1b232a3bc92_128x128.png'
}
},
methods: {
handleClose() {
this.$emit('closeDialog')
},
handleCheckImg(index) {
this.currentPosition = index
},
handleSelectSuccess() {
if (this.currentPosition === -1) {
this.$message.error('请选择图片')
return
}
debugger
if (this.selectType === 1) { // 去水印
this.showLamaImageDialog = true
} else {
this.$emit('selectSuccess', this.imageList[this.currentPosition])
this.$emit('closeDialog')
}
},
handleGetImageBase64(imgUrlBase64) {
this.$emit('imageBase64Str', imgUrlBase64)
this.showLamaImageDialog = false
this.$emit('closeDialog')
}
}
}
</script>
<style lang="less" scoped>
.list {
width: 720px;
.select_item {
width: 100px;
height: 100px;
position: relative;
margin-right: 20px;
margin-bottom: 20px;
display: inline-block;
.gallery {
width: 100px;
height: 100px;
}
.check_img {
position: absolute;
top: 10px;
right: 10px;
width: 20px;
height: 20px;
}
}
}
</style>
<template>
<div class="app-container">
<div class="op-container">
<el-button round @click="handleCancel">取消</el-button>
<el-button type="primary" style="margin-right: 20px" round @click="handlePublish">发布</el-button>
<el-button-group>
<el-button type="primary" icon="el-icon-arrow-left">上一个</el-button>
<el-button type="primary">下一个<i class="el-icon-arrow-right el-icon--right" /></el-button>
</el-button-group>
</div>
<el-card class="box-card">
<div class="detail">
<div class="detail_title">{{ `名称:` }}</div>
<div class="detail_desc">{{ `详细介绍:` }}</div>
<div class="detail_image">
<div class="detail_title">{{ `钱币图片:` }}</div>
<div class="list">
<div v-for="(item, index) in goodsParams.images" :key="item + index" class="select_item">
<img :src="item" class="gallery">
</div>
</div>
</div>
</div>
</el-card>
<div class="info">
<div class="info_left">
<el-card>
<div>
<div>第一步:请选择图片,去水印,并保存图片(若无需去水印,可直接进行第二步抠图)</div>
<el-button type="primary" style="margin-top: 20px" @click="handleSelectImage(1)">选择图片,去水印</el-button>
</div>
</el-card>
<el-card style="margin-top: 20px">
<div class="tool">
<div>第二步:对去完水印的图片进行抠图</div>
<div v-if="!src && isShowCutImage" class="select">
<el-button style="width: 95px" @click="handleSelectImage">在线选图</el-button>
<el-button
type="primary"
style="position: relative;overflow: hidden;margin-top: 20px;margin-left: 0px; ">本地上传
<div id="clone_input">
<input
style="position: absolute;top: 0;left: 0;width: 120px;height: 50px;opacity: 0;"
type="file"
accept="image/*"
@change="fileChange">
</div>
</el-button>
</div>
<div v-if="src && isShowCutImage" class="cut">
<VuePictureCut
ref="pictureCut"
:src="src"
:init-angle="cutInfo.initAngle"
:msk-option="cutInfo.mskOption"
:menu-thickness="cutInfo.menuThickness"
:menu-position="cutInfo.menuPosition"
@on-change="cutChange"
>
<VuePictureCutMask
:width="mastInfo.width"
:height="mastInfo.height"
:is-round="mastInfo.isRound"
:resize="mastInfo.resize"
/>
</VuePictureCut>
</div>
<div v-if="isShowCutImage" class="cut-menu">
<div class="shape">
<div>
裁剪形状:
</div>
<div :class="{active: mastInfo.isRound}" class="shape_item" @click="handleChangeShape(1)">
圆形
</div>
<div :class="{active: !mastInfo.isRound}" class="shape_item" @click="handleChangeShape(2)">
正方形
</div>
</div>
<div class="cut-menu">
<vue-picture-cut-menu
:root="pictureCut"
:max-pixel="menuInfo.maxPixel"
:encoder-options="menuInfo.encoderOptions"
:format="menuInfo.format"
theme="gray"
size-auto-name="auto"
size-raw-name="raw"
menu-rotate-name="Rotate"
cancel-name="重置"
confirm-name="裁剪"
@on-change="cutChange"
@on-cancel="cutCancel"
/>
</div>
</div>
</div>
</el-card>
<el-card class="box-card">
<div class="image_result">
<div class="image_result--left">
<div class="result_operate">
<el-button type="primary" @click="compositeImage">合成</el-button>
<div style="text-align: end; margin-right: 60px;font-weight: bold;">原图</div>
</div>
<div class="content">
<p>钱币正面:</p>
<div class="content_img">
<a
:href="base64"
:style="{'background-image': `url(${newSrc})`}"
class="content_img--show"
@click="deleteCutImage(1)">
<i v-if="newSrc" class="el-icon-delete" />
</a>
</div>
</div>
<div class="content">
<p>钱币反面:</p>
<div class="content_img">
<a
:href="base64Second"
:style="{'background-image': `url(${newSecondSrc})`}"
class="content_img--show"
@click="deleteCutImage(2)">
<i v-if="newSecondSrc" class="el-icon-delete" />
</a>
</div>
</div>
</div>
<div class="image_result--right">
<div class="content">
<div class="content_divider" />
<div class="content_preview">
<div style="text-align: end; margin-right: 20px;font-weight: bold; width: 100%">预览</div>
<div class="content_preview--image">
<a
:href="compositeImageSrc"
:style="{'background-image': `url(${compositeImageSrc})`}"
class="image" />
</div>
</div>
</div>
</div>
</div>
</el-card>
</div>
<el-card class="info_right">
<div>
<el-form ref="goods" :model="goodsParams" label-width="150px">
<el-form-item label="所属分类:">
<el-cascader
:options="categoryList"
v-model="categoryIds"
:style="{ 'width':'300px' }"
expand-trigger="hover"
placeholder="请选择钱币分类"
@change="handleCategoryChange"/>
</el-form-item>
<el-form-item label="直径(mm):" prop="name">
<el-input v-model="goodsParams.name" :style="{ 'width':'300px' }" placeholder="请输入直径(mm)" />
</el-form-item>
<el-form-item label="厚度(mm):" prop="name">
<el-input v-model="goodsParams.name" :style="{ 'width':'300px' }" placeholder="请输入厚度(mm)" />
</el-form-item>
<el-form-item label="质量(g):" prop="name">
<el-input v-model="goodsParams.name" :style="{ 'width':'300px' }" placeholder="请输入质量(g)" />
</el-form-item>
<el-form-item label="是否作为标准货币:" prop="isOnSale">
<el-radio-group v-model="goodsParams.isOnSale">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="goodsParams.traceMsg"
:rows="5"
maxlength="50"
type="textarea"
placeholder="请输入备注(最多50个字)"/>
</el-form-item>
</el-form>
</div>
</el-card>
</div>
<SelectImageDialog
:select-image-dialog-visible="dialogVisible"
:select-type="selectDialogType"
:image-list="goodsParams.images"
@closeDialog="dialogVisible = false"
@selectSuccess="handleSelectSuccess"
@imageBase64Str="handlerGetBase64" />
</div>
</template>
<script>
import { VuePictureCut, VuePictureCutMask, VuePictureCutMenu } from 'vue-picture-cut'
import { getToken } from '@/utils/auth'
import SelectImageDialog from './components/selectImageDialog'
import { detailGoods, listCategories } from '@/api/business/goods'
export default {
name: 'InfoSet',
components: { SelectImageDialog, VuePictureCut, VuePictureCutMask, VuePictureCutMenu },
data() {
return {
pictureCut: null,
blob: '',
base64: '',
blobSecond: '',
base64Second: '',
compositeImageSrc: '',
src: '',
cutInfo: {
initAngle: 0,
maxPixel: 500,
encoderOptions: 0.8
},
mastInfo: {
width: 1,
height: 1,
isRound: true,
resize: true
},
menuInfo: {
format: 'image/jpeg/png',
maxPixel: 500,
encoderOptions: 0.8
},
dialogVisible: false,
selectDialogType: 0,
categoryList: [], // 所属分类
categoryIds: [],
goodsParams: { images: [] },
goodId: '',
goodIdList: [],
goodItemIndex: -1
}
},
computed: {
headers() {
return {
'X-Dts-Admin-Token': getToken()
}
},
isShowCutImage() {
return !this.base64 || !this.base64Second
},
newSrc() {
return this.blob ? URL.createObjectURL(this.blob) : ''
},
newSecondSrc() {
return this.blobSecond ? URL.createObjectURL(this.blobSecond) : ''
}
},
watch: {
src() {
this.init()
}
},
created() {
this.initContent()
},
mounted() {
// this.pictureCut = this.$refs['pictureCut']
// this.utils = createUtils(this.pictureCut)
},
methods: {
// 初始化内容
initContent() {
if (this.$route.query.id == null) {
return
}
this.goodId = this.$route.query.id
detailGoods(this.goodId).then(response => {
this.goodsParams = response.data.data.goods
this.categoryIds = response.data.data.categoryIds
this.itemImages = []
for (let i = 0; i < this.goodsParams.images.length; i++) {
this.itemImages.push({
url: this.goodsParams.images[i]
})
}
})
listCategories().then(response => {
this.categoryList = response.data.data.categoryList
})
},
// 初始化图片裁剪工具
init() {
if (this.src) {
setTimeout(() => {
this.pictureCut = this.$refs['pictureCut']
console.log('==7777777777==', this.pictureCut)
}, 1000)
}
},
// 选择图片结果
fileChange(e) {
if (e.target.files.length) {
this.src = URL.createObjectURL(e.target.files[0])
}
console.log('src====', this.src)
},
// 取消,回到钱币列表页
handleCancel() {
this.$router.push({ path: '/goods/list' })
},
// 发布
handlePublish() {
},
// 选择形状
handleChangeShape(type) {
if (type === 1) {
this.mastInfo.isRound = true
} else {
this.mastInfo.isRound = false
}
},
// 选择图片
handleSelectImage(type) {
if (!this.isShowCutImage) {
this.$message.error('请先删除已裁剪图片')
return
}
/* if (!this.goodsParams.images || this.goodsParams.images.length === 0) {
this.$message.error('无线上图片选择')
return
}*/
this.dialogVisible = true
this.selectDialogType = type
},
// 裁剪图片结果
cutChange(res) {
this.src = ''
if (!this.blob) {
this.blob = res.blob
this.base64 = res.base64
} else {
this.blobSecond = res.blob
this.base64Second = res.base64
}
// console.log('=====blob', this.blob)
console.log('=====base64', this.base64)
},
// 裁剪工具取消
cutCancel() {
this.src = ''
},
// 删除裁剪后图片
deleteCutImage(type) {
if (type === 1) { // 正面
this.blob = ''
this.base64 = ''
} else {
this.blobSecond = ''
this.base64Second = ''
}
this.compositeImageSrc = ''
},
// 选择线上图片后的图片链接
async handleSelectSuccess(url) {
var base64Str = await this.getBase64Image(url)
this.src = base64Str
console.log('=====&&&&==', base64Str)
},
// 去除水印后拿到的base64
handlerGetBase64(imgUrlBase64) {
if (imgUrlBase64) {
this.src = imgUrlBase64
} else {
this.$emit('获取失败,请重试...')
}
},
/** url 转换成img */
getBase64Image(img) {
return this.downloadImage(img).then(image => {
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const ctx = canvas.getContext('2d')
ctx.drawImage(image, 0, 0, image.width, image.height)
const ext = img.substring(img.lastIndexOf('.') + 1).toLowerCase()
const dataURL = canvas.toDataURL('image/' + ext)
return dataURL
})
},
downloadImage(url) {
return new Promise(resolve => {
const img = new Image()
if (!url) {
resolve(null)
return
}
if (url.indexOf('data:image') !== 0) {
img.crossOrigin = 'Anonymous'
}
img.src = url
img.onload = () => {
resolve(img)
}
img.onerror = (err) => {
console.log(img.src)
console.error(err)
resolve(null)
}
})
},
// 合成图片
compositeImage() {
if (!this.base64) {
this.$message.error('请裁剪钱币正面')
return
}
if (!this.base64Second) {
this.$message.error('请裁剪钱币反面')
return
}
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const imageWidth = 800
const imageHeight = 400
canvas.width = imageWidth
canvas.height = imageHeight
var myImg = new Image()
myImg.src = this.base64
var bgImg = new Image()
bgImg.src = this.base64Second
myImg.onload = function() {
// this.width 和 this.height 获取到的是当前图片的宽和高
// document.getElementById('canvas').width = 150
// document.getElementById('canvas').height = 150
ctx.drawImage(myImg, 0, 0, imageHeight, imageHeight)
bgImg.onload = function() {
// this.width 和 this.height 获取到的是当前图片的宽和高
// document.getElementById('canvas').width = 150
// document.getElementById('canvas').height = 150
ctx.drawImage(bgImg, imageHeight, 0, imageHeight, imageHeight)
}
}
// 转换base64格式的图片
setTimeout(() => {
this.compositeImageSrc = canvas.toDataURL('image/png')
console.log('=====img==', this.compositeImageSrc)
}, 1000)
},
// 分类结果
handleCategoryChange(value) {
this.goodsParams.cid = value[value.length - 1]
}
}
}
</script>
<style lang="less" scoped>
/deep/ .vue-picture-cut_default-menu {
height: 0px;
inset: 0px 0px 0px !important;
}
/deep/ .vue-picture-cut_main {
inset: 0px 0px 0px !important;
}
/deep/ .vue-picture-cut_menu-box {
left: 0px;
right: 0px;
bottom: 0px;
height: 0px !important;
}
/deep/ .vue-picture-cut-menu_slider {
padding: 10px 15px 0 65px !important;
}
.op-container {
text-align: right;
}
.box-card {
margin-top: 20px;
}
.detail {
.detail_title {
font-weight: bold;
}
.detail_desc {
margin-top: 15px;
}
.detail_image {
display: flex;
flex-direction: row;
.detail_title {
width: 100px;
font-weight: normal;
margin-top: 15px;
}
.list {
.select_item {
width: 100px;
height: 100px;
position: relative;
margin-right: 20px;
margin-bottom: 20px;
display: inline-block;
.gallery {
width: 100px;
height: 100px;
object-fit: contain;
}
}
}
}
}
.info {
display: flex;
flex-direction: row;
margin-top: 20px;
.info_left {
width: 60%;
margin-right: 15px;
.tool {
margin-top: 20px;
.select {
width: 100%;
height: 230px;
overflow: hidden;
margin: 20px 0;
background: white;
border: 1px dashed transparent;
background: linear-gradient(white, white) padding-box,
repeating-linear-gradient(-45deg, #ccc 0, #ccc 0.25em, white 0, white 0.75em);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.cut {
width: 100%;
height: 280px;
overflow: hidden;
margin-top: 20px;
}
.cut-menu {
.shape {
display: flex;
flex-direction: row;
height: 80px;
padding-bottom: 20px;
background: #35362e;
justify-content: center;
color: #bbb;
font-size: 14px;
align-items: center;
.shape_item {
border: 1px solid #444;
border-radius: 2px;
width: 60px;
height: 28px;
display: inline-block;
text-align: center;
line-height: 24px;
margin-right: 20px;
&.active {
border: 1px solid #bbb;
}
}
}
}
.tool_content {
margin-left: 20px;
}
}
.image_result {
display: flex;
flex-direction: row;
.result_operate {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 10px;
align-items: center;
}
&--left {
width: 50%;
.content {
display: flex;
flex-direction: row;
.content_img {
width: 150px;
height: 150px;
padding: 10px;
border: 1px solid #d8dce5;
margin-top: 18px;
.content_img--show {
width: 130px;
height: 130px;
text-align: center;
line-height: 130px;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
display: inline-block;
}
}
}
}
&--right {
width: 50%;
.content {
display: flex;
flex-direction: row;
.content_divider {
width: 1px;
height: 360px;
background: #222325;
}
.content_preview {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
.content_preview--image {
width: 300px;
height: 150px;
padding: 10px;
border: 1px solid #d8dce5;
margin: 18px 0 0 20px;
.image {
width: 280px;
height: 130px;
text-align: center;
line-height: 130px;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
display: inline-block;
}
}
}
}
}
}
}
.info_right {
width: 40%;
}
}
</style>
......@@ -61,8 +61,9 @@
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="150" class-name="small-padding fixed-width">
<el-table-column align="center" label="操作" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" style="width: 80px" @click="handleSet(scope.row)">钱币设置</el-button>
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
......@@ -150,6 +151,9 @@ export default {
handleUpdate(row) {
this.$router.push({ path: '/goods/edit', query: { id: row.id }})
},
handleSet(row) {
this.$router.push({ path: '/goods/infoSet', query: { id: row.id }})
},
showDetail(detail) {
this.goodsDetail = detail
this.detailDialogVisible = true
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment