From f0e89695f40ec3919fb9341d1ef77de1f8119a2f Mon Sep 17 00:00:00 2001 From: 2256907009 <2256907009@qq.com> Date: Mon, 6 Apr 2026 18:44:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=95=86=E5=93=81=E7=9A=84=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=A1=B9=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/admin/kvmService.js | 12 + .../admin/PermissionPathSelector.vue | 2 +- src/components/admin/PlanSelector.vue | 118 ++- src/components/admin/UserVmVolumeSelector.vue | 131 +++ src/components/admin/VmSelectorPopup.vue | 2 +- src/utils/tool.js | 52 + src/views/acs/images/ContainerImages.vue | 6 +- src/views/acs/images/ImageCategories.vue | 2 +- src/views/acs/images/ImageForm.vue | 4 +- src/views/acs/nodes/server.vue | 4 +- src/views/activity/GroupBuyActivity.vue | 2 +- src/views/activity/GroupBuyManage.vue | 2 +- src/views/audit/AllSites.vue | 2 +- src/views/dashboard/Dashboard.vue | 8 +- src/views/marketing/DiscountGoods.vue | 4 +- src/views/marketing/DiscountUsers.vue | 4 +- src/views/marketing/GroupBuyManage.vue | 2 +- src/views/marketing/UserVoucher.vue | 14 +- src/views/marketing/VoucherHistory.vue | 4 +- src/views/marketing/VoucherHolders.vue | 2 +- src/views/product/ProductGroup.vue | 28 +- src/views/product/ProductList.vue | 6 +- src/views/product/UserGoodsList.vue | 280 +++++- src/views/system/SettingManage.vue | 8 +- src/views/ticket/TicketList.vue | 2 +- src/views/user-vm/UserVmDetail.vue | 928 ++++++++++++++++-- src/views/user-vm/UserVmList.vue | 4 +- src/views/user/UserBalance.vue | 2 +- src/views/user/UserDetail.vue | 4 +- src/views/virtualization/BackupManage.vue | 2 +- src/views/virtualization/SnapshotManage.vue | 2 +- .../virtualization/UserNetworkingManage.vue | 2 +- src/views/virtualization/VmDetail.vue | 175 +++- src/views/virtualization/VolumeDetail.vue | 2 +- 审查代码提示词.MD | 64 ++ 默认模块.openapi.json | 382 ++++++- 36 files changed, 2078 insertions(+), 190 deletions(-) create mode 100644 src/components/admin/UserVmVolumeSelector.vue create mode 100644 审查代码提示词.MD diff --git a/src/api/admin/kvmService.js b/src/api/admin/kvmService.js index c9eda77..662e95b 100644 --- a/src/api/admin/kvmService.js +++ b/src/api/admin/kvmService.js @@ -450,6 +450,18 @@ export const migrateVm = (data) => { }) } +/** 发起虚拟机数据迁移 */ +export const dataMigrateVm = (data) => { + return http2.post('/api/v1/admin/service/host_service/point/vm/data_migrate', data, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +/** 获取虚拟机数据迁移进度 */ +export const getDataMigrateProgress = (params) => { + return http2.get('/api/v1/admin/service/host_service/point/vm/data_migrate/progress', { params }) +} + /** * ================================ * 主控服务接口 - 安全组管理 diff --git a/src/components/admin/PermissionPathSelector.vue b/src/components/admin/PermissionPathSelector.vue index c5bcaf7..e23fb26 100644 --- a/src/components/admin/PermissionPathSelector.vue +++ b/src/components/admin/PermissionPathSelector.vue @@ -142,7 +142,7 @@ const searchParams = reactive({ key: '', method: '', page: 1, - count: 20 + count: 10 }) // 状态 diff --git a/src/components/admin/PlanSelector.vue b/src/components/admin/PlanSelector.vue index c1de38e..7e03de7 100644 --- a/src/components/admin/PlanSelector.vue +++ b/src/components/admin/PlanSelector.vue @@ -2,9 +2,10 @@
刷新 + 新建套餐 商品 ID: {{ goodId }}
- + @@ -28,12 +29,59 @@ 确定选择
+ + + + + + + +
+
请先选择商品
+
加载参数中...
+
该商品暂无参数
+
+
+
+ {{ spec.name }} + 必填 +
+ + + +
+
+
参数 JSON:
+ +
+
+
+
+ +
+ +
diff --git a/src/components/admin/UserVmVolumeSelector.vue b/src/components/admin/UserVmVolumeSelector.vue new file mode 100644 index 0000000..b209d6e --- /dev/null +++ b/src/components/admin/UserVmVolumeSelector.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/components/admin/VmSelectorPopup.vue b/src/components/admin/VmSelectorPopup.vue index f93e95d..8c086f1 100644 --- a/src/components/admin/VmSelectorPopup.vue +++ b/src/components/admin/VmSelectorPopup.vue @@ -71,7 +71,7 @@ const loadList = async () => { if (!hostIdFilter.value) return loading.value = true try { - const res = await getVmList({ service_id: props.serviceId, host_id: hostIdFilter.value, page: 1, count: 100 }) + const res = await getVmList({ service_id: props.serviceId, host_id: hostIdFilter.value, page: 1, count: 10 }) const body = res?.data if (body?.code === 200 && body?.data) { const inner = body.data diff --git a/src/utils/tool.js b/src/utils/tool.js index f819ad7..997a926 100644 --- a/src/utils/tool.js +++ b/src/utils/tool.js @@ -122,3 +122,55 @@ export function formatToApiTime(time) { const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` } + +// ========== 虚拟机状态映射 ========== +const VM_STATUS_MAP = { + pending: { label: '等待中', type: 'info' }, + creating: { label: '创建中', type: 'warning' }, + ready: { label: '就绪', type: 'success' }, + running: { label: '运行中', type: 'success' }, + stopped: { label: '已停止', type: 'danger' }, + stop: { label: '已停止', type: 'danger' }, + shutoff: { label: '已关闭', type: 'danger' }, + error: { label: '错误', type: 'danger' }, + paused: { label: '已暂停', type: 'warning' }, + reboot: { label: '重启中', type: 'warning' }, + poweroff: { label: '已关机', type: 'info' }, + unknown: { label: '未知', type: 'info' } +} + +/** + * 获取虚拟机状态标签文字 + */ +export function vmStatusLabel(status) { + return VM_STATUS_MAP[status]?.label || status || '-' +} + +/** + * 获取虚拟机状态 Tag 类型 + */ +export function vmStatusType(status) { + return VM_STATUS_MAP[status]?.type || 'info' +} + +// ========== 磁盘状态映射 ========== +const VOLUME_STATUS_MAP = { + pending: { label: '等待中', type: 'info' }, + ready: { label: '就绪', type: 'success' }, + error: { label: '错误', type: 'danger' }, + unknown: { label: '未知', type: 'info' } +} + +/** + * 获取磁盘状态标签文字 + */ +export function volumeStatusLabel(status) { + return VOLUME_STATUS_MAP[status]?.label || status || '-' +} + +/** + * 获取磁盘状态 Tag 类型 + */ +export function volumeStatusType(status) { + return VOLUME_STATUS_MAP[status]?.type || 'info' +} diff --git a/src/views/acs/images/ContainerImages.vue b/src/views/acs/images/ContainerImages.vue index fd673c4..e0003c9 100644 --- a/src/views/acs/images/ContainerImages.vue +++ b/src/views/acs/images/ContainerImages.vue @@ -666,7 +666,7 @@ const toLoad = async (data) => { }) form.server_id = data nowserver_id.value = data - let res = await getServerPlan({server_id:data,count:100}) + let res = await getServerPlan({server_id:data,count:10}) planlist.value = res.data.data.map(item => { return { name: item.name, @@ -748,7 +748,7 @@ const fetchCategoryList = async (serverId) => { // 编辑镜像 const handleEdit = async (data) => { try { - let res = await getServerPlan({server_id: data.server_id,count: 100}) + let res = await getServerPlan({server_id: data.server_id,count: 10}) if (res.data && res.data.data) { planlist.value = res.data.data.map(item => { return { @@ -874,7 +874,7 @@ const getit = async () => { // 选择图片 const picPagin = reactive({ - count: 50, + count: 10, page: 1, key: '', user_type: 1 diff --git a/src/views/acs/images/ImageCategories.vue b/src/views/acs/images/ImageCategories.vue index 70a85fe..66aea94 100644 --- a/src/views/acs/images/ImageCategories.vue +++ b/src/views/acs/images/ImageCategories.vue @@ -262,7 +262,7 @@ const categoryRules = { // 素材库相关 const picSwitch = ref(false) const picPagin = reactive({ - count: 50, + count: 10, page: 1, key: '', user_type: 1 diff --git a/src/views/acs/images/ImageForm.vue b/src/views/acs/images/ImageForm.vue index 365e52c..b3bbf88 100644 --- a/src/views/acs/images/ImageForm.vue +++ b/src/views/acs/images/ImageForm.vue @@ -244,7 +244,7 @@ const showNewCategoryInput = ref(false) const picSwitch = ref(false) const picLoading = ref(false) const picPagin = reactive({ - count: 20, + count: 10, page: 1, key: '', user_type: 1 @@ -314,7 +314,7 @@ const initData = async () => { // Fallback: fetch list and find item const listRes = await getUserMirrorList({ server_id: serverId.value, - count: 100, + count: 10, page: 1 }) if (listRes.data.code === 200) { diff --git a/src/views/acs/nodes/server.vue b/src/views/acs/nodes/server.vue index f1ae628..319fc8b 100644 --- a/src/views/acs/nodes/server.vue +++ b/src/views/acs/nodes/server.vue @@ -1901,7 +1901,7 @@ const GetSpecs = async () => { try { let plans = await getServerPlan({ server_id: route.query.server_id, - count: 30 + count: 10 }); spec_list.value = plans.data.data; } catch (error) { @@ -2430,7 +2430,7 @@ const fetchContainerMirrorList = async () => { containerMirrorLoading.value = true; try { - const response = await getMirrorList({server_id: route.query.server_id, page: 1, count: 999,key: '',class_id: ''}); + const response = await getMirrorList({server_id: route.query.server_id, page: 1, count: 10,key: '',class_id: ''}); console.log("获取镜像列表1111:",response); if (response && response.data && response.data.code === 200) { diff --git a/src/views/activity/GroupBuyActivity.vue b/src/views/activity/GroupBuyActivity.vue index 7796c30..be5a7a0 100644 --- a/src/views/activity/GroupBuyActivity.vue +++ b/src/views/activity/GroupBuyActivity.vue @@ -218,7 +218,7 @@ const fetchTags = async () => { // 根据 tag 获取拼团类型列表 const fetchTypeListByTag = async (tag) => { try { - const res = await getGroupBuyTypeList({ page: 1, count: 100, tag }) + const res = await getGroupBuyTypeList({ page: 1, count: 10, tag }) if (res.code === 200) { typeList.value = res.data?.data || [] } diff --git a/src/views/activity/GroupBuyManage.vue b/src/views/activity/GroupBuyManage.vue index 04c5875..6c0d90a 100644 --- a/src/views/activity/GroupBuyManage.vue +++ b/src/views/activity/GroupBuyManage.vue @@ -376,7 +376,7 @@ const fetchTags = async () => { // 根据 tag 获取拼团类型列表(用于创建对话框) const fetchCreateTypeListByTag = async (tag) => { try { - const res = await getGroupBuyTypeList({ page: 1, count: 100, tag }) + const res = await getGroupBuyTypeList({ page: 1, count: 10, tag }) if (res.code === 200) { createTypeList.value = res.data?.data || [] } diff --git a/src/views/audit/AllSites.vue b/src/views/audit/AllSites.vue index c070af8..7b04d2c 100644 --- a/src/views/audit/AllSites.vue +++ b/src/views/audit/AllSites.vue @@ -419,7 +419,7 @@ const getFullStatsData = async () => { // 获取第一页大量数据来进行统计,或者调用专门的统计接口 const statsParams = { page: 1, - count: 1000, // 获取大量数据进行统计 + count: 10, // 获取大量数据进行统计 server_id: '', user_id: '', key: queryParams.domain || '' diff --git a/src/views/dashboard/Dashboard.vue b/src/views/dashboard/Dashboard.vue index 3c93035..ff981ad 100644 --- a/src/views/dashboard/Dashboard.vue +++ b/src/views/dashboard/Dashboard.vue @@ -412,14 +412,14 @@ const statisticsCards = computed(() => [ const fetchStatistics = async () => { try { // 获取用户数量 - const userRes = await getUserList({ page: 1, count: 1, key: '' }) + const userRes = await getUserList({ page: 1, count: 10, key: '' }) console.log("用户数量,",userRes) if (userRes.data?.code === 200) { userCount.value = userRes.data.data.all_count || 0 } // 获取订单数量 - const orderRes = await getOrderList({ page: 1, count: 1 }) + const orderRes = await getOrderList({ page: 1, count: 10 }) console.log("订单数量,",orderRes) if (orderRes.data?.code === 200) { orderCount.value = orderRes.data.data.all_count || 0 @@ -441,7 +441,7 @@ const fetchRecentLists = async () => { listLoading.value = true try { // 获取最近用户 - const userRes = await getUserList({ page: 1, count: 5, key: '' }) + const userRes = await getUserList({ page: 1, count: 10, key: '' }) if (userRes.data?.code === 200) { recentUsers.value = (userRes.data.data.data || []).map(user => ({ id: user.user_id, @@ -453,7 +453,7 @@ const fetchRecentLists = async () => { } // 获取最近订单 - const orderRes = await getOrderList({ page: 1, count: 5 }) + const orderRes = await getOrderList({ page: 1, count: 10 }) if (orderRes.data?.code === 200) { recentOrders.value = (orderRes.data.data.list || []).map(order => ({ id: order.id, diff --git a/src/views/marketing/DiscountGoods.vue b/src/views/marketing/DiscountGoods.vue index 30e0f6c..114f8c0 100644 --- a/src/views/marketing/DiscountGoods.vue +++ b/src/views/marketing/DiscountGoods.vue @@ -389,7 +389,7 @@ const fetchVoucherListOptions = async () => { try { const res = await getDiscountCodeList({ page: 1, - count: 1000, + count: 10, discount_type: 'coupon' }) console.log('获取代金券列表:', res.data) @@ -407,7 +407,7 @@ const fetchProductList = async () => { try { const res = await getProductList({ page: 1, - count: 1000 + count: 10 }) console.log('获取商品列表:', res.data) if (res.data.code === 200) { diff --git a/src/views/marketing/DiscountUsers.vue b/src/views/marketing/DiscountUsers.vue index 54074e0..9c55c6e 100644 --- a/src/views/marketing/DiscountUsers.vue +++ b/src/views/marketing/DiscountUsers.vue @@ -361,7 +361,7 @@ const fetchVoucherListOptions = async () => { try { const res = await getDiscountCodeList({ page: 1, - count: 1000, + count: 10, discount_type: 'coupon' }) console.log('获取代金券列表:', res.data) @@ -397,7 +397,7 @@ const fetchUserGroupList = async () => { try { const res = await getUserGroupList({ page: 1, - count: 10000, + count: 10, key: '' }) console.log('获取用户组列表:', res.data) diff --git a/src/views/marketing/GroupBuyManage.vue b/src/views/marketing/GroupBuyManage.vue index 8af52d1..03ee0df 100644 --- a/src/views/marketing/GroupBuyManage.vue +++ b/src/views/marketing/GroupBuyManage.vue @@ -123,7 +123,7 @@ const currentGroupBuy = ref(null) // 加载拼团列表 const loadGroupBuyList = async () => { try { - const resp = await getGroupBuyList({ page: 1, pageSize: 20 }) + const resp = await getGroupBuyList({ page: 1, pageSize: 10 }) if (resp && resp.code === 200) { groupBuyList.value = resp.data || [] } diff --git a/src/views/marketing/UserVoucher.vue b/src/views/marketing/UserVoucher.vue index 2ed71fe..f71e2a4 100644 --- a/src/views/marketing/UserVoucher.vue +++ b/src/views/marketing/UserVoucher.vue @@ -484,7 +484,7 @@ const fetchVoucherListOptions = async () => { try { const res = await getDiscountCodeList({ page: 1, - count: 1000, + count: 10, discount_type: 'coupon' }) console.log('获取代金券列表:', res.data) @@ -502,7 +502,7 @@ const fetchDiscountList = async () => { try { const res = await getDiscountCodeList({ page: 1, - count: 100, + count: 10, discount_type: 'coupon' }) console.log('获取代金券列表:', res.data) @@ -513,7 +513,7 @@ const fetchDiscountList = async () => { } const res2 = await getDiscountCodeList({ page: 1, - count: 100, + count: 10, discount_type: 'code' }) console.log('获取优惠码列表:', res2.data) @@ -533,7 +533,7 @@ const fetchVoucherOptions = async () => { const res = await getDiscountCodeList({ discount_type: 'coupon', page: 1, - count: 100 + count: 10 }) if (res.data.code === 200) { voucherOptions.value = res.data.data?.data || [] @@ -549,7 +549,7 @@ const fetchCodeOptions = async () => { const res = await getDiscountCodeList({ discount_type: 'code', page: 1, - count: 100 + count: 10 }) if (res.data.code === 200) { codeOptions.value = res.data.data?.data || [] @@ -566,7 +566,7 @@ const fetchUserList = async () => { try { const res = await getUserList({ page: 1, - count: 100, + count: 10, key: '' }) console.log('获取用户列表:', res.data) @@ -588,7 +588,7 @@ const fetchGroupOptions = async () => { try { const res = await getUserGroupList({ page: 1, - count: 100 + count: 10 }) if (res.data.code === 200) { groupOptions.value = res.data.data?.data || [] diff --git a/src/views/marketing/VoucherHistory.vue b/src/views/marketing/VoucherHistory.vue index 9dffd67..bbabc08 100644 --- a/src/views/marketing/VoucherHistory.vue +++ b/src/views/marketing/VoucherHistory.vue @@ -397,7 +397,7 @@ const fetchUserList = async () => { try { const res = await getUserList({ page: 1, - count: 10000, + count: 10, key: '' }) UserOptions.value = res.data.data?.data || [] @@ -412,7 +412,7 @@ const fetchDiscountList = async () => { const res = await getDiscountCodeList({ discount_type: 'coupon', page: 1, - count: 1000 + count: 10 }) if (res.data.code === 200) { discountOptions.value = res.data.data?.data || [] diff --git a/src/views/marketing/VoucherHolders.vue b/src/views/marketing/VoucherHolders.vue index e488e99..3b1bb0e 100644 --- a/src/views/marketing/VoucherHolders.vue +++ b/src/views/marketing/VoucherHolders.vue @@ -459,7 +459,7 @@ const fetchDiscountList = async () => { const res = await getDiscountCodeList({ discount_type: 'coupon', page: 1, - count: 1000 + count: 10 }) if (res.data.code === 200) { discountOptions.value = res.data.data?.data || [] diff --git a/src/views/product/ProductGroup.vue b/src/views/product/ProductGroup.vue index 0fe7a93..c431752 100644 --- a/src/views/product/ProductGroup.vue +++ b/src/views/product/ProductGroup.vue @@ -1113,7 +1113,7 @@ const viewMode = ref('tree') // 查询参数 const queryParams = reactive({ page: 1, - count: 100, + count: 10, tag: undefined, level: undefined, disable: undefined, @@ -1328,7 +1328,7 @@ const loadProductsForGroup = async (groupId) => { const res = await getProductList({ good_group_id: groupId, page: 1, - count: 100, + count: 10, delete: false }) @@ -1410,7 +1410,7 @@ const toggleExpand = async (row) => { const res = await getProductGroupList({ parent_id: group.id, level: childLevel, - count: 100 + count: 10 }) if (res.data.code === 200) { const children = res.data.data.data || [] @@ -1540,7 +1540,7 @@ const selectedTagName = computed(() => { const fetchTagOptionsForSelector = async () => { tagSelectorLoading.value = true try { - const params = { page: 1, count: 100 } + const params = { page: 1, count: 10 } if (tagSelectorSearch.value) { params.key = tagSelectorSearch.value } @@ -1571,7 +1571,7 @@ const fetchTagOptionsForSelector = async () => { // 初始化获取所有标签 const fetchAllTagOptions = async () => { try { - const res = await getProductGroupTagList({ page: 1, count: 100 }) + const res = await getProductGroupTagList({ page: 1, count: 10 }) if (res.data.code === 200) { const data = res.data.data if (Array.isArray(data)) { @@ -1920,10 +1920,10 @@ const handleEditProduct = (product, parentGroupId) => { Object.assign(productForm, { id: product.id, - name: product.name, - table: product.table, - tag: product.tag, - content: product.content, + name: product.name || '', + table: product.table || '', + tag: typeof product.tag === 'string' ? product.tag : '', + content: product.content || '', cover_id: product.coverId, good_group_id: groupId, inventory_control: product.inventoryControl, @@ -1945,10 +1945,10 @@ const submitProductForm = () => { if (valid) { try { const submitData = { - name: productForm.name.trim(), - table: productForm.table.trim(), - tag: productForm.tag.trim(), - content: productForm.content.trim(), + name: (productForm.name || '').trim(), + table: (productForm.table || '').trim(), + tag: (typeof productForm.tag === 'string' ? productForm.tag : '').trim(), + content: (productForm.content || '').trim(), cover_id: productForm.cover_id, good_group_id: productForm.good_group_id, inventory_control: productForm.inventory_control, @@ -2153,7 +2153,7 @@ const submitForm = () => { // ==================== 分组标签管理 ==================== const tagQueryParams = reactive({ page: 1, - count: 100, + count: 10, key: '' }) diff --git a/src/views/product/ProductList.vue b/src/views/product/ProductList.vue index 7418ee1..93ca3c6 100644 --- a/src/views/product/ProductList.vue +++ b/src/views/product/ProductList.vue @@ -981,7 +981,7 @@ const toggleGroupExpand = async (row) => { const res = await getProductGroupList({ parent_id: row.id, level: childLevel, - count: 100 + count: 10 }) if (res.data.code === 200) { const children = res.data.data.data || [] @@ -1060,7 +1060,7 @@ const fetchProductList = async () => { const fetchGroupList = async () => { try { // 获取全部分组用于下拉列表 - const res = await getProductGroupList({ page: 1, count: 100 }) + const res = await getProductGroupList({ page: 1, count: 10 }) if (res.data.code === 200) { groupOptions.value = res.data.data.data || [] if (groupOptions.value.length === 0) { @@ -1069,7 +1069,7 @@ const fetchGroupList = async () => { } // 获取一级分组用于树形选择器 - const treeRes = await getProductGroupList({ level: 1, count: 100 }) + const treeRes = await getProductGroupList({ level: 1, count: 10 }) if (treeRes.data.code === 200) { const rootItems = treeRes.data.data.data || [] groupTreeData.value = rootItems.map(item => ({ diff --git a/src/views/product/UserGoodsList.vue b/src/views/product/UserGoodsList.vue index 2f76d1f..fd257d5 100644 --- a/src/views/product/UserGoodsList.vue +++ b/src/views/product/UserGoodsList.vue @@ -57,14 +57,14 @@ - +
选择 - 清除 + 清除
@@ -86,11 +86,37 @@
+ readonly placeholder="可选,选择后自动填入参数" style="flex:1" /> 选择 清除
+ + + +
+ + 配置 + 清除 +
+
请先选择商品
+
+ + +
+ + + {{ createForm._goodTag === '云服务器' ? '选择虚拟机' : '使用商品ID' }} + + 清除 +
+
请先选择商品
+
云服务器商品,点击选择用户虚拟机作为归属项
+
普通商品,点击将商品ID赋值为归属项
+
+ @@ -134,21 +160,99 @@
- + - + + + + +
+ + + + + + 搜索 + + +
+ + + + + + + + + + + + + + + +
+ +
+ +
+ + + +
加载参数中...
+
该商品暂无参数配置
+
+
+
+ {{ spec.name }} + 必填 +
+ + + +
+
+
生成的参数 JSON:
+ +
+
+ +
@@ -945,4 +1663,14 @@ onMounted(async () => { .tab-toolbar { display: flex; gap: 8px; align-items: center; margin: 12px 0; } .pagination-wrapper { display: flex; justify-content: flex-end; margin-top: 12px; } .selector-row { display: flex; align-items: center; width: 100%; } +.mono-text { font-family: 'Cascadia Code', Consolas, monospace; font-size: 12px; } + +/* VM 配置网格 */ +.vm-config-grid { border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; } +.config-row { display: flex; border-bottom: 1px solid #e8e8e8; } +.config-row:last-child { border-bottom: none; } +.config-cell { flex: 1; padding: 10px 14px; display: flex; flex-direction: column; gap: 4px; border-right: 1px solid #e8e8e8; } +.config-cell:last-child { border-right: none; } +.config-label { font-size: 12px; color: #86909c; line-height: 1; } +.config-value { font-size: 13px; color: #1d2129; line-height: 1.4; word-break: break-all; } diff --git a/src/views/user-vm/UserVmList.vue b/src/views/user-vm/UserVmList.vue index 4ef1089..5ca5bce 100644 --- a/src/views/user-vm/UserVmList.vue +++ b/src/views/user-vm/UserVmList.vue @@ -48,13 +48,13 @@ diff --git a/src/views/user/UserBalance.vue b/src/views/user/UserBalance.vue index 6014e6e..b9dddd9 100644 --- a/src/views/user/UserBalance.vue +++ b/src/views/user/UserBalance.vue @@ -305,7 +305,7 @@ const currentBalanceDisplay = computed(() => { // 获取用户列表(用于显示用户名) const getUserListData = async () => { try { - const res = await getUserList({ page: 1, count: 1000, key: '' }) + const res = await getUserList({ page: 1, count: 10, key: '' }) if (res.data.code === 200) { userList.value = res.data.data.data || [] } diff --git a/src/views/user/UserDetail.vue b/src/views/user/UserDetail.vue index 54ef64a..1af50b6 100644 --- a/src/views/user/UserDetail.vue +++ b/src/views/user/UserDetail.vue @@ -866,7 +866,7 @@ const handleGroupManage = () => { } const fetchUserGroupList = async () => { - const res = await getUserGroupList({ page: 1, count: 100 }) + const res = await getUserGroupList({ page: 1, count: 10 }) if (res.data.code == 200) { userGroupList.value = res.data.data.data } @@ -1276,7 +1276,7 @@ const handleTokenManage = async () => { const fetchAdminGroupList = async () => { adminGroupLoading.value = true try { - const res = await getAdminGroupList({ params: { page: 1, count: 100 } }) + const res = await getAdminGroupList({ params: { page: 1, count: 10 } }) if (res.data.code === 200) { adminGroupList.value = res.data.data?.data || res.data.data || [] } diff --git a/src/views/virtualization/BackupManage.vue b/src/views/virtualization/BackupManage.vue index 9308868..57f94a6 100644 --- a/src/views/virtualization/BackupManage.vue +++ b/src/views/virtualization/BackupManage.vue @@ -125,7 +125,7 @@ const vmOptionsLoading = ref(false) const loadVmOptions = async () => { vmOptionsLoading.value = true try { - const res = await getVmList({ service_id: serviceId.value, page: 1, page_size: 500 }) + const res = await getVmList({ service_id: serviceId.value, page: 1, page_size: 10 }) if (res?.data?.code === 200 && res?.data?.data) { const inner = res.data.data vmOptions.value = inner.vms || inner.data || inner.list || (Array.isArray(inner) ? inner : []) diff --git a/src/views/virtualization/SnapshotManage.vue b/src/views/virtualization/SnapshotManage.vue index d9f5081..1e33e33 100644 --- a/src/views/virtualization/SnapshotManage.vue +++ b/src/views/virtualization/SnapshotManage.vue @@ -125,7 +125,7 @@ const vmOptionsLoading = ref(false) const loadVmOptions = async () => { vmOptionsLoading.value = true try { - const res = await getVmList({ service_id: serviceId.value, page: 1, page_size: 500 }) + const res = await getVmList({ service_id: serviceId.value, page: 1, page_size: 10 }) if (res?.data?.code === 200 && res?.data?.data) { const inner = res.data.data vmOptions.value = inner.vms || inner.data || inner.list || (Array.isArray(inner) ? inner : []) diff --git a/src/views/virtualization/UserNetworkingManage.vue b/src/views/virtualization/UserNetworkingManage.vue index 3898e0a..2584ede 100644 --- a/src/views/virtualization/UserNetworkingManage.vue +++ b/src/views/virtualization/UserNetworkingManage.vue @@ -236,7 +236,7 @@ const getHostLabel = (hid) => { const loadHostOptions = async () => { try { - const res = await getRemoteHostList({ service_id: serviceId.value, page: 1, page_size: 200 }) + const res = await getRemoteHostList({ service_id: serviceId.value, page: 1, page_size: 10 }) const body = res?.data if (body?.code === 200 && body?.data) { const inner = body.data diff --git a/src/views/virtualization/VmDetail.vue b/src/views/virtualization/VmDetail.vue index eea7f8d..3a3c3f6 100644 --- a/src/views/virtualization/VmDetail.vue +++ b/src/views/virtualization/VmDetail.vue @@ -34,6 +34,7 @@ 救援模式 退出救援 迁移虚拟机 + 数据迁移 @@ -225,7 +226,7 @@ --> @@ -259,7 +260,7 @@
创建安全组 绑定安全组 - 刷新 + 刷新
@@ -789,8 +790,8 @@ - - + +
+ + + + 源服务的虚拟机数据将通过 rsync 传输到目标服务的宿主机上,完成后在目标宿主机上校验并恢复虚拟机。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + {{ dataMigrateProgressData.migration_id || '-' }} + {{ dataMigrateProgressData.status || '-' }} + {{ dataMigrateProgressData.progress }}% + {{ dataMigrateProgressData.message }} + + +
+ +
+ @@ -1175,10 +1236,13 @@ import { getVmList, bindSecurityGroup, unbindSecurityGroup, getSnapshotList, createSnapshot, restoreSnapshot, deleteSnapshot, getSnapshotProgress, getSnapshotCount, setSnapshotLimit, getBackupList, createBackup, restoreBackup, deleteBackup, getBackupProgress, getBackupCount, setBackupLimit, - migrateVm, getRemoteHostGroupList, getRemoteHostDetail + migrateVm, getRemoteHostGroupList, getRemoteHostDetail, + dataMigrateVm, getDataMigrateProgress, + getKvmServiceList } from '@/api/admin/kvmService' import { getUserInfo } from '@/api/admin/user' import { extractApiError } from '@/utils/kvmErrorUtil' +import { vmStatusLabel as vmStatusLabelUtil, vmStatusType as vmStatusTypeUtil, volumeStatusLabel, volumeStatusType } from '@/utils/tool' import * as echarts from 'echarts' import ImageSelectorPopup from '@/components/admin/ImageSelectorPopup.vue' import NetworkSelectorPopup from '@/components/admin/NetworkSelectorPopup.vue' @@ -1264,10 +1328,11 @@ const handleMoreCommand = (cmd) => { migrateVm: handleMigrateVm } if (actionMap[cmd]) actionMap[cmd]() + if (cmd === 'dataMigrateVm') handleDataMigrateVm() } -const vmStatusType = (s) => ({ running: 'success', ready: 'success', creating: 'warning', pending: 'info', stopped: 'danger', stop: 'danger', shutoff: 'danger', error: 'danger', paused: 'warning', reboot: 'warning', poweroff: 'info', unknown: 'info' }[s] || 'info') -const vmStatusLabel = (s) => ({ running: '运行中', ready: '就绪', creating: '创建中', pending: '等待中', stopped: '已停止', stop: '已停止', shutoff: '已关闭', error: '错误', paused: '已暂停', reboot: '重启中', poweroff: '已关机', unknown: '未知' }[s] || s || '-') +const vmStatusType = (s) => vmStatusTypeUtil(s) +const vmStatusLabel = (s) => vmStatusLabelUtil(s) const imgStatusType = (s) => ({ ready: 'success', downloading: 'warning', pending: 'info', error: 'danger' }[s] || 'info') const imgStatusLabel = (s) => ({ ready: '就绪', downloading: '下载中', pending: '等待中', error: '错误' }[s] || s || '-') @@ -1696,14 +1761,14 @@ const submitRefactorVm = async () => { // ---- 修改带宽 ---- const trafficDialogVisible = ref(false) -const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, traffic_max: 0 }) +const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficGB: 0 }) const handleUpdateTraffic = () => { if (!detail.value) return Object.assign(trafficForm, { rx_bandwidth: detail.value.rx_bandwidth || 0, tx_bandwidth: detail.value.tx_bandwidth || 0, - traffic_max: detail.value.traffic_max || 0 + _trafficGB: ((detail.value.traffic_max || 0) / 1024).toFixed(2) * 1 }) trafficDialogVisible.value = true } @@ -1716,7 +1781,7 @@ const submitUpdateTraffic = async () => { fd.append('vm_id', vmId.value) fd.append('rx_bandwidth', trafficForm.rx_bandwidth) fd.append('tx_bandwidth', trafficForm.tx_bandwidth) - if (trafficForm.traffic_max) fd.append('traffic_max', trafficForm.traffic_max) + if (trafficForm._trafficGB) fd.append('traffic_max', Math.round(trafficForm._trafficGB * 1024)) // GB → Mb const res = await updateVmTraffic(fd) if (res?.data?.code === 200) { ElMessage.success('带宽修改成功'); trafficDialogVisible.value = false; loadDetail() } else ElMessage.error(extractApiError(res?.data, '修改失败')) @@ -1770,6 +1835,78 @@ const submitMigrateVm = async () => { } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '迁移失败')) } finally { actionLoading.value = false } } +// ---- 数据迁移 ---- +const dataMigrateVisible = ref(false) +const dataMigrateLoading = ref(false) +const dataMigrateHostsLoading = ref(false) +const dataMigrateServiceOptions = ref([]) +const dataMigrateHostOptions = ref([]) +const dataMigrateProgressVisible = ref(false) +const dataMigrateProgressLoading = ref(false) +const dataMigrateProgressData = ref(null) +const dataMigrationId = ref('') +const dataMigrateForm = reactive({ target_service_id: null, target_host_id: null, ipv4_num: 0, ipv6_num: 0 }) + +const handleDataMigrateVm = async () => { + Object.assign(dataMigrateForm, { target_service_id: null, target_host_id: null, ipv4_num: 0, ipv6_num: 0 }) + dataMigrateVisible.value = true + dataMigrateLoading.value = true + try { + const res = await getKvmServiceList({ page: 1, count: 10 }) + if (res?.data?.code === 200 && res?.data?.data) { + const inner = res.data.data + const raw = inner.data || inner.list || (Array.isArray(inner) ? inner : []) + dataMigrateServiceOptions.value = raw.map(s => ({ id: s.id ?? s.Id, name: s.name ?? s.Name })) + } + } catch { /* */ } finally { dataMigrateLoading.value = false } +} + +const loadDataMigrateHosts = async () => { + if (!dataMigrateForm.target_service_id) return + dataMigrateHostsLoading.value = true + try { + const res = await getRemoteHostList({ service_id: dataMigrateForm.target_service_id, page: 1, page_size: 10 }) + if (res?.data?.code === 200 && res?.data?.data) { + const inner = res.data.data + dataMigrateHostOptions.value = Array.isArray(inner) ? inner : (inner.hosts || inner.data || []) + } + } catch { /* */ } finally { dataMigrateHostsLoading.value = false } +} + +const submitDataMigrate = async () => { + if (!dataMigrateForm.target_service_id) { ElMessage.warning('请选择目标主控服务'); return } + if (!dataMigrateForm.target_host_id) { ElMessage.warning('请选择目标宿主机'); return } + actionLoading.value = true + try { + const fd = new FormData() + fd.append('source_service_id', serviceId.value) + fd.append('source_vm_id', vmId.value) + fd.append('target_service_id', dataMigrateForm.target_service_id) + fd.append('target_host_id', dataMigrateForm.target_host_id) + if (dataMigrateForm.ipv4_num) fd.append('ipv4_num', dataMigrateForm.ipv4_num) + if (dataMigrateForm.ipv6_num) fd.append('ipv6_num', dataMigrateForm.ipv6_num) + const res = await dataMigrateVm(fd) + if (res?.data?.code === 200) { + ElMessage.success('数据迁移已发起') + dataMigrationId.value = res.data.data?.migration_id || '' + dataMigrateVisible.value = false + // 自动打开进度弹窗 + dataMigrateProgressData.value = res.data.data + dataMigrateProgressVisible.value = true + } else ElMessage.error(extractApiError(res?.data, '发起迁移失败')) + } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '发起迁移失败')) } finally { actionLoading.value = false } +} + +const loadDataMigrateProgress = async () => { + if (!dataMigrationId.value) return + dataMigrateProgressLoading.value = true + try { + const res = await getDataMigrateProgress({ migration_id: dataMigrationId.value, service_id: serviceId.value }) + if (res?.data?.code === 200) dataMigrateProgressData.value = res.data.data + else ElMessage.warning('暂无进度信息') + } catch { /* */ } finally { dataMigrateProgressLoading.value = false } +} + // ---- VNC 连接 ---- const vncDialogVisible = ref(false) const vncNodeId = ref(null) @@ -2048,7 +2185,7 @@ const handleVolUnmount = (row) => { } const loadVmListOptions = async () => { try { - const res = await getVmList({ service_id: serviceId.value, page: 1, page_size: 200 }) + const res = await getVmList({ service_id: serviceId.value, page: 1, page_size: 10 }) if (res?.data?.code === 200 && res?.data?.data) { const inner = res.data.data vmListOptions.value = inner.vms || inner.data || (Array.isArray(inner) ? inner : []) @@ -2790,6 +2927,22 @@ const triggerTabLoad = (tab) => { if (tab === 'snapshot') { loadSnapshots(); loadSnapshotQuota() } if (tab === 'backup') { loadBackups(); loadBackupQuota() } if (tab === 'userNetworking') loadVmNetworkingList() + if (tab === 'security') loadSgLockInfo() +} + +// 请求安全组详情补充 lock 字段 +const loadSgLockInfo = async () => { + const groups = [vmPortGroup.value, vmOutPortGroup.value].filter(Boolean) + for (const sg of groups) { + try { + const res = await getSecurityGroupDetail({ service_id: serviceId.value, id: sg.id }) + if (res?.data?.code === 200 && res?.data?.data) { + const d = res.data.data.group || res.data.data.data || res.data.data + if (vmPortGroup.value?.id === sg.id) vmPortGroup.value = { ...vmPortGroup.value, lock: d.lock ?? sg.lock } + if (vmOutPortGroup.value?.id === sg.id) vmOutPortGroup.value = { ...vmOutPortGroup.value, lock: d.lock ?? sg.lock } + } + } catch { /* */ } + } } watch(vmId, () => { if (isPageActive) initPage() }) diff --git a/src/views/virtualization/VolumeDetail.vue b/src/views/virtualization/VolumeDetail.vue index 38dfbd5..e627a82 100644 --- a/src/views/virtualization/VolumeDetail.vue +++ b/src/views/virtualization/VolumeDetail.vue @@ -143,7 +143,7 @@ const formatTimestamp = (ts) => { const loadHostOptions = async () => { try { - const res = await getRemoteHostList({ service_id: serviceId.value, page: 1, page_size: 100 }) + const res = await getRemoteHostList({ service_id: serviceId.value, page: 1, page_size: 10 }) const body = res?.data if (body?.code === 200 && body?.data) { const inner = body.data diff --git a/审查代码提示词.MD b/审查代码提示词.MD new file mode 100644 index 0000000..30cbe6f --- /dev/null +++ b/审查代码提示词.MD @@ -0,0 +1,64 @@ +要让 Cursor 能够高效地完成这项复杂任务,你需要将“需求”转化为“结构化指令”。不要一次性丢给它所有任务,建议按照**“调研-分析-开发-优化”**的逻辑分阶段进行。 + +以下是为你设计的 **Prompt 模板**,你可以根据实际情况稍作修改: + +--- + +### 第一阶段:现状分析与评估(核心提示词) + +**使用场景:** 将 `openapi.json` 拖入 Cursor,打开你的项目根目录,发送以下 Prompt: + +> **Prompt:** +> 我现在正在对接用户商品管理模块,API 定义在 `@ApiServer-web-admin_dashboard_pc/默认模块.openapi.json` 中。 +> +> 请你执行以下任务: +> 1. **接口完整性对比:** 分析该 OpenAPI 文件中所有以 `/product` 或 `user` 开头的接口。对比我当前项目中 `src/api/product.ts`(或对应目录)的实现,列出缺少实现的接口、参数定义不一致的接口。 +> 2. **逻辑可行性判断:** 检查这些接口的请求方式、入参结构和响应字段,判断是否存在潜在的逻辑错误(如字段类型不匹配、缺失必要的分页参数等)。 +> 3. **输出报告:** 请整理出一份表格,列出:接口路径 | 功能描述 | 是否已实现 | 潜在风险/待修复点。 +> +> 请先完成这一步,不要急于写代码。 + +--- + +### 第二阶段:功能开发与组件化(架构层) + +**使用场景:** 在第一阶段确认无误后,让 Cursor 介入代码实现。 + +> **Prompt:** +> 基于上一步的分析结果,我们需要进行代码落地。请遵循以下工程化标准: +> 1. **请求实现:** 按照我现有项目的请求风格(例如 `axios` + `ts-interface`),补全缺失的接口请求函数。 +> 2. **组件化拆分:** 在实现业务页面时,请评估哪些逻辑可以抽离为公共组件(例如:商品详情预览框、批量操作栏、规格选择器)。如果某个功能在多个页面有重复逻辑,请将其提取为独立的 Component,并说明该组件的 Props 定义。 +> 3. **嵌套与快捷入口:** 针对“商品管理”模块,请思考是否存在需要嵌套展示的功能(如:点击列表行展开详细信息,或弹窗式管理)。如果是,请直接使用 `ant-design` (或你使用的框架) 的组件来实现这种交互,并保证良好的用户体验。 + +--- + +### 第三阶段:样式与交互优化(视觉层) + +**使用场景:** 页面雏形出来后,让 Cursor 优化细节。 + +> **Prompt:** +> 现在页面已经实现了基本功能,请重点优化 UX/UI: +> 1. **布局优化:** 请检查当前的表格布局,确保字段对齐合理、间距符合 Material Design 或 Ant Design 标准。 +> 2. **交互美化:** 增加必要的 Loading 状态、空状态(Empty Data)、操作反馈(Toast/Message)。 +> 3. **响应式评估:** 检查该 Dashboard 页面在不同分辨率下的表现,是否需要将部分复杂的筛选表单折叠到“高级筛选”面板中,以保持界面整洁。 +> 4. **视觉建议:** 请评估当前样式是否过于简单,建议增加一些层级感(例如卡片式布局、阴影、不同层级的按钮样式)。 + +--- + +### 给 Cursor 的“提效秘诀”(必读): + +为了让 Cursor 表现得更好,请务必配合以下操作: + +1. **利用 `@Codebase`:** 在 Prompt 中提到 `@Codebase`,让 Cursor 全局检索你的代码规范(例如:你以前写的 `api` 请求是怎么写的,`component` 是怎么封装的),这样它生成的代码才不会“违和”。 +2. **小步快跑:** 如果 API 接口很多(比如超过 10 个),**千万不要**让它一次性全写完。一次让它写 2-3 个接口或一个组件,改完一个确认一个。 +3. **强制提供 Interface:** 在提示词中加上一句:“请确保所有接口请求和响应都定义了对应的 `TypeScript Interface`,严禁使用 `any` 类型。” +4. **指定参考文件:** 如果你有已经写好的“完美的页面”,在对话框中输入 `#` 引用该文件,告诉 Cursor:“请参照 `src/pages/TemplatePage.tsx` 的代码规范和交互逻辑来实现商品管理页面。” + +### 总结你的检查清单(作为你的最终验收标准): +* [ ] OpenAPI 定义的字段是否全覆盖? +* [ ] 是否每个接口都写了 try-catch 或统一的错误处理? +* [ ] 是否有复用性强的组件?(避免重复代码) +* [ ] 是否有交互反馈?(Loading/Success Tips) +* [ ] 样式是否统一?(使用了全局定义的颜色/间距变量) + +如果你直接丢给它一个庞大的任务,Cursor 往往会产生“幻觉”或者代码质量下降,**分步骤引导**是使用 AI 开发复杂后台的最佳实践。 \ No newline at end of file diff --git a/默认模块.openapi.json b/默认模块.openapi.json index b62d1a0..00537ea 100644 --- a/默认模块.openapi.json +++ b/默认模块.openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "默认模块", - "description": "", + "description": "新增跨服务虚拟机数据迁移接口,支持通过 rsync + SSH tunneluser 在两个 kvm-virtctl 之间进行虚拟机数据传输和恢复。", "version": "1.0.0" }, "tags": [ @@ -35,6 +35,9 @@ }, { "name": "admin-用户虚拟机" + }, + { + "name": "UserGoods" } ], "paths": { @@ -4060,6 +4063,383 @@ }, "security": [] } + }, + "/api/v1/admin/good/user_goods/list": { + "get": { + "summary": "获取用户商品列表", + "deprecated": false, + "description": "", + "tags": [ + "UserGoods" + ], + "parameters": [ + { + "name": "page", + "in": "query", + "description": "页码", + "required": false, + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "count", + "in": "query", + "description": "每页数量", + "required": false, + "schema": { + "type": "integer", + "default": 10 + } + }, + { + "name": "user_id", + "in": "query", + "description": "按用户 ID 筛选", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "good_id", + "in": "query", + "description": "按商品 ID 筛选", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "key", + "in": "query", + "description": "关键词搜索(按商品名称模糊匹配)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Authorization", + "in": "header", + "description": "", + "example": "Bearer {{token}}", + "schema": { + "type": "string", + "default": "Bearer {{token}}" + } + } + ], + "responses": { + "200": { + "description": "成功", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserGoods" + } + }, + "all_count": { + "type": "integer", + "description": "总数" + } + } + } + } + }, + "headers": {} + } + }, + "security": [] + } + }, + "/api/v1/admin/good/user_goods/detail": { + "get": { + "summary": "获取用户商品详情", + "deprecated": false, + "description": "", + "tags": [ + "UserGoods" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "description": "用户商品 ID", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "Authorization", + "in": "header", + "description": "", + "example": "Bearer {{token}}", + "schema": { + "type": "string", + "default": "Bearer {{token}}" + } + } + ], + "responses": { + "200": { + "description": "成功", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/UserGoods" + } + } + } + } + }, + "headers": {} + } + }, + "security": [] + } + }, + "/api/v1/admin/good/user_goods/create": { + "post": { + "summary": "新增用户商品", + "deprecated": false, + "description": "", + "tags": [ + "UserGoods" + ], + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "", + "example": "Bearer {{token}}", + "schema": { + "type": "string", + "default": "Bearer {{token}}" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "good_id": { + "type": "integer", + "description": "商品 ID", + "example": 0 + }, + "user_id": { + "type": "integer", + "description": "用户 ID", + "example": 0 + }, + "order_id": { + "type": "integer", + "description": "订单 ID", + "example": 0 + }, + "good_plan_id": { + "type": "integer", + "description": "商品套餐 ID", + "example": 0 + }, + "item_id": { + "type": "integer", + "description": "归属项 ID", + "example": 0 + }, + "renew_price": { + "type": "integer", + "description": "续费价格", + "example": 0 + }, + "base_price": { + "type": "integer", + "description": "基础价格", + "example": 0 + }, + "note": { + "type": "string", + "description": "备注说明", + "example": "" + }, + "expire_time": { + "type": "string", + "description": "到期时间,格式:2006-01-02 15:04:05", + "example": "" + }, + "order_args": { + "description": "订单参数 json", + "example": "", + "type": "string" + } + }, + "required": [ + "good_id", + "user_id" + ] + }, + "examples": {} + } + }, + "required": true + }, + "responses": { + "200": { + "description": "创建成功", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/UserGoods" + } + } + } + } + }, + "headers": {} + } + }, + "security": [] + } + }, + "/api/v1/admin/good/user_goods/update": { + "post": { + "summary": "修改用户商品", + "deprecated": false, + "description": "", + "tags": [ + "UserGoods" + ], + "parameters": [ + { + "name": "Authorization", + "in": "header", + "description": "", + "example": "Bearer {{token}}", + "schema": { + "type": "string", + "default": "Bearer {{token}}" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "用户商品 ID", + "example": 0 + }, + "note": { + "type": "string", + "description": "备注说明", + "example": "" + }, + "renew_price": { + "type": "integer", + "description": "续费价格", + "example": 0 + }, + "base_price": { + "type": "integer", + "description": "基础价格", + "example": 0 + }, + "expire_time": { + "type": "string", + "description": "到期时间,格式:2006-01-02 15:04:05", + "example": "" + }, + "item_id": { + "type": "integer", + "description": "归属项 ID", + "example": 0 + } + }, + "required": [ + "id" + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "修改成功", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/UserGoods" + } + } + } + } + }, + "headers": {} + } + }, + "security": [] + } + }, + "/api/v1/admin/good/user_goods/delete": { + "delete": { + "summary": "删除用户商品", + "deprecated": false, + "description": "", + "tags": [ + "UserGoods" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "description": "用户商品 ID", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "Authorization", + "in": "header", + "description": "", + "example": "Bearer {{token}}", + "schema": { + "type": "string", + "default": "Bearer {{token}}" + } + } + ], + "responses": { + "200": { + "description": "删除成功", + "headers": {} + } + }, + "security": [] + } } }, "components": {