main #6

Merged
charles merged 24 commits from main into prod 2025-08-12 19:58:39 +08:00
66 changed files with 7969 additions and 788 deletions

View File

@ -90,7 +90,9 @@ async def email_info(msg, indent=0):
index = find_data.index("<")
name = find_data[:index]
if price and name:
mail_code = await sor.R('mail_code',{'mailcode':name,'del_flg':'0'})
mail_code_sql = """SELECT * FROM mail_code WHERE LOCATE(mailcode, name) > 0 and del_flg = '0';"""
mail_code = await sor.sqlExe(mail_code_sql, {})
# mail_code = await sor.R('mail_code',{'mailcode':name,'del_flg':'0'})
date = await get_business_date(sor=None)
recharge_log = {'customerid': mail_code[0]['customer_id'], 'recharge_amt': price,
'action': 'RECHARGE', 'recharge_path': '2', 'recharge_date': date}

View File

@ -0,0 +1,46 @@
async def company_category_add(ns={}):
if not ns.get('url_link'):
return {
'status': False,
'msg': '请传递url_link'
}
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
if 'localhost' in domain_name:
domain_name = 'dev.opencomputing.cn'
ns_dic = {
'id': uuid(),
'domain_name': domain_name,
'company_category': ns.get('company_category')
}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
if ns.get('userid'):
userid = ns.get('userid')
else:
userid = await get_user()
# 区分运营和普通客户
user_list = await sor.R('users', {'id': userid})
if not user_list:
return {
'status': False,
'msg': '没有找到匹配的用户'
}
orgid = user_list[0]['orgid']
ns_dic['orgid'] = orgid
try:
await sor.C('user_publish_company_category', ns_dic)
return {
'status': True,
'msg': 'Company category created successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to create company category, %s' % str(e)
}
ret = await company_category_add(params_kw)
return ret

View File

@ -0,0 +1,18 @@
async def company_category_delete(ns={}):
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
update_sql = """update user_publish_company_category set del_flg = '1' where id = '%s';""" % ns.get('id')
await sor.sqlExe(update_sql, {})
return {
'status': True,
'msg': 'Company category deleted successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to delete company category, %s' % str(e)
}
ret = await company_category_delete(params_kw)
return ret

View File

@ -0,0 +1,27 @@
async def company_category_search(ns={}):
if not ns.get('url_link'):
return {
'status': False,
'msg': '请传递url_link'
}
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
if 'localhost' in domain_name:
domain_name = 'dev.opencomputing.cn'
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
result = await sor.R('user_publish_company_category', {'domain_name': domain_name, 'del_flg': '0'})
return {
'status': True,
'msg': 'Company categories retrieved successfully',
'data': result
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to retrieve company categories, %s' % str(e)
}
ret = await company_category_search(params_kw)
return ret

View File

@ -0,0 +1,18 @@
async def company_category_update(ns={}):
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
update_sql = """update user_publish_company_category set company_category = '%s' where id = '%s';""" % (ns.get('company_category'), ns.get('id'))
await sor.sqlExe(update_sql, {})
return {
'status': True,
'msg': 'Company category updated successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to update company category, %s' % str(e)
}
ret = await company_category_update(params_kw)
return ret

View File

@ -0,0 +1,48 @@
async def product_category_add(ns={}):
if not ns.get('url_link'):
return {
'status': False,
'msg': '请传递路由链接'
}
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
if 'localhost' in domain_name:
domain_name = 'dev.opencomputing.cn'
ns_dic = {
'id': uuid(),
'domain_name': domain_name,
'product_category': ns.get('product_category'),
'parentid': ns.get('parentid') if ns.get('parentid') else None,
'priority': ns['priority'] if ns.get('priority') else 99,
'permission': ns.get('permission') if ns.get('permission') else '1',
'cart_flag': ns.get('cart_flag') if ns.get('cart_flag') else None
}
if ns.get('userid'):
userid = ns.get('userid')
else:
userid = await get_user()
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# 区分运营和普通客户
user_list = await sor.R('users', {'id': userid})
if not user_list:
return {
'status': False,
'msg': '没有找到匹配的用户'
}
orgid = user_list[0]['orgid']
ns_dic['orgid'] = orgid
try:
await sor.C('user_publish_product_category', ns_dic)
return {
'status': True,
'msg': 'product category created successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to create product category, %s' % str(e)
}
ret = await product_category_add(params_kw)
return ret

View File

@ -0,0 +1,18 @@
async def product_category_delete(ns={}):
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
update_sql = """update user_publish_product_category set del_flg = '1' where id = '%s';""" % ns.get('id')
await sor.sqlExe(update_sql, {})
return {
'status': True,
'msg': 'product category deleted successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to delete product category, %s' % str(e)
}
ret = await product_category_delete(params_kw)
return ret

View File

@ -0,0 +1,85 @@
async def build_tree(parent_id=None, all_cats=None):
return [{
"id": cat['id'],
"name": cat['product_category'],
"cart_flag": cat['cart_flag'],
"children": await build_tree(cat['id'], all_cats)
} for cat in all_cats if cat['parentid'] == parent_id]
async def get_user_role(ns={}):
sor = ns['sor']
# get role
ns['del_flg'] = '0'
res_role = await sor.R('userrole', ns)
if res_role:
user_role = res_role[0]
else:
return {
"status": False,
"msg": "userrole table, user id can not find..."
}
roleid = user_role.get('roleid')
# get role name
role_name = await sor.R('role', {'id': roleid})
if role_name:
role = role_name[0].get('role')
else:
return {
"status": False,
"msg": "role table, can not get role name"
}
return role
async def product_category_search(ns={}):
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
if 'localhost' in domain_name:
domain_name = 'dev.opencomputing.cn'
to_page = ns.get('to_page')
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# 首页不登录 通过域名筛选
if to_page == 'show':
find_sql = """SELECT * FROM user_publish_product_category WHERE domain_name = '%s' AND del_flg = '0' ORDER BY priority;""" % domain_name
result = await sor.sqlExe(find_sql, {})
return {
'status': True,
'msg': 'product categories retrieved successfully',
'data': result
}
# 发布弹窗中的产品种类通过用户筛选 普通用户/管理人员
if ns.get('userid'):
userid = ns.get('userid')
else:
userid = await get_user()
# 区分运营和普通客户
user_list = await sor.R('users', {'id': userid})
if not user_list:
return {
'status': False,
'msg': '没有找到匹配的用户'
}
orgid = user_list[0]['orgid']
user_role = await get_user_role({'userid': userid, 'sor': sor})
try:
if user_role == '客户':
find_sql = """SELECT * FROM user_publish_product_category WHERE domain_name = '%s' AND permission = '1' AND del_flg = '0' ORDER BY priority;""" % domain_name
else:
find_sql = """SELECT * FROM user_publish_product_category WHERE domain_name = '%s' AND del_flg = '0' ORDER BY priority;""" % domain_name
result = await sor.sqlExe(find_sql, {})
# res = await build_tree(parent_id=None, all_cats=result)
return {
'status': True,
'msg': 'product categories retrieved successfully',
'data': result
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to retrieve product categories, %s' % str(e)
}
ret = await product_category_search(params_kw)
return ret

View File

@ -0,0 +1,18 @@
async def product_category_update(ns={}):
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
update_sql = """update user_publish_product_category set product_category = '%s' where id = '%s';""" % (ns.get('product_category'), ns.get('id'))
await sor.sqlExe(update_sql, {})
return {
'status': True,
'msg': 'product category updated successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to update product category, %s' % str(e)
}
ret = await product_category_update(params_kw)
return ret

View File

@ -0,0 +1,107 @@
async def get_user_role(ns={}):
sor = ns['sor']
# get role
ns['del_flg'] = '0'
res_role = await sor.R('userrole', ns)
if res_role:
user_role = res_role[0]
else:
return {
"status": False,
"msg": "userrole table, user id can not find..."
}
roleid = user_role.get('roleid')
# get role name
role_name = await sor.R('role', {'id': roleid})
if role_name:
role = role_name[0].get('role')
else:
return {
"status": False,
"msg": "role table, can not get role name"
}
return role
async def publish_product_add(ns={}):
if ns.get('userid'):
userid = ns.get('userid')
else:
userid = await get_user()
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# 区分运营和普通客户
user_list = await sor.R('users', {'id': userid})
if not user_list:
return {
'status': False,
'msg': '没有找到匹配的用户'
}
orgid = user_list[0]['orgid']
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
if 'localhost' in domain_name:
domain_name = 'dev.opencomputing.cn'
product_category = ns.get('product_category')
company_type = ns.get('company_type')
ns_dic = {
"id": uuid(),
"publish_type": ns.get("publish_type"),
"orgid": orgid,
"img": ns.get('img'),
"domain_name": domain_name,
"product_name": ns.get("product_name"),
"product_category": product_category,
"company_name": ns.get("company_name"),
"company_type": json.dumps(company_type) if isinstance(company_type, list) else company_type,
"contact_person": ns.get("contact_person"),
"job_title": ns.get("job_title"),
"phone_number": ns.get("phone_number"),
"email": ns.get("email"),
"cpu": ns.get("cpu"),
"memory": ns.get("memory"),
"gpu": ns.get("gpu"),
"sys_disk": ns.get("sys_disk"),
"data_disk": ns.get("data_disk"),
"net_card": ns.get("net_card"),
"price": ns.get("price"),
"unit": ns.get("unit"),
"discount": ns.get("discount"),
"short_term": ns.get("short_term"),
"priority": ns.get("priority") if ns.get("priority") else 1,
"status": ns.get("status") if ns.get("status") else '0',
"title": ns.get("title"),
"label": ns.get("label"),
"first_page": ns.get("first_page") if ns.get("first_page") else '0',
"requirement_summary": ns.get("requirement_summary"),
"related_parameters": ns.get("related_parameters"),
"application_scenario": ns.get("application_scenario"),
"audit_status": ns.get('audit_status', 'pending'),
}
if ns.get('discount') == '0':
ns['discount'] = None
ns_dic['discount'] = None
if ns.get('price') and ns.get('discount'):
ns_dic['discount_price'] = float(ns.get('price')) * float(ns['discount']) / 10
else:
ns_dic['discount_price'] = ns['price']
user_role = await get_user_role({'userid': userid, 'sor': sor})
# 非客户角色不需要审批
if user_role != '客户':
ns['status'] = '1'
ns_dic['audit_status'] = 'approved'
ns_dic['first_page'] = '1'
try:
await sor.C('user_publish_product', ns_dic)
return {
'status': True,
'msg': 'publish product created successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to publish product, %s' % str(e)
}
ret = await publish_product_add(params_kw)
return ret

View File

