refactor: 项目简化

Former-commit-id: 73a4a6c9c41e013928e6205dd7c078d0e955f487
This commit is contained in:
horizons
2022-09-13 07:44:55 +08:00
parent 90c6059f3f
commit 81d7880ebc
64 changed files with 204 additions and 5279 deletions

View File

@@ -11,6 +11,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^1.0.0",
"@types/js-cookie": "^3.0.2",
"@vueuse/core": "^9.1.1",
"@wangeditor/editor": "^5.0.0",
"@wangeditor/editor-for-vue": "^5.1.10",
@@ -19,6 +20,7 @@
"default-passive-events": "^2.0.0",
"echarts": "^5.2.2",
"element-plus": "^2.2.5",
"js-cookie": "^3.0.1",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",

View File

@@ -1,8 +1,4 @@
import {
DeptFormData,
DeptItem,
DeptQueryParam
} from '@/types/api/system/dept';
import { DeptFormData, DeptItem, DeptQueryParam } from '@/types/api/dept';
import { Option } from '@/types/common';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@@ -16,7 +12,7 @@ export function listDepartments(
queryParams?: DeptQueryParam
): AxiosPromise<DeptItem[]> {
return request({
url: '/youlai-admin/api/v1/depts',
url: '/youlai-system/api/v1/depts',
method: 'get',
params: queryParams
});
@@ -27,7 +23,7 @@ export function listDepartments(
*/
export function listDeptOptions(): AxiosPromise<Option[]> {
return request({
url: '/youlai-admin/api/v1/depts/options',
url: '/youlai-system/api/v1/depts/options',
method: 'get'
});
}
@@ -39,7 +35,7 @@ export function listDeptOptions(): AxiosPromise<Option[]> {
*/
export function getDeptDetail(id: string): AxiosPromise<DeptFormData> {
return request({
url: '/youlai-admin/api/v1/depts/' + id,
url: '/youlai-system/api/v1/depts/' + id,
method: 'get'
});
}
@@ -51,7 +47,7 @@ export function getDeptDetail(id: string): AxiosPromise<DeptFormData> {
*/
export function addDept(data: DeptFormData) {
return request({
url: '/youlai-admin/api/v1/depts',
url: '/youlai-system/api/v1/depts',
method: 'post',
data: data
});
@@ -65,7 +61,7 @@ export function addDept(data: DeptFormData) {
*/
export function updateDept(id: string, data: DeptFormData) {
return request({
url: '/youlai-admin/api/v1/depts/' + id,
url: '/youlai-system/api/v1/depts/' + id,
method: 'put',
data: data
});
@@ -78,7 +74,7 @@ export function updateDept(id: string, data: DeptFormData) {
*/
export function deleteDept(ids: string) {
return request({
url: '/youlai-admin/api/v1/depts/' + ids,
url: '/youlai-system/api/v1/depts/' + ids,
method: 'delete'
});
}

View File

@@ -5,8 +5,8 @@ import {
DictItemPageResult,
DictItemQueryParam,
DictPageResult,
DictQueryParam,
} from '@/types/api/system/dict';
DictQueryParam
} from '@/types/api/dict';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@@ -19,9 +19,9 @@ export function listPageDictTypes(
queryParams: DictQueryParam
): AxiosPromise<DictPageResult> {
return request({
url: '/youlai-admin/api/v1/dict-types',
url: '/youlai-system/api/v1/dict-types',
method: 'get',
params: queryParams,
params: queryParams
});
}
@@ -32,8 +32,8 @@ export function listPageDictTypes(
*/
export function getDictFormData(id: number): AxiosPromise<DictFormTypeData> {
return request({
url: '/youlai-admin/api/v1/dict-types/' + id + '/form_data',
method: 'get',
url: '/youlai-system/api/v1/dict-types/' + id + '/form_data',
method: 'get'
});
}
@@ -44,9 +44,9 @@ export function getDictFormData(id: number): AxiosPromise<DictFormTypeData> {
*/
export function addDictType(data: DictFormTypeData) {
return request({
url: '/youlai-admin/api/v1/dict-types',
url: '/youlai-system/api/v1/dict-types',
method: 'post',
data: data,
data: data
});
}
@@ -58,9 +58,9 @@ export function addDictType(data: DictFormTypeData) {
*/
export function updateDictType(id: number, data: DictFormTypeData) {
return request({
url: '/youlai-admin/api/v1/dict-types/' + id,
url: '/youlai-system/api/v1/dict-types/' + id,
method: 'put',
data: data,
data: data
});
}
@@ -71,8 +71,8 @@ export function updateDictType(id: number, data: DictFormTypeData) {
*/
export function deleteDictTypes(ids: string) {
return request({
url: '/youlai-admin/api/v1/dict-types/' + ids,
method: 'delete',
url: '/youlai-system/api/v1/dict-types/' + ids,
method: 'delete'
});
}
@@ -85,9 +85,9 @@ export function listPageDictItems(
queryParams: DictItemQueryParam
): AxiosPromise<DictItemPageResult> {
return request({
url: '/youlai-admin/api/v1/dict-items',
url: '/youlai-system/api/v1/dict-items',
method: 'get',
params: queryParams,
params: queryParams
});
}
@@ -100,9 +100,9 @@ export function getDictItemsByTypeCode(
typeCode: string
): AxiosPromise<Option[]> {
return request({
url: '/youlai-admin/api/v1/dict-items/select_list',
url: '/youlai-system/api/v1/dict-items/select_list',
method: 'get',
params: { typeCode: typeCode },
params: { typeCode: typeCode }
});
}
@@ -113,8 +113,8 @@ export function getDictItemsByTypeCode(
*/
export function getDictItemData(id: number): AxiosPromise<DictItemFormData> {
return request({
url: '/youlai-admin/api/v1/dict-items/' + id + '/form_data',
method: 'get',
url: '/youlai-system/api/v1/dict-items/' + id + '/form_data',
method: 'get'
});
}
@@ -125,9 +125,9 @@ export function getDictItemData(id: number): AxiosPromise<DictItemFormData> {
*/
export function addDictItem(data: DictItemFormData) {
return request({
url: '/youlai-admin/api/v1/dict-items',
url: '/youlai-system/api/v1/dict-items',
method: 'post',
data: data,
data: data
});
}
@@ -139,9 +139,9 @@ export function addDictItem(data: DictItemFormData) {
*/
export function updateDictItem(id: number, data: DictItemFormData) {
return request({
url: '/youlai-admin/api/v1/dict-items/' + id,
url: '/youlai-system/api/v1/dict-items/' + id,
method: 'put',
data: data,
data: data
});
}
@@ -152,7 +152,7 @@ export function updateDictItem(id: number, data: DictItemFormData) {
*/
export function deleteDictItems(ids: string) {
return request({
url: '/youlai-admin/api/v1/dict-items/' + ids,
method: 'delete',
url: '/youlai-system/api/v1/dict-items/' + ids,
method: 'delete'
});
}

View File

@@ -9,12 +9,12 @@ export function uploadFile(file: File) {
const formData = new FormData();
formData.append('file', file);
return request({
url: '/youlai-admin/api/v1/files',
url: '/youlai-system/api/v1/files',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
'Content-Type': 'multipart/form-data'
}
});
}
@@ -25,8 +25,8 @@ export function uploadFile(file: File) {
*/
export function deleteFile(path?: string) {
return request({
url: '/youlai-admin/api/v1/files',
url: '/youlai-system/api/v1/files',
method: 'delete',
params: { path: path },
params: { path: path }
});
}

View File

@@ -1,8 +1,4 @@
import {
Captcha,
LoginFormData,
LoginResponseData,
} from '@/types/api/system/login';
import { Captcha, LoginFormData, LoginResponseData } from '@/types/api/login';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@@ -16,8 +12,8 @@ export function login(data: LoginFormData): AxiosPromise<LoginResponseData> {
method: 'post',
params: data,
headers: {
Authorization: 'Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2', // 客户端信息Base64明文mall-admin-web:123456
},
Authorization: 'Basic bWFsbC1hZG1pbi13ZWI6MTIzNDU2' // 客户端信息Base64明文mall-admin-web:123456
}
});
}
@@ -27,7 +23,7 @@ export function login(data: LoginFormData): AxiosPromise<LoginResponseData> {
export function logout() {
return request({
url: '/youlai-auth/oauth/logout',
method: 'delete',
method: 'delete'
});
}
@@ -37,6 +33,6 @@ export function logout() {
export function getCaptcha(): AxiosPromise<Captcha> {
return request({
url: '/captcha?t=' + new Date().getTime().toString(),
method: 'get',
method: 'get'
});
}

View File

@@ -2,8 +2,8 @@ import {
MenuFormData,
MenuItem,
MenuQueryParam,
Resource,
} from '@/types/api/system/menu';
Resource
} from '@/types/api/menu';
import { Option } from '@/types/common';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
@@ -13,8 +13,8 @@ import { AxiosPromise } from 'axios';
*/
export function listRoutes() {
return request({
url: '/youlai-admin/api/v1/menus/routes',
method: 'get',
url: '/youlai-system/api/v1/menus/routes',
method: 'get'
});
}
@@ -27,9 +27,9 @@ export function listMenus(
queryParams: MenuQueryParam
): AxiosPromise<MenuItem[]> {
return request({
url: '/youlai-admin/api/v1/menus',
url: '/youlai-system/api/v1/menus',
method: 'get',
params: queryParams,
params: queryParams
});
}
@@ -38,8 +38,8 @@ export function listMenus(
*/
export function listMenuOptions(): AxiosPromise<Option[]> {
return request({
url: '/youlai-admin/api/v1/menus/options',
method: 'get',
url: '/youlai-system/api/v1/menus/options',
method: 'get'
});
}
@@ -48,8 +48,8 @@ export function listMenuOptions(): AxiosPromise<Option[]> {
*/
export function listResources(): AxiosPromise<Resource[]> {
return request({
url: '/youlai-admin/api/v1/menus/resources',
method: 'get',
url: '/youlai-system/api/v1/menus/resources',
method: 'get'
});
}
@@ -59,8 +59,8 @@ export function listResources(): AxiosPromise<Resource[]> {
*/
export function getMenuDetail(id: string): AxiosPromise<MenuFormData> {
return request({
url: '/youlai-admin/api/v1/menus/' + id,
method: 'get',
url: '/youlai-system/api/v1/menus/' + id,
method: 'get'
});
}
@@ -71,9 +71,9 @@ export function getMenuDetail(id: string): AxiosPromise<MenuFormData> {
*/
export function addMenu(data: MenuFormData) {
return request({
url: '/youlai-admin/api/v1/menus',
url: '/youlai-system/api/v1/menus',
method: 'post',
data: data,
data: data
});
}
@@ -85,9 +85,9 @@ export function addMenu(data: MenuFormData) {
*/
export function updateMenu(id: string, data: MenuFormData) {
return request({
url: '/youlai-admin/api/v1/menus/' + id,
url: '/youlai-system/api/v1/menus/' + id,
method: 'put',
data: data,
data: data
});
}
@@ -98,7 +98,7 @@ export function updateMenu(id: string, data: MenuFormData) {
*/
export function deleteMenus(ids: string) {
return request({
url: '/youlai-admin/api/v1/menus/' + ids,
method: 'delete',
url: '/youlai-system/api/v1/menus/' + ids,
method: 'delete'
});
}

View File

@@ -1,30 +0,0 @@
import { OrderPageResult, OrderQueryParam } from '@/types/api/oms/order';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取订单分页列表
*
* @param queryParams
*/
export function listOrderPages(
queryParams: OrderQueryParam
): AxiosPromise<OrderPageResult> {
return request({
url: '/mall-oms/api/v1/orders',
method: 'get',
params: queryParams,
});
}
/**
* 获取订单详情
*
* @param orderId
*/
export function getOrderDetail(orderId: number) {
return request({
url: '/mall-oms/api/v1/orders/' + orderId,
method: 'get',
});
}

View File

@@ -1,27 +0,0 @@
import request from '@/utils/request';
/**
* 获取商品属性列表
*
* @param params
*/
export function listAttributes(params: object) {
return request({
url: '/mall-pms/api/v1/attributes',
method: 'get',
params: params,
});
}
/**
* 批量修改商品属性
*
* @param data
*/
export function saveAttributeBatch(data: object) {
return request({
url: '/mall-pms/api/v1/attributes/batch',
method: 'post',
data: data,
});
}

View File

@@ -1,89 +0,0 @@
import {
BrandFormData,
BrandItem,
BrandPageResult,
BrandQueryParam,
} from '@/types/api/pms/brand';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取品牌分页列表
*
* @param queryParams
*/
export function listBrandPages(
queryParams: BrandQueryParam
): AxiosPromise<BrandPageResult> {
return request({
url: '/mall-pms/api/v1/brands/pages',
method: 'get',
params: queryParams,
});
}
/**
* 获取品牌列表
*
* @param queryParams
*/
export function listBrands(
queryParams?: BrandQueryParam
): AxiosPromise<BrandItem[]> {
return request({
url: '/mall-pms/api/v1/brands',
method: 'get',
params: queryParams,
});
}
/**
* 获取品牌详情
*
* @param id
*/
export function getBrandFormDetail(id: number): AxiosPromise<BrandFormData> {
return request({
url: '/mall-pms/api/v1/brands/' + id,
method: 'get',
});
}
/**
* 添加品牌
*
* @param data
*/
export function addBrand(data: BrandFormData) {
return request({
url: '/mall-pms/api/v1/brands',
method: 'post',
data: data,
});
}
/**
* 修改品牌
*
* @param id
* @param data
*/
export function updateBrand(id: number, data: BrandFormData) {
return request({
url: '/mall-pms/api/v1/brands/' + id,
method: 'put',
data: data,
});
}
/**
* 删除品牌
*
* @param ids
*/
export function deleteBrands(ids: string) {
return request({
url: '/mall-pms/api/v1/brands/' + ids,
method: 'delete',
});
}

View File

@@ -1,93 +0,0 @@
import request from '@/utils/request';
import { Option } from '@/types/common';
import { AxiosPromise } from 'axios';
/**
* 获取商品分类列表
*
* @param queryParams
*/
export function listCategories(queryParams: object) {
return request({
url: '/mall-pms/api/v1/categories',
method: 'get',
params: queryParams,
});
}
/**
* 获取商品分类级联器树形列表
*
* @param queryParams
*/
export function listCategoryOptions(): AxiosPromise<Option[]> {
return request({
url: '/mall-pms/api/v1/categories/options',
method: 'get',
});
}
/**
* 获取商品分类详情
*
* @param id
*/
export function getCategoryDetail(id: number) {
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'get',
});
}
/**
* 添加商品分类
*
* @param data
*/
export function addCategory(data: object) {
return request({
url: '/mall-pms/api/v1/categories',
method: 'post',
data: data,
});
}
/**
* 修改商品分类
*
* @param id
* @param data
*/
export function updateCategory(id: number, data: object) {
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'put',
data: data,
});
}
/**
* 删除商品分类
*
* @param ids
*/
export function deleteCategories(ids: string) {
return request({
url: '/mall-pms/api/v1/categories/' + ids,
method: 'delete',
});
}
/**
* 选择性修改商品分类
*
* @param id
* @param data
*/
export function updateCategoryPart(id: number, data: object) {
return request({
url: '/mall-pms/api/v1/categories/' + id,
method: 'patch',
data: data,
});
}

View File

@@ -1,73 +0,0 @@
import {
GoodsDetail,
GoodsPageResult,
GoodsQueryParam,
} from '@/types/api/pms/goods';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取商品分页列表
*
* @param queryParams
*/
export function listSpuPages(
queryParams: GoodsQueryParam
): AxiosPromise<GoodsPageResult> {
return request({
url: '/mall-pms/api/v1/spu/pages',
method: 'get',
params: queryParams,
});
}
/**
* 获取商品详情
*
* @param id
*/
export function getSpuDetail(id: string): AxiosPromise<GoodsDetail> {
return request({
url: '/mall-pms/api/v1/spu/' + id,
method: 'get',
});
}
/**
* 添加商品
*
* @param data
*/
export function addSpu(data: object) {
return request({
url: '/mall-pms/api/v1/spu',
method: 'post',
data: data,
});
}
/**
* 修改商品
*
* @param id
* @param data
*/
export function updateSpu(id: number, data: object) {
return request({
url: '/mall-pms/api/v1/spu/' + id,
method: 'put',
data: data,
});
}
/**
* 删除商品
*
* @param ids
*/
export function deleteSpu(ids: string) {
return request({
url: '/mall-pms/api/v1/spu/' + ids,
method: 'delete',
});
}

View File

@@ -2,8 +2,8 @@ import {
RoleFormData,
RolePageResult,
RoleQueryParam,
RoleResource,
} from '@/types/api/system/role';
RoleResource
} from '@/types/api/role';
import { Option } from '@/types/common';
import request from '@/utils/request';
@@ -18,9 +18,9 @@ export function listRolePages(
queryParams?: RoleQueryParam
): AxiosPromise<RolePageResult> {
return request({
url: '/youlai-admin/api/v1/roles/pages',
url: '/youlai-system/api/v1/roles/pages',
method: 'get',
params: queryParams,
params: queryParams
});
}
@@ -33,9 +33,9 @@ export function listRoleOptions(
queryParams?: RoleQueryParam
): AxiosPromise<Option[]> {
return request({
url: '/youlai-admin/api/v1/roles/options',
url: '/youlai-system/api/v1/roles/options',
method: 'get',
params: queryParams,
params: queryParams
});
}
@@ -46,8 +46,8 @@ export function listRoleOptions(
*/
export function getRoleResources(roleId: string): AxiosPromise<RoleResource> {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/resources',
method: 'get',
url: '/youlai-system/api/v1/roles/' + roleId + '/resources',
method: 'get'
});
}
@@ -61,9 +61,9 @@ export function updateRoleResource(
data: RoleResource
): AxiosPromise<any> {
return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/resources',
url: '/youlai-system/api/v1/roles/' + roleId + '/resources',
method: 'put',
data: data,
data: data
});
}
@@ -74,8 +74,8 @@ export function updateRoleResource(
*/
export function getRoleFormDetail(id: number): AxiosPromise<RoleFormData> {
return request({
url: '/youlai-admin/api/v1/roles/' + id,
method: 'get',
url: '/youlai-system/api/v1/roles/' + id,
method: 'get'
});
}
@@ -86,9 +86,9 @@ export function getRoleFormDetail(id: number): AxiosPromise<RoleFormData> {
*/
export function addRole(data: RoleFormData) {
return request({
url: '/youlai-admin/api/v1/roles',
url: '/youlai-system/api/v1/roles',
method: 'post',
data: data,
data: data
});
}
@@ -100,9 +100,9 @@ export function addRole(data: RoleFormData) {
*/
export function updateRole(id: number, data: RoleFormData) {
return request({
url: '/youlai-admin/api/v1/roles/' + id,
url: '/youlai-system/api/v1/roles/' + id,
method: 'put',
data: data,
data: data
});
}
@@ -113,7 +113,7 @@ export function updateRole(id: number, data: RoleFormData) {
*/
export function deleteRoles(ids: string) {
return request({
url: '/youlai-admin/api/v1/roles/' + ids,
method: 'delete',
url: '/youlai-system/api/v1/roles/' + ids,
method: 'delete'
});
}

View File

@@ -1,73 +0,0 @@
import {
AdvertFormData,
AdvertPageResult,
AdvertQueryParam,
} from '@/types/api/sms/advert';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取广告分页列表
*
* @param queryParams
*/
export function listAdvertPages(
queryParams: AdvertQueryParam
): AxiosPromise<AdvertPageResult> {
return request({
url: '/mall-sms/api/v1/adverts/pages',
method: 'get',
params: queryParams,
});
}
/**
* 获取广告详情
*
* @param id
*/
export function getAdvertFormDetail(id: number): AxiosPromise<AdvertFormData> {
return request({
url: '/mall-sms/api/v1/adverts/' + id,
method: 'get',
});
}
/**
* 添加广告
*
* @param data
*/
export function addAdvert(data: AdvertFormData) {
return request({
url: '/mall-sms/api/v1/adverts',
method: 'post',
data: data,
});
}
/**
* 修改广告
*
* @param id
* @param data
*/
export function updateAdvert(id: number, data: AdvertFormData) {
return request({
url: '/mall-sms/api/v1/adverts/' + id,
method: 'put',
data: data,
});
}
/**
* 删除广告
*
* @param ids
*/
export function deleteAdverts(ids: string) {
return request({
url: '/mall-sms/api/v1/adverts/' + ids,
method: 'delete',
});
}

View File

@@ -1,73 +0,0 @@
import {
CouponQueryParam,
CouponPageResult,
CouponFormData,
} from '@/types/api/sms/coupon';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取优惠券分页列表
*
* @param queryParams
*/
export function lisCouponPages(
queryParams: CouponQueryParam
): AxiosPromise<CouponPageResult> {
return request({
url: '/mall-sms/api/v1/coupons/pages',
method: 'get',
params: queryParams,
});
}
/**
* 获取优惠券表单数据
*
* @param id
*/
export function getCouponFormData(id: number): AxiosPromise<CouponFormData> {
return request({
url: '/mall-sms/api/v1/coupons/' + id + '/form_data',
method: 'get',
});
}
/**
* 添加优惠券
*
* @param data
*/
export function addCoupon(data: CouponFormData) {
return request({
url: '/mall-sms/api/v1/coupons',
method: 'post',
data: data,
});
}
/**
* 修改优惠券
*
* @param id
* @param data
*/
export function updateCoupon(id: number, data: CouponFormData) {
return request({
url: '/mall-sms/api/v1/coupons/' + id,
method: 'put',
data: data,
});
}
/**
* 删除优惠券
*
* @param ids
*/
export function deleteCoupons(ids: string) {
return request({
url: '/mall-sms/api/v1/coupons/' + ids,
method: 'delete',
});
}

View File

@@ -1,55 +0,0 @@
import {
ClientFormData,
ClientPageResult,
ClientQueryParam,
} from '@/types/api/system/client';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export function listClientPages(
queryParams: ClientQueryParam
): AxiosPromise<ClientPageResult> {
return request({
url: '/youlai-admin/api/v1/oauth-clients',
method: 'get',
params: queryParams,
});
}
export function getClientFormDetial(id: number): AxiosPromise<ClientFormData> {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'get',
});
}
export function addClient(data: ClientFormData) {
return request({
url: '/youlai-admin/api/v1/oauth-clients',
method: 'post',
data: data,
});
}
export function updateClient(id: string, data: ClientFormData) {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'put',
data: data,
});
}
export function deleteClients(ids: string) {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + ids,
method: 'delete',
});
}
export function updateClientPart(id: number, data: object) {
return request({
url: '/youlai-admin/api/v1/oauth-clients/' + id,
method: 'patch',
data: data,
});
}

View File

@@ -1,89 +0,0 @@
import {
PermFormData,
PermItem,
PermPageResult,
PermQueryParam,
} from '@/types/api/system/perm';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取权限分页列表
*
* @param queryParams
*/
export function listPermPages(
queryParams: PermQueryParam
): AxiosPromise<PermPageResult> {
return request({
url: '/youlai-admin/api/v1/permissions/page',
method: 'get',
params: queryParams,
});
}
/**
* 获取权限列表
*
* @param queryParams
*/
export function listPerms(
queryParams: PermQueryParam
): AxiosPromise<PermItem[]> {
return request({
url: '/youlai-admin/api/v1/permissions',
method: 'get',
params: queryParams,
});
}
/**
* 获取权限详情
*
* @param id
*/
export function getPermFormDetail(id: number): AxiosPromise<PermFormData> {
return request({
url: '/youlai-admin/api/v1/permissions/' + id,
method: 'get',
});
}
/**
* 添加权限
*
* @param data
*/
export function addPerm(data: PermFormData) {
return request({
url: '/youlai-admin/api/v1/permissions',
method: 'post',
data: data,
});
}
/**
* 更新权限
*
* @param id
* @param data
*/
export function updatePerm(id: number, data: PermFormData) {
return request({
url: '/youlai-admin/api/v1/permissions/' + id,
method: 'put',
data: data,
});
}
/**
* 批量删除权限,多个以英文逗号(,)分割
*
* @param ids
*/
export function deletePerms(ids: string) {
return request({
url: '/youlai-admin/api/v1/permissions/' + ids,
method: 'delete',
});
}

View File

@@ -1,57 +0,0 @@
import { MemberPageResult, MemberQueryParam } from '@/types/api/ums/member';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取会员分页列表
*
* @param queryParams
*/
export function listMemebersPage(
queryParams: MemberQueryParam
): AxiosPromise<MemberPageResult> {
return request({
url: '/mall-ums/api/v1/members',
method: 'get',
params: queryParams,
});
}
/**
* 获取会员详情
*
* @param id
*/
export function getMemberDetail(id: number) {
return request({
url: '/mall-ums/api/v1/members/' + id,
method: 'get',
});
}
/**
* 添加会员
*
* @param data
*/
export function addMember(data: object) {
return request({
url: '/mall-ums/api/v1/members',
method: 'post',
data: data,
});
}
/**
* 添加会员
*
* @param id
* @param data
*/
export function updateMember(id: number, data: object) {
return request({
url: '/mall-ums/api/v1/members/' + id,
method: 'put',
data: data,
});
}

View File

@@ -5,14 +5,14 @@ import {
UserInfo,
UserPageResult,
UserQueryParam
} from '@/types/api/system/user';
} from '@/types/api/user';
/**
*
*/
export function getUserInfo(): AxiosPromise<UserInfo> {
return request({
url: '/youlai-admin/api/v1/users/me',
url: '/youlai-system/api/v1/users/me',
method: 'get'
});
}
@@ -26,7 +26,7 @@ export function listUserPages(
queryParams: UserQueryParam
): AxiosPromise<UserPageResult> {
return request({
url: '/youlai-admin/api/v1/users/pages',
url: '/youlai-system/api/v1/users/pages',
method: 'get',
params: queryParams
});
@@ -39,7 +39,7 @@ export function listUserPages(
*/
export function getUserDetail(userId: number): AxiosPromise<UserFormData> {
return request({
url: '/youlai-admin/api/v1/users/' + userId,
url: '/youlai-system/api/v1/users/' + userId,
method: 'get'
});
}
@@ -51,7 +51,7 @@ export function getUserDetail(userId: number): AxiosPromise<UserFormData> {
*/
export function addUser(data: any) {
return request({
url: '/youlai-admin/api/v1/users',
url: '/youlai-system/api/v1/users',
method: 'post',
data: data
});
@@ -65,7 +65,7 @@ export function addUser(data: any) {
*/
export function updateUser(id: number, data: UserFormData) {
return request({
url: '/youlai-admin/api/v1/users/' + id,
url: '/youlai-system/api/v1/users/' + id,
method: 'put',
data: data
});
@@ -79,7 +79,7 @@ export function updateUser(id: number, data: UserFormData) {
*/
export function updateUserStatus(id: number, status: number) {
return request({
url: '/youlai-admin/api/v1/users/' + id + '/status',
url: '/youlai-system/api/v1/users/' + id + '/status',
method: 'patch',
params: { status: status }
});
@@ -93,7 +93,7 @@ export function updateUserStatus(id: number, status: number) {
*/
export function updateUserPassword(id: number, password: string) {
return request({
url: '/youlai-admin/api/v1/users/' + id + '/password',
url: '/youlai-system/api/v1/users/' + id + '/password',
method: 'patch',
params: { password: password }
});
@@ -106,7 +106,7 @@ export function updateUserPassword(id: number, password: string) {
*/
export function deleteUsers(ids: string) {
return request({
url: '/youlai-admin/api/v1/users/' + ids,
url: '/youlai-system/api/v1/users/' + ids,
method: 'delete'
});
}
@@ -118,7 +118,7 @@ export function deleteUsers(ids: string) {
*/
export function downloadTemplate() {
return request({
url: '/youlai-admin/api/v1/users/template',
url: '/youlai-system/api/v1/users/template',
method: 'get',
responseType: 'arraybuffer'
});
@@ -132,7 +132,7 @@ export function downloadTemplate() {
*/
export function exportUser(queryParams: UserQueryParam) {
return request({
url: '/youlai-admin/api/v1/users/_export',
url: '/youlai-system/api/v1/users/_export',
method: 'get',
params: queryParams,
responseType: 'arraybuffer'
@@ -150,7 +150,7 @@ export function importUser(deptId: number, roleIds: string, file: File) {
formData.append('deptId', deptId.toString());
formData.append('roleIds', roleIds);
return request({
url: '/youlai-admin/api/v1/users/_import',
url: '/youlai-system/api/v1/users/_import',
method: 'post',
data: formData,
headers: {

View File

@@ -36,7 +36,7 @@ import {
UploadRawFile,
UploadRequestOptions
} from 'element-plus';
import { uploadFile, deleteFile } from '@/api/system/file';
import { uploadFile, deleteFile } from '@/api/file';
const emit = defineEmits(['update:modelValue']);

View File

@@ -24,7 +24,7 @@ import { onBeforeUnmount, shallowRef, reactive, toRefs } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
// API 引用
import { uploadFile } from '@/api/system/file';
import { uploadFile } from '@/api/file';
const props = defineProps({
modelValue: {

View File

@@ -21,14 +21,14 @@ import i18n from '@/lang/index';
import '@/styles/index.scss';
// 根据字典编码获取字典列表全局方法
import { getDictItemsByTypeCode } from '@/api/system/dict';
import { getDictItemsByTypeCode } from '@/api/dict';
const app = createApp(App);
// 自定义指令
import * as directive from '@/directive';
Object.keys(directive).forEach((key) => {
Object.keys(directive).forEach(key => {
app.directive(key, (directive as { [key: string]: Directive })[key]);
});

View File

@@ -1,6 +1,7 @@
import router from '@/router';
import { ElMessage } from 'element-plus';
import useStore from '@/store';
import { hasLogin } from '@/utils/auth';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
NProgress.configure({ showSpinner: false }); // 进度环显示/隐藏
@@ -11,8 +12,8 @@ const whiteList = ['/login'];
router.beforeEach(async (to, from, next) => {
NProgress.start();
const { user, permission } = useStore();
const hasToken = user.token;
if (hasToken) {
if (hasLogin()) {
// 登录成功,跳转到首页
if (to.path === '/login') {
next({ path: '/' });

View File

@@ -2,7 +2,7 @@ import { PermissionState } from '@/types/store/permission';
import { RouteRecordRaw } from 'vue-router';
import { defineStore } from 'pinia';
import { constantRoutes } from '@/router';
import { listRoutes } from '@/api/system/menu';
import { listRoutes } from '@/api/menu';
const modules = import.meta.glob('../../views/**/**.vue');
export const Layout = () => import('@/layout/index.vue');
@@ -12,7 +12,7 @@ const hasPermission = (roles: string[], route: RouteRecordRaw) => {
if (roles.includes('ROOT')) {
return true;
}
return roles.some((role) => {
return roles.some(role => {
if (route.meta?.roles !== undefined) {
return (route.meta.roles as string[]).includes(role);
}
@@ -26,7 +26,7 @@ export const filterAsyncRoutes = (
roles: string[]
) => {
const res: RouteRecordRaw[] = [];
routes.forEach((route) => {
routes.forEach(route => {
const tmp = { ...route } as any;
if (hasPermission(roles, tmp)) {
if (tmp.component == 'Layout') {
@@ -53,7 +53,7 @@ const usePermissionStore = defineStore({
id: 'permission',
state: (): PermissionState => ({
routes: [],
addRoutes: [],
addRoutes: []
}),
actions: {
setRoutes(routes: RouteRecordRaw[]) {
@@ -63,18 +63,18 @@ const usePermissionStore = defineStore({
generateRoutes(roles: string[]) {
return new Promise((resolve, reject) => {
listRoutes()
.then((response) => {
.then(response => {
const asyncRoutes = response.data;
const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles);
this.setRoutes(accessedRoutes);
resolve(accessedRoutes);
})
.catch((error) => {
.catch(error => {
reject(error);
});
});
},
},
}
}
});
export default usePermissionStore;

View File

@@ -1,10 +1,10 @@
import { defineStore } from 'pinia';
import { LoginFormData } from '@/types/api/system/login';
import { LoginFormData } from '@/types/api/login';
import { UserState } from '@/types/store/user';
import { localStorage } from '@/utils/storage';
import { login, logout } from '@/api/login';
import { getUserInfo } from '@/api/system/user';
import { getUserInfo } from '@/api/user';
import { resetRouter } from '@/router';
const useUserStore = defineStore({
@@ -14,7 +14,7 @@ const useUserStore = defineStore({
nickname: '',
avatar: '',
roles: [],
perms: [],
perms: []
}),
actions: {
async RESET_STATE() {
@@ -31,16 +31,16 @@ const useUserStore = defineStore({
password: password,
grant_type: 'captcha',
code: code,
uuid: uuid,
uuid: uuid
})
.then((response) => {
.then(response => {
const { access_token, token_type } = response.data;
const accessToken = token_type + ' ' + access_token;
localStorage.set('token', accessToken);
this.token = accessToken;
resolve(access_token);
})
.catch((error) => {
.catch(error => {
reject(error);
});
});
@@ -65,7 +65,8 @@ const useUserStore = defineStore({
this.perms = perms;
resolve(data);
})
.catch((error) => {
.catch(error => {
console.log('error', error);
reject(error);
});
});
@@ -83,7 +84,7 @@ const useUserStore = defineStore({
resetRouter();
resolve(null);
})
.catch((error) => {
.catch(error => {
reject(error);
});
});
@@ -93,13 +94,13 @@ const useUserStore = defineStore({
* 清除 Token
*/
resetToken() {
return new Promise((resolve) => {
return new Promise(resolve => {
localStorage.remove('token');
this.RESET_STATE();
resolve(null);
});
},
},
}
}
});
export default useUserStore;

View File

@@ -1,4 +1,4 @@
import { PageQueryParam, PageResult } from '../base';
import { PageQueryParam, PageResult } from './base';
/**
*

View File

@@ -1,56 +0,0 @@
import { PageQueryParam, PageResult } from '../base';
/**
* 订单查询参数类型声明
*/
export interface OrderQueryParam extends PageQueryParam {
orderSn: string | undefined;
status: number | undefined;
}
/**
* 订单分页列表项声明
*/
export interface Order {
id: string;
orderSn: string;
totalAmount: string;
payAmount: string;
payType: number;
status: number;
totalQuantity: number;
createTime: string;
memberId: string;
sourceType: number;
orderItems: OrderItem[];
}
export interface OrderItem {
id: string;
orderId: string;
skuId: string;
skuName: string;
picUrl: string;
price: string;
count: number;
totalAmount: number;
}
/**
* 订单分页项类型声明
*/
export type OrderPageResult = PageResult<Order[]>;
/**
* 订单表单类型声明
*/
export interface OrderDetail {
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

View File

@@ -1,4 +1,4 @@
import { PageQueryParam, PageResult } from '../base';
import { PageQueryParam, PageResult } from './base';
/**
*

View File

@@ -1,33 +0,0 @@
import { PageQueryParam, PageResult } from '../base';
/**
* 品牌查询参数类型声明
*/
export interface BrandQueryParam extends PageQueryParam {
name?: string;
}
/**
* 品牌分页列表项声明
*/
export interface BrandItem {
id: string;
name: string;
logoUrl: string;
sort: number;
}
/**
* 品牌分页项类型声明
*/
export type BrandPageResult = PageResult<BrandItem[]>;
/**
* 品牌表单类型声明
*/
export interface BrandFormData {
id: number | undefined;
name: string;
logoUrl: string;
sort: number;
}

View File

@@ -1,70 +0,0 @@
import { PageQueryParam, PageResult } from '../base';
/**
* 商品查询参数类型声明
*/
export interface GoodsQueryParam extends PageQueryParam {
name?: stirng;
categoryId?: number;
}
/**
* 商品列表项类型声明
*/
export interface GoodsItem {
id: string;
name: string;
categoryId?: any;
brandId?: any;
originPrice: string;
price: string;
sales: number;
picUrl?: any;
album?: any;
unit?: any;
description: string;
detail: string;
status?: any;
categoryName: string;
brandName: string;
skuList: SkuItem[];
}
/**
* 商品规格项类型声明
*/
export interface SkuItem {
id: string;
skuSn?: any;
name: string;
spuId?: any;
specIds: string;
price: string;
stockNum: number;
lockedStockNum?: any;
picUrl?: any;
}
/**
* 商品分页项类型声明
*/
export type GoodsPageResult = PageResult<GoodsItem[]>;
/**
* 商品表单数据类型声明
*/
export interface GoodsDetail {
id?: string;
name?: string;
categoryId?: string;
brandId?: string;
originPrice?: number;
price?: number;
picUrl?: string;
album: string[];
description?: string;
detail?: string;
attrList: any[];
specList: any[];
skuList: any[];
}

View File

@@ -1,5 +1,4 @@
import { StringMap } from 'i18next';
import { PageQueryParam, PageResult } from '../base';
import { PageQueryParam, PageResult } from './base';
/**
*

View File

@@ -1,38 +0,0 @@
import { PageQueryParam, PageResult } from '../base';
/**
* 广告查询参数类型
*/
export interface AdvertQueryParam extends PageQueryParam {
keywords: string;
}
/**
* 广告分页列表项
*/
export interface AdvertItem {
id: string;
name: string;
logoUrl: string;
sort: number;
}
/**
* 广告分页项类型
*/
export type AdvertPageResult = PageResult<AdvertItem[]>;
/**
* 广告表单类型
*/
export interface AdvertFormData {
id?: number;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

View File

@@ -1,113 +0,0 @@
import { PageQueryParam, PageResult } from '../base';
/**
* 优惠券查询参数类型
*/
export interface CouponQueryParam extends PageQueryParam {
status?: number;
keywords?: string;
}
/**
* 优惠券分页列表项
*/
export interface CouponItem {
id: string;
name: string;
code: string;
platformLabel: string;
typeLabel: string;
faceValueLabel: string;
validityPeriodLabel: string;
}
/**
*优惠券分页
*/
export type CouponPageResult = PageResult<CouponItem[]>;
/**
* 优惠券表单类型
*/
export interface CouponFormData {
/**
* ID
*/
id?: number;
/**
* 优惠券名称
*/
name: string;
/**
* 优惠券码
*/
code: string;
/**
* 使用平台(0:全平台;1:移动端;2:PC;)
*/
platform: number;
/**
* 优惠券类型(1:满减券;2:直减券;3:折扣券)
*/
type: number;
/**
* 优惠券面值类型
*/
faceValueType: number;
/**
* 优惠券面值
*/
faceValue: number;
/**
* 优惠券折扣
*/
discount: number;
/**
* 发行量
*/
circulation: number;
/**
* 使用门槛(0:无门槛)
*/
minPoint: number;
/**
* 每人限领张数(-1:无限制)
*/
perLimit: number;
/**
* 有效期类型(1:日期范围;2:固定天数)
*/
validityPeriodType: number;
/**
* 自领取之日起有效天数
*/
validityDays: number;
/**
* 有效期起始时间
*/
validityBeginTime: string;
/**
* 有效期截止时间
*/
validityEndTime: string;
/**
* 应用范围(0:全场通用;1:指定商品分类;2:指定商品)
*/
applicationScope: number;
/**
* 使用类型:指定商品分类
*/
spuCategoryIds: number[];
/**
* 使用类型:指定商品
*/
spuIds: number[];
/**
* 使用说明
*/
remark: string;
}

View File

@@ -1,46 +0,0 @@
import { PageQueryParam, PageResult } from '../base';
/**
* 客户端查询参数类型
*/
export interface ClientQueryParam extends PageQueryParam {
keywords?: string;
}
/**
* 客户端分页列表项
*/
export interface ClientItem {
clientId: string;
clientSecret: string;
resourceIds: string;
scope: string;
authorizedGrantTypes: string;
webServerRedirectUri?: any;
authorities?: any;
accessTokenValidity: number;
refreshTokenValidity: number;
additionalInformation?: any;
autoapprove: string;
}
/**
* 客户端分页项类型
*/
export type ClientPageResult = PageResult<ClientItem[]>;
/**
* 客户端表单类型
*/
export interface ClientFormData {
authorizedGrantTypes: string;
clientId: string;
clientSecret: string;
accessTokenValidity: string;
refreshTokenValidity: string;
webServerRedirectUri: string;
authorities: string;
additionalInformation: string;
autoapprove: string;
scope: string;
}

View File

@@ -1,64 +0,0 @@
import { PageQueryParam, PageResult } from '../base';
/**
* 会员查询参数类型声明
*/
export interface MemberQueryParam extends PageQueryParam {
nickName?: string;
}
/**
* 会员分页列表项声明
*/
export interface MemberItem {
id: string;
gender: number;
nickName: string;
mobile: string;
birthday?: any;
avatarUrl: string;
openid: string;
sessionKey?: any;
city: string;
country: string;
language: string;
province: string;
status: number;
balance: string;
deleted: number;
point: number;
addressList: AddressItem[];
}
export interface AddressItem {
id: string;
memberId: string;
consigneeName: string;
consigneeMobile: string;
province: string;
city: string;
area: string;
detailAddress: string;
zipCode?: any;
defaulted: number;
}
/**
* 会员分页项类型声明
*/
export type MemberPageResult = PageResult<MemberItem[]>;
/**
* 会员表单类型声明
*/
export interface MemberFormData {
id: number | undefined;
title: string;
picUrl: string;
beginTime: string;
endTime: string;
status: number;
sort: number;
url: string;
remark: string;
}

View File

@@ -1,4 +1,4 @@
import { PageQueryParam, PageResult } from '../base';
import { PageQueryParam, PageResult } from './base';
/**
*

13
src/utils/auth.ts Normal file
View File

@@ -0,0 +1,13 @@
import Cookies from 'js-cookie';
const SESSION_ID_KEY = 'SCG_SESSION_ID';
export const hasLogin = () => {
const sessionId = Cookies.get(SESSION_ID_KEY);
console.log('sessionId', sessionId);
if (sessionId) {
return true;
} else {
return false;
}
};

View File

@@ -53,7 +53,7 @@ service.interceptors.response.use(
if (code === 'A0230') {
// token 过期
localStorage.clear(); // 清除浏览器全部缓存
window.location.href = '/'; // 跳转登录页
//window.location.href = '/'; // 跳转登录页
ElMessageBox.alert('当前页面已失效,请重新登录', '提示', {});
} else {
ElMessage({

View File

@@ -140,7 +140,7 @@ import useStore from '@/store';
// API依赖
import { getCaptcha } from '@/api/login';
import { useRoute } from 'vue-router';
import { LoginFormData } from '@/types/api/system/login';
import { LoginFormData } from '@/types/api/login';
const { user } = useStore();
const route = useRoute();
@@ -207,6 +207,9 @@ function showPwd() {
});
}
/**
* login
*/
function handleLogin() {
loginFormRef.value.validate((valid: boolean) => {
if (valid) {
@@ -219,6 +222,8 @@ function handleLogin() {
})
.catch(() => {
state.loading = false;
// 生成验证码
handleCaptchaGenerate();
});
} else {

View File

@@ -1,233 +0,0 @@
<script lang="ts">
export default {
name: 'order',
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from 'vue';
import { ElForm } from 'element-plus';
import { Order, OrderQueryParam } from '@/types/api/oms/order';
import { Dialog } from '@/types/common';
import { listOrderPages, getOrderDetail } from '@/api/oms/order';
import { Search, Refresh } from '@element-plus/icons-vue';
const queryFormRef = ref(ElForm);
const orderSourceMap = {
1: '微信小程序',
2: 'APP',
3: 'PC',
};
const orderStatusMap = {
101: '待付款',
102: '用户取消',
103: '系统取消',
201: '已付款',
202: '申请退款',
203: '已退款',
301: '待发货',
401: '已发货',
501: '用户收货',
502: '系统收货',
901: '已完成',
};
const payTypeMap = {
1: '支付宝',
2: '微信',
3: '会员余额',
};
const state = reactive({
loading: false,
ids: [],
single: true,
multiple: true,
dateRange: [] as any,
queryParams: {
pageNum: 1,
pageSize: 10,
} as OrderQueryParam,
orderList: [] as Order[],
total: 0,
dialog: {
title: '订单详情',
visible: false,
} as Dialog,
dialogVisible: false,
orderDetail: {
order: {
refundAmount: undefined,
refundType: undefined,
refundNote: undefined,
gmtRefund: undefined,
confirmTime: undefined,
gmtDelivery: undefined,
shipSn: undefined,
shipChannel: undefined,
gmtPay: undefined,
integralPrice: undefined,
payChannel: undefined,
skuPrice: undefined,
couponPrice: undefined,
freightPrice: undefined,
orderPrice: undefined,
},
member: {},
orderItems: [],
},
orderSourceMap,
orderStatusMap,
payTypeMap,
});
const { loading, queryParams, orderList, total, dateRange } = toRefs(state);
function handleQuery() {
state.loading = true;
listOrderPages(state.queryParams).then(({ data }) => {
state.orderList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function viewDetail(row: any) {
state.dialog.visible = true;
getOrderDetail(row.id).then((response: any) => {
state.orderDetail = response.data;
});
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item prop="orderSn">
<el-input v-model="queryParams.orderSn" placeholder="订单号" />
</el-form-item>
<el-form-item>
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-select
v-model="queryParams.status"
class="filter-item"
placeholder="订单状态"
>
<el-option
v-for="(key, value) in orderStatusMap"
:key="key"
:label="key"
:value="value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"
>查询</el-button
>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table ref="dataTable" v-loading="loading" :data="orderList" border>
<el-table-column type="expand" width="100" label="订单商品">
<template #default="scope">
<el-table :data="scope.row.orderItems" border>
<el-table-column label="序号" type="index" width="100" />
<el-table-column label="商品编号" align="center" prop="skuSn" />
<el-table-column label="商品规格" align="center" prop="skuName" />
<el-table-column label="图片" prop="picUrl">
<template #default="scope">
<img :src="scope.row.picUrl" width="40" />
</template>
</el-table-column>
<el-table-column align="center" label="单价" prop="price">
<template #default="scope">{{ scope.row.price }}</template>
</el-table-column>
<el-table-column align="center" label="数量" prop="count">
<template #default="scope">{{ scope.row.count }}</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
<el-table-column align="center" prop="orderSn" label="订单编号" />
<el-table-column align="center" prop="memberId" label="会员ID" />
<el-table-column align="center" label="订单来源">
<template #default="scope">
<el-tag>{{ scope.row.sourceType }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="订单状态">
<template #default="scope">
<el-tag>{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="orderPrice" label="订单金额">
<template #default="scope">
{{ scope.row.totalAmount }}
</template>
</el-table-column>
<el-table-column align="center" prop="payPrice" label="支付金额">
<template #default="scope">
{{ scope.row.payAmount }}
</template>
</el-table-column>
<el-table-column align="center" label="支付方式">
<template #default="scope">
<el-tag>{{ scope.row.payType }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="gmtCreate" label="创建时间" />
<el-table-column align="center" label="操作">
<template #default="scope">
<el-button @click="viewDetail(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
</div>
</template>
<style scoped></style>

View File

@@ -1,279 +0,0 @@
<script lang="ts">
export default {
name: 'brand',
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from 'vue';
import { ElForm, ElTable, ElMessage, ElMessageBox } from 'element-plus';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import {
BrandFormData,
BrandItem,
BrandQueryParam,
} from '@/types/api/pms/brand';
import { Dialog } from '@/types/common';
import {
listBrandPages,
getBrandFormDetail,
updateBrand,
addBrand,
deleteBrands,
} from '@/api/pms/brand';
import SingleUpload from '@/components/Upload/SingleUpload.vue';
const queryFormRef = ref(ElForm); // 属性名必须和元素的ref属性值一致
const dataFormRef = ref(ElForm); // 属性名必须和元素的ref属性值一致
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
} as BrandQueryParam,
brandList: [] as BrandItem[],
total: 0,
dialog: {} as Dialog,
formData: { sort: 1 } as BrandFormData,
rules: {
name: [
{
required: true,
message: '请输入品牌名称',
trigger: 'blur',
},
],
},
});
const {
loading,
multiple,
queryParams,
brandList,
total,
dialog,
formData,
rules,
} = toRefs(state);
function handleQuery() {
state.loading = true;
listBrandPages(state.queryParams).then(({ data }) => {
state.brandList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.id);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
function handleAdd() {
state.dialog = {
title: '添加品牌',
visible: true,
};
}
function handleUpdate(row: any) {
state.dialog = {
title: '修改品牌',
visible: true,
};
const brandId = row.id || state.ids;
getBrandFormDetail(brandId).then(({ data }) => {
state.formData = data;
});
}
/**
* 表单提交
*/
function submitForm() {
dataFormRef.value.validate((isValid: boolean) => {
if (isValid) {
if (state.formData.id) {
updateBrand(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
addBrand(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
/**
* 取消
*/
function cancel() {
state.dialog.visible = false;
dataFormRef.value.resetFields();
}
/**
* 删除
*/
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deleteBrands(ids).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
})
.catch(() => ElMessage.info('已取消删除'));
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd"
>新增</el-button
>
<el-button
type="danger"
:icon="Delete"
click="handleDelete"
:disabled="multiple"
>删除</el-button
>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="queryParams.name" placeholder="品牌名称" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="brandList"
@selection-change="handleSelectionChange"
border
>
<el-table-column type="selection" min-width="5%" />
<el-table-column prop="name" label="品牌名称" min-width="10" />
<el-table-column prop="logoUrl" label="LOGO" min-width="10">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.logoUrl" width="400" height="400" />
<template #reference>
<img
:src="scope.row.logoUrl"
style="max-height: 60px; max-width: 60px"
/>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" min-width="10" />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
@click="handleUpdate(scope.row)"
type="primary"
:icon="Edit"
circle
plain
/>
<el-button
type="danger"
:icon="Delete"
circle
plain
@click="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
<!-- 表单弹窗 -->
<el-dialog
:title="dialog.title"
v-model="dialog.visible"
top="5vh"
width="600px"
>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="品牌名称" prop="name">
<el-input v-model="formData.name" auto-complete="off" />
</el-form-item>
<el-form-item label="LOGO" prop="logoUrl">
<single-upload v-model="formData.logoUrl" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style scoped></style>

View File

@@ -1,183 +0,0 @@
<template>
<div class="component-container">
<el-card class="box-card" shadow="always">
<el-row>
<el-col :span="12">
<el-tag type="success" v-if="category && category.name"
>{{ category.name }} {{ attributeTypeName }}
</el-tag>
<el-tag v-else type="info"
><i class="el-icon-info"></i> 请选择商品分类</el-tag
>
</el-col>
<el-col :span="12" style="text-align: right">
<el-button type="primary" :icon="Check" @click="submitForm"
>提交</el-button
>
</el-col>
</el-row>
<el-row style="margin-top: 10px">
<el-form
ref="form"
:model="formData"
:disabled="category?.childrenLen > 0"
label-width="100"
>
<el-form-item
v-for="(item, index) in formData.attributes"
:key="index"
:label="attributeTypeName + (index + 1)"
:prop="'attributes.' + index + '.name'"
:rules="rules.attribute.name"
>
<el-input v-model="item.name" style="width: 300px" />
<el-button
v-if="index === 0"
type="success"
:icon="Plus"
circle
plain
@click.prevent="handleAdd()"
style="margin-left: 15px"
/>
<el-button
type="danger"
:icon="Delete"
plain
circle
@click.prevent="handleDelete(index)"
style="margin-left: 15px"
/>
</el-form-item>
</el-form>
</el-row>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, toRefs, watch } from 'vue';
import { listAttributes, saveAttributeBatch } from '@/api/pms/attribute';
import { Plus, Check, Delete } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
const props = defineProps({
attributeType: {
type: Number,
default: 1,
},
category: {
type: Object,
default: () => {
return {
id: undefined,
name: '',
childrenLen: 0,
};
},
},
});
const attributeTypeName = computed(() =>
props.attributeType === 1 ? '规格' : '属性'
);
const attributeNameValidator = (rule: any, value: any, callback: any) => {
if (!value) {
return callback(new Error('请输入' + attributeTypeName.value + '名称'));
} else {
callback();
}
};
const state = reactive({
formData: {
categoryId: undefined,
type: 1,
attributes: [
{
id: undefined,
name: '',
},
],
},
rules: {
attribute: {
name: [
{ required: true, validator: attributeNameValidator, trigger: 'blur' },
],
},
},
});
const { formData, rules } = toRefs(state);
watch(
() => props.category.id as any,
() => {
const categoryId = props.category.id;
if (categoryId) {
listAttributes({
categoryId: categoryId,
type: props.attributeType,
}).then((response) => {
const { data } = response;
if (data && data.length > 0) {
state.formData.attributes = response.data;
} else {
state.formData.attributes = [
{
id: undefined,
name: '',
},
];
}
});
} else {
state.formData.attributes = [
{
id: undefined,
name: '',
},
];
}
}
);
function handleAdd() {
state.formData.attributes.push({
id: undefined,
name: '',
});
}
function handleDelete(index: number) {
if (state.formData.attributes.length == 1) {
state.formData.attributes = [
{
id: undefined,
name: '',
},
];
return;
}
state.formData.attributes.splice(index, 1);
}
function submitForm() {
state.formData.categoryId = props.category.id;
state.formData.type = props.attributeType;
saveAttributeBatch(state.formData).then(() => {
ElMessage.success('提交成功');
});
}
</script>
<style scoped>
.component-container {
margin-bottom: 20px;
}
</style>

View File

@@ -1,289 +0,0 @@
<script setup lang="ts">
import {
listCategories,
addCategory,
updateCategory,
deleteCategories,
} from '@/api/pms/category';
import { Plus, Edit, Delete, Picture } from '@element-plus/icons-vue';
import SingleUpload from '@/components/Upload/SingleUpload.vue';
import { onMounted, reactive, ref, toRefs, unref } from 'vue';
import { ElForm, ElMessage, ElMessageBox, ElTree } from 'element-plus';
const emit = defineEmits(['categoryClick']);
const categoryTreeRef = ref(ElTree);
const dataFormRef = ref(ElForm);
const state = reactive({
// 遮罩层
loading: true,
ids: [],
queryParam: {},
categoryOptions: [] as Array<any>,
formData: {
id: undefined,
name: undefined,
parentId: 0,
level: undefined,
iconUrl: undefined,
visible: 1,
sort: 100,
},
rules: {
parentId: [
{
required: true,
message: '请选择上级分类',
trigger: 'blur',
},
],
name: [
{
required: true,
message: '请输入分类名称',
trigger: 'blur',
},
],
},
dialog: {
title: '',
visible: false,
},
parent: {} as any,
current: {} as any,
});
const { loading, categoryOptions, formData, rules, dialog, parent } =
toRefs(state);
function handleQuery() {
state.loading = true;
listCategories(state.queryParam).then((response) => {
state.categoryOptions = [
{
id: 0,
name: '全部分类',
parentId: 0,
level: 0,
children: response.data,
},
];
state.loading = false;
});
}
function handleNodeClick(row: any) {
const categoryTree = unref(categoryTreeRef);
const parentNode = categoryTree.getNode(row.parentId);
state.parent = {
id: parentNode.key,
name: parentNode.label,
level: row.level,
};
state.current = JSON.parse(JSON.stringify(row));
emit('categoryClick', row);
}
function handleAdd(row: any) {
state.dialog = {
title: '新增商品分类',
visible: true,
};
if (row.id != null) {
// 行点击新增
state.parent = {
id: row.id,
name: row.name,
level: row.level,
};
}
}
function handleUpdate(row: any) {
handleNodeClick(row);
state.dialog = {
title: '修改商品分类',
visible: true,
};
Object.assign(state.formData, state.current);
}
function submitForm() {
dataFormRef.value.validate((valid: any) => {
if (valid) {
if (state.formData.id) {
updateCategory(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
const parentCategory = state.parent as any;
console.log('parent', parentCategory);
state.formData.parentId = parentCategory.id;
state.formData.level = parentCategory.level + 1;
addCategory(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
deleteCategories(ids).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
});
}
function cancel() {
state.dialog.visible = false;
dataFormRef.value.resetFields();
state.dialog.visible = false;
}
onMounted(() => {
handleQuery();
});
</script>
<!-- 商品分类层级最多为三层level字段标识 -->
<template>
<div class="component-container">
<el-tree
v-loading="loading"
ref="categoryTreeRef"
:data="categoryOptions"
:props="{ label: 'name', children: 'children', disabled: '' }"
node-key="id"
:expand-on-click-node="false"
default-expand-all
:accordion="true"
@node-click="handleNodeClick"
>
<template #default="scope">
<div class="custom-tree-node">
<span>
<el-image
v-show="scope.data.level == 3"
:src="scope.data.iconUrl"
style="
width: 20px;
height: 20px;
vertical-align: middle;
margin-top: -5px;
"
>
<template #error>
<div class="image-slot">
<Picture style="width: 20px; height: 20px" />
</div>
</template>
</el-image>
{{ scope.data.name }}
</span>
<span>
<el-button
v-show="scope.data.level != 3"
type="success"
:icon="Plus"
circle
plain
@click.stop="handleAdd(scope.data)"
/>
<el-button
v-show="scope.data.id !== 0"
type="warning"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.data)"
/>
<el-button
v-show="
scope.data.id &&
(!scope.data.children || scope.data.children.length <= 0)
"
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.data)"
/>
</span>
</div>
</template>
</el-tree>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="750px">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="上级分类" prop="parentId">
<el-input v-model="parent.name" readonly />
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="分类图标" prop="iconUrl">
<single-upload v-model="formData.iconUrl" />
</el-form-item>
<el-form-item label="显示状态" prop="visible">
<el-radio-group v-model="formData.visible">
<el-radio :label="1">显示</el-radio>
<el-radio :label="0">隐藏</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style>
.component-container {
height: 100%;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
line-height: 40px0;
}
.el-tree-node__content {
height: 40px;
}
</style>

View File

@@ -1,79 +0,0 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: 'category',
};
</script>
<script setup lang="ts">
import Category from './components/Category.vue';
import Attribute from './components/Attribute.vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import { reactive, toRefs } from 'vue';
const state = reactive({
category: {
id: undefined,
name: '',
childrenLen: 0,
},
});
const { category } = toRefs(state);
function handleCategoryClick(categoryRow: any) {
if (categoryRow) {
state.category = {
id: categoryRow.id,
name: categoryRow.name,
childrenLen: categoryRow.children.length,
};
} else {
state.category = {
id: undefined,
name: '',
childrenLen: 0,
};
}
}
</script>
<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="14" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="menu" />
商品分类
</template>
<Category ref="categoryRef" @categoryClick="handleCategoryClick" />
</el-card>
</el-col>
<el-col :span="10" :xs="24">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="menu" />
{{ category.name }} 规格属性
</template>
<!-- 商品规格 -->
<Attribute
ref="specificationRef"
:attributeType="1"
:category="category"
/>
<!-- 商品属性 -->
<Attribute
ref="attributeRef"
:attributeType="2"
:category="category"
/>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style scoped></style>

View File

@@ -1,178 +0,0 @@
<template>
<div class="component-container">
<div class="component-container__main">
<el-card class="box-card">
<template #header>
<span>商品属性</span>
<el-button
style="float: right"
type="success"
:icon="Plus"
size="small"
@click="handleAdd"
>
添加属性
</el-button>
</template>
<el-form
ref="dataFormRef"
:model="goodsInfo"
:rules="rules"
size="small"
:inline="true"
>
<el-table
:data="goodsInfo.attrList"
size="small"
highlight-current-row
border
>
<el-table-column property="name" label="属性名称">
<template #default="scope">
<el-form-item
:prop="'attrList[' + scope.$index + '].name'"
:rules="rules.name"
>
<el-input v-model="scope.row.name" />
</el-form-item>
</template>
</el-table-column>
<el-table-column property="value" label="属性值">
<template #default="scope">
<el-form-item
:prop="'attrList[' + scope.$index + '].value'"
:rules="rules.value"
>
<el-input v-model="scope.row.value" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-form-item>
<el-button
v-if="scope.$index > 0"
type="danger"
:icon="Minus"
size="small"
circle
plain
@click.stop="handleRemove(scope.$index)"
/>
</el-form-item>
</template>
</el-table-column>
</el-table>
</el-form>
</el-card>
</div>
<div class="component-container__footer">
<el-button @click="handlePrev">上一步填写商品信息</el-button>
<el-button type="primary" @click="handleNext"
>下一步设置商品库存</el-button
>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref, toRefs, watch } from 'vue';
import { listAttributes } from '@/api/pms/attribute';
import { ElForm } from 'element-plus';
import { Plus, Minus } from '@element-plus/icons-vue';
const emit = defineEmits(['prev', 'next', 'update:modelValue']);
const dataFormRef = ref(ElForm);
const props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
});
const goodsInfo: any = computed({
get: () => props.modelValue,
set: (value) => {
emit('update:modelValue', value);
},
});
watch(
() => goodsInfo.value.categoryId,
(newVal) => {
// 商品编辑不加载分类下的属性
const goodsId = goodsInfo.value.id;
if (goodsId) {
return false;
}
// 商品新增加载默认分类下的属性
if (newVal) {
// type=2 商品分类下的属性
listAttributes({ categoryId: newVal, type: 2 }).then((response) => {
const attrList = response.data;
if (attrList && attrList.length > 0) {
goodsInfo.value.attrList = attrList;
} else {
goodsInfo.value.attrList = [{}];
}
});
} else {
goodsInfo.value.attrList = [{}];
}
},
{
immediate: true,
deep: true,
}
);
const state = reactive({
rules: {
name: [{ required: true, message: '请填写属性名称', trigger: 'blur' }],
value: [{ required: true, message: '请填写属性值', trigger: 'blur' }],
},
});
const { rules } = toRefs(state);
function handleAdd() {
goodsInfo.value.attrList.push({});
}
function handleRemove(index: number) {
goodsInfo.value.attrList.splice(index, 1);
}
function handlePrev() {
emit('prev');
}
function handleNext() {
dataFormRef.value.validate((valid: any) => {
if (valid) {
emit('next');
}
});
}
</script>
<style lang="scss" scoped>
.component-container {
&__main {
margin: 20px auto;
}
&__footer {
position: fixed;
bottom: 20px;
right: 20px;
}
}
.el-form-item--mini.el-form-item {
margin-top: 18px;
}
</style>

View File

@@ -1,113 +0,0 @@
<template>
<div class="component-container">
<div class="component-container__main">
<el-cascader-panel
ref="categoryRef"
:options="categoryOptions"
v-model="goodsInfo.categoryId"
:props="{ emitPath: false }"
@change="handleCategoryChange"
/>
<div style="margin-top: 20px">
<el-link type="info" :underline="false" v-show="pathLabels.length > 0"
>您选择的商品分类:</el-link
>
<el-link
type="danger"
:underline="false"
v-for="(item, index) in pathLabels"
:key="index"
style="margin-left: 5px"
>
{{ item }}
<CaretRight
v-show="index < pathLabels.length - 1"
style="width: 1em; height: 1em; margin-left: 5px"
/>
</el-link>
</div>
</div>
<div class="component-container__footer">
<el-button type="primary" @click="handleNext"
>下一步填写商品信息</el-button
>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, nextTick, reactive, ref, toRefs } from 'vue';
import { ElCascaderPanel, ElMessage } from 'element-plus';
import { CaretRight } from '@element-plus/icons-vue';
// API 引用
import { listCategoryOptions } from '@/api/pms/category';
import { computed } from 'vue';
import { Option } from '@/types/common';
const emit = defineEmits(['next', 'update:modelValue']);
const props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
});
const goodsInfo: any = computed({
get: () => props.modelValue,
set: (value) => {
emit('update:modelValue', value);
},
});
const state = reactive({
categoryOptions: [] as Option[],
pathLabels: [],
});
const { categoryOptions, pathLabels } = toRefs(state);
function loadData() {
listCategoryOptions().then(({ data }) => {
state.categoryOptions = data;
if (goodsInfo.value.id) {
nextTick(() => {
handleCategoryChange();
});
}
});
}
const categoryRef = ref(ElCascaderPanel);
function handleCategoryChange() {
const checkNode = categoryRef.value.getCheckedNodes()[0];
state.pathLabels = checkNode.pathLabels; // 商品分类选择层级提示
goodsInfo.value.categoryId = checkNode.value;
}
function handleNext() {
if (!goodsInfo.value.categoryId) {
ElMessage.warning('请选择商品分类');
return false;
}
emit('next');
}
onMounted(() => {
loadData();
});
</script>
<style lang="scss" scoped>
.component-container {
&__main {
margin: 20px auto;
}
&__footer {
position: fixed;
bottom: 20px;
right: 20px;
}
}
</style>

View File

@@ -1,220 +0,0 @@
<template>
<div class="component-container">
<div class="component-container__main">
<el-form
ref="dataFormRef"
:rules="rules"
:model="goodsInfo"
label-width="120px"
>
<el-form-item label="商品品牌" prop="brandId">
<el-select v-model="goodsInfo.brandId" style="width: 400px" clearable>
<el-option
v-for="item in brandOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="商品名称" prop="name">
<el-input style="width: 400px" v-model="goodsInfo.name" />
</el-form-item>
<el-form-item label="原价" prop="originPrice">
<el-input style="width: 400px" v-model="goodsInfo.originPrice" />
</el-form-item>
<el-form-item label="现价" prop="price">
<el-input style="width: 400px" v-model="goodsInfo.price" />
</el-form-item>
<el-form-item label="商品简介">
<el-input
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
v-model="goodsInfo.description"
/>
</el-form-item>
<el-form-item label="商品相册">
<el-card
v-for="(item, index) in pictures"
:key="index"
style="
width: 170px;
display: inline-block;
margin-left: 10px;
text-align: center;
"
:body-style="{ padding: '10px' }"
>
<single-upload v-model="item.url" :show-close="true" />
<div v-if="item.url">
<el-link type="danger" class="button" v-if="item.main == true"
>商品主图</el-link
>
<el-link
type="info"
class="button"
v-else
@click="changeMainPicture(index)"
>设为主图</el-link
>
</div>
<div v-else>
<!-- 占位 -->
<el-link type="info" />
</div>
</el-card>
</el-form-item>
<el-form-item label="商品详情" prop="detail">
<editor v-model="goodsInfo.detail" style="height: 600px" />
</el-form-item>
</el-form>
</div>
<div class="component-container__footer">
<el-button @click="handlePrev">上一步选择商品分类</el-button>
<el-button type="primary" @click="handleNext"
>下一步设置商品属性</el-button
>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, toRefs } from 'vue';
import { ElForm } from 'element-plus';
// API 依赖
import { listBrands } from '@/api/pms/brand';
// 自定义组件依赖
import Editor from '@/components/WangEditor/index.vue';
import SingleUpload from '@/components/Upload/SingleUpload.vue';
const emit = defineEmits(['prev', 'next', 'update:modelValue']);
const dataFormRef = ref(ElForm);
const props = defineProps({
modelValue: {
type: Object,
default: () => {},
},
});
const goodsInfo: any = computed({
get: () => props.modelValue,
set: (value) => {
emit('update:modelValue', value);
},
});
const state = reactive({
brandOptions: [] as Array<any>,
// 商品图册
pictures: [
{ url: undefined, main: true }, // main为true代表主图可切换
{ url: undefined, main: false },
{ url: undefined, main: false },
{ url: undefined, main: false },
{ url: undefined, main: false },
] as Array<any>,
rules: {
name: [{ required: true, message: '请填写商品名称', trigger: 'blur' }],
originPrice: [{ required: true, message: '请填写原价', trigger: 'blur' }],
price: [{ required: true, message: '请填写现价', trigger: 'blur' }],
brandId: [{ required: true, message: '请选择商品品牌', trigger: 'blur' }],
},
});
const { brandOptions, pictures, rules } = toRefs(state);
function loadData() {
listBrands().then(({ data }) => {
state.brandOptions = data;
});
const goodsId = goodsInfo.value.id;
if (goodsId) {
// 主图
const mainPicUrl = goodsInfo.value.picUrl;
if (mainPicUrl) {
state.pictures.filter((item) => item.main)[0].url = mainPicUrl;
}
// 商品副图
const subPicUrls = goodsInfo.value.subPicUrls;
if (subPicUrls && subPicUrls.length > 0) {
for (let i = 1; i <= subPicUrls.length; i++) {
state.pictures[i].url = subPicUrls[i - 1];
}
}
}
}
/**
* 切换主图
*/
function changeMainPicture(changeIndex: number) {
const currMainPicture = JSON.parse(JSON.stringify(state.pictures[0]));
const nextMainPicture = JSON.parse(
JSON.stringify(state.pictures[changeIndex])
);
state.pictures[0].url = nextMainPicture.url;
state.pictures[changeIndex].url = currMainPicture.url;
}
function handlePrev() {
emit('prev');
}
function handleNext() {
dataFormRef.value.validate((valid: any) => {
if (valid) {
// 商品主图
const mainPicUrl = state.pictures
.filter((item) => item.main == true && item.url)
.map((item) => item.url);
if (mainPicUrl && mainPicUrl.length > 0) {
goodsInfo.value.picUrl = mainPicUrl[0];
}
// 商品副图
const subPicUrls = state.pictures
.filter((item) => item.main == false && item.url)
.map((item) => item.url);
if (subPicUrls && subPicUrls.length > 0) {
goodsInfo.value.subPicUrls = subPicUrls;
} else {
goodsInfo.value.subPicUrls = [];
}
emit('next');
}
});
}
onMounted(() => {
loadData();
});
</script>
<style lang="scss" scoped>
.component-container {
&__main {
margin: 20px auto;
.button {
margin-left: 10px;
}
}
&__footer {
position: fixed;
bottom: 20px;
right: 20px;
}
}
</style>

View File

@@ -1,643 +0,0 @@
<template>
<div class="component-container">
<div class="component-container__main">
<el-card class="box-card">
<template #header>
<span>商品规格</span>
<el-button
:icon="Plus"
type="success"
@click="handleSpecAdd"
size="small"
style="float: right"
>
添加规格项
</el-button>
</template>
<el-form
ref="specFormRef"
:model="specForm"
:inline="true"
size="small"
>
<el-table
ref="specTableRef"
:data="specForm.specList"
row-key="id"
size="small"
>
<el-table-column align="center" width="50">
<template>
<svg-icon class="drag-handler" icon-class="drag" />
</template>
</el-table-column>
<el-table-column label="规格名" width="200">
<template #default="scope">
<el-form-item
:prop="'specList[' + scope.$index + '].name'"
:rules="rules.spec.name"
>
<el-input
type="text"
v-model="scope.row.name"
size="small"
@input="handleSpecChange()"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column>
<template #header>
规格值
<el-link
type="danger"
style="font-size: 12px"
:underline="false"
>默认第一条规格包含图片</el-link
>
</template>
<template #default="scope">
<div
v-for="item in scope.row.values"
:key="item.id"
style="margin-right: 15px; display: inline-block"
>
<el-tag
size="small"
closable
:type="(colors[scope.$index % colors.length] as any)"
@close="handleSpecValueRemove(scope.$index, item.id)"
>
{{ item.value }}
</el-tag>
<single-upload
v-model="item.picUrl"
v-if="scope.$index == 0"
style="margin-top: 5px"
/>
</div>
<el-input
v-if="tagInputs.length > 0 && tagInputs[scope.$index].visible"
v-model="tagInputs[scope.$index].value"
@keyup.enter="handleSpecValueInput(scope.$index)"
@blur="handleSpecValueInput(scope.$index)"
style="width: 80px; vertical-align: top"
size="small"
/>
<el-button
v-else
@click="handleSpecValueAdd(scope.$index)"
:icon="Plus"
style="vertical-align: top"
size="small"
>
添加规格值
</el-button>
</template>
</el-table-column>
<el-table-column width="60" label="操作">
<template #default="scope">
<el-button
type="danger"
:icon="Minus"
size="small"
circle
plain
@click.stop="handleSpecRemove(scope.$index)"
/>
</template>
</el-table-column>
</el-table>
</el-form>
</el-card>
<el-card class="box-card">
<template #header>
<span>商品库存</span>
</template>
<el-form ref="skuFormRef" :model="skuForm" size="small" :inline="true">
<el-table
:data="skuForm.skuList"
:span-method="(objectSpanMethod as any)"
highlight-current-row
size="small"
border
>
<el-table-column
v-for="(title, index) in specTitles"
:key="index"
align="center"
:prop="'specValue' + (index + 1)"
:label="title"
>
</el-table-column>
<el-table-column label="商品编码" align="center">
<template #default="scope">
<el-form-item
:prop="'skuList[' + scope.$index + '].skuSn'"
:rules="rules.sku.skuSn"
>
<el-input v-model="scope.row.skuSn" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="价格" align="center">
<template #default="scope">
<el-form-item
:prop="'skuList[' + scope.$index + '].price'"
:rules="rules.sku.price"
>
<el-input v-model="scope.row.price" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="库存" align="center">
<template #default="scope">
<el-form-item
:prop="'skuList[' + scope.$index + '].stockNum'"
:rules="rules.sku.stockNum"
>
<el-input v-model="scope.row.stockNum" />
</el-form-item>
</template>
</el-table-column>
</el-table>
</el-form>
</el-card>
</div>
<div class="component-container__footer">
<el-button @click="handlePrev">上一步设置商品属性</el-button>
<el-button type="primary" @click="submitForm">提交</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, toRefs, watch } from 'vue';
import { useRouter } from 'vue-router';
import { Plus, Minus } from '@element-plus/icons-vue';
import { ElNotification, ElMessage, ElTable, ElForm } from 'element-plus';
// API 引用
import { listAttributes } from '@/api/pms/attribute';
import { addSpu, updateSpu } from '@/api/pms/goods';
// 自定义组件引用
import SvgIcon from '@/components/SvgIcon/index.vue';
import SingleUpload from '@/components/Upload/SingleUpload.vue';
const emit = defineEmits(['prev', 'next', 'update:modelValue']);
const router = useRouter();
const specFormRef = ref(ElForm);
const skuFormRef = ref(ElForm);
const props = defineProps({
modelValue: {
type: Object,
default: () => {
return {};
},
},
});
const goodsInfo: any = computed({
get: () => props.modelValue,
set: (value) => {
emit('update:modelValue', value);
},
});
const state = reactive({
specForm: {
specList: [] as any[],
},
skuForm: {
skuList: [] as any[],
},
// 规格项表格标题
specTitles: [] as any[],
rules: {
spec: {
name: [{ required: true, message: '请输入规格名称', trigger: 'blur' }],
value: [{ required: true, message: '请输入规格值', trigger: 'blur' }],
},
sku: {
skuSn: [{ required: true, message: '请输入商品编号', trigger: 'blur' }],
price: [{ required: true, message: '请输入商品价格', trigger: 'blur' }],
stockNum: [
{ required: true, message: '请输入商品库存', trigger: 'blur' },
],
},
},
colors: ['', 'success', 'warning', 'danger'],
tagInputs: [{ value: undefined, visible: false }], // 规格值标签临时值和显隐控制
});
const { specForm, skuForm, specTitles, rules, colors, tagInputs } =
toRefs(state);
watch(
() => goodsInfo.value.categoryId,
(newVal) => {
// 商品编辑不加载分类下的规格
const goodsId = goodsInfo.value.id;
if (goodsId) {
return false;
}
if (newVal) {
// type=1 商品分类下的规格
listAttributes({ categoryId: newVal, type: 1 }).then((response) => {
const specList = response.data;
if (specList && specList.length > 0) {
specList.forEach((item: any) => {
state.specForm.specList.push({
name: item.name,
values: [],
});
});
loadData();
}
});
}
},
{
immediate: true,
deep: true,
}
);
watch(state.specForm.specList, () => {
generateSkuList();
});
function loadData() {
goodsInfo.value.specList.forEach((specItem: any) => {
const specIndex = state.specForm.specList.findIndex(
(item: any) => item.name == specItem.name
);
if (specIndex > -1) {
(state.specForm.specList[specIndex] as any).values.push({
id: specItem.id,
value: specItem.value,
picUrl: specItem.picUrl,
});
} else {
state.specForm.specList.push({
name: specItem.name,
values: [
{ id: specItem.id, value: specItem.value, picUrl: specItem.picUrl },
],
});
}
});
// 每个规格项追加一个添加规格值按钮
for (let i = 0; i < state.specForm.specList.length; i++) {
state.tagInputs.push({ value: undefined, visible: false });
}
// SKU规格ID拼接字符串处理
goodsInfo.value.skuList.forEach((sku: any) => {
sku.specIdArr = sku.specIds.split('_');
});
generateSkuList();
handleSpecChange();
handleSpecReorder();
}
/**
* 生成SKU列表的title
*/
function handleSpecChange() {
const specList = JSON.parse(JSON.stringify(state.specForm.specList));
state.specTitles = specList.map((item: any) => item.name);
}
/**
* 规格列表重排序
*/
function handleSpecReorder() {
state.specForm.specList.forEach((item, index) => {
item.index = index;
});
}
/**
* 根据商品规格笛卡尔积生成SKU列表
*
* 规格列表:
* [
* { 'id':1,'name':'颜色','values':[{id:1,value:'白色'},{id:2,value:'黑色'},{id:3,value:'蓝色'}] },
* { 'id':2,'name':'版本','values':[{id:1,value:'6+128G'},{id:2,value:'8+128G'},{id:3,value:'8G+256G'}] }
* ]
*/
function generateSkuList() {
// 如果规格为空生成SKU列表为空
if (state.specForm.specList.length == 0) {
state.skuForm.skuList = [];
return;
}
const specList = JSON.parse(
JSON.stringify(
state.specForm.specList.filter(
(item) => item.values && item.values.length > 0
)
)
); // 深拷贝取有属性的规格项否则笛卡尔积运算得到的SKU列表值为空
const skuList = specList.reduce(
(acc: any, curr: any) => {
let result = [] as any[];
acc.forEach((item: any) => {
// curr => { 'id':1,'name':'颜色','values':[{id:1,value:'白色'},{id:2,value:'黑色'},{id:3,value:'蓝色'}] }
curr.values.forEach((v: any) => {
// v=>{id:1,value:'白色'}
let temp = Object.assign({}, item);
temp.specValues += v.value + '_'; // 规格值拼接
temp.specIds += v.id + '|'; // 规格ID拼接
result.push(temp);
});
});
return result;
},
[{ specValues: '', specIds: '' }]
);
skuList.forEach((item: any) => {
item.specIds = item.specIds.substring(0, item.specIds.length - 1);
item.name = item.specValues
.substring(0, item.specValues.length - 1)
.replaceAll('_', ' ');
const specIdArr = item.specIds.split('|');
const skus = goodsInfo.value.skuList.filter(
(sku: any) =>
sku.specIdArr.length === specIdArr.length &&
sku.specIdArr.every((a: any) => specIdArr.some((b: any) => a === b)) &&
specIdArr.every((x: any) => sku.specIdArr.some((y: any) => x === y))
); // 数据库的SKU列表
if (skus && skus.length > 0) {
const sku = skus[0];
item.id = sku.id;
item.skuSn = sku.skuSn;
item.price = sku.price / 100;
item.stockNum = sku.stockNum;
}
const specValueArr = item.specValues
.substring(0, item.specValues.length - 1)
.split('_'); // ['黑','6+128G','官方标配']
specValueArr.forEach((v: any, i: any) => {
const key = 'specValue' + (i + 1);
item[key] = v;
if (i == 0 && state.specForm.specList.length > 0) {
const valueIndex = state.specForm.specList[0].values.findIndex(
(specValue: any) => specValue.value == v
);
if (valueIndex > -1) {
item.picUrl = state.specForm.specList[0].values[valueIndex].picUrl;
}
}
});
});
state.skuForm.skuList = JSON.parse(JSON.stringify(skuList));
}
/**
* 添加规格
*/
function handleSpecAdd() {
if (state.specForm.specList.length >= 3) {
ElMessage.warning('最多支持3组规格');
return;
}
state.specForm.specList.push({ values: [] });
state.tagInputs.push({ value: undefined, visible: false });
handleSpecReorder();
}
/**
* 删除规格
* @param index
*/
function handleSpecRemove(index: any) {
state.specForm.specList.splice(index, 1);
state.tagInputs.splice(index, 1);
generateSkuList();
handleSpecReorder();
handleSpecChange();
}
/**
* 添加规格值
*
* @param specIndex
*/
function handleSpecValueAdd(specIndex: any) {
state.tagInputs[specIndex].visible = true;
}
/**
* 删除规格值
*
* @param rowIndex
* @param specValueId
*/
function handleSpecValueRemove(rowIndex: any, specValueId: any) {
const specList = JSON.parse(JSON.stringify(state.specForm.specList));
const removeIndex = specList[rowIndex].values
.map((item: any) => item.id)
.indexOf(specValueId);
specList[rowIndex].values.splice(removeIndex, 1);
state.specForm.specList = specList;
generateSkuList();
handleSpecChange();
handleSpecReorder();
}
/**
* 规格值输入
*/
function handleSpecValueInput(rowIndex: any) {
const currSpecValue = state.tagInputs[rowIndex].value;
const specValues = state.specForm.specList[rowIndex].values;
if (
specValues &&
specValues.length > 0 &&
specValues.map((item: any) => item.value).includes(currSpecValue)
) {
ElMessage.warning('规格值重复,请重新输入');
return false;
}
if (currSpecValue) {
if (specValues && specValues.length > 0) {
// 临时规格值ID tid_1_1
let maxSpecValueIndex = specValues
.filter((item: any) => item.id.includes('tid_'))
.map((item: any) => item.id.split('_')[2])
.reduce((acc: any, curr: any) => {
return acc > curr ? acc : curr;
}, 0);
state.specForm.specList[rowIndex].values[specValues.length] = {
value: currSpecValue,
id: 'tid_' + (rowIndex + 1) + '_' + ++maxSpecValueIndex,
};
} else {
state.specForm.specList[rowIndex].values = [
{ value: currSpecValue, id: 'tid_' + (rowIndex + 1) + '_1' },
];
}
}
state.tagInputs[rowIndex].value = undefined;
state.tagInputs[rowIndex].visible = false;
generateSkuList();
}
/**
* 合并规格单元格
*
* @param cellObj 单元格对象
*/
const objectSpanMethod = ({ rowIndex, columnIndex }: any) => {
let mergeRows = [1, 1, 1]; // 分别对应规格1、规格2、规格3列合并的行数
const specLen = state.specForm.specList.filter(
(item) => item.values && item.values.length > 0
).length;
if (specLen == 2) {
const values_len_2 = state.specForm.specList[1].values
? state.specForm.specList[1].values.length
: 1; // 第2个规格项的规格值的数量
mergeRows = [values_len_2, 1, 1];
} else if (specLen == 3) {
const values_len_2 = state.specForm.specList[1].values
? state.specForm.specList[1].values.length
: 1; // 第2个规格项的规格值的数量
const values_len_3 = state.specForm.specList[2].values
? state.specForm.specList[2].values.length
: 1; // 第3个规格项的规格值的数量
mergeRows = [values_len_2 * values_len_3, values_len_3, 1];
}
if (columnIndex == 0) {
if (rowIndex % mergeRows[0] === 0) {
return [mergeRows[0], 1]; // 合并单元格
} else {
return [0, 0]; // 隐藏单元格
}
}
if (columnIndex == 1) {
if (rowIndex % mergeRows[1] === 0) {
return [mergeRows[1], 1]; // 合并单元格
} else {
return [0, 0]; // 隐藏单元格
}
}
};
/**
* 商品表单提交
*/
function submitForm() {
// 判断商品SKU列表是否为空
if (!state.skuForm.skuList || state.skuForm.skuList.length === 0) {
ElMessage.warning('未添加商品库存');
return false;
}
specFormRef.value.validate((specValid: any) => {
if (specValid) {
skuFormRef.value.validate((skuValid: any) => {
if (skuValid) {
// 重组商品的规格和SKU列表
let submitsData = Object.assign({}, goodsInfo.value);
delete submitsData.specList;
delete submitsData.skuList;
let specList = [] as any[];
state.specForm.specList.forEach((item) => {
item.values.forEach((value: any) => {
value.name = item.name;
});
specList = specList.concat(item.values);
});
submitsData.specList = specList; // 规格列表
submitsData.price *= 100; // 金额转成分保存至数据库
submitsData.originPrice *= 100;
let skuList = JSON.parse(JSON.stringify(state.skuForm.skuList));
skuList.map((item: any) => {
item.price *= 100;
return item;
});
submitsData.skuList = skuList;
console.log('提交数据', submitsData);
const goodsId = goodsInfo.value.id;
if (goodsId) {
// 编辑商品提交
updateSpu(goodsId, submitsData).then(() => {
router.push({ path: '/pms/goods' });
ElNotification({
title: '提示',
message: '编辑商品成功',
type: 'success',
});
});
} else {
// 新增商品提交
addSpu(submitsData).then(() => {
router.push({ path: '/pms/goods' });
ElNotification({
title: '提示',
message: '新增商品成功',
type: 'success',
});
});
}
}
});
}
});
}
function handlePrev() {
emit('prev');
}
onMounted(() => {
loadData();
});
</script>
<style lang="scss" scoped>
.component-container {
&__main {
margin: 20px auto;
}
&__footer {
position: fixed;
bottom: 20px;
right: 20px;
}
.box-card {
margin-bottom: 20px;
}
}
.el-form--inline .el-form-item {
margin-top: 18px;
}
</style>

View File

@@ -1,110 +0,0 @@
<template>
<div class="app-container">
<el-steps
:active="active"
process-status="finish"
finish-status="success"
simple
>
<el-step title="选择商品分类" />
<el-step title="填写商品信息" />
<el-step title="设置商品属性" />
<el-step title="设置商品库存" />
</el-steps>
<GoodsCategory
v-show="active == 0"
v-model="goodsInfo"
v-if="loaded == true"
@prev="prev"
@next="next"
/>
<GoodsInfo
v-show="active == 1"
v-model="goodsInfo"
v-if="loaded == true"
@prev="prev"
@next="next"
/>
<GoodsAttribute
v-show="active == 2"
v-model="goodsInfo"
v-if="loaded == true"
@prev="prev"
@next="next"
/>
<GoodsStock
v-show="active == 3"
v-model="goodsInfo"
v-if="loaded == true"
@prev="prev"
@next="next"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, toRefs } from 'vue';
import GoodsCategory from './components/GoodsCategory.vue';
import GoodsInfo from './components/GoodsInfo.vue';
import GoodsAttribute from './components/GoodsAttribute.vue';
import GoodsStock from './components/GoodsStock.vue';
import { getSpuDetail } from '@/api/pms/goods';
import { useRoute } from 'vue-router';
import { GoodsDetail } from '@/types/api/pms/goods';
const route = useRoute();
const state = reactive({
loaded: false,
active: 0,
goodsInfo: {
album: [],
attrList: [],
specList: [],
skuList: [],
} as GoodsDetail,
});
const { loaded, active, goodsInfo } = toRefs(state);
function loadData() {
const goodsId = route.query.goodsId as string;
if (goodsId) {
getSpuDetail(goodsId).then((response) => {
state.goodsInfo = response.data;
state.goodsInfo.originPrice = (state.goodsInfo.originPrice as any) / 100;
state.goodsInfo.price = (state.goodsInfo.price as any) / 100;
state.loaded = true;
});
} else {
state.loaded = true;
}
}
function prev() {
if (state.active-- <= 0) {
state.active = 0;
}
}
function next() {
if (state.active++ >= 3) {
state.active = 0;
}
}
onMounted(() => {
loadData();
});
</script>
<style lang="scss" scoped>
.app-container {
width: 1200px;
margin: 50px auto;
border: 1px solid #eee;
}
</style>

View File

@@ -1,277 +0,0 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: 'goods',
};
</script>
<script setup lang="ts">
import { reactive, ref, onMounted, toRefs } from 'vue';
import { ElTable, ElMessage, ElMessageBox } from 'element-plus';
import { useRouter } from 'vue-router';
import {
Search,
Position,
Edit,
Refresh,
Delete,
View,
} from '@element-plus/icons-vue';
import { listSpuPages, deleteSpu } from '@/api/pms/goods';
import { listCategoryOptions } from '@/api/pms/category';
import { GoodsItem, GoodsQueryParam } from '@/types/api/pms/goods';
import { moneyFormatter } from '@/utils/filter';
import { Option } from '@/types/common';
const dataTableRef = ref(ElTable);
const router = useRouter();
const state = reactive({
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
} as GoodsQueryParam,
goodsList: [] as GoodsItem[],
categoryOptions: [] as Option[],
goodDetail: undefined,
dialogVisible: false,
});
const {
loading,
multiple,
queryParams,
goodsList,
categoryOptions,
goodDetail,
total,
dialogVisible,
} = toRefs(state);
function handleQuery() {
state.loading = true;
listSpuPages(state.queryParams).then(({ data }) => {
state.goodsList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
state.queryParams = {
pageNum: 1,
pageSize: 10,
name: undefined,
categoryId: undefined,
};
handleQuery();
}
function handleGoodsView(detail: any) {
state.goodDetail = detail;
state.dialogVisible = true;
}
function handleAdd() {
router.push({ path: 'goods-detail' });
}
function handleUpdate(row: any) {
router.push({
path: 'goods-detail',
query: { goodsId: row.id, categoryId: row.categoryId },
});
}
function handleDelete(row: any) {
const ids = row.id || state.ids.join(',');
ElMessageBox.confirm('是否确认删除选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(function () {
return deleteSpu(ids);
})
.then(() => {
ElMessage.success('删除成功');
handleQuery();
});
}
function handleRowClick(row: any) {
dataTableRef.value.toggleRowSelection(row);
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: { id: any }) => item.id);
state.single = selection.length != 1;
state.multiple = !selection.length;
}
onMounted(() => {
listCategoryOptions().then(({ data }) => {
categoryOptions.value = data;
});
handleQuery();
});
</script>
<template>
<div class="app-container">
<el-form ref="queryForm" :inline="true">
<el-form-item>
<el-button type="success" :icon="Position" @click="handleAdd"
>发布商品</el-button
>
<el-button
type="danger"
:icon="Delete"
@click="handleDelete"
:disabled="multiple"
>删除</el-button
>
</el-form-item>
<el-form-item>
<el-input
v-model="queryParams.name"
placeholder="商品名称"
clearable
></el-input>
</el-form-item>
<el-form-item>
<el-cascader
v-model="queryParams.categoryId"
placeholder="商品分类"
:props="{ emitPath: false }"
:options="categoryOptions"
clearable
style="width: 300px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"
>查询</el-button
>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
ref="dataTableRef"
v-loading="loading"
:data="goodsList"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
border
>
<el-table-column type="selection" min-width="5%" center />
<el-table-column type="expand" width="120" label="库存信息">
<template #default="props">
<el-table :data="props.row.skuList" border>
<el-table-column align="center" label="商品编码" prop="skuSn" />
<el-table-column align="center" label="商品规格" prop="name" />
<el-table-column label="图片" prop="picUrl">
<template #default="scope">
<img :src="scope.row.picUrl" width="40" />
</template>
</el-table-column>
<el-table-column align="center" label="现价" prop="price">
<template #default="scope">{{
moneyFormatter(scope.row.price)
}}</template>
</el-table-column>
<el-table-column align="center" label="库存" prop="stockNum" />
</el-table>
</template>
</el-table-column>
<el-table-column label="商品名称" prop="name" min-width="140" />
<el-table-column label="商品图片">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.picUrl" width="400" height="400" />
<template #reference>
<img
:src="scope.row.picUrl"
style="max-height: 60px; max-width: 60px"
/>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column label="商品类目" prop="categoryName" min-width="100" />
<el-table-column label="商品品牌" prop="brandName" min-width="100" />
<el-table-column align="center" label="零售价" prop="originalPrice">
<template #default="scope">{{
moneyFormatter(scope.row.originPrice)
}}</template>
</el-table-column>
<el-table-column align="center" label="促销价" prop="price">
<template #default="scope">{{
moneyFormatter(scope.row.price)
}}</template>
</el-table-column>
<el-table-column label="销量" prop="sales" min-width="100" />
<el-table-column label="单位" prop="unit" min-width="100" />
<el-table-column
label="描述"
prop="description"
width="300"
:show-overflow-tooltip="true"
/>
<el-table-column label="详情" prop="detail">
<template #default="scope">
<el-button
type="primary"
:icon="View"
circle
plain
@click.stop="handleGoodsView(scope.row.detail)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button
type="primary"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.row)"
/>
<el-button
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
<el-dialog v-model="dialogVisible" title="商品详情">
<div class="goods-detail-box" v-html="goodDetail" />
</el-dialog>
</div>
</template>
<style scoped></style>

View File

@@ -1,306 +0,0 @@
<script lang="ts">
export default {
name: 'advert',
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from 'vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import SingleUpload from '@/components/Upload/SingleUpload.vue';
import {
listAdvertPages,
getAdvertFormDetail,
updateAdvert,
addAdvert,
deleteAdverts,
} from '@/api/sms/advert';
import { Dialog } from '@/types/common';
import {
AdvertFormData,
AdvertItem,
AdvertQueryParam,
} from '@/types/api/sms/advert';
const queryFormRef = ref(ElForm); // 属性名必须和元素的ref属性值一致
const dataFormRef = ref(ElForm); // 属性名必须和元素的ref属性值一致
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
queryParams: { pageNum: 1, pageSize: 10 } as AdvertQueryParam,
advertList: [] as AdvertItem[],
total: 0,
dialog: { title: '', visible: false } as Dialog,
formData: {
status: 1,
sort: 100,
} as AdvertFormData,
rules: {
title: [{ required: true, message: '请输入广告名称', trigger: 'blur' }],
picUrl: [{ required: true, message: '请上传广告图片', trigger: 'blur' }],
},
validityPeriod: '' as any,
});
const {
loading,
multiple,
queryParams,
advertList,
total,
dialog,
formData,
rules,
validityPeriod,
} = toRefs(state);
function handleQuery() {
state.loading = true;
listAdvertPages(state.queryParams).then(({ data }) => {
state.advertList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.id);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
function handleAdd() {
state.dialog = {
title: '添加广告',
visible: true,
};
}
function handleUpdate(row: any) {
state.dialog = {
title: '修改广告',
visible: true,
};
const advertId = row.id || state.ids;
getAdvertFormDetail(advertId).then(({ data }) => {
state.formData = data;
validityPeriod.value = [data.beginTime, data.endTime];
});
}
function submitForm() {
dataFormRef.value.validate((valid: any) => {
if (valid) {
const avertId = state.formData.id;
if (avertId) {
// 有效期转换
if (validityPeriod.value) {
formData.value.beginTime = validityPeriod.value[0];
formData.value.endTime = validityPeriod.value[1];
}
updateAdvert(avertId, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
addAdvert(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
function cancel() {
state.formData.id = undefined;
dataFormRef.value.resetFields();
state.dialog.visible = false;
}
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deleteAdverts(ids).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
})
.catch(() => ElMessage.info('已取消删除'));
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd"
>新增</el-button
>
<el-button
type="danger"
:icon="Delete"
:disabled="multiple"
@click="handleDelete"
>删除</el-button
>
</el-form-item>
<el-form-item prop="title">
<el-input
v-model="queryParams.keywords"
placeholder="标题"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="advertList"
@selection-change="handleSelectionChange"
border
>
<el-table-column type="selection" min-width="5" align="center" />
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="title" min-width="100" label="广告标题" />
<el-table-column label="广告图片" width="100">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.picUrl" width="400" height="400" />
<template #reference>
<img
:src="scope.row.picUrl"
style="max-height: 60px; max-width: 60px"
/>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="beginTime" label="开始时间" width="150" />
<el-table-column prop="endTime" label="结束时间" width="150" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">开启</el-tag>
<el-tag v-else type="info">关闭</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button
type="primary"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.row)"
/>
<el-button
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="700px">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="广告标题" prop="title">
<el-input v-model="formData.title" />
</el-form-item>
<el-form-item label="有效期">
<el-date-picker
v-model="validityPeriod"
type="daterange"
range-separator="~"
start-placeholder="起始时间"
end-placeholder="截止时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="广告图片" prop="picUrl">
<single-upload v-model="formData.picUrl" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" style="width: 200px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">开启</el-radio>
<el-radio :label="0">关闭</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="跳转链接" prop="url">
<el-input v-model="formData.url" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@@ -1,532 +0,0 @@
<!--优惠券-->
<script lang="ts">
export default {
name: 'coupon',
};
</script>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs } from 'vue';
import { ElForm, ElMessage, ElMessageBox, ElCascaderPanel } from 'element-plus';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import {
lisCouponPages,
getCouponFormData,
updateCoupon,
addCoupon,
deleteCoupons,
} from '@/api/sms/coupon';
import { listCategoryOptions } from '@/api/pms/category';
import { listSpuPages } from '@/api/pms/goods';
import { Dialog, Option } from '@/types/common';
import {
CouponItem,
CouponQueryParam,
CouponFormData,
} from '@/types/api/sms/coupon';
import { GoodsItem, GoodsQueryParam } from '@/types/api/pms/goods';
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const spuCategoryRef = ref(ElCascaderPanel);
const state = reactive({
loading: true,
ids: [],
single: true,
multiple: true,
queryParams: { pageNum: 1, pageSize: 10 } as CouponQueryParam,
couponList: [] as CouponItem[],
total: 0,
dialog: {
visible: false,
} as Dialog,
//指定商品分类选择Dialog
spuCategoryChooseDialog: {
visible: false,
} as Dialog,
// 指定商品选择ialog
spuChooseDialog: {
visible: false,
} as Dialog,
formData: {
type: 1,
platform: 0,
validityPeriodType: 1,
perLimit: 1,
applicationScope: 0,
} as CouponFormData,
rules: {
type: [{ required: true, message: '请输入优惠券名称', trigger: 'blur' }],
name: [{ required: true, message: '请选择优惠券类型', trigger: 'blur' }],
},
validityPeriod: '' as any,
perLimitChecked: false,
spuCategoryOptions: [] as Option[],
spuCategoryProps: {
multiple: true,
emitPath: false,
},
spuList: [] as GoodsItem[],
spuTotal: 0,
spuQueryParams: { pageNum: 1, pageSize: 10 } as GoodsQueryParam,
checkedSpuIds: [],
});
const {
loading,
multiple,
queryParams,
couponList,
total,
dialog,
formData,
rules,
validityPeriod,
perLimitChecked,
spuCategoryOptions,
spuCategoryProps,
spuList,
spuTotal,
checkedSpuIds,
} = toRefs(state);
/**
* 查询
*/
function handleQuery() {
state.loading = true;
lisCouponPages(queryParams.value).then(({ data }) => {
couponList.value = data.list;
total.value = data.total;
loading.value = false;
});
}
/**
* 查询重置
*/
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.id);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
/**
* 加载商品分类
*/
async function loadSpuCategoryOptions() {
listCategoryOptions().then(({ data }) => {
spuCategoryOptions.value = data;
});
}
async function loadSpuList() {
const queryParams = { pageNum: 1, pageSize: 10 } as GoodsQueryParam;
listSpuPages(queryParams).then(({ data }) => {
spuList.value = data.list;
});
}
function handleAdd() {
dialog.value = {
title: '新增优惠券',
visible: true,
};
loadSpuCategoryOptions();
loadSpuList();
}
async function handleUpdate(row: any) {
dialog.value = {
title: '编辑优惠券',
visible: true,
};
const id = row.id;
await loadSpuCategoryOptions();
await loadSpuList();
getCouponFormData(id).then(({ data }) => {
formData.value = data;
perLimitChecked.value = data.perLimit == -1;
// 有效期转换
if (data.validityPeriodType == 1) {
validityPeriod.value = [data.validityBeginTime, data.validityEndTime];
}
// 金额转换分→元
if (formData.value.faceValue) {
formData.value.faceValue /= 100;
}
if (formData.value.minPoint) {
formData.value.minPoint /= 100;
}
});
}
function submitForm() {
dataFormRef.value.validate((valid: any) => {
if (valid) {
const applicationScope = formData.value.applicationScope;
console.log('applicationScope', applicationScope);
if (applicationScope == 1) {
// 指定商品分类
formData.value.spuCategoryIds =
spuCategoryRef.value.getCheckedNodes()[0].data.value;
}
// 有效期转换
if (formData.value.validityPeriodType == 1 && validityPeriod.value) {
formData.value.validityBeginTime = validityPeriod.value[0];
formData.value.validityEndTime = validityPeriod.value[1];
}
// 金额转换元→分
if (formData.value.faceValue) {
formData.value.faceValue *= 100;
}
if (formData.value.faceValue) {
formData.value.minPoint *= 100;
}
const couponId = formData.value.id;
if (couponId) {
updateCoupon(couponId, formData.value).then(() => {
ElMessage.success('编辑优惠券成功');
cancel();
handleQuery();
});
} else {
addCoupon(formData.value).then(() => {
ElMessage.success('新增优惠券成功');
cancel();
handleQuery();
});
}
}
});
}
/**
* 取消
*/
function cancel() {
state.formData.id = undefined;
dataFormRef.value.resetFields();
state.dialog.visible = false;
}
/**
* 删除优惠券
*/
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deleteCoupons(ids).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
})
.catch(() => ElMessage.info('已取消删除'));
}
function handleSpuQuery() {
listSpuPages(queryParams.value).then(({ data }) => {
spuList.value = data.list;
spuTotal.value = data.total;
});
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd"
>新增</el-button
>
<el-button
type="danger"
:icon="Delete"
:disabled="multiple"
@click="handleDelete"
>删除</el-button
>
</el-form-item>
<el-form-item prop="keywords">
<el-input
v-model="queryParams.keywords"
placeholder="优惠券名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="couponList"
@selection-change="handleSelectionChange"
border
>
<el-table-column type="selection" min-width="5" align="center" />
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="name" min-width="100" label="优惠券名称" />
<el-table-column prop="code" min-width="100" label="优惠券码" />
<el-table-column prop="typeLabel" min-width="100" label="优惠券类型" />
<el-table-column prop="faceValueLabel" min-width="100" label="面值" />
<el-table-column prop="minPointLabel" min-width="100" label="使用门槛" />
<el-table-column
prop="validityPeriodLabel"
min-width="200"
label="有效期"
/>
<el-table-column label="操作" align="center" width="150">
<template #default="scope">
<el-button
type="primary"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.row)"
/>
<el-button
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
<!-- 表单Dialog -->
<el-dialog
:title="dialog.title"
v-model="dialog.visible"
width="1000px;"
top="5vh"
>
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="150px"
>
<el-form-item label="优惠券类型" prop="type">
<el-radio-group v-model="formData.type">
<el-radio :label="1">满减券</el-radio>
<el-radio :label="2">直减券</el-radio>
<el-radio :label="3">折扣券</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="优惠券名称" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="优惠券面值" prop="faceValueType">
<el-radio-group
v-model="formData.faceValueType"
style="
display: flex;
flex-direction: column;
justify-content: space-between;
"
>
<div>
<el-radio :label="1">现金</el-radio>
<el-input
v-model="formData.faceValue"
:disabled="formData.faceValueType !== 1"
placeholder="0.5-1000的数字"
style="width: 180px"
>
<template #append></template>
</el-input>
</div>
<div>
<el-radio :label="2">折扣</el-radio>
<el-input
v-model="formData.discount"
:disabled="formData.faceValueType !== 2"
placeholder="1-9.9的数字"
style="width: 180px"
>
<template #append></template>
</el-input>
</div>
</el-radio-group>
</el-form-item>
<el-form-item label="使用门槛" prop="minPoint">
<el-input
v-model="formData.minPoint"
placeholder="0为无限制"
style="width: 300px"
>
<template #prepend></template>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="有效时间" prop="validType">
<el-radio-group
v-model="formData.validityPeriodType"
style="display: flex; flex-direction: column"
>
<div>
<el-radio :label="1">日期范围</el-radio>
<el-date-picker
v-model="validityPeriod"
type="daterange"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:disabled="formData.validityPeriodType !== 1"
/>
</div>
<div style="width: 100%">
<el-radio :label="2">固定天数</el-radio>
<el-input
v-model="formData.validityDays"
style="width: 150px"
:disabled="formData.validityPeriodType !== 2"
>
<template #append></template>
</el-input>
</div>
</el-radio-group>
</el-form-item>
<el-form-item label="发行量" prop="circulation">
<el-input
v-model="formData.circulation"
placeholder="-1为无限制"
style="width: 200px"
>
<template #prepend></template>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="每人限领张数" prop="perLimit">
<el-input
v-model="formData.perLimit"
style="width: 200px"
placeholder="-1为不限制"
>
<template #prepend></template>
<template #append></template>
</el-input>
</el-form-item>
<el-form-item label="备注信息" prop="remark">
<el-input type="textarea" v-model="formData.remark" />
</el-form-item>
<el-form-item label="商品范围" prop="useType">
<el-radio-group
v-model="formData.applicationScope"
style="width: 100%"
>
<el-radio :label="0">全场通用</el-radio>
<el-radio :label="1">指定商品分类</el-radio>
<el-radio :label="2">指定商品</el-radio>
</el-radio-group>
<div class="application-container">
<!-- 指定商品分类 -->
<el-cascader
ref="spuCategoryRef"
v-if="formData.applicationScope == 1"
v-model="formData.spuCategoryIds"
:options="spuCategoryOptions"
:props="spuCategoryProps"
:show-all-levels="true"
style="width: 450px"
/>
<el-transfer
class="application-container__transfer"
v-model="formData.spuIds"
v-if="formData.applicationScope == 2"
filterable
filter-placeholder="商品名称/编码"
:data="spuList"
:titles="['商品列表', '已选择商品']"
:props="{
key: 'id',
label: 'name',
}"
>
<template #left-footer>
<pagination
:total="spuTotal"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleSpuQuery"
layout="prev, pager, next,"
/>
</template>
</el-transfer>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="scss">
.application-container {
margin-top: 20px;
&__transfer {
.pagination-container {
padding: 0 !important;
}
}
}
</style>

View File

@@ -1,374 +0,0 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: 'client',
};
</script>
<script setup lang="ts">
import {
listClientPages,
getClientFormDetial,
addClient,
updateClient,
deleteClients,
} from '@/api/system/client';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { onMounted, reactive, getCurrentInstance, ref, toRefs } from 'vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import {
ClientFormData,
ClientItem,
ClientQueryParam,
} from '@/types/api/system/client';
import { Option } from '@/types/common';
const { proxy }: any = getCurrentInstance();
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
} as ClientQueryParam,
clientList: [] as ClientItem[],
total: 0,
dialog: {
title: '',
visible: false,
type: 'create',
},
formData: {} as ClientFormData,
rules: {
clientId: [
{ required: true, message: '客户端ID不能为空', trigger: 'blur' },
],
},
authorizedGrantTypesOptions: [] as Option[],
checkedAuthorizedGrantTypes: [] as string[],
});
const {
loading,
ids,
multiple,
queryParams,
clientList,
total,
dialog,
formData,
rules,
authorizedGrantTypesOptions,
checkedAuthorizedGrantTypes,
} = toRefs(state);
function handleQuery() {
listClientPages(state.queryParams).then(({ data }) => {
state.clientList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: any) => item.clientId);
state.single = selection.length !== 1;
state.multiple = !selection.length;
}
function handleAdd() {
proxy.$getDictItemsByTypeCode('grant_type').then((response: any) => {
state.authorizedGrantTypesOptions = response.data;
});
state.dialog = {
title: '添加客户端',
visible: true,
type: 'create',
};
}
function handleUpdate(row: any) {
state.dialog = {
title: '修改客户端',
visible: true,
type: 'edit',
};
const clientId = row.clientId || ids;
proxy.$getDictItemsByTypeCode('grant_type').then((res: any) => {
state.authorizedGrantTypesOptions = res.data;
getClientFormDetial(clientId).then(({ data }) => {
state.formData = data;
state.checkedAuthorizedGrantTypes = data.authorizedGrantTypes?.split(',');
});
});
}
function submitForm() {
dataFormRef.value.validate((isvalid: boolean) => {
if (isvalid) {
state.formData.authorizedGrantTypes =
state.checkedAuthorizedGrantTypes.join(',');
if (state.dialog.type == 'edit') {
updateClient(state.formData.clientId, state.formData).then(() => {
ElMessage.success('修改成功');
state.dialog.visible = false;
cancel();
handleQuery();
});
} else {
addClient(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
function cancel() {
state.dialog.visible = false;
dataFormRef.value.resetFields();
state.checkedAuthorizedGrantTypes = [];
}
function handleDelete(row: any) {
const clientIds = [row.clientId || ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deleteClients(clientIds).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
})
.catch(() => ElMessage.info('已取消删除'));
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-button type="success" :icon="Plus" @click="handleAdd"
>新增</el-button
>
<el-button
type="danger"
:icon="Delete"
:disabled="multiple"
@click="handleDelete"
>删除</el-button
>
</el-form-item>
<el-form-item>
<el-input
v-model="queryParams.keywords"
placeholder="客户端ID"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="clientList"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="55" align="center" />
<el-table-column label="客户端ID" prop="clientId" width="200" />
<el-table-column label="客户端密钥" prop="clientSecret" width="100" />
<el-table-column label="域" width="100" prop="scope" />
<el-table-column label="自动放行" prop="autoapprove" width="100" />
<el-table-column label="授权方式" prop="authorizedGrantTypes" />
<el-table-column
label="认证令牌时效(单位:秒)"
width="200"
prop="accessTokenValidity"
/>
<el-table-column
label="刷新令牌时效(单位:秒)"
width="200"
prop="refreshTokenValidity"
/>
<el-table-column label="操作" align="center" width="120">
<template #default="scope">
<el-button
type="primary"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.row)"
/>
<el-button
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
<!-- 表单弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="700px">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-row>
<el-col :span="12">
<el-form-item label="客户端ID" prop="clientId">
<el-input
v-model="formData.clientId"
placeholder="请输入客户端ID"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户端密钥" prop="clientSecret">
<el-input
v-model="formData.clientSecret"
placeholder="请输入客户端密钥"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="域" prop="scope">
<el-input v-model="formData.scope" placeholder="请输入域" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="自动放行" prop="autoapprove">
<el-radio-group v-model="formData.autoapprove">
<el-radio label="true"></el-radio>
<el-radio label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="授权方式" prop="authorizedGrantTypes">
<el-checkbox-group v-model="checkedAuthorizedGrantTypes">
<el-checkbox
v-for="item in authorizedGrantTypesOptions"
:key="item.value"
:label="item.value"
>{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="认证令牌时效" prop="accessTokenValidity">
<el-input
v-model="formData.accessTokenValidity"
placeholder="请输入认证令牌时效"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="刷新令牌时效" prop="refreshTokenValidity">
<el-input
v-model="formData.refreshTokenValidity"
placeholder="请输入刷新令牌时效"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="回调地址" prop="webServerRedirectUri">
<el-input
v-model="formData.webServerRedirectUri"
placeholder="请输入回调地址"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="权限" prop="authorities">
<el-input
v-model="formData.authorities"
placeholder="请输入权限"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="扩展信息" prop="additionalInformation">
<el-input
v-model="formData.additionalInformation"
type="textarea"
placeholder="JSON格式"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> 确定 </el-button>
<el-button @click="cancel"> 取消 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@@ -16,16 +16,12 @@ import {
addDept,
listDeptOptions,
listDepartments
} from '@/api/system/dept';
} from '@/api/dept';
// 组件依赖
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import {
DeptFormData,
DeptItem,
DeptQueryParam
} from '@/types/api/system/dept';
import { DeptFormData, DeptItem, DeptQueryParam } from '@/types/api/dept';
import { Dialog, Option } from '@/types/common';
// DOM元素的引用声明定义

View File

@@ -1,6 +1,6 @@
<script lang="ts">
export default {
name: 'dictItem',
name: 'dictItem'
};
</script>
@@ -10,8 +10,8 @@ import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import {
DictItem,
DictItemFormData,
DictItemQueryParam,
} from '@/types/api/system/dict';
DictItemQueryParam
} from '@/types/api/dict';
import { Dialog } from '@/types/common';
import {
@@ -19,8 +19,8 @@ import {
getDictItemData,
addDictItem,
updateDictItem,
deleteDictItems,
} from '@/api/system/dict';
deleteDictItems
} from '@/api/dict';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
const props = defineProps({
@@ -28,19 +28,19 @@ const props = defineProps({
type: String,
default: () => {
return '';
},
}
},
typeName: {
type: String,
default: () => {
return '';
},
},
}
}
});
watch(
() => props.typeCode,
(value) => {
value => {
state.queryParams.typeCode = value;
state.formData.typeCode = value;
handleQuery();
@@ -66,14 +66,14 @@ const state = reactive({
typeCode: props.typeCode,
typeName: props.typeName,
status: 1,
sort: 1,
sort: 1
} as DictItemFormData,
rules: {
name: [{ required: true, message: '请输入字典项名称', trigger: 'blur' }],
value: [{ required: true, message: '请输入字典项值', trigger: 'blur' }],
value: [{ required: true, message: '请输入字典项值', trigger: 'blur' }]
},
localDictCode: props.typeCode,
localDictName: props.typeName,
localDictName: props.typeName
});
const {
@@ -84,7 +84,7 @@ const {
dialog,
formData,
rules,
total,
total
} = toRefs(state);
function handleQuery() {
@@ -121,14 +121,14 @@ function handleAdd() {
}
state.dialog = {
title: '添加字典数据项',
visible: true,
visible: true
};
}
function handleUpdate(row: any) {
state.dialog = {
title: '修改字典数据项',
visible: true,
visible: true
};
const id = row.id || state.ids;
getDictItemData(id).then(({ data }) => {
@@ -167,7 +167,7 @@ function handleDelete(row: any) {
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
type: 'warning'
})
.then(() => {
deleteDictItems(ids).then(() => {

View File

@@ -1,6 +1,6 @@
<script lang="ts">
export default {
name: 'dictType',
name: 'dictType'
};
</script>
@@ -135,17 +135,13 @@ import {
getDictFormData,
addDictType,
updateDictType,
deleteDictTypes,
} from '@/api/system/dict';
deleteDictTypes
} from '@/api/dict';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { Dialog } from '@/types/common';
import {
Dict,
DictFormTypeData,
DictQueryParam,
} from '@/types/api/system/dict';
import { Dict, DictFormTypeData, DictQueryParam } from '@/types/api/dict';
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
@@ -162,18 +158,18 @@ const state = reactive({
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
pageSize: 10
} as DictQueryParam,
dictList: [] as Dict[],
total: 0,
dialog: { visible: false } as Dialog,
formData: {
status: 1,
status: 1
} as DictFormTypeData,
rules: {
name: [{ required: true, message: '请输入字典名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入字典编码', trigger: 'blur' }],
},
code: [{ required: true, message: '请输入字典编码', trigger: 'blur' }]
}
});
const { total, dialog, loading, dictList, formData, rules, queryParams } =
@@ -203,14 +199,14 @@ function handleSelectionChange(selection: any) {
function handleAdd() {
state.dialog = {
title: '添加字典',
visible: true,
visible: true
};
}
function handleUpdate(row: any) {
state.dialog = {
title: '修改字典',
visible: true,
visible: true
};
const id = row.id || state.ids;
getDictFormData(id).then(({ data }) => {
@@ -249,7 +245,7 @@ function handleDelete(row: any) {
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
type: 'warning'
})
.then(() => {
deleteDictTypes(ids).then(() => {

View File

@@ -233,11 +233,7 @@ import { ElForm, ElMessage, ElMessageBox, ElPopover } from 'element-plus';
import { Dialog, Option } from '@/types/common';
import {
MenuFormData,
MenuItem,
MenuQueryParam
} from '@/types/api/system/menu';
import { MenuFormData, MenuItem, MenuQueryParam } from '@/types/api/menu';
// API 依赖
import {
listMenus,
@@ -246,7 +242,7 @@ import {
addMenu,
deleteMenus,
updateMenu
} from '@/api/system/menu';
} from '@/api/menu';
import SvgIcon from '@/components/SvgIcon/index.vue';
import IconSelect from '@/components/IconSelect/index.vue';

View File

@@ -5,7 +5,7 @@ import {
reactive,
ref,
getCurrentInstance,
toRefs,
toRefs
} from 'vue';
import {
@@ -13,19 +13,15 @@ import {
getPermFormDetail,
addPerm,
updatePerm,
deletePerms,
} from '@/api/system/perm';
deletePerms
} from '@/api/perm';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { Dialog, Option } from '@/types/common';
import {
PermFormData,
PermItem,
PermQueryParam,
} from '@/types/api/system/perm';
import { MenuItem } from '@/types/api/system/menu';
import { PermFormData, PermItem, PermQueryParam } from '@/types/api/perm';
import { MenuItem } from '@/types/api/menu';
const { proxy }: any = getCurrentInstance();
@@ -37,19 +33,19 @@ const props = defineProps({
type: Object,
default: () => {
return {} as MenuItem;
},
},
}
}
});
watch(
() => props.menu,
(value) => {
value => {
queryParams.value.menuId = value.id;
console.log('menu', value);
handleQuery();
},
{
deep: true,
deep: true
}
);
@@ -63,26 +59,26 @@ const state = reactive({
multiple: true,
queryParams: {
pageNum: 1,
pageSize: 10,
pageSize: 10
} as PermQueryParam,
permList: [] as PermItem[],
total: 0,
dialog: {
visible: false,
visible: false
} as Dialog,
formData: {} as PermFormData,
rules: {
name: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
perm: [{ required: true, message: '请输入权限标识', trigger: 'blur' }],
method: [{ required: true, message: '请选择请求方式', trigger: 'blur' }],
method: [{ required: true, message: '请选择请求方式', trigger: 'blur' }]
},
microServiceOptions: [] as Option[],
requestMethodOptions: [] as Option[],
urlPerm: {
requestMethod: '',
serviceName: '',
requestPath: '',
},
requestPath: ''
}
});
const {
@@ -96,7 +92,7 @@ const {
microServiceOptions,
requestMethodOptions,
urlPerm,
queryParams,
queryParams
} = toRefs(state);
function handleQuery() {
@@ -143,7 +139,7 @@ function handleAdd() {
loadDictOptions();
state.dialog = {
title: '添加权限',
visible: true,
visible: true
};
}
@@ -151,14 +147,14 @@ function handleUpdate(row: any) {
loadDictOptions();
state.dialog = {
title: '修改权限',
visible: true,
visible: true
};
const id = row.id || state.ids;
getPermFormDetail(id).then((response) => {
getPermFormDetail(id).then(response => {
const { data } = response;
state.formData = data;
if (data && data.urlPerm) {
// GET:/youlai-admin/api/v1/users
// GET:/youlai-system/api/v1/users
const urlPermArr = data.urlPerm.split(':');
state.urlPerm.requestMethod = urlPermArr[0];
state.urlPerm.serviceName = urlPermArr[1].substring(
@@ -225,7 +221,7 @@ function resetForm() {
state.urlPerm = {
requestMethod: '',
serviceName: '',
requestPath: '',
requestPath: ''
};
}
@@ -239,7 +235,7 @@ function handleDelete(row: any) {
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
type: 'warning'
})
.then(() => {
deletePerms(ids).then(() => {

View File

@@ -36,9 +36,9 @@ import PermTable from './components/Perm.vue';
import { reactive, toRefs } from 'vue';
import { WarningFilled } from '@element-plus/icons-vue';
import { MenuItem } from '@/types/api/system/menu';
import { MenuItem } from '@/types/api/menu';
const state = reactive({
menu: {} as MenuItem,
menu: {} as MenuItem
});
const { menu } = toRefs(state);

View File

@@ -14,17 +14,13 @@ import {
deleteRoles,
getRoleResources,
updateRoleResource
} from '@/api/system/role';
import { listResources } from '@/api/system/menu';
} from '@/api/role';
import { listResources } from '@/api/menu';
import { ElForm, ElMessage, ElMessageBox, ElTree } from 'element-plus';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import {
RoleFormData,
RoleItem,
RoleQueryParam
} from '@/types/api/system/role';
import { Resource } from '@/types/api/system/menu';
import { RoleFormData, RoleItem, RoleQueryParam } from '@/types/api/role';
import { Resource } from '@/types/api/menu';
import SvgIcon from '@/components/SvgIcon/index.vue';
const emit = defineEmits(['roleClick']);

View File

@@ -27,9 +27,9 @@ import {
downloadTemplate,
exportUser,
importUser
} from '@/api/system/user';
import { listDeptOptions } from '@/api/system/dept';
import { listRoleOptions } from '@/api/system/role';
} from '@/api/user';
import { listDeptOptions } from '@/api/dept';
import { listRoleOptions } from '@/api/role';
// 组件依赖
import {
@@ -55,7 +55,7 @@ import {
UserQueryParam,
UserFormData,
UserImportFormData
} from '@/types/api/system/user';
} from '@/types/api/user';
import { Option, Dialog } from '@/types/common';

View File

@@ -1,173 +0,0 @@
<script lang="ts">
export default {
name: 'member',
};
</script>
<script setup lang="ts">
import { reactive, onMounted, toRefs } from 'vue';
import { ElTable } from 'element-plus';
import { Search, Refresh } from '@element-plus/icons-vue';
import { listMemebersPage } from '@/api/ums/member';
import { MemberQueryParam, MemberItem } from '@/types/api/ums/member';
const state = reactive({
// 遮罩层
loading: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
} as MemberQueryParam,
memberList: [] as MemberItem[],
});
const { loading, queryParams, memberList, total } = toRefs(state);
function handleQuery() {
state.loading = true;
listMemebersPage(state.queryParams).then(({ data }) => {
state.memberList = data.list;
state.total = data.total;
state.loading = false;
});
}
function resetQuery() {
state.queryParams = {
pageNum: 1,
pageSize: 10,
nickName: '',
};
handleQuery();
}
function handleSelectionChange(selection: any) {
state.ids = selection.map((item: { id: any }) => item.id);
state.single = selection.length != 1;
state.multiple = !selection.length;
}
onMounted(() => {
handleQuery();
});
</script>
<template>
<div class="app-container">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item>
<el-input
v-model="queryParams.nickName"
placeholder="会员昵称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery"
>搜索</el-button
>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="memberList"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" align="center" />
<el-table-column type="expand" width="120" label="会员地址">
<template #default="scope">
<el-table :data="scope.row.addressList" size="small" border>
<el-table-column
type="index"
label="序号"
width="100"
align="center"
/>
<el-table-column align="center" label="收货人" prop="name" />
<el-table-column align="center" label="联系方式" prop="mobile" />
<el-table-column align="center" label="收货地址">
<template #default="scope">
{{
scope.row.province +
scope.row.city +
scope.row.area +
scope.row.address
}}
</template>
</el-table-column>
<el-table-column align="center" label="邮编" prop="zipCode" />
<el-table-column align="center" label="是否默认">
<template #default="scope">
<el-tag v-if="scope.row.defaulted == 1" type="success"
></el-tag
>
<el-tag v-if="scope.row.defaulted == 0" type="info"></el-tag>
</template>
</el-table-column>
</el-table>
</template>
</el-table-column>
<el-table-column type="index" label="序号" width="100" align="center" />
<el-table-column prop="nickName" label="昵称" />
<el-table-column label="性别" width="80">
<template #default="scope">
<span v-if="scope.row.gender === 0">未知</span>
<span v-if="scope.row.gender === 1"></span>
<span v-if="scope.row.gender === 2"></span>
</template>
</el-table-column>
<el-table-column label="头像" width="100">
<template #default="scope">
<el-popover placement="right" :width="400" trigger="hover">
<img :src="scope.row.avatarUrl" width="400" height="400" />
<template #reference>
<img
:src="scope.row.avatarUrl"
style="max-height: 60px; max-width: 60px"
/>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="mobile" label="手机号码" />
<el-table-column prop="birthday" label="出生日期" />
<el-table-column prop="status" width="80" label="状态">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">正常</el-tag>
<el-tag v-else type="info">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="注册时间" />
<el-table-column label="账户余额">
<template #default="scope">
{{ scope.row.balance / 100 }}
</template>
</el-table-column>
</el-table>
<!-- 分页工具条 -->
<pagination
v-if="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
</div>
</template>
<style scoped></style>