首页
This commit is contained in:
parent
1bc7e4a098
commit
c5d7b5e70a
|
|
@ -35,17 +35,25 @@
|
|||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑 Banner' : '新增 Banner'" width="500px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
|
||||
<el-form-item label="图片" prop="imageUrl">
|
||||
<el-input v-model="form.imageUrl" placeholder="图片地址" />
|
||||
<el-upload
|
||||
action="/api/upload/image"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="(res) => form.imageUrl = res.url"
|
||||
:on-success="onUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
accept="image/*"
|
||||
style="margin-top: 8px;"
|
||||
>
|
||||
<el-button size="small">上传图片</el-button>
|
||||
<el-image
|
||||
v-if="form.imageUrl"
|
||||
:src="form.imageUrl"
|
||||
style="width: 200px; height: 100px; cursor: pointer;"
|
||||
fit="cover"
|
||||
/>
|
||||
<el-button v-else size="small" type="primary">选择图片</el-button>
|
||||
</el-upload>
|
||||
<div v-if="form.imageUrl" style="margin-top: 4px;">
|
||||
<el-button size="small" text type="danger" @click="form.imageUrl = ''">移除图片</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="链接类型" prop="linkType">
|
||||
<el-select v-model="form.linkType" style="width: 100%;">
|
||||
|
|
@ -86,6 +94,27 @@ const formRef = ref(null)
|
|||
|
||||
const uploadHeaders = { Authorization: `Bearer ${localStorage.getItem('admin_token')}` }
|
||||
|
||||
/** 上传成功回调 */
|
||||
function onUploadSuccess(res) {
|
||||
form.imageUrl = res.url
|
||||
ElMessage.success('图片上传成功')
|
||||
}
|
||||
|
||||
/** 上传前校验 */
|
||||
function beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件')
|
||||
return false
|
||||
}
|
||||
if (!isLt5M) {
|
||||
ElMessage.error('图片大小不能超过 5MB')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const defaultForm = () => ({
|
||||
imageUrl: '',
|
||||
linkType: 'External',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
<template>
|
||||
<div>
|
||||
<h3 style="margin: 0 0 16px;">服务入口管理</h3>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||||
<h3 style="margin: 0;">服务入口管理</h3>
|
||||
<el-button type="primary" @click="openDialog()">新增服务入口</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="list" v-loading="loading" border>
|
||||
<el-table-column prop="name" label="服务名称" width="140" />
|
||||
<el-table-column label="图标" width="100">
|
||||
<el-table-column label="背景图" width="160">
|
||||
<template #default="{ row }">
|
||||
<el-image :src="row.iconUrl" style="width: 40px; height: 40px;" fit="contain" />
|
||||
<el-image :src="row.iconUrl" style="width: 120px; height: 40px; border-radius: 4px;" fit="cover" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pagePath" label="跳转路径" show-overflow-tooltip />
|
||||
|
|
@ -16,31 +19,51 @@
|
|||
<el-tag :type="row.isEnabled ? 'success' : 'info'">{{ row.isEnabled ? '启用' : '禁用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="openDialog(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" title="编辑服务入口" width="500px">
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑服务入口' : '新增服务入口'" width="500px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
|
||||
<el-form-item label="服务名称">
|
||||
<el-input :model-value="editName" disabled />
|
||||
<el-form-item label="服务类型" prop="name">
|
||||
<el-select v-model="form.name" placeholder="请选择服务类型" :disabled="isEdit" @change="onTypeChange" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="item in serviceTypes"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
:value="item.name"
|
||||
:disabled="existingNames.includes(item.name)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标图片" prop="iconUrl">
|
||||
<el-input v-model="form.iconUrl" placeholder="图标图片地址" />
|
||||
<el-form-item label="背景图片" prop="iconUrl">
|
||||
<el-upload
|
||||
action="/api/upload/image"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="(res) => form.iconUrl = res.url"
|
||||
:on-success="onUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
accept="image/*"
|
||||
style="margin-top: 8px;"
|
||||
>
|
||||
<el-button size="small">上传图片</el-button>
|
||||
<el-image
|
||||
v-if="form.iconUrl"
|
||||
:src="form.iconUrl"
|
||||
style="width: 300px; height: 100px; cursor: pointer; border-radius: 8px;"
|
||||
fit="cover"
|
||||
/>
|
||||
<el-button v-else size="small" type="primary">选择图片</el-button>
|
||||
</el-upload>
|
||||
<div v-if="form.iconUrl" style="margin-top: 4px;">
|
||||
<el-button size="small" text type="danger" @click="form.iconUrl = ''">移除图片</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转路径">
|
||||
<el-input v-model="form.pagePath" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序权重">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" />
|
||||
|
|
@ -59,23 +82,65 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import request from '../utils/request'
|
||||
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const list = ref([])
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const editId = ref(null)
|
||||
const editName = ref('')
|
||||
const formRef = ref(null)
|
||||
|
||||
const uploadHeaders = { Authorization: `Bearer ${localStorage.getItem('admin_token')}` }
|
||||
|
||||
const form = reactive({ iconUrl: '', sortOrder: 0, isEnabled: true })
|
||||
/** 服务类型与跳转路径映射 */
|
||||
const serviceTypes = [
|
||||
{ name: '代取', path: '/pages/pickup/pickup' },
|
||||
{ name: '代送', path: '/pages/delivery/delivery' },
|
||||
{ name: '万能帮', path: '/pages/help/help' },
|
||||
{ name: '代购', path: '/pages/purchase/purchase' },
|
||||
{ name: '美食街', path: '/pages/food/food' }
|
||||
]
|
||||
|
||||
/** 已存在的服务名称(新增时禁用已有的选项) */
|
||||
const existingNames = ref([])
|
||||
|
||||
/** 选择服务类型后自动匹配跳转路径 */
|
||||
function onTypeChange(name) {
|
||||
const match = serviceTypes.find(t => t.name === name)
|
||||
if (match) form.pagePath = match.path
|
||||
}
|
||||
|
||||
/** 上传成功回调 */
|
||||
function onUploadSuccess(res) {
|
||||
form.iconUrl = res.url
|
||||
ElMessage.success('图片上传成功')
|
||||
}
|
||||
|
||||
/** 上传前校验 */
|
||||
function beforeUpload(file) {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isImage) { ElMessage.error('只能上传图片文件'); return false }
|
||||
if (!isLt5M) { ElMessage.error('图片大小不能超过 5MB'); return false }
|
||||
return true
|
||||
}
|
||||
|
||||
const defaultForm = () => ({
|
||||
name: '',
|
||||
iconUrl: '',
|
||||
pagePath: '',
|
||||
sortOrder: 0,
|
||||
isEnabled: true
|
||||
})
|
||||
|
||||
const form = reactive(defaultForm())
|
||||
|
||||
const rules = {
|
||||
iconUrl: [{ required: true, message: '图标图片地址不能为空', trigger: 'blur' }]
|
||||
name: [{ required: true, message: '请选择服务类型', trigger: 'change' }],
|
||||
iconUrl: [{ required: true, message: '请上传背景图片', trigger: 'change' }]
|
||||
}
|
||||
|
||||
/** 获取服务入口列表 */
|
||||
|
|
@ -83,28 +148,40 @@ async function fetchList() {
|
|||
loading.value = true
|
||||
try {
|
||||
list.value = await request.get('/admin/service-entries')
|
||||
existingNames.value = list.value.map(item => item.name)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开编辑弹窗 */
|
||||
/** 打开新增/编辑弹窗 */
|
||||
function openDialog(row) {
|
||||
editId.value = row.id
|
||||
editName.value = row.name
|
||||
Object.assign(form, { iconUrl: row.iconUrl, sortOrder: row.sortOrder, isEnabled: row.isEnabled })
|
||||
isEdit.value = !!row
|
||||
editId.value = row?.id || null
|
||||
Object.assign(form, row ? {
|
||||
name: row.name,
|
||||
iconUrl: row.iconUrl,
|
||||
pagePath: row.pagePath,
|
||||
sortOrder: row.sortOrder,
|
||||
isEnabled: row.isEnabled
|
||||
} : defaultForm())
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/** 提交更新 */
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const valid = await formRef.value.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
await request.put(`/admin/service-entries/${editId.value}`, form)
|
||||
ElMessage.success('更新成功')
|
||||
if (isEdit.value) {
|
||||
await request.put(`/admin/service-entries/${editId.value}`, form)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await request.post('/admin/service-entries', form)
|
||||
ElMessage.success('创建成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
fetchList()
|
||||
} finally {
|
||||
|
|
@ -112,5 +189,13 @@ async function handleSubmit() {
|
|||
}
|
||||
}
|
||||
|
||||
/** 删除服务入口 */
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除「${row.name}」?`, '提示', { type: 'warning' })
|
||||
await request.delete(`/admin/service-entries/${row.id}`)
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
}
|
||||
|
||||
onMounted(fetchList)
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,35 +1,28 @@
|
|||
<template>
|
||||
<view class="index-page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="navbar-content" :style="{ height: navBarHeight + 'px' }">
|
||||
<text class="navbar-title">首页</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 占位,防止内容被导航栏遮挡 -->
|
||||
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }"></view>
|
||||
|
||||
<!-- Banner 轮播区域 -->
|
||||
<swiper
|
||||
v-if="banners.length > 0"
|
||||
class="banner-swiper"
|
||||
indicator-dots
|
||||
autoplay
|
||||
circular
|
||||
indicator-color="rgba(255,255,255,0.5)"
|
||||
indicator-active-color="#ffffff"
|
||||
>
|
||||
<swiper-item
|
||||
v-for="item in banners"
|
||||
:key="item.id"
|
||||
@click="onBannerClick(item)"
|
||||
>
|
||||
<swiper v-if="banners.length > 0" class="banner-swiper" indicator-dots autoplay circular
|
||||
indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#ffffff">
|
||||
<swiper-item v-for="item in banners" :key="item.id" @click="onBannerClick(item)">
|
||||
<image class="banner-image" :src="item.imageUrl" mode="aspectFill"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 服务入口区域 -->
|
||||
<view class="service-section">
|
||||
<view class="service-title">下订单</view>
|
||||
<view class="service-grid">
|
||||
<view
|
||||
class="service-item"
|
||||
v-for="entry in serviceEntries"
|
||||
:key="entry.id"
|
||||
@click="onServiceClick(entry)"
|
||||
>
|
||||
<image class="service-icon" :src="entry.iconUrl" mode="aspectFit"></image>
|
||||
<!-- 服务入口卡片列表 -->
|
||||
<view class="service-list">
|
||||
<view class="service-card" v-for="entry in serviceEntries" :key="entry.id" @click="onServiceClick(entry)">
|
||||
<image class="service-bg" :src="entry.iconUrl" mode="aspectFit"></image>
|
||||
<view class="service-overlay">
|
||||
<text class="service-name">{{ entry.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -38,114 +31,142 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { getBanners, getServiceEntries } from '../../utils/api'
|
||||
import {
|
||||
getBanners,
|
||||
getServiceEntries
|
||||
} from '../../utils/api'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
banners: [],
|
||||
serviceEntries: []
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/** 加载 Banner 和服务入口数据 */
|
||||
async loadData() {
|
||||
try {
|
||||
const [bannerRes, entryRes] = await Promise.all([
|
||||
getBanners(),
|
||||
getServiceEntries()
|
||||
])
|
||||
this.banners = bannerRes || []
|
||||
this.serviceEntries = entryRes || []
|
||||
} catch (e) {
|
||||
// 静默处理,页面展示空状态
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
banners: [],
|
||||
serviceEntries: [],
|
||||
statusBarHeight: 0,
|
||||
navBarHeight: 44
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
const sysInfo = uni.getSystemInfoSync()
|
||||
this.statusBarHeight = sysInfo.statusBarHeight || 0
|
||||
},
|
||||
onShow() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
/** 加载 Banner 和服务入口数据 */
|
||||
async loadData() {
|
||||
try {
|
||||
const [bannerRes, entryRes] = await Promise.all([
|
||||
getBanners(),
|
||||
getServiceEntries()
|
||||
])
|
||||
this.banners = bannerRes || []
|
||||
this.serviceEntries = entryRes || []
|
||||
} catch (e) {
|
||||
// 静默处理
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Banner 点击处理
|
||||
* 外部链接通过 web-view 打开
|
||||
* 内部页面直接跳转
|
||||
*/
|
||||
onBannerClick(item) {
|
||||
if (!item.linkUrl) return
|
||||
/** Banner 点击处理 */
|
||||
onBannerClick(item) {
|
||||
if (!item.linkUrl) return
|
||||
if (item.linkType === 'External') {
|
||||
uni.navigateTo({
|
||||
url: `/pages/webview/webview?url=${encodeURIComponent(item.linkUrl)}`
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: item.linkUrl
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
if (item.linkType === 'External') {
|
||||
// 外部链接,通过 web-view 打开
|
||||
/** 服务入口点击跳转 */
|
||||
onServiceClick(entry) {
|
||||
if (!entry.pagePath) return
|
||||
uni.navigateTo({
|
||||
url: `/pages/webview/webview?url=${encodeURIComponent(item.linkUrl)}`
|
||||
url: entry.pagePath
|
||||
})
|
||||
} else {
|
||||
// 内部页面跳转
|
||||
uni.navigateTo({ url: item.linkUrl })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 服务入口点击跳转
|
||||
*/
|
||||
onServiceClick(entry) {
|
||||
if (!entry.pagePath) return
|
||||
uni.navigateTo({ url: entry.pagePath })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.index-page {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
.index-page {
|
||||
padding-bottom: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.banner-swiper {
|
||||
width: 100%;
|
||||
height: 320rpx;
|
||||
}
|
||||
/* 自定义导航栏 */
|
||||
.custom-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 999;
|
||||
background: linear-gradient(to right, #FFB700, #FFD59B);
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 320rpx;
|
||||
}
|
||||
.navbar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.service-section {
|
||||
margin: 30rpx 24rpx 0;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
.navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #363636;
|
||||
}
|
||||
|
||||
.service-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
.banner-swiper {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
.service-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 20%;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
/* 服务入口卡片列表 */
|
||||
.service-list {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.service-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 180rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
</style>
|
||||
.service-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.service-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #363636;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||||
letter-spacing: 8rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.3" />
|
||||
<PackageReference Include="Tencent.QCloud.Cos.Sdk" Version="5.4.*" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,33 @@ using System.ComponentModel.DataAnnotations;
|
|||
|
||||
namespace CampusErrand.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// 服务入口创建请求
|
||||
/// </summary>
|
||||
public class ServiceEntryCreateRequest
|
||||
{
|
||||
/// <summary>服务名称</summary>
|
||||
[Required(ErrorMessage = "服务名称不能为空")]
|
||||
[MaxLength(32)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>背景图片地址</summary>
|
||||
[Required(ErrorMessage = "背景图片不能为空")]
|
||||
[MaxLength(512)]
|
||||
public string IconUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>跳转页面路径</summary>
|
||||
[Required(ErrorMessage = "跳转路径不能为空")]
|
||||
[MaxLength(256)]
|
||||
public string PagePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>排序权重</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>是否启用</summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务入口更新请求
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -428,6 +428,51 @@ app.MapPut("/api/admin/service-entries/{id}", async (int id, ServiceEntryUpdateR
|
|||
});
|
||||
}).RequireAuthorization("AdminOnly");
|
||||
|
||||
// 创建服务入口
|
||||
app.MapPost("/api/admin/service-entries", async (ServiceEntryCreateRequest request, AppDbContext db) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
return Results.BadRequest(new { code = 400, message = "服务名称不能为空" });
|
||||
if (string.IsNullOrWhiteSpace(request.IconUrl))
|
||||
return Results.BadRequest(new { code = 400, message = "背景图片不能为空" });
|
||||
if (string.IsNullOrWhiteSpace(request.PagePath))
|
||||
return Results.BadRequest(new { code = 400, message = "跳转路径不能为空" });
|
||||
|
||||
var entry = new ServiceEntry
|
||||
{
|
||||
Name = request.Name,
|
||||
IconUrl = request.IconUrl,
|
||||
PagePath = request.PagePath,
|
||||
SortOrder = request.SortOrder,
|
||||
IsEnabled = request.IsEnabled
|
||||
};
|
||||
|
||||
db.ServiceEntries.Add(entry);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Results.Created($"/api/admin/service-entries/{entry.Id}", new ServiceEntryResponse
|
||||
{
|
||||
Id = entry.Id,
|
||||
Name = entry.Name,
|
||||
IconUrl = entry.IconUrl,
|
||||
PagePath = entry.PagePath,
|
||||
SortOrder = entry.SortOrder,
|
||||
IsEnabled = entry.IsEnabled
|
||||
});
|
||||
}).RequireAuthorization("AdminOnly");
|
||||
|
||||
// 删除服务入口
|
||||
app.MapDelete("/api/admin/service-entries/{id}", async (int id, AppDbContext db) =>
|
||||
{
|
||||
var entry = await db.ServiceEntries.FindAsync(id);
|
||||
if (entry == null)
|
||||
return Results.NotFound(new { code = 404, message = "服务入口不存在" });
|
||||
|
||||
db.ServiceEntries.Remove(entry);
|
||||
await db.SaveChangesAsync();
|
||||
return Results.NoContent();
|
||||
}).RequireAuthorization("AdminOnly");
|
||||
|
||||
// ========== 订单接口 ==========
|
||||
|
||||
// 创建订单
|
||||
|
|
@ -2426,33 +2471,43 @@ app.MapPost("/api/upload/image", async (IFormFile file, IConfiguration config) =
|
|||
if (!allowedExtensions.Contains(ext))
|
||||
return Results.BadRequest(new { code = 400, message = $"不支持的图片格式,仅支持 {string.Join(", ", allowedExtensions)}" });
|
||||
|
||||
// 保存文件
|
||||
var uploadDir = config.GetValue<string>("Upload:Directory") ?? "uploads";
|
||||
var fullDir = Path.Combine(Directory.GetCurrentDirectory(), uploadDir);
|
||||
Directory.CreateDirectory(fullDir);
|
||||
// 上传到腾讯云 COS
|
||||
var cosConfig = new COSXML.CosXmlConfig.Builder()
|
||||
.IsHttps(true)
|
||||
.SetRegion(config["COS:Region"])
|
||||
.Build();
|
||||
var credential = new COSXML.Auth.DefaultQCloudCredentialProvider(
|
||||
config["COS:SecretId"], config["COS:SecretKey"], 600);
|
||||
var cosXml = new COSXML.CosXmlServer(cosConfig, credential);
|
||||
|
||||
var fileName = $"{Guid.NewGuid()}{ext}";
|
||||
var filePath = Path.Combine(fullDir, fileName);
|
||||
var bucket = config["COS:Bucket"]!;
|
||||
var cosKey = $"uploads/{DateTime.UtcNow:yyyyMMdd}/{Guid.NewGuid()}{ext}";
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||
// 将上传文件写入临时文件
|
||||
var tempPath = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
using (var stream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
var url = $"/{uploadDir}/{fileName}";
|
||||
return Results.Ok(new UploadImageResponse { Url = url });
|
||||
var putRequest = new COSXML.Model.Object.PutObjectRequest(bucket, cosKey, tempPath);
|
||||
var putResult = cosXml.PutObject(putRequest);
|
||||
|
||||
if (putResult.httpCode != 200)
|
||||
return Results.BadRequest(new { code = 400, message = "图片上传失败" });
|
||||
|
||||
var url = $"{config["COS:BaseUrl"]}/{cosKey}";
|
||||
return Results.Ok(new UploadImageResponse { Url = url });
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(tempPath)) File.Delete(tempPath);
|
||||
}
|
||||
}).RequireAuthorization()
|
||||
.DisableAntiforgery();
|
||||
|
||||
// 静态文件服务(上传的图片)
|
||||
var uploadPath = Path.Combine(Directory.GetCurrentDirectory(),
|
||||
builder.Configuration.GetValue<string>("Upload:Directory") ?? "uploads");
|
||||
Directory.CreateDirectory(uploadPath);
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(uploadPath),
|
||||
RequestPath = "/" + (builder.Configuration.GetValue<string>("Upload:Directory") ?? "uploads")
|
||||
});
|
||||
|
||||
// ========== 系统配置接口 ==========
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,16 @@
|
|||
"AppSecret": "your_wechat_appsecret"
|
||||
},
|
||||
"Upload": {
|
||||
"Directory": "uploads",
|
||||
"MaxFileSizeBytes": 5242880,
|
||||
"AllowedExtensions": [ ".jpg", ".jpeg", ".png", ".gif", ".webp" ]
|
||||
},
|
||||
"COS": {
|
||||
"SecretId": "AKIDPioO4YovwtfMrwrYJq8CNN9qT4c0IyQd",
|
||||
"SecretKey": "1nLfLp44pOxUsKe1AmS8gFoBLH0Vloco",
|
||||
"Region": "ap-nanjing",
|
||||
"Bucket": "xypt-1410898760",
|
||||
"BaseUrl": "https://xypt-1410898760.cos.ap-nanjing.myqcloud.com"
|
||||
},
|
||||
"Admin": {
|
||||
"Username": "admin",
|
||||
"Password": "admin123"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
"Microsoft.EntityFrameworkCore.Design": "10.0.3",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer": "10.0.3",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "10.0.3",
|
||||
"Swashbuckle.AspNetCore": "10.1.4"
|
||||
"Swashbuckle.AspNetCore": "10.1.4",
|
||||
"Tencent.QCloud.Cos.Sdk": "5.4.51"
|
||||
},
|
||||
"runtime": {
|
||||
"CampusErrand.dll": {}
|
||||
|
|
@ -846,6 +847,14 @@
|
|||
"fileVersion": "9.0.425.16305"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tencent.QCloud.Cos.Sdk/5.4.51": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/COSXML.dll": {
|
||||
"assemblyVersion": "5.4.51.0",
|
||||
"fileVersion": "5.4.51.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1225,6 +1234,13 @@
|
|||
"sha512": "sha512-o94k2RKuAce3GeDMlUvIXlhVa1kWpJw95E6C9LwW0KlG0nj5+SgCiIxJ2Eroqb9sLtG1mEMbFttZIBZ13EJPvQ==",
|
||||
"path": "system.security.cryptography.protecteddata/9.0.4",
|
||||
"hashPath": "system.security.cryptography.protecteddata.9.0.4.nupkg.sha512"
|
||||
},
|
||||
"Tencent.QCloud.Cos.Sdk/5.4.51": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-ZLU6XX7vyLaVFN0i+fcFbiXSN2BENKxlTe7inIRT4JibuzhU9cZxGj+PfSIib46+uJM6HSRJE2ynnGLCMtTt1Q==",
|
||||
"path": "tencent.qcloud.cos.sdk/5.4.51",
|
||||
"hashPath": "tencent.qcloud.cos.sdk.5.4.51.nupkg.sha512"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -23,10 +23,16 @@
|
|||
"AppSecret": "your_wechat_appsecret"
|
||||
},
|
||||
"Upload": {
|
||||
"Directory": "uploads",
|
||||
"MaxFileSizeBytes": 5242880,
|
||||
"AllowedExtensions": [ ".jpg", ".jpeg", ".png", ".gif", ".webp" ]
|
||||
},
|
||||
"COS": {
|
||||
"SecretId": "AKIDPioO4YovwtfMrwrYJq8CNN9qT4c0IyQd",
|
||||
"SecretKey": "1nLfLp44pOxUsKe1AmS8gFoBLH0Vloco",
|
||||
"Region": "ap-nanjing",
|
||||
"Bucket": "xypt-1410898760",
|
||||
"BaseUrl": "https://xypt-1410898760.cos.ap-nanjing.myqcloud.com"
|
||||
},
|
||||
"Admin": {
|
||||
"Username": "admin",
|
||||
"Password": "admin123"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@
|
|||
"Swashbuckle.AspNetCore": {
|
||||
"target": "Package",
|
||||
"version": "[10.1.4, )"
|
||||
},
|
||||
"Tencent.QCloud.Cos.Sdk": {
|
||||
"target": "Package",
|
||||
"version": "[5.4.*, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using System.Reflection;
|
|||
[assembly: System.Reflection.AssemblyCompanyAttribute("CampusErrand")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8245a67b883c1c90d3366fdad3ae6571b49fb874")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1bc7e4a09873a38e8ff83966dc378b341abe98ad")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("CampusErrand")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("CampusErrand")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
98518d7bd93239cba34464b0a48bacbdc87dd10aa86c7d87d6cac18429a41da5
|
||||
96b198ea2f78751f1455d36835312860960c974f190b06da8b6cfc34bed4d63b
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
44792c73e656d5f2a97e5c9b6b8ce93c50dcddc8336f696a305446e8c692ccef
|
||||
78df5ed8d99d8eb59d9966deec229e9c6198cae7cafc5e797e106a95bd2f5937
|
||||
|
|
|
|||
|
|
@ -163,3 +163,4 @@ F:\gitCode\uniapp\campus-errand\server\bin\Debug\net10.0\runtimes\win-x86\native
|
|||
F:\gitCode\uniapp\campus-errand\server\bin\Debug\net10.0\Microsoft.Extensions.Caching.StackExchangeRedis.dll
|
||||
F:\gitCode\uniapp\campus-errand\server\bin\Debug\net10.0\Pipelines.Sockets.Unofficial.dll
|
||||
F:\gitCode\uniapp\campus-errand\server\bin\Debug\net10.0\StackExchange.Redis.dll
|
||||
F:\gitCode\uniapp\campus-errand\server\bin\Debug\net10.0\COSXML.dll
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
{"GlobalPropertiesHash":"FvzDiEs0EAZdBI8Qwd38I/qGfErxBHzrnzacDdp9C+k=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["GK5QIP/XYYWUmh4Y3T9pQ7nr7hLlrhu9WxJOHJg3jys=","z/gGq5d2i3Bdu2Yx2tEtdvNdI2DMCuO1Loennep6rpA=","SoTqP58euSfouOt\u002BChgsmMST1TEMY1s4SX8M5\u002BcSaTw=","2QszdUJOx7XR0BbgOxnwnj2CMLTUOxSCfW9scid9JZc=","Fnoznyra3P\u002BfAr4\u002B9jhnDM6hctHw/2GK9WTw8eHJKVU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"FvzDiEs0EAZdBI8Qwd38I/qGfErxBHzrnzacDdp9C+k=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["GK5QIP/XYYWUmh4Y3T9pQ7nr7hLlrhu9WxJOHJg3jys=","7O1biPThQ/Mx8/A74Cld5MaMED9jut8kLikNlVczxp0=","SoTqP58euSfouOt\u002BChgsmMST1TEMY1s4SX8M5\u002BcSaTw=","2QszdUJOx7XR0BbgOxnwnj2CMLTUOxSCfW9scid9JZc=","kZStffijBiMuyyJSjpIXxXHAqVzgrSRDWd\u002BAbED3Bw0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
|
|
@ -1 +1 @@
|
|||
{"GlobalPropertiesHash":"xZi1R2Spy0ovDkIJOzs1I1YG/hzfBSNqDGLZKXuVfgw=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["GK5QIP/XYYWUmh4Y3T9pQ7nr7hLlrhu9WxJOHJg3jys=","z/gGq5d2i3Bdu2Yx2tEtdvNdI2DMCuO1Loennep6rpA=","SoTqP58euSfouOt\u002BChgsmMST1TEMY1s4SX8M5\u002BcSaTw=","2QszdUJOx7XR0BbgOxnwnj2CMLTUOxSCfW9scid9JZc=","Fnoznyra3P\u002BfAr4\u002B9jhnDM6hctHw/2GK9WTw8eHJKVU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"xZi1R2Spy0ovDkIJOzs1I1YG/hzfBSNqDGLZKXuVfgw=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["GK5QIP/XYYWUmh4Y3T9pQ7nr7hLlrhu9WxJOHJg3jys=","7O1biPThQ/Mx8/A74Cld5MaMED9jut8kLikNlVczxp0=","SoTqP58euSfouOt\u002BChgsmMST1TEMY1s4SX8M5\u002BcSaTw=","2QszdUJOx7XR0BbgOxnwnj2CMLTUOxSCfW9scid9JZc=","kZStffijBiMuyyJSjpIXxXHAqVzgrSRDWd\u002BAbED3Bw0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
|
|
@ -1 +1 @@
|
|||
{"GlobalPropertiesHash":"UIXRzOYtQqecH7DZRES/B1ooBYuqqrRO0PLf2qdAYiY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["GK5QIP/XYYWUmh4Y3T9pQ7nr7hLlrhu9WxJOHJg3jys=","z/gGq5d2i3Bdu2Yx2tEtdvNdI2DMCuO1Loennep6rpA="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"UIXRzOYtQqecH7DZRES/B1ooBYuqqrRO0PLf2qdAYiY=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["GK5QIP/XYYWUmh4Y3T9pQ7nr7hLlrhu9WxJOHJg3jys=","7O1biPThQ/Mx8/A74Cld5MaMED9jut8kLikNlVczxp0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
|
|
@ -1198,6 +1198,15 @@
|
|||
"build": {
|
||||
"buildTransitive/net8.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Tencent.QCloud.Cos.Sdk/5.4.51": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/netstandard2.0/COSXML.dll": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/COSXML.dll": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -3669,6 +3678,19 @@
|
|||
"system.security.cryptography.protecteddata.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
},
|
||||
"Tencent.QCloud.Cos.Sdk/5.4.51": {
|
||||
"sha512": "ZLU6XX7vyLaVFN0i+fcFbiXSN2BENKxlTe7inIRT4JibuzhU9cZxGj+PfSIib46+uJM6HSRJE2ynnGLCMtTt1Q==",
|
||||
"type": "package",
|
||||
"path": "tencent.qcloud.cos.sdk/5.4.51",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"lib/net45/COSXML.dll",
|
||||
"lib/netstandard2.0/COSXML.dll",
|
||||
"tencent.qcloud.cos.sdk.5.4.51.nupkg.sha512",
|
||||
"tencent.qcloud.cos.sdk.nuspec"
|
||||
]
|
||||
}
|
||||
},
|
||||
"projectFileDependencyGroups": {
|
||||
|
|
@ -3678,7 +3700,8 @@
|
|||
"Microsoft.EntityFrameworkCore.Design >= 10.0.3",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer >= 10.0.3",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis >= 10.0.3",
|
||||
"Swashbuckle.AspNetCore >= 10.1.4"
|
||||
"Swashbuckle.AspNetCore >= 10.1.4",
|
||||
"Tencent.QCloud.Cos.Sdk >= 5.4.*"
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
|
|
@ -3754,6 +3777,10 @@
|
|||
"Swashbuckle.AspNetCore": {
|
||||
"target": "Package",
|
||||
"version": "[10.1.4, )"
|
||||
},
|
||||
"Tencent.QCloud.Cos.Sdk": {
|
||||
"target": "Package",
|
||||
"version": "[5.4.*, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "bN2gwtm/FeU=",
|
||||
"dgSpecHash": "1DPzBxf0SFk=",
|
||||
"success": true,
|
||||
"projectFilePath": "F:\\gitCode\\uniapp\\campus-errand\\server\\CampusErrand.csproj",
|
||||
"expectedPackageFiles": [
|
||||
|
|
@ -60,7 +60,8 @@
|
|||
"C:\\Users\\HJL\\.nuget\\packages\\system.identitymodel.tokens.jwt\\8.0.1\\system.identitymodel.tokens.jwt.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\HJL\\.nuget\\packages\\system.memory.data\\8.0.1\\system.memory.data.8.0.1.nupkg.sha512",
|
||||
"C:\\Users\\HJL\\.nuget\\packages\\system.security.cryptography.pkcs\\9.0.4\\system.security.cryptography.pkcs.9.0.4.nupkg.sha512",
|
||||
"C:\\Users\\HJL\\.nuget\\packages\\system.security.cryptography.protecteddata\\9.0.4\\system.security.cryptography.protecteddata.9.0.4.nupkg.sha512"
|
||||
"C:\\Users\\HJL\\.nuget\\packages\\system.security.cryptography.protecteddata\\9.0.4\\system.security.cryptography.protecteddata.9.0.4.nupkg.sha512",
|
||||
"C:\\Users\\HJL\\.nuget\\packages\\tencent.qcloud.cos.sdk\\5.4.51\\tencent.qcloud.cos.sdk.5.4.51.nupkg.sha512"
|
||||
],
|
||||
"logs": []
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user