@ -0,0 +1,68 @@
async def publish_product_search(ns={}):
"""
普通客户查看
运营查看自己提交内容
运营查看所有用户内容
:param ns:
:return:
"""
offset = ns['offset']
flag = ns.get('flag')
if ns.get('userid'):
userid = ns.get('userid')
else:
userid = await get_user()
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# 区分运营和普通客户
user_list = await sor.R('users', {'id': userid})
if not user_list:
return {
'status': False,
'msg': '没有找到匹配的用户'
}
orgid = user_list[0]['orgid']
org_parentid_li = await sor.R('organization', {'id': orgid})
org_parentid = org_parentid_li[0]['parentid']
user_role = await get_user_role({'userid': userid, 'sor': sor})
try:
# 非客户角色
if user_role != '客户':
ns['del_flg'] = '0'
# 业主机构角色并且是只查看业主机构自己 flag==single
if orgid == 'mIWUHBeeDM8mwAFPIQ8pS' and flag == 'single':
find_sql = """SELECT upr.* FROM user_publish_product AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE org.parentid IS NULL AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % offset
# 业主机构角色并且是查看所有(包括业主机构自己) flag!=single
elif orgid == 'mIWUHBeeDM8mwAFPIQ8pS':
find_sql = """SELECT upr.* FROM user_publish_product AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE org.id = '%s' or org.parentid = '%s' AND org.org_type != '1' AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, orgid, offset)
# 其他机构非用户角色 只查看自己
elif flag == 'single':
find_sql = """SELECT upr.* FROM user_publish_product AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE org.id = '%s' AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, offset)
# 其他机构非用户角色查看所有
else:
find_sql = """SELECT upr.* FROM user_publish_product AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE (org.id = '%s' or org.parentid = '%s') AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, orgid, offset)
# 客户角色
else:
ns['del_flg'] = '0'
ns['orgid'] = user_list[0]['id']
find_sql = """SELECT * FROM user_publish_product WHERE orgid = '%s' AND del_flg = '0' ORDER BY create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, offset)
result = await sor.sqlExe(find_sql, {})
return {
'status': True,
'msg': 'Requirements retrieved successfully',
'data': result
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to retrieve requirements, %s' % str(e)
}
ret = await publish_product_search(params_kw)
return ret

View File

@ -0,0 +1,83 @@
async def publish_product_search_first_page(ns={}):
"""
普通客户查看
运营查看自己提交内容
运营查看所有用户内容
:param ns:
:return:
"""
publish_type = ns.get('publish_type')
page_size = int(ns['page_size']) if ns.get('page_size') else 8
current_page_param = int(ns['current_page']) if ns.get('current_page') else 1
current_page = (current_page_param - 1) * page_size
to_page = ns.get('to_page') if ns.get('to_page') else 'first_page'
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
company_type = ns.get('company_type')
product_category = ns.get('product_category')
if 'localhost' in domain_name:
domain_name = 'dev.opencomputing.cn'
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
ns['del_flg'] = '0'
if to_page == 'first_page':
count_sql = """SELECT COUNT(*) AS total_count FROM user_publish_product WHERE domain_name = '%s' AND first_page = '1' AND audit_status = 'approved' AND del_flg = '0' AND publish_type = '%s' ORDER BY create_at DESC;""" % (domain_name, publish_type)
find_sql = """SELECT upp.* FROM user_publish_product AS upp INNER JOIN user_publish_product_category AS uppc ON upp.product_category LIKE uppc.id WHERE upp.domain_name = '%s' AND upp.first_page = '1' AND upp.audit_status = 'approved' AND upp.del_flg = '0' AND upp.publish_type = '%s' ORDER BY uppc.priority ASC, upp.create_at DESC LIMIT %s OFFSET %s;""" % (domain_name, publish_type, page_size, current_page)
elif to_page == 'square' and product_category:
count_sql = """SELECT COUNT(*) AS total_count FROM user_publish_product WHERE domain_name = '%s' AND product_category LIKE """ % domain_name + """'%%""" + product_category + """%%'""" + """ AND audit_status = 'approved' AND del_flg = '0' AND publish_type = '%s' ORDER BY create_at DESC;""" % publish_type
find_sql = """SELECT * FROM user_publish_product WHERE domain_name = '%s' AND product_category LIKE """ % domain_name + """'%%""" + product_category + """%%'""" + """ AND audit_status = 'approved' AND del_flg = '0' AND publish_type = '%s' ORDER BY create_at DESC LIMIT %s OFFSET %s;""" % (publish_type, page_size, current_page)
elif to_page == 'square':
count_sql = """SELECT COUNT(*) AS total_count FROM user_publish_product WHERE domain_name = '%s' AND audit_status = 'approved' AND del_flg = '0' AND publish_type = '%s' ORDER BY create_at DESC;""" % (domain_name, publish_type)
find_sql = """SELECT * FROM user_publish_product WHERE domain_name = '%s' AND audit_status = 'approved' AND del_flg = '0' AND publish_type = '%s' ORDER BY create_at DESC LIMIT %s OFFSET %s;""" % (domain_name, publish_type, page_size, current_page)
else:
count_sql = ''
find_sql = ''
total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count']
result = await sor.sqlExe(find_sql, {})
category_all = await sor.R('user_publish_product_category', {})
product_dic = {}
for res in result:
# 公司类别筛选
if company_type:
list1 = [item.strip() for item in company_type.split(',')]
list2 = [item.strip() for item in res['company_type'].split(',')]
if not bool(set(list1) & set(list2)):
continue
res['img'] = 'https://' + domain_name + '/idfile?path=' + res['img']
# 电话和邮箱模糊化处理
if res.get('phone_number'):
res['phone_number'] = res['phone_number'][:3] + '****' + res['phone_number'][7:]
else:
res['phone_number'] = '198****8601'
if res.get('email'):
res['email'] = res['email'][:2] + '****' + res['email'][6:]
else:
res['email'] = 'kawa@****.com'
category_id = res['product_category'].split(',')[0]
category_name_li = [item['product_category'] for item in category_all if item['id'] == category_id]
category_name = category_name_li[0] if category_name_li else '其他'
if product_dic.get(category_name):
product_dic[category_name].append(res)
else:
product_dic[category_name] = [res]
product_list = []
for key, value in product_dic.items():
product_list.append({'id': uuid(), 'name': key, 'total_count': total_count, 'page_size': page_size, 'current_page': current_page_param, 'product_list': value})
return {
'status': True,
'msg': 'Requirements retrieved successfully',
'data': product_list
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to retrieve requirements, %s' % str(e)
}
ret = await publish_product_search_first_page(params_kw)
return ret

View File

@ -0,0 +1,72 @@
async def requirement_add(ns={}):
if ns.get('userid'):
userid = ns.get('userid')
else:
userid = await get_user()
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# 区分运营和普通客户
user_list = await sor.R('users', {'id': userid})
if not user_list:
return {
'status': False,
'msg': '没有找到匹配的用户'
}
orgid = user_list[0]['orgid']
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
if 'localhost' in domain_name:
domain_name = 'dev.opencomputing.cn'
product_category = ns.get('product_category')
company_type = ns.get('company_type')
ns_dic = {
"id": uuid(),
"orgid": orgid,
"domain_name": domain_name,
"requirement_name": ns.get("requirement_name"),
"product_category": product_category,
"company_name": ns.get("company_name"),
"company_type": json.dumps(company_type) if isinstance(company_type, list) else company_type,
"contact_person": ns.get("contact_person"),
"job_title": ns.get("job_title"),
"phone_number": ns.get("phone_number"),
"email": ns.get("email"),
"cpu": ns.get("cpu"),
"memory": ns.get("memory"),
"gpu": ns.get("gpu"),
"sys_disk": ns.get("sys_disk"),
"data_disk": ns.get("data_disk"),
"net_card": ns.get("net_card"),
"price": ns.get("price"),
"priority": ns.get("priority") if ns.get("priority") else 1,
"status": ns.get("status") if ns.get("status") else '0',
"title": ns.get("title"),
"label": ns.get("label"),
"first_page": ns.get("first_page") if ns.get("first_page") else '0',
"requirement_summary": ns.get("requirement_summary"),
"related_parameters": ns.get("related_parameters"),
"application_scenario": ns.get("application_scenario"),
"audit_status": ns.get('audit_status', 'pending'),
}
user_role = await get_user_role({'userid': userid, 'sor': sor})
# 非客户角色不需要审批
if user_role != '客户':
ns['status'] = '1'
ns_dic['audit_status'] = 'approved'
ns_dic['first_page'] = '1'
try:
await sor.C('user_publish_requirement', ns_dic)
return {
'status': True,
'msg': 'Requirement created successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to create requirement, %s' % str(e)
}
ret = await requirement_add(params_kw)
return ret

View File

@ -0,0 +1,21 @@
async def requirement_delete(ns={}):
ns_dic = {
'id': ns.get('id'),
'del_flg': '1'
}
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
await sor.U('user_publish_requirement', ns_dic)
return {
'status': True,
'msg': 'Requirement delete successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to delete requirement, %s' % str(e)
}
ret = await requirement_delete(params_kw)
return ret

View File

@ -0,0 +1,93 @@
async def get_user_role(ns={}):
sor = ns['sor']
# get role
ns['del_flg'] = '0'
res_role = await sor.R('userrole', ns)
if res_role:
user_role = res_role[0]
else:
return {
"status": False,
"msg": "userrole table, user id can not find..."
}
roleid = user_role.get('roleid')
# get role name
role_name = await sor.R('role', {'id': roleid})
if role_name:
role = role_name[0].get('role')
else:
return {
"status": False,
"msg": "role table, can not get role name"
}
return role
async def requirement_search(ns={}):
"""
普通客户查看
运营查看自己提交内容
运营查看所有用户内容
:param ns:
:return:
"""
offset = ns['offset']
flag = ns.get('flag')
if ns.get('userid'):
userid = ns.get('userid')
else:
userid = await get_user()
db = DBPools()
async with db.sqlorContext('kboss') as sor:
# 区分运营和普通客户
user_list = await sor.R('users', {'id': userid})
if not user_list:
return {
'status': False,
'msg': '没有找到匹配的用户'
}
orgid = user_list[0]['orgid']
org_parentid_li = await sor.R('organization', {'id': orgid})
org_parentid = org_parentid_li[0]['parentid']
user_role = await get_user_role({'userid': userid, 'sor': sor})
try:
# 非客户角色
if user_role != '客户':
ns['del_flg'] = '0'
# 业主机构角色并且是只查看业主机构自己 flag==single
if orgid == 'mIWUHBeeDM8mwAFPIQ8pS' and flag == 'single':
find_sql = """SELECT upr.* FROM user_publish_requirement AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE org.parentid IS NULL AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % offset
# 业主机构角色并且是查看所有(包括业主机构自己) flag!=single
elif orgid == 'mIWUHBeeDM8mwAFPIQ8pS':
find_sql = """SELECT upr.* FROM user_publish_requirement AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE org.id = '%s' or org.parentid = '%s' AND org.org_type != '1' AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, orgid, offset)
# 其他机构非用户角色 只查看自己
elif flag == 'single':
find_sql = """SELECT upr.* FROM user_publish_requirement AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE org.id = '%s' AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, offset)
# 其他机构非用户角色查看所有
else:
find_sql = """SELECT upr.* FROM user_publish_requirement AS upr LEFT JOIN organization AS org ON upr.orgid = org.id
WHERE (org.id = '%s' or org.parentid = '%s') AND upr.del_flg = '0' ORDER BY upr.create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, orgid, offset)
# 客户角色
else:
ns['del_flg'] = '0'
ns['orgid'] = user_list[0]['id']
find_sql = """SELECT * FROM user_publish_requirement WHERE orgid = '%s' AND del_flg = '0' ORDER BY create_at DESC LIMIT 20 OFFSET %s;""" % (orgid, offset)
result = await sor.sqlExe(find_sql, {})
return {
'status': True,
'msg': 'Requirements retrieved successfully',
'data': result
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to retrieve requirements, %s' % str(e)
}
ret = await requirement_search(params_kw)
return ret

View File

@ -0,0 +1,23 @@
async def requirement_update(ns={}):
product_category = ns.get('product_category')
company_type = ns.get('company_type')
if product_category:
ns['product_category'] = json.dumps(product_category) if isinstance(product_category, list) else product_category
if company_type:
ns['company_type'] = json.dumps(company_type) if isinstance(company_type, list) else company_type
db = DBPools()
async with db.sqlorContext('kboss') as sor:
try:
await sor.U('user_publish_requirement', ns)
return {
'status': True,
'msg': 'Requirement updated successfully'
}
except Exception as e:
return {
'status': False,
'msg': 'Failed to update requirement, %s' % str(e)
}
ret = await requirement_update(params_kw)
return ret

View File

@ -60,7 +60,7 @@ async def get_ipc_logo(ns={}):
# [{'name': '地址', 'value': '北京市朝阳区农展馆南路13号瑞辰国际'},{'name': '电话', 'value': '400-6150805 010-65917875'},{'name': '邮箱', 'value': 'Open-computing@kaiyuancloud.cn'},{'name': 'IPC备案号', 'value': '京ICP备2022001945号-2'},{'name': '版权所有', 'value': '@kaiyuanyun 2023'},{'name': '京公网安备', 'value': '11010502054007'},{'name': '经营许可证', 'value': '京B2-20232313'}]
yezhu = json.loads(domain_res.replace("'", '"')) if not isinstance(domain_res, dict) else domain_res
yezhu['domain_name'] = domain_url
if 'ncmatch' in domain_url or '9527' in domain_url:
if 'ncmatch' in domain_url or '9527' in domain_url or 'opencomputing' in domain_url:
yezhu['logo'] = "https://www.kaiyuancloud.cn/idfile?path=logo_ncmatch.png"
yezhu['additional_msg'] = {
"home": {
@ -69,8 +69,8 @@ async def get_ipc_logo(ns={}):
"adress": "北京市石景山区和平西路60号院1号楼11层1101-30",
"footerTitle": "开元数智(北京)科技有限公司",
'qrCode': 'https://www.kaiyuancloud.cn/idfile?path=firstpagehot/ncmatch_inquiry.jpg',
'footer_info': '京ICP备2022001945号-4 开元数智(北京)科技有限公司 版权所有 @kaiyuanyun 2023',
"copyright": "@kaiyuanyun 2023",
'footer_info': '京ICP备2022001945号-4 开元数智(北京)科技有限公司',
"copyright": "",
"domain_name": "ncmatch.cn",
"email": "Open-computing@kaiyuancloud.cn",
"license": "2022001945号-4",

View File

@ -51,6 +51,7 @@
"uuid": "^9.0.0",
"vue": "2.6.10",
"vue-count-to": "^1.0.13",
"vue-cropper": "^0.6.5",
"vue-device-detector": "^1.1.6",
"vue-infinite-scroll": "^2.0.2",
"vue-router": "^3.0.2",

View File

@ -0,0 +1,91 @@
import request from "@/utils/request";
//获取商品分类
export function reqGetProductCategorySearch(data) {
return request({
url: '/product/product_category_search.dspy',
method: 'post',
headers: {
'Content-Type': 'application/json'
},
data
})
}
//添加产品 publish_product_add
export function reqPublishProductAdd(data) {
return request({
url: '/product/publish_product_add.dspy',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data
})
}
//获公司类型 /product/company_category_search.dspy
export function reqCompanyCategorySearch(data) {
return request({
url: '/product/company_category_search.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}
//获取全部产品/product/publish_product_search_first_page.dspy
export function reqPublishProductSearchFirstPage(data) {
return request({
url: '/product/publish_product_search_first_page.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}
//算力供需广场 获取列表
export function reqGetSupplyAndDemandSquareList(data) {
return request({
url: '/product/publish_product_search_first_page.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}
//获取商品详情
export function reqGetProductDetail(data) {
return request({
url: '/product/publish_product_search_detail.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}
//提交审批 ///user/enterprise_audit_info_add.dspy
export function reqApproveUser(data){
return request({
url: '/user/enterprise_audit_info_add.dspy',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
data
})
}
//获取菜单ncmatch菜单 menu
export function reqNcMatchMenu(data){
return request({
url: '/product/homepage_category_tree_search.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}
//运营查找商品|需求
export function reqSearchByMangement(data){
return request({
url: '/product/publish_product_search.dspy',
method: 'post',
headers: { 'Content-Type': 'application/json' },
data
})
}

View File

@ -7,6 +7,7 @@ import {getToken} from "@/utils/auth"; // get token from cookie
import getPageTitle from "@/utils/get-page-title";
import {asyncRoutes, constantRoutes} from "@/router";
import Layout from "@/layout";
import {getHomePath} from "@/views/setting/tools";
NProgress.configure({showSpinner: false}); // NProgress Configuration
@ -74,6 +75,15 @@ router.beforeEach(async (to, from, next) => {
const userid = sessionStorage.getItem("userId");
const user = sessionStorage.getItem("user");
const auths = sessionStorage.getItem("auths");
const homePath =getHomePath()
if (to.path === homePath) {
next(); // 如果已经是目标路径,直接放行
NProgress.done();
return;
}
console.log("homePath",homePath);
// const homePath ='/homePage/index';
// const homePath ='/ncmatchHome/index';
const hasRoutes =
JSON.parse(sessionStorage.getItem("routes")) &&
JSON.parse(sessionStorage.getItem("routes")).length
@ -86,8 +96,8 @@ router.beforeEach(async (to, from, next) => {
NProgress.done();
return
}
if (to.path === '/homePage') {
next('/homePage/index');
if (to.path === '/homePage'||to.path === '/ncmatchHome') {
next(homePath);
NProgress.done();
return
}
@ -96,7 +106,7 @@ router.beforeEach(async (to, from, next) => {
// NProgress.done();
// return
// }
if (to.path.includes("/kyyForm") || to.path.includes("/screen") || to.path.includes("/beforeLogin") || to.path.includes("/wxDetailPage") || to.path.includes("/wxPage") || to.path.includes("/login") || to.path.includes("/homePage") || to.path.includes("/registrationPage") || to.path.includes("/payPage") || to.path.includes("/paySuccess") || to.path.includes("/homePageImage")) {
if (to.path.includes("/ncmatchHome")||to.path.includes("/kyyForm") || to.path.includes("/screen") || to.path.includes("/beforeLogin") || to.path.includes("/wxDetailPage") || to.path.includes("/wxPage") || to.path.includes("/login") || to.path.includes("/homePage") || to.path.includes("/registrationPage") || to.path.includes("/payPage") || to.path.includes("/paySuccess") || to.path.includes("/homePageImage")) {
console.log("to", to)
try {
if (to.path.includes("/beforeLogin") || to.path.includes("/registrationPage")) {
@ -125,7 +135,8 @@ router.beforeEach(async (to, from, next) => {
}
}
// next(`/login?redirect=${to.path}`);
next('/homePage/index')
console.log("路由执行了2@@@")
next(homePath)
// next(`/beforeLogin`);
// console.log("111行被打印了")
NProgress.done();
@ -154,7 +165,7 @@ router.beforeEach(async (to, from, next) => {
// console.log(error);
Message.error(error || "Has Error");
// next(`/login?redirect=${to.path}`);
next('/homePage/index')
next(homePath)
// console.log("137行被打印了")
NProgress.done();
}

View File

@ -1,19 +1,8 @@
import Vue from "vue";
import Router from "vue-router";
import store from "@/store";
Vue.use(Router);
// const originalPush = Router.prototype.push
// Router.prototype.push = function push(location) {
// return originalPush.call(this, location).catch(err => err)}
/* Layout */
import Layout from "@/layout";
import {loginUserAPI} from "@/api/login";
import {Message} from "element-ui";
import {logger} from "runjs/lib/common";
import GpuProduct from "@/views/product/productHome/capitalOnline/productItem/GpuProduct/index.vue";
import CreatePrivateNet from "@/views/product/productHome/capitalOnline/Net/privateNet/createPrivateNet/index.vue";
import ShowPrivateNet from "@/views/product/productHome/capitalOnline/Net/privateNet/showPrivateNet/index.vue";
@ -29,6 +18,13 @@ import SecurityGroupDetail
import ShowGpu from "@/views/product/productHome/capitalOnline/productItem/GpuProduct/ShowGpu/index.vue";
import ShowEip from "@/views/product/productHome/capitalOnline/Net/Eip/showEip/index.vue";
import CreateEip from "@/views/product/productHome/capitalOnline/Net/Eip/createEip/index.vue";
import { getHomePath } from '@/views/setting/tools'
Vue.use(Router);
// const originalPush = Router.prototype.push
// Router.prototype.push = function push(location) {
// return originalPush.call(this, location).catch(err => err)}
// import nestedRouter from '@/router/modules/nested'
/**
* Note: sub-menu only appear when route children.length >= 1
@ -68,7 +64,28 @@ export const constantRoutes = [
title: 'beforeLogin',
component: () => import('@/views/beforeLogin/index.vue'),
hidden: true
}, {
},
{
hidden: true,
alwaysShow: true,
path: "/productMangement",
component: Layout,
name: "productMangement",
redirect: "/productMangement/index",
meta: { fullPath: "/productMangement", title: "商品管理", noCache: true, icon: 'el-icon-s-home' },
children: [
{
path: "index",
component: () => import("@/views/customer/productMangement/productList/index.vue"),
name: "productList",
meta: { title: "商品清单", fullPath: "/productMangement/index" },
},
]
},
{
path: '/wxPage',
name: 'wxPage',
title: '热门产品',
@ -165,6 +182,30 @@ export const constantRoutes = [
hidden: true,
meta: { title: "注册" },
},
{
hidden: true,
path: "/ncmatchHome",
component: () => import("@/views/homePage/ncmatch/index.vue"),
name: "ncmatchHome",
redirect: "/ncmatchHome/index",
meta: { fullPath: "/ncmatchHome/index", title: "官网首页", noCache: true, icon: 'el-icon-s-home' },
children: [
{
path: "index",
component: () => import("@/views/homePage/ncmatch/mainPage/index.vue"),
name: "ncmatchPageIndex",
hidden: true,
meta: { title: "首页", fullPath: "/ncmatch/mainPage/index" },
},
{
path: "supplyAndDemandSquare",
component: () => import("@/views/homePage/ncmatch/supplyAndDemandSquare/index.vue"),
name: "supplyAndDemandSquare",
hidden: true,
meta: { title: "算力供需广场", fullPath: "/ncmatch/supplyAndDemandSquare" },
},
]
},
{
path: "/homePage",
component: () => import("@/views/homePage/indexLast.vue"),
@ -272,8 +313,9 @@ export const asyncRoutes = [
// name: 'productHome',
// meta: {title: "概览", fullPath: "/productHome", noCache: true}
// },
{
path: "/homePage",
path: getHomePath() == '/ncmatchHome/index' ? "/ncmatchHome" : "/homePage",
component: () => import("@/views/homePage/indexLast.vue"),
name: "homePage",
redirect: "/homePage/index",
@ -703,6 +745,13 @@ export const asyncRoutes = [
component: () => import("@/views/customer/workOrderManagement"),
name: "WorkOrderManagement",
meta: { title: "工单管理", fullPath: "/customer/workOrderManagement" },
},
{
hidden: true,
path: 'approve',
component: () => import('@/views/customer/ncApprove/index.vue'),
name: "Approve",
meta: { title: "信息审核", fullPath: "/customer/ncApprove" },
}, {
hidden: true,
path: "channelMangement",
@ -790,7 +839,7 @@ export const asyncRoutes = [
), name: "userResource", meta: { title: "资源实例", fullPath: "/customer/userResource", noCache: true },
},
{
hidden: true,
path: "sshTerminal",
component: () => import(
// "@/views/customer/userResource/iframeJiNan.vue"//iframe报错
@ -986,19 +1035,22 @@ export const asyncRoutes = [
), name: "supplierTransferApproval", meta: {
title: "供应商销售收入转账审批", fullPath: "/operation/examinationManagement/supplierTransferApproval",
},
}, {
},
{
path: "distributorDiscountSetting", component: () => import(
"@/views/operation/examinationManagement/distributorDiscountSetting"
), name: "DistributorDiscountSetting", meta: {
title: "分销商折扣设置审批", fullPath: "/operation/examinationManagement/distributorDiscountSetting",
},
}, {
},
{
path: "distributorRebateSetUp", component: () => import(
"@/views/operation/examinationManagement/distributorRebateSetUp"
), name: "DistributorRebateSetUp", meta: {
title: "分销商回佣率设置审批", fullPath: "/operation/examinationManagement/DistributorRebateSetUp",
},
}, {
},
{
path: "distributorCommissionTransferApproval", component: () => import(
"@/views/operation/examinationManagement/distributorCommissionTransferApproval"
), name: "DistributorCommissionTransferApproval", meta: {
@ -1371,7 +1423,8 @@ export const asyncRoutes = [
title: "Test", icon: "el-icon-shopping-cart-2", noCache: true, fullPath: "/Test/index",
},
},],
}, {
},
{
path: "/operationAndMaintenance", component: Layout, redirect: "/operationAndMaintenance/index", meta: {
title: "运维", icon: "el-icon-s-tools", noCache: true, fullPath: "/operationAndMaintenance",
}, children: [{

View File

@ -0,0 +1,33 @@
const state = {
showProductDetail: false,
// 产品详情
productDetail: { },
//是否是审核
isAudit: false,
}
const mutations = {
SETPRODUCTDETAIL(state, productDetail) {
state.productDetail = productDetail
},
GETPRODUCTDETAIL(state) {
return state.productDetail
},
SHOWPRODUCTDETAIL(state, showProductDetail) {
state.showProductDetail = showProductDetail
},
GETSHOWPRODUCTDETAIL(state) {
return state.showProductDetail
}
}
const actions = {
}
const getters = {}
export default {
state,
actions,
mutations,
getters
}

View File

@ -5,6 +5,7 @@ import {Message} from "element-ui";
import router, {resetRouter} from "@/router";
import store from "@/store";
import {myBalanceAPI} from "@/api/finance/customerRechargeManagement";
import {testData} from "@/views/homePage/components/topBox/testData";
const state = {
token: getToken(),
@ -86,7 +87,6 @@ const actions = {
}
)
resetRouter();
console.log("挂载前的路由是:", accessRoutes)
router.addRoutes(accessRoutes);
resolve(response);
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,945 @@
<template>
<div class="nc-approve-container">
<el-card class="approve-card">
<div slot="header" class="card-header">
<span class="header-title">
<i class="el-icon-document"></i>
信息审核
</span>
<el-button type="primary" size="small" @click="handleSubmit" :loading="submitLoading"
:disabled="!canSubmit">
<i class="el-icon-check"></i>
提交审批
</el-button>
</div>
<el-form ref="approveForm" :model="approveForm" :rules="formRules" label-width="120px" class="approve-form">
<!-- 基本信息 -->
<el-divider content-position="left">
<i class="el-icon-info"></i>
基本信息
</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="账号类型" prop="account_type">
<el-select v-model="approveForm.account_type" placeholder="请选择账号类型" style="width: 100%">
<el-option label="企业账号" value="enterprise"></el-option>
<el-option label="个人账号" value="personal"></el-option>
<el-option label="政府机构" value="government"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司名称" prop="company_name">
<el-input v-model="approveForm.company_name" placeholder="请输入公司名称" maxlength="100"
show-word-limit>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="执照号码" prop="license_number">
<el-input v-model="approveForm.license_number" placeholder="请输入营业执照号码" maxlength="50">
</el-input>
</el-form-item>
</el-col>
<!-- <el-col :span="12">
<el-form-item label="审核状态" prop="audit_status">
<el-tag
:type="getStatusType(approveForm.audit_status)"
size="medium">
{{ getStatusText(approveForm.audit_status) }}
</el-tag>
</el-form-item>
</el-col> -->
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="办公地址" prop="office_address">
<el-input v-model="approveForm.office_address" placeholder="请输入办公地址" maxlength="200"
show-word-limit>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="注册地址" prop="registered_address">
<el-input v-model="approveForm.registered_address" placeholder="请输入注册地址" maxlength="200"
show-word-limit>
</el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 联系信息 -->
<el-divider content-position="left">
<i class="el-icon-phone"></i>
联系信息
</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人姓名" prop="contact_name">
<el-input v-model="approveForm.contact_name" placeholder="请输入联系人姓名" maxlength="20">
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="固定电话" prop="telephone">
<el-input v-model="approveForm.telephone" placeholder="请输入固定电话" maxlength="20">
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="移动电话" prop="mobile_phone">
<el-input v-model="approveForm.mobile_phone" placeholder="请输入移动电话" maxlength="11">
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱地址" prop="email">
<el-input v-model="approveForm.email" placeholder="请输入邮箱地址" type="email" maxlength="50">
</el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 营业执照上传 -->
<el-divider content-position="left">
<i class="el-icon-picture"></i>
营业执照
</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="营业执照正本" prop="license_original_img">
<el-upload
class="license-uploader"
action="#"
:http-request="handleLicenseUpload"
:show-file-list="false"
:before-upload="beforeLicenseUpload"
accept="image/*">
<img v-if="approveForm.license_original_img"
:src="approveForm.license_original_img"
class="license-image">
<i v-else class="el-icon-plus license-uploader-icon"></i>
</el-upload>
<div class="upload-tip">
<i class="el-icon-info"></i>
支持 JPGPNGGIF 格式文件大小不超过 5MB
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="营业执照副本" prop="license_copy_img">
<el-upload
class="license-uploader"
action="#"
:http-request="handleLicenseCopyUpload"
:show-file-list="false"
:before-upload="beforeLicenseUpload"
accept="image/*">
<img v-if="approveForm.license_copy_img"
:src="approveForm.license_copy_img"
class="license-image">
<i v-else class="el-icon-plus license-uploader-icon"></i>
</el-upload>
<div class="upload-tip">
<i class="el-icon-info"></i>
支持 JPGPNGGIF 格式文件大小不超过 5MB
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 操作按钮 -->
<el-form-item class="form-actions">
<el-button @click="handleReset" size="medium">
<i class="el-icon-refresh"></i>
重置
</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading" :disabled="!canSubmit"
size="medium">
<i class="el-icon-check"></i>
提交审批
</el-button>
<el-button type="success" @click="handlePreview" size="medium">
<i class="el-icon-view"></i>
预览
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 预览对话框 -->
<el-dialog title="审批信息预览" :visible.sync="previewVisible" top="3vh" width="60%" :before-close="handlePreviewClose">
<div class="preview-content">
<el-descriptions :column="2" border>
<el-descriptions-item label="账号类型">
{{ getAccountTypeText(approveForm.account_type) }}
</el-descriptions-item>
<el-descriptions-item label="公司名称">
{{ approveForm.company_name }}
</el-descriptions-item>
<el-descriptions-item label="执照号码">
{{ approveForm.license_number }}
</el-descriptions-item>
<!-- <el-descriptions-item label="审核状态1">
<el-tag :type="getStatusType(approveForm.audit_status)">
{{ getStatusText(approveForm.audit_status) }}
</el-tag>
</el-descriptions-item> -->
<el-descriptions-item label="办公地址">
{{ approveForm.office_address }}
</el-descriptions-item>
<el-descriptions-item label="注册地址">
{{ approveForm.registered_address }}
</el-descriptions-item>
<el-descriptions-item label="联系人">
{{ approveForm.contact_name }}
</el-descriptions-item>
<el-descriptions-item label="联系电话">
{{ approveForm.mobile_phone }}
</el-descriptions-item>
<el-descriptions-item label="邮箱地址">
{{ approveForm.email }}
</el-descriptions-item>
</el-descriptions>
<div class="preview-images">
<div class="preview-image" v-if="approveForm.license_original_img">
<h4>营业执照正本</h4>
<img :src="approveForm.license_original_img" alt="营业执照正本" class="preview-license-img">
</div>
<div class="preview-image" v-if="approveForm.license_copy_img">
<h4>营业执照副本</h4>
<img :src="approveForm.license_copy_img" alt="营业执照副本" class="preview-license-img">
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="previewVisible = false">关闭</el-button>
<el-button type="primary" @click="handleSubmit">确认提交</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {reqApproveUser } from '@/api/ncmatch/index'
export default {
name: "ncApprove",
data() {
return {
approveForm: {
account_type: "", //
company_name: '', //
license_number: '', //
license_original_img: '', //
license_copy_img: '', //
license_original_file: null, //
license_copy_file: null, //
office_address: '', //
registered_address: '', //
contact_name: '', //
telephone: '', //
mobile_phone: '', //
email: '', //
audit_status: 'pending', //
},
formRules: {
account_type: [
{ required: true, message: '请选择账号类型', trigger: 'change' }
],
company_name: [
{ required: true, message: '请输入公司名称', trigger: 'blur' },
{ min: 2, max: 100, message: '公司名称长度在 2 到 100 个字符', trigger: 'blur' }
],
license_number: [
{ required: true, message: '请输入执照号码', trigger: 'blur' }
],
contact_name: [
{ required: true, message: '请输入联系人姓名', trigger: 'blur' }
],
mobile_phone: [
{ required: true, message: '请输入移动电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
license_original_img: [
{
required: true,
validator: (rule, value, callback) => {
if (!this.approveForm.license_original_file) {
callback(new Error('请上传营业执照正本'));
} else {
callback();
}
},
trigger: 'change'
}
],
license_copy_img: [
{
required: true,
validator: (rule, value, callback) => {
if (!this.approveForm.license_copy_file) {
callback(new Error('请上传营业执照副本'));
} else {
callback();
}
},
trigger: 'change'
}
]
},
submitLoading: false,
previewVisible: false
}
},
computed: {
canSubmit() {
return this.approveForm.account_type &&
this.approveForm.company_name &&
this.approveForm.license_number &&
this.approveForm.contact_name &&
this.approveForm.mobile_phone &&
this.approveForm.email &&
this.approveForm.license_original_file &&
this.approveForm.license_copy_file;
}
},
methods: {
//
getAccountTypeText(type) {
const typeMap = {
'enterprise': '企业账号',
'personal': '个人账号',
'government': '政府机构'
};
return typeMap[type] || '未知';
},
//
getStatusType(status) {
const statusMap = {
'pending': 'warning',
'approved': 'success',
'rejected': 'danger',
'processing': 'info'
};
return statusMap[status] || 'info';
},
//
getStatusText(status) {
const statusMap = {
'pending': '待审核',
'approved': '已通过',
'rejected': '已拒绝',
'processing': '审核中'
};
return statusMap[status] || '未知';
},
//
beforeLicenseUpload(file) {
const isImage = file.type.startsWith('image/');
const isLt5M = file.size / 1024 / 1024 < 5;
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (!isImage || !allowedTypes.includes(file.type)) {
this.$message.error('只能上传 JPG、PNG、GIF 格式的图片文件!');
return false;
}
if (!isLt5M) {
this.$message.error('图片大小不能超过 5MB!');
return false;
}
return true;
},
//
handleLicenseUpload(options) {
const file = options.file;
//
this.approveForm.license_original_file = file;
//
const reader = new FileReader();
reader.onload = (e) => {
this.approveForm.license_original_img = e.target.result;
this.$message.success('营业执照正本上传成功');
};
reader.readAsDataURL(file);
},
//
handleLicenseCopyUpload(options) {
const file = options.file;
//
this.approveForm.license_copy_file = file;
//
const reader = new FileReader();
reader.onload = (e) => {
this.approveForm.license_copy_img = e.target.result;
this.$message.success('营业执照副本上传成功');
};
reader.readAsDataURL(file);
},
//
handleSubmit() {
this.$refs.approveForm.validate((valid) => {
if (valid) {
this.submitLoading = true;
let formData = new FormData();
//
const basicFields = [
'account_type', 'company_name', 'license_number',
'office_address', 'registered_address', 'contact_name',
'telephone', 'mobile_phone', 'email', 'audit_status'
];
basicFields.forEach(field => {
if (this.approveForm[field]) {
formData.append(field, this.approveForm[field]);
}
});
//
if (this.approveForm.license_original_file) {
formData.append('license_original_img', this.approveForm.license_original_file);
}
if (this.approveForm.license_copy_file) {
formData.append('license_copy_img', this.approveForm.license_copy_file);
}
reqApproveUser(formData).then(res => {
this.submitLoading = false;
if (res.status) {
this.$message.success('审批信息提交成功!');
this.approveForm.audit_status = 'processing';
} else {
this.$message.error(res.message || '提交失败,请重试');
}
}).catch(error => {
this.submitLoading = false;
this.$message.error('网络错误,请重试');
console.error('提交失败:', error);
});
} else {
this.$message.error('请完善必填信息');
}
});
},
//
handleReset() {
this.$refs.approveForm.resetFields();
this.approveForm.license_original_img = '';
this.approveForm.license_copy_img = '';
this.approveForm.license_original_file = null;
this.approveForm.license_copy_file = null;
this.approveForm.audit_status = 'pending';
this.$message.info('表单已重置');
},
//
handlePreview() {
if (this.canSubmit) {
this.previewVisible = true;
} else {
this.$message.warning('请先完善必填信息');
}
},
//
handlePreviewClose(done) {
done();
},
//
getFileInfo(file) {
if (!file) return null;
return {
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified
};
}
}
}
</script>
<style lang="scss" scoped>
.nc-approve-container {
min-height: 100vh;
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="50" cy="50" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
pointer-events: none;
}
.approve-card {
// max-width: 1200px;
margin: 0 45px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.1),
0 8px 16px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
overflow: hidden;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.header-title {
font-size: 20px;
font-weight: 700;
color: #2c3e50;
display: flex;
align-items: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
i {
margin-right: 12px;
color: #667eea;
font-size: 24px;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
}
.el-button {
border-radius: 8px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
}
}
.approve-form {
// padding: 32px;
.el-divider {
margin: 40px 0 24px 0;
.el-divider__text {
font-size: 18px;
font-weight: 700;
color: #2c3e50;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
padding: 0 20px;
i {
margin-right: 10px;
color: #667eea;
font-size: 20px;
}
}
&::before,
&::after {
background: linear-gradient(90deg, transparent, #667eea, transparent);
}
}
.el-form-item {
margin-bottom: 24px;
.el-form-item__label {
font-weight: 600;
color: #2c3e50;
font-size: 14px;
}
.el-input,
.el-select {
.el-input__inner {
border-radius: 8px;
border: 2px solid #e1e8ed;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
&:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
&:hover {
border-color: #b8c5d6;
}
}
}
.el-tag {
border-radius: 6px;
font-weight: 600;
padding: 8px 16px;
font-size: 13px;
border: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
.license-uploader {
border: 2px dashed #d1d9e0;
border-radius: 12px;
cursor: pointer;
position: relative;
overflow: hidden;
width:150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: #667eea;
background: linear-gradient(135deg, #f0f4ff 0%, #e8f0ff 100%);
// transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2);
}
.license-uploader-icon {
font-size: 28px;
color: #667eea;
width: 100%;
height: 160px;
line-height: 160px;
text-align: center;
transition: all 0.3s ease;
}
.license-image {
width: 100%;
height: 160px;
display: block;
object-fit: cover;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
.upload-tip {
margin-top: 12px;
font-size: 13px;
color: #6c757d;
background: rgba(102, 126, 234, 0.1);
padding: 4px 8px;
border-radius: 6px;
border-left: 3px solid #667eea;
i {
margin-right: 6px;
color: #667eea;
}
}
.form-actions {
margin-top: 40px;
text-align: center;
border-top: 2px solid #f1f3f4;
padding-top: 30px;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
margin: 40px -32px -32px -32px;
padding: 30px 32px;
.el-button {
margin: 0 12px;
border-radius: 8px;
font-weight: 600;
padding: 12px 24px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
&.el-button--primary {
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
}
&.el-button--success {
background: linear-gradient(135deg, #56ab2f, #a8e6cf);
border: none;
box-shadow: 0 4px 12px rgba(86, 171, 47, 0.3);
&:hover {
box-shadow: 0 8px 20px rgba(86, 171, 47, 0.4);
}
}
}
}
}
}
.preview-content {
.preview-images {
margin-top: 24px;
display: flex;
gap: 20px;
flex-wrap: wrap;
.preview-image {
flex: 1;
min-width: 300px;
text-align: center;
h4 {
margin-bottom: 16px;
color: #2c3e50;
font-weight: 600;
font-size: 16px;
}
.preview-license-img {
max-width: 100%;
max-height: 250px;
border: 2px solid #e1e8ed;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
}
.el-descriptions {
.el-descriptions__body {
.el-descriptions__table {
.el-descriptions__cell {
padding: 16px 20px;
font-size: 14px;
}
}
}
}
}
}
//
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.nc-approve-container {
animation: fadeInUp 0.6s ease-out;
}
//
@media (max-width: 768px) {
.nc-approve-container {
padding: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
.approve-card {
border-radius: 12px;
margin: 0 5px;
.card-header {
flex-direction: column;
gap: 15px;
padding: 20px;
.header-title {
font-size: 18px;
}
}
.approve-form {
padding: 20px;
.el-divider {
margin: 30px 0 20px 0;
.el-divider__text {
font-size: 16px;
}
}
.el-form-item {
margin-bottom: 20px;
}
.license-uploader {
width: 100%;
height: 120px;
.license-uploader-icon {
width: 100%;
height: 120px;
line-height: 120px;
font-size: 24px;
}
.license-image {
width: 100%;
height: 120px;
}
}
.preview-content {
.preview-images {
flex-direction: column;
gap: 15px;
.preview-image {
min-width: auto;
}
}
}
.form-actions {
margin: 30px -20px -20px -20px;
padding: 20px;
.el-button {
margin: 5px;
padding: 10px 20px;
font-size: 14px;
}
}
}
}
}
}
//
@media (prefers-color-scheme: dark) {
.nc-approve-container {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
.approve-card {
background: rgba(44, 62, 80, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
.card-header {
background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
.header-title {
color: #ecf0f1;
}
}
.approve-form {
.el-divider__text {
color: #ecf0f1;
}
.el-form-item__label {
color: #ecf0f1;
}
.el-input__inner {
background: rgba(52, 73, 94, 0.9);
border-color: #34495e;
color: #ecf0f1;
&:focus {
border-color: #667eea;
}
}
.license-uploader {
background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
border-color: #34495e;
&:hover {
background: linear-gradient(135deg, #3a5a78 0%, #34495e 100%);
}
}
.form-actions {
background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
border-top-color: rgba(255, 255, 255, 0.1);
}
}
}
}
}
//
.el-loading-mask {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
}
//
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 4px;
&:hover {
background: linear-gradient(135deg, #5a6fd8, #6a4190);
}
}
</style>

View File

@ -0,0 +1,350 @@
<template>
<div>
<div>
<ul class="search-box">
<li>
<span class="search-label">创建日期:</span>
<el-date-picker size="mini" style="width: 220px;" v-model="searchDate" type="datetimerange"
align="right" start-placeholder="开始日期" end-placeholder="结束日期"
:default-time="['00:00:00', '23:59:59']" format="yyyy-MM-dd" value-format="yyyy-MM-dd"
:picker-options="pickerOptions">
</el-date-picker>
</li>
<li>
<el-input size="mini" v-model="searchData.keyword" placeholder="请输入商品名称或关键词" clearable></el-input>
</li>
<li>
<el-button size="mini" @click="resetSearch">重置</el-button>
<el-button type="primary" @click="getTableData" size="mini">查询</el-button>
</li>
</ul>
</div>
<div class="table-box">
<div style="margin-bottom: 10px;">
<el-radio-group v-model="searchData.radioType" class="radio-group" size="mini"
@change="handleRadioChange">
<el-radio-button v-for="item in radioOptions" :key="item.value" :label="item.value">
{{ item.label }}
</el-radio-button>
</el-radio-group>
<el-button style="margin-left: 10px;" size="mini" @click="exportData">
<i class="el-icon-upload2"></i> 导出
</el-button>
</div>
<el-table height="calc(100vh - 210px)" v-loading="loading" :data="tableData"
style="width: 100%;border:1px solid #ccc;" element-loading-text="加载中..."
element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.8)">
<el-table-column prop="date" label="商品图片" width="180">
<template slot-scope="scope">
<el-image :src="scope.row.img || 'https://www.kaiyuancloud.cn/idfile?path=logo_ncmatch.png'"
style="width: 80px; height: 60px;border: 1px solid #ccc;border-radius: 6px;"
:preview-src-list="[scope.row.img || 'https://www.kaiyuancloud.cn/idfile?path=logo_ncmatch.png']"
fit="cover" :initial-index="0" preview-teleported :z-index="3000">
</el-image>
</template>
</el-table-column>
<el-table-column prop="product_name" show-overflow-tooltip label="商品名称" min-width="180">
</el-table-column>
<el-table-column prop="product_category" show-overflow-tooltip label="所属类别" min-width="180">
</el-table-column>
<el-table-column prop="company_name" show-overflow-tooltip label="所属企业" min-width="180">
</el-table-column>
<el-table-column prop="company_type" show-overflow-tooltip label="公司类别" min-width="180">
</el-table-column>
<el-table-column prop="address" show-overflow-tooltip label="商品概述" min-width="180">
</el-table-column>
<el-table-column prop="address" show-overflow-tooltip label="商品详情" min-width="180">
</el-table-column>
<el-table-column prop="contact_person" show-overflow-tooltip label="联系人" min-width="180">
</el-table-column>
<el-table-column prop="phone_number" show-overflow-tooltip label="联系电话" min-width="180">
</el-table-column>
<el-table-column prop="create_at" show-overflow-tooltip label="创建时间" min-width="180">
</el-table-column>
<el-table-column fixed="right" min-width="240" label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="openDetail(scope.row)">查看</el-button>
<el-button type="text" style="color: #409EFF;" size="small">修改</el-button>
<!-- <el-button type="text" style="color: #E6A23C;" size="small">导出</el-button> -->
<el-button type="text" style="color: #67C23A;" size="small">上架</el-button>
<el-button type="text" size="small">下架</el-button>
<el-button type="text" style="color: #F56C6C;" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="background-color: white;" @size-change="handleSizeChange"
@current-change="handleCurrentChange" :current-page.sync="current_page" :page-size="page_size"
layout="total, prev, pager, next" :total="total_count">
</el-pagination>
</div>
<commonDetail
:visible="showProductDetail"
@close="handleDetailClose"
@approve="handleApprove"
@reject="handleReject">
</commonDetail>
</div>
</template>
<script>
import { reqSearchByMangement, reqGetProductDetail } from '@/api/ncmatch';
import commonDetail from '@/views/customer/productMangement/commonDetail/index.vue';
import { mapState } from 'vuex';
export default {
name: 'commonBox',
components: { commonDetail },
props: {
role: {
type: Object,
default: () => { }
}
},
data() {
return {
searchDate: [],
searchData: {
radioType: "1",
start_date: null,
end_date: null,
keyword: "",
audit_status: null,
listing_status: null,
publish_type: "2",
manager_self: "",
url_link: window.location.href,
current_page: 1,
page_size: 8,
},
radioMap: {
'1': {
label: '用户需求',
publish_type: '2',
manager_self: null,
},
'2': {
label: '用户商品',
publish_type: '1',
manager_self: null,
},
'3': {
label: '平台需求',
publish_type: '2',
manager_self: 'single ',
},
'4': {
label: '平台商品',
publish_type: '1',
manager_self: 'single',
},
},
radioOptions: [
{
label: '用户需求',
value: '1'
},
{
label: '用户商品',
value: '2'
},
{
label: '平台需求',
value: '3'
},
{
label: '平台商品',
value: '4'
},
],
manager_self: null,
page_size: 10,
current_page: 1,
publish_type: 1,
tableData: [],
total_count: 0,
loading: false,
pickerOptions: {
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
}
},
created() {
this.getTableData();
},
methods: {
handleRadioChange(value) {
this.searchData.publish_type = this.radioMap[value].publish_type;
this.searchData.manager_self = this.radioMap[value].manager_self;
//
this.searchData.current_page = 1;
this.getTableData();
},
resetSearch() {
this.searchDate = [];
this.searchData.keyword = "";
this.searchData.current_page = 1;
this.getTableData();
},
exportData() {
this.$message.info('导出功能开发中...');
},
handleDetailClose() {
//
console.log('弹窗已关闭');
},
handleApprove() {
//
console.log('审核通过');
//
},
handleReject() {
//
console.log('审核不通过');
//
},
getTableData() {
//
if (this.searchDate && this.searchDate.length === 2) {
this.searchData.start_date = this.searchDate[0];
this.searchData.end_date = this.searchDate[1];
} else {
this.searchData.start_date = null;
this.searchData.end_date = null;
}
console.log("searchData", this.searchData);
this.loading = true;
reqSearchByMangement(this.searchData).then(res => {
console.log(res);
if (res.status) {
this.tableData = res.data.product_list;
this.total_count = res.data.total_count;
}
}).catch(error => {
console.error('获取数据失败:', error);
}).finally(() => {
this.loading = false;
})
},
//
handleSizeChange(val) {
console.log(`每页 ${val}`);
},
handleCurrentChange(val) {
this.getTableData();
console.log(`当前页: ${val}`);
},
openDetail(item) {
this.loading = true;
reqGetProductDetail({
id: item.id,
from: 'b'
}).then(async res => {
this.loading = false
if (res.status) {
await this.$store.commit('SETPRODUCTDETAIL', res.data);
await this.$store.commit('SHOWPRODUCTDETAIL', true);
} else {
this.$message.error(res.msg);
this.$set(this.loadingStates, item.id, false);
}
})
}
},
computed: {
showProductDetail: {
get() {
return this.$store.state.ncmatch.showProductDetail;
},
set(value) {
this.$store.commit('SHOWPRODUCTDETAIL', value);
}
}
}
}
</script>
<style scoped>
.search-box {
display: flex;
align-items: center;
justify-content: flex-start;
background-color: white;
padding: 10px;
li {
margin-right: 15px;
display: flex;
align-items: center;
justify-content: flex-start;
}
margin-bottom: 10px;
border-radius: 8px;
}
.table-box {
height: calc(100vh - 120px);
padding: 10px;
background-color: white;
}
::v-deep .radio-group {
.el-radio-button__inner {
border-radius: 4px;
margin-right: 8px;
border: 1px solid #dcdfe6;
background-color: #f5f7fa;
color: #606266;
&:hover {
background-color: #ecf5ff;
border-color: #409eff;
color: #409eff;
}
}
.el-radio-button__orig-radio:checked+.el-radio-button__inner {
background-color: #409eff;
border-color: #409eff;
color: #fff;
box-shadow: -1px 0 0 0 #409eff;
}
}
.search-label {
font-size: 14px;
font-weight: bold;
display: flex;
margin-right: 10px;
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<el-dialog
:title="dialogTitle"
top="5vh"
:visible.sync="visible"
width="80%"
@open="scrollToTop"
@close="handleClose">
<ProductDetail></ProductDetail>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="success" @click="handleApprove">审核通过</el-button>
<el-button type="danger" @click="handleReject">审核不通过</el-button>
</span>
</el-dialog>
</template>
<script>
import ProductDetail from '@/views/homePage/ncmatch/proDetail/index.vue';
import { mapState } from 'vuex';
export default {
name: 'commonDetail',
components: { ProductDetail },
props: {
visible: {
type: Boolean,
default: false
}
},
computed: {
...mapState({
productDetailInfo: state => state.ncmatch.productDetail,
}),
dialogTitle() {
return this.productDetailInfo.publish_type === '1' ? '商品详情' : '需求详情';
}
},
methods: {
scrollToTop() {
this.$nextTick(() => {
const dialogBody = document.querySelector('.el-dialog__body');
if (dialogBody) {
dialogBody.scrollTop = 0;
}
});
},
handleClose() {
this.$store.commit('SHOWPRODUCTDETAIL', false);
this.$emit('close');
},
handleApprove() {
this.$confirm('确认审核通过吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// API
this.$emit('approve');
this.$message.success('审核通过成功');
this.handleClose();
}).catch(() => {
this.$message.info('已取消操作');
});
},
handleReject() {
this.$confirm('确认审核不通过吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// API
this.$emit('reject');
this.$message.success('审核不通过成功');
this.handleClose();
}).catch(() => {
this.$message.info('已取消操作');
});
}
}
}
</script>
<style scoped>
.dialog-footer {
text-align: right;
}
.dialog-footer .el-button {
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div>
<router-view></router-view>
<el-dialog :title="productDetailInfo.publish_type === '1' ? '商品详情' : '需求详情'" top="5vh"
:visible.sync="showProductDetail" width="80%" @open="scrollToTop">
<ProductDetail></ProductDetail>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="closeProductDetail"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { mapGetters, mapState } from "vuex";
import ProductDetail from "@/views/homePage/ncmatch/proDetail/index.vue";
export default {
name: 'productMangement',
components: {
ProductDetail: ProductDetail
},
data() {
return {
}
},
methods: {
scrollToTop() {
window.scrollTo(0, 0);
},
closeProductDetail() {
this.$store.commit('SHOWPRODUCTDETAIL', false);
}
},
computed: {
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
productDetailInfo: state => state.ncmatch.productDetail,
showProductDetail: state => state.ncmatch.showProductDetail,
}),
},
}
</script>

View File

@ -0,0 +1,26 @@
<template>
<div>
<commonBox :role="role"></commonBox>
</div>
</template>
<script>
//| |
import commonBox from '@/views/customer/productMangement/commonBox/index.vue';
export default {
name: 'productList',
components: { commonBox },
data() {
return {
role:{
role_type:'customer',
type:'look'
}
}
},
created() {
},
}
</script>

View File

@ -2,15 +2,15 @@
<div class="top-nav">
<div id="topContainer" class="container">
<div class="logo">
<img v-if="JSON.stringify(logoInfoNew)!=='{}'" @click="$router.push('/homePage/index')"
<img v-if="JSON.stringify(logoInfoNew)!=='{}'" @click="$router.push(homePath)"
style="cursor:pointer;margin-right: 71px" class="logoImg"
:src=" logoInfoNew.home.logoImg || '' "
alt="">
<nav class="main-nav">
<nav class="main-nav" v-if="!isNcmatchHome">
<ul >
<li :class="{ active: $route.path.includes('/index') }">
<a @click="$router.push('/homePage/index')">首页</a>
<a @click="$router.push(homePath)">首页</a>
</li>
<li @mouseleave="sildeOut" @mouseenter="sildeIn(product_service)"><a>产品与服务</a></li>
<!-- <li :class="{ active: $route.path.includes('/new') }" @mouseleave="sildeOut"-->
@ -202,11 +202,13 @@ import {mapGetters, mapState} from "vuex";
import {getLogoAPI, getUserInfoAPI} from "@/api/login";
import {reqApplyChannel} from "@/api/customer/channel";
import store from "@/store";
import { windows } from 'codemirror/src/util/browser';
import { getHomePath } from '@/views/setting/tools'
export default Vue.extend({
name: "TopBox",
data() {
return {
homePath: getHomePath(),
isShowKbossCharge: false,
role: sessionStorage.getItem("jueseNew") == "admin" ? [] : sessionStorage.getItem("jueseNew"),
userId: sessionStorage.getItem("userId"),
@ -258,6 +260,9 @@ export default Vue.extend({
},
watch: {},
computed: {
isNcmatchHome() {
return window.location.href.includes('ncmatchHome')
},
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
isShowPanel: (state) => state.product.showHomeNav,
@ -320,6 +325,7 @@ export default Vue.extend({
// url_link: 'https://www.baidu.com'
}
getLogoAPI(params).then((res) => {
console.log("获取接口触发了")
// if (res.data.status == true && res.data.data && res.data.data.length) {
// this.photosUrl = res.data.data[0];

View File

@ -10,8 +10,7 @@
<span
class="rightText"> 您身边的AI管家</span></span>
<span class="rightText"> 您身边的AI管家</span></span>
<!-- <span class="description">支持模型训练推理和数据处理灵活配置助您高效释放AI潜能</span>-->
<ul style="margin-top: 50px" class="tagUl">
<li> AI </li>
@ -29,9 +28,7 @@
<span class="productName">云资源</span>
<span class="price">
<span
style="margin-top: 15px"
class="smallText">直降 <span style="font-size: 30px;display: inline-block"
<span style="margin-top: 15px" class="smallText">直降 <span style="font-size: 30px;display: inline-block"
class="bigText">20%</span></span> </span>
</span>
</div>
@ -55,16 +52,13 @@
<p style="line-height: 2" class="description">{{ baseItem.description }}</p>
<ul class="itemContentTag">
<li v-for="(item, index) in baseItem.list" :key="index">
<img :src="item.icon" alt=""><span v-if="item.name"
:style="item.name === '高可靠' ? { width: '150px' } : {}"
class="tagTitle">{{ item.name }}</span> <span
class="tagContent">{{ item.content }}</span>
<img :src="item.icon" alt=""><span v-if="item.name" :style="item.name === '高可靠' ? { width: '150px' } : {}"
class="tagTitle">{{ item.name }}</span> <span class="tagContent">{{ item.content }}</span>
</li>
</ul>
<span class="basePrice"><span style="font-weight: bold;"></span><span
style="font-weight: bold;font-size: 48px">{{ baseItem.price }}</span>
<span
style="color: #7A82A0">/{{ baseItem.price_unit }}</span>
<span style="color: #7A82A0">/{{ baseItem.price_unit }}</span>
<!-- <span-->
<!-- style="color: #7A82A0;text-decoration-line: line-through">{{ baseItem.pre_price }}/{{-->
<!-- baseItem.price_unit-->
@ -82,8 +76,8 @@
</div>
</div>
<span class="discountStyle"><span style="font-size: 18px">官网折扣</span> <span
style="font-size: 24px">{{ baseItem.discount }}</span></span>
<span class="discountStyle"><span style="font-size: 18px">官网折扣</span> <span style="font-size: 24px">{{
baseItem.discount }}</span></span>
</div>
</div>
@ -105,17 +99,14 @@
<span class="title">{{ suanItem.title }}</span>
<ul class="itemContentTag">
<li v-for="(item, index) in suanItem.list" :key="index">
<img :src="item.icon" alt=""><span v-if="item.name"
:style="item.name === '高可靠' ? { width: '150px' } : {}"
class="tagTitle">{{ item.name }}</span> <span
class="tagContent">{{ item.content }}</span>
<img :src="item.icon" alt=""><span v-if="item.name" :style="item.name === '高可靠' ? { width: '150px' } : {}"
class="tagTitle">{{ item.name }}</span> <span class="tagContent">{{ item.content }}</span>
</li>
</ul>
<span class="basePrice"><span style="font-weight: bold;"></span><span
style="font-weight: bold;font-size: 48px">{{ suanItem.price }}</span>
<span
style="color: #7A82A0">/{{ suanItem.price_unit }}</span>
<span style="color: #7A82A0">/{{ suanItem.price_unit }}</span>
<!-- <span-->
<!-- style="color: #7A82A0;text-decoration-line: line-through">{{ suanItem.pre_price }}/{{-->
<!-- suanItem.price_unit-->
@ -164,8 +155,7 @@
<li v-for="(item, index) in netItem.advantageList" :key="index">
<img :src="item.icon" alt=""><span style="width: fit-content!important;" class="tagTitle">{{
item.name
}}</span> <span
class="tagContent">{{ item.content }}</span>
}}</span> <span class="tagContent">{{ item.content }}</span>
</li>
</ul>
<span class="subTitle">应用场景</span>
@ -299,21 +289,13 @@
<Talk></Talk>
<el-dialog
title=""
:visible.sync="mesDialog"
custom-class="msgDialog"
:before-close="handleClose"
width="30%"
>
<el-dialog title="" :visible.sync="mesDialog" custom-class="msgDialog" :before-close="handleClose" width="30%">
<span class="title">官方申明</span>
<p>尊敬用户</p>
<p>您好~~ </p>
<p>本平台公示的所有产品折扣活动均真实有效价格体系严格遵循公开透明原则 </p>
<p> 特别说明<span
style="color: #7A82A0!important;">云服务产品页面所示价格属参考性标价实际交易金额须以资源清单订单页面为准</span>
<p> 特别说明<span style="color: #7A82A0!important;">云服务产品页面所示价格属参考性标价实际交易金额须以资源清单订单页面为准</span>
</p>
<p>请您知悉上述条款并放心进行购买决策如有任何疑问可随时联系平台官方客服我们将为您详细说明</p>
<div style="margin-top: 85px" class="footerBox">
@ -575,11 +557,15 @@ export default Vue.extend({
font-size: 64px;
.leftText {
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
}
}
@ -615,11 +601,15 @@ export default Vue.extend({
}
.commonTextColor {
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
font-weight: bold;
}
@ -650,11 +640,15 @@ export default Vue.extend({
}
.price {
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
font-weight: bold;
.bigText {
@ -737,10 +731,12 @@ export default Vue.extend({
z-index: 10;
padding-left: 50px;
padding-top: 50px;
color: #222F60;;
color: #222F60;
;
* {
color: #222F60;;
color: #222F60;
;
}
padding-bottom: 25px;
@ -834,11 +830,15 @@ export default Vue.extend({
}
.lookDetailStyle {
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
font-weight: bold;
margin-left: 85px;
border-radius: 12px;
@ -926,11 +926,13 @@ export default Vue.extend({
z-index: 10;
padding-left: 50px;
padding-top: 50px;
color: #222F60;;
color: #222F60;
;
* {
color: #222F60;;
color: #222F60;
;
}
width: 1400px;
@ -1026,11 +1028,15 @@ export default Vue.extend({
}
.lookDetailStyle {
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
font-weight: bold;
margin-left: 85px;
@ -1097,10 +1103,12 @@ export default Vue.extend({
z-index: 10;
padding-left: 50px;
padding-top: 50px;
color: #222F60;;
color: #222F60;
;
* {
color: #222F60;;
color: #222F60;
;
}
padding-bottom: 25px;
@ -1195,11 +1203,15 @@ export default Vue.extend({
}
.lookDetailStyle {
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
font-weight: bold;
margin-left: 85px;
border-radius: 12px;
@ -1300,21 +1312,30 @@ export default Vue.extend({
.numberStyle {
position: relative;
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
.mapTag {
font-size: 24px;
position: absolute;
right: -20px;
top: 0;
background: linear-gradient(to right, #275AFF, #2EBDFA); /* 渐变方向颜色 */
-webkit-background-clip: text; /* 关键属性:裁剪背景到文字 */
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent; /* 文字颜色透明 */
display: inline-block; /* 确保渐变生效 */
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
}
}

View File

@ -0,0 +1,324 @@
<template>
<div id="homeOut" class="homeOut">
<TopBox id="topBox"></TopBox>
<div class="search-box">
<search></search>
</div>
<div style="width: 90%;max-width: 1600px;border:1px solid red;">
<router-view></router-view>
</div>
<el-dialog :title="productDetailInfo.publish_type==='1'? '商品详情' :'需求详情'" top="5vh" :visible.sync="showProductDetail" width="80%" @open="scrollToTop">
<ProductDetail></ProductDetail>
<span slot="footer" class="dialog-footer">
<!-- <el-button @click="closeProductDetail"> </el-button> -->
<el-button type="primary" @click="closeProductDetail"> </el-button>
</span>
</el-dialog>
<!-- footer-->
<div class="footer">
<div class="left-box" style="border-bottom: 1px solid #7A82A0">
<div style="display: flex;flex-direction: column">
<img v-if="JSON.stringify(logoInfoNew) !== '{}'" style="width: 148px;height: 48px;"
:src="logoInfoNew.home.logoImg" alt="" class="img">
<div class="content-main">
<ul class="info">
<li>地址<span v-if="JSON.stringify(logoInfoNew) !== '{}'">{{ logoInfoNew.home.adress }}</span>
</li>
<li v-if="JSON.stringify(logoInfoNew) !== '{}'"> 邮箱{{ logoInfoNew.home.email }}</li>
<li v-if="JSON.stringify(logoInfoNew) !== '{}'">电话: <span class="tel">{{ logoInfoNew.home.mobile }}</span>
</li>
<li>
<!-- <a href="" rel="noreferrer" target="_blank"></a> -->
</li>
</ul>
</div>
</div>
<ul class="bigUl">
</ul>
<div v-if="JSON.stringify(logoInfoNew) !== '{}' && logoInfoNew.home.bannerTitle !== '开元数智'" class="right-box">
<div class="qr-box">
<div class="qr-code">
<img src="../img/kefu.jpg" style="padding: 0.08rem" alt="">
</div>
<span class="qr-content">微信客服</span>
</div>
<div class="qr-box" style="margin-left: 0.667rem">
<div class="qr-code">
<img src="../img/img.png" alt="">
</div>
<span class="qr-content">关注公众号</span>
</div>
</div>
</div>
<div style="display: flex;justify-content: center;align-items: center;width: 100%; ">
<span v-if="JSON.stringify(logoInfoNew) !== '{}'"
style="margin:15px 0 ;width: 1400px;display:flex;justify-content:center;align-items:center;color: #7A82A0;"><span
class="goStyle" @click="goOut('https://beian.miit.gov.cn/#/Integrated/index')">
京ICP备{{
logoInfoNew.home.license
}}&nbsp;
</span> &nbsp;&nbsp;{{
logoInfoNew.home.footerTitle
}}&nbsp;{{
logoInfoNew.home.copyright
}}&nbsp; </span>
<!-- IPC备案号:{{ ICP }} <span style="margin-left: 0.267rem">版权所有 @kaiyuanyun 2023</span>-->
<!-- <img src="../../image/login/policeInsignia/policeInsignia.png" alt=""-->
<!-- style="width:0.227rem;height:0.227rem;margin-right: 0.027rem;">-->
<!-- <a href="https://beian.mps.gov.cn/#/query/webSearch?code=11010502054007" rel="noreferrer"-->
<!-- target="_blank"-->
<!-- style="margin-right:0.4rem;">京公网安备11010502054007</a>-->
<!-- <span>-->
<!-- <router-link tag="a" target="_blank"-->
<!-- :to="{ name: 'homePageImage' }">经营许可证:京B2-20232313</router-link>-->
<!-- </span>-->
</div>
</div>
</div>
</template>
<script>
import Vue from 'vue'
import TopBox from "@/views/homePage/components/topBox/index.vue";
import { mapGetters, mapState } from "vuex";
import search from "./search/index.vue";
import productDetail from "./proDetail/index.vue";
export default Vue.extend({
name: "indexLast",
components: { TopBox, search, ProductDetail: productDetail },
data() {
return {
currentBaseMenu: "hot", //
}
},
computed: {
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
isShowPanel: (state) => state.product.showHomeNav,
productDetailInfo: state => state.ncmatch.productDetail,
navIndex: (state) => state.product.navIndex,
gridObj: state => state.operationAnalysis.gridObj,
mybalance: state => state.user.mybalance,
logoutUrl: state => state.login.logoutUrl,
loginState: state => state.login.loginState,
logoInfoNew: state => state.product.logoInfoNew,
}),
showProductDetail: {
get() {
return this.$store.state.ncmatch.showProductDetail;
},
set(value) {
this.$store.commit('SHOWPRODUCTDETAIL', value);
}
},
showRegisterButton() {
const orgType = window.sessionStorage.getItem('org_type');
const userId = window.sessionStorage.getItem('userId');
console.log("此时是:", orgType !== '2' && orgType !== '3' && userId !== null)
return orgType !== '2' && orgType !== '3' && userId === null;
},
username() {
return sessionStorage.getItem('username') || '';
},
},
methods: {
closeProductDetail() {
this.$store.commit('SHOWPRODUCTDETAIL', false)
},
scrollToTop() {
// 使nextTickDOM
this.$nextTick(() => {
const dialogBody = document.querySelector('.el-dialog__body');
if (dialogBody) {
dialogBody.scrollTop = 0;
}
});
},
goOut(url) {
window.open(url)
},
scrollToElement(id) {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({
behavior: 'smooth', //
block: 'start', // 'start', 'center', 'end', 'nearest'
});
}
},
clickBaseMenu(menu) {
console.log("emnu", menu)
this.currentBaseMenu = menu.id;
},
async goBaidu() {
this.scrollToElement('topBox')
await this.$store.commit('setShowHomeNav', true)
await this.$store.commit('setNavIndex', 0)
},
async goAliyun() {
this.scrollToElement('topBox')
await this.$store.commit('setShowHomeNav', true)
await this.$store.commit('setNavIndex', 1)
},
}
})
</script>
<style scoped lang="scss">
.search-box {
width: 100%;
margin-top: 55px;
border: 1px solid red;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.homeOut {
//padding-top: 60px;
height: 100%;
overflow: auto !important;
min-width: 1500px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.footer {
padding: 35px 0;
width: 100%;
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.left-box {
width: 1400px;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 15px;
}
.right-box {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.content-main {
mix-blend-mode: normal;
color: rgba(0, 0, 0, 1);
font-size: 0.187rem;
}
.qr-code {
img {
width: 100%;
height: 100%;
}
width: 1.853rem;
height: 1.853rem;
}
.qr-content {
mix-blend-mode: normal;
color: rgba(24, 24, 24, 1);
font-family: PingFang SC, serif;
font-weight: 600;
font-size: 0.187rem;
}
.qr-box {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.logo {
background-color: #32abfc;
img {
width: 100%;
height: 100%;
}
}
.bigUl {
margin: 0 15px;
margin-left: -250px;
//padding-top: 15px;
width: fit-content;
display: flex;
justify-content: flex-start;
.bigLi {
margin: 0 25px;
}
height: 100%;
.title {
color: #222F60;
font-size: 18px;
font-weight: bold;
}
.smallUl {
font-size: 16px;
color: #7A82A0;
li {
margin: 10px 0;
}
}
}
.info {
font-size: 14px;
color: #7A82A0;
li {
margin: 10px 0;
}
}
.tel {
color: #222F60;
font-size: 20px;
}
.smallLi {
&:hover {
color: #1b5bff;
cursor: pointer;
}
}
.goStyle {
cursor: pointer;
transition: all .3s ease-in-out;
&:hover {
transition: all .3s ease-in-out;
color: #1b5bff;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753777468651" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6851" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14"><path d="M566.61333333 480.32426667c175.3088 16.384 309.6576 109.7728 309.6576 222.27626666 0 124.5184-163.29386667 225.00693333-364.81706666 225.00693334S147.18293333 827.11893333 147.18293333 703.14666667c0-102.67306667 111.4112-189.50826667 264.32853334-216.2688v103.76533333c0 20.20693333 8.192 39.86773333 22.9376 54.0672 14.7456 14.19946667 34.4064 22.39146667 54.61333333 22.39146667l6.5536-0.54613334c39.86773333-3.2768 70.4512-36.0448 70.99733333-76.45866666V480.32426667z" p-id="6852" fill="#ffffff"></path><path d="M492.88533333 61.44L801.45066667 243.3024c9.8304 5.46133333 12.56106667 18.0224 7.09973333 27.30666667-2.18453333 4.36906667-6.00746667 7.09973333-10.92266667 8.73813333l-277.43573333 97.21173333v207.53066667c0 15.83786667-13.1072 28.94506667-28.94506667 28.94506667s-28.94506667-12.56106667-28.94506666-28.94506667V78.91626667c0-7.09973333 3.82293333-14.19946667 10.37653333-17.47626667 6.00746667-3.82293333 13.65333333-3.82293333 20.20693333 0z" p-id="6853" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753942817241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1738" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14"><path d="M783.53 240.47C641.08 98.02 413.8 91.41 263.09 220.03V167.1c0-17.67-14.33-32-32-32s-32 14.33-32 32v128c0 17.67 14.33 32 32 32h128c17.67 0 32-14.33 32-32s-14.33-32-32-32h-48.15c125.55-101.54 310.66-94.06 427.34 22.62 124.75 124.77 124.75 327.8 0 452.56-124.78 124.75-327.78 124.75-452.56 0C225.28 677.84 192 597.48 192 512c0-17.67-14.33-32-32-32s-32 14.33-32 32c0 102.58 39.94 199.02 112.47 271.53 74.86 74.86 173.19 112.3 271.53 112.3 98.33 0 196.67-37.44 271.53-112.3 149.7-149.72 149.7-393.34 0-543.06z" fill="#333333" p-id="1739"></path><path d="M512 288c-17.67 0-32 14.33-32 32v185.38c0 12.81 5 24.88 14.06 33.94l123.31 123.31c6.25 6.25 14.44 9.38 22.62 9.38s16.38-3.12 22.62-9.38c12.5-12.5 12.5-32.75 0-45.25L544 498.75V320c0-17.67-14.33-32-32-32z" fill="#333333" p-id="1740"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753953602669" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4522" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14"><path d="M830.386555 223.731092c-146.285714 34.420168-172.10084 154.890756-154.890757 223.731093C572.235294 326.991597 572.235294 197.915966 572.235294 0c-326.991597 120.470588-249.546218 473.277311-258.15126 576.537815-77.445378-68.840336-94.655462-223.731092-94.655463-223.731092C133.378151 395.831933 90.352941 516.302521 90.352941 610.957983c0 232.336134 189.310924 413.042017 421.647059 413.042017s421.647059-189.310924 421.647059-413.042017c0-137.680672-103.260504-206.521008-103.260504-387.226891z" fill="#E02020" p-id="4523"></path></svg>

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753777558445" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10925" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14"><path d="M781.1072 971.0592H249.2928c-90.3168 0-163.84-73.5232-163.84-163.84V402.688c0-90.3168 73.5232-163.84 163.84-163.84h531.8144c90.3168 0 163.84 73.5232 163.84 163.84v404.5312c0 90.3168-73.472 163.84-163.84 163.84zM249.2928 300.288c-56.4736 0-102.4 45.9264-102.4 102.4v404.5312c0 56.4736 45.9264 102.4 102.4 102.4h531.8144c56.4736 0 102.4-45.9264 102.4-102.4V402.688c0-56.4736-45.9264-102.4-102.4-102.4H249.2928z" fill="#ffffff" p-id="10926"></path><path d="M691.968 457.8816c-16.9472 0-30.72-13.7728-30.72-30.72V262.5024c0-81.664-66.4064-148.0704-148.0704-148.0704S365.1072 180.8384 365.1072 262.5024v164.6592c0 16.9472-13.7728 30.72-30.72 30.72s-30.72-13.7728-30.72-30.72V262.5024c0-115.5584 94.0032-209.5104 209.5104-209.5104s209.5104 94.0032 209.5104 209.5104v164.6592c0 16.9472-13.7216 30.72-30.72 30.72z" fill="#ffffff" p-id="10927"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753952615473" class="icon" viewBox="0 0 1204 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8296" xmlns:xlink="http://www.w3.org/1999/xlink" width="16.4609375" height="14"><path d="M44.436179 823.338913a44.436179 44.436179 0 0 1-44.436179-44.436179v-285.661149a44.436179 44.436179 0 0 1 89.189759 0v285.661149a44.436179 44.436179 0 0 1-44.436179 44.436179M1159.403385 823.338913a44.436179 44.436179 0 0 1-44.436179-44.436179v-285.661149a44.436179 44.436179 0 0 1 88.872358 0v285.661149a44.436179 44.436179 0 0 1-44.436179 44.436179M802.580869 44.436179a44.436179 44.436179 0 0 1-44.436179 44.436179h-285.661149a44.436179 44.436179 0 0 1 0-88.872358h285.661149a44.436179 44.436179 0 0 1 44.436179 44.436179" fill="#246EFF" p-id="8297"></path><path d="M767.539768 248.080838H660.194656v-160.605046H571.322299v160.605046H436.490236a276.266072 276.266072 0 0 0-275.94867 275.94867v224.021822a276.266072 276.266072 0 0 0 275.94867 275.94867h331.049532a276.266072 276.266072 0 0 0 275.94867-275.94867V524.029508a276.266072 276.266072 0 0 0-275.94867-275.94867z m186.758912 499.970492a187.012832 187.012832 0 0 1-186.758912 186.758911H436.490236a187.012832 187.012832 0 0 1-186.758911-186.758911V524.029508a187.012832 187.012832 0 0 1 186.758911-186.758911h331.049532a187.012832 187.012832 0 0 1 186.758912 186.758911z" fill="#246EFF" p-id="8298"></path><path d="M436.998078 572.464943a71.351807 71.351807 0 0 0-71.351807 71.351807 71.351807 71.351807 0 1 0 71.351807-71.351807zM760.303019 572.464943a71.351807 71.351807 0 0 0-71.351807 71.351807 71.351807 71.351807 0 1 0 71.351807-71.415287z" fill="#246EFF" p-id="8299"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753942811442" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1574" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14"><path d="M544.32 107.91h0.16-0.16M512 173.39l83.56 169.3 14.89 30.17 33.3 4.84 186.84 27.15-135.2 131.78-24.09 23.49 5.69 33.16 31.92 186.08L541.8 691.5 512 675.85l-29.78 15.66-167.11 87.85 31.92-186.08 5.69-33.16-24.09-23.49-135.2-131.78 186.84-27.15 33.3-4.84 14.89-30.17L512 173.39M512 64c-9.97 0-19.95 5.2-25.07 15.59L371.05 314.37l-259.1 37.65c-22.93 3.33-32.09 31.52-15.5 47.69l187.49 182.75-44.26 258.05c-3.1 18.07 11.26 32.75 27.55 32.75 4.3 0 8.73-1.02 13.02-3.27L512 748.16 743.75 870c4.28 2.25 8.72 3.27 13.02 3.27 16.29 0 30.65-14.68 27.55-32.75l-44.26-258.05 187.49-182.75c16.6-16.18 7.44-44.36-15.5-47.69l-259.1-37.65L537.07 79.59C531.95 69.2 521.97 64 512 64z" fill="#333333" p-id="1575"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753777515332" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7923" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14"><path d="M316.034305 645.090181c-16.969487 0-30.789237-13.821797-30.789237-30.79026 0-16.991999 13.820774-30.841426 30.789237-30.841426l376.865253 0c16.993023 0 30.813796 13.847379 30.813796 30.841426 0 16.968463-13.820774 30.79026-30.813796 30.79026L316.034305 645.090181 316.034305 645.090181zM311.444776 811.624055c-16.995069 0-30.815843-13.82282-30.815843-30.81482 0-16.968463 13.81975-30.815843 30.815843-30.815843l376.838647 0c16.994046 0 30.81482 13.847379 30.81482 30.815843 0 16.991999-13.820774 30.81482-30.81482 30.81482L311.444776 811.624055 311.444776 811.624055zM144.125003 1024c-53.214992 0-83.21526-33.604352-83.21526-84.266196L60.909743 116.042923c0-48.669466 24.882714-90.86652 85.261872-90.86652l530.546402 0c12.982686 0 24.206308 6.936993 30.027897 18.554588 5.796006 11.631922 4.64274 24.785499-3.173273 35.168987-6.294356 8.419763-16.33913 13.45443-26.854625 13.45443l-442.508299 0c-19.617803 0-35.196617 0-47.888683 0l-25.255197 0c-7.945973 0-14.319124 0-19.984147 0l-13.034874-0.039909 0 864.495216 771.300674 0-0.024559-13.007245c-0.077771-19.433608-0.052189-33.910321-0.026606-52.241828l0-0.788969c0-11.120268 0.026606-23.813359 0.026606-40.1003L899.322931 361.549429c0-10.476609 4.982478-20.494777 13.323447-26.802436 6.033413-4.510734 12.955056-6.872524 20.062941-6.872524 16.231683 0 33.778314 12.863982 33.778314 33.662681l0 582.291926c0 25.308409-5.506411 44.662199-16.338107 57.485249-12.668531 15.030321-34.146705 22.686698-63.832817 22.686698L144.125003 1024 144.125003 1024zM824.397475 53.905724l7.867178-9.677407c6.531763-8.063653 15.448854-19.092847 20.928658-25.110911 9.31004-10.215666 22.107507-15.631002 37.004799-15.631002 9.335622 0 17.333784 2.085498 22.421662 3.843538 16.944927 5.820566 39.312354 25.387203 49.907667 43.612287 11.434423 19.709901 11.33107 39.969317-0.261966 55.599296-9.389858 12.745279-16.809851 22.212908-19.590174 25.675776l-7.107885 8.955976L824.397475 53.905724 824.397475 53.905724zM614.432439 472.129944c-6.242168 0-11.304464-1.952468-15.080463-5.810333-4.771677-4.889357-6.897084-12.285814-6.661723-23.248494 0.419556-18.712177 10.490935-77.614705 14.320147-90.099041 2.883677-9.388834 6.39771-17.873066 14.160511-28.691459 11.199063-15.552208 150.09139-199.605084 177.812755-236.347916l7.082302-9.360182L917.077294 165.785837l-6.738471 9.009188c-39.235606 52.267411-167.714769 223.508493-174.953637 232.766344-6.374174 8.157797-14.923898 16.194844-22.843264 21.4925-16.548908 11.106965-69.654406 36.911678-87.146802 41.475624C621.276311 471.606011 617.683483 472.129944 614.432439 472.129944L614.432439 472.129944 614.432439 472.129944z" fill="#ffffff" p-id="7924"></path></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753942853047" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1907" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14"><path d="M840.88 330H631.67c-11.17 0-19.3-10.6-16.41-21.38l23.16-86.43c14.45-53.93-17.55-109.36-71.48-123.81A98.616 98.616 0 0 0 541.36 95c-43.88 0-84.3 29.38-96.17 73.66l-19.8 73.88C411.57 294.12 364.82 330 311.41 330H150c-17.67 0-32 14.33-32 32v448c0 17.67 14.33 32 32 32h618.88c30.8 0 57.23-21.94 62.9-52.21l72-384c7.39-39.38-22.82-75.79-62.9-75.79zM182 394h93.63L248.2 778H182V394z m586.88 384H312.37l27.6-386.39c69.97-11.15 128.47-62.44 147.25-132.51l19.8-73.88c4.07-15.2 18.52-26.22 34.35-26.22 3.01 0 6.04 0.4 9.01 1.2 19.76 5.29 31.52 25.68 26.23 45.43l-23.16 86.43c-6.57 24.52-1.47 50.13 13.98 70.26 15.45 20.14 38.87 31.69 64.25 31.69h209.21L768.88 778z" fill="#333333" p-id="1908"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,640 @@
<script lang="ts">
import Vue from 'vue'
import { reqPublishProductSearchFirstPage } from '@/api/ncmatch'
import {mapGetters, mapState} from "vuex";
export default Vue.extend({
name: "mainPage",
components: {
sendProduct: () => import('./sendProduct/index.vue'),
productCard: () => import('./productCard/index.vue'),
search: () => import('../search/index.vue'),
menuAside: () => import('./menuAside/index.vue')
},
created() {
this.init_product_list()
},
data() {
return {
publish_type: null,
sendProductVisible: false,
currentHotMenu: "",
product: [],
hotProductList: [
// {
// id: 1,
// name: 'NVIDIA-4090',
// image: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDIwMCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiBmaWxsPSIjMjIyMjIyIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5WSUQpQSBSVFggNDA5MDwvdGV4dD4KPC9zdmc+Cg==',
// price: 6000,
// cpu: 'AMD EPYC 7542 32 C * 2',
// memory: '64G DDR4-3200 * 8',
// gpu: 'NVIDIA-4090-24GB * 8',
// sys_disk: '960G SATA SSD * 2 (Raid)',
// data_disk: '3.84T U.2 NVMe SSD * 1',
// net_card: 'Mellanox Connect4 25G SFP28 2-port * 1'
// },
// {
// id: 2,
// name: 'NVIDIA-4090',
// image: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDIwMCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiBmaWxsPSIjMjIyMjIyIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5WSUQpQSBSVFggNDA5MDwvdGV4dD4KPC9zdmc+Cg==',
// price: 6000,
// cpu: 'AMD EPYC 7542 32 C * 2',
// memory: '64G DDR4-3200 * 8',
// gpu: 'NVIDIA-4090-24GB * 8',
// sys_disk: '960G SATA SSD * 2 (Raid)',
// data_disk: '3.84T U.2 NVMe SSD * 1',
// net_card: 'Mellanox Connect4 25G SFP28 2-port * 1'
// }
],
hotMenuList: [
{
id: "hot",
name: "热门推荐",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png")
},
{
id: "hlzx",
name: "互联网专线",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "SDWAN",
name: "SDWAN",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "DCI",
name: "DCI",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "AI",
name: "AI专线",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
}
],
searchKeyword: '',
hotSearchKeywords: [
'昆仑芯-P800',
'天垓-150',
'4090',
'云服务器-GPU',
'人脸识别',
'SDWAN',
'互联网专线',
'DCI',
'AI专线',
'对象存储',
'智慧医疗',
'智慧客服',
'A800',
'A100',
'910B'
],
topNavItems: [
{ name: '早十好价', icon: '10', color: 'red' },
{ name: '司法拍卖', icon: '⚖️', color: 'red' },
{ name: '企业购', icon: '🏢', color: 'blue' },
{ name: '京东新品', icon: '🎁', color: 'blue' },
{ name: '男装馆', icon: '👔', color: 'blue' },
{ name: '黑色星期五', icon: '🍃', color: 'green' },
{ name: '服饰美妆', icon: '👗', color: 'purple' },
{ name: '清洁馆', icon: '💧', color: 'blue' },
{ name: '五金城', icon: '⚙️', color: 'red' },
{ name: '全部频道', icon: '📱', color: 'rainbow' }
],
categories: [
{ name: '云', icon: require('./img/cloud.png'), product_list: ['百度云'] },
{ name: '国产算力', icon: require('./img/computing.png'), product_list: ['昇腾910B', 'P800', '其他'] },
{ name: 'NVIDIA', icon: require('./img/nvidia.png'), product_list: ['3090', '4090', '5080', '5090'] },
{ name: '网', icon: require('./img/net.png'), product_list: ['AI专线', 'SDWAN', '互联网专线', 'DCI'] },
{ name: '一体机', icon: require('./img/computer.png'), product_list: ['昆仑芯', '天数智芯'] },
{ name: '硬件', icon: require('./img/ying.png'), product_list: ['机器人', 'AR眼镜'] },
{ name: 'AI应用', icon: require('./img/aiApp.png'), product_list: ['数字人', '智慧医疗', '智能客服'] },
],
current_page: 1,
page_size: 8,
}
},
computed: {
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
isShowPanel: (state) => state.product.showHomeNav,
navIndex: (state) => state.product.navIndex,
gridObj: state => state.operationAnalysis.gridObj,
mybalance: state => state.user.mybalance,
logoutUrl: state => state.login.logoutUrl,
loginStateVuex: state => state.login.loginState,
logoInfoNew: state => state.product.logoInfoNew,
}),
loginState() {
const userId = sessionStorage.getItem('userId');
return this.loginStateVuex || (userId !== null && userId !== 'null' && userId !== '');
},
},
methods: {
sendInfo(type) {
if (this.loginState) {
this.publish_type = type
this.sendProductVisible = true
} else {
this.$router.push('/login')
}
},
init_product_list() {
reqPublishProductSearchFirstPage({ publish_type: "1", url_link: window.location.href, to_page: 'first_page', page_size: this.page_size, current_page: this.current_page, product_type: this.currentHotMenu }).then(res => {
if (res.status) {
if (res.data.length > 0) {
this.currentHotMenu = res.data[0].id
this.hotProductList = res.data[0].product_list
this.product = res.data
}
}
})
},
sendProductSuccess() {
this.sendProductVisible = false; //
this.init_product_list()
},
clickNetMenu(menu) {
this.currentHotMenu = menu.id;
this.hotProductList = menu.product_list || [];
},
handleSearch() {
console.log('搜索:1', this.searchKeyword)
}
}
})
</script>
<template>
<div class="jd-homepage">
<!-- 顶部Header -->
<header class="header">
<div class="header-content">
<!-- 左侧Logo -->
<!-- <div class="logo-section"> -->
<!--
<div class="logo">
<img src="./img/logo.png" alt="">
</div> -->
<!-- </div> -->
</div>
</header>
<!-- 主内容区域 -->
<main class="main-content">
<div class="content-wrapper">
<menuAside></menuAside>
<!-- 中间主内容 -->
<section class="main-section">
<!-- 顶部促销横幅 -->
<div id="banner" class="banner">
<div class="centerBox">
<span style="margin-top: 100px" class="title">
<span class="leftText">
NCMatch &nbsp;&nbsp;
</span>
<span class="rightText" style="margin-left: 10px;"> 您身边的AI管家</span>
</span>
<!-- <span class="description">支持模型训练推理和数据处理灵活配置助您高效释放AI潜能</span>-->
<ul style="margin-top: 50px" class="tagUl">
<li> AI </li>
</ul>
</div>
</div>
</section>
<!-- 右侧用户信息栏 -->
<aside class="user-sidebar" style="background-color: #f8fbfe;padding: 10px;padding-top: 20px;">
<span class="publish-goods" @click="$router.push('/ncmatchHome/supplyAndDemandSquare')"> 算力供需广场</span>
<span class="publish-goods" @click="sendInfo('2')">发布需求</span>
<span class="publish-goods" @click="sendInfo('1')">发布商品</span>
<ul class="userBtn">
<li><img src="./img/eye.png" alt="">浏览记录</li>
<li><img src="./img/collect.png" alt="">收藏商品</li>
<li><img src="./img/like.png" alt="">关注需求</li>
</ul>
</aside>
</div>
</main>
<div class="productList">
<ul class="myTab">
<li v-for="menu in product" :class="currentHotMenu === menu.id ? 'activeMenu' : ''" @click="clickNetMenu(menu)"
:key="menu.name">
<!-- <img :src="currentHotMenu === menu.id ? menu.activeIcon : menu.icon" alt="" /> -->
<span class="tab-text">{{ menu.name }}</span>
</li>
</ul>
<productCard :productList="hotProductList"></productCard>
<el-dialog :title="publish_type === '2' ? '发布需求' : '发布商品'" width="60vw" top="5vh"
:visible.sync="sendProductVisible">
<sendProduct v-if="publish_type" @success="sendProductSuccess" :publish_type="publish_type"></sendProduct>
</el-dialog>
</div>
</div>
</template>
<style scoped lang="scss">
.userBtn {
margin-top: 40px !important;
display: flex;
justify-content: space-around;
align-items: center;
li {
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
&:hover {
transition: all 0.3s;
cursor: pointer;
color: #2c96fc;
svg {
path {
fill: #2c96fc !important;
}
}
}
img {
width: 16px;
height: 16px;
}
}
}
.productList {
width: 100%;
max-width: 1600px;
margin: 0 auto;
padding-bottom: 45px;
}
.publish-goods {
margin-bottom: 25px;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 60px;
border-radius: 10px;
background: url('./img//btnBg.png');
background-size: cover;
background-position: top;
color: #222F60 !important;
font-size: 18px;
font-weight: 900;
&:hover {
cursor: pointer;
// background: #2EBDFA;
}
}
.banner {
border-radius: 10px;
background: url("../../newImg/banner.png");
width: 100%;
background-size: cover;
background-position: center;
position: relative;
height: 100%;
.centerBox {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
position: absolute;
top: 35%;
left: 50%;
width: 100%;
transform: translate(-50%, -50%);
.title {
color: #222F60;
font-size: 42px;
display: flex;
.leftText {
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
}
}
.description {
margin: 15px 0;
color: #7A82A0;
font-size: 24px;
height: 75px;
line-height: 1.6;
}
.tagUl {
color: #222F60;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
li {
padding: 0 30px;
margin-top: 25px;
display: flex;
justify-content: center;
align-items: center;
&:not(:last-child) {
border-right: 2px solid #D2D7E6;
}
}
}
}
}
.myTab {
width: 100%;
font-size: 18px;
border-radius: 16px;
background: #f7f8fc;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
.activeMenu {
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
color: white;
}
li {
transition: all .2s ease-in-out;
z-index: 2;
padding: 10px 20px;
margin-right: 45px;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
&:hover {
cursor: pointer !important;
}
img {
width: 24px;
height: 24px;
margin-right: 5px;
}
}
}
.jd-homepage {
min-height: 100vh;
background-color: #f8fbfe;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
// Header
.header {
padding: 10px 0;
display: flex;
justify-content: flex-start;
align-items: center;
position: relative;
height: 100px;
.header-content {
width: 100%;
max-width: 950px;
// margin: 0 auto;
margin-top: 15px;
display: flex;
align-items: center;
// gap: 20px;
position: absolute;
left: 420px;
}
.logo-section {
margin: 0 50px;
img {
height: 70px;
width: 70px;
}
.logo {
display: flex;
align-items: center;
height: 100%;
.logo-text {
font-size: 24px;
font-weight: bold;
color: #e1251b;
}
.logo-mascot {
font-size: 20px;
}
}
}
.signin-section {
.signin-btn {
background: linear-gradient(45deg, #ffd700, #ff6b35);
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 5px;
.signin-icon {
font-weight: bold;
}
}
}
}
//
.main-content {
padding: 16px;
width: 100%;
max-width: 1600px;
margin: 20px auto;
margin-top: 10px;
border-radius: 10px;
overflow: hidden;
background-color: white;
height: 360px;
.content-wrapper {
box-sizing: border-box;
display: grid;
grid-template-columns: 300px 1fr 250px;
gap: 20px;
}
}
//
.main-section {
.promo-banner {
background: linear-gradient(45deg, #52c41a, #73d13d);
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
position: relative;
height: 100%;
.banner-content {
display: flex;
justify-content: space-between;
align-items: center;
.banner-text {
color: white;
h3 {
margin: 0 0 10px 0;
font-size: 24px;
}
p {
margin: 5px 0;
font-size: 16px;
}
}
.banner-image {
position: relative;
.fridge-img {
width: 120px;
height: 90px;
object-fit: cover;
}
.flower-decoration {
position: absolute;
bottom: 0;
right: 0;
font-size: 20px;
}
}
}
}
.subsidy-section,
.live-section,
.seckill-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.official-tag,
.live-tag,
.seckill-tag {
background: #e1251b;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
}
}
}
.user-sidebar {
height: 100%;
}
//
@media (max-width: 1200px) {
.main-content .content-wrapper {
grid-template-columns: 180px 1fr 220px;
gap: 15px;
}
}
@media (max-width: 768px) {
.main-content .content-wrapper {
grid-template-columns: 1fr;
}
.category-sidebar,
.user-sidebar {
display: none;
}
.header .header-content {
flex-direction: column;
gap: 10px;
}
.search-section {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,39 @@
export function buildDynamicStructure(data, parentId = "None", currentLevel = 1) {
// 1. 找出当前层级的节点
const currentNodes = data.filter(item => item.parentid === parentId);
if (currentNodes.length === 0) return [];
// 2. 处理每个节点
return currentNodes.map(node => {
const resultNode = {};
// 设置层级名称字段
if (currentLevel === 1) {
resultNode.first_level_name = node.name;
resultNode.icon = node.icon || require('../img/cloud.png');
} else if (currentLevel === 2) {
resultNode.second_level_name = node.name;
} else if (currentLevel === 3) {
resultNode.third_level_name = node.name;
}
// 3. 递归处理子节点
const children = buildDynamicStructure(data, node.id, currentLevel + 1);
// 4. 根据当前层级设置子节点字段
if (children.length > 0) {
if (currentLevel === 1) {
resultNode.secondaryClassification = children;
} else if (currentLevel === 2) {
resultNode.thirdClassification = children;
} else if (currentLevel === 3) {
// 第四级特殊处理为product_list
resultNode.product_list = children.map(child => ({
first_level_name: child.third_level_name || child.name
}));
}
}
return resultNode;
});
}

View File

@ -0,0 +1,502 @@
<template>
<!-- 左侧分类导航 -->
<aside class="category-sidebar">
<ul class="category-list">
<li class="category-item" style="color: #E02020;"><img src="../img/hot.svg" style="margin-right: 10px;"
alt=""> 热门推荐 / 活动促销</li>
<li v-for="category in categories" :key="category.first_level_name" class="category-item"
@mouseenter="showProductList(category)" @mouseleave="hideProductList">
<span class="category-icon"> <img style="width: 24px;height: 24px;" :src="category.icon" alt=""> </span>
<span class="category-name">{{ category.first_level_name }}</span>
<span style="display: flex;margin-left: 0px;padding-left: 0px;">|</span>
<div class="menu-item">
<span v-for="(secondary, index) in category.secondaryClassification"
:key="secondary.second_level_name">
{{ secondary.second_level_name }}{{ index < category.secondaryClassification.length - 1 ? ' / '
: '' }} </span>
</div>
</li>
</ul>
<transition name="slide-fade">
<!-- v-if="currentCategory" -->
<div class="rightBox" v-if="currentCategory" @mouseenter="keepProductList" @mouseleave="hideProductList">
<div class="rightBox-content">
<!-- 二级菜单标题 -->
<div class="secondary-menu">
<div v-for="secondary in currentCategory.secondaryClassification"
:key="secondary.second_level_name" class="secondary-item"
:class="{ active: selectedSecondary === secondary }"
@mouseenter="selectSecondary(secondary)">
{{ secondary.second_level_name }}
</div>
</div>
<!-- 三级和四级菜单内容 -->
<div class="menu-content">
<!-- 如果有选中的二级菜单且有三级菜单显示京东风格的分类区域 -->
<div v-if="selectedSecondary && selectedSecondary.thirdClassification && selectedSecondary.thirdClassification.length > 0"
class="jd-style-menu">
<div v-for="third in selectedSecondary.thirdClassification" :key="third.third_level_name"
class="category-section">
<!-- 只有当有四级菜单时才显示三级菜单标题 -->
<div v-if="third.product_list && third.product_list.length > 0" class="section-header">
<span class="section-title">{{ third.third_level_name }}</span>
<span class="section-arrow">></span>
</div>
<div class="section-content">
<!-- 如果有四级菜单product_list直接显示所有四级菜单项 -->
<div v-if="third.product_list && third.product_list.length > 0"
class="product-grid">
<div v-for="(product, index) in third.product_list"
:key="product.first_level_name" class="product-tag">
{{ product.first_level_name }}
</div>
</div>
<!-- 如果没有四级菜单将三级菜单项视为四级菜单项显示 -->
<div v-else class="product-grid">
<div class="product-tag ">
{{ third.third_level_name }}
</div>
</div>
</div>
</div>
</div>
<!-- 如果没有三级菜单显示二级菜单项 -->
<div v-else-if="selectedSecondary" class="jd-style-menu">
<div class="category-section">
<div class="section-header">
<span class="section-title">{{ selectedSecondary.second_level_name }}</span>
<span class="section-arrow">></span>
</div>
<div class="section-content">
<div class="product-grid">
<div class="product-tag hot-tag">
{{ selectedSecondary.second_level_name }}
</div>
</div>
</div>
</div>
</div>
<!-- 默认显示所有二级菜单项 -->
<div v-else class="jd-style-menu">
<div v-for="secondary in currentCategory.secondaryClassification"
:key="secondary.second_level_name" class="category-section">
<div class="section-header">
<span class="section-title">{{ secondary.second_level_name }}</span>
<span class="section-arrow">></span>
</div>
<div class="section-content">
<div class="product-grid">
<div class="product-tag ">
{{ secondary.second_level_name }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</transition>
</aside>
</template>
<script>
import { reqNcMatchMenu } from '@/api/ncmatch';
import { buildDynamicStructure } from './buildNcmatchTree';
export default {
name: 'menuAside',
data() {
return {
currentCategory: null,
selectedSecondary: null,
hideTimer: null,
categories: [
{
first_level_name: '云', icon: require('../img/cloud.png'), secondaryClassification: [
{
second_level_name: '百度云',
thirdClassification: [
{
third_level_name: '计算',
product_list: [{ first_level_name: 'ECS1' }, { first_level_name: 'ECS2' }, { first_level_name: 'ECS3' }]
},
{
third_level_name: '存储',
product_list: [{ first_level_name: 'BOS1' }, { first_level_name: 'BOS2' }]
}
]
},
{
second_level_name: '阿里云',
thirdClassification: [
{
third_level_name: '计算',
product_list: [{ first_level_name: 'ECS-A' }, { first_level_name: 'ECS-B' }]
}
]
}
]
},
{
first_level_name: '国产算力', icon: require('../img/cloud.png'), secondaryClassification: [
{
second_level_name: '昇腾910B',
thirdClassification: [
{
third_level_name: '昇腾910B1',
},
{
third_level_name: '昇腾910B2',
}
]
},
{
second_level_name: '昆仑芯',
thirdClassification: [
{
third_level_name: '昆仑芯A',
},
{
third_level_name: '昆仑芯B',
}
]
}
]
}
],
}
},
created() {
this.getCategories();
},
methods: {
getCategories() {
reqNcMatchMenu({ url_link: window.location.href }).then(res => {
if (res.status) {
this.categories = buildDynamicStructure(res.data)
// this.categories = res.data.map(item => {
// return {
// name: item.name,
// icon: item.icon,
// product_list: item.product_list
// }
// });
}
})
},
showProductList(category) {
//
if (this.hideTimer) {
clearTimeout(this.hideTimer);
this.hideTimer = null;
}
this.currentCategory = category;
//
if (category.secondaryClassification && category.secondaryClassification.length > 0) {
this.selectedSecondary = category.secondaryClassification[0];
} else {
this.selectedSecondary = null;
}
},
selectSecondary(secondary) {
this.selectedSecondary = secondary;
},
keepProductList() {
//
if (this.hideTimer) {
clearTimeout(this.hideTimer);
this.hideTimer = null;
}
},
hideProductList() {
// rightBox
this.hideTimer = setTimeout(() => {
this.currentCategory = null;
this.selectedSecondary = null;
this.hideTimer = null;
}, 200);
}
}
}
</script>
<style scoped lang="scss">
.menu-item {
flex: 1;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
/* 确保flex子项可以收缩 */
}
//
.category-sidebar {
background-color: #f8fbfe;
height: 100%;
border-radius: 10px;
// padding: 15px;
padding: 5px;
.category-list {
list-style: none;
padding: 0;
margin: 0;
.category-item {
display: flex;
margin-bottom: 10px;
align-items: center;
gap: 10px;
padding: 4px 0;
cursor: pointer;
transition: all 0.3s;
padding: 0 10px;
&:hover {
color: #2c96fc !important;
background: #c3daee !important;
}
&:last-child {
border-bottom: none;
}
.category-icon {
width: 20px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.category-name {
font-size: 14px;
}
}
}
}
@media (max-width: 768px) {
.main-content .content-wrapper {
grid-template-columns: 1fr;
}
.category-sidebar,
.user-sidebar {
display: none;
}
.header .header-content {
flex-direction: column;
gap: 10px;
}
.search-section {
width: 100%;
}
}
.rightBox {
position: absolute;
left: 100%;
top: 0;
width: 900px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
padding: 25px;
min-height: 400px;
margin-left: 10px;
/* 添加一个透明的连接区域,防止鼠标移动时意外消失 */
&::before {
content: '';
position: absolute;
left: -10px;
top: 0;
width: 10px;
height: 100%;
background: transparent;
}
.rightBox-content {
.secondary-menu {
line-height: 1.5;
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
.secondary-item {
padding: 4px 8px;
background: #f8f9fa;
border-radius: 4px;
cursor: pointer;
display: flex;
transition: all 0.3s ease;
font-size: 12px;
color: #333;
font-weight: 500;
&:hover,
&.active {
background-color: #ffebf1;
color: #ff0f23;
}
}
}
.menu-content {
min-height: 200px;
.jd-style-menu {
width: 100%;
display: flex;
flex-direction: column;
gap: 20px;
border: 1px solid red;
.category-section {
display: flex;
justify-content: flex-start;
align-items: center;
border: 1px solid blue;
.section-header {
display: flex;
align-items: center;
.section-title {
font-size: 12px;
font-weight: 600;
color: #333;
border: 1px solid red;
;
margin-right: 8px;
}
.section-arrow {
color: #999;
font-size: 12px;
}
}
.section-content {
line-height: 1.5;
.product-grid {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
.product-tag {
border: 1px solid red;
height: fit-content !important;
background: #f8f9fa;
font-size: 12px;
color: #666;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
position: relative;
&:hover {
color: #e1251b;
}
}
}
}
}
}
.hot-tag {
color: #e1251b !important;
border-color: #e1251b !important;
background: #fff5f5 !important;
font-weight: 500 !important;
position: relative;
&::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background: #e1251b;
border-radius: 0 0 4px 4px;
}
}
.product-list {
flex: 1;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
align-content: start;
justify-content: center;
.product-item {
width: fit-content;
border-radius: 6px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
* {
color: #2c96fc;
}
}
.product-name {
font-size: 14px;
color: #333;
font-weight: 500;
}
}
}
}
}
}
.category-sidebar {
position: relative;
}
/* 过渡动画样式 */
.slide-fade-enter-active {
transition: all 0.3s ease;
}
.slide-fade-leave-active {
transition: all 0.3s ease;
}
.slide-fade-enter {
// transform: translateX(-20px);
opacity: 0;
}
.slide-fade-leave-to {
// transform: translateX(-20px);
opacity: 0;
}
.slide-fade-enter-to,
.slide-fade-leave {
// transform: translateX(0);
opacity: 1;
}
</style>

View File

@ -0,0 +1,304 @@
<template>
<!-- 产品列表 -->
<div>
<ul v-if="type === 'homePage'" class="productListContent" style="padding-top: 16px;">
<li class="product-item" v-for="item in productList" :key="item.id">
<div v-if="item.img!=null" class="product-image">
<img :src="item.img" style="min-width: 278px;min-height: 185px;" :alt="item.name">
</div>
<div class="product-info">
<h3 class="product-name">{{ item.product_name }}</h3>
<div class="product-specs">
<div class="spec-item" v-if="type === 'homePage'">
<span class="spec-label">企业名称:</span>
<span class="spec-value">{{ item.company_name }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">CPU:</span>
<span class="spec-value">{{ item.cpu }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">内存:</span>
<span class="spec-value">{{ item.memory }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">GPU:</span>
<span class="spec-value">{{ item.gpu }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">系统盘:</span>
<span class="spec-value">{{ item.sys_disk }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">数据盘:</span>
<span class="spec-value">{{ item.data_disk }}</span>
</div>
<div v-if="item.cart_flag==='1'" class="spec-item">
<span class="spec-label">网卡:</span>
<span class="spec-value">{{ item.net_card }}</span>
</div>
<div v-if="item.cart_flag!=='1'" class="spec-item">
<span cl ass="spec-label">商品描述:</span>
<span class="spec-value showText">{{ item.requirement_summary }}</span>
</div>
<div v-if="item.cart_flag!=='1'" class="spec-item">
<span class="spec-label">相关参数:</span>
<span class="spec-value showText">{{ item.related_parameters }}</span>
</div>
<div v-if="item.cart_flag!=='1'" class="spec-item">
<span class="spec-label">应用场景:</span>
<span class="spec-value showText">{{ item.application_scenario }}</span>
</div>
</div>
<div class="product-price">
<span class="price">¥{{ item.discount_price }}</span>
<span class="price-unit">{{ item.unit }} {{ item.short_term === '1' ? '(可短租)' : '' }} </span>
</div>
<div style="display: flex;justify-content: space-between;align-items: center;">
<el-button :disabled="loadingStates[item.id]" class="consult-btn" @click="openTalk">立即咨询</el-button> <span @click="openDetail(item)"
class="detail-btn">详情 <i v-if="loadingStates[item.id]" class="el-icon-loading"></i> <span v-else>>></span> </span>
</div>
</div>
</li>
</ul>
<ul v-else-if="type === 'supplyAndDemandSquare'" class="productListContent" style="padding: 16px;">
<li class="product-item" v-for="item in productList" :key="item.id">
<div v-if="item.img!=null" class="product-image">
<img :src="item.img" style="min-width: 278px;min-height: 185px;" :alt="item.name">
</div>
<div class="product-info">
<h3 class="product-name">{{ item.product_name }}</h3>
<div class="product-specs">
<div class="spec-item">
<span class="spec-label">企业名称:</span>
<span class="spec-value">{{ item.company_name }}</span>
</div>
<div class="spec-item">
<span class="spec-label">所属类别:</span>
<span class="spec-value">{{ item.company_type }}</span>
</div>
<div class="spec-item">
<span class="spec-label">更新日期:</span>
<span class="spec-value">{{ item.update_time }}</span>
</div>
<div class="spec-item">
<span class="spec-label">{{ publish_type === '1' ? '商品描述' : '需求描述' }}:</span>
<span class="spec-value showText">{{ item.requirement_summary }}</span>
</div>
</div>
<div style="display: flex;justify-content: space-between;align-items: center;">
<el-button :disabled="loadingStates[item.id]" class="consult-btn" @click="openTalk">立即咨询</el-button> <span @click="openDetail(item)"
class="detail-btn">详情 <i v-if="loadingStates[item.id]" class="el-icon-loading"></i> <span v-else>>></span> </span>
</div>
</div>
</li>
</ul>
<Talk></Talk>
</div>
</template>
<script>
import Talk from '@/views/homePage/dialog/talk/index.vue'
import { reqGetProductDetail } from '@/api/ncmatch'
export default {
name: 'productCard',
components: {
Talk
},
props: {
productList: {
type: Array,
default: () => []
},
type: {
type: String,
default: 'homePage'
},
publish_type: {
type: String,
default: '1'
}
},
data(){
return{
loadingStates: {}
}
},
methods: {
openTalk() {
this.$store.commit('setShowTalk', true);
},
async openDetail(item) {
this.$set(this.loadingStates, item.id, true);
reqGetProductDetail({
id: item.id,
from:'f'
}).then(res => {
if(res.status){
this.$store.commit('SETPRODUCTDETAIL', res.data);
this.$store.commit('SHOWPRODUCTDETAIL', true);
this.$set(this.loadingStates, item.id, false);
}else{
this.$message.error(res.msg);
this.$set(this.loadingStates, item.id, false);
}
})
}
}
}
</script>
<style scoped lang="scss">
.showText {
//
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
word-break: break-all;
}
.productListContent {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
// padding: 16px;
padding-top: 16px;
width: 100%;
max-width: 1600px;
margin: 0 auto;
background-color: white;
.product-item {
width: 100%;
background: #F7F9FD;
border-radius: 12px;
// box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 24px;
margin-bottom: 20px;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
// &:hover {
// transform: translateY(-2px);
// box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
// }
.product-image {
margin-bottom: 20px;
text-align: center;
flex-shrink: 0;
img {
width: 100%;
max-width: 300px;
height: 200px;
object-fit: cover;
border-radius: 8px;
background: #1a1a1a;
}
}
.product-info {
display: flex;
flex-direction: column;
height: 100%;
.product-name {
font-size: 24px;
font-weight: bold;
color: #333;
margin: 0 0 16px 0;
text-align: start;
}
.product-specs {
margin-bottom: 20px;
flex: 1;
.spec-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.spec-label {
color: #222F60 !important;
font-size: 14px;
font-weight: 900;
min-width: 80px;
}
.spec-value {
color: #666;
font-size: 14px;
text-align: right;
flex: 1;
margin-left: 16px;
}
}
}
.product-price {
text-align: start;
margin-bottom: 20px;
.price {
font-size: 28px;
font-weight: bold;
color: #e1251b;
margin-right: 8px;
}
.price-unit {
font-size: 14px;
color: #666;
}
}
.consult-btn {
width: 100%;
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 4px 12px rgba(39, 90, 255, 0.3);
}
}
.detail-btn {
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 14px;
color: #275AFF;
cursor: pointer;
min-width: 50px;
margin-left: 25px;
}
}
}
}
</style>

View File

@ -0,0 +1,19 @@
export const buildCaTree = (data,parentid=null) => {
const tree = [];
for (const item of data) {
if (item.parentid === parentid) {
const children = buildCaTree(data, item.id);
let i = {
label:item.product_category,
value:item.id,
cart_flag:item.cart_flag
}
if (children.length > 0) {
i.children = children;
}
tree.push(i);
}
}
return tree;
};

View File

@ -0,0 +1,986 @@
<template>
<div class="form-container">
<el-form :model="form" :rules="rules" ref="form" label-width="120px" class="two-column-form">
<!-- 商品图片 - 单独一行 -->
<el-form-item v-if="publish_type === '1'" :label="publish_type === '2' ? '需求图片' : '商品图片'" prop="img"
class="full-width">
<div class="upload-area" @click="!selectedImage && triggerFileInput()">
<input ref="fileInput" type="file" accept="image/*" @change="handleFileChange"
style="display: none;">
<div v-if="!selectedImage" class="upload-placeholder">
<i class="el-icon-plus" style="font-size: 40px;"></i>
</div>
<div v-else class="image-preview">
<img :src="selectedImage" alt="预览图" @click="previewImage">
<div class="preview-overlay">
<i class="el-icon-zoom-in"></i>
</div>
</div>
</div>
<!-- 图片操作按钮 -->
<div v-if="selectedImage" class="image-actions">
<el-button size="mini" type="primary" @click="previewImage">预览图片</el-button>
<el-button size="mini" type="primary" @click="openCropper">裁剪图片</el-button>
<el-button size="mini" type="danger" @click="removeImage">删除</el-button>
</div>
</el-form-item>
<el-form-item :label="publish_type === '2' ? '需求名称' : '商品名称'" prop="product_name" required>
<el-input v-model="form.product_name"
:placeholder="publish_type === '2' ? '请输入需求名称' : '请输入商品名称'"></el-input>
</el-form-item>
<!-- 表单项直接排列 -->
<div class="form-row">
<el-form-item label="所属类别" prop="product_category" required class="form-item-half">
<el-cascader v-model="form.product_category" style="width: 100%;" :options="typeList"
@change="handleChange"></el-cascader>
</el-form-item>
<el-form-item label="企业名称" prop="company_name" required class="form-item-half">
<el-input v-model="form.company_name" placeholder="请输入企业名称"></el-input>
</el-form-item>
</div>
<el-form-item label="公司类别" prop="company_type" required class="full-width">
<el-checkbox-group v-model="form.company_type">
<el-checkbox v-for="item in company_category_list" :key="item.value"
:label="item.label"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<div class="form-row">
<el-form-item label="联系人" prop="contact_person" required class="form-item-half">
<el-input v-model="form.contact_person" placeholder="请输入联系人姓名"></el-input>
</el-form-item>
<el-form-item label="职务" prop="job_title" class="form-item-half">
<el-input v-model="form.job_title" placeholder="请输入职务"></el-input>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="手机号码" prop="phone_number" required class="form-item-half">
<el-input v-model="form.phone_number" placeholder="请输入手机号码"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email" class="form-item-half">
<el-input v-model="form.email" placeholder="请输入邮箱地址"></el-input>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="GPU支持" prop="cart_flag" required class="form-item-half">
<el-radio-group v-model="form.cart_flag">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
</div>
<div class="form-row" v-if="form.cart_flag == '1'">
<el-form-item label="CPU" prop="cpu" class="form-item-half">
<el-input v-model="form.cpu" placeholder="请输入CPU规格"></el-input>
</el-form-item>
<el-form-item label="内存" prop="memory" class="form-item-half">
<el-input v-model="form.memory" placeholder="请输入内存规格"></el-input>
</el-form-item>
</div>
<div class="form-row" v-if="form.cart_flag == '1'">
<el-form-item label="GPU" prop="gpu" class="form-item-half">
<el-input v-model="form.gpu" placeholder="请输入GPU规格"></el-input>
</el-form-item>
<el-form-item label="系统盘" prop="sys_disk" class="form-item-half">
<el-input v-model="form.sys_disk" placeholder="请输入系统盘规格"></el-input>
</el-form-item>
</div>
<div class="form-row" v-if="form.cart_flag == '1'">
<el-form-item label="数据盘" prop="data_disk" class="form-item-half">
<el-input v-model="form.data_disk" placeholder="请输入数据盘规格"></el-input>
</el-form-item>
<el-form-item label="网卡" prop="net_card" class="form-item-half">
<el-input v-model="form.net_card" placeholder="请输入网卡规格"></el-input>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="价格" prop="price" required class="form-item-half">
<div class="price-unit-container">
<el-input v-model="form.price" placeholder="请输入价格" class="price-input"></el-input>
<el-input v-model="form.unit" placeholder="单位" class="unit-input"></el-input>
</div>
</el-form-item>
<el-form-item label="是否短租" prop="short_term" required class="form-item-half">
<el-radio-group v-model="form.short_term">
<el-radio label="1"></el-radio>
<el-radio label="2"></el-radio>
</el-radio-group>
</el-form-item>
</div>
<div class="form-row">
<el-form-item label="折扣" class="full-width">
<el-input-number :controls="false" v-model="form.discount" :precision="2" :step="0.01" :max="10">
</el-input-number>
<span style="margin-left: 10px; font-weight: bold;"> </span>
<!-- <el-input v-model="form.discount" placeholder="请输入折扣" type="number">
<template slot="append"></template>
</el-input> -->
</el-form-item>
<!-- <el-form-item label="折扣后价格" class="full-width">
<span>{{ form.price * form.discount }}</span>
</el-form-item> -->
</div>
<el-form-item :label="publish_type === '2' ? '需求概述' : '商品概述'" prop="requirement_summary" required
class="full-width">
<el-input type="textarea" v-model="form.requirement_summary" :rows="6"
:placeholder="publish_type === '2' ? '请输入需求概述' : '请输入商品概述'"></el-input>
</el-form-item>
<el-form-item label="相关参数" prop="related_parameters" class="full-width">
<el-input type="textarea" v-model="form.related_parameters" :rows="6" placeholder="请输入相关参数"></el-input>
</el-form-item>
<el-form-item label="应用场景" prop="application_scenario" class="full-width">
<el-input type="textarea" v-model="form.application_scenario" :rows="6"
placeholder="请输入应用场景"></el-input>
</el-form-item>
<!-- 提交按钮 -->
<div class="form-actions">
<el-button type="primary" @click="submitForm" size="large">{{ publish_type === '2' ? '发布需求' : '发布商品'
}}</el-button>
<el-button @click="resetForm" size="large">重置</el-button>
<!-- <el-button type="info" @click="getBinaryData" size="large">获取二进制数据</el-button> -->
</div>
</el-form>
<!-- 图片裁剪弹窗 -->
<el-dialog title="图片裁剪" :visible.sync="showCropper" width="60%" top="5vh" :before-close="closeCropper"
custom-class="cropper-dialog" :append-to-body="true" :modal-append-to-body="true" :z-index="9999"
:destroy-on-close="true">
<div class="cropper-container">
<vue-cropper ref="cropper" :img="cropperImage" :outputSize="1" :info="true" :full="true" :canMove="true"
:canMoveBox="true" :original="false" :autoCrop="true" :autoCropWidth="300" :autoCropHeight="200"
:fixed="false" :centerBox="true" :infoTrue="true" :high="true" mode="cover" :maxImgSize="2000"
:enlargeImg="1" :limitMinSize="[100, 100]" @realTime="realTime" />
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="closeCropper">取消</el-button>
<el-button type="primary" @click="cropImage">确认裁剪</el-button>
</div>
</el-dialog>
<!-- 图片预览弹窗 -->
<el-dialog title="图片预览" :visible.sync="showPreview" width="50%" center top="5vh" :append-to-body="true"
:modal-append-to-body="true" :z-index="9998" :destroy-on-close="true" custom-class="preview-dialog">
<div class="preview-container">
<img :src="selectedImage" alt="预览图" class="preview-image">
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="showPreview = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { VueCropper } from 'vue-cropper'
import { buildCaTree } from './buildCaTree'
import { reqGetProductCategorySearch, reqPublishProductAdd, reqCompanyCategorySearch } from '@/api/ncmatch'
export default {
name: 'sendProduct',
props: {
publish_type: {
type: String,
default: '1' // 'product'
}
},
components: {
VueCropper
},
data() {
return {
company_category_list: [],
typeList: [
],
current_product_category: {},//
selectedImage: null,
showCropper: false,
showPreview: false,
cropperImage: '',
inputVisible: false,
inputValue: '',
form: {
url_link: window.location.href,
img: null, //
product_name: "",//
product_category: "",//
company_name: "",//
company_type: [],//
contact_person: "",//
job_title: "",//
phone_number: "",//
email: "",//
cpu: "",//CPU
memory: "",//
gpu: "",//GPU
sys_disk: "",//
data_disk: "",//
net_card: "",//
priority: "",//
price: "",//
unit: "",//
short_term: "",//
label: "",//
requirement_summary: "",//
related_parameters: "",//
application_scenario: "",//
unit: "",//
short_term: "",//
discount:null,//
cart_flag: '1',//GPU
},
rules: {
product_name: [
{ required: true, message: '请输入商品名称', trigger: 'blur' }
],
product_category: [
{ required: true, message: '请选择所属类别', trigger: 'change' }
],
company_name: [
{ required: true, message: '请输入企业名称', trigger: 'blur' }
],
company_type: [
{ required: true, message: '请选择公司类别', trigger: 'change' }
],
contact_person: [
{ required: true, message: '请输入联系人', trigger: 'blur' }
],
phone_number: [
{ required: true, message: '请输入手机号码', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
],
cpu: [
{ required: true, message: '请输入CPU规格', trigger: 'blur' }
],
memory: [
{ required: true, message: '请输入内存规格', trigger: 'blur' }
],
gpu: [
{ required: true, message: '请输入GPU规格', trigger: 'blur' }
],
sys_disk: [
{ required: true, message: '请输入系统盘规格', trigger: 'blur' }
],
data_disk: [
{ required: true, message: '请输入数据盘规格', trigger: 'blur' }
],
net_card: [
{ required: true, message: '请输入网卡规格', trigger: 'blur' }
],
price: [
{ required: true, message: '请输入价格和单位', trigger: 'blur' }
],
unit: [
{ required: true, message: '请输入单位', trigger: 'blur' }
],
short_term: [
{ required: true, message: '请选择是否短租', trigger: 'change' }
],
img: [
{
required: true,
validator: (rule, value, callback) => {
if (!value) {
callback(new Error('请上传商品图片'))
} else {
callback()
}
},
trigger: 'change'
}
],
requirement_summary: [
{ required: true, message: '请输入商品概述', trigger: 'blur' }
]
}
}
},
created() {
this.init_product_category()
this.init_company_category()
},
methods: {
init_company_category() {
reqCompanyCategorySearch({ url_link: window.location.href }).then(res => {
if (res.status) {
this.company_category_list = []
for (let item of res.data) {
this.company_category_list.push({
label: item.company_category,
value: item.company_category
})
}
}
})
},
// base64Blob
base64ToBlob(base64, mimeType = 'image/jpeg') {
const byteString = atob(base64.split(',')[1]);
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: mimeType });
},
init_product_category() {
// publish_type to_page
reqGetProductCategorySearch({ url_link: window.location.href, to_page: 'publish' }).then(res => {
if (res.status) {
this.typeList = buildCaTree(res.data)
}
})
},
//
triggerFileInput() {
this.$refs.fileInput.click()
},
//
handleFileChange(event) {
const file = event.target.files[0]
if (file) {
//
if (file.size > 5 * 1024 * 1024) {
this.$message.error('图片大小不能超过5MB')
return
}
//
if (!file.type.startsWith('image/')) {
this.$message.error('请选择图片文件')
return
}
console.log('选择的文件:', file)
console.log('文件类型:', file.type)
console.log('文件大小:', file.size)
// img
this.form.img = file
// URL
const imageUrl = URL.createObjectURL(file)
this.selectedImage = imageUrl
console.log('保存到 form.img:', this.form.img)
this.$refs.form.validateField('img')
}
},
//
openCropper() {
if (this.form.img) {
// Filebase64
const reader = new FileReader()
reader.onload = (e) => {
this.cropperImage = e.target.result
this.showCropper = true
// DOM
this.$nextTick(() => {
if (this.$refs.cropper) {
this.$refs.cropper.refresh()
}
// dialog
setTimeout(() => {
const dialogWrapper = document.querySelector('.cropper-dialog')
if (dialogWrapper) {
dialogWrapper.style.zIndex = '9999'
}
}, 100)
})
}
reader.readAsDataURL(this.form.img)
}
},
//
closeCropper() {
this.showCropper = false
this.cropperImage = ''
},
//
realTime(data) {
//
console.log('裁剪实时数据:', data)
},
//
cropImage() {
this.$refs.cropper.getCropData((data) => {
// data base64
// base64Blob
const blob = this.base64ToBlob(data)
this.form.img = blob
// URL
this.selectedImage = URL.createObjectURL(blob)
this.$message.success('图片裁剪成功!')
this.closeCropper()
})
},
//
removeImage() {
// URL
if (this.selectedImage) {
URL.revokeObjectURL(this.selectedImage)
}
this.selectedImage = null
this.form.img = null
this.$refs.fileInput.value = ''
this.$refs.form.validateField('img')
},
//
previewImage() {
if (this.selectedImage) {
this.showPreview = true
}
},
//
removeTag(tag) {
this.form.companyTags.splice(this.form.companyTags.indexOf(tag), 1)
},
showInput() {
this.inputVisible = true
this.$nextTick(_ => {
this.$refs.saveTagInput.$refs.input.focus()
})
},
handleInputConfirm() {
let inputValue = this.inputValue
if (inputValue) {
this.form.companyTags.push(inputValue)
}
this.inputVisible = false
this.inputValue = ''
},
//
submitForm() {
this.$refs.form.validate((valid) => {
if (valid) {
console.log('表单数据:', this.form)
console.log('发布类型:', this.publish_type)
let formdata = new FormData();
for (let key in this.form) {
formdata.append(key, this.form[key]);
}
formdata.append('publish_type', this.publish_type)
//
// formdata.append('publish_type', this.publish_type);
reqPublishProductAdd(formdata).then(res => {
if (res.status) {
this.$emit('success');
const successMessage = this.publish_type === '2' ? '添加需求成功!' : '添加产品成功!'
this.$message.success(successMessage)
}
})
} else {
this.$message.error('请完善表单信息')
}
})
},
//
resetForm() {
// URL
if (this.selectedImage) {
URL.revokeObjectURL(this.selectedImage)
}
this.$refs.form.resetFields()
this.selectedImage = null
this.form.img = null
this.$refs.fileInput.value = ''
},
//
handleChange(value) {
console.log('选中的id数组:', value)
//
function findNodeByValue(options, value) {
for (let item of options) {
if (item.value === value) return item;
if (item.children) {
const found = findNodeByValue(item.children, value);
if (found) return found;
}
}
return null;
}
const currentId = value[value.length - 1];
const currentNode = findNodeByValue(this.typeList, currentId);
console.log('当前节点对象:', currentNode);
this.current_product_category = currentNode
},
//
getBinaryData() {
console.log('form.img:', this.form.img)
console.log('form.img 类型:', typeof this.form.img)
console.log('form.img 构造函数:', this.form.img?.constructor?.name)
if (this.form.img) {
console.log('二进制数据 (Blob/File):', this.form.img)
console.log('文件大小:', this.form.img.size, '字节')
console.log('文件类型:', this.form.img.type)
// BlobFile
const file = new File([this.form.img], 'cropped-image.jpg', {
type: this.form.img.type
})
console.log('File对象:', file)
// BlobArrayBuffer
const reader = new FileReader()
reader.onload = (e) => {
const arrayBuffer = e.target.result
console.log('ArrayBuffer:', arrayBuffer)
}
reader.readAsArrayBuffer(this.form.img)
return this.form.img
} else {
console.log('没有二进制数据')
return null
}
}
}
}
</script>
<style scoped lang="scss">
.form-container {
padding: 20px;
max-width: 1000px;
margin: 0 auto;
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
.form-item-half {
flex: 1;
min-width: 0;
/* 防止flex项目溢出 */
}
}
.full-width {
width: 100%;
margin-bottom: 20px;
}
//
.el-form-item {
margin-bottom: 18px;
&.form-item-half {
margin-bottom: 0;
}
&.full-width {
margin-bottom: 20px;
}
}
.form-actions {
text-align: center;
padding: 20px 0;
background: #fff;
border-radius: 8px;
}
.tags-container {
.el-tag {
margin-right: 8px;
margin-bottom: 8px;
}
.tag-input {
width: 90px;
margin-right: 8px;
vertical-align: bottom;
}
.button-new-tag {
margin-left: 8px;
height: 32px;
line-height: 30px;
padding-top: 0;
padding-bottom: 0;
}
}
.upload-area {
display: flex;
align-items: center;
justify-content: flex-start;
overflow: hidden;
width: 80px;
height: 80px;
border: 2px dashed #ddd;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: #fafafa;
display: flex;
align-items: center;
justify-content: center;
&:hover {
border-color: #409EFF;
background: #f0f9ff;
}
.upload-placeholder {
width: 100%;
height: 100%;
border: 1px solid #ddd;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
i {
color: #999;
display: block;
}
p {
margin: 5px 0;
color: #666;
&.upload-tip {
font-size: 12px;
color: #999;
}
}
}
.image-preview {
position: relative;
cursor: pointer;
img {
max-width: 100%;
max-height: 300px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: scale(1.05);
}
}
.preview-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 8px;
i {
color: white;
font-size: 24px;
}
}
&:hover .preview-overlay {
opacity: 1;
}
.image-actions {
margin-top: 15px;
display: flex;
gap: 10px;
justify-content: center;
}
}
.image-actions {
margin-top: 10px;
display: flex;
gap: 8px;
justify-content: center;
}
}
}
//
.cropper-dialog {
z-index: 9999 !important;
.el-dialog {
z-index: 9999 !important;
}
.el-dialog__wrapper {
z-index: 9999 !important;
}
.el-dialog__mask {
z-index: 9998 !important;
}
.cropper-container {
height: 350px;
display: flex;
justify-content: center;
align-items: center;
background: #f5f5f5;
border-radius: 8px;
padding: 20px;
}
.dialog-footer {
text-align: center;
padding: 20px 0;
}
}
//
.preview-dialog {
z-index: 9998 !important;
.el-dialog {
z-index: 9998 !important;
}
.el-dialog__wrapper {
z-index: 9998 !important;
}
.el-dialog__mask {
z-index: 9997 !important;
}
.preview-container {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
min-height: 300px;
.preview-image {
max-width: 100%;
max-height: 500px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
.dialog-footer {
text-align: center;
padding: 20px 0;
}
}
//
:global(.vue-cropper) {
.cropper-container {
width: 100% !important;
height: 100% !important;
}
.cropper-wrap-box {
width: 100% !important;
height: 100% !important;
}
.cropper-canvas {
width: 100% !important;
height: 100% !important;
}
.cropper-view-box {
border: 1px solid #fff;
box-shadow: 0 0 0 1px #39f;
}
.cropper-face {
background-color: rgba(39, 90, 255, 0.1);
}
.cropper-line {
background-color: #39f;
}
.cropper-point {
background-color: #39f;
border: 1px solid #fff;
width: 8px;
height: 8px;
}
}
// dialog
:global(.el-dialog__wrapper) {
&.cropper-dialog {
z-index: 9999 !important;
}
}
:global(.el-dialog) {
&.cropper-dialog {
z-index: 9999 !important;
}
}
// dialog
:global(.cropper-dialog) {
z-index: 9999 !important;
.el-dialog {
z-index: 9999 !important;
}
.el-dialog__wrapper {
z-index: 9999 !important;
}
.el-dialog__mask {
z-index: 9998 !important;
}
}
//
:deep(.vue-cropper) {
.cropper-view-box {
border-radius: 0;
}
.cropper-face {
background-color: rgba(39, 90, 255, 0.1);
}
}
//
.price-unit-container {
display: flex;
align-items: center;
gap: 8px;
.price-input {
flex: 1;
}
.unit-input {
width: 80px;
.el-input__inner {
font-size: 12px;
padding: 0 8px;
}
}
}
//
:deep(.vue-cropper) {
width: 100% !important;
height: 100% !important;
position: relative;
.cropper-container {
width: 100% !important;
height: 100% !important;
position: relative;
}
.cropper-wrap-box {
width: 100% !important;
height: 100% !important;
position: relative;
}
.cropper-canvas {
width: 100% !important;
height: 100% !important;
position: relative;
}
.cropper-view-box {
border: 1px solid #fff;
box-shadow: 0 0 0 1px #39f;
outline: 0;
}
.cropper-face {
background-color: rgba(39, 90, 255, 0.1);
}
.cropper-line {
background-color: #39f;
}
.cropper-point {
background-color: #39f;
border: 1px solid #fff;
width: 8px;
height: 8px;
}
.cropper-center {
background-color: #39f;
border: 1px solid #fff;
width: 8px;
height: 8px;
}
.cropper-dashed {
border: 0 dashed #fff;
}
.cropper-modal {
background-color: rgba(0, 0, 0, 0.5);
}
.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xOdTWsmQAAAGiSURBVDhPY2AYBYMRMDIyMjAyMjL8//+f4f///wz//v1j+Pv3L8OfP38Yfv/+zfDnzx+G37//MPz69Yvh58+fDD9+/GD4/v07w7dv3xi+fv3K8OXLF4bPnz8zfPr0ieHjx48MHz58YHj//j3Du3fvGN6+fcvw5s0bhtevXzO8evWK4eXLlwwvXrxgeP78OcOzZ88Ynj59yvDkyROGx48fMzx69Ijh4cOHDA8ePGC4f/8+w7179xju3r3LcOfOHYbbt28z3Lp1i+HmzZsMN27cYLh+/TrDtWvXGK5evcpw5coVhsuXLzNcunSJ4eLFiwwXLlxgOH/+PMO5c+cYzp49y3DmzBmG06dPM5w6dYrh5MmTDCdOnGA4fvw4w7FjxxiOHj3KcOTIEYbDhw8zHDp0iOHgwYMMBw4cYNi/fz/Dvn37GPbu3cuwZ88eht27dzPs2rWLYefOnQw7duxg2L59O8O2bdsYtm7dyrBlyxaGzZs3M2zatIlh48aNDBs2bGBYv349w7p16xjWrl3LsGbNGoZVq1YxrFy5kmHFihUMy5cvZ1i2bBnD0qVLGZYsWcKwePFihkWLFjEsXLiQYcGCBQzz59nmDdvHsPcuXMZ5syZwzB79myGWbNmMcycOZNhxowZDNOnT2eYNm0aw9SpUxmmTJnCMHnyZIZJkyYxTJw4kWHChAkM48ePZxg3bhzD2LFjGcaMGcMwevRohlGjRjGMHDmSYcSIEQzDhw9nGDZsGMPQoUMZhgwZwjB48GCGQYMG0Q8AABKxLhX5TqgFAAAAAElFTkSuQmCC');
}
}
</style>

View File

@ -0,0 +1,564 @@
<template>
<div class="form-container">
<el-form :model="form" :rules="rules" ref="form" label-width="100px">
<el-form-item label="商品图片" prop="image">
<div class="upload-area" @click="!selectedImage && triggerFileInput()">
<input
ref="fileInput"
type="file"
accept="image/*"
@change="handleFileChange"
style="display: none;"
>
<div v-if="!selectedImage" class="upload-placeholder">
<i class="el-icon-plus" style="font-size: 40px;"></i>
<!-- <p class="upload-tip">支持 JPGPNG 格式最大 5MB</p> -->
</div>
<div v-else class="image-preview">
<img :src="selectedImage" alt="预览图" @click="previewImage">
<div class="preview-overlay">
<i class="el-icon-zoom-in"></i>
</div>
</div>
</div>
<!-- 图片操作按钮 -->
<div v-if="selectedImage" class="image-actions">
<el-button size="mini" type="primary" @click="previewImage">预览图片</el-button>
<el-button size="mini" type="primary" @click="openCropper">裁剪图片</el-button>
<el-button size="mini" type="danger" @click="removeImage">删除</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
<!-- 图片裁剪弹窗 -->
<el-dialog
title="图片裁剪"
:visible.sync="showCropper"
width="60%"
top="5vh"
:before-close="closeCropper"
custom-class="cropper-dialog"
:append-to-body="true"
:modal-append-to-body="true"
:z-index="9999"
:destroy-on-close="true"
>
<div class="cropper-container">
<vue-cropper
ref="cropper"
:img="cropperImage"
:outputSize="1"
:info="true"
:full="true"
:canMove="true"
:canMoveBox="true"
:original="false"
:autoCrop="true"
:autoCropWidth="300"
:autoCropHeight="200"
:fixed="false"
:centerBox="true"
:infoTrue="true"
:high="true"
mode="cover"
:maxImgSize="2000"
:enlargeImg="1"
:limitMinSize="[100, 100]"
@realTime="realTime"
/>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="closeCropper">取消</el-button>
<el-button type="primary" @click="cropImage">确认裁剪</el-button>
</div>
</el-dialog>
<!-- 图片预览弹窗 -->
<el-dialog
title="图片预览"
:visible.sync="showPreview"
width="50%"
center
top="5vh"
:append-to-body="true"
:modal-append-to-body="true"
:z-index="9998"
:destroy-on-close="true"
custom-class="preview-dialog"
>
<div class="preview-container">
<img :src="selectedImage" alt="预览图" class="preview-image">
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="showPreview = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { VueCropper } from 'vue-cropper'
export default {
name: 'sendProduct',
components: {
VueCropper
},
data() {
return {
selectedImage: null,
showCropper: false,
showPreview: false,
cropperImage: '',
form: {
image: ''
},
rules: {
image: [
{ required: true, message: '请上传商品图片', trigger: 'change' }
]
}
}
},
methods: {
//
triggerFileInput() {
this.$refs.fileInput.click()
},
//
handleFileChange(event) {
const file = event.target.files[0]
if (file) {
//
if (file.size > 5 * 1024 * 1024) {
this.$message.error('图片大小不能超过5MB')
return
}
//
if (!file.type.startsWith('image/')) {
this.$message.error('请选择图片文件')
return
}
const reader = new FileReader()
reader.onload = (e) => {
this.selectedImage = e.target.result
this.form.image = e.target.result
this.$refs.form.validateField('image')
}
reader.readAsDataURL(file)
}
},
//
openCropper() {
if (this.selectedImage) {
this.cropperImage = this.selectedImage
this.showCropper = true
// DOM
this.$nextTick(() => {
if (this.$refs.cropper) {
this.$refs.cropper.refresh()
}
// dialog
setTimeout(() => {
const dialogWrapper = document.querySelector('.cropper-dialog')
if (dialogWrapper) {
dialogWrapper.style.zIndex = '9999'
}
}, 100)
})
}
},
//
closeCropper() {
this.showCropper = false
this.cropperImage = ''
},
//
realTime(data) {
//
console.log('裁剪实时数据:', data)
},
//
cropImage() {
this.$refs.cropper.getCropData((data) => {
// data base64
this.selectedImage = data
this.form.image = data
this.$message.success('图片裁剪成功!')
this.closeCropper()
})
},
//
removeImage() {
this.selectedImage = null
this.form.image = ''
this.$refs.fileInput.value = ''
this.$refs.form.validateField('image')
},
//
previewImage() {
if (this.selectedImage) {
this.showPreview = true
}
},
//
submitForm() {
this.$refs.form.validate((valid) => {
if (valid) {
console.log('表单数据:', this.form)
this.$message.success('提交成功!')
} else {
this.$message.error('请完善表单信息')
}
})
},
//
resetForm() {
this.$refs.form.resetFields()
this.selectedImage = null
this.$refs.fileInput.value = ''
}
}
}
</script>
<style scoped lang="scss">
.form-container {
padding: 20px;
max-width: 600px;
margin: 0 auto;
.upload-area {
display: flex;
align-items: center;
justify-content: flex-start;
overflow: hidden;
width: 80px;
height: 80px;
border: 2px dashed #ddd;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: #fafafa;
display: flex;
align-items: center;
justify-content: center;
&:hover {
border-color: #409EFF;
background: #f0f9ff;
}
.upload-placeholder {
width: 100%;
height: 100%;
border: 1px solid #ddd;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
i {
color: #999;
display: block;
}
p {
margin: 5px 0;
color: #666;
&.upload-tip {
font-size: 12px;
color: #999;
}
}
}
.image-preview {
position: relative;
cursor: pointer;
img {
max-width: 100%;
max-height: 300px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: scale(1.05);
}
}
.preview-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
border-radius: 8px;
i {
color: white;
font-size: 24px;
}
}
&:hover .preview-overlay {
opacity: 1;
}
.image-actions {
margin-top: 15px;
display: flex;
gap: 10px;
justify-content: center;
}
}
.image-actions {
margin-top: 10px;
display: flex;
gap: 8px;
justify-content: center;
}
}
}
//
.cropper-dialog {
z-index: 9999 !important;
.el-dialog {
z-index: 9999 !important;
}
.el-dialog__wrapper {
z-index: 9999 !important;
}
.el-dialog__mask {
z-index: 9998 !important;
}
.cropper-container {
height: 350px;
display: flex;
justify-content: center;
align-items: center;
background: #f5f5f5;
border-radius: 8px;
padding: 20px;
}
.dialog-footer {
text-align: center;
padding: 20px 0;
}
}
//
.preview-dialog {
z-index: 9998 !important;
.el-dialog {
z-index: 9998 !important;
}
.el-dialog__wrapper {
z-index: 9998 !important;
}
.el-dialog__mask {
z-index: 9997 !important;
}
.preview-container {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
min-height: 300px;
.preview-image {
max-width: 100%;
max-height: 500px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
.dialog-footer {
text-align: center;
padding: 20px 0;
}
}
//
:global(.vue-cropper) {
.cropper-container {
width: 100% !important;
height: 100% !important;
}
.cropper-wrap-box {
width: 100% !important;
height: 100% !important;
}
.cropper-canvas {
width: 100% !important;
height: 100% !important;
}
.cropper-view-box {
border: 1px solid #fff;
box-shadow: 0 0 0 1px #39f;
}
.cropper-face {
background-color: rgba(39, 90, 255, 0.1);
}
.cropper-line {
background-color: #39f;
}
.cropper-point {
background-color: #39f;
border: 1px solid #fff;
width: 8px;
height: 8px;
}
}
// dialog
:global(.el-dialog__wrapper) {
&.cropper-dialog {
z-index: 9999 !important;
}
}
:global(.el-dialog) {
&.cropper-dialog {
z-index: 9999 !important;
}
}
// dialog
:global(.cropper-dialog) {
z-index: 9999 !important;
.el-dialog {
z-index: 9999 !important;
}
.el-dialog__wrapper {
z-index: 9999 !important;
}
.el-dialog__mask {
z-index: 9998 !important;
}
}
//
:deep(.vue-cropper) {
.cropper-view-box {
border-radius: 0;
}
.cropper-face {
background-color: rgba(39, 90, 255, 0.1);
}
}
//
:deep(.vue-cropper) {
width: 100% !important;
height: 100% !important;
position: relative;
.cropper-container {
width: 100% !important;
height: 100% !important;
position: relative;
}
.cropper-wrap-box {
width: 100% !important;
height: 100% !important;
position: relative;
}
.cropper-canvas {
width: 100% !important;
height: 100% !important;
position: relative;
}
.cropper-view-box {
border: 1px solid #fff;
box-shadow: 0 0 0 1px #39f;
outline: 0;
}
.cropper-face {
background-color: rgba(39, 90, 255, 0.1);
}
.cropper-line {
background-color: #39f;
}
.cropper-point {
background-color: #39f;
border: 1px solid #fff;
width: 8px;
height: 8px;
}
.cropper-center {
background-color: #39f;
border: 1px solid #fff;
width: 8px;
height: 8px;
}
.cropper-dashed {
border: 0 dashed #fff;
}
.cropper-modal {
background-color: rgba(0, 0, 0, 0.5);
}
.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xOdTWsmQAAAGiSURBVDhPY2AYBYMRMDIyMjAyMjL8//+f4f///wz//v1j+Pv3L8OfP38Yfv/+zfDnzx+G37//MPz69Yvh58+fDD9+/GD4/v07w7dv3xi+fv3K8OXLF4bPnz8zfPr0ieHjx48MHz58YHj//j3Du3fvGN6+fcvw5s0bhtevXzO8evWK4eXLlwwvXrxgeP78OcOzZ88Ynj59yvDkyROGx48fMzx69Ijh4cOHDA8ePGC4f/8+w7179xju3r3LcOfOHYbbt28z3Lp1i+HmzZsMN27cYLh+/TrDtWvXGK5evcpw5coVhsuXLzNcunSJ4eLFiwwXLlxgOH/+PMO5c+cYzp49y3DmzBmG06dPM5w6dYrh5MmTDCdOnGA4fvw4w7FjxxiOHj3KcOTIEYbDhw8zHDp0iOHgwYMMBw4cYNi/fz/Dvn37GPbu3cuwZ88eht27dzPs2rWLYefOnQw7duxg2L59O8O2bdsYtm7dyrBlyxaGzZs3M2zatIlh48aNDBs2bGBYv349w7p16xjWrl3LsGbNGoZVq1YxrFy5kmHFihUMy5cvZ1i2bBnD0qVLGZYsWcKwePFihkWLFjEsXLiQYcGCBQzz59nmDdvHsPcuXMZ5syZwzB79myGWbNmMcycOZNhxowZDNOnT2eYNm0aw9SpUxmmTJnCMHnyZIZJkyYxTJw4kWHChAkM48ePZxg3bhzD2LFjGcaMGcMwevRohlGjRjGMHDmSYcSIEQzDhw9nGDZsGMPQoUMZhgwZwjB48GCGQYMG0Q8AABKxLhX5TqgFAAAAAElFTkSuQmCC');
}
}
</style>

View File

@ -0,0 +1,521 @@
<template>
<div class="enterprise-demand-detail">
<div style="display: flex;justify-content: space-between;align-items: center;">
<h1 class="demand-title" style="margin: 0 0;">
{{ productDetailInfo.product_name }}
</h1>
<button class="consult-btn" @click="openTalk">立即咨询</button>
</div>
<!-- 标题区域 -->
<ul class="title-section">
<li class="info-item" style="margin:5px 0;margin-top: 15px;">
<span class="mylabel">企业名称</span>
<span class="value">{{ productDetailInfo.company_name }}</span>
</li>
<li class="info-item" style="margin:5px 0;">
<span class="mylabel">所属类别</span>
<span class="value">{{ productDetailInfo.product_category }}</span>
</li>
<li class="info-item" style="margin:5px 0;">
<span class="mylabel">企业类别</span>
<span class="value">{{ productDetailInfo.company_type }}</span>
</li>
</ul>
<!-- 联系信息区域 -->
<ul class="infoUl">
<li class="contact-item">
<span class="mylabel">联系人</span>
<span class="value">{{ productDetailInfo.contact_person }} {{ productDetailInfo.phone_number }}</span>
</li>
<li class="contact-item">
<span class="mylabel">邮箱</span>
<span class="value">{{ productDetailInfo.email }}</span>
</li>
<li class="contact-item">
<span class="mylabel">职务</span>
<span class="value">{{ productDetailInfo.job_title ? productDetailInfo.job_title : '--' }}</span>
</li>
<li class="contact-item">
<span class="mylabel">发布日期</span>
<span class="value">{{ productDetailInfo.create_at ? productDetailInfo.create_at : '暂未发布' }}</span>
</li>
</ul>
<div v-if="productDetailInfo.cart_flag==='1'" style="position: relative;width: 100%;padding-left: 16px;" class="mysection">
<div class="section-title">配置数据</div>
<div class="section-content">
</div>
</div>
<ul v-if="productDetailInfo.cart_flag==='1'" class="adUl">
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/ssc2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">CPU</span>
<span class="subDescription">{{ productDetailInfo.cpu }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/cpu2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">内存</span>
<span class="subDescription">{{ productDetailInfo.memory }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/sdcard2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">GPU</span>
<span class="subDescription">{{ productDetailInfo.gpu }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/ssdr2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">系统盘</span>
<span class="subDescription">{{ productDetailInfo.data_disk }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/servermini2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">数据盘</span>
<span class="subDescription">{{ productDetailInfo.sys_disk }}</span>
</div>
</li>
<li>
<div class="leftBox">
<img src="https://www.kaiyuancloud.cn/idfile?path=firstpagehot/serverpath2x.png" alt="">
</div>
<div class="rightBox">
<span class="subTitle">网卡</span>
<span class="subDescription">{{ productDetailInfo.net_card }}</span>
</div>
</li>
</ul>
<!-- 配置数据区域 -->
<!-- 需求描述区域 -->
<div class="demand-section">
<div class="demand-content">
<div class="section-title">需求描述</div>
{{ productDetailInfo.requirement_summary ? productDetailInfo.requirement_summary : '--' }}
</div>
</div>
<!-- 相关参数区域 -->
<div class="params-section">
<div class="params-content">
<div class="section-title">相关参数</div>
{{ productDetailInfo.related_parameters ? productDetailInfo.related_parameters : '--' }}
</div>
</div>
<!-- 预期目标区域 -->
<div class="goals-section">
<div class="goals-content">
<div class="section-title">应用场景</div>
{{ productDetailInfo.application_scenario ? productDetailInfo.application_scenario : '--' }}
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: 'proDetail',
data() {
return {
}
},
computed: {
...mapState({
productDetailInfo: state => state.ncmatch.productDetail,
showProductDetail: state => state.ncmatch.showProductDetail
})
},
methods: {
openTalk() {
this.$store.commit('setShowTalk', true);
}
}
}
</script>
<style scoped lang="scss">
.consult-btn {
width: fit-content;
background: linear-gradient(90deg, #275AFF 0%, #2EBDFA 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(39, 90, 255, 0.3);
}
}
.mylabel {
font-weight: 500;
color: #252424;
min-width: 80px;
}
.infoUl {
border-top: 1px solid #e5e5e5;
padding: 30px 0;
display: flex;
justify-content: space-between;
align-items: center;
li {
margin-bottom: 10px;
}
}
.adUl {
margin-bottom: 30px;
background: url("../../../homePage/detail/img/liBg.png");
background-position: top;
background-size: cover;
width: 100%;
min-width: 800px;
display: flex !important;
flex-wrap: wrap !important;
justify-content: space-between !important;
align-items: center !important;
li {
margin: 15px 0;
width: 49%;
display: flex !important;
justify-content: flex-start;
align-items: center;
.leftBox {
width: 50px;
height: 50px;
padding: 10px;
min-width: 50px;
min-height: 50px;
img {
width: 100%;
height: 100%;
}
}
.rightBox {
padding-left: 25px;
display: flex !important;
flex-direction: column !important;
.subTitle {
font-size: 16px;
color: #333;
}
.subDescription {
margin-top: 10px;
font-size: 14px;
color: #999;
}
}
}
}
.enterprise-demand-detail {
margin: 0 auto;
padding: 16px;
padding-top: 0;
background: #fff;
font-family: 'Microsoft YaHei', sans-serif;
}
//
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
.header-left {
.demand-icon {
display: flex;
align-items: center;
gap: 10px;
i {
background: #67c23a;
color: white;
padding: 8px;
border-radius: 4px;
font-size: 16px;
}
span {
font-size: 16px;
font-weight: 500;
color: #333;
}
}
}
.header-right {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
.consult-btn {
background: #67c23a;
color: white;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background 0.3s;
&:hover {
background: #5daf34;
}
}
.demand-number {
font-size: 12px;
color: #666;
}
}
}
//
.title-section {
margin-bottom: 30px;
.demand-title {
font-size: 24px;
font-weight: bold;
color: #333;
margin: 0;
}
}
//
.company-info {
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
.info-item {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
font-weight: 500;
color: #666;
min-width: 80px;
}
.value {
color: #333;
}
}
}
//
.contact-info {
margin-bottom: 30px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
.contact-item {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.label {
font-weight: 500;
color: #666;
min-width: 80px;
}
.value {
color: #333;
}
}
}
//
.collaboration-section {
margin-bottom: 30px;
.collab-title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.collab-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
.collab-btn {
padding: 8px 16px;
background: #e8f5e8;
color: #67c23a;
border: 1px solid #67c23a;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #67c23a;
color: white;
}
}
}
}
//
.mysection,
.config-section,
.demand-section,
.params-section,
.goals-section {
margin-bottom: 30px;
.section-title {
margin-bottom: 10px;
font-size: 14px;
color: #333;
font-weight: bold !important;
position: relative;
&::before {
content: '';
position: absolute;
top: 6px;
left: -5px;
display: block;
width: 2px;
height: 12px;
background: #666;
}
}
}
//
.config-section {
.config-content {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
.config-item {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
.config-label {
font-weight: 500;
color: #666;
min-width: 80px;
}
.config-value {
color: #333;
}
}
}
}
//
.demand-section,
.params-section,
.goals-section {
.demand-content,
.params-content,
.goals-content {
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
line-height: 1.8;
color: #333;
text-align: justify;
}
}
//
@media (max-width: 768px) {
.enterprise-demand-detail {
padding: 15px;
}
.header-section {
flex-direction: column;
gap: 15px;
text-align: center;
}
.collab-buttons {
justify-content: center;
}
.info-item,
.contact-item,
.config-item {
flex-direction: column;
gap: 5px;
.label {
min-width: auto;
}
}
}
</style>

View File

@ -0,0 +1,161 @@
<template>
<div
style="display: flex;align-items: center;justify-content: center;border:1px solid red;width: 100%;max-width: 1400px;">
<img style="width: 180px;height: 60px;padding-right: 20px;" src="https://www.kaiyuancloud.cn/idfile?path=logo_ncmatch.png" alt="">
<div
style="min-width:800px;border-bottom: 1px solid #E5E5E5;display: flex;align-items: center;justify-content: space-between;">
<!-- 中间搜索区域 -->
<div class="search-section" style="position: relative;margin: 0 20px;">
<div class="search-bar">
<input v-model="searchKeyword" type="text" class="search-input" placeholder="搜你想搜...">
<button class="search-btn" @click="handleSearch">搜索</button>
</div>
<!-- 热搜关键词 -->
<div class="hot-search">
<a v-for="keyword in hotSearchKeywords" :key="keyword" href="#" class="hot-keyword">
{{ keyword }}
</a>
</div>
</div>
<!-- position: absolute;right: -150px;top: 0px; -->
<span
style="height: 44px;line-height: 44px;border-radius: 8px;border:1px solid #275AFF; color: #275AFF;font-size: 18px;font-weight: 500;padding: 0 20px;z-index: 10;display: flex;align-items: center;gap: 10px;">
<img style="width: 24px;height: 24px;" src="../mainPage/img/robot.svg" alt="">
NC AI</span>
</div>
</div>
</template>
<script>
export default {
name: 'search',
data() {
return {
hotSearchKeywords: [
'昆仑芯-P800',
'天垓-150',
'4090',
'云服务器-GPU',
'人脸识别',
'SDWAN',
'互联网专线',
'DCI',
'AI专线',
'对象存储',
'智慧医疗',
'智慧客服',
'A800',
'A100',
'910B'
],
hotMenuList: [
{
id: "hot",
name: "热门推荐",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png")
},
{
id: "hlzx",
name: "互联网专线",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "SDWAN",
name: "SDWAN",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "DCI",
name: "DCI",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
},
{
id: "AI",
name: "AI专线",
icon: require("../../newImg/niu.png"),
activeIcon: require("../../newImg/niuActive.png"),
}
],
}
}
}
</script>
<style scoped lang="scss">
.search-section {
border: 1px solid red;
flex: 1;
min-width: 850px;
.search-bar {
display: flex;
align-items: center;
background: white;
border: 2px solid #3f68d8;
border-radius: 4px;
overflow: hidden;
.search-input {
flex: 1;
padding: 12px 15px;
border: none;
outline: none;
font-size: 14px;
}
.camera-icon {
padding: 0 10px;
color: #666;
cursor: pointer;
}
.search-btn {
background: #0864dd;
color: white;
border: none;
padding: 12px 20px;
cursor: pointer;
font-size: 14px;
&:hover {
background: #173ad4;
}
}
}
.hot-search {
margin-top: 8px;
display: flex;
gap: 15px;
flex-wrap: wrap;
.hot-keyword {
&:nth-child(-n+3) {
color: #e1251b;
}
color: #666;
text-decoration: none;
font-size: 12px;
&:hover {
color: #e1251b;
}
}
}
}
@media (max-width: 768px) {
.search-section {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,157 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1080 1080" height="1080px" width="1080px">
<title>Empty state_money</title>
<defs>
<linearGradient id="linearGradient-1" y2="49.999%" x2="-25.0486869%" y1="49.999%" x1="120.941313%">
<stop offset="0%" stop-opacity="0" stop-color="#C8CACD"></stop>
<stop offset="100%" stop-color="#B2B5B8"></stop>
</linearGradient>
<linearGradient id="linearGradient-2" y2="99.9798247%" x2="49.4990945%" y1="9.63971254%" x1="50.517101%">
<stop offset="0%" stop-color="#F4F5F5"></stop>
<stop offset="100%" stop-color="#B3B6B9"></stop>
</linearGradient>
<linearGradient id="linearGradient-3" y2="99.8333092%" x2="50.3293803%" y1="7.46334065%" x1="49.8201842%">
<stop offset="0%" stop-color="#F3F4F4"></stop>
<stop offset="100%" stop-color="#B3B6B9"></stop>
</linearGradient>
<linearGradient id="linearGradient-4" y2="-44.1593651%" x2="49.9967123%" y1="100.010635%" x1="49.9967123%">
<stop offset="0%" stop-color="#F8F8F9"></stop>
<stop offset="100%" stop-color="#E2E3E4"></stop>
</linearGradient>
<linearGradient id="linearGradient-5" y2="2.87680233%" x2="49.9950427%" y1="99.6368023%" x1="49.9950427%">
<stop offset="0%" stop-opacity="0" stop-color="#F4F5F5"></stop>
<stop offset="100%" stop-color="#EEEFF0"></stop>
</linearGradient>
<linearGradient id="linearGradient-6" y2="2.85491453%" x2="50.0013111%" y1="99.7049145%" x1="50.0013111%">
<stop offset="0%" stop-opacity="0" stop-color="#F4F5F5"></stop>
<stop offset="100%" stop-color="#EEEFF0"></stop>
</linearGradient>
<linearGradient id="linearGradient-7" y2="0.10431694%" x2="49.9970261%" y1="100.004317%" x1="49.9970261%">
<stop offset="0.2903294%" stop-opacity="0" stop-color="#F4F5F5"></stop>
<stop offset="97.98%" stop-color="#F0F1F2"></stop>
</linearGradient>
<linearGradient id="linearGradient-8" y2="100.135451%" x2="49.2674945%" y1="9.51551319%" x1="50.8593448%">
<stop offset="0%" stop-color="#F4F5F5"></stop>
<stop offset="100%" stop-color="#B3B6B9"></stop>
</linearGradient>
<linearGradient id="linearGradient-9" y2="99.8624686%" x2="50.460387%" y1="7.70267331%" x1="49.5709381%">
<stop offset="0%" stop-color="#F3F4F4"></stop>
<stop offset="100%" stop-color="#B3B6B9"></stop>
</linearGradient>
<linearGradient id="linearGradient-10" y2="100.468411%" x2="49.150831%" y1="9.94853854%" x1="50.7540359%">
<stop offset="0%" stop-color="#F4F5F5"></stop>
<stop offset="100%" stop-color="#B3B6B9"></stop>
</linearGradient>
<linearGradient id="linearGradient-11" y2="99.7607635%" x2="50.6091894%" y1="7.54080286%" x1="49.6697403%">
<stop offset="0%" stop-color="#F3F4F4"></stop>
<stop offset="100%" stop-color="#B3B6B9"></stop>
</linearGradient>
<linearGradient id="linearGradient-12" y2="124.53%" x2="50.0116297%" y1="1.10241782e-11%" x1="50.0116297%">
<stop offset="0%" stop-color="#C5C8CA"></stop>
<stop offset="100%" stop-color="#E0E1E2"></stop>
</linearGradient>
<linearGradient id="linearGradient-13" y2="99.9989189%" x2="49.9992857%" y1="-0.00108108108%" x1="49.9992857%">
<stop offset="0%" stop-color="#CCCED1"></stop>
<stop offset="100%" stop-color="#EDEEEF"></stop>
</linearGradient>
<linearGradient id="linearGradient-14" y2="100.002245%" x2="49.9992857%" y1="0.00224532224%" x1="49.9992857%">
<stop offset="0%" stop-color="#D7D8DA"></stop>
<stop offset="100%" stop-color="#EDEEEF"></stop>
</linearGradient>
<linearGradient id="linearGradient-15" y2="99.8875529%" x2="50.0089873%" y1="-0.00244712991%" x1="50.0089873%">
<stop offset="0%" stop-color="#CBCDD0"></stop>
<stop offset="100%" stop-color="#F4F5F5"></stop>
</linearGradient>
<linearGradient id="linearGradient-16" y2="100.005118%" x2="50.0089873%" y1="0.00511784512%" x1="50.0089873%">
<stop offset="0%" stop-color="#D7D8DA"></stop>
<stop offset="100%" stop-color="#EDEEEF"></stop>
</linearGradient>
<linearGradient id="linearGradient-17" y2="99.995445%" x2="50.0207011%" y1="-0.00455497383%" x1="50.0207011%">
<stop offset="0%" stop-color="#CCCED1"></stop>
<stop offset="100%" stop-color="#EDEEEF"></stop>
</linearGradient>
<linearGradient id="linearGradient-18" y2="100.004421%" x2="50.0274074%" y1="0.00442105263%" x1="50.0274074%">
<stop offset="0%" stop-color="#D7D8DA"></stop>
<stop offset="100%" stop-color="#EDEEEF"></stop>
</linearGradient>
<linearGradient id="linearGradient-19" y2="99.9996543%" x2="50.0045339%" y1="-0.000345710623%" x1="50.0045339%">
<stop offset="0%" stop-color="#E4E5E6"></stop>
<stop offset="100%" stop-color="#AFB2B6"></stop>
</linearGradient>
<linearGradient id="linearGradient-20" y2="99.9996982%" x2="50.0045339%" y1="-0.000301783277%" x1="50.0045339%">
<stop offset="0%" stop-color="#EFF0F1"></stop>
<stop offset="100%" stop-color="#BBBDC1"></stop>
</linearGradient>
<linearGradient id="linearGradient-21" y2="49.9929885%" x2="99.9328107%" y1="49.9929885%" x1="0.282810707%">
<stop offset="0%" stop-color="#C4C7C9"></stop>
<stop offset="100%" stop-color="#E0E1E2"></stop>
</linearGradient>
<linearGradient id="linearGradient-22" y2="50.0006173%" x2="99.9328107%" y1="50.0006173%" x1="0.282810707%">
<stop offset="0%" stop-color="#FFFFFF"></stop>
<stop offset="100%" stop-opacity="0.09743772" stop-color="#FFFFFF"></stop>
</linearGradient>
<linearGradient id="linearGradient-23" y2="6.50282609%" x2="49.9976087%" y1="114.902826%" x1="49.9976087%">
<stop offset="0%" stop-color="#FFFFFF"></stop>
<stop offset="100%" stop-color="#CFD1D3"></stop>
</linearGradient>
<linearGradient id="linearGradient-24" y2="6.51717391%" x2="49.9976087%" y1="114.907174%" x1="49.9976087%">
<stop offset="0%" stop-opacity="0.09743772" stop-color="#FFFFFF"></stop>
<stop offset="100%" stop-color="#FFFFFF"></stop>
</linearGradient>
</defs>
<g opacity="0.8" fill-rule="evenodd" fill="none" stroke-width="1" stroke="none" id="基础组件">
<g fill-rule="nonzero" id="Empty-state_money">
<g id="编组">
<g id="编组-14">
<rect height="1080" width="1080" y="0" x="0" fill="#FFFFFF" fill-opacity="0" id="矩形"></rect>
<ellipse ry="15" rx="148.5" cy="881.7" cx="540" opacity="0.331" fill="url(#linearGradient-1)" id="椭圆形"></ellipse>
<g transform="translate(167.161830, 585.966414)" opacity="0.7794" id="编组备份-2">
<ellipse ry="5.4" rx="17.4" cy="82.1667915" cx="47.5190856" opacity="0.5988" fill="#9DA1A6" id="椭圆形_1_"></ellipse>
<path opacity="0.5765" fill="url(#linearGradient-2)" id="椭圆形_2_" d="M32.2190856,82.1667915 C37.0190856,84.2667915 60.4190856,47.6667915 63.7190856,38.0667915 C67.0190856,28.4667916 63.4190856,17.6667916 55.6190856,14.3667916 C47.8190856,11.0667916 38.8190856,16.1667916 35.5190856,26.0667916 C32.2190856,35.9667915 27.1190857,80.0667915 32.2190856,82.1667915 Z"></path>
<path fill="url(#linearGradient-3)" id="椭圆形_3_" d="M33.1190856,82.1667915 C39.1190856,80.3667915 38.2190856,28.4667916 35.2190856,16.7667916 C32.2190856,5.06679156 22.3190857,-2.13320844 12.7190857,0.56679156 C3.11908572,3.26679156 -1.98091428,14.9667916 0.719085721,26.6667916 C3.41908572,38.3667915 27.1190857,83.9667915 33.1190856,82.1667915 Z"></path>
</g>
<path opacity="0.7" fill="url(#linearGradient-4)" id="形状结合" d="M171.6,245.7 C170.4,245.7 169.2,245.7 168,245.4 C166.5,245.7 165,245.7 163.5,245.7 C140.4,245.7 121.5,227.1 121.5,203.7 C121.5,180.3 140.1,162 163.5,162 C166.2,162 168.9,162.3 171.3,162.6 C180.9,144.6 199.8,132.3 221.7,132.3 C252.9,132.3 278.4,157.8 278.4,189 L278.4,189 L312,189 C327.6,189 340.5,201.6 340.5,217.2 C340.5,232.8 327.9,245.7 312,245.7 L171.6,245.7 Z"></path>
<g transform="translate(108.000000, 658.800000)" id="编组-2">
<path fill="url(#linearGradient-5)" id="路径" d="M0,140.4 C46.8,71.7 91.8,37.2 134.4,37.2 C177,37.2 225.9,71.7 280.8,140.4 L0,140.4 Z"></path>
<path fill="url(#linearGradient-6)" id="路径-2" d="M166.2,140.4 C215.7,46.8 268.8,0 325.2,0 C381.6,0 484.2,46.8 633,140.4 L166.2,140.4 Z"></path>
<path fill="url(#linearGradient-7)" id="路径-3" d="M864,140.4 C812.4,67.2 763.5,30.6 717.9,30.6 C672.3,30.6 598.5,67.2 496.8,140.4 L864,140.4 Z"></path>
</g>
<g transform="translate(772.317252, 537.206304)" id="编组-62">
<ellipse ry="4.2" rx="13.5" cy="57.9468483" cx="37.7413755" opacity="0.6699" fill="#9DA1A6" id="椭圆形_4_"></ellipse>
<path opacity="0.5765" fill="url(#linearGradient-8)" id="椭圆形_5_" d="M26.0413755,57.9468483 C29.9413755,59.4468483 47.6413755,33.3468483 50.3413755,26.4468484 C53.0413755,19.5468484 50.0413755,12.0468484 44.3413755,9.64684836 C38.6413755,7.24684836 31.4413755,11.1468484 28.7413755,18.0468484 C26.0413755,24.6468484 22.4413755,56.4468483 26.0413755,57.9468483 Z"></path>
<path fill="url(#linearGradient-9)" id="椭圆形_6_" d="M25.4413755,57.9468483 C30.2413755,56.7468483 29.3413755,20.1468484 27.2413755,12.0468484 C25.1413755,3.94684836 17.0413755,-1.45315164 9.84137547,0.34684836 C2.64137547,2.14684836 -1.55862453,10.5468484 0.541375469,18.6468484 C2.64137547,26.7468484 20.9413755,59.1468483 25.4413755,57.9468483 Z"></path>
</g>
<g transform="translate(796.723260, 788.264682)" opacity="0.8969" id="编组">
<ellipse ry="5.4" rx="17.7" cy="97.2676599" cx="56.2883712" opacity="0.5969" fill="#9DA1A6" id="椭圆形_7_"></ellipse>
<path opacity="0.5765" fill="url(#linearGradient-10)" id="椭圆形_8_" d="M43.6883712,98.1676599 C49.9883712,100.56766 80.2883712,56.4676599 84.7883712,44.7676599 C89.2883712,33.0676599 84.4883712,20.1676598 74.2883712,16.2676598 C64.0883712,12.3676598 52.3883712,18.6676598 47.8883712,30.3676599 C43.3883712,42.0676599 37.3883712,95.7676599 43.6883712,98.1676599 Z"></path>
<path fill="url(#linearGradient-11)" id="椭圆形_9_" d="M43.6883712,98.4676599 C51.7883712,96.3676599 50.5883712,34.2676599 46.6883712,20.4676598 C42.7883712,6.66765978 29.2883711,-2.63234023 16.6883711,0.667659768 C4.08837108,3.96765978 -2.81162892,18.0676598 1.08837108,32.1676599 C4.98837108,46.2676599 35.5883712,100.56766 43.6883712,98.4676599 Z"></path>
</g>
</g>
<g transform="translate(342.468906, 383.400000)" id="编组-13">
<polygon points="45.6 69 356.7 69 356.7 168 45.6 168" fill="url(#linearGradient-12)" id="矩形_1_"></polygon>
<path opacity="0.755208333" fill="url(#linearGradient-13)" id="矩形_2_" d="M1.8,129.3 L84,0 L84,0 L84,98.4 L0,288.6 L0,135 C0,132.9 0.6,130.8 1.8,129.3 Z"></path>
<path fill="url(#linearGradient-14)" id="矩形备份-17" d="M1.8,137.4 L84,8.1 L84,8.1 L84,106.5 L0,296.7 L0,143.1 C0,141 0.6,138.9 1.8,137.4 Z"></path>
<rect height="99.3" width="237" y="0" x="84" opacity="0.755208333" fill="url(#linearGradient-15)" id="矩形_3_"></rect>
<rect height="89.1" width="237" y="5.4" x="84" fill="url(#linearGradient-16)" id="矩形备份-16"></rect>
<path opacity="0.755208333" fill="url(#linearGradient-17)" id="矩形_4_" d="M400.5,126.9 L321,0 L321,0 L321,98.4 L402.3,286.5 L402.3,132.6 C402.3,130.8 401.7,128.7 400.5,126.9 Z"></path>
<path fill="url(#linearGradient-18)" id="矩形备份-18" d="M400.5,133.5 L321.3,8.1 L321.3,8.1 L321.3,106.5 L402.3,293.1 L402.3,139.5 C402.3,137.4 401.7,135.3 400.5,133.5 Z"></path>
<g transform="translate(0.000000, 132.244899)" id="形状结合备份-7">
<g fill="url(#linearGradient-19)" id="蒙版">
<path id="path-24" d="M402.3,0 L402.3,212.7 C402.3,224.7 392.7,234.3 380.7,234.3 L21.6,234.3 C9.6,234.3 0,224.7 0,212.7 L0,0 L164.7,0 L164.7,0 C164.7,20.1 180.9,36.6 201.3,36.6 C221.7,36.6 237.6,20.1 237.6,0 L402.3,0 Z"></path>
</g>
<g fill="url(#linearGradient-20)" transform="translate(0.000000, 5.400000)" id="Clipped">
<path id="路径_1_" d="M402.3,0 L402.3,197.1 C402.3,209.1 392.7,218.7 380.7,218.7 L21.6,218.7 C9.6,218.7 0,209.1 0,197.1 L0,0 L164.7,0 L164.7,0 C164.7,20.1 180.9,36.6 201,36.6 C221.1,36.6 237.6,20.1 237.6,0 L402.3,0 Z"></path>
</g>
</g>
<g fill="url(#linearGradient-21)" transform="translate(125.357142, 206.632653)" id="蒙版_1_">
<path id="path-28" d="M26.1,0 L130.8,0 C145.2,0 156.9,11.7 156.9,26.1 C156.9,40.5 145.2,52.2 130.8,52.2 L26.1,52.2 C11.7,52.2 0,40.5 0,26.1 C0,11.7 11.7,0 26.1,0 Z"></path>
</g>
<g fill="url(#linearGradient-22)" transform="translate(125.357142, 204.979593)" id="蒙版备份-4">
<path id="path-31" d="M24.3,0 L132.6,0 C146.1,0 156.9,10.8 156.9,24.3 C156.9,37.8 146.1,48.6 132.6,48.6 L24.3,48.6 C10.8,48.6 0,37.8 0,24.3 C0,10.8 10.8,0 24.3,0 Z"></path>
</g>
</g>
<circle r="13.8" cy="612.3" cx="545.1" fill="url(#linearGradient-23)" id="椭圆形_10_"></circle>
<circle r="13.8" cy="615" cx="545.1" fill="url(#linearGradient-24)" id="椭圆形备份-17"></circle>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,491 @@
<template>
<div class="main-box">
<div style="margin-top: 25px;">
<span style="margin-top: 100px;" class="title">
<span class="leftText">
算力供需
</span>
<span class="rightText"> 广场</span></span>
</div>
<div style="width: 100%;max-width: 1600px;">
<div class="category-filter">
<!-- 新增的需求和商品radio按钮组 -->
<div class="radio-group-container">
<div class="radio-group">
<label class="radio-item" style="margin-right: 25px;" :class="{ active: publish_type === '1' }">
<input type="radio" v-model="publish_type" value="1" @change="handleTypeChange">
<span class="radio-text">企业商品</span>
</label>
<label class="radio-item" :class="{ active: publish_type === '2' }">
<input type="radio" v-model="publish_type" value="2" @change="handleTypeChange">
<span class="radio-text">企业需求</span>
</label>
</div>
</div>
<!-- 所属类别 -->
<div class="category-section">
<div class="category-title">所属类别</div>
<div class="category-tags">
<span v-for="category in typeList" :key="category.id"
:class="['category-tag', { active: selectedCategory === category.value }]"
@click="selectCategory(category.value)">
{{ category.label }}
</span>
</div>
</div>
<!-- 公司类别 -->
<div class="category-section">
<div class="category-title">公司类别</div>
<div class="category-tags">
<span v-for="company in companies" :key="company.id"
:class="['category-tag', { active: selectedCompanies.includes(company.value) }]"
@click="selectCompany(company.value)">
{{ company.label }}
</span>
</div>
</div>
<!-- 产品 -->
<div class="product-card-container">
<productCard v-if="productList.length > 0" type="supplyAndDemandSquare" :publish_type="publish_type"
:productList="productList">
</productCard>
<div v-else class="no-data">
<img style="width: 150px;height: 10px;" src="./img/empty.svg" alt="">
暂无数据
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { reqGetProductCategorySearch, reqGetSupplyAndDemandSquareList, reqCompanyCategorySearch } from '@/api/ncmatch'
import { mapGetters, mapState } from 'vuex'
export default {
name: 'supplyAndDemandSquare',
components: {
productCard: () => import('@/views/homePage/ncmatch/mainPage/productCard/index.vue'),
},
data() {
return {
page_size: 8,
current_page: 1,
typeList: [],
publish_type: '1', //
selectedCategory: '', //
selectedCompanies: [], //
categories: [],
companies: [],
productList: [],
detailVisible: false
}
},
created() {
this.initAllData()
},
computed: {
isNcmatchHome() {
return window.location.href.includes('ncmatchHome')
},
...mapGetters(["sidebar", "avatar", "device"]),
...mapState({
isShowPanel: (state) => state.product.showHomeNav,
navIndex: (state) => state.product.navIndex,
gridObj: state => state.operationAnalysis.gridObj,
mybalance: state => state.user.mybalance,
logoutUrl: state => state.login.logoutUrl,
loginStateVuex: state => state.login.loginState,
logoInfoNew: state => state.product.logoInfoNew,
}),
loginState() {
const userId = sessionStorage.getItem('userId');
return this.loginStateVuex || (userId !== null && userId !== 'null' && userId !== '');
},
},
methods: {
initAllData() {
// selectedCategory
this.init_product_category().then(() => {
//
this.init_company_category().then(() => {
// initData
this.initData()
})
})
},
init_company_category() {
return reqCompanyCategorySearch({ url_link: window.location.href }).then(res => {
if (res.status) {
this.company_category_list = []
for (let item of res.data) {
this.company_category_list.push({
label: item.company_category,
value: item.company_category
})
}
this.companies = this.company_category_list
}
return res
})
},
selectCategory(categoryId) {
if (categoryId === this.selectedCategory) return;
this.selectedCategory = categoryId
this.initData(this.selectedCategory)
},
init_product_category(product_category) {
return reqGetProductCategorySearch({ url_link: window.location.href, to_page: 'show' }).then(res => {
if (res.status) {
let list = []
for (let item of res.data) {
if (item.parentid === null) {
list.push({
label: item.product_category,
value: item.id
})
}
}
this.selectedCategory = list[0].value
this.typeList = list
}
return res
})
},
handleTypeChange() {
//
console.log('切换到:', this.publish_type)
//
this.initData()
},
initData() {
let ploay = {
product_category: this.selectedCategory,
to_page: "square",
url_link: window.location.href,
page_size: this.page_size,
current_page: this.current_page,
company_type: this.selectedCompanies.length > 0 ? this.selectedCompanies.join(",") : "",
publish_type: this.publish_type
}
reqGetSupplyAndDemandSquareList(ploay).then(res => {
if (res.status) {
if (res.data.length === 0) {
this.productList = []
} else {
this.productList = res.data[0].product_list
}
}
})
},
selectCompany(companyId) {
const index = this.selectedCompanies.indexOf(companyId)
if (index > -1) {
//
this.selectedCompanies.splice(index, 1)
} else {
//
this.selectedCompanies.push(companyId)
}
console.log("this.selectedCompanies", this.selectedCompanies);
this.initData()
},
getProductList() {
this.productList = [
{
id: 1,
name: 'NVIDIA-4090',
image: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDIwMCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiBmaWxsPSIjMjIyMjIyIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5WSUQpQSBSVFggNDA5MDwvdGV4dD4KPC9zdmc+Cg==',
price: 6000,
cpu: 'AMD EPYC 7542 32 C * 2',
memory: '64G DDR4-3200 * 8',
gpu: 'NVIDIA-4090-24GB * 8',
sys_disk: '960G SATA SSD * 2 (Raid)',
data_disk: '3.84T U.2 NVMe SSD * 1',
net_card: 'Mellanox Connect4 25G SFP28 2-port * 1'
},
{
id: 2,
name: 'NVIDIA-A100',
image: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDIwMCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiBmaWxsPSIjMjIyMjIyIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5WSUQpQSBSVFggNDA5MDwvdGV4dD4KPC9zdmc+Cg==',
price: 8000,
cpu: 'AMD EPYC 7542 32 C * 2',
memory: '128G DDR4-3200 * 8',
gpu: 'NVIDIA-A100-80GB * 8',
sys_disk: '1.92T SATA SSD * 2 (Raid)',
data_disk: '7.68T U.2 NVMe SSD * 1',
net_card: 'Mellanox Connect4 25G SFP28 2-port * 1'
},
{
id: 3,
name: '昇腾910B',
image: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDIwMCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiBmaWxsPSIjMjIyMjIyIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5WSUQpQSBSVFggNDA5MDwvdGV4dD4KPC9zdmc+Cg==',
price: 5000,
cpu: 'Kunpeng 920 64 C * 2',
memory: '64G DDR4-3200 * 8',
gpu: '昇腾910B-32GB * 8',
sys_disk: '960G SATA SSD * 2 (Raid)',
data_disk: '3.84T U.2 NVMe SSD * 1',
net_card: 'Mellanox Connect4 25G SFP28 2-port * 1'
},
{
id: 4,
name: '昆仑芯P800',
image: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDIwMCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiBmaWxsPSIjMjIyMjIyIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5WSUQpQSBSVFggNDA5MDwvdGV4dD4KPC9zdmc+Cg==',
price: 4000,
cpu: 'Kunpeng 920 64 C * 2',
memory: '64G DDR4-3200 * 8',
gpu: '昆仑芯P800-16GB * 8',
sys_disk: '960G SATA SSD * 2 (Raid)',
data_disk: '3.84T U.2 NVMe SSD * 1',
net_card: 'Mellanox Connect4 25G SFP28 2-port * 1'
}
]
}
},
mounted() {
// this.getProductList()
}
}
</script>
<style scoped lang="scss">
.back-btn {
width: 100%;
height: 40px;
line-height: 40px;
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 20px;
font-size: 16px;
color: #666;
&:hover {
cursor: pointer;
color: #275AFF;
}
}
/* 新增的radio按钮组样式 */
.radio-group-container {
width: 100% !important;
display: flex;
justify-content: flex-start;
// margin: 20px 0;
align-items: center;
max-width: 1400px;
}
.radio-group {
display: flex;
background: #fff;
border-radius: 8px;
margin-bottom: 15px;
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.radio-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
background: #fff;
border: 1px solid #e8e8e8;
margin-right: 4px;
&:last-child {
margin-right: 0;
}
input[type="radio"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.radio-text {
font-size: 14px;
color: #275AFF;
font-weight: 500;
transition: all 0.3s ease;
}
&:hover {
border-color: #275AFF;
}
&.active {
background: linear-gradient(to right, #275AFF, #2EBDFA);
border-color: #275AFF;
box-shadow: 0 2px 8px rgba(39, 90, 255, 0.3);
.radio-text {
color: #fff;
}
}
}
.title {
color: #666 !important;
font-size: 36px;
margin: 25px;
.leftText {
background: linear-gradient(to right, #275AFF, #2EBDFA);
/* 渐变方向颜色 */
-webkit-background-clip: text;
/* 关键属性:裁剪背景到文字 */
background-clip: text;
color: transparent;
/* 文字颜色透明 */
display: inline-block;
/* 确保渐变生效 */
}
}
.itemTitle {
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
.topText {
color: #222F60;
font-size: 48px;
font-weight: bold;
}
.bottomText {
font-size: 24px;
color: #7A82A0;
margin-top: 15px;
}
}
.product-list {
width: 100%;
// display: grid;
// grid-template-columns: repeat(4, 1fr);
// gap: 20px;
.no-data {
text-align: center;
font-size: 16px;
}
}
/* 产品卡片容器样式 */
.product-card-container {
margin-top: 20px;
width: 100%;
}
.main-box {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.category-filter {
// padding: 20px;
bottom: 1rem;
background-color: #fff;
border-radius: 8px;
margin: 20px;
margin-left: 0;
margin-right: 0;
.category-section {
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.category-title {
font-size: 14px;
color: #333;
// margin-bottom: 12px;
font-weight: 500;
display: flex;
justify-content: flex-end;
align-items: center;
height: 100%;
margin-right: 15px;
}
.category-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
.category-tag {
padding: 3px 6px;
background-color: #fff;
border: 1px solid #e8e8e8;
border-radius: 2px;
font-size: 12px;
color: #666;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
border-color: #1890ff;
color: #1890ff;
}
&.active {
background-color: #e6f7ff;
border-color: #1890ff;
color: #1890ff;
}
}
}
}
}
.no-data {
min-height: 500px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
color: #7A82A0;
flex-direction: column;
}
</style>

View File

@ -198,7 +198,7 @@ import './js/wxLogin.js'
import {Message} from "element-ui";
import router, {resetRouter} from "@/router";
import {reqNewHomeFestival} from "@/api/newHome";
import { getHomePath } from '@/views/setting/tools'
export default {
name: "indexNew",
components: {BeforeLogin, promotionalInvitationCode},
@ -597,7 +597,7 @@ export default {
// } else {
// window.location.href = 'https://www.opencomputing.cn/';
// }
this.$router.push('/homePage/index')
this.$router.push(getHomePath())
},
init() {
// let params = {

View File

@ -0,0 +1,28 @@
export function getHomePath() {
let homePath= "/homePage/index"
// let homePath= "/ncmatchHome/index"
// 业主机构信息
let url_link = window.location.href || '';
let domain_url = '';
if (url_link.includes('?domain_name=')) {
domain_url = url_link.split('?domain_name=')[1];
}
// 如果是业主机构
if ((domain_url.includes('ncmatch') ||
domain_url.includes('9527') ||
domain_url.includes('8889') ||
domain_url.includes('8891') ||
['xterm.kaiyuancloud.cn', 'www.kaiyuancloud.cn', 'dev.kaiyuancloud.cn', 'dev.opencomputing.cn', 'test.kaiyuancloud.cn', 'localhost'].includes(domain_url)) &&
!url_link.includes('/domain/')) {
if (domain_url.includes('ncmatch') || domain_url.includes('9527')) {
homePath = '/homePath/index'
} else if (domain_url.includes('kaiyuancloud') || domain_url.includes('opencomputing') || domain_url.includes('localhost')) {
homePath = '/ncmatchHome/index'
}
}
console.log("res是",homePath)
return "/homePage/index"
}

View File

@ -0,0 +1,8 @@
const webConfig={
KYY:{
homePath:"",
},
NCMATCH:{
homePath:"",
}
}