较多改动,暂存
This commit is contained in:
		
							
								
								
									
										13
									
								
								frontend/src/api/qcloud-cos.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/src/api/qcloud-cos.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取 COS 存储桶上传临时 Credential
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function getTmpCosCredential(params) {
 | 
			
		||||
    return send_request({
 | 
			
		||||
        url: '/shop/good/manage/imageUpload/getTmpCosCredential',
 | 
			
		||||
        method: 'GET',
 | 
			
		||||
        params: params,
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										51
									
								
								frontend/src/api/shop-good.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								frontend/src/api/shop-good.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import send_request from '../utils/send_request';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取商品列表
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function getGoodList(params) {
 | 
			
		||||
    return send_request({
 | 
			
		||||
        url: '/shop/good/manage/getGoodList',
 | 
			
		||||
        method: 'GET',
 | 
			
		||||
        params: params,
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 添加/修改商品信息
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function editGood(params) {
 | 
			
		||||
    return send_request({
 | 
			
		||||
        url: '/shop/good/manage/editGood',
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        useQS: true,
 | 
			
		||||
        params: params,
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 删除商品
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function deleteGood(params) {
 | 
			
		||||
    return send_request({
 | 
			
		||||
        url: '/shop/good/manage/deleteGood',
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        useQS: true,
 | 
			
		||||
        params: params,
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 导出商品列表
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function exportGoodList(params) {
 | 
			
		||||
    return send_request({
 | 
			
		||||
        url: '/shop/good/manage/exportGoodList',
 | 
			
		||||
        method: 'GET',
 | 
			
		||||
        params: params,
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
@@ -1,310 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="calender-container">
 | 
			
		||||
        <div class="calender-toolbox">
 | 
			
		||||
            <div style="width: 100%;">
 | 
			
		||||
                <slot name="toolbox"></slot>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="calender-title">
 | 
			
		||||
            <div v-for="w in week">
 | 
			
		||||
                <p>{{ w }}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="calender-grid" :style="{ gridTemplateRows: `repeat(${rowCount}, ${Math.ceil(100 / rowCount)}fr)` }">
 | 
			
		||||
            <div v-for="item in dayItem" class="calender-grid-item" :class="item.class">
 | 
			
		||||
                <template v-if="item.type == 'day'">
 | 
			
		||||
                    <!-- 控制按钮 -->
 | 
			
		||||
                    <div class="calender-grid-item-ctrl-btn-container">
 | 
			
		||||
                        <div class="calender-grid-item-ctrl-btn">
 | 
			
		||||
                            <el-icon :size="20" @click="uploadFile(item)">
 | 
			
		||||
                                <UploadFilled />
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <!-- 日期编号 -->
 | 
			
		||||
                    <p class="calender-grid-item-day">{{ item.day }}</p>
 | 
			
		||||
                    <!-- 附件 -->
 | 
			
		||||
                    <div v-if="item.event.length > 0" class="calender-grid-item-attachment-container">
 | 
			
		||||
                        <div v-for="e in item.event" class="calender-grid-item-attachment">
 | 
			
		||||
                            <a class="download-link" :href="e.file.downloadLink" :download="e.file.displayName"
 | 
			
		||||
                                :title="e.file.displayName" @contextmenu.prevent.native="contextMenuRef.openMenu($event)"
 | 
			
		||||
                                @mouseover="showPopover($event, e)" @mouseleave="hidePopover()">
 | 
			
		||||
                                {{ e.file.displayName }}
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- 鼠标悬浮弹窗 -->
 | 
			
		||||
        <filePopover ref="filePopoverRef" />
 | 
			
		||||
 | 
			
		||||
        <!-- 鼠标右键菜单 -->
 | 
			
		||||
        <contextMenu ref="contextMenuRef" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, defineProps, onMounted, onBeforeUpdate, nextTick, defineExpose } from 'vue';
 | 
			
		||||
import contextMenu from './context-menu.vue';
 | 
			
		||||
import filePopover from '../components/file-popover.vue';
 | 
			
		||||
 | 
			
		||||
const filePopoverRef = ref()
 | 
			
		||||
const contextMenuRef = ref()
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    'year': {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    'month': {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
    'events': {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        require: false,
 | 
			
		||||
        default: [],
 | 
			
		||||
    },
 | 
			
		||||
    'doUpload': {
 | 
			
		||||
        type: Function,
 | 
			
		||||
        require: true,
 | 
			
		||||
    },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const week = [
 | 
			
		||||
    "星期一",
 | 
			
		||||
    "星期二",
 | 
			
		||||
    "星期三",
 | 
			
		||||
    "星期四",
 | 
			
		||||
    "星期五",
 | 
			
		||||
    "星期六",
 | 
			
		||||
    "星期日",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const dayItem = ref([]);
 | 
			
		||||
 | 
			
		||||
const rowCount = ref(5);
 | 
			
		||||
 | 
			
		||||
let isMounted = false;
 | 
			
		||||
 | 
			
		||||
const filePopoverInfo = ref({
 | 
			
		||||
    visable: false,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 绘制月历
 | 
			
		||||
function getMonthCander(year, month, file) {
 | 
			
		||||
    console.log("getMonthCander", isMounted)
 | 
			
		||||
    console.log("file", file)
 | 
			
		||||
    let firstDay = new Date(year, month - 1, 1);
 | 
			
		||||
    let nextMonthFirstDay = new Date(year, (month + 1) - 1, 1);
 | 
			
		||||
 | 
			
		||||
    let dayOfWeek = firstDay.getDay();
 | 
			
		||||
    let space = (dayOfWeek - 1 + 7) % 7; // 前面填充的空白格
 | 
			
		||||
    // console.log("space", space);
 | 
			
		||||
 | 
			
		||||
    let dayCountInMonth = (nextMonthFirstDay - firstDay) / (1000 * 3600 * 24);
 | 
			
		||||
    // console.log("dayCountInMonth", dayCountInMonth);
 | 
			
		||||
 | 
			
		||||
    // 空白填充格
 | 
			
		||||
    dayItem.value.push(...(new Array(space)).fill({
 | 
			
		||||
        "type": "space",
 | 
			
		||||
        "class": "space-item",
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    // 日期格
 | 
			
		||||
    for (let i = 1; i <= dayCountInMonth; i++) {
 | 
			
		||||
        dayItem.value.push({
 | 
			
		||||
            "type": "day",
 | 
			
		||||
            "day": i,
 | 
			
		||||
            "class": "day-item",
 | 
			
		||||
            "event": file.filter((f) => f.day === i),
 | 
			
		||||
            "date": new Date(year, month - 1, i),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    // console.log(dayItem.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    console.log("calender onMounted.", "props:", props)
 | 
			
		||||
    getMonthCander(props.year, props.month, props.events);
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        console.log("calender onMounted nextTick.");
 | 
			
		||||
        isMounted = true;
 | 
			
		||||
    })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onBeforeUpdate(() => {
 | 
			
		||||
    console.log("calender onBeforeUpdate.", "props:", props)
 | 
			
		||||
    if (isMounted) {
 | 
			
		||||
        dayItem.value = [];
 | 
			
		||||
        getMonthCander(props.year, props.month, props.events);
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// TODO
 | 
			
		||||
function uploadFile(item) {
 | 
			
		||||
    console.log("uploadFile item:", item);
 | 
			
		||||
    if (typeof (props.doUpload) === "function") {
 | 
			
		||||
        props.doUpload(item.date);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 弹出鼠标悬浮窗
 | 
			
		||||
function showPopover($event, fileInfo) {
 | 
			
		||||
    // console.log("showPopover $event:", $event, "fileInfo:", fileInfo, $event.target);
 | 
			
		||||
    filePopoverRef.value.updateInfo({
 | 
			
		||||
        visable: true,
 | 
			
		||||
        left: $event.clientX - $event.offsetX + $event.target.offsetWidth / 2,
 | 
			
		||||
        top: $event.clientY - $event.offsetY + $event.target.offsetHeight + 5,
 | 
			
		||||
        width: 100,
 | 
			
		||||
        height: 100,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hidePopover() {
 | 
			
		||||
    filePopoverRef.value.updateInfo({
 | 
			
		||||
        visable: false,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除文件
 | 
			
		||||
// function removeFile(...args) {
 | 
			
		||||
//     console.log("removeFile args:", args)
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// defineExpose({
 | 
			
		||||
//     removeFile
 | 
			
		||||
// })
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.calender-container {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 1200px) {
 | 
			
		||||
    .calender-container {
 | 
			
		||||
        width: 90%;
 | 
			
		||||
        margin: 0 auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 2400px) {
 | 
			
		||||
    .calender-container {
 | 
			
		||||
        width: 80%;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-toolbox {
 | 
			
		||||
    /* background-color: aqua; */
 | 
			
		||||
    height: 80px;
 | 
			
		||||
    display: grid;
 | 
			
		||||
    place-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-title {
 | 
			
		||||
    background-color: #A0CFFF;
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(7, 1fr);
 | 
			
		||||
    place-items: center;
 | 
			
		||||
    height: 38px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid {
 | 
			
		||||
    background-color: #C6E2FF;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(7, 1fr);
 | 
			
		||||
    gap: 8px;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item {
 | 
			
		||||
    max-height: 160px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-day {
 | 
			
		||||
    padding-top: 8px;
 | 
			
		||||
    padding-left: 10px;
 | 
			
		||||
    /* padding-bottom: 2px; */
 | 
			
		||||
 | 
			
		||||
    height: 30px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-ctrl-btn-container {
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-ctrl-btn {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 7px;
 | 
			
		||||
    top: 5px;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item:hover .calender-grid-item-ctrl-btn {
 | 
			
		||||
    opacity: .45;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item:hover .calender-grid-item-ctrl-btn:hover {
 | 
			
		||||
    opacity: 0.9;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-attachment-container {
 | 
			
		||||
    padding: 0 3px;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    height: calc(100% - 30px);
 | 
			
		||||
    overflow-y: auto;
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
    padding-bottom: 5px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item.day-item {
 | 
			
		||||
    background-color: #ECF5FF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-attachment,
 | 
			
		||||
.download-link {
 | 
			
		||||
    background-color: #A0CFFF;
 | 
			
		||||
    color: black;
 | 
			
		||||
    border-radius: 50px;
 | 
			
		||||
    padding: 0.2px 5px;
 | 
			
		||||
    transition: all 0.12s;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    margin-bottom: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calender-grid-item-attachment:hover,
 | 
			
		||||
.calender-grid-item-attachment:hover>.download-link {
 | 
			
		||||
    background-color: #409EFF;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.download-link {
 | 
			
		||||
    display: block;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 600px) {
 | 
			
		||||
 | 
			
		||||
    .calender-title,
 | 
			
		||||
    .calender-grid-item.space-item {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .calender-grid {
 | 
			
		||||
        grid-template-columns: repeat(1, 1fr);
 | 
			
		||||
        grid-template-rows: repeat(31, 120px) !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
 | 
			
		||||
      <li>菜单一</li>
 | 
			
		||||
      <li>菜单二</li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  components: {},
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      visible: false,
 | 
			
		||||
      top: 0,
 | 
			
		||||
      left: 0,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  expose: ['openMenu'],
 | 
			
		||||
  watch: {
 | 
			
		||||
    //   监听属性对象,newValue为新的值,也就是改变后的值
 | 
			
		||||
    visible(newValue, oldValue) {
 | 
			
		||||
      if (newValue) {
 | 
			
		||||
        //菜单显示的时候
 | 
			
		||||
        // document.body.addEventListener,document.body.removeEventListener它们都接受3个参数
 | 
			
		||||
        // ("事件名" , "事件处理函数" , "布尔值");
 | 
			
		||||
        // 在body上添加事件处理程序
 | 
			
		||||
        document.body.addEventListener("click", this.closeMenu);
 | 
			
		||||
      } else {
 | 
			
		||||
        //菜单隐藏的时候
 | 
			
		||||
        // 移除body上添加的事件处理程序
 | 
			
		||||
        document.body.removeEventListener("click", this.closeMenu);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    //右击
 | 
			
		||||
    openMenu(e) {
 | 
			
		||||
      var x = e.pageX; //这个应该是相对于整个浏览器页面的x坐标,左上角为坐标原点(0,0)
 | 
			
		||||
      var y = e.pageY; //这个应该是相对于整个浏览器页面的y坐标,左上角为坐标原点(0,0)
 | 
			
		||||
      this.top = y + 2;
 | 
			
		||||
      this.left = x + 2;
 | 
			
		||||
      this.visible = true; //显示菜单
 | 
			
		||||
    },
 | 
			
		||||
    //关闭菜单
 | 
			
		||||
    closeMenu() {
 | 
			
		||||
      this.visible = false; //关闭菜单
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
<style scoped>
 | 
			
		||||
.contextmenu {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  z-index: 3000;
 | 
			
		||||
  /* //关键样式设置固定定位 */
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
  padding: 5px 0;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  color: #333;
 | 
			
		||||
  box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextmenu li {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 7px 16px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextmenu li:hover {
 | 
			
		||||
  background: #eee;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										82
									
								
								frontend/src/components/image-upload.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								frontend/src/components/image-upload.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false" accept="image/*"
 | 
			
		||||
        :on-success="handleSuccess" :before-upload="beforeUpload" :http-request="handleHttpRequest">
 | 
			
		||||
        <img v-if="props.imageUrl" :src="props.imageUrl" class="avatar" />
 | 
			
		||||
        <el-icon v-else class="avatar-uploader-icon">
 | 
			
		||||
            <Plus />
 | 
			
		||||
        </el-icon>
 | 
			
		||||
    </el-upload>
 | 
			
		||||
    imageUrl: {{ imageUrl }}
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from 'vue'
 | 
			
		||||
import { ElMessage, UploadProps, UploadRequestOptions } from 'element-plus'
 | 
			
		||||
import { Plus } from '@element-plus/icons-vue'
 | 
			
		||||
import * as cos from '../utils/qcloud-cos-upload';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    // 获取列表 接口函数
 | 
			
		||||
    'imageUrl': {
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true,
 | 
			
		||||
        default: '',
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['change'])
 | 
			
		||||
 | 
			
		||||
const handleHttpRequest = async (data: UploadRequestOptions) => {
 | 
			
		||||
    console.log('httpRequest', data)
 | 
			
		||||
    await cos.upload(data.file)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleSuccess: UploadProps['onSuccess'] = (
 | 
			
		||||
    response,
 | 
			
		||||
    uploadFile
 | 
			
		||||
) => {
 | 
			
		||||
    let url = URL.createObjectURL(uploadFile.raw!)
 | 
			
		||||
    emit('change', url)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
 | 
			
		||||
    console.log("rawFile", rawFile)
 | 
			
		||||
    if (!['image/jpeg', 'image/png'].includes(rawFile.type)) {
 | 
			
		||||
        ElMessage.error('仅支持 JPG、PNG 格式图片,请重新选择')
 | 
			
		||||
        return false
 | 
			
		||||
    } else if (rawFile.size > 2 * 1024 * 1024) {
 | 
			
		||||
        ElMessage.error('图片太大,请选择 2MB 以内的图片')
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
    return true
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.avatar-uploader .avatar {
 | 
			
		||||
    width: 120px;
 | 
			
		||||
    height: 120px;
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.avatar-uploader .el-upload {
 | 
			
		||||
    border: 1px dashed var(--el-border-color);
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    transition: var(--el-transition-duration-fast);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.avatar-uploader .el-upload:hover {
 | 
			
		||||
    border-color: var(--el-color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-icon.avatar-uploader-icon {
 | 
			
		||||
    font-size: 28px;
 | 
			
		||||
    color: #8c939d;
 | 
			
		||||
    width: 120px;
 | 
			
		||||
    height: 120px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -28,8 +28,24 @@
 | 
			
		||||
            <!-- 表格 -->
 | 
			
		||||
            <el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
 | 
			
		||||
                <el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
 | 
			
		||||
                <el-table-column v-for="field in tableFields" :prop="field.prop" :label="field.label"
 | 
			
		||||
                    align="center"></el-table-column>
 | 
			
		||||
                <el-table-column v-for="(field, index) in tableFields" :prop="field.prop" :label="field.label" :key="index"
 | 
			
		||||
                    align="center">
 | 
			
		||||
                    <template #default="scope" v-if="field.type == 'image'">
 | 
			
		||||
                        <el-image style="width: 100%; height: 100%;" :src="scope.row.picUrl" fit="cover" />
 | 
			
		||||
                    </template>
 | 
			
		||||
                    <template #default="scope" v-else-if="field.type == 'longtext'">
 | 
			
		||||
                        <el-tooltip placement="top">
 | 
			
		||||
                            <template #content>
 | 
			
		||||
                                <p v-for="line in scope.row.detail.split(/[\r\n]/g)" style="max-width: 300px;">
 | 
			
		||||
                                    {{ line }}
 | 
			
		||||
                                </p>
 | 
			
		||||
                            </template>
 | 
			
		||||
                            <div class="oneLine">
 | 
			
		||||
                                {{scope.row.detail}}
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                    </template>
 | 
			
		||||
                </el-table-column>
 | 
			
		||||
                <el-table-column label="操作" width="220" align="center">
 | 
			
		||||
                    <template #default="scope">
 | 
			
		||||
                        <el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)"
 | 
			
		||||
@@ -61,11 +77,16 @@
 | 
			
		||||
                    <el-input v-if="(formId > 0 ? field.editType : field.addType) == 'input'"
 | 
			
		||||
                        :placeholder="formId > 0 ? field.editPlaceholder : field.addPlaceholder" class="popup-item"
 | 
			
		||||
                        v-model="form[field.field]"></el-input>
 | 
			
		||||
                    <el-input v-else-if="(formId > 0 ? field.editType : field.addType) == 'textarea'"
 | 
			
		||||
                        :placeholder="formId > 0 ? field.editPlaceholder : field.addPlaceholder" class="popup-item"
 | 
			
		||||
                        v-model="form[field.field]" type="textarea" :rows="4"></el-input>
 | 
			
		||||
                    <el-select v-else-if="(formId > 0 ? field.editType : field.addType) == 'select'" class="popup-item"
 | 
			
		||||
                        v-model="form[field.field]" :clearable="true">
 | 
			
		||||
                        <el-option v-for="optKey in Object.keys(field.options)" :key="optKey" :label="field.options[optKey]"
 | 
			
		||||
                            :value="optKey"></el-option>
 | 
			
		||||
                    </el-select>
 | 
			
		||||
                    <ImageUpload v-else-if="(formId > 0 ? field.editType : field.addType) == 'image'"
 | 
			
		||||
                        :imageUrl="form[field.field]" @change="(value: any) => form[field.field] = value" />
 | 
			
		||||
                    <el-input v-else-if="(formId > 0 ? field.editType : field.addType) == 'plainText'" class="popup-item"
 | 
			
		||||
                        v-model="form[field.field]" :disabled="true"></el-input>
 | 
			
		||||
                    <!-- {{ field }} -->
 | 
			
		||||
@@ -112,6 +133,7 @@ import { FormInstance, FormRules, ElMessage, ElMessageBox } from 'element-plus';
 | 
			
		||||
import { Delete, Edit, Search, Plus, Filter, Download } from '@element-plus/icons-vue';
 | 
			
		||||
import * as xlsx from 'xlsx';
 | 
			
		||||
import Mock from 'mockjs';
 | 
			
		||||
import ImageUpload from './image-upload.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    // 获取列表 接口函数
 | 
			
		||||
@@ -229,13 +251,13 @@ const getData = async () => {
 | 
			
		||||
 | 
			
		||||
        // 表格列
 | 
			
		||||
        tableFields.value = data.columns
 | 
			
		||||
            .filter((field: any) => field.showInTable)
 | 
			
		||||
            .filter((field: any) => field.fieldType != "null")
 | 
			
		||||
            .map((field: any) => {
 | 
			
		||||
                // query 填充默认空字符串
 | 
			
		||||
                if (typeof (query[field.field]) === "undefined") {
 | 
			
		||||
                    query[field.field] = ''
 | 
			
		||||
                }
 | 
			
		||||
                return { prop: field.prop, label: field.label }
 | 
			
		||||
                return { prop: field.prop, label: field.label, type: field.fieldType }
 | 
			
		||||
            });
 | 
			
		||||
        console.log("tableFields", tableFields.value)
 | 
			
		||||
        // 表格数据
 | 
			
		||||
@@ -294,7 +316,7 @@ const getData = async () => {
 | 
			
		||||
 | 
			
		||||
        // 导出 excel 字段映射
 | 
			
		||||
        exportFields = data.columns
 | 
			
		||||
            .filter((field: any) => field.showInTable)
 | 
			
		||||
            .filter((field: any) => field.fieldType != "null")
 | 
			
		||||
            .map((field: any) => {
 | 
			
		||||
                return { field: field.field, label: field.label }
 | 
			
		||||
            });
 | 
			
		||||
@@ -544,6 +566,9 @@ const doMockData = (isEdit: boolean) => {
 | 
			
		||||
            case "DPD":
 | 
			
		||||
                form[mock.field] = Mock.mock(mock.str)
 | 
			
		||||
                break;
 | 
			
		||||
            case "IMG":
 | 
			
		||||
                form[mock.field] = Mock.Random.dataImage(...mock.str.split(','))
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -591,4 +616,10 @@ onMounted(() => {
 | 
			
		||||
.popup-item {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.oneLine {
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								frontend/src/utils/qcloud-cos-upload.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								frontend/src/utils/qcloud-cos-upload.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import COS from 'cos-js-sdk-v5';
 | 
			
		||||
import * as qCloudCosApi from '../api/qcloud-cos';
 | 
			
		||||
 | 
			
		||||
export function upload(file) {
 | 
			
		||||
    console.log("file", file);
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        qCloudCosApi.getTmpCosCredential({
 | 
			
		||||
            ext: 'jpg'
 | 
			
		||||
        }).then((data) => {
 | 
			
		||||
            console.log("data", data)
 | 
			
		||||
            // {
 | 
			
		||||
            //     "tmpSecretId": "",
 | 
			
		||||
            //     "tmpSecretKey": "",
 | 
			
		||||
            //     "sessionToken": "",
 | 
			
		||||
            //     "objectKey": "",
 | 
			
		||||
            //     "startTimestamp": 1680534188320,
 | 
			
		||||
            //     "expiredTimestamp": 1680535988320,
 | 
			
		||||
            //     "bucket": "epp-1302260381",
 | 
			
		||||
            //     "region": "ap-shanghai"
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            let cos = new COS({
 | 
			
		||||
                // getAuthorization 必选参数
 | 
			
		||||
                getAuthorization: function (options, callback) {
 | 
			
		||||
                    // 异步获取临时密钥
 | 
			
		||||
                    callback({
 | 
			
		||||
                        TmpSecretId: data.tmpSecretId,
 | 
			
		||||
                        TmpSecretKey: data.tmpSecretKey,
 | 
			
		||||
                        SecurityToken: data.sessionToken,
 | 
			
		||||
                        // 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
 | 
			
		||||
                        StartTime: Math.floor(data.startTimestamp / 1000), // 时间戳,单位秒,如:1580000000
 | 
			
		||||
                        ExpiredTime: Math.floor(data.expiredTimestamp / 1000), // 时间戳,单位秒,如:1580000000
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            console.log("cos", cos)
 | 
			
		||||
 | 
			
		||||
            cos.putObject({
 | 
			
		||||
                Bucket: data.bucket, /* 填入您自己的存储桶,必须字段 */
 | 
			
		||||
                Region: data.region,  /* 存储桶所在地域,例如ap-beijing,必须字段 */
 | 
			
		||||
                Key: data.objectKey,  /* 存储在桶里的对象键(例如1.jpg,a/b/test.txt),必须字段 */
 | 
			
		||||
                Body: file,
 | 
			
		||||
            }, function (err, data) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    reject(err);
 | 
			
		||||
                } else {
 | 
			
		||||
                    resolve(data);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }).catch((err) => {
 | 
			
		||||
            reject(err)
 | 
			
		||||
        })
 | 
			
		||||
    })
 | 
			
		||||
};
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <manageList :list-func="userApi.getUserList" :add-func="userApi.editUser" :edit-func="userApi.editUser"
 | 
			
		||||
            :delete-func="userApi.deleteUser" :export-func="userApi.exportUserList" edit-permiss="privilege-user-setting" />
 | 
			
		||||
        <manageList :list-func="shopGoodApi.getGoodList" :add-func="shopGoodApi.editGood" :edit-func="shopGoodApi.editGood"
 | 
			
		||||
            :delete-func="shopGoodApi.deleteGood" :export-func="shopGoodApi.exportGoodList" edit-permiss="shop-good-setting" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import manageList from '../components/manage-list.vue';
 | 
			
		||||
import * as userApi from '../api/user';
 | 
			
		||||
import * as shopGoodApi from '../api/shop-good';
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user