补充缺失文件

This commit is contained in:
2025-07-30 15:34:55 +08:00
parent aa9e9a6b2f
commit 6ce8253f26
38 changed files with 5708 additions and 24 deletions

View File

@@ -0,0 +1,106 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopGoodsCoupon, ShopGoodsCouponParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询商品优惠券表
*/
export async function pageShopGoodsCoupon(params: ShopGoodsCouponParam) {
const res = await request.get<ApiResult<PageResult<ShopGoodsCoupon>>>(
MODULES_API_URL + '/shop/shop-goods-coupon/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询商品优惠券表列表
*/
export async function listShopGoodsCoupon(params?: ShopGoodsCouponParam) {
const res = await request.get<ApiResult<ShopGoodsCoupon[]>>(
MODULES_API_URL + '/shop/shop-goods-coupon',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加商品优惠券表
*/
export async function addShopGoodsCoupon(data: ShopGoodsCoupon) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改商品优惠券表
*/
export async function updateShopGoodsCoupon(data: ShopGoodsCoupon) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除商品优惠券表
*/
export async function removeShopGoodsCoupon(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除商品优惠券表
*/
export async function removeBatchShopGoodsCoupon(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods-coupon/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询商品优惠券表
*/
export async function getShopGoodsCoupon(id: number) {
const res = await request.get<ApiResult<ShopGoodsCoupon>>(
MODULES_API_URL + '/shop/shop-goods-coupon/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,35 @@
import type { PageParam } from '@/api';
/**
* 商品优惠券表
*/
export interface ShopGoodsCoupon {
//
id?: number;
// 商品id
goodsId?: number;
// 优惠劵id
issueCouponId?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 状态, 0正常, 1冻结
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 用户ID
userId?: number;
// 租户id
tenantId?: number;
// 注册时间
createTime?: string;
// 修改时间
updateTime?: string;
}
/**
* 商品优惠券表搜索条件
*/
export interface ShopGoodsCouponParam extends PageParam {
id?: number;
keywords?: string;
}

106
src/api/shop/spec/index.ts Normal file
View File

@@ -0,0 +1,106 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { Spec, SpecParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询规格
*/
export async function pageSpec(params: SpecParam) {
const res = await request.get<ApiResult<PageResult<Spec>>>(
MODULES_API_URL + '/shop/spec/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询规格列表
*/
export async function listSpec(params?: SpecParam) {
const res = await request.get<ApiResult<Spec[]>>(
MODULES_API_URL + '/shop/spec',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加规格
*/
export async function addSpec(data: Spec) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改规格
*/
export async function updateSpec(data: Spec) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除规格
*/
export async function removeSpec(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除规格
*/
export async function removeBatchSpec(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询规格
*/
export async function getSpec(id: number) {
const res = await request.get<ApiResult<Spec>>(
MODULES_API_URL + '/shop/spec/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,37 @@
import type { PageParam } from '@/api';
/**
* 规格
*/
export interface Spec {
// 规格ID
specId?: number;
// 规格名称
specName?: string;
// 规格值
specValue?: string;
// 创建用户
userId?: number;
// 更新者
updater?: number;
// 商户ID
merchantId?: number;
// 备注
comments?: string;
// 状态, 0正常, 1待修,2异常已修3异常未修
status?: number;
// 排序号
sortNumber?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
}
/**
* 规格搜索条件
*/
export interface SpecParam extends PageParam {
specId?: number;
keywords?: string;
}

View File

@@ -0,0 +1,106 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { SpecValue, SpecValueParam } from './model';
import { MODULES_API_URL } from '@/config/setting';
/**
* 分页查询规格值
*/
export async function pageSpecValue(params: SpecValueParam) {
const res = await request.get<ApiResult<PageResult<SpecValue>>>(
MODULES_API_URL + '/shop/spec-value/page',
{
params
}
);
if (res.data.code === 0) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 查询规格值列表
*/
export async function listSpecValue(params?: SpecValueParam) {
const res = await request.get<ApiResult<SpecValue[]>>(
MODULES_API_URL + '/shop/spec-value',
{
params
}
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 添加规格值
*/
export async function addSpecValue(data: SpecValue) {
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 修改规格值
*/
export async function updateSpecValue(data: SpecValue) {
const res = await request.put<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value',
data
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 删除规格值
*/
export async function removeSpecValue(id?: number) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value/' + id
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 批量删除规格值
*/
export async function removeBatchSpecValue(data: (number | undefined)[]) {
const res = await request.delete<ApiResult<unknown>>(
MODULES_API_URL + '/shop/spec-value/batch',
{
data
}
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}
/**
* 根据id查询规格值
*/
export async function getSpecValue(id: number) {
const res = await request.get<ApiResult<SpecValue>>(
MODULES_API_URL + '/shop/spec-value/' + id
);
if (res.data.code === 0 && res.data.data) {
return res.data.data;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -0,0 +1,33 @@
import type { PageParam } from '@/api';
/**
* 规格值
*/
export interface SpecValue {
// 规格值ID
specValueId?: number;
// 规格值
specValue?: string;
// 规格组ID
specId?: number;
// 描述
comments?: string;
// 排序
sortNumber?: number;
// 租户id
tenantId?: number;
key?: string;
label?: string;
value?: string;
detail?: [string];
specName?: string;
}
/**
* 规格值搜索条件
*/
export interface SpecValueParam extends PageParam {
specValueId?: number;
specId?: number;
keywords?: string;
}

View File

@@ -0,0 +1,100 @@
<!-- 搜索表单 -->
<template>
<a-space style="flex-wrap: wrap">
<a-button
type="text"
@click="openUrl(`/website/field`)"
>字段扩展
</a-button>
<a-button
type="text"
@click="openUrl('/website/dict')"
>字典管理
</a-button>
<a-button
type="text"
@click="openUrl('/website/domain')"
>域名管理
</a-button
>
<a-button
type="text"
@click="openUrl('/website/model')"
>模型管理
</a-button
>
<a-button
type="text"
@click="openUrl('/website/form')"
>表单管理
</a-button
>
<a-button
type="text"
@click="openUrl('/website/lang')"
>国际化
</a-button
>
<a-button
type="text"
@click="openUrl('/website/setting')"
>网站设置
</a-button
>
<a-button
type="text"
class="ele-btn-icon"
@click="clearSiteInfoCache">
清除缓存
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import {watch,nextTick} from 'vue';
import {CmsWebsite} from '@/api/cms/cmsWebsite/model';
import {openUrl} from "@/utils/common";
import {message} from 'ant-design-vue';
import {removeSiteInfoCache} from "@/api/cms/cmsWebsite";
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
website?: CmsWebsite;
count?: 0;
}>(),
{}
);
const emit = defineEmits<{
(e: 'add'): void;
}>();
const add = () => {
emit('add');
};
// 清除缓存
const clearSiteInfoCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId') + "*").then(
(msg) => {
if (msg) {
message.success(msg);
}
}
);
};
nextTick(() => {
if(localStorage.getItem('NotActive')){
// IsActive.value = false
}
})
watch(
() => props.selection,
() => {
}
);
</script>

View File

@@ -0,0 +1,400 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑小程序' : '创建小程序'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
:confirm-loading="loading"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="LOGO" name="avatar">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseImage"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="小程序名称" name="websiteName">
<a-input
allow-clear
placeholder="请输入小程序名称"
v-model:value="form.websiteName"
/>
</a-form-item>
<a-form-item label="网站域名" name="domain" v-if="form.type == 10">
<a-input
v-model:value="form.domain"
placeholder="huawei.com"
>
<template #addonBefore>
<a-select v-model:value="form.prefix" style="width: 90px">
<a-select-option value="http://">http://</a-select-option>
<a-select-option value="https://">https://</a-select-option>
</a-select>
</template>
</a-input>
</a-form-item>
<a-form-item label="AppId" name="websiteCode" v-if="form.type == 20">
<a-input
allow-clear
placeholder="请输入AppId"
v-model:value="form.websiteCode"
/>
</a-form-item>
<a-form-item label="小程序描述" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入小程序描述"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="账号类型" name="type">
{{ form.websiteType }}
</a-form-item>
<a-form-item label="小程序码" name="avatar">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="websiteQrcode"
@done="chooseQrcode"
@del="onDeleteQrcode"
/>
</a-form-item>
<!-- <a-form-item label="SEO关键词" name="keywords">-->
<!-- <a-textarea-->
<!-- :rows="4"-->
<!-- :maxlength="200"-->
<!-- placeholder="请输入SEO关键词"-->
<!-- v-model:value="form.keywords"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="全局样式" name="style">-->
<!-- <a-textarea-->
<!-- :rows="4"-->
<!-- :maxlength="200"-->
<!-- placeholder="全局样式"-->
<!-- v-model:value="form.style"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="小程序类型" name="websiteType">-->
<!-- <a-select-->
<!-- :options="websiteType"-->
<!-- :value="form.websiteType"-->
<!-- placeholder="请选择主体类型"-->
<!-- @change="onCmsWebsiteType"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="当前版本" name="version">-->
<!-- <a-tag color="red" v-if="form.version === 10">标准版</a-tag>-->
<!-- <a-tag color="green" v-if="form.version === 20">专业版</a-tag>-->
<!-- <a-tag color="cyan" v-if="form.version === 30">永久授权</a-tag>-->
<!-- </a-form-item>-->
<a-form-item label="状态" name="running">
<a-radio-group
v-model:value="form.running"
:disabled="form.running == 4 || form.running == 5"
>
<a-radio :value="1">运行中</a-radio>
<a-radio :value="2">维护中</a-radio>
<a-radio :value="3">已关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item v-if="form.running == 2" label="维护说明" name="statusText">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="状态说明"
v-model:value="form.statusText"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import {ref, reactive, watch} from 'vue';
import {Form, message} from 'ant-design-vue';
import {assignObject, uuid} from 'ele-admin-pro';
import {addCmsWebsite, updateCmsWebsite} from '@/api/cms/cmsWebsite';
import {CmsWebsite} from '@/api/cms/cmsWebsite/model';
import {useThemeStore} from '@/store/modules/theme';
import {storeToRefs} from 'pinia';
import {FormInstance} from 'ant-design-vue/es/form';
import {ItemType} from 'ele-admin-pro/es/ele-image-upload/types';
import {FileRecord} from '@/api/system/file/model';
import {updateCmsDomain} from '@/api/cms/cmsDomain';
import {updateTenant} from "@/api/system/tenant";
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const {styleResponsive} = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: CmsWebsite | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
const websiteQrcode = ref<ItemType[]>([]);
const oldDomain = ref();
const files = ref<ItemType[]>([]);
// 用户信息
const form = reactive<CmsWebsite>({
websiteId: undefined,
websiteLogo: undefined,
websiteName: undefined,
websiteCode: undefined,
type: 20,
files: undefined,
keywords: '',
prefix: '',
domain: '',
adminUrl: '',
style: '',
icpNo: undefined,
email: undefined,
version: undefined,
websiteType: '',
running: 1,
expirationTime: undefined,
sortNumber: undefined,
comments: undefined,
status: undefined,
statusText: undefined
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
comments: [
{
required: true,
type: 'string',
message: '请填写小程序描述',
trigger: 'blur'
}
],
keywords: [
{
required: true,
type: 'string',
message: '请填写SEO关键词',
trigger: 'blur'
}
],
running: [
{
required: true,
type: 'number',
message: '请选择小程序状态',
trigger: 'change'
}
],
domain: [
{
required: true,
type: 'string',
message: '请填写小程序域名',
trigger: 'blur'
}
],
websiteCode: [
{
required: true,
type: 'string',
message: '请填写小程序码',
trigger: 'blur'
}
],
adminUrl: [
{
required: true,
type: 'string',
message: '请填写小程序后台管理地址',
trigger: 'blur'
}
],
icpNo: [
{
required: true,
type: 'string',
message: '请填写ICP备案号',
trigger: 'blur'
}
],
appSecret: [
{
required: true,
type: 'string',
message: '请填写小程序秘钥',
trigger: 'blur'
}
],
websiteName: [
{
required: true,
type: 'string',
message: '请填写小程序信息名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.websiteLogo = data.downloadUrl;
};
const chooseQrcode = (data: FileRecord) => {
websiteQrcode.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.websiteDarkLogo = data.downloadUrl;
}
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.websiteLogo = '';
};
const onDeleteQrcode = (index: number) => {
websiteQrcode.value.splice(index, 1);
form.websiteDarkLogo = '';
};
const {resetFields} = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateCmsWebsite : addCmsWebsite;
if (!isUpdate.value) {
updateVisible(false);
message.loading('创建过程中请勿刷新页面!', 0)
}
const formData = {
...form,
type: 20,
adminUrl: `mp.websoft.top`,
files: JSON.stringify(files.value),
};
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
updateVisible(false);
updateCmsDomain({
websiteId: form.websiteId,
domain: `${localStorage.getItem('TenantId')}.shoplnk.cn`
});
updateTenant({
tenantName: `${form.websiteName}`
}).then(() => {
})
localStorage.setItem('Domain', `${form.websiteCode}.shoplnk.cn`);
localStorage.setItem('WebsiteId', `${form.websiteId}`);
localStorage.setItem('WebsiteName', `${form.websiteName}`);
message.destroy();
message.success(msg);
// window.location.reload();
emit('done');
})
.catch((e) => {
loading.value = false;
message.destroy();
message.error(e.message);
});
})
.catch(() => {
});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
files.value = [];
websiteQrcode.value = [];
if (props.data?.websiteId) {
assignObject(form, props.data);
if (props.data.websiteLogo) {
images.value.push({
uid: uuid(),
url: props.data.websiteLogo,
status: 'done'
});
}
if (props.data.websiteDarkLogo) {
websiteQrcode.value.push({
uid: uuid(),
url: props.data.websiteDarkLogo,
status: 'done'
});
}
if (props.data.files) {
files.value = JSON.parse(props.data.files);
}
if (props.data.websiteCode) {
oldDomain.value = props.data.websiteCode;
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{immediate: true}
);
</script>

425
src/views/shop/index.vue Normal file
View File

@@ -0,0 +1,425 @@
<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<a-spin :spinning="loading" class="page">
<a-card>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="LOGO" name="avatar">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseImage"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="小程序名称" name="websiteName">
<a-input
allow-clear
placeholder="请输入小程序名称"
v-model:value="form.websiteName"
/>
</a-form-item>
<a-form-item label="网站域名" name="domain" v-if="form.type == 10">
<a-input
v-model:value="form.domain"
placeholder="huawei.com"
>
<template #addonBefore>
<a-select v-model:value="form.prefix" style="width: 90px">
<a-select-option value="http://">http://</a-select-option>
<a-select-option value="https://">https://</a-select-option>
</a-select>
</template>
</a-input>
</a-form-item>
<a-form-item label="AppId" name="websiteCode" v-if="form.type == 20">
<a-input
allow-clear
placeholder="请输入AppId"
v-model:value="form.websiteCode"
/>
</a-form-item>
<a-form-item label="小程序描述" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入小程序描述"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="账号类型" name="type">
{{ form.websiteType }}
</a-form-item>
<a-form-item label="小程序码" name="avatar">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="websiteQrcode"
@done="chooseQrcode"
@del="onDeleteQrcode"
/>
</a-form-item>
<!-- <a-form-item label="SEO关键词" name="keywords">-->
<!-- <a-textarea-->
<!-- :rows="4"-->
<!-- :maxlength="200"-->
<!-- placeholder="请输入SEO关键词"-->
<!-- v-model:value="form.keywords"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="全局样式" name="style">-->
<!-- <a-textarea-->
<!-- :rows="4"-->
<!-- :maxlength="200"-->
<!-- placeholder="全局样式"-->
<!-- v-model:value="form.style"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="小程序类型" name="websiteType">-->
<!-- <a-select-->
<!-- :options="websiteType"-->
<!-- :value="form.websiteType"-->
<!-- placeholder="请选择主体类型"-->
<!-- @change="onCmsWebsiteType"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="当前版本" name="version">-->
<!-- <a-tag color="red" v-if="form.version === 10">标准版</a-tag>-->
<!-- <a-tag color="green" v-if="form.version === 20">专业版</a-tag>-->
<!-- <a-tag color="cyan" v-if="form.version === 30">永久授权</a-tag>-->
<!-- </a-form-item>-->
<a-form-item label="状态" name="running">
<a-radio-group
v-model:value="form.running"
:disabled="form.running == 4 || form.running == 5"
>
<a-radio :value="1">运行中</a-radio>
<a-radio :value="2">维护中</a-radio>
<a-radio :value="3">已关闭</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item v-if="form.running == 2" label="维护说明" name="statusText">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="状态说明"
v-model:value="form.statusText"
/>
</a-form-item>
</a-form>
</a-card>
</a-spin>
</a-page-header>
</template>
<script lang="ts" setup>
import {ref, reactive, watch} from 'vue';
import {Form, message} from 'ant-design-vue';
import {assignObject, uuid} from 'ele-admin-pro';
import {addCmsWebsite, updateCmsWebsite} from '@/api/cms/cmsWebsite';
import {CmsWebsite} from '@/api/cms/cmsWebsite/model';
import {useThemeStore} from '@/store/modules/theme';
import {storeToRefs} from 'pinia';
import {FormInstance} from 'ant-design-vue/es/form';
import {ItemType} from 'ele-admin-pro/es/ele-image-upload/types';
import {FileRecord} from '@/api/system/file/model';
import {updateCmsDomain} from '@/api/cms/cmsDomain';
import {updateTenant} from "@/api/system/tenant";
import {getPageTitle, push} from "@/utils/common";
import router from "@/router";
import {getSiteInfo} from "@/api/layout";
import useFormData from "@/utils/use-form-data";
import type {User} from "@/api/system/user/model";
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const {styleResponsive} = storeToRefs(themeStore);
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
const siteInfo = ref<CmsWebsite>({})
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
const websiteQrcode = ref<ItemType[]>([]);
const files = ref<ItemType[]>([]);
// 用户信息
const {form, assignFields} = useFormData<CmsWebsite>({
websiteId: undefined,
websiteLogo: undefined,
websiteName: undefined,
websiteCode: undefined,
type: 20,
files: undefined,
keywords: '',
prefix: '',
domain: '',
adminUrl: '',
style: '',
icpNo: undefined,
email: undefined,
version: undefined,
websiteType: '',
running: 1,
expirationTime: undefined,
sortNumber: undefined,
comments: undefined,
status: undefined,
statusText: undefined
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
comments: [
{
required: true,
type: 'string',
message: '请填写小程序描述',
trigger: 'blur'
}
],
keywords: [
{
required: true,
type: 'string',
message: '请填写SEO关键词',
trigger: 'blur'
}
],
running: [
{
required: true,
type: 'number',
message: '请选择小程序状态',
trigger: 'change'
}
],
domain: [
{
required: true,
type: 'string',
message: '请填写小程序域名',
trigger: 'blur'
}
],
websiteCode: [
{
required: true,
type: 'string',
message: '请填写小程序码',
trigger: 'blur'
}
],
adminUrl: [
{
required: true,
type: 'string',
message: '请填写小程序后台管理地址',
trigger: 'blur'
}
],
icpNo: [
{
required: true,
type: 'string',
message: '请填写ICP备案号',
trigger: 'blur'
}
],
appSecret: [
{
required: true,
type: 'string',
message: '请填写小程序秘钥',
trigger: 'blur'
}
],
websiteName: [
{
required: true,
type: 'string',
message: '请填写小程序信息名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.websiteLogo = data.downloadUrl;
};
const chooseQrcode = (data: FileRecord) => {
websiteQrcode.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.websiteDarkLogo = data.downloadUrl;
}
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.websiteLogo = '';
};
const onDeleteQrcode = (index: number) => {
websiteQrcode.value.splice(index, 1);
form.websiteDarkLogo = '';
};
const {resetFields} = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateCmsWebsite : addCmsWebsite;
if (!isUpdate.value) {
updateVisible(false);
message.loading('创建过程中请勿刷新页面!', 0)
}
const formData = {
...form,
type: 20,
adminUrl: `mp.websoft.top`,
files: JSON.stringify(files.value),
};
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
updateVisible(false);
updateCmsDomain({
websiteId: form.websiteId,
domain: `${localStorage.getItem('TenantId')}.shoplnk.cn`
});
updateTenant({
tenantName: `${form.websiteName}`
}).then(() => {
})
localStorage.setItem('Domain', `${form.websiteCode}.shoplnk.cn`);
localStorage.setItem('WebsiteId', `${form.websiteId}`);
localStorage.setItem('WebsiteName', `${form.websiteName}`);
message.destroy();
message.success(msg);
// window.location.reload();
emit('done');
})
.catch((e) => {
loading.value = false;
message.destroy();
message.error(e.message);
});
})
.catch(() => {
});
};
const reload = async () => {
const data = await getSiteInfo()
if (data) {
console.log(data)
assignFields({
...data
});
images.value.push(
{
uid: uuid(),
url: data.websiteLogo,
status: 'done'
}
)
}
}
watch(
() => router.currentRoute.value.query,
(query) => {
if (query) {
reload();
}
},
{immediate: true}
);
// watch(
// () => props.visible,
// (visible) => {
// if (visible) {
// images.value = [];
// files.value = [];
// websiteQrcode.value = [];
// if (props.data?.websiteId) {
// assignObject(form, props.data);
// if (props.data.websiteLogo) {
// images.value.push({
// uid: uuid(),
// url: props.data.websiteLogo,
// status: 'done'
// });
// }
// if (props.data.websiteDarkLogo) {
// websiteQrcode.value.push({
// uid: uuid(),
// url: props.data.websiteDarkLogo,
// status: 'done'
// });
// }
// if (props.data.files) {
// files.value = JSON.parse(props.data.files);
// }
// if (props.data.websiteCode) {
// oldDomain.value = props.data.websiteCode;
// }
// isUpdate.value = true;
// } else {
// isUpdate.value = false;
// }
// } else {
// resetFields();
// }
// },
// {immediate: true}
// );
</script>
<script lang="ts">
export default {
name: 'CmsWebsite'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,39 @@
<!-- 机构选择下拉框 -->
<template>
<a-tree-select
allow-clear
tree-default-expand-all
:placeholder="placeholder"
:value="value || undefined"
:tree-data="data"
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
@update:value="updateValue"
/>
</template>
<script lang="ts" setup>
import type { Organization } from '@/api/system/organization/model';
const emit = defineEmits<{
(e: 'update:value', value?: number): void;
}>();
withDefaults(
defineProps<{
// 选中的数据(v-modal)
value?: number;
// 提示信息
placeholder?: string;
// 机构数据
data: Organization[];
}>(),
{
placeholder: '请选择角色'
}
);
/* 更新选中数据 */
const updateValue = (value?: number) => {
emit('update:value', value);
};
</script>

View File

@@ -0,0 +1,71 @@
<!-- 角色选择下拉框 -->
<template>
<a-select
allow-clear
mode="multiple"
:value="roleIds"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
>
<a-select-option
v-for="item in data"
:key="item.roleId"
:value="item.roleId"
>
{{ item.roleName }}
</a-select-option>
</a-select>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { message } from 'ant-design-vue/es';
import { listRoles } from '@/api/system/role';
import type { Role } from '@/api/system/role/model';
const emit = defineEmits<{
(e: 'update:value', value: Role[]): void;
(e: 'blur'): void;
}>();
const props = withDefaults(
defineProps<{
// 选中的角色
value?: Role[];
//
placeholder?: string;
}>(),
{
placeholder: '请选择角色'
}
);
// 选中的角色id
const roleIds = computed(() => props.value?.map((d) => d.roleId as number));
// 角色数据
const data = ref<Role[]>([]);
/* 更新选中数据 */
const updateValue = (value: number[]) => {
emit(
'update:value',
value.map((v) => ({ roleId: v }))
);
};
/* 获取角色数据 */
listRoles()
.then((list) => {
data.value = list;
})
.catch((e) => {
message.error(e.message);
});
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,45 @@
<!-- 角色选择下拉框 -->
<template>
<a-select
show-search
optionFilterProp="label"
:options="data"
allow-clear
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
@blur="onBlur"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
}>();
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择性别'
}
);
// 字典数据
const data = getDictionaryOptions('sex');
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>

View File

@@ -0,0 +1,299 @@
<!-- 管理员编辑弹窗 -->
<template>
<ele-modal
:width="500"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '编辑员工' : '添加员工'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 17, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="姓名" name="realName">
<a-input
allow-clear
:maxlength="20"
:disabled="isUpdate"
placeholder="请输入真实姓名"
v-model:value="form.realName"
/>
</a-form-item>
<a-form-item label="手机号" name="phone">
<a-input
allow-clear
:maxlength="11"
:disabled="isUpdate"
placeholder="请输入手机号"
v-model:value="form.phone"
/>
</a-form-item>
<a-form-item label="角色" name="roles">
<role-select v-model:value="form.roles" />
</a-form-item>
<a-form-item v-if="!isUpdate" label="登录密码" name="password">
<a-input-password
:maxlength="20"
v-model:value="form.password"
placeholder="请输入登录密码"
/>
</a-form-item>
<a-form-item label="性别" name="sex">
<DictSelect
dict-code="sex"
:placeholder="`请选择性别`"
v-model:value="form.sexName"
@done="chooseSex"
/>
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input
allow-clear
:maxlength="100"
placeholder="请输入邮箱"
v-model:value="form.email"
/>
</a-form-item>
<a-form-item label="所属机构" name="type">
<org-select
:data="organizationList"
placeholder="请选择所属机构"
v-model:value="form.organizationId"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { emailReg, phoneReg } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import RoleSelect from './role-select.vue';
import { addUser, updateUser, checkExistence } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
import OrgSelect from './org-select.vue';
// import { getDictionaryOptions } from '@/utils/common';
import { Organization } from '@/api/system/organization/model';
import { Grade } from '@/api/user/grade/model';
import {TEMPLATE_ID} from "@/config/setting";
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
// 获取字典数据
// const userTypeData = getDictionaryOptions('userType');
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: User | null;
// 全部机构
organizationList: Organization[];
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<User>({
type: undefined,
userId: undefined,
username: '',
nickname: '',
realName: '',
companyName: '',
sex: undefined,
sexName: undefined,
roles: [],
email: '',
phone: '',
mobile: '',
password: '',
introduction: '',
organizationId: undefined,
birthday: '',
idCard: '',
comments: '',
gradeName: '',
isAdmin: true,
gradeId: undefined,
templateId: TEMPLATE_ID
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
username: [
{
required: true,
type: 'string',
validator: (_rule: Rule, value: string) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('请输入管理员账号');
}
checkExistence('username', value, props.data?.userId)
.then(() => {
reject('账号已经存在');
})
.catch(() => {
resolve();
});
});
},
trigger: 'blur'
}
],
nickname: [
{
required: true,
message: '请输入昵称',
type: 'string',
trigger: 'blur'
}
],
realName: [
{
required: true,
message: '请输入真实姓名',
type: 'string',
trigger: 'blur'
}
],
// sex: [
// {
// required: true,
// message: '请选择性别',
// type: 'string',
// trigger: 'blur'
// }
// ],
roles: [
{
required: true,
message: '请选择角色',
type: 'array',
trigger: 'blur'
}
],
email: [
{
pattern: emailReg,
message: '邮箱格式不正确',
type: 'string',
trigger: 'blur'
}
],
password: [
{
required: true,
type: 'string',
validator: async (_rule: Rule, value: string) => {
if (isUpdate.value || /^[\S]{5,18}$/.test(value)) {
return Promise.resolve();
}
return Promise.reject('密码必须为5-18位非空白字符');
},
trigger: 'blur'
}
],
phone: [
{
required: true,
pattern: phoneReg,
message: '手机号格式不正确',
type: 'string',
trigger: 'blur'
}
]
});
const chooseGradeId = (data: Grade) => {
form.gradeName = data.name;
form.gradeId = data.gradeId;
};
const chooseSex = (data: any) => {
form.sex = data.key;
form.sexName = data.label;
};
const updateIsAdmin = (value: boolean) => {
form.isAdmin = value;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
form.username = form.phone;
form.nickname = form.realName;
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
assignFields({
...props.data,
password: ''
});
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>

View File

@@ -0,0 +1,88 @@
<!-- 用户导入弹窗 -->
<template>
<ele-modal
:width="520"
:footer="null"
title="导入用户"
:visible="visible"
@update:visible="updateVisible"
>
<a-spin :spinning="loading">
<a-upload-dragger
accept=".xls,.xlsx"
:show-upload-list="false"
:customRequest="doUpload"
style="padding: 24px 0; margin-bottom: 16px"
>
<p class="ant-upload-drag-icon">
<cloud-upload-outlined />
</p>
<p class="ant-upload-hint">将文件拖到此处或点击上传</p>
</a-upload-dragger>
</a-spin>
<div class="ele-text-center">
<span>只能上传xlsxlsx文件</span>
<a
href="https://oss.wsdns.cn/20200610/用户导入模板.xlsx"
download="用户导入模板.xlsx"
>
下载模板
</a>
</div>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue/es';
import { CloudUploadOutlined } from '@ant-design/icons-vue';
import { importUsers } from '@/api/system/user';
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
defineProps<{
// 是否打开弹窗
visible: boolean;
}>();
// 导入请求状态
const loading = ref(false);
/* 上传 */
const doUpload = ({ file }) => {
if (
![
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
].includes(file.type)
) {
message.error('只能选择 excel 文件');
return false;
}
if (file.size / 1024 / 1024 > 10) {
message.error('大小不能超过 10MB');
return false;
}
loading.value = true;
importUsers(file)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
return false;
};
/* 更新 visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
</script>

View File

@@ -0,0 +1,143 @@
<!-- 用户编辑弹窗 -->
<template>
<a-drawer
:width="680"
:visible="visible"
:confirm-loading="loading"
:title="'基本信息'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
:footer="null"
>
<a-form
:label-col="{ md: { span: 6 }, sm: { span: 24 } }"
:wrapper-col="{ md: { span: 19 }, sm: { span: 24 } }"
>
<a-row :gutter="16">
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="账号">
<span class="ele-text">{{ user.username }}</span>
</a-form-item>
<a-form-item label="昵称">
<span class="ele-text">{{ user.nickname }}</span>
</a-form-item>
<a-form-item label="性别">
<span class="ele-text">{{ user.sexName }}</span>
</a-form-item>
<a-form-item label="手机号">
<span class="ele-text">{{ user.phone }}</span>
</a-form-item>
<a-form-item label="角色">
<a-tag v-for="item in user.roles" :key="item.roleId" color="blue">
{{ item.roleName }}
</a-tag>
</a-form-item>
<a-form-item label="状态">
<a-badge
v-if="typeof user.status === 'number'"
:status="(['processing', 'error'][user.status] as any)"
:text="['正常', '冻结'][user.status]"
/>
</a-form-item>
<a-form-item label="地址">
<span class="ele-text">{{ user.address }}</span>
</a-form-item>
</a-col>
<a-col
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
>
<a-form-item label="可用余额">
<span class="ele-text-success">{{ formatNumber(user.balance) }}</span>
</a-form-item>
<a-form-item label="可用积分">
<span class="ele-text">{{ user.points }}</span>
</a-form-item>
<a-form-item label="实际消费">
<span class="ele-text">{{ user.payMoney }}</span>
</a-form-item>
<a-form-item label="机构/部门">
<span class="ele-text">{{ user.organizationName }}</span>
</a-form-item>
<a-form-item label="头像">
<a-image :src="user.avatar" :width="36" />
</a-form-item>
<a-form-item label="生日">
<span class="ele-text">{{ user.birthday }}</span>
</a-form-item>
<a-form-item label="创建时间">
<span class="ele-text">{{ user.createTime }}</span>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro';
import type { User } from '@/api/system/user/model';
import { useThemeStore } from '@/store/modules/theme';
import { formatNumber } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: User | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 用户信息
const user = reactive<User>({
username: '',
nickname: '',
sexName: '',
phone: '',
avatar: '',
balance: undefined,
points: 0,
payMoney: 0,
birthday: '',
address: '',
roles: [],
createTime: undefined,
status: undefined
});
// 请求状态
const loading = ref(true);
const { resetFields } = useForm(user);
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
loading.value = false;
assignObject(user, props.data);
}
} else {
resetFields();
}
}
);
</script>

View File

@@ -0,0 +1,111 @@
<!-- 搜索表单 -->
<template>
<a-form
:label-col="
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
"
:wrapper-col="
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
"
>
<a-row :gutter="8">
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item label="用户账号">
<a-input
v-model:value.trim="form.username"
placeholder="请输入"
allow-clear
/>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item label="昵称">
<a-input
v-model:value.trim="form.nickname"
placeholder="请输入"
allow-clear
/>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item label="性别">
<a-select v-model:value="form.sex" placeholder="请选择" allow-clear>
<a-select-option value="1">男</a-select-option>
<a-select-option value="2">女</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col
v-bind="
styleResponsive
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
: { span: 6 }
"
>
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
<a-space>
<a-button type="primary" @click="search">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import type { UserParam } from '@/api/system/user/model';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 默认搜索条件
where?: UserParam;
}>();
const emit = defineEmits<{
(e: 'search', where?: UserParam): void;
}>();
// 表单数据
const { form, resetFields } = useFormData<UserParam>({
username: '',
nickname: '',
sex: undefined,
...props.where
});
/* 搜索 */
const search = () => {
emit('search', form);
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
</script>

View File

@@ -0,0 +1,274 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑' : '添加'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="用户唯一小程序id" name="openId">
<a-input
allow-clear
placeholder="请输入用户唯一小程序id"
v-model:value="form.openId"
/>
</a-form-item>
<a-form-item label="小程序用户秘钥" name="sessionKey">
<a-input
allow-clear
placeholder="请输入小程序用户秘钥"
v-model:value="form.sessionKey"
/>
</a-form-item>
<a-form-item label="用户名" name="username">
<a-input
allow-clear
placeholder="请输入用户名"
v-model:value="form.username"
/>
</a-form-item>
<a-form-item label="头像地址" name="avatarUrl">
<a-input
allow-clear
placeholder="请输入头像地址"
v-model:value="form.avatarUrl"
/>
</a-form-item>
<a-form-item label="1男2女" name="gender">
<a-input
allow-clear
placeholder="请输入1男2女"
v-model:value="form.gender"
/>
</a-form-item>
<a-form-item label="国家" name="country">
<a-input
allow-clear
placeholder="请输入国家"
v-model:value="form.country"
/>
</a-form-item>
<a-form-item label="省份" name="province">
<a-input
allow-clear
placeholder="请输入省份"
v-model:value="form.province"
/>
</a-form-item>
<a-form-item label="城市" name="city">
<a-input
allow-clear
placeholder="请输入城市"
v-model:value="form.city"
/>
</a-form-item>
<a-form-item label="手机号码" name="phone">
<a-input
allow-clear
placeholder="请输入手机号码"
v-model:value="form.phone"
/>
</a-form-item>
<a-form-item label="积分" name="integral">
<a-input
allow-clear
placeholder="请输入积分"
v-model:value="form.integral"
/>
</a-form-item>
<a-form-item label="余额" name="money">
<a-input
allow-clear
placeholder="请输入余额"
v-model:value="form.money"
/>
</a-form-item>
<a-form-item label="" name="idcard">
<a-input
allow-clear
placeholder="请输入"
v-model:value="form.idcard"
/>
</a-form-item>
<a-form-item label="" name="truename">
<a-input
allow-clear
placeholder="请输入"
v-model:value="form.truename"
/>
</a-form-item>
<a-form-item label="是否管理员1是2否" name="isAdmin">
<a-input
allow-clear
placeholder="请输入是否管理员1是2否"
v-model:value="form.isAdmin"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addUser, updateUser } from '@/api/system/user';
import { User } from '@/api/system/user/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: User | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<User>({
id: undefined,
openId: undefined,
sessionKey: undefined,
username: undefined,
avatarUrl: undefined,
gender: undefined,
country: undefined,
province: undefined,
city: undefined,
phone: undefined,
integral: undefined,
money: undefined,
createTime: undefined,
idcard: undefined,
truename: undefined,
isAdmin: undefined,
tenantId: undefined,
userId: undefined,
userName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
userName: [
{
required: true,
type: 'string',
message: '请填写名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,130 @@
<template>
<div class="ele-body">
<a-card title="基本信息" :bordered="false">
<a-form
class="ele-form-detail"
:label-col="
styleResponsive ? { md: 2, sm: 4, xs: 6 } : { flex: '90px' }
"
:wrapper-col="
styleResponsive ? { md: 22, sm: 20, xs: 18 } : { flex: '1' }
"
>
<a-form-item label="账号">
<div class="ele-text-secondary">{{ form.username }}</div>
</a-form-item>
<a-form-item label="昵称">
<div class="ele-text-secondary">{{ form.nickname }}</div>
</a-form-item>
<a-form-item label="性别">
<div class="ele-text-secondary">{{ form.sexName }}</div>
</a-form-item>
<a-form-item label="手机号">
<div class="ele-text-secondary">{{ form.phone }}</div>
</a-form-item>
<a-form-item label="真实姓名">
<div class="ele-text-secondary">{{ form.realName }}</div>
</a-form-item>
<a-form-item label="别名">
<div class="ele-text-secondary">{{ form.alias }}</div>
</a-form-item>
<a-form-item label="角色">
<a-tag v-for="item in form.roles" :key="item.roleId" color="blue">
{{ item.roleName }}
</a-tag>
</a-form-item>
<a-form-item label="创建时间">
<div class="ele-text-secondary">{{ form.createTime }}</div>
</a-form-item>
<a-form-item label="状态">
<a-badge
v-if="typeof form.status === 'number'"
:status="(['processing', 'error'][form.status] as any)"
:text="['正常', '冻结'][form.status]"
/>
</a-form-item>
</a-form>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, unref } from 'vue';
import { useRouter } from 'vue-router';
import { message } from 'ant-design-vue/es';
import { toDateString } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { setPageTabTitle } from '@/utils/page-tab-util';
import { getUser } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
const ROUTE_PATH = '/system/user/details';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const { currentRoute } = useRouter();
// 用户信息
const { form, assignFields } = useFormData<User>({
userId: undefined,
alias: '',
realName: '',
username: '',
nickname: '',
sexName: '',
phone: '',
roles: [],
createTime: undefined,
status: undefined
});
// 请求状态
const loading = ref(true);
/* */
const query = () => {
const { query } = unref(currentRoute);
const id = query.id;
if (!id || form.userId === Number(id)) {
return;
}
loading.value = true;
getUser(Number(id))
.then((data) => {
loading.value = false;
assignFields({
...data,
createTime: toDateString(data.createTime)
});
// 修改页签标题
if (unref(currentRoute).path === ROUTE_PATH) {
setPageTabTitle(data.nickname + '的信息');
}
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
};
watch(
currentRoute,
(route) => {
const { path } = unref(route);
if (path !== ROUTE_PATH) {
return;
}
query();
},
{ immediate: true }
);
</script>
<script lang="ts">
export default {
name: 'SystemUserDetails'
};
</script>

View File

@@ -0,0 +1,446 @@
<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<a-card title="角色管理" style="margin-bottom: 20px">
<template #extra>
<a-button type="primary" @click="push(`/system/role`)">添加角色</a-button>
</template>
<a-space>
<template v-for="(item,_) in roles" :key="index">
<a-button>{{ item.roleName }}</a-button>
</template>
</a-space>
</a-card>
<a-card :bordered="false">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="userId"
:columns="columns"
:datasource="datasource"
:scroll="{ x: 1300 }"
:where="defaultWhere"
:customRow="customRow"
cache-key="proSystemUserTable"
>
<template #toolbar>
<a-space>
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
<template #icon>
<plus-outlined/>
</template>
<span>新建</span>
</a-button>
<a-input-search
allow-clear
v-model:value="searchText"
placeholder="请输入关键词"
@search="reload"
@pressEnter="reload"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'avatar'">
<a-avatar
:size="30"
:src="`${record.avatar}`"
style="margin-right: 4px"
>
<template #icon>
<UserOutlined/>
</template>
</a-avatar>
</template>
<template v-if="column.key === 'nickname'">
<span>{{ record.nickname }}</span>
</template>
<template v-if="column.key === 'mobile'">
<span v-if="hasRole('superAdmin')">{{ record.phone }}</span>
<span v-else>{{ record.mobile }}</span>
</template>
<template v-if="column.key === 'roles'">
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
{{ item.roleName }}
</a-tag>
</template>
<template v-if="column.key === 'platform'">
<WechatOutlined v-if="record.platform === 'MP-WEIXIN'"/>
<Html5Outlined v-if="record.platform === 'H5'"/>
<ChromeOutlined v-if="record.platform === 'WEB'"/>
</template>
<template v-if="column.key === 'balance'">
<span class="ele-text-success">
{{ formatNumber(record.balance) }}
</span>
</template>
<template v-if="column.key === 'expendMoney'">
<span class="ele-text-warning">
{{ formatNumber(record.expendMoney) }}
</span>
</template>
<template v-if="column.key === 'isAdmin'">
<a-switch
:checked="record.isAdmin == 1"
@change="updateIsAdmin(record)"
/>
</template>
<template v-if="column.key === 'action'">
<div>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical"/>
<a @click="resetPsw(record)">重置</a>
<a-divider type="vertical"/>
<a-popconfirm
placement="topRight"
title="确定要删除此用户吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</div>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<user-edit
v-model:visible="showEdit"
:data="current"
:organization-list="data"
@done="reload"
/>
<!-- 导入弹窗 -->
<user-import v-model:visible="showImport" @done="reload"/>
<!-- 用户详情 -->
<user-info v-model:visible="showInfo" :data="current" @done="reload"/>
</a-page-header>
</template>
<script lang="ts" setup>
import {createVNode, ref, reactive, watch} from 'vue';
import {message, Modal} from 'ant-design-vue/es';
import {
PlusOutlined,
UserOutlined,
Html5Outlined,
ChromeOutlined,
WechatOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import type {EleProTable} from 'ele-admin-pro/es';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import {messageLoading, formatNumber} from 'ele-admin-pro/es';
import UserEdit from './components/user-edit.vue';
import UserImport from './components/user-import.vue';
import UserInfo from './components/user-info.vue';
import {toDateString} from 'ele-admin-pro';
import {
pageUsers,
removeUser,
removeUsers,
updateUserPassword,
updateUser
} from '@/api/system/user';
import type {User, UserParam} from '@/api/system/user/model';
import {toTreeData, uuid} from 'ele-admin-pro';
import {listRoles} from '@/api/system/role';
import {listOrganizations} from '@/api/system/organization';
import {Organization} from '@/api/system/organization/model';
import {hasRole} from '@/utils/permission';
import {getPageTitle, push} from "@/utils/common";
import router from "@/router";
import {listUserRole} from "@/api/system/userRole";
// 加载状态
const loading = ref(true);
// 树形数据
const data = ref<Organization[]>([]);
// 树展开的key
const expandedRowKeys = ref<number[]>([]);
// 树选中的key
const selectedRowKeys = ref<number[]>([]);
// 表格选中数据
const selection = ref<User[]>([]);
// 当前编辑数据
const current = ref<User | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示用户详情
const showInfo = ref(false);
// 是否显示用户导入弹窗
const showImport = ref(false);
const userType = ref<number>();
const searchText = ref('');
// 加载角色
const roles = ref<any[]>([]);
// 加载机构
listOrganizations()
.then((list) => {
loading.value = false;
const eks: number[] = [];
list.forEach((d) => {
d.key = d.organizationId;
d.value = d.organizationId;
d.title = d.organizationName;
if (typeof d.key === 'number') {
eks.push(d.key);
}
});
expandedRowKeys.value = eks;
data.value = toTreeData({
data: list,
idField: 'organizationId',
parentIdField: 'parentId'
});
if (list.length) {
if (typeof list[0].key === 'number') {
selectedRowKeys.value = [list[0].key];
}
// current.value = list[0];
} else {
selectedRowKeys.value = [];
// current.value = null;
}
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'userId',
width: 90,
showSorterTooltip: false
},
{
title: '真实姓名',
dataIndex: 'realName',
align: 'center',
showSorterTooltip: false
},
{
title: '手机号码',
dataIndex: 'mobile',
align: 'center',
key: 'mobile',
showSorterTooltip: false
},
{
title: '性别',
dataIndex: 'sexName',
align: 'center',
showSorterTooltip: false
},
{
title: '所属部门',
dataIndex: 'organizationName',
key: 'organizationName',
align: 'center'
},
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
align: 'center'
},
{
title: '注册时间',
dataIndex: 'createTime',
sorter: true,
align: 'center',
showSorterTooltip: false,
ellipsis: true,
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center'
}
]);
// 默认搜索条件
const defaultWhere = reactive({
username: '',
nickname: ''
});
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
where = {};
where.roleId = filters.roles;
where.keywords = searchText.value;
where.isAdmin = 1;
return pageUsers({page, limit, ...where, ...orders});
};
/* 搜索 */
const reload = (where?: UserParam) => {
selection.value = [];
tableRef?.value?.reload({where});
};
/* 打开编辑弹窗 */
const openEdit = (row?: User) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开用户详情弹窗 */
const openInfo = (row?: User) => {
current.value = row ?? null;
showInfo.value = true;
};
/* 打开编辑弹窗 */
const openImport = () => {
showImport.value = true;
};
const handleTabs = (e) => {
userType.value = Number(e.target.value);
reload();
};
/* 删除单个 */
const remove = (row: User) => {
const hide = messageLoading('请求中..', 0);
removeUser(row.userId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的用户吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = messageLoading('请求中..', 0);
removeUsers(selection.value.map((d) => d.userId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 重置用户密码 */
const resetPsw = (row: User) => {
Modal.confirm({
title: '提示',
content: '确定要重置此用户的密码吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
const password = uuid(8);
updateUserPassword(row.userId, password)
.then((msg) => {
hide();
message.success(msg + ',新密码:' + password);
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 修改用户状态 */
const updateIsAdmin = (row: User) => {
row.isAdmin = row.isAdmin ? 0 : 1;
updateUser(row)
.then((msg) => {
message.success(msg);
})
.catch((e) => {
message.error(e.message);
});
};
/* 自定义行属性 */
const customRow = (record: User) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
const query = async () => {
const info = await listRoles({})
if(info){
roles.value = info
}
}
watch(
() => router.currentRoute.value.query,
() => {
query();
},
{immediate: true}
);
</script>
<script lang="ts">
export default {
name: 'SystemAdmin'
};
</script>
<style lang="less" scoped>
.user-box {
display: flex;
align-items: center;
.user-info {
display: flex;
flex-direction: column;
align-items: start;
}
}
</style>

View File

@@ -25,17 +25,17 @@
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<a-space class="flex items-center">
<a-image :src="record.image" v-if="record.image" :width="50"/>
<a-space class="flex items-center cursor-pointer">
<a-image :src="record.image" v-if="record.image" :preview="false" :width="50"/>
<span class="text-gray-700 font-bold">{{ record.name }}</span>
</a-space>
</template>
<template v-if="column.key === 'recommend'">
<a-space @click="onRecommend(record)">
<span v-if="record.recommend === 1" class="ele-text-success"
<a-space @click.stop="onRecommend(record)">
<span v-if="record.recommend === 1" class="ele-text-success cursor-pointer"
><CheckOutlined
/></span>
<span v-else class="ele-text-placeholder"><CloseOutlined/></span>
<span v-else class="ele-text-placeholder cursor-pointer"><CloseOutlined/></span>
</a-space>
</template>
<template v-if="column.key === 'status'">
@@ -299,11 +299,11 @@ const customRow = (record: ShopGoods) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
openEdit(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
// openEdit(record);
}
};
};

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,219 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑商品优惠券表' : '添加商品优惠券表'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="商品id" name="goodsId">
<a-input
allow-clear
placeholder="请输入商品id"
v-model:value="form.goodsId"
/>
</a-form-item>
<a-form-item label="优惠劵id" name="issueCouponId">
<a-input
allow-clear
placeholder="请输入优惠劵id"
v-model:value="form.issueCouponId"
/>
</a-form-item>
<a-form-item label="排序(数字越小越靠前)" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
<a-form-item label="状态, 0正常, 1冻结" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">显示</a-radio>
<a-radio :value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="是否删除, 0否, 1是" name="deleted">
<a-input
allow-clear
placeholder="请输入是否删除, 0否, 1是"
v-model:value="form.deleted"
/>
</a-form-item>
<a-form-item label="用户ID" name="userId">
<a-input
allow-clear
placeholder="请输入用户ID"
v-model:value="form.userId"
/>
</a-form-item>
<a-form-item label="修改时间" name="updateTime">
<a-input
allow-clear
placeholder="请输入修改时间"
v-model:value="form.updateTime"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addShopGoodsCoupon, updateShopGoodsCoupon } from '@/api/shop/shopGoodsCoupon';
import { ShopGoodsCoupon } from '@/api/shop/shopGoodsCoupon/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: ShopGoodsCoupon | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<ShopGoodsCoupon>({
id: undefined,
goodsId: undefined,
issueCouponId: undefined,
sortNumber: undefined,
status: undefined,
deleted: undefined,
userId: undefined,
tenantId: undefined,
createTime: undefined,
updateTime: undefined,
shopGoodsCouponId: undefined,
shopGoodsCouponName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
shopGoodsCouponName: [
{
required: true,
type: 'string',
message: '请填写商品优惠券表名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateShopGoodsCoupon : addShopGoodsCoupon;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,257 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="shopGoodsCouponId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<ShopGoodsCouponEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import ShopGoodsCouponEdit from './components/shopGoodsCouponEdit.vue';
import { pageShopGoodsCoupon, removeShopGoodsCoupon, removeBatchShopGoodsCoupon } from '@/api/shop/shopGoodsCoupon';
import type { ShopGoodsCoupon, ShopGoodsCouponParam } from '@/api/shop/shopGoodsCoupon/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ShopGoodsCoupon[]>([]);
// 当前编辑数据
const current = ref<ShopGoodsCoupon | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageShopGoodsCoupon({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90,
},
{
title: '商品id',
dataIndex: 'goodsId',
key: 'goodsId',
align: 'center',
},
{
title: '优惠劵id',
dataIndex: 'issueCouponId',
key: 'issueCouponId',
align: 'center',
},
{
title: '排序(数字越小越靠前)',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center',
},
{
title: '状态, 0正常, 1冻结',
dataIndex: 'status',
key: 'status',
align: 'center',
},
{
title: '是否删除, 0否, 1是',
dataIndex: 'deleted',
key: 'deleted',
align: 'center',
},
{
title: '用户ID',
dataIndex: 'userId',
key: 'userId',
align: 'center',
},
{
title: '注册时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '修改时间',
dataIndex: 'updateTime',
key: 'updateTime',
align: 'center',
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ShopGoodsCouponParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ShopGoodsCoupon) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ShopGoodsCoupon) => {
const hide = message.loading('请求中..', 0);
removeShopGoodsCoupon(row.shopGoodsCouponId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchShopGoodsCoupon(selection.value.map((d) => d.shopGoodsCouponId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: ShopGoodsCoupon) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'ShopGoodsCoupon'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,278 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑商品sku列表' : '添加商品sku列表'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="商品ID" name="goodsId">
<a-input
allow-clear
placeholder="请输入商品ID"
v-model:value="form.goodsId"
/>
</a-form-item>
<a-form-item label="商品属性索引值 (attr_value|attr_value[|....])" name="sku">
<a-input
allow-clear
placeholder="请输入商品属性索引值 (attr_value|attr_value[|....])"
v-model:value="form.sku"
/>
</a-form-item>
<a-form-item
label="商品图片"
name="image">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseImage"
@del="onDeleteItem"
/>
</a-form-item>
<a-form-item label="商品价格" name="price">
<a-input
allow-clear
placeholder="请输入商品价格"
v-model:value="form.price"
/>
</a-form-item>
<a-form-item label="市场价格" name="salePrice">
<a-input
allow-clear
placeholder="请输入市场价格"
v-model:value="form.salePrice"
/>
</a-form-item>
<a-form-item label="成本价" name="cost">
<a-input
allow-clear
placeholder="请输入成本价"
v-model:value="form.cost"
/>
</a-form-item>
<a-form-item label="库存" name="stock">
<a-input
allow-clear
placeholder="请输入库存"
v-model:value="form.stock"
/>
</a-form-item>
<a-form-item label="sku编码" name="skuNo">
<a-input
allow-clear
placeholder="请输入sku编码"
v-model:value="form.skuNo"
/>
</a-form-item>
<a-form-item label="商品条码" name="barCode">
<a-input
allow-clear
placeholder="请输入商品条码"
v-model:value="form.barCode"
/>
</a-form-item>
<a-form-item label="重量" name="weight">
<a-input
allow-clear
placeholder="请输入重量"
v-model:value="form.weight"
/>
</a-form-item>
<a-form-item label="体积" name="volume">
<a-input
allow-clear
placeholder="请输入体积"
v-model:value="form.volume"
/>
</a-form-item>
<a-form-item label="唯一值" name="uuid">
<a-input
allow-clear
placeholder="请输入唯一值"
v-model:value="form.uuid"
/>
</a-form-item>
<a-form-item label="状态, 0正常, 1异常" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">显示</a-radio>
<a-radio :value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="备注" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入描述"
v-model:value="form.comments"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addShopGoodsSku, updateShopGoodsSku } from '@/api/shop/shopGoodsSku';
import { ShopGoodsSku } from '@/api/shop/shopGoodsSku/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: ShopGoodsSku | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<ShopGoodsSku>({
id: undefined,
goodsId: undefined,
sku: undefined,
image: undefined,
price: undefined,
salePrice: undefined,
cost: undefined,
stock: undefined,
skuNo: undefined,
barCode: undefined,
weight: undefined,
volume: undefined,
uuid: undefined,
status: undefined,
comments: undefined,
tenantId: undefined,
createTime: undefined,
shopGoodsSkuId: undefined,
shopGoodsSkuName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
shopGoodsSkuName: [
{
required: true,
type: 'string',
message: '请填写商品sku列表名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateShopGoodsSku : addShopGoodsSku;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,299 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="shopGoodsSkuId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<ShopGoodsSkuEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import ShopGoodsSkuEdit from './components/shopGoodsSkuEdit.vue';
import { pageShopGoodsSku, removeShopGoodsSku, removeBatchShopGoodsSku } from '@/api/shop/shopGoodsSku';
import type { ShopGoodsSku, ShopGoodsSkuParam } from '@/api/shop/shopGoodsSku/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ShopGoodsSku[]>([]);
// 当前编辑数据
const current = ref<ShopGoodsSku | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageShopGoodsSku({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '主键ID',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90,
},
{
title: '商品ID',
dataIndex: 'goodsId',
key: 'goodsId',
align: 'center',
},
{
title: '商品属性索引值 (attr_value|attr_value[|....])',
dataIndex: 'sku',
key: 'sku',
align: 'center',
},
{
title: '商品图片',
dataIndex: 'image',
key: 'image',
align: 'center',
},
{
title: '商品价格',
dataIndex: 'price',
key: 'price',
align: 'center',
},
{
title: '市场价格',
dataIndex: 'salePrice',
key: 'salePrice',
align: 'center',
},
{
title: '成本价',
dataIndex: 'cost',
key: 'cost',
align: 'center',
},
{
title: '库存',
dataIndex: 'stock',
key: 'stock',
align: 'center',
},
{
title: 'sku编码',
dataIndex: 'skuNo',
key: 'skuNo',
align: 'center',
},
{
title: '商品条码',
dataIndex: 'barCode',
key: 'barCode',
align: 'center',
},
{
title: '重量',
dataIndex: 'weight',
key: 'weight',
align: 'center',
},
{
title: '体积',
dataIndex: 'volume',
key: 'volume',
align: 'center',
},
{
title: '唯一值',
dataIndex: 'uuid',
key: 'uuid',
align: 'center',
},
{
title: '状态, 0正常, 1异常',
dataIndex: 'status',
key: 'status',
align: 'center',
},
{
title: '备注',
dataIndex: 'comments',
key: 'comments',
align: 'center',
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ShopGoodsSkuParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ShopGoodsSku) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ShopGoodsSku) => {
const hide = message.loading('请求中..', 0);
removeShopGoodsSku(row.shopGoodsSkuId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchShopGoodsSku(selection.value.map((d) => d.shopGoodsSkuId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: ShopGoodsSku) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'ShopGoodsSku'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,201 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑商品多规格' : '添加商品多规格'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="商品ID" name="goodsId">
<a-input
allow-clear
placeholder="请输入商品ID"
v-model:value="form.goodsId"
/>
</a-form-item>
<a-form-item label="规格ID" name="specId">
<a-input
allow-clear
placeholder="请输入规格ID"
v-model:value="form.specId"
/>
</a-form-item>
<a-form-item label="规格名称" name="specName">
<a-input
allow-clear
placeholder="请输入规格名称"
v-model:value="form.specName"
/>
</a-form-item>
<a-form-item label="规格值" name="specValue">
<a-input
allow-clear
placeholder="请输入规格值"
v-model:value="form.specValue"
/>
</a-form-item>
<a-form-item label="活动类型 0=商品1=秒杀2=砍价3=拼团" name="type">
<a-input
allow-clear
placeholder="请输入活动类型 0=商品1=秒杀2=砍价3=拼团"
v-model:value="form.type"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addShopGoodsSpec, updateShopGoodsSpec } from '@/api/shop/shopGoodsSpec';
import { ShopGoodsSpec } from '@/api/shop/shopGoodsSpec/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: ShopGoodsSpec | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<ShopGoodsSpec>({
id: undefined,
goodsId: undefined,
specId: undefined,
specName: undefined,
specValue: undefined,
type: undefined,
tenantId: undefined,
shopGoodsSpecId: undefined,
shopGoodsSpecName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
shopGoodsSpecName: [
{
required: true,
type: 'string',
message: '请填写商品多规格名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateShopGoodsSpec : addShopGoodsSpec;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,236 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="shopGoodsSpecId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<ShopGoodsSpecEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import ShopGoodsSpecEdit from './components/shopGoodsSpecEdit.vue';
import { pageShopGoodsSpec, removeShopGoodsSpec, removeBatchShopGoodsSpec } from '@/api/shop/shopGoodsSpec';
import type { ShopGoodsSpec, ShopGoodsSpecParam } from '@/api/shop/shopGoodsSpec/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ShopGoodsSpec[]>([]);
// 当前编辑数据
const current = ref<ShopGoodsSpec | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageShopGoodsSpec({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '主键',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90,
},
{
title: '商品ID',
dataIndex: 'goodsId',
key: 'goodsId',
align: 'center',
},
{
title: '规格ID',
dataIndex: 'specId',
key: 'specId',
align: 'center',
},
{
title: '规格名称',
dataIndex: 'specName',
key: 'specName',
align: 'center',
},
{
title: '规格值',
dataIndex: 'specValue',
key: 'specValue',
align: 'center',
},
{
title: '活动类型 0=商品1=秒杀2=砍价3=拼团',
dataIndex: 'type',
key: 'type',
align: 'center',
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ShopGoodsSpecParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ShopGoodsSpec) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ShopGoodsSpec) => {
const hide = message.loading('请求中..', 0);
removeShopGoodsSpec(row.shopGoodsSpecId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchShopGoodsSpec(selection.value.map((d) => d.shopGoodsSpecId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: ShopGoodsSpec) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'ShopGoodsSpec'
};
</script>
<style lang="less" scoped></style>

View File

@@ -1,18 +1,28 @@
<!-- 用户编辑弹窗 -->
<template>
<ele-modal
:width="`80%`"
<a-drawer
:width="`65%`"
:visible="visible"
:confirm-loading="loading"
:maxable="maxAble"
:title="isUpdate ? '编辑订单' : '订单详情'"
:body-style="{ paddingBottom: '8px', background: '#f3f3f3' }"
@update:visible="updateVisible"
:maskClosable="false"
:footer="null"
@ok="save"
>
<a-card style="margin-bottom: 20px" :bordered="false">
<template #extra>
<a-space>
<a-button
type="primary"
@click="save"
:loading="loading"
>发货
</a-button>
<a-button @click="updateVisible(false)" danger>取消订单</a-button>
</a-space>
</template>
<a-card title="基本信息" style="margin-bottom: 20px" :bordered="false">
<a-descriptions :column="3">
<!-- 第一排-->
<a-descriptions-item
@@ -217,13 +227,13 @@
<a-tag v-if="form.isSettled == 1" color="green">已结算</a-tag>
</a-descriptions-item>
<!-- <a-descriptions-item span="3">-->
<!-- <a-divider/>-->
<!-- </a-descriptions-item>-->
<!-- <a-descriptions-item span="3">-->
<!-- <a-divider/>-->
<!-- </a-descriptions-item>-->
</a-descriptions>
</a-card>
<a-card class="order-card" :bordered="false">
<a-card title="商品信息" style="margin-bottom: 20px" :bordered="false">
<a-spin :spinning="loading">
<a-table
:data-source="orderGoods"
@@ -232,7 +242,16 @@
/>
</a-spin>
</a-card>
</ele-modal>
<a-card title="收货信息" style="margin-bottom: 20px" :bordered="false">
<a-spin :spinning="loading">
<a-table
:data-source="orderGoods"
:columns="columns"
:pagination="false"
/>
</a-spin>
</a-card>
</a-drawer>
</template>
<script lang="ts" setup>
@@ -466,7 +485,7 @@ const columns = ref<ColumnItem[]>([
title: '金额',
dataIndex: 'price',
align: 'center',
customRender: ({ record }) => {
customRender: ({record}) => {
return `${record.price || 0}`;
}
},
@@ -474,7 +493,7 @@ const columns = ref<ColumnItem[]>([
title: '数量',
dataIndex: 'quantity',
align: 'center',
customRender: ({ record }) => {
customRender: ({record}) => {
return record.quantity || 1;
}
},
@@ -488,7 +507,7 @@ const columns = ref<ColumnItem[]>([
title: '是否免费',
dataIndex: 'isFree',
align: 'center',
customRender: ({ record }) => {
customRender: ({record}) => {
return record.isFree ? '是' : '否';
}
}

View File

@@ -38,8 +38,8 @@
</template>
</template>
<template v-if="column.key === 'payStatus'">
<a-tag v-if="record.payStatus == 1" color="green" @click="updatePayStatus(record)">已付款</a-tag>
<a-tag v-if="record.payStatus == 0" @click="updatePayStatus(record)">未付款</a-tag>
<a-tag v-if="record.payStatus == 1" color="green" @click.stop="updatePayStatus(record)" class="cursor-pointer">已付款</a-tag>
<a-tag v-if="record.payStatus == 0" @click.stop="updatePayStatus(record)" class="cursor-pointer">未付款</a-tag>
<a-tag v-if="record.payStatus == 3">未付款,占场中</a-tag>
</template>
<template v-if="column.key === 'image'">
@@ -103,6 +103,7 @@ import type {
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import {getPageTitle} from "@/utils/common";
import { toDateString } from 'ele-admin-pro';
import OrderInfo from './components/orderInfo.vue';
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
import {pageShopOrder, repairOrder} from "@/api/shop/shopOrder";
@@ -204,7 +205,8 @@ const columns = ref<ColumnItem[]>([
align: 'center',
width: 180,
sorter: true,
ellipsis: true
ellipsis: true,
customRender: ({ text }) => toDateString(text)
},
// {
// title: '操作',
@@ -266,11 +268,11 @@ const customRow = (record: ShopOrder) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
openEdit(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
// openEdit(record);
}
};
};

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,228 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑规格' : '添加规格'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="规格名称" name="specName">
<a-input
allow-clear
placeholder="请输入规格名称"
v-model:value="form.specName"
/>
</a-form-item>
<a-form-item label="规格值" name="specValue">
<a-input
allow-clear
placeholder="请输入规格值"
v-model:value="form.specValue"
/>
</a-form-item>
<a-form-item label="商户ID" name="merchantId">
<a-input
allow-clear
placeholder="请输入商户ID"
v-model:value="form.merchantId"
/>
</a-form-item>
<a-form-item label="创建用户" name="userId">
<a-input
allow-clear
placeholder="请输入创建用户"
v-model:value="form.userId"
/>
</a-form-item>
<a-form-item label="更新者" name="updater">
<a-input
allow-clear
placeholder="请输入更新者"
v-model:value="form.updater"
/>
</a-form-item>
<a-form-item label="备注" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入描述"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">显示</a-radio>
<a-radio :value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序号" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addShopSpec, updateShopSpec } from '@/api/shop/shopSpec';
import { ShopSpec } from '@/api/shop/shopSpec/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: ShopSpec | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<ShopSpec>({
specId: undefined,
specName: undefined,
specValue: undefined,
merchantId: undefined,
userId: undefined,
updater: undefined,
comments: undefined,
status: undefined,
sortNumber: undefined,
tenantId: undefined,
createTime: undefined,
shopSpecId: undefined,
shopSpecName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
shopSpecName: [
{
required: true,
type: 'string',
message: '请填写规格名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateShopSpec : addShopSpec;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,263 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="shopSpecId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<ShopSpecEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import ShopSpecEdit from './components/shopSpecEdit.vue';
import { pageShopSpec, removeShopSpec, removeBatchShopSpec } from '@/api/shop/shopSpec';
import type { ShopSpec, ShopSpecParam } from '@/api/shop/shopSpec/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ShopSpec[]>([]);
// 当前编辑数据
const current = ref<ShopSpec | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageShopSpec({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '规格ID',
dataIndex: 'specId',
key: 'specId',
align: 'center',
width: 90,
},
{
title: '规格名称',
dataIndex: 'specName',
key: 'specName',
align: 'center',
},
{
title: '规格值',
dataIndex: 'specValue',
key: 'specValue',
align: 'center',
},
{
title: '商户ID',
dataIndex: 'merchantId',
key: 'merchantId',
align: 'center',
},
{
title: '创建用户',
dataIndex: 'userId',
key: 'userId',
align: 'center',
},
{
title: '更新者',
dataIndex: 'updater',
key: 'updater',
align: 'center',
},
{
title: '备注',
dataIndex: 'comments',
key: 'comments',
align: 'center',
},
{
title: '状态, 0正常, 1待修,2异常已修3异常未修',
dataIndex: 'status',
key: 'status',
align: 'center',
},
{
title: '排序号',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center',
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ShopSpecParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ShopSpec) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ShopSpec) => {
const hide = message.loading('请求中..', 0);
removeShopSpec(row.shopSpecId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchShopSpec(selection.value.map((d) => d.shopSpecId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: ShopSpec) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'ShopSpec'
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,197 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑规格值' : '添加规格值'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="规格组ID" name="specId">
<a-input
allow-clear
placeholder="请输入规格组ID"
v-model:value="form.specId"
/>
</a-form-item>
<a-form-item label="规格值" name="specValue">
<a-input
allow-clear
placeholder="请输入规格值"
v-model:value="form.specValue"
/>
</a-form-item>
<a-form-item label="备注" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入描述"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="排序号" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addShopSpecValue, updateShopSpecValue } from '@/api/shop/shopSpecValue';
import { ShopSpecValue } from '@/api/shop/shopSpecValue/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: ShopSpecValue | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
// 用户信息
const form = reactive<ShopSpecValue>({
specValueId: undefined,
specId: undefined,
specValue: undefined,
comments: undefined,
sortNumber: undefined,
tenantId: undefined,
createTime: undefined,
shopSpecValueId: undefined,
shopSpecValueName: '',
status: 0,
comments: '',
sortNumber: 100
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
shopSpecValueName: [
{
required: true,
type: 'string',
message: '请填写规格值名称',
trigger: 'blur'
}
]
});
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
const onDeleteItem = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updateShopSpecValue : addShopSpecValue;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
if (props.data) {
assignObject(form, props.data);
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,239 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="shopSpecValueId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<ShopSpecValueEdit v-model:visible="showEdit" :data="current" @done="reload" />
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import ShopSpecValueEdit from './components/shopSpecValueEdit.vue';
import { pageShopSpecValue, removeShopSpecValue, removeBatchShopSpecValue } from '@/api/shop/shopSpecValue';
import type { ShopSpecValue, ShopSpecValueParam } from '@/api/shop/shopSpecValue/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<ShopSpecValue[]>([]);
// 当前编辑数据
const current = ref<ShopSpecValue | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageShopSpecValue({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '规格值ID',
dataIndex: 'specValueId',
key: 'specValueId',
align: 'center',
width: 90,
},
{
title: '规格组ID',
dataIndex: 'specId',
key: 'specId',
align: 'center',
},
{
title: '规格值',
dataIndex: 'specValue',
key: 'specValue',
align: 'center',
},
{
title: '备注',
dataIndex: 'comments',
key: 'comments',
align: 'center',
},
{
title: '排序号',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center',
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: ShopSpecValueParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: ShopSpecValue) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: ShopSpecValue) => {
const hide = message.loading('请求中..', 0);
removeShopSpecValue(row.shopSpecValueId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchShopSpecValue(selection.value.map((d) => d.shopSpecValueId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: ShopSpecValue) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'ShopSpecValue'
};
</script>
<style lang="less" scoped></style>