Merge pull request 'main' (#52) from main into prod

Reviewed-on: #52
This commit is contained in:
charles 2025-12-08 17:20:04 +08:00
commit a2294d7696
43 changed files with 4789 additions and 219 deletions

View File

@ -802,7 +802,15 @@ async def baidu_confirm_auto_renew_order(ns={}):
renew_status = await get_baidu_orderlist({'order_id': order_id, 'userid': user_id}) renew_status = await get_baidu_orderlist({'order_id': order_id, 'userid': user_id})
if renew_status.get('status'): if renew_status.get('status'):
renew_status_count += 1 renew_status_count += 1
else:
ns_err_log = {
'id': uuid(),
'log_level': 'ERROR',
'log_content': '续费订单 %s 失败 %s' % (order_id, str(renew_status)),
'user_id': user_id,
'request_url': '/baiduc/baidu_confirm_auto_renew_order.dspy'
}
await sor.C('warn_error_log', ns_err_log)
# 并触发update函数 # 并触发update函数
update_msg = await update_baidu_order_list({'orgid': orgid}) update_msg = await update_baidu_order_list({'orgid': orgid})

View File

@ -745,11 +745,32 @@ async def update_baidu_order_list(ns={}):
} }
} }
async def baidu_orderdetail_by_uuid(ns={}):
order_id = ns.get('order_id')
baidu_id = ns.get('baidu_id')
method = 'POST'
nss = {'uuids': [order_id], 'queryAccountId': baidu_id}
ns_format = '&'.join(['%s=%s' % (k, v) for k, v in ns.items()])
url = 'https://billing.baidubce.com/v1/order/getByUuid?%s' % ns_format
method = 'POST'
header = {
"Host": "billing.baidubce.com",
"ContentType": "application/json;charset=UTF-8"
}
header = await get_auth_header(method=method, url=url, header=header)
async with aiohttp_client.request(
method=method,
url=url,
headers=header,
json=nss) as res:
data_ = await res.json()
return {
'status': True,
'msg': 'get baidu order detail success',
'data': data_
}
async def baidu_confirm_refund_order(ns={}): async def baidu_confirm_refund_order(ns={}):
# ns = {
# 'order_id': ["2996f0baf34c4a0a98e1da0b4e290a35"],
# 'userid': 'y_xQK0G62dtZT5EneMQFT'
# }
import asyncio import asyncio
# 把 NEED_CONFIRM的订单同步到本地库用于后续状态更新 # 把 NEED_CONFIRM的订单同步到本地库用于后续状态更新
await update_baidu_order_list({'userid': ns.get('userid')}) await update_baidu_order_list({'userid': ns.get('userid')})
@ -764,18 +785,21 @@ async def baidu_confirm_refund_order(ns={}):
'msg': '用户 %s 未绑定百度智能云账号' % ns.get('userid') 'msg': '用户 %s 未绑定百度智能云账号' % ns.get('userid')
} }
orders = json.loads(ns.get('order_id')) if isinstance(ns.get('order_id'), str) else ns.get('order_id') refund_orders_base_list = json.loads(ns.get('order_id')) if isinstance(ns.get('order_id'), str) else ns.get('order_id')
for order_id in refund_orders_base_list:
with open('baidu_refund_order_id.txt', 'a+') as f:
f.write('%s 当前退订订单ID: %s ' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), order_id))
for order_id in orders:
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
refund_status_li = await sor.R('baidu_orders', {'orderid': order_id}) refund_status_li = await sor.R('baidu_orders', {'orderid': order_id})
refundstatus = refund_status_li[0]['refundstatus'] refundstatus = refund_status_li[0]['refundstatus']
refund_id = refund_status_li[0]['id'] refund_id = refund_status_li[0]['id']
ns_record_li = await sor.R('user_action', {'orderid': order_id}) # ns_record_li = await sor.R('user_action', {'orderid': order_id})
ns_record_id = ns_record_li[0]['id'] if ns_record_li else None # ns_record_id = ns_record_li[0]['id'] if ns_record_li else None
ns_record = {'id': ns_record_id} if ns_record_id else None # ns_record = {'id': ns_record_id} if ns_record_id else None
if not refundstatus: if not refundstatus:
# data_ = {} # data_ = {}
@ -787,6 +811,7 @@ async def baidu_confirm_refund_order(ns={}):
header = { header = {
"Host": "billing.baidubce.com" "Host": "billing.baidubce.com"
} }
await asyncio.sleep(0.5)
header = await get_auth_header(method=method, url=url, header=header) header = await get_auth_header(method=method, url=url, header=header)
async with aiohttp_client.request( async with aiohttp_client.request(
method=method, method=method,
@ -808,19 +833,8 @@ async def baidu_confirm_refund_order(ns={}):
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
await sor.U('baidu_orders', {'id': refund_id, 'refundstatus': '1'}) await sor.U('baidu_orders', {'id': refund_id, 'refundstatus': '1'})
# 增加延迟 with open('baidu_refund_order_id.txt', 'a+') as f:
await asyncio.sleep(4) f.write('远程退订成功: %s \n' % str(data_))
# 延迟2-3秒还是获取到 ready状态的订单那就重复请求一次目的是尽快刷新状态
async with aiohttp_client.request(
method=method,
url=url,
headers=header,
json=paydata) as res:
await res.json()
# 把 NEED_CONFIRM的本地库改为CREATED
await update_baidu_order_list({'userid': ns.get('userid')})
else: else:
ns_record = { ns_record = {
'orderid': order_id, 'orderid': order_id,
@ -828,43 +842,84 @@ async def baidu_confirm_refund_order(ns={}):
'userid': ns.get('userid'), 'userid': ns.get('userid'),
'reason': '产品退费失败, %s' % str(data_)[:400] 'reason': '产品退费失败, %s' % str(data_)[:400]
} }
with open('baidu_refund_order_id.txt', 'a+') as f:
f.write('远程退订失败: %s ' % str(data_))
db = DBPools()
async with db.sqlorContext('kboss') as sor:
await asyncio.sleep(3)
order_status_dict = await baidu_orderdetail_by_uuid({'order_id': order_id, 'baidu_id': ns.get('baidu_id')})
if order_status_dict.get('data') and order_status_dict['data'].get('orders') and order_status_dict['data']['orders'][0]['status'] == 'NEED_CONFIRM':
await user_action_record(ns_record) await user_action_record(ns_record)
return { # 从列表中移除该订单ID
'status': False, refund_orders_base_list.remove(order_id)
'msg': '产品退款出错!%s' % str(data_)[:400] ns_err_log = {
'id': uuid(),
'log_level': 'ERROR',
'log_content': '检测到 产品仍在使用中 暂时不退费, 订单详情 %s' % str(order_status_dict['data']),
'user_id': ns.get('userid'),
'request_url': '/baiduc//baidu_confirm_refund_order.dspy',
'request_params': json.dumps({'order_id': order_id, 'baidu_id': ns.get('baidu_id')}),
} }
await sor.C('warn_error_log', ns_err_log)
with open('baidu_refund_order_id.txt', 'a+') as f:
f.write('检测到 产品仍在使用中 暂时不退费 %s \n' % str(order_status_dict['data']))
else:
ns_record['reason'] = '远程退款失败, 产品已释放, 订单状态 %s, 正常退费' % order_status_dict['data']['orders'][0]['status']
await user_action_record(ns_record)
with open('baidu_refund_order_id.txt', 'a+') as f:
f.write('检测到 产品已释放,订单状态 %s, 继续进入退费程序 \n' % order_status_dict['data']['orders'][0]['status'])
if refundstatus == '2': if refundstatus == '2':
""" """
退款状态为2, 说明退款成功, 无需继续处理 退款状态为2, 说明退款成功, 无需继续处理
""" """
refund_orders_base_list.remove(order_id)
continue continue
# 分割
# 增加延迟
await asyncio.sleep(5)
# 把 NEED_CONFIRM的本地库改为CREATED
await update_baidu_order_list({'userid': ns.get('userid')})
for order_id in refund_orders_base_list:
# 获取created状态后再去退款 # 获取created状态后再去退款
local_refund_status = await get_baidu_orderlist({'order_id': order_id, 'userid': ns.get('userid')}) local_refund_status = await get_baidu_orderlist({'order_id': order_id, 'userid': ns.get('userid')})
print('local_refund_status', local_refund_status) print('local_refund_status', local_refund_status)
if local_refund_status.get('status'): if local_refund_status.get('status'):
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:
refund_status_li = await sor.R('baidu_orders', {'orderid': order_id})
refundstatus = refund_status_li[0]['refundstatus']
refund_id = refund_status_li[0]['id']
baidu_orders_status_update = """update baidu_orders set refundstatus='2' where id='%s';""" % refund_id baidu_orders_status_update = """update baidu_orders set refundstatus='2' where id='%s';""" % refund_id
await sor.sqlExe(baidu_orders_status_update, {}) await sor.sqlExe(baidu_orders_status_update, {})
await sor.U('user_action', {'id': ns_record.get('id'), 'ordertype': 'REFUND', 'reason': '远程退款成功, 本地客户退款成功'}) ns_record_li = await sor.R('user_action', {'orderid': order_id})
ns_record_id = ns_record_li[0]['id'] if ns_record_li else None
await sor.U('user_action', {'id': ns_record_id, 'ordertype': 'REFUND', 'reason': '远程退款成功, 本地客户退款成功'})
continue continue
# return {
# 'status': True,
# 'msg': '百度云给平台退款成功,平台给客户退款成功'
# }
else: else:
if local_refund_status.get('msg') == 'delay_order': ns_err_log = {
return { 'id': uuid(),
'status': False, 'log_level': 'ERROR',
'msg': '百度远程订单还未生成, 请十秒后重试, %s' % str(local_refund_status) 'log_content': '远程退款成功,本地退款失败 %s' % str(local_refund_status),
'user_id': ns.get('userid'),
'request_url': '/baiduc//baidu_confirm_refund_order.dspy',
'request_params': json.dumps({'order_id': order_id, 'baidu_id': ns.get('baidu_id')}),
} }
return { await sor.C('warn_error_log', ns_err_log)
'status': False,
'msg': '百度云退款成功,本机构给客户退款出错!, %s' % str(local_refund_status) ns_record = {
'orderid': order_id,
'ordertype': 'REFUND',
'userid': ns.get('userid'),
'reason': '远程退款成功,本地退款失败, %s' % str(local_refund_status)
} }
await user_action_record(ns_record)
continue
return { return {
'status': True, 'status': True,

View File

@ -0,0 +1,97 @@
AK = 'ALTAKPk92fX9cgGDax83yNL8mP'
SK = '9b16b8efd4dc463d8bbd5462db1db8a5'
async def get_auth_header(method: str, url: str, header: dict, query={}, get_res=False):
import urllib
import hmac
# 解析uri
url_parse = urllib.parse.urlparse(url)
uri = url_parse.path
url_query = url_parse.query
# 获取query
if url_query:
query = dict(urllib.parse.parse_qsl(url_query))
# 2.x-bce-date
x_bce_date = time.gmtime()
x_bce_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', x_bce_date)
# 4.认证字符串前缀
authStringPrefix = "bce-auth-v1" + "/" + AK + "/" + x_bce_date + "/" + "1800"
# windows下为urllib.parse.quoteLinux下为urllib.quote
# 5.生成CanonicalRequest
# 5.1生成CanonicalURI
CanonicalURI = urllib.parse.quote(uri)
# 如果您调用的接口的query比较复杂的话需要做额外处理
# 5.2生成CanonicalQueryString
# CanonicalQueryString = query
result = ['%s=%s' % (k, urllib.parse.quote(str(v))) for k, v in query.items() if k.lower != 'authorization']
result.sort()
CanonicalQueryString = '&'.join(result)
# 5.3生成CanonicalHeaders
result = []
signedHeaders_list = []
for key,value in header.items():
tempStr = str(urllib.parse.quote(key.lower(),safe="")) + ":" + str(urllib.parse.quote(value,safe=""))
signedHeaders_list.append(str(urllib.parse.quote(key.lower(),safe="")))
result.append(tempStr)
result.sort()
signedHeaders = ';'.join(sorted(signedHeaders_list))
CanonicalHeaders = "\n".join(result)
# 5.4拼接得到CanonicalRequest
CanonicalRequest = method + "\n" + CanonicalURI + "\n" + CanonicalQueryString +"\n" + CanonicalHeaders
# 6.生成signingKey
signingKey = hmac.new(SK.encode('utf-8'), authStringPrefix.encode('utf-8'), hashlib.sha256)
# 7.生成Signature
Signature = hmac.new((signingKey.hexdigest()).encode('utf-8'), CanonicalRequest.encode('utf-8'), hashlib.sha256)
# 8.生成Authorization并放到header里
header['Authorization'] = authStringPrefix + "/" + signedHeaders + "/" + Signature.hexdigest()
if get_res:
# 异步请求链接 返回结果
async with aiohttp_client.request(
method=method,
url=url,
headers=header,
allow_redirects=True,
json=query) as res:
return res
else:
return header
async def get_orderdetail_by_uuid(ns={}):
order_id = ns.get('order_id')
baidu_id = ns.get('baidu_id')
method = 'POST'
nss = {'uuids': [order_id], 'queryAccountId': baidu_id}
ns_format = '&'.join(['%s=%s' % (k, v) for k, v in ns.items()])
url = 'https://billing.baidubce.com/v1/order/getByUuid?%s' % ns_format
method = 'POST'
header = {
"Host": "billing.baidubce.com",
"ContentType": "application/json;charset=UTF-8"
}
header = await get_auth_header(method=method, url=url, header=header)
async with aiohttp_client.request(
method=method,
url=url,
headers=header,
json=nss) as res:
data_ = await res.json()
return {
'status': True,
'msg': 'get baidu order detail success',
'data': data_
}
ret = await get_orderdetail_by_uuid(params_kw)
return ret

View File

@ -84,6 +84,10 @@ async def affirmbz_order(ns={}):
# 处理购买逻辑 # 处理购买逻辑
else: else:
if j.get('chargemode') == 'postpay' and j.get('orderkey') == 'snapshot':
# 快照后付费不创建客户产品记录
continue
product = await sor.R('product', {'id': j['productid']}) product = await sor.R('product', {'id': j['productid']})
nss = {} nss = {}
nss['id'] = uuid() nss['id'] = uuid()

View File

@ -582,7 +582,7 @@ async def get_firstpage_product_tree(ns={}):
{ {
'id': '211', 'id': '211',
'thrTitle': None, 'thrTitle': None,
'value': [{'id': '2111', 'name': '容器云'}, 'value': [#{'id': '2111', 'name': '容器云'},
{'id': '2113', 'name': '裸金属'}, {'id': '2113', 'name': '裸金属'},
#{'id': '2114', 'name': '裸金属-910B'}, #{'id': '2114', 'name': '裸金属-910B'},
{'id': '2115', 'name': '一体机-昆仑芯'}, {'id': '2115', 'name': '一体机-昆仑芯'},

View File

@ -21,6 +21,8 @@ async def baidu_query_by_expire_time(ns={}):
'msg': 'User not synchronized' 'msg': 'User not synchronized'
} }
ns['queryAccountId'] = baiduid ns['queryAccountId'] = baiduid
ns['pageNo'] = int(ns.get('pageno', 1))
ns['pageSize'] = int(ns.get('pagesize', 100))
method = 'POST' method = 'POST'
ns_format = '&'.join(['%s=%s' % (k, v) for k, v in ns.items()]) ns_format = '&'.join(['%s=%s' % (k, v) for k, v in ns.items()])
url = 'https://billing.baidubce.com/v1/resource/queryByExpireTime?%s' % ns_format url = 'https://billing.baidubce.com/v1/resource/queryByExpireTime?%s' % ns_format
@ -35,11 +37,18 @@ async def baidu_query_by_expire_time(ns={}):
json=ns) as res: json=ns) as res:
data_ = await res.json() data_ = await res.json()
print('data_', data_) print('data_', data_)
if data_.get('pageSize'):
return { return {
'status': True, 'status': True,
'msg': 'get baidu resource expire time success', 'msg': 'get baidu resource expire time success',
'data': data_ 'data': data_
} }
else:
return {
'status': False,
'msg': 'get baidu resource expire time failed',
'data': data_
}
async def calculate_time_diff(time_str=None): async def calculate_time_diff(time_str=None):
# 将字符串时间转换为 datetime 对象 # 将字符串时间转换为 datetime 对象
@ -63,22 +72,46 @@ async def get_resource_expire_time(ns={}):
else: else:
userid = await get_user() userid = await get_user()
data = [] data = []
baidu_resource_data = await baidu_query_by_expire_time({'userid': userid}) baidu_resource_data = await baidu_query_by_expire_time(ns)
if baidu_resource_data.get('data'): if baidu_resource_data.get('data'):
for baidubaidu_resource in baidu_resource_data['data']['result']: data_result = baidu_resource_data['data']['result'] if baidu_resource_data['data'].get('result') else []
baidu_r = { # if not data_result:
'id': uuid(), # return {
'name': baidubaidu_resource['serviceTypeName'], # 'status': True,
'instanceid': baidubaidu_resource['shortId'], # 'msg': '无资源数据',
'status': baidubaidu_resource['status'], # 'data': str(baidu_resource_data['data'])
'expiretime': await time_convert(baidubaidu_resource['expireTime']), # }
'days': await calculate_time_diff(baidubaidu_resource['expireTime']) for baidubaidu_resource in data_result:
} baidubaidu_resource['id'] = uuid()
data.append(baidu_r) baidubaidu_resource['name'] = baidubaidu_resource['serviceTypeName']
baidubaidu_resource['instanceid'] = baidubaidu_resource['shortId']
baidubaidu_resource['expiretime'] = await time_convert(baidubaidu_resource['expireTime'])
baidubaidu_resource['days'] = await calculate_time_diff(baidubaidu_resource['expireTime'])
# 读取数据库表product,匹配跳转链接
db = DBPools()
async with db.sqlorContext('kboss') as sor:
find_sql = """select * from product where providerpid='baidu_%s' and del_flg='0';""" % baidubaidu_resource['serviceType']
product_li = await sor.sqlExe(find_sql, {})
spec_note_li = json.loads(product_li[0]['spec_note']) if product_li else []
for spec_note in spec_note_li:
if spec_note.get('configName') == 'listUrl':
baidubaidu_resource['list_url'] = spec_note.get('value')
break
else:
baidubaidu_resource['list_url'] = ''
data.append(baidubaidu_resource)
return { return {
'status': True, 'status': True,
'msg': 'get resouce expire time success', 'msg': 'get resouce expire time success',
'data': data 'data': data,
# 分页
'pagination': {
'total': baidu_resource_data['data'].get('totalCount'),
'page_size': baidu_resource_data['data'].get('pageSize'),
'current_page': baidu_resource_data['data'].get('pageNo')
}
} }
ret = await get_resource_expire_time(params_kw) ret = await get_resource_expire_time(params_kw)

View File

@ -0,0 +1,50 @@
async def home_page_content_add(ns={}):
"""
添加首页内容项
:param ns: 包含content_type, title, description等字段的字典
:return: 创建结果
"""
ns_dic = {
'id': uuid(), # 固定写法
'menu_product_id': ns.get('menu_product_id'),
'parent_id': ns.get('parent_id'),
'level': ns.get('level', 1),
'content_type': ns.get('content_type'), # advantage, feature, application, product
'sort_order': ns.get('sort_order', 0),
'title': ns.get('title'),
'description': ns.get('description'),
'img': ns.get('img'),
'name': ns.get('name'),
'price': ns.get('price'),
'pre_price': ns.get('pre_price'),
'price_unit': ns.get('price_unit'),
'discount': ns.get('discount'),
'bg_img_url': ns.get('bg_img_url'),
'icon': ns.get('icon')
}
# 必填字段验证
if not ns_dic.get('content_type'):
return {
'status': False,
'msg': 'content_type is required'
}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
await sor.C('home_page_content_items', ns_dic)
return {
'status': True,
'msg': 'create home page content success',
'data': {'id': ns_dic['id']}
}
except Exception as e:
await sor.rollback()
return {
'status': False,
'msg': 'create home page content failed, %s' % str(e)
}
ret = await home_page_content_add(params_kw)
return ret

View File

@ -0,0 +1,33 @@
async def home_page_content_delete(ns={}):
"""
软删除内容项id值必传并且把del_flg值修改为1
:param ns: 包含id的字典
:return: 删除结果
"""
if not ns.get('id'):
return {
'status': False,
'msg': 'id is required'
}
ns_dic = {
'id': ns.get('id'),
'del_flg': '1'
}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
await sor.U('home_page_content_items', ns_dic)
return {
'status': True,
'msg': 'delete home page content success'
}
except Exception as e:
await sor.rollback()
return {
'status': False,
'msg': 'delete home page content failed, %s' % str(e)
}
ret = await home_page_content_delete(params_kw)
return ret

View File

@ -0,0 +1,118 @@
async def home_page_content_get_tree(ns={}):
"""
获取首页内容的树形结构按content_type分组
:param ns: 包含menu_product_id的字典
:return: 树形结构数据
"""
menu_product_id = ns.get('menu_product_id')
if not menu_product_id:
return {
'status': False,
'msg': 'menu_product_id is required'
}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
# 查询所有有效的内容项
sql = """
SELECT * FROM home_page_content_items
WHERE menu_product_id = '%s' AND del_flg = '0'
ORDER BY sort_order ASC, create_at ASC
""" % menu_product_id
all_items = await sor.sqlExe(sql, {})
# 通过home_page_product_info表查找标题和描述
find_sql = """
SELECT * FROM home_page_product_info
WHERE id = '%s' AND del_flg = '0'
""" % menu_product_id
product_info = await sor.sqlExe(find_sql, {})
product_title = None
product_description = None
if product_info:
product_info = product_info[0]
product_title = product_info.get('name', '')
product_description = product_info.get('description', '')
# 按content_type分组
result = {
'title': product_title, # 可以从其他表获取
'description': product_description, # 可以从其他表获取
'advantages': [],
'features': [],
'applications': [],
'products': []
}
for item in all_items:
content_type = item.get('content_type')
item_data = {
'id': item['id'],
'title': item['title'],
'description': item['description'],
'img': item['img'],
'icon': item['icon']
}
# 根据内容类型添加到对应的列表
if content_type == 'advantage':
result['advantages'].append(item_data)
elif content_type == 'feature':
result['features'].append(item_data)
elif content_type == 'application':
# 应用场景可能包含子项
app_data = item_data.copy()
if not item.get('parent_id'): # 顶级应用
app_data['provide'] = [] # 子项将在后续处理
result['applications'].append(app_data)
elif content_type == 'product' and not item.get('parent_id'):
# 产品可能包含配置项
product_data = item_data.copy()
product_data['price'] = item.get('price')
product_data['pre_price'] = item.get('pre_price')
product_data['price_unit'] = item.get('price_unit')
product_data['discount'] = item.get('discount')
product_data['bg_img_url'] = item.get('bg_img_url')
product_data['list'] = [] # 子项将在后续处理
result['products'].append(product_data)
# 处理子级项目
for item in all_items:
if item.get('parent_id') != '0':
parent_id = item.get('parent_id')
content_type = item.get('content_type')
# 查找父级并添加子项
if content_type == 'application':
for app in result['applications']:
if app['id'] == parent_id:
app['provide'].append({
'id': item['id'],
'title': item['title'],
'description': item['description']
})
elif content_type == 'product':
for product in result['products']:
if product['id'] == parent_id:
product['list'].append({
'id': item['id'],
'name': item['name'],
'description': item['description'],
'icon': item['icon']
})
return {
'status': True,
'msg': 'get home page content tree success',
'data': result
}
except Exception as e:
return {
'status': False,
'msg': 'get home page content tree failed, %s' % str(e)
}
ret = await home_page_content_get_tree(params_kw)
return ret

View File

@ -0,0 +1,36 @@
async def home_page_content_search(ns={}):
"""
查找内容项支持按内容类型、父级ID等条件查询
:param ns: 查询条件字典
:return: 查询结果
"""
# 构建查询条件
conditions = {"del_flg": "0"}
if ns.get('content_type'):
conditions['content_type'] = ns.get('content_type')
if ns.get('parent_id'):
conditions['parent_id'] = ns.get('parent_id')
if ns.get('menu_product_id'):
conditions['menu_product_id'] = ns.get('menu_product_id')
if ns.get('id'):
conditions['id'] = ns.get('id')
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
result = await sor.R('home_page_content_items', conditions)
return {
'status': True,
'msg': 'search home page content success',
'data': result
}
except Exception as e:
await sor.rollback()
return {
'status': False,
'msg': 'search home page content failed, %s' % str(e)
}
ret = await home_page_content_search(params_kw)
return ret

View File

@ -0,0 +1,39 @@
async def home_page_content_update(ns={}):
"""
更新内容项
:param ns: 包含id和需要更新的字段的字典
:return: 更新结果
"""
if not ns.get('id'):
return {
'status': False,
'msg': 'id is required'
}
# 构建更新字段,排除空值
ns_dic = {'id': ns.get('id')}
update_fields = ['title', 'description', 'img', 'name', 'price',
'pre_price', 'price_unit', 'discount', 'bg_img_url',
'icon', 'sort_order', 'parent_id', 'level']
for field in update_fields:
if ns.get(field):
ns_dic[field] = ns.get(field)
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
await sor.U('home_page_content_items', ns_dic)
return {
'status': True,
'msg': 'update home page content success'
}
except Exception as e:
await sor.rollback()
return {
'status': False,
'msg': 'update home page content failed, %s' % str(e)
}
ret = await home_page_content_update(params_kw)
return ret

View File

@ -13,13 +13,13 @@ async def home_page_product_menu_add(ns={}):
# 验证必填字段 # 验证必填字段
if not ns_dic.get('menu_level') or not ns_dic.get('title'): # if not ns_dic.get('menu_level') or not ns_dic.get('title'):
return { # return {
'status': False, # 'status': False,
'msg': 'menu_level and title are required' # 'msg': 'menu_level and title are required'
} # }
if ns.get('menu_level') > 1 and not ns.get('parent_id'): if int(ns.get('menu_level')) > 1 and not ns.get('parent_id'):
return { return {
'status': False, 'status': False,
'msg': 'parent_id is required for menu_level > 1' 'msg': 'parent_id is required for menu_level > 1'

View File

@ -165,7 +165,7 @@ async def build_menu_tree(menu_list, target_level=None, target_title=None):
""" """
# 通过sort_order对菜单进行排序 # 通过sort_order对菜单进行排序
menu_list.sort(key=lambda x: x['sort_order'], reverse=True) menu_list.sort(key=lambda x: int(x['sort_order']), reverse=False)
menu_dict = {} menu_dict = {}
result = [] result = []

View File

@ -14,12 +14,12 @@ async def home_page_product_menu_update(ns={}):
ns_dic = {'id': ns.get('id')} ns_dic = {'id': ns.get('id')}
if ns.get('title'): if ns.get('title'):
ns_dic['title'] = ns.get('title') ns_dic['title'] = ns.get('title')
if ns.get('parent_id') is not None: if ns.get('parent_id'):
ns_dic['parent_id'] = ns.get('parent_id') ns_dic['parent_id'] = ns.get('parent_id')
if ns.get('menu_level'): if ns.get('menu_level'):
ns_dic['menu_level'] = ns.get('menu_level') ns_dic['menu_level'] = ns.get('menu_level')
if ns.get('sort_order') is not None: if ns.get('sort_order'):
ns_dic['sort_order'] = ns.get('sort_order') ns_dic['sort_order'] = int(ns.get('sort_order'))
db = DBPools() db = DBPools()
async with db.sqlorContext('kboss') as sor: async with db.sqlorContext('kboss') as sor:

View File

@ -14,21 +14,21 @@ async def home_page_product_update(ns={}):
ns_dic = {'id': ns.get('id')} ns_dic = {'id': ns.get('id')}
if ns.get('name'): if ns.get('name'):
ns_dic['name'] = ns.get('name') ns_dic['name'] = ns.get('name')
if ns.get('description') is not None: if ns.get('description'):
ns_dic['description'] = ns.get('description') ns_dic['description'] = ns.get('description')
if ns.get('label') is not None: if ns.get('label'):
ns_dic['label'] = ns.get('label') ns_dic['label'] = ns.get('label')
if ns.get('product_group') is not None: if ns.get('product_group'):
ns_dic['product_group'] = ns.get('product_group') ns_dic['product_group'] = ns.get('product_group')
if ns.get('url') is not None: if ns.get('url'):
ns_dic['url'] = ns.get('url') ns_dic['url'] = ns.get('url')
if ns.get('list_url') is not None: if ns.get('list_url'):
ns_dic['list_url'] = ns.get('list_url') ns_dic['list_url'] = ns.get('list_url')
if ns.get('icon_url') is not None: if ns.get('icon_url'):
ns_dic['icon_url'] = ns.get('icon_url') ns_dic['icon_url'] = ns.get('icon_url')
if ns.get('source') is not None: if ns.get('source'):
ns_dic['source'] = ns.get('source') ns_dic['source'] = ns.get('source')
if ns.get('sort_order') is not None: if ns.get('sort_order'):
ns_dic['sort_order'] = ns.get('sort_order') ns_dic['sort_order'] = ns.get('sort_order')
if ns.get('menu_id'): if ns.get('menu_id'):
ns_dic['menu_id'] = ns.get('menu_id') ns_dic['menu_id'] = ns.get('menu_id')

Binary file not shown.

View File

@ -0,0 +1,9 @@
import request from "@/utils/request";
export const reqHotProduct = () => {
return request({
url: '/product/get_firstpage_jizuonet.dspy',
method: 'post',
})
}

View File

@ -0,0 +1,117 @@
import request from "@/utils/request";
// 菜单添加
export function addProductMenuAPI(params) {
return request({
url: `/product/home_page_product_menu_add.dspy`,
method: 'post',
data: params,
})
}
// 菜单更新
export function updateProductMenuAPI(params) {
return request({
url: `/product/home_page_product_menu_update.dspy`,
method: 'post',
data: params,
})
}
// 菜单查找
export function findProductMenuAPI(params) {
return request({
url: `/product/home_page_product_menu_search.dspy`,
method: 'get',
data: params,
})
}
// 菜单删除
export function deleteProductMenuAPI(params) {
return request({
url: `/product/home_page_product_menu_delete.dspy`,
method: 'post',
data: params,
})
}
// 产品添加
export function addProductThreeMenuAPI(params) {
return request({
url: `/product/home_page_product_add.dspy`,
method: 'post',
data: params,
})
}
// 产品列表
export function findProductThreeMenuAPI(params) {
return request({
url: `/product/home_page_product_search.dspy`,
method: 'post',
data: params,
})
}
// 三级产品更新
export function updateProductThreeMenuAPI(params) {
return request({
url: `/product/home_page_product_update.dspy`,
method: 'post',
data: params,
})
}
// 三级产品删除
export function deleteProductThreeMenuAPI(params) {
return request({
url: `/product/home_page_product_delete.dspy`,
method: 'post',
data: params,
})
}
//产品介绍页数据添加
export function addProductIntroAPI(params) {
return request({
url: `/product/home_page_content_add.dspy`,
method: 'post',
data: params,
})
}
// 产品介绍页数据删除
export function deleteProductIntroAPI(params) {
return request({
url: `/product/home_page_content_delete.dspy`,
method: 'get',
data: params,
})
}
// 产品介绍页数据更新
export function updateProductIntroAPI(params) {
return request({
url: `/product/home_page_content_update.dspy`,
method: 'post',
data: params,
})
}
// 产品介绍页数据树
export function findProductIntroAPI(params) {
return request({
url: `/product/home_page_content_get_tree.dspy`,
method: 'post',
data: params,
})
}
// 产品介绍页数据查找
export function findProductIntroTreeAPI(params) {
return request({
url: `/product/home_page_content_search.dspy`,
method: 'get',
data: params,
})
}

View File

@ -80,7 +80,16 @@ export const constantRoutes = [
title: '产品详情', title: '产品详情',
component: () => import('@/views/homePage/wxDetailPage.vue'), component: () => import('@/views/homePage/wxDetailPage.vue'),
hidden: true hidden: true
}, { },
{
path: '/h5HomePage',
name: 'H5HomePage',
title: 'H5首页',
component: () => import('@/views/H5/indexPage/index.vue'),
hidden: true,
},
{
hidden: true, path: '/screen', name: 'screen', title: '可视化大屏', meta: { hidden: true, path: '/screen', name: 'screen', title: '可视化大屏', meta: {
title: "可视化大屏", fullPath: "/operation/analyze/screen", title: "可视化大屏", fullPath: "/operation/analyze/screen",
}, component: () => import('@/views/product/bigScreen/index.vue'), children: [{ }, component: () => import('@/views/product/bigScreen/index.vue'), children: [{
@ -1156,8 +1165,17 @@ export const asyncRoutes = [
path: "index", path: "index",
component: () => import("@/views/operation/menuMangement/index.vue"), component: () => import("@/views/operation/menuMangement/index.vue"),
name: "menuMangement", name: "menuMangement",
meta: { title: "菜单管理", fullPath: "/menuMangement/index" }, meta: { title: "ncmatch菜单管理", fullPath: "/menuMangement/index" },
}, },
{
path: "productsServices",
component: () => import("@/views/operation/productsServices/index.vue"),
name: "productsServices",
meta: {
title: "产品与服务管理",
fullPath: "/operation/productsServices"
}
}
] ]
}, },

View File

@ -145,6 +145,22 @@ const mutations = {
}; };
const actions = { const actions = {
/**
* 生成动态路由
*
* 根据用户类型组织类型和权限列表生成对应的动态路由配置
* 包含管理员和普通用户的不同路由生成逻辑
*
* @param {Object} context - Vuex上下文对象
* @param {Function} context.commit - 提交mutation的方法
* @param {Object} context.rootState - 根模块的状态
* @param {Object} params - 参数对象
* @param {string} [params.userType] - 用户类型
* @param {number} [params.orgType] - 组织类型
* @param {Array} [params.auths] - 权限列表
* @param {Object} [params.user] - 用户信息对象
* @returns {Promise<Array>} 解析后的动态路由数组
*/
generateRoutes({ commit, rootState }, params) { generateRoutes({ commit, rootState }, params) {
console.log("ACTION generateRoutes - params:", params); console.log("ACTION generateRoutes - params:", params);
return new Promise((resolve) => { return new Promise((resolve) => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,392 @@
<template>
<div class="main-page">
<!-- banner图 -->
<div class="top-box">
<div class="title">
<p class="title-top">
<span class="name">开元云</span> 您身边的AI管家
</p>
<p class="title-btm">
全球领先的AI服务运营商
</p>
</div>
</div>
<!-- 云筑企业基座 -->
<div class="base-box">
<div class="text">
<p class="text-top">云筑企业基座</p>
<p class="text-btm">多云结合 让上云更简单</p>
</div>
<!-- 内容 -->
<div class="content">
<div class="item-box" v-for="(item, index) in baseData" :key="index">
<!-- 标题 -->
<div class="item-title">
{{ item.title }}
</div>
<!-- 介绍 -->
<div class="item-description">
{{ item.description }}
</div>
<!-- 内容 -->
<div class="item-content">
{{ item.content }}
</div>
<div class="icon-box">
</div>
<!-- 价格 -->
<div class="item-price">
<span class="price-icon"></span> <span class="price">{{ item.price }}</span> /
</div>
<!-- 按钮 -->
<div class="item-button">
<div class="item-button-text">立即咨询</div>
</div>
</div>
</div>
</div>
<!-- 智算未来征程 -->
<div class="journey-box">
<div class="text">
<p class="text-top">智算未来征程</p>
<p class="text-btm">多元异构 灵活短租</p>
</div>
<!-- 内容 -->
<div class="content">
<div class="item-box" v-for="(item, index) in baseData" :key="index">
<!-- 标题 -->
<div class="item-title">
{{ item.title }}
</div>
<!-- 介绍 -->
<div class="item-description">
{{ item.description }}
</div>
<!-- 内容 -->
<div class="item-content">
{{ item.content }}
</div>
<div class="icon-box">
</div>
<!-- 价格 -->
<div class="item-price">
<span class="price-icon"></span> <span class="price">{{ item.price }}</span> /
</div>
<!-- 按钮 -->
<div class="item-button">
<div class="item-button-text">立即咨询</div>
</div>
</div>
</div>
</div>
<!-- 网织智能经纬 -->
<div class="latitude-box">
<div class="text">
<p class="text-top">网织智能经纬</p>
<p class="text-btm">云算网结合 让连接更简单</p>
</div>
<!-- 内容 -->
<div class="content">
<div class="item-box" v-for="(item, index) in baseData" :key="index">
<!-- 标题 -->
<div class="item-title">
{{ item.title }}
</div>
<!-- 介绍 -->
<div class="item-description">
{{ item.description }}
</div>
<!-- 内容 -->
<div class="item-content">
{{ item.content }}
</div>
<div class="icon-box">
</div>
<!-- 价格 -->
<div class="item-price">
<span class="price-icon"></span> <span class="price">{{ item.price }}</span> /
</div>
<!-- 按钮 -->
<div class="item-button">
<div class="item-button-text">立即咨询</div>
</div>
</div>
</div>
</div>
<!-- 合作机构 -->
<div class="partner">
<div class="text">
<p class="text-top">合作伙伴</p>
<p class="text-btm">Partners</p>
</div>
<!-- 滚动合作伙伴内容 -->
<div class="partner-scroll">
<div class="logo-scroll-wrapper">
<div class="logo-scroll-container">
<div v-for="(image, index) in duplicatedImages" :key="index" class="logo-item">
<img :src="image" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { reqHotProduct } from '@/api/H5/index.js'
export default {
data() {
return {
baseData: [],
journeyData: [],
latitude: [],
images: [
require('../images/top/img.png'),
require('../images/top/img_1.png'),
require('../images/top/img_2.png'),
require('../images/top/img_3.png'),
require('../images/top/img_4.png'),
require('../images/top/img_5.png'),
require('../images/top/img_6.png'),
require('../images/top/img_8.png'),
require('../images/top/img_9.png')
]
}
},
computed: {
duplicatedImages() {
return [...this.images, ...this.images]
}
},
created() {
this.getHotProduct()
},
methods: {
async getHotProduct() {
const res = await reqHotProduct()
console.log('数据', res);
if (res.status == true) {
this.baseData = res.data.base
this.journeyData = res.data.suan
this.latitude = res.data.net
}
}
}
}
</script>
<style lang="less" scoped>
* {
margin: 0;
padding: 0;
font-size: 16px;
box-sizing: border-box;
}
.main-page {
width: 100%;
height: 100%;
background-color: #f8fafd;
}
.top-box {
width: 100%;
height: 50vh;
background: url("../images/banner.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
}
.title {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #222f60;
.name {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: .55rem;
}
.title-top {
font-size: .55rem;
}
.title-btm {
font-size: .26rem;
margin-top: .98rem;
}
}
//
.text {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: .14rem 0;
width: 100%;
.text-top {
font-size: .26rem;
color: #000;
}
.text-btm {
margin-top: .14rem;
font-size: .18rem;
color: #707070;
}
}
//
.base-box,
.journey-box,
.latitude-box {
width: 100%;
display: flex;
flex-direction: column;
padding: 0.3rem 0.4rem;
.content {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 0.2rem;
.item-box {
width: 48%;
background-color: #fff;
border-radius: 0.1rem;
padding: 0.2rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
margin-bottom: 0.2rem;
display: flex;
flex-direction: column;
.item-title {
font-size: 0.2rem;
font-weight: bold;
color: #333;
margin-bottom: 0.1rem;
}
.item-description {
font-size: 0.14rem;
color: #666;
margin-bottom: 0.15rem;
}
.item-content {
font-size: 0.14rem;
color: #333;
line-height: 1.4;
flex: 1;
}
.item-price {
font-size: 0.1rem;
font-weight: bold;
color: #d4d6e1;
margin: 0.15rem 0;
.price {
font-size: .22rem;
color: #f52220;
}
.price-icon {
color: #f52220;
font-size: .1rem;
}
}
.item-button {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
border-radius: 0.04rem;
padding: 0.08rem 0;
text-align: center;
.item-button-text {
color: #fff;
font-size: 0.14rem;
}
}
}
}
}
//
.partner {
background-color: #f8f9fd;
width: 100%;
display: flex;
flex-direction: column;
padding: 0.3rem 0.4rem;
.partner-scroll {
width: 100%;
max-width: 14rem;
margin: 0 auto;
padding: .15rem;
background: #fff;
border-radius: .16rem;
margin-bottom: .3rem;
.logo-scroll-wrapper {
width: 100%;
height: .85rem;
overflow: hidden;
position: relative;
}
.logo-scroll-container {
display: flex;
height: 100%;
animation: scroll 15s linear infinite;
white-space: nowrap;
}
.logo-item {
flex: 0 0 auto;
width: 1.68rem;
height: 100%;
margin-right: .2rem;
background: #fff;
border-radius: .04rem;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
}
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
</style>

View File

@ -189,7 +189,7 @@
</el-table-column> </el-table-column>
<el-table-column align="center" header-align="center" label="订单原价" width="140"> <el-table-column align="center" header-align="center" label="订单原价" width="140">
<template slot-scope="scope"> <template slot-scope="scope">
<div class="cell-content">{{ scope.row.list_price }}</div> <div class="cell-content">{{ scope.row.origin_amout }}</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" header-align="center" label="订单折后价" width="140"> <el-table-column align="center" header-align="center" label="订单折后价" width="140">

View File

@ -38,7 +38,7 @@
@mouseenter="selectSecondary(secondary)" @mouseenter="selectSecondary(secondary)"
@click="handleSecondaryClick(secondary)"> @click="handleSecondaryClick(secondary)">
{{ secondary.second_level_name }} {{ secondary.second_level_name }}
<span v-if="secondary.thirdClassification && secondary.thirdClassification.length > 0" class="item-arrow"></span> <!-- <span v-if="secondary.thirdClassification && secondary.thirdClassification.length > 0" class="item-arrow"></span> -->
</div> </div>
</div> </div>

View File

@ -341,10 +341,10 @@ export default {
align-items: center; align-items: center;
} }
.banner-box { .banner-box {
height: 700px!important; height: 600px!important;
background: url('./img/tt-banner.png') no-repeat ; background: url('./img/tt-banner.png') no-repeat ;
width: 100%; width: 100%;
padding-bottom: 200px; // padding-bottom: 10px;
.conter { .conter {
width: 700px; width: 700px;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,687 @@
.mianBox {
display: flex;
justify-content: space-between;
min-height: 100vh;
gap: 16px;
padding-bottom: 20px;
padding-bottom: 40px;
}
.left {
width: 30%;
min-width: 320px;
height: 100vh;
}
.left-card {
display: flex;
flex-direction: column;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
}
.left-card /deep/ .el-card__body {
padding: 20px;
flex: 1;
height: 100%;
}
.right {
flex: 1;
min-width: 0;
}
.toptit {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #e4e7ed;
}
.search {
margin-bottom: 20px;
}
.search /deep/ .el-input-group__append {
background-color: #409eff;
border-color: #409eff;
}
.search /deep/ .el-input-group__append .el-button {
color: white;
}
.search /deep/ .el-input-group__append .el-button:hover {
background-color: #66b1ff;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding: 6px 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.custom-tree-node:hover {
background-color: #f5f7fa;
}
.custom-tree-node .el-button {
padding: 4px;
margin-left: 4px;
}
.custom-tree-node .el-button i {
font-size: 12px;
}
.custom-tree-node .el-button:hover {
background-color: rgba(64, 158, 255, 0.1);
border-radius: 50%;
}
.tree-container {
overflow-y: auto;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 12px;
background-color: #fafafa;
}
.tree-container /deep/ .el-tree .el-tree-node .el-tree-node__content {
height: 36px;
}
.tree-container /deep/ .el-tree .el-tree-node .el-tree-node__content:hover {
background-color: #f0f7ff;
}
.tree-container /deep/ .el-tree .el-tree-node.is-current > .el-tree-node__content {
background-color: #ecf5ff;
color: #409eff;
font-weight: 500;
}
.add-menu-box {
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed #c0c4cc;
border-radius: 6px;
padding: 12px;
text-align: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
margin-top: 20px;
background-color: #fafafa;
color: #606266;
}
.add-menu-box:hover {
border-color: #409eff;
background-color: #f0f7ff;
color: #409eff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
}
.add-menu-box .add-icon {
font-size: 20px;
font-weight: 600;
margin-right: 8px;
line-height: 1;
}
.add-menu-box div {
font-size: 14px;
font-weight: 500;
}
.product-card {
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
margin-bottom: 20px;
}
.product-card /deep/ .el-card__body {
padding: 24px;
}
.rigtop {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.rigtop-left {
flex: 1;
min-width: 0;
}
.breadcrumb {
margin-top: 8px;
}
.breadcrumb /deep/ .el-breadcrumb__inner {
color: #909399;
font-size: 13px;
}
.breadcrumb /deep/ .el-breadcrumb__inner:hover {
color: #409eff;
}
.breadcrumb /deep/ .el-breadcrumb__separator {
color: #c0c4cc;
}
.current-menu {
font-size: 20px;
font-weight: 600;
color: #303133;
line-height: 1.4;
}
.add-product-btn .el-button {
border-radius: 6px;
padding: 10px 20px;
font-weight: 500;
}
.add-product-btn .el-button i {
margin-right: 4px;
}
.product-table {
margin-bottom: 24px;
}
.product-table /deep/ .el-table {
border-radius: 6px;
overflow: hidden;
border: 1px solid #ebeef5;
}
.product-table /deep/ .el-table th {
background-color: #f5f7fa;
color: #303133;
font-weight: 600;
height: 48px;
}
.product-table /deep/ .el-table td {
padding: 16px 0;
}
.product-table /deep/ .el-table .el-table__row {
cursor: pointer;
transition: background-color 0.2s;
}
.product-table /deep/ .el-table .el-table__row:hover {
background-color: #f5f7fa;
}
.product-table /deep/ .el-table .el-table__row.current-row {
background-color: #ecf5ff;
}
.product-info .product-name {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 6px;
line-height: 1.4;
}
.product-info .product-desc {
font-size: 12px;
color: #909399;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.product-tags .el-tag {
border-radius: 4px;
padding: 0 8px;
height: 24px;
line-height: 22px;
font-size: 12px;
}
.product-tags .el-tag--info {
background-color: #f4f4f5;
border-color: #e9e9eb;
color: #909399;
}
.action-buttons {
display: flex;
gap: 8px;
}
.action-buttons .el-button {
min-width: 60px;
padding: 7px 15px;
border-radius: 4px;
font-size: 12px;
}
.action-buttons .el-button--danger {
background-color: #fef0f0;
border-color: #fbc4c4;
color: #f56c6c;
}
.action-buttons .el-button--danger:hover {
background-color: #f56c6c;
color: white;
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #ebeef5;
}
.pagination /deep/ .el-pagination .el-pagination__sizes,
.pagination /deep/ .el-pagination .el-pagination__jump {
margin-left: 16px;
}
.pagination /deep/ .el-pagination .btn-prev,
.pagination /deep/ .el-pagination .btn-next {
border-radius: 4px;
border: 1px solid #d8dce5;
}
.pagination /deep/ .el-pagination .el-pager li {
border-radius: 4px;
border: 1px solid #d8dce5;
margin: 0 4px;
}
.pagination /deep/ .el-pagination .el-pager li.active {
background-color: #409eff;
color: white;
border-color: #409eff;
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
margin-bottom: 24px;
}
.card-top .top-tit span:first-child {
font-size: 18px;
font-weight: 600;
color: #303133;
display: block;
margin-bottom: 4px;
}
.card-top .top-tit span:last-child {
font-size: 13px;
color: #909399;
}
.card-top .top-btn .el-button {
border-radius: 6px;
padding: 10px 24px;
font-weight: 500;
}
.card-content {
width: 100%;
}
.card-content .content-tab {
width: 100%;
}
.card-content .content-tab .tab-top {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
}
.card-content .content-tab .tab-content {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 24px;
}
.card-content .content-tab .tab-content .tab-item {
padding: 10px 24px;
border-radius: 20px;
background-color: #f5f7fa;
color: #606266;
font-size: 14px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid transparent;
}
.card-content .content-tab .tab-content .tab-item:hover {
background-color: #e4e7ed;
transform: translateY(-1px);
}
.card-content .content-tab .tab-content .tab-item.active {
background-color: #409eff;
color: white;
font-weight: 500;
border-color: #409eff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
}
.product-detail-card {
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
}
.product-detail-card /deep/ .el-card__body {
padding: 24px;
}
.product-detail-card .product-basic-info {
padding: 20px;
background-color: #fafafa;
border-radius: 6px;
border: 1px solid #ebeef5;
}
.product-detail-card .product-basic-info /deep/ .el-form-item {
margin-bottom: 20px;
}
.product-detail-card .product-basic-info /deep/ .el-form-item:last-child {
margin-bottom: 0;
}
.product-detail-card .product-basic-info /deep/ .el-form-item .el-form-item__label {
color: #606266;
font-weight: 500;
}
.product-detail-card .product-basic-info /deep/ .el-form-item .el-input .el-input__inner,
.product-detail-card .product-basic-info /deep/ .el-form-item .el-textarea .el-input__inner,
.product-detail-card .product-basic-info /deep/ .el-form-item .el-input .el-textarea__inner,
.product-detail-card .product-basic-info /deep/ .el-form-item .el-textarea .el-textarea__inner {
border-radius: 4px;
}
.product-detail-card .product-basic-info /deep/ .el-form-item .el-input .el-input__inner:focus,
.product-detail-card .product-basic-info /deep/ .el-form-item .el-textarea .el-input__inner:focus,
.product-detail-card .product-basic-info /deep/ .el-form-item .el-input .el-textarea__inner:focus,
.product-detail-card .product-basic-info /deep/ .el-form-item .el-textarea .el-textarea__inner:focus {
border-color: #409eff;
}
.product-detail-card .product-basic-info /deep/ .el-form-item .el-input-number .el-input-number__decrease,
.product-detail-card .product-basic-info /deep/ .el-form-item .el-input-number .el-input-number__increase {
border-radius: 4px 0 0 4px;
}
.product-detail-card .product-basic-info /deep/ .el-form-item .el-input-number .el-input__inner {
border-radius: 0 4px 4px 0;
}
.product-detail-card .product-detail-content {
margin-top: 16px;
}
.product-detail-card .product-detail-content .editor-container /deep/ .el-textarea .el-textarea__inner {
border-radius: 6px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
line-height: 1.6;
padding: 16px;
border: 1px solid #dcdfe6;
}
.product-detail-card .product-detail-content .editor-container /deep/ .el-textarea .el-textarea__inner:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.product-detail-card .product-detail-content .editor-container /deep/ .el-textarea .el-textarea__inner::placeholder {
color: #c0c4cc;
}
.empty-detail {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 40px;
text-align: center;
}
.empty-detail .empty-icon {
font-size: 64px;
margin-bottom: 24px;
opacity: 0.3;
animation: float 3s ease-in-out infinite;
}
.empty-detail .empty-text {
color: #909399;
font-size: 16px;
line-height: 1.6;
max-width: 300px;
}
/deep/ .el-dialog {
border-radius: 8px;
}
/deep/ .el-dialog .el-dialog__header {
padding: 20px 24px 10px;
border-bottom: 1px solid #e4e7ed;
}
/deep/ .el-dialog .el-dialog__header .el-dialog__title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
/deep/ .el-dialog .el-dialog__body {
padding: 20px 24px;
}
/deep/ .el-dialog .el-dialog__body .el-form-item {
margin-bottom: 20px;
}
/deep/ .el-dialog .el-dialog__body .el-form-item:last-child {
margin-bottom: 0;
}
/deep/ .el-dialog .el-dialog__footer {
padding: 10px 24px 20px;
border-top: 1px solid #e4e7ed;
}
/deep/ .el-dialog .el-dialog__footer .dialog-footer .el-button {
border-radius: 6px;
padding: 10px 20px;
min-width: 80px;
}
/deep/ .el-dialog .el-dialog__footer .dialog-footer .el-button--primary {
font-weight: 500;
}
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
@media (max-width: 1200px) {
.mianBox {
flex-direction: column;
}
.left,
.right {
width: 100%;
}
.left {
margin-bottom: 16px;
}
.tree-container {
height: 300px;
}
}
.pagination-controls {
width: 100%;
display: flex;
justify-content: center;
}
@media (max-width: 768px) {
.rigtop {
flex-direction: column;
align-items: stretch;
}
.add-product-btn {
align-self: flex-start;
}
.tab-content {
flex-direction: column;
}
.tab-content .tab-item {
width: 100%;
text-align: center;
}
.action-buttons {
flex-direction: column;
gap: 4px;
}
}
.content-form {
padding: 20px;
}
.content-form .el-form-item {
margin-bottom: 20px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.section-header h3 {
margin: 0;
font-size: 18px;
color: #303133;
}
.form-list {
margin-top: 20px;
}
.form-item {
padding: 20px;
margin-bottom: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #fafafa;
}
.form-item.main-form-item {
background-color: #f5f7fa;
}
.form-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px dashed #ebeef5;
color: #409eff;
}
.form-actions {
text-align: right;
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed #ebeef5;
}
.form-actions .el-button {
padding: 0 10px;
}
.saved-list {
margin-bottom: 30px;
}
.list-item {
padding: 15px;
margin-bottom: 15px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #fff;
}
.list-item.main-item {
background-color: #f8f9fa;
}
.list-item .item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
}
.list-item .item-header .item-title {
font-weight: bold;
color: #606266;
}
.list-item .item-content {
display: flex;
align-items: flex-start;
}
.list-item .item-content .item-image {
margin-right: 15px;
flex-shrink: 0;
}
.list-item .item-content .item-details {
flex: 1;
}
.list-item .item-content .item-details div {
margin-bottom: 5px;
}
.list-item .item-content .item-details div strong {
color: #606266;
margin-right: 5px;
}
.detail-section {
margin-top: 15px;
padding-top: 15px;
border-top: 1px dashed #e4e7ed;
}
.detail-list .detail-item {
padding: 10px;
margin-bottom: 10px;
background-color: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
}
.detail-list .detail-item .detail-title {
font-weight: bold;
margin-bottom: 8px;
color: #67c23a;
}
.detail-list .detail-item .detail-content {
margin-bottom: 8px;
}
.detail-list .detail-item .detail-content div {
margin-bottom: 3px;
}
.detail-list .detail-item .detail-content div strong {
color: #909399;
margin-right: 5px;
}
.detail-list .detail-item .detail-actions {
text-align: right;
}
.detail-form-section {
margin-top: 15px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 4px;
border: 1px dashed #dcdfe6;
}
.detail-form-item {
margin-bottom: 15px;
padding: 15px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #ebeef5;
}
.add-detail-btn {
text-align: center;
margin-top: 10px;
}
.main-product-form {
padding: 20px;
margin-bottom: 30px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #fafafa;
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #ebeef5;
}
.card-top .top-tit {
display: flex;
flex-direction: column;
}
.card-top .top-tit span:first-child {
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
.card-top .top-tit span:last-child {
font-size: 14px;
color: #409eff;
}
.no-product-selected {
padding: 80px 20px;
text-align: center;
}
.no-product-selected .empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
}
.product-table /deep/ .el-table__row {
cursor: pointer;
}
.product-table /deep/ .el-table__row:hover {
background-color: #f5f7fa;
}
.product-table /deep/ .el-table__row.current-row {
background-color: #ecf5ff;
}

View File

@ -0,0 +1,868 @@
.mianBox {
display: flex;
justify-content: space-between;
min-height: 100vh;
// min-height: 600px;
gap: 16px;
padding-bottom: 20px;
padding-bottom: 40px;
// box-sizing: border-box
}
.left {
width: 30%;
min-width: 320px;
height: 100vh;
}
.left-card {
// height: 100%;
display: flex;
flex-direction: column;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
/deep/ .el-card__body {
padding: 20px;
flex: 1;
height: 100%;
}
}
.right {
flex: 1;
min-width: 0; // 防止flex溢出
}
.toptit {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #e4e7ed;
}
.search {
margin-bottom: 20px;
/deep/ .el-input-group__append {
background-color: #409eff;
border-color: #409eff;
.el-button {
color: white;
&:hover {
background-color: #66b1ff;
}
}
}
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding: 6px 8px;
border-radius: 4px;
transition: background-color 0.2s;
&:hover {
background-color: #f5f7fa;
}
.el-button {
padding: 4px;
margin-left: 4px;
i {
font-size: 12px;
}
&:hover {
background-color: rgba(64, 158, 255, 0.1);
border-radius: 50%;
}
}
}
.tree-container {
// height: 400px;
overflow-y: auto;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 12px;
background-color: #fafafa;
/deep/ .el-tree {
.el-tree-node {
.el-tree-node__content {
height: 36px;
&:hover {
background-color: #f0f7ff;
}
}
&.is-current {
> .el-tree-node__content {
background-color: #ecf5ff;
color: #409eff;
font-weight: 500;
}
}
}
}
}
.add-menu-box {
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed #c0c4cc;
border-radius: 6px;
padding: 12px;
text-align: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
margin-top: 20px;
background-color: #fafafa;
color: #606266;
&:hover {
border-color: #409eff;
background-color: #f0f7ff;
color: #409eff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
}
.add-icon {
font-size: 20px;
font-weight: 600;
margin-right: 8px;
line-height: 1;
}
div {
font-size: 14px;
font-weight: 500;
}
}
// 右侧产品卡片样式
.product-card {
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
margin-bottom: 20px;
/deep/ .el-card__body {
padding: 24px;
}
}
.rigtop {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.rigtop-left {
flex: 1;
min-width: 0;
}
.breadcrumb {
margin-top: 8px;
/deep/ .el-breadcrumb__inner {
color: #909399;
font-size: 13px;
&:hover {
color: #409eff;
}
}
/deep/ .el-breadcrumb__separator {
color: #c0c4cc;
}
}
.current-menu {
font-size: 20px;
font-weight: 600;
color: #303133;
line-height: 1.4;
}
.add-product-btn {
.el-button {
border-radius: 6px;
padding: 10px 20px;
font-weight: 500;
i {
margin-right: 4px;
}
}
}
// 产品表格样式
.product-table {
margin-bottom: 24px;
/deep/ .el-table {
border-radius: 6px;
overflow: hidden;
border: 1px solid #ebeef5;
th {
background-color: #f5f7fa;
color: #303133;
font-weight: 600;
height: 48px;
}
td {
padding: 16px 0;
}
.el-table__row {
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #f5f7fa;
}
&.current-row {
background-color: #ecf5ff;
}
}
}
}
.product-info {
.product-name {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 6px;
line-height: 1.4;
}
.product-desc {
font-size: 12px;
color: #909399;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
.el-tag {
border-radius: 4px;
padding: 0 8px;
height: 24px;
line-height: 22px;
font-size: 12px;
&--info {
background-color: #f4f4f5;
border-color: #e9e9eb;
color: #909399;
}
}
}
// 操作按钮样式
.action-buttons {
display: flex;
gap: 8px;
.el-button {
min-width: 60px;
padding: 7px 15px;
border-radius: 4px;
font-size: 12px;
&--danger {
background-color: #fef0f0;
border-color: #fbc4c4;
color: #f56c6c;
&:hover {
background-color: #f56c6c;
color: white;
}
}
}
}
// 分页样式
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #ebeef5;
/deep/ .el-pagination {
.el-pagination__sizes,
.el-pagination__jump {
margin-left: 16px;
}
.btn-prev,
.btn-next {
border-radius: 4px;
border: 1px solid #d8dce5;
}
.el-pager {
li {
border-radius: 4px;
border: 1px solid #d8dce5;
margin: 0 4px;
&.active {
background-color: #409eff;
color: white;
border-color: #409eff;
}
}
}
}
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
margin-bottom: 24px;
.top-tit {
span:first-child {
font-size: 18px;
font-weight: 600;
color: #303133;
display: block;
margin-bottom: 4px;
}
span:last-child {
font-size: 13px;
color: #909399;
}
}
.top-btn {
.el-button {
border-radius: 6px;
padding: 10px 24px;
font-weight: 500;
}
}
}
.card-content {
width: 100%;
.content-tab {
width: 100%;
.tab-top {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
}
.tab-content {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 24px;
.tab-item {
padding: 10px 24px;
border-radius: 20px;
background-color: #f5f7fa;
color: #606266;
font-size: 14px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid transparent;
&:hover {
background-color: #e4e7ed;
transform: translateY(-1px);
}
&.active {
background-color: #409eff;
color: white;
font-weight: 500;
border-color: #409eff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
}
}
}
}
}
// 产品详情卡片样式
.product-detail-card {
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
/deep/ .el-card__body {
padding: 24px;
}
.product-basic-info {
padding: 20px;
background-color: #fafafa;
border-radius: 6px;
border: 1px solid #ebeef5;
/deep/ .el-form-item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.el-form-item__label {
color: #606266;
font-weight: 500;
}
.el-input,
.el-textarea {
.el-input__inner,
.el-textarea__inner {
border-radius: 4px;
&:focus {
border-color: #409eff;
}
}
}
.el-input-number {
.el-input-number__decrease,
.el-input-number__increase {
border-radius: 4px 0 0 4px;
}
.el-input__inner {
border-radius: 0 4px 4px 0;
}
}
}
}
.product-detail-content {
margin-top: 16px;
.editor-container {
/deep/ .el-textarea {
.el-textarea__inner {
border-radius: 6px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
line-height: 1.6;
padding: 16px;
border: 1px solid #dcdfe6;
&:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
&::placeholder {
color: #c0c4cc;
}
}
}
}
}
}
.empty-detail {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 40px;
text-align: center;
.empty-icon {
font-size: 64px;
margin-bottom: 24px;
opacity: 0.3;
animation: float 3s ease-in-out infinite;
}
.empty-text {
color: #909399;
font-size: 16px;
line-height: 1.6;
max-width: 300px;
}
}
// 对话框样式优化
/deep/ .el-dialog {
border-radius: 8px;
.el-dialog__header {
padding: 20px 24px 10px;
border-bottom: 1px solid #e4e7ed;
.el-dialog__title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
}
.el-dialog__body {
padding: 20px 24px;
.el-form-item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
}
.el-dialog__footer {
padding: 10px 24px 20px;
border-top: 1px solid #e4e7ed;
.dialog-footer {
.el-button {
border-radius: 6px;
padding: 10px 20px;
min-width: 80px;
&--primary {
font-weight: 500;
}
}
}
}
}
// 添加动画效果
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
// 响应式调整
@media (max-width: 1200px) {
.mianBox {
flex-direction: column;
}
.left,
.right {
width: 100%;
}
.left {
margin-bottom: 16px;
}
.tree-container {
height: 300px;
}
}
.pagination-controls{
width: 100%;
display: flex;
justify-content: center;
}
@media (max-width: 768px) {
.rigtop {
flex-direction: column;
align-items: stretch;
}
.add-product-btn {
align-self: flex-start;
}
.tab-content {
flex-direction: column;
.tab-item {
width: 100%;
text-align: center;
}
}
.action-buttons {
flex-direction: column;
gap: 4px;
}
}
// 详情:添加样式
.content-form {
padding: 20px;
.el-form-item {
margin-bottom: 20px;
}
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
h3 {
margin: 0;
font-size: 18px;
color: #303133;
}
}
.form-list {
margin-top: 20px;
}
.form-item {
padding: 20px;
margin-bottom: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #fafafa;
&.main-form-item {
background-color: #f5f7fa;
}
}
.form-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px dashed #ebeef5;
color: #409eff;
}
.form-actions {
text-align: right;
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed #ebeef5;
.el-button {
padding: 0 10px;
}
}
.saved-list {
margin-bottom: 30px;
}
.list-item {
padding: 15px;
margin-bottom: 15px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #fff;
&.main-item {
background-color: #f8f9fa;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
.item-title {
font-weight: bold;
color: #606266;
}
}
.item-content {
display: flex;
align-items: flex-start;
.item-image {
margin-right: 15px;
flex-shrink: 0;
}
.item-details {
flex: 1;
div {
margin-bottom: 5px;
strong {
color: #606266;
margin-right: 5px;
}
}
}
}
}
.detail-section {
margin-top: 15px;
padding-top: 15px;
border-top: 1px dashed #e4e7ed;
}
.detail-list {
.detail-item {
padding: 10px;
margin-bottom: 10px;
background-color: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
.detail-title {
font-weight: bold;
margin-bottom: 8px;
color: #67c23a;
}
.detail-content {
margin-bottom: 8px;
div {
margin-bottom: 3px;
strong {
color: #909399;
margin-right: 5px;
}
}
}
.detail-actions {
text-align: right;
}
}
}
.detail-form-section {
margin-top: 15px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 4px;
border: 1px dashed #dcdfe6;
}
.detail-form-item {
margin-bottom: 15px;
padding: 15px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #ebeef5;
}
.add-detail-btn {
text-align: center;
margin-top: 10px;
}
.main-product-form {
padding: 20px;
margin-bottom: 30px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #fafafa;
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #ebeef5;
.top-tit {
display: flex;
flex-direction: column;
span:first-child {
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
span:last-child {
font-size: 14px;
color: #409eff;
}
}
}
.no-product-selected {
padding: 80px 20px;
text-align: center;
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 200px;
}
}
// 详情:为产品表格行添加悬停效果
.product-table {
/deep/ .el-table__row {
cursor: pointer;
&:hover {
background-color: #f5f7fa;
}
&.current-row {
background-color: #ecf5ff;
}
}
}

View File

@ -81,7 +81,7 @@
</template> </template>
<script> <script>
import { reqNavList } from "@/api/newHome"; import { reqNavList, reqNewHomeSync, reqNewHomeFestival } from "@/api/newHome";
export default { export default {
name: "ProductServicePage", name: "ProductServicePage",
@ -125,6 +125,11 @@ export default {
); );
return subItem && subItem.thrMenu ? subItem.thrMenu : []; return subItem && subItem.thrMenu ? subItem.thrMenu : [];
},
//
loginState() {
const userId = sessionStorage.getItem('userId');
return userId !== null && userId !== 'null' && userId !== '';
} }
}, },
async mounted() { async mounted() {
@ -134,13 +139,11 @@ export default {
methods: { methods: {
// key // key
getSubItemKey(subItem) { getSubItemKey(subItem) {
// 使ID
return `${subItem.id}_${this.activeCategory}_${subItem.secTitle}`; return `${subItem.id}_${this.activeCategory}_${subItem.secTitle}`;
}, },
// key // key
getThirdItemKey(thirdItem) { getThirdItemKey(thirdItem) {
// 使IDID
return `${thirdItem.id}_${this.activeSubId}_${thirdItem.thrTitle}`; return `${thirdItem.id}_${this.activeSubId}_${thirdItem.thrTitle}`;
}, },
@ -326,9 +329,35 @@ export default {
return descriptions[product.name] || '专业的云服务产品,提供稳定可靠的服务'; return descriptions[product.name] || '专业的云服务产品,提供稳定可靠的服务';
}, },
//
async handleAliyunProductClick() {
try {
//
const syncResponse = await reqNewHomeSync();
if (!syncResponse.status) {
this.$message.warning(syncResponse.msg || '同步失败,请稍后重试');
return;
}
//
const festivalResponse = await reqNewHomeFestival();
if (festivalResponse.status && festivalResponse.data) {
window.open(festivalResponse.data);
} else {
this.$message.warning(festivalResponse.msg || '获取跳转链接失败');
}
} catch (error) {
console.error('阿里云跳转失败:', error);
this.$message.error('网络错误,请稍后重试');
}
},
// //
handleProductClick(product) { async handleProductClick(product) {
// console.log('点击产品:', product);
const userId = sessionStorage.getItem('userId'); const userId = sessionStorage.getItem('userId');
if (product.type === '百度云') { if (product.type === '百度云') {
@ -354,8 +383,9 @@ export default {
}); });
} }
} else if (product.type === '阿里云') { } else if (product.type === '阿里云') {
if (userId) { if (userId) {
window.open(product.url); await this.handleAliyunProductClick();
} else { } else {
this.$router.push({ this.$router.push({
path: "/login", path: "/login",
@ -397,7 +427,6 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
/* 样式保持不变 */
.product-service-page { .product-service-page {
margin: 0 auto; margin: 0 auto;
padding: 24px; padding: 24px;
@ -561,7 +590,7 @@ export default {
line-height: 1.5; line-height: 1.5;
margin-bottom: 16px; margin-bottom: 16px;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; // -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
} }

View File

@ -41,17 +41,28 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="expiretime" label="到期时间" min-width="120">
<template slot-scope="scope">
<span class="time">{{ scope.row.expiretime }}</span>
</template>
</el-table-column>
<el-table-column prop="days" label="剩余天数" min-width="80"> <el-table-column prop="days" label="剩余天数" min-width="80">
<template slot-scope="scope"> <template slot-scope="scope">
<span :class="scope.row.days < 3 ? 'critical' : ''">{{ scope.row.days }}</span> <span :class="scope.row.days < 3 ? 'critical' : ''">{{ scope.row.days }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="80">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="goBaiDu(scope.row.list_url)">
续费
</el-button>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
</div> </div>
</div> </div>
<!-- 右侧部分保持不变 -->
<div class="rightBox"> <div class="rightBox">
<div class="user card"> <div class="user card">
<div class="userImg"> <div class="userImg">
@ -64,7 +75,8 @@
<p><i class="el-icon-phone"></i> 手机号: {{ userInfo.mobile }}</p> <p><i class="el-icon-phone"></i> 手机号: {{ userInfo.mobile }}</p>
<p><i class="el-icon-message"></i> 邮箱: {{ userInfo.email }}</p> <p><i class="el-icon-message"></i> 邮箱: {{ userInfo.email }}</p>
</div> </div>
</div>· </div> </div>
</div>
<div class="price card"> <div class="price card">
<div class="title">账户余额</div> <div class="title">账户余额</div>
@ -128,8 +140,6 @@ export default Vue.extend({
}, },
viewList: [], viewList: [],
navList: [], navList: [],
// data mybalance
mybalance: 0,
todoList: [ todoList: [
{ name: '待支付', count: 0 }, { name: '待支付', count: 0 },
{ name: '待续费', count: 0 }, { name: '待续费', count: 0 },
@ -137,12 +147,10 @@ export default Vue.extend({
{ name: '站内信', count: 0 } { name: '站内信', count: 0 }
], ],
messageCenterVisible: false, messageCenterVisible: false,
//
navIcons: [icon1, icon2, icon3, icon4] navIcons: [icon1, icon2, icon3, icon4]
} }
}, },
created() { created() {
this.initMybalance(); this.initMybalance();
this.getUnreadMsgCount(); this.getUnreadMsgCount();
this.fetchTodoCount(); this.fetchTodoCount();
@ -168,6 +176,16 @@ export default Vue.extend({
}) })
}, },
methods: { methods: {
goBaiDu(listUrl) {
this.$store.commit('setRedirectUrl', listUrl);
localStorage.setItem('redirectUrl', listUrl);
this.$router.push({
name: 'baiduProductShow',
params: {
listUrl: listUrl,
}
});
},
goBaidu(item) { goBaidu(item) {
this.$store.commit('setRedirectUrl', item.url) this.$store.commit('setRedirectUrl', item.url)
localStorage.setItem('redirectUrl', item.url) localStorage.setItem('redirectUrl', item.url)
@ -180,9 +198,7 @@ export default Vue.extend({
} }
}) })
}, },
//
getNavIcon(index) { getNavIcon(index) {
// 使
const iconIndex = index % this.navIcons.length; const iconIndex = index % this.navIcons.length;
return this.navIcons[iconIndex]; return this.navIcons[iconIndex];
}, },
@ -190,7 +206,8 @@ export default Vue.extend({
async initMybalance() { async initMybalance() {
const res = await editReachargelogAPI() const res = await editReachargelogAPI()
if (res.status) { if (res.status) {
this.mybalance = res.data // user 使 'SETMYBANLANCE' 'user/setMybalance'
this.$store.commit('SETMYBANLANCE', res.data)
} }
}, },
async getUnreadMsgCount() { async getUnreadMsgCount() {
@ -209,12 +226,10 @@ export default Vue.extend({
this.$message.error('获取未读消息失败'); this.$message.error('获取未读消息失败');
} }
}, },
//
async fetchTodoCount() { async fetchTodoCount() {
try { try {
const res = await todoCount(); const res = await todoCount();
if (res.status && res.data) { if (res.status && res.data) {
//
this.todoList = this.todoList.map(item => { this.todoList = this.todoList.map(item => {
switch(item.name) { switch(item.name) {
case '待支付': case '待支付':
@ -240,27 +255,17 @@ export default Vue.extend({
this.openMessageCenter(); this.openMessageCenter();
} else { } else {
let query = {}; let query = {};
switch(todoName) { switch(todoName) {
case '待支付': case '待支付':
query = { query = { filterType: 'processing' };
filterType: 'processing'
};
break; break;
case '待续费': case '待续费':
query = { query = { filterType: 'pendingPayment' };
filterType: 'pendingPayment'
};
break; break;
case '处理中': case '处理中':
query = { query = { filterType: 'processing' };
filterType: 'processing'
};
break; break;
} }
console.log(`跳转到资源概览,筛选类型: ${todoName}`, query);
this.$router.push({ this.$router.push({
path: '/orderManagement/orderManagement', path: '/orderManagement/orderManagement',
query: query query: query
@ -367,7 +372,6 @@ export default Vue.extend({
justify-content: center; justify-content: center;
min-width: 0; min-width: 0;
//
.nav-icon { .nav-icon {
width: 24px; width: 24px;
height: 24px; height: 24px;
@ -375,13 +379,6 @@ export default Vue.extend({
object-fit: contain; object-fit: contain;
} }
//
// i {
// font-size: 24px;
// margin-bottom: 10px;
// color: #409eff;
// }
&:hover { &:hover {
transform: translateY(-3px); transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2); box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);

View File

@ -31,7 +31,8 @@ class BaiduSMS:
# 替换为您的百度短信签名名称 # 替换为您的百度短信签名名称
# self.signature_id = 'sms-sign-qQHYeC17077' # 开元云科技 # self.signature_id = 'sms-sign-qQHYeC17077' # 开元云科技
# self.signature_id = 'sms-sign-BqOhYB33019' # 开元云 # self.signature_id = 'sms-sign-BqOhYB33019' # 开元云
self.signature_id = 'sms-sign-LOShPq75464' # 开元云北京 # self.signature_id = 'sms-sign-LOShPq75464' # 开元云北京
self.signature_id = 'sms-sign-xQYUwp42637' # 开元云北京
# 短信模板类型映射键为业务类型值为对应模板ID # 短信模板类型映射键为业务类型值为对应模板ID
self.sms_types = { self.sms_types = {
"注册登录验证": "sms-tpl-123", # 示例模板ID "注册登录验证": "sms-tpl-123", # 示例模板ID