1
0
Code Issues Pull Requests Packages Projects Releases Wiki Activity GitHub Gitee

较多改动,暂存

This commit is contained in:
2023-04-04 01:06:22 +08:00
parent ac885fb06b
commit a68307b9f9
44 changed files with 2467 additions and 649 deletions

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

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

View File

@@ -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>

View File

@@ -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.addEventListenerdocument.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>

View 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>

View File

@@ -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>

View 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.jpga/b/test.txt必须字段 */
Body: file,
}, function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
}).catch((err) => {
reject(err)
})
})
};

View File

@@ -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>