(no commit message provided)
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -0,0 +1,123 @@
|
|||||||
|
from flask import request, jsonify, current_app
|
||||||
|
from . import inbound_bp
|
||||||
|
from app.schemas.stock_schema import stock_service_schema
|
||||||
|
from app.services.inbound.service_service import ServiceService
|
||||||
|
from app.utils.decorators import token_required, role_required
|
||||||
|
|
||||||
|
|
||||||
|
@inbound_bp.route('/service', methods=['GET'])
|
||||||
|
@token_required
|
||||||
|
def get_service_list():
|
||||||
|
"""获取服务权益列表"""
|
||||||
|
page = request.args.get('page', 1, type=int)
|
||||||
|
per_page = request.args.get('per_page', 20, type=int)
|
||||||
|
keyword = request.args.get('keyword', None)
|
||||||
|
start_date = request.args.get('start_date', None)
|
||||||
|
end_date = request.args.get('end_date', None)
|
||||||
|
provider_name = request.args.get('provider_name', None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = ServiceService.get_service_list(
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
keyword=keyword,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
provider_name=provider_name
|
||||||
|
)
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'msg': 'success',
|
||||||
|
'data': result
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'获取服务列表失败: {str(e)}')
|
||||||
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@inbound_bp.route('/service', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
@role_required('admin,manager')
|
||||||
|
def create_service():
|
||||||
|
"""创建服务权益"""
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
||||||
|
errors = stock_service_schema.validate(data)
|
||||||
|
if errors:
|
||||||
|
return jsonify({'code': 400, 'msg': '数据校验失败', 'errors': errors}), 400
|
||||||
|
try:
|
||||||
|
service = ServiceService.create_service(data)
|
||||||
|
return jsonify({
|
||||||
|
'code': 201,
|
||||||
|
'msg': '创建成功',
|
||||||
|
'data': stock_service_schema.dump(service)
|
||||||
|
}), 201
|
||||||
|
except ValueError as e:
|
||||||
|
return jsonify({'code': 400, 'msg': str(e)}), 400
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'创建服务权益失败: {str(e)}')
|
||||||
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@inbound_bp.route('/service/<int:service_id>', methods=['GET'])
|
||||||
|
@token_required
|
||||||
|
def get_service(service_id):
|
||||||
|
"""获取单个服务权益详情"""
|
||||||
|
try:
|
||||||
|
service = ServiceService.get_service(service_id)
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'msg': 'success',
|
||||||
|
'data': stock_service_schema.dump(service)
|
||||||
|
})
|
||||||
|
except ValueError as e:
|
||||||
|
return jsonify({'code': 404, 'msg': str(e)}), 404
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'获取服务权益详情失败: {str(e)}')
|
||||||
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@inbound_bp.route('/service/<int:service_id>', methods=['PUT'])
|
||||||
|
@token_required
|
||||||
|
@role_required('admin,manager')
|
||||||
|
def update_service(service_id):
|
||||||
|
"""更新服务权益"""
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
||||||
|
# 部分字段不允许更新,可在此过滤
|
||||||
|
allowed_fields = {'sale_price', 'provider_name', 'description'}
|
||||||
|
filtered_data = {k: v for k, v in data.items() if k in allowed_fields}
|
||||||
|
if not filtered_data:
|
||||||
|
return jsonify({'code': 400, 'msg': '无有效更新字段'}), 400
|
||||||
|
try:
|
||||||
|
service = ServiceService.update_service(service_id, filtered_data)
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'msg': '更新成功',
|
||||||
|
'data': stock_service_schema.dump(service)
|
||||||
|
})
|
||||||
|
except ValueError as e:
|
||||||
|
return jsonify({'code': 404, 'msg': str(e)}), 404
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'更新服务权益失败: {str(e)}')
|
||||||
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@inbound_bp.route('/service/<int:service_id>', methods=['DELETE'])
|
||||||
|
@token_required
|
||||||
|
@role_required('admin,manager')
|
||||||
|
def delete_service(service_id):
|
||||||
|
"""删除服务权益"""
|
||||||
|
try:
|
||||||
|
ServiceService.delete_service(service_id)
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'msg': '删除成功'
|
||||||
|
})
|
||||||
|
except ValueError as e:
|
||||||
|
return jsonify({'code': 404, 'msg': str(e)}), 404
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'删除服务权益失败: {str(e)}')
|
||||||
|
return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500
|
||||||
|
|||||||
@ -0,0 +1,46 @@
|
|||||||
|
from app import db
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class StockService(db.Model):
|
||||||
|
"""
|
||||||
|
服务权益库存表
|
||||||
|
对应数据库表: stock_service
|
||||||
|
"""
|
||||||
|
__tablename__ = 'stock_service'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
# 关联基础物料信息
|
||||||
|
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
|
||||||
|
# 系统生成的SKU,格式 SRV-YYYYMMDD-XXXX
|
||||||
|
sku = db.Column(db.String(64), unique=True, nullable=False)
|
||||||
|
# 售价
|
||||||
|
sale_price = db.Column(db.Numeric(10, 2), nullable=False)
|
||||||
|
# 服务商名称
|
||||||
|
provider_name = db.Column(db.String(255), nullable=False, default='')
|
||||||
|
# 服务详情/简介
|
||||||
|
description = db.Column(db.Text, default='')
|
||||||
|
# 创建时间与更新时间
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
|
||||||
|
# 软删除标志
|
||||||
|
is_deleted = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
|
|
||||||
|
# 关系(可选)
|
||||||
|
material_base = db.relationship('MaterialBase', backref='service_stocks', lazy='joined')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""转为字典,用于 API 响应"""
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'base_id': self.base_id,
|
||||||
|
'sku': self.sku,
|
||||||
|
'sale_price': float(self.sale_price) if self.sale_price is not None else 0,
|
||||||
|
'provider_name': self.provider_name,
|
||||||
|
'description': self.description,
|
||||||
|
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None,
|
||||||
|
'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None,
|
||||||
|
'material_name': self.material_base.name if self.material_base else None,
|
||||||
|
'spec_model': self.material_base.spec_model if self.material_base else None,
|
||||||
|
'unit': self.material_base.unit if self.material_base else None,
|
||||||
|
}
|
||||||
|
|||||||
@ -38,5 +38,28 @@ class StockBuySchema(Schema):
|
|||||||
# 这里暂时不强制抛出错误,交给 Service 层处理 "SKU不存在且无名字" 的情况
|
# 这里暂时不强制抛出错误,交给 Service 层处理 "SKU不存在且无名字" 的情况
|
||||||
|
|
||||||
|
|
||||||
|
class StockServiceSchema(Schema):
|
||||||
|
# 只用于输出的字段
|
||||||
|
id = fields.Int(dump_only=True)
|
||||||
|
sku = fields.Str(dump_only=True)
|
||||||
|
created_at = fields.DateTime(format='%Y-%m-%d %H:%M:%S', dump_only=True)
|
||||||
|
updated_at = fields.DateTime(format='%Y-%m-%d %H:%M:%S', dump_only=True)
|
||||||
|
material_name = fields.Str(dump_only=True)
|
||||||
|
spec_model = fields.Str(dump_only=True)
|
||||||
|
unit = fields.Str(dump_only=True)
|
||||||
|
|
||||||
|
# 输入字段
|
||||||
|
base_id = fields.Int(required=True, error_messages={"required": "必须选择基础物料"})
|
||||||
|
sale_price = fields.Float(required=True, validate=validate.Range(min=0, error="售价不能为负数"))
|
||||||
|
provider_name = fields.Str(required=True, error_messages={"required": "服务商名称不能为空"})
|
||||||
|
description = fields.Str(missing='')
|
||||||
|
|
||||||
|
@validates_schema
|
||||||
|
def validate_base_id(self, data, **kwargs):
|
||||||
|
# 可以在这里添加对 base_id 是否存在的检查,但更建议在 Service 层进行
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# 实例化 Schema
|
# 实例化 Schema
|
||||||
stock_buy_schema = StockBuySchema()
|
stock_buy_schema = StockBuySchema()
|
||||||
|
stock_service_schema = StockServiceSchema()
|
||||||
|
|||||||
@ -0,0 +1,126 @@
|
|||||||
|
from app import db
|
||||||
|
from app.models.inbound.service import StockService
|
||||||
|
from app.models.material_base import MaterialBase
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceService:
|
||||||
|
"""服务权益库存业务逻辑"""
|
||||||
|
|
||||||
|
SKU_PREFIX = 'SRV'
|
||||||
|
SKU_DATE_FORMAT = '%Y%m%d'
|
||||||
|
SKU_SUFFIX_LEN = 4
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _generate_sku(cls):
|
||||||
|
"""生成唯一SKU,格式 SRV-YYYYMMDD-XXXX"""
|
||||||
|
today_str = datetime.now().strftime(cls.SKU_DATE_FORMAT)
|
||||||
|
prefix = f'{cls.SKU_PREFIX}-{today_str}-'
|
||||||
|
# 查找今天已有的最大后缀
|
||||||
|
max_sku = db.session.query(db.func.max(StockService.sku)).filter(
|
||||||
|
StockService.sku.like(f'{prefix}%')
|
||||||
|
).scalar()
|
||||||
|
if not max_sku:
|
||||||
|
suffix_num = 1
|
||||||
|
else:
|
||||||
|
# 提取后缀数字
|
||||||
|
suffix_part = max_sku.replace(prefix, '')
|
||||||
|
match = re.match(r'^(\d+)', suffix_part)
|
||||||
|
suffix_num = int(match.group(1)) if match else 0
|
||||||
|
suffix_num += 1
|
||||||
|
# 格式化为4位数字,左侧补零
|
||||||
|
suffix = str(suffix_num).zfill(cls.SKU_SUFFIX_LEN)
|
||||||
|
return f'{prefix}{suffix}'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_service(cls, data):
|
||||||
|
"""创建服务权益记录"""
|
||||||
|
# 检查基础物料是否存在
|
||||||
|
base = MaterialBase.query.get(data.get('base_id'))
|
||||||
|
if not base:
|
||||||
|
raise ValueError('基础物料不存在')
|
||||||
|
# 生成SKU
|
||||||
|
sku = cls._generate_sku()
|
||||||
|
service = StockService(
|
||||||
|
base_id=data['base_id'],
|
||||||
|
sku=sku,
|
||||||
|
sale_price=data['sale_price'],
|
||||||
|
provider_name=data['provider_name'],
|
||||||
|
description=data.get('description', '')
|
||||||
|
)
|
||||||
|
db.session.add(service)
|
||||||
|
db.session.commit()
|
||||||
|
return service
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_service(cls, service_id):
|
||||||
|
"""获取单个服务权益"""
|
||||||
|
service = StockService.query.filter_by(id=service_id, is_deleted=False).first()
|
||||||
|
if not service:
|
||||||
|
raise ValueError('服务权益记录不存在')
|
||||||
|
return service
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_service(cls, service_id, data):
|
||||||
|
"""更新服务权益记录"""
|
||||||
|
service = cls.get_service(service_id)
|
||||||
|
# 不允许修改 base_id 和 sku(业务上不允许变更基础物料)
|
||||||
|
if 'sale_price' in data:
|
||||||
|
service.sale_price = data['sale_price']
|
||||||
|
if 'provider_name' in data:
|
||||||
|
service.provider_name = data['provider_name']
|
||||||
|
if 'description' in data:
|
||||||
|
service.description = data.get('description', '')
|
||||||
|
service.updated_at = datetime.now()
|
||||||
|
db.session.commit()
|
||||||
|
return service
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_service(cls, service_id):
|
||||||
|
"""软删除服务权益"""
|
||||||
|
service = cls.get_service(service_id)
|
||||||
|
service.is_deleted = True
|
||||||
|
service.updated_at = datetime.now()
|
||||||
|
db.session.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_service_list(cls, page=1, per_page=20, keyword=None,
|
||||||
|
start_date=None, end_date=None, provider_name=None):
|
||||||
|
"""分页查询服务权益列表"""
|
||||||
|
query = StockService.query.filter_by(is_deleted=False)
|
||||||
|
# 关键词搜索:可搜索 SKU 或 关联物料名称
|
||||||
|
if keyword:
|
||||||
|
# 子查询查找物料名称匹配的 base_id
|
||||||
|
subquery = MaterialBase.query.filter(
|
||||||
|
MaterialBase.name.ilike(f'%{keyword}%')
|
||||||
|
).subquery()
|
||||||
|
query = query.filter(
|
||||||
|
db.or_(
|
||||||
|
StockService.sku.ilike(f'%{keyword}%'),
|
||||||
|
StockService.base_id.in_([row.id for row in db.session.query(subquery.c.id)])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if start_date:
|
||||||
|
start = datetime.strptime(start_date, '%Y-%m-%d')
|
||||||
|
query = query.filter(StockService.created_at >= start)
|
||||||
|
if end_date:
|
||||||
|
end = datetime.strptime(end_date, '%Y-%m-%d')
|
||||||
|
# 包含当天
|
||||||
|
end = end + timedelta(days=1) - timedelta(seconds=1)
|
||||||
|
query = query.filter(StockService.created_at <= end)
|
||||||
|
if provider_name:
|
||||||
|
query = query.filter(StockService.provider_name.ilike(f'%{provider_name}%'))
|
||||||
|
# 总数
|
||||||
|
total = query.count()
|
||||||
|
# 分页
|
||||||
|
items = query.order_by(StockService.created_at.desc())\
|
||||||
|
.offset((page - 1) * per_page)\
|
||||||
|
.limit(per_page).all()
|
||||||
|
return {
|
||||||
|
'items': [item.to_dict() for item in items],
|
||||||
|
'total': total,
|
||||||
|
'page': page,
|
||||||
|
'per_page': per_page
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,91 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export interface ServiceItem {
|
||||||
|
id: number
|
||||||
|
base_id: number
|
||||||
|
sku: string
|
||||||
|
sale_price: number
|
||||||
|
provider_name: string
|
||||||
|
description: string
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
material_name?: string
|
||||||
|
spec_model?: string
|
||||||
|
unit?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceListResponse {
|
||||||
|
code: number
|
||||||
|
msg: string
|
||||||
|
data: {
|
||||||
|
items: ServiceItem[]
|
||||||
|
total: number
|
||||||
|
page: number
|
||||||
|
per_page: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceQueryParams {
|
||||||
|
page?: number
|
||||||
|
per_page?: number
|
||||||
|
keyword?: string
|
||||||
|
start_date?: string
|
||||||
|
end_date?: string
|
||||||
|
provider_name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceCreateRequest {
|
||||||
|
base_id: number
|
||||||
|
sale_price: number
|
||||||
|
provider_name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceUpdateRequest {
|
||||||
|
sale_price?: number
|
||||||
|
provider_name?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取服务权益列表
|
||||||
|
export function getServiceList(params: ServiceQueryParams) {
|
||||||
|
return request<ServiceListResponse>({
|
||||||
|
url: '/v1/inbound/service',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建服务权益
|
||||||
|
export function createService(data: ServiceCreateRequest) {
|
||||||
|
return request({
|
||||||
|
url: '/v1/inbound/service',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取服务权益详情
|
||||||
|
export function getServiceDetail(id: number) {
|
||||||
|
return request<ServiceListResponse>({
|
||||||
|
url: `/v1/inbound/service/${id}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新服务权益
|
||||||
|
export function updateService(id: number, data: ServiceUpdateRequest) {
|
||||||
|
return request({
|
||||||
|
url: `/v1/inbound/service/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除服务权益
|
||||||
|
export function deleteService(id: number) {
|
||||||
|
return request({
|
||||||
|
url: `/v1/inbound/service/${id}`,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -1 +1,307 @@
|
|||||||
<template><div style="padding:20px;"><h2>服务权益管理</h2></div></template>
|
<template>
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<h2 style="margin-bottom: 20px;">服务权益管理</h2>
|
||||||
|
|
||||||
|
<div class="header-toolbar">
|
||||||
|
<el-form :inline="true" @submit.prevent>
|
||||||
|
<el-form-item label="关键词">
|
||||||
|
<el-input
|
||||||
|
v-model="searchForm.keyword"
|
||||||
|
placeholder="SKU/物料名称"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
style="width: 200px;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="服务商">
|
||||||
|
<el-input
|
||||||
|
v-model="searchForm.provider_name"
|
||||||
|
placeholder="服务商名称"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
style="width: 200px;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="开始日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="searchForm.start_date"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结束日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="searchForm.end_date"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
<el-button @click="resetSearch">重置</el-button>
|
||||||
|
<el-button type="success" @click="handleAdd">新增服务</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table :data="tableData" border stripe style="width: 100%;" v-loading="loading">
|
||||||
|
<el-table-column prop="sku" label="SKU" width="200" />
|
||||||
|
<el-table-column prop="material_name" label="物料名称" />
|
||||||
|
<el-table-column prop="provider_name" label="服务商" width="150" />
|
||||||
|
<el-table-column prop="sale_price" label="售价" width="120">
|
||||||
|
<template #default="{row}">¥{{ row.sale_price.toFixed(2) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="description" label="简介" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="created_at" label="创建时间" width="160" />
|
||||||
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
|
<template #default="{row}">
|
||||||
|
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px; text-align: center;">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="page"
|
||||||
|
v-model:page-size="perPage"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
width="500px"
|
||||||
|
@close="resetDialog"
|
||||||
|
>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-form-item label="基础物料ID" prop="base_id">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.base_id"
|
||||||
|
placeholder="请输入基础物料ID"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
<div style="font-size:12px;color:#999;">需要先创建基础物料</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="售价" prop="sale_price">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.sale_price"
|
||||||
|
placeholder="请输入售价"
|
||||||
|
:controls="false"
|
||||||
|
:precision="2"
|
||||||
|
:min="0"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="服务商" prop="provider_name">
|
||||||
|
<el-input
|
||||||
|
v-model="form.provider_name"
|
||||||
|
placeholder="请输入服务商名称"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="简介" prop="description">
|
||||||
|
<el-input
|
||||||
|
v-model="form.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入服务简介"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleDialogConfirm">确认</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import {
|
||||||
|
getServiceList,
|
||||||
|
createService,
|
||||||
|
updateService,
|
||||||
|
deleteService,
|
||||||
|
type ServiceItem,
|
||||||
|
type ServiceQueryParams,
|
||||||
|
type ServiceCreateRequest
|
||||||
|
} from '@/api/inbound/service'
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref<ServiceItem[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const page = ref(1)
|
||||||
|
const perPage = ref(20)
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
const searchForm = reactive({
|
||||||
|
keyword: '',
|
||||||
|
provider_name: '',
|
||||||
|
start_date: '',
|
||||||
|
end_date: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载列表
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params: ServiceQueryParams = {
|
||||||
|
page: page.value,
|
||||||
|
per_page: perPage.value,
|
||||||
|
keyword: searchForm.keyword || undefined,
|
||||||
|
provider_name: searchForm.provider_name || undefined,
|
||||||
|
start_date: searchForm.start_date || undefined,
|
||||||
|
end_date: searchForm.end_date || undefined
|
||||||
|
}
|
||||||
|
const res = await getServiceList(params)
|
||||||
|
if (res.code === 200) {
|
||||||
|
tableData.value = res.data.items
|
||||||
|
total.value = res.data.total
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '加载失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('网络错误')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
page.value = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
const resetSearch = () => {
|
||||||
|
Object.assign(searchForm, {
|
||||||
|
keyword: '',
|
||||||
|
provider_name: '',
|
||||||
|
start_date: '',
|
||||||
|
end_date: ''
|
||||||
|
})
|
||||||
|
page.value = 1
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
const handleSizeChange = (val: number) => {
|
||||||
|
perPage.value = val
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
const handlePageChange = (val: number) => {
|
||||||
|
page.value = val
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗相关
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const form = reactive({
|
||||||
|
id: 0,
|
||||||
|
base_id: 0,
|
||||||
|
sale_price: 0,
|
||||||
|
provider_name: '',
|
||||||
|
description: ''
|
||||||
|
})
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
base_id: [
|
||||||
|
{ required: true, message: '请选择基础物料', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 1, message: '物料ID必须大于0', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
sale_price: [
|
||||||
|
{ required: true, message: '请输入售价', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 0, message: '售价不能为负数', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
provider_name: [
|
||||||
|
{ required: true, message: '请输入服务商名称', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
dialogTitle.value = '新增服务'
|
||||||
|
Object.assign(form, {
|
||||||
|
id: 0,
|
||||||
|
base_id: 0,
|
||||||
|
sale_price: 0,
|
||||||
|
provider_name: '',
|
||||||
|
description: ''
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
const handleEdit = (row: ServiceItem) => {
|
||||||
|
dialogTitle.value = '编辑服务'
|
||||||
|
Object.assign(form, {
|
||||||
|
id: row.id,
|
||||||
|
base_id: row.base_id,
|
||||||
|
sale_price: row.sale_price,
|
||||||
|
provider_name: row.provider_name,
|
||||||
|
description: row.description
|
||||||
|
})
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
const handleDialogConfirm = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
try {
|
||||||
|
const reqData: ServiceCreateRequest = {
|
||||||
|
base_id: form.base_id,
|
||||||
|
sale_price: form.sale_price,
|
||||||
|
provider_name: form.provider_name,
|
||||||
|
description: form.description
|
||||||
|
}
|
||||||
|
if (form.id === 0) {
|
||||||
|
await createService(reqData)
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
} else {
|
||||||
|
await updateService(form.id, reqData)
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
loadData()
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.response?.data?.msg || '操作失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const resetDialog = () => {
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = (row: ServiceItem) => {
|
||||||
|
ElMessageBox.confirm(`确定删除服务权益 "${row.sku}" 吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
await deleteService(row.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.response?.data?.msg || '删除失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user