fix: 商品、订单部分相关代码生成,下一步先把商品搞完再搞订单
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled

This commit is contained in:
2025-05-20 16:57:17 +08:00
parent 84caccaf05
commit 7a52227003
91 changed files with 5455 additions and 375 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref, watch, onMounted, nextTick } from 'vue';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { useVModel } from '@vueuse/core';
import { Modal, Upload } from 'ant-design-vue';
@@ -92,7 +92,7 @@ watch(
// console.log('[UploadImage] 父组件传入新的 modelValue:', newVal);
syncModelToFileList(newVal);
},
{ deep: true, immediate: true }
{ deep: true, immediate: true },
);
const customRequest = async (e: any) => {
@@ -137,7 +137,9 @@ const handlePreview = async (file) => {
previewImage.value = file.response?.url || file.url || file.preview;
previewVisible.value = true;
previewTitle.value =
file.name || file.url?.slice(Math.max(0, file.url.lastIndexOf('/') + 1)) || '预览图片';
file.name ||
file.url?.slice(Math.max(0, file.url.lastIndexOf('/') + 1)) ||
'预览图片';
};
// 计算是否还能上传更多图片

View File

@@ -1,32 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
const list = ref('');
const previewTitle = ref('二维码');
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
const { values, title } = modalApi.getData<Record<string, any>>();
if (values) {
list.value = values;
previewTitle.value = title;
}
}
},
});
</script>
<template>
<Modal title="提示框" class="w-[50%]">
您的处方中有以下药品存在{{ previewTitle }}
</Modal>
</template>

View File

@@ -1,169 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Avatar, Button, Image } from 'ant-design-vue';
import html2canvas from 'html2canvas';
import { getDoctorInfoByStoreApi } from '#/views/doctor/doctor/api';
const qrCodeUrl = ref('');
const htmlToImage = ref('');
const previewAddress = ref('');
const goodAt = ref('');
const doctorInfo = ref();
const doctorId = ref(0);
const storeId = ref(0);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
footer: false,
showCancelButton: true,
showConfirmButton: true,
confirmText: '保存医生卡片图片',
cancelText: '保存医生二维码图片',
draggable: true,
onCancel() {
doctorInfo.value = null;
modalApi.close();
},
onConfirm: async () => {
doctorInfo.value = null;
modalApi.close();
},
onOpenChange(isOpen: boolean) {
modalApi.setState({
loading: true,
});
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
if (values.doctor_id !== undefined) {
doctorId.value = values.doctor_id;
}
if (values.store_id !== undefined) {
storeId.value = values.store_id;
}
getDoctorInfoByStore();
}
}
},
});
const getDoctorInfoByStore = () => {
getDoctorInfoByStoreApi({
doctor_id: doctorId.value,
store_id: storeId.value,
}).then((res) => {
doctorInfo.value = res;
goodAt.value = res.doctor.good_at;
previewAddress.value =
res.store.province.name + res.store.city.name + res.store.position;
modalApi.setState({
loading: false,
});
});
};
/**
* 下载二维码
* @param name
* @param isQr
*/
const downloadQRCode = async (name: string, isQr = false) => {
const element = isQr ? qrCodeUrl.value : htmlToImage.value;
html2canvas(element, {
useCORS: true,
allowTaint: true,
logging: false,
}).then((canvas) => {
// 创建a标签下载
const link = document.createElement('a'); // 创建a标签
link.href = canvas.toDataURL(); // 是canvas对象的一种方法用于将canvas对象转换为base64位编码
link.setAttribute('download', `${doctorInfo.value.doctor.name}${name}.png`); // 利用了a标签的download 来下载 canvas图片
link.style.display = 'none'; // 将图片隐藏起来
document.body.append(link); // 插入到其中
link.click();
});
};
</script>
<template>
<Modal v-if="doctorInfo" title="医生二维码" class="w-[50%]">
<template #footer>
<Button type="primary" @click="downloadQRCode('二维码卡片下载')">
二维码卡片下载
</Button>
<Button type="primary" @click="downloadQRCode('二维码下载', true)">
二维码下载
</Button>
</template>
<div class="qrBox" style="text-align: center">
<div ref="htmlToImage" class="qrModal">
<div id="qrcode" ref="qrCodeUrl" class="qrcode">
<Avatar :src="doctorInfo.doctor.avatar" />
<div class="qrTitle">
{{ doctorInfo.doctor.name }} {{ doctorInfo.doctor.title.name }}
</div>
<div class="qrAddress">
<span class="addressTitle">诊所地址</span>{{ previewAddress }}
</div>
<Image
:preview="false"
:src="doctorInfo.qr_code"
class="mt-3"
height="30"
width="30"
/>
<h3 class="border-l-4 border-blue-500 pl-3 text-lg font-semibold">
擅长领域
</h3>
<div
class="prose max-w-none whitespace-pre-wrap"
style="text-indent: 2em"
>
{{ doctorInfo.doctor.good_at }}
</div>
</div>
</div>
</div>
</Modal>
</template>
<style lang="scss" scoped>
.qrModal {
padding: 20px 30px;
// 宽度适应文本长度
margin: 0 auto;
text-align: center;
background: radial-gradient(
circle at center,
rgb(173 216 230 / 80%),
rgb(135 206 235 / 80%),
rgb(100 149 237 / 80%)
);
.qrTitle {
color: #333;
}
.qrAddress {
margin-top: 10px;
font-size: 12px;
font-weight: bold;
color: #333;
}
.addressTitle {
color: #3a8ee6;
}
.qrcode {
width: fit-content;
padding: 20px 30px;
//width: 50%;
margin: 0 auto;
text-align: center;
background-color: #fff;
border-radius: 20px;
}
}
</style>

View File

