zhibo/Zhibo/admin/src/views/gift/manage/index.vue

555 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<!-- 统计卡片 -->
<el-row :gutter="20" class="mb20">
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-card">
<i class="el-icon-present stat-icon" style="color: #409EFF"></i>
<div class="stat-content">
<div class="stat-value">{{ statistics.total }}</div>
<div class="stat-label">礼物总数</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-card">
<i class="el-icon-check stat-icon" style="color: #67C23A"></i>
<div class="stat-content">
<div class="stat-value">{{ statistics.enabled }}</div>
<div class="stat-label">启用中</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-card">
<i class="el-icon-close stat-icon" style="color: #F56C6C"></i>
<div class="stat-content">
<div class="stat-value">{{ statistics.disabled }}</div>
<div class="stat-label">已禁用</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-card">
<i class="el-icon-coin stat-icon" style="color: #E6A23C"></i>
<div class="stat-content">
<div class="stat-value">{{ statistics.totalValue }}</div>
<div class="stat-label">总价值(钻石)</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 搜索和操作栏 -->
<el-card class="mb20">
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
<el-form-item label="礼物名称">
<el-input v-model="queryParams.name" placeholder="请输入礼物名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option label="全部" value="" />
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">添加礼物</el-button>
<el-button type="danger" icon="el-icon-delete" :disabled="multiple" @click="handleDelete">批量删除</el-button>
</el-row>
</el-card>
<!-- 礼物列表 -->
<el-card>
<el-table v-loading="loading" :data="giftList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="ID" align="center" prop="id" width="80" />
<el-table-column label="礼物图标" align="center" width="100">
<template slot-scope="scope">
<el-image
:src="scope.row.image"
:preview-src-list="[scope.row.image]"
style="width: 50px; height: 50px"
fit="cover"
>
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</template>
</el-table-column>
<el-table-column label="礼物名称" align="center" prop="name" />
<el-table-column label="价格(钻石)" align="center" prop="diamondPrice" width="120">
<template slot-scope="scope">
<el-tag type="warning">{{ scope.row.diamondPrice }}</el-tag>
</template>
</el-table-column>
<el-table-column label="亲密度" align="center" prop="intimacy" width="100" />
<el-table-column label="等级" align="center" prop="level" width="80">
<template slot-scope="scope">
<el-tag :type="['', 'success', 'info', 'warning', 'danger'][scope.row.level] || 'info'">
{{ scope.row.level }}级
</el-tag>
</template>
</el-table-column>
<el-table-column label="排序" align="center" prop="sort" width="80" />
<el-table-column label="状态" align="center" width="100">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" style="color: #F56C6C">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.page"
:limit.sync="queryParams.limit"
@size-change="getList"
@current-change="getList"
/>
</el-card>
<!-- 添加或修改礼物对话框 -->
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="礼物名称" prop="name">
<el-input v-model="form.name" placeholder="请输入礼物名称" />
</el-form-item>
<el-form-item label="礼物图标" prop="image">
<el-upload
class="avatar-uploader"
action="#"
:show-file-list="false"
:http-request="handleUpload"
:before-upload="beforeUpload"
>
<img v-if="form.image" :src="form.image" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<div class="el-upload__tip">建议上传200x200像素的PNG图片</div>
</el-form-item>
<el-form-item label="价格(钻石)" prop="diamondPrice">
<el-input-number v-model="form.diamondPrice" :min="0.01" :max="999999" :precision="2" />
</el-form-item>
<el-form-item label="亲密度" prop="intimacy">
<el-input-number v-model="form.intimacy" :min="0" :max="999999" />
</el-form-item>
<el-form-item label="礼物等级" prop="level">
<el-select v-model="form.level" placeholder="请选择等级">
<el-option label="1级" :value="1" />
<el-option label="2级" :value="2" />
<el-option label="3级" :value="3" />
<el-option label="4级" :value="4" />
<el-option label="5级" :value="5" />
</el-select>
</el-form-item>
<el-form-item label="是否心动礼物" prop="isHeartbeat">
<el-switch v-model="form.isHeartbeat" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" :min="0" :max="9999" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { giftListApi, giftAddApi, giftUpdateApi, giftDeleteApi, giftStatusApi } from '@/api/gift';
import { fileImageApi } from '@/api/systemSetting';
import DataPagination from '@/components/common/DataPagination';
export default {
name: 'GiftManage',
components: {
pagination: DataPagination
},
data() {
return {
loading: false,
multiple: true,
ids: [],
total: 0,
giftList: [],
title: '',
open: false,
statistics: {
total: 0,
enabled: 0,
disabled: 0,
totalValue: 0
},
queryParams: {
page: 1,
limit: 10,
name: '',
status: ''
},
form: {},
rules: {
name: [
{ required: true, message: '礼物名称不能为空', trigger: 'blur' }
],
image: [
{ required: true, message: '请上传礼物图标', trigger: 'change' }
],
diamondPrice: [
{ required: true, message: '价格不能为空', trigger: 'blur' }
]
}
};
},
created() {
this.getList();
this.getStatistics();
},
methods: {
getList() {
this.loading = true;
giftListApi(this.queryParams).then(response => {
console.log('礼物列表响应:', response);
// axios 拦截器已经返回了 res.data所以 response 就是 CommonPage 对象
// response = { list: [], total: 0, page: 1, limit: 10, totalPage: 1 }
const list = response.list || [];
// 确保 status 字段是数字类型1 或 0
this.giftList = list.map(item => ({
...item,
status: Number(item.status) // 确保是数字类型
}));
this.total = response.total || 0;
console.log('礼物列表数据:', this.giftList);
console.log('总数:', this.total);
this.loading = false;
}).catch(error => {
console.error('获取礼物列表失败:', error);
this.$message.error('获取礼物列表失败');
this.loading = false;
});
},
getStatistics() {
// 计算统计数据
giftListApi({ page: 1, limit: 9999 }).then(response => {
const list = response.list || [];
this.statistics.total = list.length;
this.statistics.enabled = list.filter(item => item.status === 1 || item.status === true).length;
this.statistics.disabled = list.filter(item => item.status === 0 || item.status === false).length;
this.statistics.totalValue = list.reduce((sum, item) => sum + (parseFloat(item.diamondPrice) || 0), 0).toFixed(2);
}).catch(error => {
console.error('获取统计数据失败:', error);
});
},
handleQuery() {
this.queryParams.page = 1;
this.getList();
},
resetQuery() {
this.queryParams = {
page: 1,
limit: 10,
name: '',
status: ''
};
this.getList();
},
handleAdd() {
this.reset();
this.open = true;
this.title = '添加礼物';
},
handleUpdate(row) {
this.reset();
this.form = { ...row };
this.open = true;
this.title = '修改礼物';
},
handleDelete(row) {
const ids = row.id || this.ids;
this.$confirm('是否确认删除选中的礼物?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return giftDeleteApi(ids);
}).then(() => {
this.getList();
this.getStatistics();
this.$message.success('删除成功');
});
},
handleStatusChange(row) {
const text = row.status === 1 ? '启用' : '禁用';
this.$confirm('确认要' + text + '该礼物吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return giftStatusApi(row.id, row.status);
}).then(() => {
this.$message.success(text + '成功');
this.getStatistics();
}).catch(() => {
row.status = row.status === 0 ? 1 : 0;
});
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.multiple = !selection.length;
},
submitForm() {
this.$refs['form'].validate(valid => {
if (valid) {
if (this.form.id) {
giftUpdateApi(this.form).then(() => {
this.$message.success('修改成功');
this.open = false;
this.getList();
this.getStatistics();
});
} else {
giftAddApi(this.form).then(() => {
this.$message.success('新增成功');
this.open = false;
this.getList();
this.getStatistics();
});
}
}
});
},
cancel() {
this.open = false;
this.reset();
},
reset() {
this.form = {
id: null,
name: '',
image: '',
diamondPrice: 1,
intimacy: 1,
level: 1,
isHeartbeat: 0,
sort: 0,
status: 1,
remark: '',
buyType: '钻石',
belong: '平台'
};
this.$nextTick(() => {
if (this.$refs['form']) {
this.$refs['form'].clearValidate();
}
});
},
handleUpload(param) {
const formData = new FormData();
formData.append('multipart', param.file); // 后端参数名是 multipart
// 显示上传中提示
const loading = this.$loading({
lock: true,
text: '上传中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
fileImageApi(formData, { model: 'gift', pid: 0 })
.then(response => {
loading.close();
console.log('上传响应:', response);
// 根据响应格式设置图片地址
if (response) {
// response 可能是 FileResultVo 对象或直接是 URL
this.form.image = response.url || response.fileUrl || response;
this.$message.success('上传成功');
} else {
this.$message.error('上传失败,返回数据格式错误');
}
})
.catch(error => {
loading.close();
console.error('上传失败:', error);
this.$message.error('上传失败: ' + (error.message || '未知错误'));
});
},
beforeUpload(file) {
const isImage = file.type.indexOf('image/') === 0;
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isImage) {
this.$message.error('只能上传图片文件!');
}
if (!isLt2M) {
this.$message.error('上传图片大小不能超过 2MB!');
}
return isImage && isLt2M;
},
getImageUrl(url) {
if (!url) {
console.log('图片URL为空');
return '';
}
console.log('原始图片URL:', url);
console.log('URL类型:', typeof url);
// 转换为字符串(防止是对象)
let urlStr = String(url).trim();
console.log('转换后的URL:', urlStr);
// 修复如果URL被重复拼接提取正确的路径
// 例如http://domain/http://domain/crmebimage/... -> crmebimage/...
if (urlStr.indexOf('http://') > 0 || urlStr.indexOf('https://') > 0) {
console.log('检测到重复拼接的URL尝试修复');
// 找到第二个 http:// 或 https:// 的位置
const secondHttpIndex = urlStr.indexOf('http://', 1);
const secondHttpsIndex = urlStr.indexOf('https://', 1);
if (secondHttpIndex > 0) {
urlStr = urlStr.substring(secondHttpIndex);
console.log('修复后的URL:', urlStr);
} else if (secondHttpsIndex > 0) {
urlStr = urlStr.substring(secondHttpsIndex);
console.log('修复后的URL:', urlStr);
}
}
// 如果已经是完整的 URLhttp:// 或 https://),直接返回
if (urlStr.indexOf('http://') === 0 || urlStr.indexOf('https://') === 0) {
console.log('检测到完整URL直接返回');
return urlStr;
}
// 如果是相对路径,拼接服务器地址
// 移除开头的 / 避免重复
let path = urlStr;
if (path.charAt(0) === '/') {
path = path.substring(1);
}
// 使用当前页面的协议和主机名
const baseUrl = window.location.origin;
const fullUrl = baseUrl + '/' + path;
console.log('拼接后的完整URL:', fullUrl);
return fullUrl;
}
}
};
</script>
<style scoped>
.mb20 {
margin-bottom: 20px;
}
.stat-card {
display: flex;
align-items: center;
}
.stat-icon {
font-size: 48px;
margin-right: 20px;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #303133;
}
.stat-label {
font-size: 14px;
color: #909399;
margin-top: 5px;
}
.avatar-uploader {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
display: block;
}
.avatar {
width: 120px;
height: 120px;
display: block;
}
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
font-size: 30px;
}
</style>