@@ -0,0 +1,479 @@
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Tag } from 'ant-design-vue';
import { getProductInfoApi } from '#/views/my-gen/product/api';
// 定义商品类型
interface Product {
id?: string;
title?: string;
introduction?: string;
price?: number;
originalPrice?: number;
discount?: number;
cover?: string;
images?: string[];
isNew?: boolean;
isFavorite?: boolean;
viewCount?: number;
favoriteCount?: number;
soldCount?: number;
returnCount?: number;
[key: string]: any;
}
const props = defineProps({
// 可选的默认商品数据
defaultProduct: {
type: Object as () => Product,
default: () => ({}),
},
});
const emits = defineEmits(['addToCart', 'buyNow', 'favoriteChanged']);
const product = ref<Product>(props.defaultProduct || {});
const previewTitle = ref('商品详情');
// 轮播图状态
const currentSlideIndex = ref(0);
let autoplayTimer: null | ReturnType<typeof setInterval> = null;
// 缩略图容器引用
const thumbnailContainer = ref<HTMLDivElement | null>(null);
// 缩略图滚动状态
const canScrollLeft = ref(false);
const canScrollRight = ref(false);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
getProductInfoApi(values).then((res: Product) => {
product.value = res;
previewTitle.value = `${res.title || '商品'}详情`;
currentSlideIndex.value = 0; // 重置轮播图索引
startAutoplay(); // 启动自动轮播
// 等待DOM更新后滚动到第一个缩略图
nextTick(() => {
updateScrollState();
scrollToActiveThumbnail();
});
});
}
} else {
stopAutoplay(); // 关闭时停止自动轮播
}
},
});
// 监听外部props变化更新商品数据
watch(
() => props.defaultProduct,
(newVal: Product) => {
if (newVal && JSON.stringify(newVal) !== JSON.stringify(product.value)) {
product.value = { ...newVal };
currentSlideIndex.value = 0; // 重置轮播图索引
// 等待DOM更新后滚动到第一个缩略图
nextTick(() => {
updateScrollState();
scrollToActiveThumbnail();
});
}
},
{ deep: true },
);
// 轮播图控制方法
const nextSlide = () => {
const imageCount = product.value.images?.length || 0;
if (imageCount > 0) {
currentSlideIndex.value = (currentSlideIndex.value + 1) % imageCount;
resetAutoplay();
}
};
const prevSlide = () => {
const imageCount = product.value.images?.length || 0;
if (imageCount > 0) {
currentSlideIndex.value =
(currentSlideIndex.value - 1 + imageCount) % imageCount;
resetAutoplay();
}
};
// 自动轮播控制
const startAutoplay = () => {
stopAutoplay();
const imageCount = product.value.images?.length || 0;
if (imageCount > 1) {
autoplayTimer = setInterval(() => {
nextSlide();
}, 5000);
}
};
const stopAutoplay = () => {
if (autoplayTimer) {
clearInterval(autoplayTimer);
autoplayTimer = null;
}
};
const resetAutoplay = () => {
stopAutoplay();
startAutoplay();
};
// 图片加载错误处理
const handleImageError = (e: Event) => {
(e.target as HTMLImageElement).src =
'https://picsum.photos/800/600?random=error';
};
// 缩略图滚动控制
const scrollThumbnails = (direction: number) => {
if (!thumbnailContainer.value) return;
const container = thumbnailContainer.value;
const scrollAmount = container.clientWidth * 0.7; // 每次滚动70%的容器宽度
container.scrollBy({
left: direction * scrollAmount,
behavior: 'smooth',
});
// 更新滚动状态
setTimeout(updateScrollState, 300);
};
// 滚动到当前选中的缩略图
const scrollToActiveThumbnail = () => {
if (
!thumbnailContainer.value ||
!product.value.images ||
product.value.images.length === 0
)
return;
const container = thumbnailContainer.value;
const thumbnails = container.querySelectorAll('img');
const activeThumbnail = thumbnails[currentSlideIndex.value];
if (activeThumbnail) {
// 计算将当前缩略图居中所需的滚动位置
const thumbnailRect = activeThumbnail.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const scrollPosition =
activeThumbnail.offsetLeft -
(container.clientWidth - thumbnailRect.width) / 2;
// 平滑滚动到计算的位置
container.scrollTo({
left: scrollPosition,
behavior: 'smooth',
});
// 更新滚动状态
setTimeout(updateScrollState, 300);
}
};
// 更新滚动按钮状态
const updateScrollState = () => {
if (!thumbnailContainer.value) return;
const container = thumbnailContainer.value;
canScrollLeft.value = container.scrollLeft > 10;
canScrollRight.value =
container.scrollLeft < container.scrollWidth - container.clientWidth - 10;
};
// 监听当前图片索引变化,滚动到对应缩略图
watch(currentSlideIndex, () => {
scrollToActiveThumbnail();
});
// 生命周期钩子
onMounted(() => {
startAutoplay();
// 监听容器滚动事件,更新滚动按钮状态
if (thumbnailContainer.value) {
thumbnailContainer.value.addEventListener('scroll', updateScrollState);
}
});
onUnmounted(() => {
stopAutoplay();
// 移除滚动事件监听器
if (thumbnailContainer.value) {
thumbnailContainer.value.removeEventListener('scroll', updateScrollState);
}
});
</script>
<template>
<Modal :title="previewTitle" class="w-[50%]">
<div class="p-6">
<div class="grid grid-cols-1 gap-8 lg:grid-cols-5">
<!-- 图片轮播区域内联实现 -->
<div class="space-y-4 lg:col-span-2">
<div
class="carousel-shadow group relative overflow-hidden rounded-xl"
>
<!-- 主图轮播 -->
<div
class="flex transition-transform duration-500 ease-in-out"
:style="{ transform: `translateX(-${currentSlideIndex * 100}%)` }"
>
<img
v-for="(img, index) in product.images || []"
:key="index"
:src="img"
alt="商品图片"
class="h-auto w-full object-cover"
height="600"
@error="handleImageError"
/>
</div>
<!-- 轮播控制器 -->
<button
@click="prevSlide"
class="text-primary absolute left-4 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-white/80 opacity-70 shadow-md transition-all duration-300 hover:bg-white group-hover:opacity-100"
>
<span class="icon-[icon-park-outline--left]"></span>
</button>
<button
@click="nextSlide"
class="text-primary absolute right-4 top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full bg-white/80 opacity-70 shadow-md transition-all duration-300 hover:bg-white group-hover:opacity-100"
>
<span class="icon-[icon-park-outline--right]"></span>
</button>
<!-- 指示器 -->
<div
class="absolute bottom-4 left-1/2 flex -translate-x-1/2 space-x-2"
>
<button
v-for="(img, index) in product.images || []"
:key="index"
@click="currentSlideIndex = index"
class="h-3 w-3 rounded-full transition-all duration-300"
:class="{
'bg-primary': index === currentSlideIndex,
'bg-white/50 hover:bg-white/80': index !== currentSlideIndex,
}"
></button>
</div>
<!-- 标签和计数器 -->
<div class="absolute left-4 top-4 z-10" v-if="product.isNew">
<span
class="bg-secondary rounded-full px-3 py-1 text-sm font-medium text-white shadow-md"
>新品</span
>
</div>
<div
class="absolute right-4 top-4 rounded bg-black/50 px-2 py-1 text-xs font-medium text-white shadow-md"
>
{{ currentSlideIndex + 1 }} /
{{ (product.images && product.images.length) || 0 }}
</div>
</div>
<!-- 缩略图区域 -->
<div class="relative">
<!-- 左侧滑动按钮 -->
<button
@click="scrollThumbnails(-1)"
:disabled="!canScrollLeft"
class="text-primary absolute left-0 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full bg-white/90 opacity-80 shadow-md transition-all duration-300 hover:bg-white"
:class="{ 'cursor-not-allowed opacity-40': !canScrollLeft }"
>
<span class="icon-[icon-park-outline--left]"></span>
</button>
<!-- 缩略图容器 -->
<div
ref="thumbnailContainer"
class="scrollbar-hide flex space-x-3 overflow-x-auto pb-2 pl-10 pr-10"
>
<img
v-for="(img, index) in product.images || []"
:key="index"
:src="img"
alt="商品缩略图"
@click="currentSlideIndex = index"
class="thumbnail-shadow h-20 w-20 cursor-pointer rounded-lg border-2 object-cover transition-all duration-300 sm:h-24 sm:w-24"
:class="{
'border-primary ring-primary/30 ring-2':
index === currentSlideIndex,
'hover:border-primary/50 border-transparent':
index !== currentSlideIndex,
}"
@error="handleImageError"
/>
</div>
<!-- 右侧滑动按钮 -->
<button
@click="scrollThumbnails(1)"
:disabled="!canScrollRight"
class="text-primary absolute right-0 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full bg-white/90 opacity-80 shadow-md transition-all duration-300 hover:bg-white"
:class="{ 'cursor-not-allowed opacity-40': !canScrollRight }"
>
<span class="icon-[icon-park-outline--right]"></span>
</button>
</div>
</div>
<!-- 商品信息区域保持不变 -->
<div class="lg:col-span-3">
<!-- 保持原有内容不变 -->
<div class="space-y-6">
<div>
<h1
class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold leading-tight"
>
{{ product.title }}
</h1>
<p class="mt-2 text-neutral-500">{{ product.introduction }}</p>
</div>
<!-- 价格区域 -->
<div class="rounded-lg p-4">
<div class="mb-2 flex items-end">
<span class="text-3xl font-bold">¥{{ product.price }}</span>
<span
v-if="product.originalPrice"
class="ml-3 text-lg line-through"
>¥{{ product.originalPrice || 0 }}</span>
<span
v-if="product.discount"
class="bg-secondary/10 text-secondary ml-2 rounded px-2 py-1 text-sm"
>
¥{{ product.discount }}
</span>
</div>
<div class="flex flex-wrap gap-x-6 gap-y-2 text-sm">
<div class="flex items-center">
<i class="fa fa-eye mr-1"></i>
<span>浏览: {{ product.viewCount || 0 }}</span>
</div>
<div class="flex items-center">
<i class="fa fa-heart mr-1"></i>
<span>收藏: {{ product.favoriteCount || 0 }}</span>
</div>
<div class="flex items-center">
<i class="fa fa-shopping-bag mr-1"></i>
<span>已售: {{ product.soldCount || 0 }}</span>
</div>
<div class="flex items-center">
<i class="fa fa-exchange mr-1"></i>
<span>退货: {{ product.returnCount || 0 }}</span>
</div>
</div>
</div>
<!-- 商品特性 -->
<div class="space-y-4">
<div class="flex items-start">
<div class="mt-1 w-16 flex-shrink-0 text-neutral-500">配送</div>
<div class="ml-4">
<div class="flex items-center">
<span class="text-neutral-800">全国大部分地区包邮</span>
<span
class="text-primary ml-2 cursor-pointer hover:underline"
>配送查询</span
>
</div>
<p class="mt-1 text-sm text-neutral-500">预计2-5天送达</p>
</div>
</div>
<div class="flex items-start">
<div class="mt-1 w-16 flex-shrink-0 text-neutral-500">服务</div>
<div class="ml-4 flex flex-wrap gap-4">
<div
v-for="item in product.labels"
:key="item.id"
class="flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="#059669"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
color="#059669"
>
<path
d="M18.99 19H19m-.01 0c-.622.617-1.75.464-2.542.464c-.972 0-1.44.19-2.133.883C13.725 20.937 12.934 22 12 22s-1.725-1.063-2.315-1.653c-.694-.693-1.162-.883-2.133-.883c-.791 0-1.92.154-2.543-.464c-.627-.622-.473-1.756-.473-2.552c0-1.007-.22-1.47-.937-2.186C2.533 13.196 2 12.662 2 12s.533-1.196 1.6-2.262c.64-.64.936-1.274.936-2.186c0-.791-.154-1.92.464-2.543c.622-.627 1.756-.473 2.552-.473c.912 0 1.546-.297 2.186-.937C10.804 2.533 11.338 2 12 2s1.196.533 2.262 1.6c.64.64 1.274.936 2.186.936c.791 0 1.92-.154 2.543.464c.627.622.473 1.756.473 2.552c0 1.007.22 1.47.937 2.186C21.467 10.804 22 11.338 22 12s-.533 1.196-1.6 2.262c-.716.717-.936 1.18-.936 2.186c0 .796.154 1.93-.473 2.552"
/>
<path
d="M9 12.893s1.2.652 1.8 1.607c0 0 1.8-3.75 4.2-5"
/>
</g>
</svg>
<Tag color="#7166f0">{{ item.name }}</Tag>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Modal>
</template>
<style scoped>
.carousel-shadow {
box-shadow:
0 10px 25px -5px rgba(0, 0, 0, 0.1),
0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
.thumbnail-shadow {
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.opacity-40 {
opacity: 0.4;
}
.cursor-not-allowed {
cursor: not-allowed;
}
</style>

View File

@@ -1,135 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button, Image } from 'ant-design-vue';
import html2canvas from 'html2canvas';
const qrCodeUrl = ref('');
const htmlToImage = ref('');
const url = ref('');
const previewTitle = ref('二维码');
const previewAddress = ref('');
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
footer: false,
header: false,
showCancelButton: true,
showConfirmButton: true,
confirmText: '保存诊所卡片图片',
cancelText: '保存诊所二维码图片',
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
const { values, title, address } =
modalApi.getData<Record<string, any>>();
if (values) {
url.value = values;
qrCodeUrl.value = values;
previewTitle.value = title;
previewAddress.value = address;
}
}
},
});
/**
* 下载二维码
* @param name
* @param isQr
*/
const downloadQRCode = async (name: string, isQr = false) => {
const element = isQr ? qrCodeUrl.value : htmlToImage.value;
html2canvas(element, {
useCORS: true,
allowTaint: true,
logging: false,
}).then((canvas) => {
// 创建a标签下载
const link = document.createElement('a'); // 创建a标签
link.href = canvas.toDataURL(); // 是canvas对象的一种方法用于将canvas对象转换为base64位编码
link.setAttribute('download', `${previewTitle.value}${name}.png`); // 利用了a标签的download 来下载 canvas图片
link.style.display = 'none'; // 将图片隐藏起来
document.body.append(link); // 插入到其中
link.click();
});
};
</script>
<template>
<Modal :title="`${previewTitle}二维码`" class="w-[50%]">
<div class="qrBox" style="text-align: center">
<div ref="htmlToImage" class="qrModal">
<div id="qrcode" ref="qrCodeUrl" class="qrcode">
<div class="qrTitle">{{ previewTitle }}</div>
<Image :preview="false" :src="url" height="30" width="30" />
<div class="qrAddress">
<span class="addressTitle">诊所地址</span>{{ previewAddress }}
</div>
</div>
</div>
<Button
style="margin: 20px auto"
type="primary"
@click="downloadQRCode('二维码卡片下载')"
>
二维码卡片下载
</Button>
<Button
style="margin: 20px auto 20px 20px"
type="primary"
@click="downloadQRCode('二维码下载', true)"
>
二维码下载
</Button>
</div>
</Modal>
</template>
<style lang="scss" scoped>
.qrModal {
padding: 20px 30px;
// 宽度适应文本长度
margin: 0 auto;
text-align: center;
background: radial-gradient(
circle at center,
rgb(173 216 230 / 80%),
rgb(135 206 235 / 80%),
rgb(100 149 237 / 80%)
);
.qrTitle {
margin-bottom: 30px;
font-size: 20px;
font-weight: bold;
color: #333;
}
.qrAddress {
margin-top: 30px;
font-size: 12px;
font-weight: bold;
color: #333;
}
.addressTitle {
color: #3a8ee6;
}
.qrcode {
width: fit-content;
padding: 20px 30px;
//width: 50%;
margin: 0 auto;
text-align: center;
background-color: #fff;
border-radius: 20px;
}
}
</style>

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup>
import type { JsonViewerAction, JsonViewerValue } from '@vben/common-ui';
import { computed, ref } from 'vue';
import { Page } from '@vben/common-ui';
import { JsonViewer, Page } from '@vben/common-ui';
import { Button, Input, Select, Switch, Table } from 'ant-design-vue';
import { Button, Input, message, Select, Switch, Table } from 'ant-design-vue';
import filedType from '#/util/json/filedType.json';
import formType from '#/util/json/formType.json';
@@ -67,11 +69,39 @@ const columns = computed(() => [
fixed: 'right',
},
]);
function handleKeyClick(key: string) {
message.info(`点击了Key ${key}`);
}
function handleValueClick(value: JsonViewerValue) {
message.info(`点击了Value ${JSON.stringify(value)}`);
}
function handleCopied(_event: JsonViewerAction) {
message.success('已复制JSON');
}
const jsonValue = ref('');
const changeJsonValue = () => {
tableData.value = JSON.parse(jsonValue.value);
};
</script>
<template>
<Page auto-content-height>
{{ tableData }}
<Input v-model:value="jsonValue" />
<Button @click="changeJsonValue">写入json</Button>
<JsonViewer
:value="tableData"
:expand-depth="3"
copyable
:sort="false"
@key-click="handleKeyClick"
@value-click="handleValueClick"
@copied="handleCopied"
boxed
/>
<Table
:columns="columns"
:data-source="tableData"
@@ -122,7 +152,7 @@ const columns = computed(() => [
<!-- 基础配置 -->
<template v-if="column.dataIndex === 'base'">
<!-- 开关控件 -->
<div>可以为空 <Switch v-model:checked="record.null" /></div>
<div>不能为空 <Switch v-model:checked="record.not_null" /></div>
<div class="mt-2">
表单显示 <Switch v-model:checked="record.formShow" />
</div>

View File

@@ -1,6 +1,6 @@
import { requestClient } from '#/api/request';
const prefix = 'store/';
const prefix = 'mall/';
/**
*
* @param data

View File

@@ -1,6 +1,6 @@
import type { VbenFormProps } from '#/adapter/form';
import { getStoreClassificationOptionApi } from '#/views/my-gen/store-classification/api';
import { getStoreClassificationOptionApi } from '#/views/my-gen/mall-classification/api';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,

View File

@@ -27,17 +27,29 @@ export const gridOptions: VxeGridProps<RowType> = {
// 生成的列表字段
{ field: 'name', title: '店铺名称' },
{ field: 'level', title: '店铺等级' },
{ field: 'avatar', title: '店铺头像' },
{ field: 'avatar', title: '店铺头像', slots: { default: 'avatar' } },
{ field: 'introduction', title: '店铺简介' },
{ field: 'description', title: '详细介绍' },
// { field: 'description', title: '详细介绍' },
{ field: 'phone', title: '商家电话号码' },
{ field: 'person', title: '联系人名称' },
{ field: 'id_card', title: '身份证号码' },
{ field: 'qr_code', title: '店铺二维码' },
{ field: 'is_featured', title: '是否为推荐店铺' },
{ field: 'business_license', title: '营业执照' },
{ field: 'id_card_portrait_side', title: '身份证人面' },
{ field: 'id_card_national_emblem_side', title: '身份证国徽面' },
{
field: 'business_license',
title: '营业执照',
slots: { default: 'business_license' },
},
{
field: 'id_card_portrait_side',
title: '身份证人面',
slots: { default: 'id_card_portrait_side' },
},
{
field: 'id_card_national_emblem_side',
title: '身份证国徽面',
slots: { default: 'id_card_national_emblem_side' },
},
{ field: 'chain', title: '是否连锁' },
{ field: 'business_suspended', title: '是否暂停营业' },

View File

@@ -1,6 +1,6 @@
import { requestClient } from '#/api/request';
const prefix = 'store-classification/';
const prefix = 'mall-classification/';
/**
*
* @param data

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'mall-membership-level/';
/**
* 分页查询店铺会员等级列表
* @param data
*/
export async function getStoreMembershipLevelListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取店铺会员等级下拉列表
*/
export async function getStoreMembershipLevelOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取店铺会员等级详情
* @param id
*/
export async function getStoreMembershipLevelInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增店铺会员等级
* @param data
*/
export async function createStoreMembershipLevelApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑店铺会员等级
* @param data
*/
export async function updateStoreMembershipLevelApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除店铺会员等级
* @param data
*/
export async function deleteStoreMembershipLevelApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createStoreMembershipLevelApi,
updateStoreMembershipLevelApi,
} from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'StoreMembershipLevelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateStoreMembershipLevelApi
: createStoreMembershipLevelApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}店铺会员等级`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,40 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'name',
label: '等级名称',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'experience',
label: '升级所需经验',
component: 'InputNumber',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,25 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'name',
label: '等级名称',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,69 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getStoreMembershipLevelListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'name', title: '等级名称' },
{ field: 'experience', title: '升级所需经验' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getStoreMembershipLevelListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteStoreMembershipLevelApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteStoreMembershipLevelApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="店铺会员等级管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,42 @@
import { requestClient } from '#/api/request';
const prefix = 'mall/';
/**
* 分页查询店铺管理列表
* @param data
*/
export async function getStoreListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取店铺管理详情
* @param id
*/
export async function getStoreInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增店铺管理
* @param data
*/
export async function createStoreApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑店铺管理
* @param data
*/
export async function updateStoreApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除店铺管理
* @param data
*/
export async function deleteStoreApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,66 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createStoreApi, updateStoreApi } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'StoreDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateStoreApi : createStoreApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}店铺管理`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,179 @@
import type { VbenFormProps } from '#/adapter/form';
import { getStoreClassificationOptionApi } from '#/views/my-gen/mall-classification/api';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'name',
label: '店铺名称',
component: 'VbenInput',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'level',
label: '店铺等级',
component: 'InputNumber',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'introduction',
label: '店铺简介',
component: 'Textarea',
rules: 'required',
},
{
fieldName: 'description',
label: '详细介绍',
component: 'Textarea',
rules: 'required',
},
{
fieldName: 'phone',
label: '商家电话号码',
component: 'VbenInput',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'person',
label: '联系人名称',
component: 'VbenInput',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'id_card',
label: '身份证号码',
component: 'VbenInput',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'classification_id',
label: '行业',
component: 'ApiTreeSelect',
componentProps: {
childrenField: 'children',
labelField: 'name',
valueField: 'id',
api: getStoreClassificationOptionApi,
},
formItemClass: 'col-span-6',
defaultValue: 0,
rules: 'required',
},
{
fieldName: 'is_featured',
label: '推荐店铺',
component: 'RadioGroup',
componentProps: {
options: [
{
label: '是',
value: 1,
},
{
label: '否',
value: 0,
},
],
},
formItemClass: 'col-span-6',
defaultValue: 0,
rules: 'required',
},
{
fieldName: 'chain',
label: '连锁',
component: 'RadioGroup',
componentProps: {
options: [
{
label: '是',
value: 1,
},
{
label: '否',
value: 0,
},
],
},
formItemClass: 'col-span-6',
defaultValue: 0,
rules: 'required',
},
{
fieldName: 'business_suspended',
label: '暂停营业',
component: 'RadioGroup',
componentProps: {
options: [
{
label: '是',
value: 1,
},
{
label: '否',
value: 0,
},
],
},
formItemClass: 'col-span-6',
defaultValue: 0,
rules: 'required',
},
{
fieldName: 'avatar',
label: '店铺头像',
component: 'Avatar',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'business_license',
label: '营业执照',
component: 'Avatar',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'id_card_portrait_side',
label: '身份证人面',
component: 'Avatar',
formItemClass: 'col-span-6',
rules: 'required',
},
{
fieldName: 'id_card_national_emblem_side',
label: '身份证国徽面',
component: 'Avatar',
formItemClass: 'col-span-6',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,55 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'name',
label: '店铺名称',
component: 'VbenInput',
},
{
fieldName: 'level',
label: '店铺等级',
component: 'InputNumber',
},
{
fieldName: 'phone',
label: '商家电话号码',
component: 'VbenInput',
},
{
fieldName: 'person',
label: '联系人名称',
component: 'VbenInput',
},
{
fieldName: 'is_featured',
label: '是否为推荐店铺',
component: 'VbenSelect',
},
{
fieldName: 'chain',
label: '是否连锁',
component: 'VbenSelect',
},
{
fieldName: 'business_suspended',
label: '是否暂停营业',
component: 'VbenSelect',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,94 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getStoreListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'name', title: '店铺名称' },
{ field: 'level', title: '店铺等级' },
{ field: 'avatar', title: '店铺头像', slots: { default: 'avatar' } },
{ field: 'introduction', title: '店铺简介' },
// { field: 'description', title: '详细介绍' },
{ field: 'phone', title: '商家电话号码' },
{ field: 'person', title: '联系人名称' },
{ field: 'id_card', title: '身份证号码' },
{ field: 'qr_code', title: '店铺二维码' },
{ field: 'is_featured', title: '是否为推荐店铺' },
{
field: 'business_license',
title: '营业执照',
slots: { default: 'business_license' },
},
{
field: 'id_card_portrait_side',
title: '身份证人面',
slots: { default: 'id_card_portrait_side' },
},
{
field: 'id_card_national_emblem_side',
title: '身份证国徽面',
slots: { default: 'id_card_national_emblem_side' },
},
{ field: 'chain', title: '是否连锁' },
{ field: 'business_suspended', title: '是否暂停营业' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getStoreListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,137 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteStoreApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteStoreApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="店铺管理管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #business_license="{ row }">
<Image :src="row.business_license" height="30" width="30" />
</template>
<template #id_card_portrait_side="{ row }">
<Image :src="row.id_card_portrait_side" height="30" width="30" />
</template>
<template #id_card_national_emblem_side="{ row }">
<Image :src="row.id_card_national_emblem_side" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'order-detail/';
/**
* 分页查询订单详情列表
* @param data
*/
export async function getOrderDetailListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取订单详情下拉列表
*/
export async function getOrderDetailOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取订单详情详情
* @param id
*/
export async function getOrderDetailInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增订单详情
* @param data
*/
export async function createOrderDetailApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑订单详情
* @param data
*/
export async function updateOrderDetailApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除订单详情
* @param data
*/
export async function deleteOrderDetailApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createOrderDetailApi, updateOrderDetailApi } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'OrderDetailDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateOrderDetailApi
: createOrderDetailApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}订单详情`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,58 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'order_id',
label: '订单ID',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'product_id',
label: '商品ID',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'quantity',
label: '商品数量',
component: 'VbenInputNumber',
rules: 'required',
},
{
fieldName: 'subtotal',
label: '小计金额',
component: 'VbenInputNumber',
rules: '',
},
{
fieldName: 'snapshot',
label: '商品快照',
component: 'VbenInput',
rules: '',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,30 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'order_id',
label: '订单ID',
component: 'VbenInput',
},
{
fieldName: 'product_id',
label: '商品ID',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,73 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getOrderDetailListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'order_id', title: '订单ID' },
{ field: 'product_id', title: '商品ID' },
{ field: 'quantity', title: '商品数量' },
{ field: 'price', title: '商品单价' },
{ field: 'subtotal', title: '小计金额' },
{ field: 'snapshot', title: '商品快照' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getOrderDetailListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteOrderDetailApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteOrderDetailApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="订单详情管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'order/';
/**
* 分页查询订单管理列表
* @param data
*/
export async function getOrderListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取订单管理下拉列表
*/
export async function getOrderOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取订单管理详情
* @param id
*/
export async function getOrderInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增订单管理
* @param data
*/
export async function createOrderApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑订单管理
* @param data
*/
export async function updateOrderApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除订单管理
* @param data
*/
export async function deleteOrderApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,66 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createOrderApi, updateOrderApi } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'OrderDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateOrderApi : createOrderApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}订单管理`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,52 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'user_id',
label: '用户ID',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'order_no',
label: '订单编号',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'order_status',
label: '订单状态',
component: 'VbenSelect',
rules: 'required',
},
{
fieldName: 'total_amount',
label: '订单总金额',
component: 'VbenInputNumber',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,35 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'user_id',
label: '用户ID',
component: 'VbenInput',
},
{
fieldName: 'order_no',
label: '订单编号',
component: 'VbenInput',
},
{
fieldName: 'order_status',
label: '订单状态',
component: 'VbenSelect',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,71 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getOrderListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'user_id', title: '用户ID' },
{ field: 'order_no', title: '订单编号' },
{ field: 'order_status', title: '订单状态' },
{ field: 'total_amount', title: '订单总金额' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getOrderListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -10,7 +10,7 @@ import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteStoreApi } from './api';
import { deleteOrderApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
@@ -55,7 +55,7 @@ const hasDelete = (row: any) => {
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteStoreApi({ ids }).then(() => {
deleteOrderApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
@@ -63,7 +63,7 @@ const hasDelete = (row: any) => {
</script>
<template>
<Page auto-content-height title="店铺管理管理">
<Page auto-content-height title="订单管理管理">
<FormModal />
<Grid>
<template #toolbar-buttons>

View File

@@ -0,0 +1,55 @@
import { requestClient } from '#/api/request';
const prefix = 'product-label-relations/';
/**
* 分页查询商品标签关联列表
* @param data
*/
export async function getProduct_label_relationsListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取商品标签关联下拉列表
*/
export async function getProduct_label_relationsOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取商品标签关联详情
* @param id
*/
export async function getProduct_label_relationsInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增商品标签关联
* @param data
*/
export async function createProduct_label_relationsApi(
data: Record<string, any>,
) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑商品标签关联
* @param data
*/
export async function updateProduct_label_relationsApi(
data: Record<string, any>,
) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除商品标签关联
* @param data
*/
export async function deleteProduct_label_relationsApi(
data: Record<string, any>,
) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createProduct_label_relationsApi,
updateProduct_label_relationsApi,
} from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'ProductLabelRelationsDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateProduct_label_relationsApi
: createProduct_label_relationsApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}商品标签关联`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,40 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'product_id',
label: '商品',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'label_id',
label: '标签',
component: 'VbenInput',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,30 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'product_id',
label: '商品',
component: 'VbenInput',
},
{
fieldName: 'label_id',
label: '标签',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,69 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getProduct_label_relationsListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'product_id', title: '商品' },
{ field: 'label_id', title: '标签' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getProduct_label_relationsListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteProduct_label_relationsApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteProduct_label_relationsApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="商品标签关联管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -9,6 +9,14 @@ export async function getProductLabelListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取商品标签详情
* @param id
*/
export async function getProductLabelOptionApi(id: number) {
return requestClient.get<any>(`${prefix}option`, { params: { id } });
}
/**
* 获取商品标签详情
* @param id

View File

@@ -9,6 +9,13 @@ export async function getProductListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取产品详情
*/
export async function getProductOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取产品详情
* @param id

View File

@@ -13,7 +13,6 @@ import { modalFormProps } from '../config/form';
defineOptions({
name: 'ProductDemo',
});
const isUpdate = ref(false);
const gridApi = ref();

View File

@@ -1,6 +1,7 @@
import type { VbenFormProps } from '#/adapter/form';
import { getProductClassificationTreeOptionApi } from '#/views/my-gen/product-classification/api';
import { getProductLabelOptionApi } from '#/views/my-gen/product-label/api';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
@@ -25,16 +26,16 @@ export const modalFormProps: VbenFormProps = {
},
},
// 生成的表单字段
{
fieldName: 'title',
label: '商品名称',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'cover',
label: '封面图',
component: 'Avatar',
rules: 'required',
},
{
fieldName: 'title',
label: '商品名称',
component: 'VbenInput',
formItemClass: 'col-span-6',
rules: 'required',
},
@@ -66,6 +67,28 @@ export const modalFormProps: VbenFormProps = {
},
rules: 'required',
},
{
fieldName: 'labels',
label: '产品标签',
component: 'ApiSelect',
componentProps: {
api: getProductLabelOptionApi,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
mode: 'multiple',
},
rules: 'required',
},
{
fieldName: 'images',
label: '产品图片',

View File

@@ -23,21 +23,40 @@ export const gridOptions: VxeGridProps<RowType> = {
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'id', align: 'left', title: 'ID', width: 50 },
// 生成的列表字段
{ field: 'title', title: '商品名称' },
{ field: 'mall_id', title: '商家ID' },
{ field: 'user_id', title: '上传用户ID' },
{ field: 'introduction', title: '简介' },
{ field: 'cover', title: '封面图', slots: { default: 'cover' } },
{
field: 'title',
title: '商品名称',
slots: { default: 'title' },
width: 200,
},
{ field: 'mall', title: '商家', slots: { default: 'mall' }, width: 250 },
{
field: 'user',
title: '上传用户',
slots: { default: 'user' },
width: 150,
},
// { field: 'introduction', title: '简介' },
{ field: 'images', title: '商品图册', slots: { default: 'images' } },
{ field: 'description', title: '详细说明' },
{ field: 'price', title: '价格' },
{ field: 'classification_id', title: '产品分类' },
// { field: 'description', title: '详细说明' },
// { field: 'price', title: '默认价格' },
{
field: 'classification',
title: '产品分类',
slots: { default: 'classification' },
},
{ field: 'label', title: '产品分类', slots: { default: 'label' } },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
{
type: 'html',
title: '操作',
slots: { default: 'action' },
width: 150,
},
],
keepSource: true,
pagerConfig: {},

View File

@@ -5,10 +5,12 @@ import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, ImagePreviewGroup, message } from 'ant-design-vue';
import { Button, Image, ImagePreviewGroup, message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import ProductDetail from '#/components/modal/ProductPreview.vue';
import { TableAction } from '#/components/table-action';
import ProductSkuDemo from '#/views/my-gen/sku/components/product-modal.vue';
import { deleteProductApi } from './api';
import FormModalDemo from './components/modal.vue';
@@ -38,6 +40,14 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const [ProductSkuDemoModal, ProductSkuDemoModalApi] = useVbenModal({
connectedComponent: ProductSkuDemo,
});
const [ProductDetailModal, ProductDetailModalApi] = useVbenModal({
connectedComponent: ProductDetail,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
@@ -48,6 +58,16 @@ const showModal = (data = {}, isUpdate = false) => {
formModalApi.open();
};
const showSkuModal = (data = {}, isUpdate = false) => {
ProductSkuDemoModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
ProductSkuDemoModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
@@ -60,11 +80,21 @@ const hasDelete = (row: any) => {
gridApi.reload();
});
};
const showProductDetailModal = (row: any) => {
ProductDetailModalApi.setData({
// 表单值
values: row,
});
ProductDetailModalApi.open();
};
</script>
<template>
<Page auto-content-height title="产品管理">
<FormModal />
<ProductDetailModal />
<ProductSkuDemoModal />
<Grid>
<template #toolbar-buttons>
<TableAction
@@ -93,8 +123,75 @@ const hasDelete = (row: any) => {
</template>
</TableAction>
</template>
<template #cover="{ row }">
<Image :src="row.cover" height="30" width="30" />
<template #title="{ row }">
<div
class="h-20 w-20 flex-shrink-0 overflow-hidden rounded-full"
style="margin: 0 auto"
>
<Image :src="row.cover" class="h-full w-full object-cover" />
</div>
<Button type="link" @click="showProductDetailModal(row.id)">
{{ row.title }}
</Button>
<div>
价格<span class="font-bold text-red-500">{{ row.price }}</span>
</div>
</template>
<template #mall="{ row }">
<div class="items-center space-x-3">
<div
class="h-10 w-10 flex-shrink-0 overflow-hidden rounded-full text-left"
style="margin: 0 auto"
>
<Image
:src="row.mall.avatar"
:alt="row.mall.name"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
{{ row.mall.name }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
ID: {{ row.mall.id }}
</div>
</div>
</div>
</template>
<template #user="{ row }">
<div class="flex items-center space-x-3">
<div
class="h-10 w-10 flex-shrink-0 overflow-hidden rounded-full text-left"
>
<Image
:src="row.user.avatar"
:alt="row.user.nick_name"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
{{ row.user.nick_name }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
ID: {{ row.user.id }}
</div>
</div>
</div>
</template>
<template #classification="{ row }">
<Tag color="#7166f0">{{ row.classification.name }}</Tag>
</template>
<template #label="{ row }">
<Tag
v-for="(item, index) in row.labels_name"
:key="index"
class="mt-3"
color="#7166f0"
>
{{ item }}
</Tag>
</template>
<template #images="{ row }">
<div class="relative inline-block">
@@ -129,6 +226,14 @@ const hasDelete = (row: any) => {
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '新增sku',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showSkuModal.bind(null, row, false),
},
{
label: '删除',
type: 'link',

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'sku/';
/**
* 分页查询sku管理列表
* @param data
*/
export async function getSkuListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取sku管理下拉列表
*/
export async function getSkuOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取sku管理详情
* @param id
*/
export async function getSkuInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增sku管理
* @param data
*/
export async function createSkuApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑sku管理
* @param data
*/
export async function updateSkuApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除sku管理
* @param data
*/
export async function deleteSkuApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,66 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createSkuApi, updateSkuApi } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'SkuDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateSkuApi : createSkuApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}sku管理`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createSkuApi, updateSkuApi } from '../api';
import { productModalFormProps } from '../config/form';
defineOptions({
name: 'ProductSkuDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(productModalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateSkuApi : createSkuApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues({
product_id: values.id,
});
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}sku管理`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,148 @@
import type { VbenFormProps } from '#/adapter/form';
import { getProductOptionApi } from '#/views/my-gen/product/api';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'product_id',
label: '商品ID',
component: 'ApiSelect',
componentProps: {
api: getProductOptionApi,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; title: string }[]) => {
return data.map((item: any) => ({
label: item.title,
value: item.id,
}));
},
},
rules: 'required',
},
{
fieldName: 'name',
label: 'sku名称',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'cover',
label: 'sku图片',
component: 'Avatar',
rules: 'required',
},
{
fieldName: 'inventory',
label: '商品库存',
component: 'InputNumber',
rules: 'required',
},
{
fieldName: 'price',
label: '商品单价',
component: 'InputNumber',
rules: 'required',
},
],
showDefaultActions: false,
};
export const productModalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'product_id',
label: '商品ID',
component: 'ApiSelect',
componentProps: {
api: getProductOptionApi,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; title: string }[]) => {
return data.map((item: any) => ({
label: item.title,
value: item.id,
}));
},
},
disabled: true,
rules: 'required',
},
{
fieldName: 'name',
label: 'sku名称',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'cover',
label: 'sku图片',
component: 'Avatar',
rules: 'required',
},
{
fieldName: 'inventory',
label: '商品库存',
component: 'InputNumber',
rules: 'required',
},
{
fieldName: 'price',
label: '商品单价',
component: 'InputNumber',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,25 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'product_id',
label: '商品ID',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,72 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getSkuListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'product.title', title: '商品' },
{ field: 'name', title: 'sku名称' },
{ field: 'cover', title: 'sku图片', slots: { default: 'cover' } },
{ field: 'inventory', title: '商品库存' },
{ field: 'price', title: '商品单价' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getSkuListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteSkuApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteSkuApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="sku管理管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #cover="{ row }">
<Image :src="row.cover" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'user-browsing-history/';
/**
* 分页查询用户浏览记录列表
* @param data
*/
export async function getUserBrowsingHistoryListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取用户浏览记录下拉列表
*/
export async function getUserBrowsingHistoryOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取用户浏览记录详情
* @param id
*/
export async function getUserBrowsingHistoryInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增用户浏览记录
* @param data
*/
export async function createUserBrowsingHistoryApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑用户浏览记录
* @param data
*/
export async function updateUserBrowsingHistoryApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除用户浏览记录
* @param data
*/
export async function deleteUserBrowsingHistoryApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createUserBrowsingHistoryApi,
updateUserBrowsingHistoryApi,
} from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'UserBrowsingHistoryDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateUserBrowsingHistoryApi
: createUserBrowsingHistoryApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}用户浏览记录`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,40 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'user_id',
label: '用户',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'product_id',
label: '商品id',
component: 'VbenInput',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,30 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'user_id',
label: '用户',
component: 'VbenInput',
},
{
fieldName: 'product_id',
label: '商品id',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,69 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getUserBrowsingHistoryListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'user', title: '用户', slots: { default: 'user' } },
{ field: 'product', title: '商品', slots: { default: 'product' } },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getUserBrowsingHistoryListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,168 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteUserBrowsingHistoryApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteUserBrowsingHistoryApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="用户浏览记录管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #user="{ row }">
<div class="flex items-center space-x-3">
<div
class="h-10 w-10 flex-shrink-0 overflow-hidden rounded-full text-left"
>
<Image
:src="row.user.avatar"
:alt="row.user.nick_name"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
{{ row.user.nick_name }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
ID: {{ row.user.id }}
</div>
</div>
</div>
</template>
<template #product="{ row }">
<div class="items-center space-x-3">
<div
class="h-20 w-20 flex-shrink-0 overflow-hidden rounded-full"
style="margin: 0 auto"
>
<Image
:src="row.product.cover"
:alt="row.product.nick_name"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
{{ row.product.title }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
ID: {{ row.product.id }}
</div>
</div>
</div>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'user-collect/';
/**
* 分页查询用户收藏列表
* @param data
*/
export async function getUserCollectListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取用户收藏下拉列表
*/
export async function getUserCollectOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取用户收藏详情
* @param id
*/
export async function getUserCollectInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增用户收藏
* @param data
*/
export async function createUserCollectApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑用户收藏
* @param data
*/
export async function updateUserCollectApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除用户收藏
* @param data
*/
export async function deleteUserCollectApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createUserCollectApi, updateUserCollectApi } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'UserCollectDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateUserCollectApi
: createUserCollectApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}用户收藏`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,40 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'user_id',
label: '用户',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'product_id',
label: '商品id',
component: 'VbenInput',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,30 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'user_id',
label: '用户',
component: 'VbenInput',
},
{
fieldName: 'product_id',
label: '商品id',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,69 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getUserCollectListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'user', title: '用户', slots: { default: 'user' } },
{ field: 'product', title: '商品', slots: { default: 'product' } },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getUserCollectListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,168 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteUserCollectApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteUserCollectApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="用户收藏管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #user="{ row }">
<div class="flex items-center space-x-3">
<div
class="h-10 w-10 flex-shrink-0 overflow-hidden rounded-full text-left"
>
<Image
:src="row.user.avatar"
:alt="row.user.nick_name"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
{{ row.user.nick_name }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
ID: {{ row.user.id }}
</div>
</div>
</div>
</template>
<template #product="{ row }">
<div class="items-center space-x-3">
<div
class="h-20 w-20 flex-shrink-0 overflow-hidden rounded-full"
style="margin: 0 auto"
>
<Image
:src="row.product.cover"
:alt="row.product.nick_name"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
{{ row.product.title }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
ID: {{ row.product.id }}
</div>
</div>
</div>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'user-membership/';
/**
* 分页查询用户会员列表
* @param data
*/
export async function getUserMembershipListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取用户会员下拉列表
*/
export async function getUserMembershipOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取用户会员详情
* @param id
*/
export async function getUserMembershipInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增用户会员
* @param data
*/
export async function createUserMembershipApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑用户会员
* @param data
*/
export async function updateUserMembershipApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除用户会员
* @param data
*/
export async function deleteUserMembershipApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createUserMembershipApi, updateUserMembershipApi } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'UserMembershipDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateUserMembershipApi
: createUserMembershipApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}用户会员`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,40 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'name',
label: '会员等级说明',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'experience',
label: '升级所需经验',
component: 'InputNumber',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,25 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'name',
label: '会员等级说明',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,69 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getUserMembershipListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'name', title: '会员等级说明' },
{ field: 'experience', title: '升级所需经验' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getUserMembershipListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteUserMembershipApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteUserMembershipApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="用户会员管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'user-receiving-address/';
/**
* 分页查询用户收货地址列表
* @param data
*/
export async function getUserReceivingAddressListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取用户收货地址下拉列表
*/
export async function getUserReceivingAddressOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取用户收货地址详情
* @param id
*/
export async function getUserReceivingAddressInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增用户收货地址
* @param data
*/
export async function createUserReceivingAddressApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑用户收货地址
* @param data
*/
export async function updateUserReceivingAddressApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除用户收货地址
* @param data
*/
export async function deleteUserReceivingAddressApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createUserReceivingAddressApi,
updateUserReceivingAddressApi,
} from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'UserReceivingAddressDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateUserReceivingAddressApi
: createUserReceivingAddressApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}用户收货地址`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,64 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'user_id',
label: '用户',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'province',
label: '省',
component: 'InputNumber',
rules: 'required',
},
{
fieldName: 'city',
label: '市',
component: 'InputNumber',
rules: 'required',
},
{
fieldName: 'district',
label: '区',
component: 'InputNumber',
rules: 'required',
},
{
fieldName: 'detailed_address',
label: '详细地址',
component: 'Textarea',
rules: 'required',
},
{
fieldName: 'complete_address',
label: '完整地址',
component: 'VbenInput',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,50 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'user_id',
label: '用户',
component: 'VbenInput',
},
{
fieldName: 'province',
label: '省',
component: 'InputNumber',
},
{
fieldName: 'city',
label: '市',
component: 'InputNumber',
},
{
fieldName: 'district',
label: '区',
component: 'InputNumber',
},
{
fieldName: 'detailed_address',
label: '详细地址',
component: 'Textarea',
},
{
fieldName: 'complete_address',
label: '完整地址',
component: 'VbenInput',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,73 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getUserReceivingAddressListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
// 生成的列表字段
{ field: 'user_id', title: '用户' },
{ field: 'province', title: '省' },
{ field: 'city', title: '市' },
{ field: 'district', title: '区' },
{ field: 'detailed_address', title: '详细地址' },
{ field: 'complete_address', title: '完整地址' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getUserReceivingAddressListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteUserReceivingAddressApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteUserReceivingAddressApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="用户收货地址管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'user/';
/**
* 分页查询用户列表
* @param data
*/
export async function getUserListApi(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取用户下拉列表
*/
export async function getUserOptionApi() {
return requestClient.get<any>(`${prefix}option`);
}
/**
* 获取用户详情
* @param id
*/
export async function getUserInfoApi(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增用户
* @param data
*/
export async function createUserApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑用户
* @param data
*/
export async function updateUserApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除用户
* @param data
*/
export async function deleteUserApi(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,63 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createUserApi, updateUserApi } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'UserDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateUserApi : createUserApi;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}用户`" class="w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,70 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
// 生成的表单字段
{
fieldName: 'nick_name',
label: '用户昵称',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'phone',
label: '手机号码',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'email',
label: '邮箱',
component: 'VbenInput',
rules: 'required',
},
{
fieldName: 'level',
label: '用户会员等级',
component: 'InputNumber',
rules: 'required',
},
{
fieldName: 'balance',
label: '用户余额',
component: 'InputNumber',
rules: 'required',
},
{
fieldName: 'avatar',
label: '用户头像',
component: 'Avatar',
rules: 'required',
},
{
fieldName: 'integral',
label: '积分',
component: 'InputNumber',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,45 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
// 生成的搜索字段
{
fieldName: 'open_id',
label: '平台OpenId',
component: 'VbenInput',
},
{
fieldName: 'nick_name',
label: '用户昵称',
component: 'VbenInput',
},
{
fieldName: 'phone',
label: '手机号码',
component: 'VbenInput',
},
{
fieldName: 'email',
label: '邮箱',
component: 'VbenInput',
},
{
fieldName: 'level',
label: '用户会员等级',
component: 'InputNumber',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,80 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getUserListApi } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
// 生成的列表字段
{ field: 'avatar', title: '用户信息', slots: { default: 'avatar' } },
{
field: 'open_id',
title: '手机号码&邮箱&平台OpenId',
slots: { default: 'open_id' },
width: 250,
},
// { field: 'phone', title: '手机号码' },
// { field: 'email', title: '邮箱' },
{ field: 'level', title: '用户会员等级' },
{ field: 'balance', title: '用户余额' },
{ field: 'integral', title: '积分' },
// { field: 'wx_open_id', title: '微信小程序唯一标识' },
// { field: 'zfb_open_id', title: '支付宝小程序唯一标识' },
{ field: 'created_at', title: '创建时间' },
{ field: 'updated_at', title: '编辑时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getUserListApi({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,177 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { useClipboard } from '@vueuse/core';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteUserApi } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const hasDelete = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteUserApi({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
const source = ref('Hello');
const { copy } = useClipboard({ legacy: true, source });
const copyText = (string: string) => {
copy(string);
message.success('复制成功!');
};
</script>
<template>
<Page auto-content-height title="用户管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定删除吗',
confirm: hasDelete.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px"> 批量操作 </Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<div class="flex items-center space-x-3">
<div
class="h-10 w-10 flex-shrink-0 overflow-hidden rounded-full text-left"
>
<Image
:src="row.avatar"
:alt="row.nick_name"
class="h-full w-full object-cover"
/>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
{{ row.nick_name }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
ID: {{ row.id }}
</div>
</div>
</div>
<!-- <Image :src="row.avatar" height="30" width="30" />-->
</template>
<template #open_id="{ row }">
<Button
type="link"
@click="copyText(row.phone)"
class="text-sm font-medium"
>
{{ row.phone }}
</Button>
<Button
type="link"
@click="copyText(row.email)"
class="text-sm font-medium"
>
{{ row.email }}
</Button>
<Button
type="link"
@click="copyText(row.open_id)"
class="text-sm font-medium"
>
{{ row.open_id }}
</Button>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: hasDelete.bind(null, row.id),
},
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -5,7 +5,7 @@ export default defineConfig(async () => {
application: {},
vite: {
server: {
port: 18866,
port: 18_866,
proxy: {
'/api': {
changeOrigin: true,