commit
6dbe8fdcc3
@ -1,3 +1,34 @@
|
|||||||
|
# 新版本表结构
|
||||||
|
CREATE TABLE `enterprise_news_article` (
|
||||||
|
`id` varchar(32) NOT NULL COMMENT '唯一标识符',
|
||||||
|
`domain_name` varchar(64) NOT NULL COMMENT '所属域名',
|
||||||
|
`title_zh` varchar(100) DEFAULT NULL COMMENT '中文标题',
|
||||||
|
`title_en` varchar(100) DEFAULT NULL COMMENT '英文标题',
|
||||||
|
`summary_zh` varchar(255) DEFAULT NULL COMMENT '中文摘要',
|
||||||
|
`summary_en` varchar(255) DEFAULT NULL COMMENT '英文摘要',
|
||||||
|
`article_type_zh` varchar(20) DEFAULT NULL COMMENT '中文文章类型',
|
||||||
|
`article_type_en` varchar(20) DEFAULT NULL COMMENT '英文文章类型',
|
||||||
|
`content_zh` text DEFAULT NULL COMMENT '中文正文',
|
||||||
|
`content_en` text DEFAULT NULL COMMENT '英文正文',
|
||||||
|
`cover_img_zh` varchar(255) DEFAULT NULL COMMENT '中文封面图片',
|
||||||
|
`cover_img_en` varchar(255) DEFAULT NULL COMMENT '英文封面图片',
|
||||||
|
`publish_time_zh` date DEFAULT NULL COMMENT '中文发布时间',
|
||||||
|
`publish_time_en` date DEFAULT NULL COMMENT '英文发布时间',
|
||||||
|
`title` varchar(100) DEFAULT NULL COMMENT '文章标题',
|
||||||
|
`article_type` varchar(20) DEFAULT NULL COMMENT '文章类型(企业动态/产品动态/行业洞察/活动资讯)',
|
||||||
|
`summary` varchar(255) DEFAULT NULL COMMENT '文章摘要',
|
||||||
|
`cover_img` varchar(255) DEFAULT NULL COMMENT '封面图片',
|
||||||
|
`content` text DEFAULT NULL COMMENT '文章正文',
|
||||||
|
`status` varchar(1) DEFAULT '0' COMMENT '状态(0-草稿/1-已发布)',
|
||||||
|
`publish_time` date DEFAULT NULL COMMENT '发布时间',
|
||||||
|
`read_count` int(11) DEFAULT 0 COMMENT '阅读数据',
|
||||||
|
`del_flg` varchar(1) DEFAULT '0' COMMENT '删除标志(0-正常/1-已删除)',
|
||||||
|
`update_time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '更新时间',
|
||||||
|
`create_at` timestamp NULL DEFAULT current_timestamp() COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='企业文章表';
|
||||||
|
|
||||||
|
# 旧版本表结构
|
||||||
CREATE TABLE `enterprise_news_article` (
|
CREATE TABLE `enterprise_news_article` (
|
||||||
`id` varchar(32) NOT NULL COMMENT '唯一标识符',
|
`id` varchar(32) NOT NULL COMMENT '唯一标识符',
|
||||||
`domain_name` varchar(64) NOT NULL COMMENT '所属域名',
|
`domain_name` varchar(64) NOT NULL COMMENT '所属域名',
|
||||||
|
|||||||
@ -11,7 +11,9 @@ async def front_news_detail(ns={}):
|
|||||||
update_sql = """update enterprise_news_article set read_count = ifnull(read_count, 0) + 1 where id = '%s' and status = '1' and del_flg = '0';""" % ns.get('id')
|
update_sql = """update enterprise_news_article set read_count = ifnull(read_count, 0) + 1 where id = '%s' and status = '1' and del_flg = '0';""" % ns.get('id')
|
||||||
await sor.sqlExe(update_sql, {})
|
await sor.sqlExe(update_sql, {})
|
||||||
search_sql = """
|
search_sql = """
|
||||||
select id, title, article_type, summary, cover_img, content, publish_time, read_count
|
select id, title_zh, title_en, article_type_zh, article_type_en,
|
||||||
|
summary_zh, summary_en, cover_img_zh, cover_img_en,
|
||||||
|
content_zh, content_en, publish_time_zh, publish_time_en, read_count
|
||||||
from enterprise_news_article
|
from enterprise_news_article
|
||||||
where id = '%s' and status = '1' and del_flg = '0';
|
where id = '%s' and status = '1' and del_flg = '0';
|
||||||
""" % ns.get('id')
|
""" % ns.get('id')
|
||||||
|
|||||||
@ -13,10 +13,14 @@ async def front_news_search(ns={}):
|
|||||||
offset = (current_page - 1) * page_size
|
offset = (current_page - 1) * page_size
|
||||||
|
|
||||||
conditions = ["domain_name = '%s'" % domain_name, "status = '1'", "del_flg = '0'"]
|
conditions = ["domain_name = '%s'" % domain_name, "status = '1'", "del_flg = '0'"]
|
||||||
if ns.get('article_type'):
|
if ns.get('article_type_zh'):
|
||||||
conditions.append("article_type = '%s'" % ns.get('article_type'))
|
conditions.append("article_type_zh = '%s'" % ns.get('article_type_zh'))
|
||||||
if ns.get('title'):
|
if ns.get('article_type_en'):
|
||||||
conditions.append("title like '%%%%%s%%%%'" % ns.get('title'))
|
conditions.append("article_type_en = '%s'" % ns.get('article_type_en'))
|
||||||
|
if ns.get('title_zh'):
|
||||||
|
conditions.append("title_zh like '%%%%%s%%%%'" % ns.get('title_zh'))
|
||||||
|
if ns.get('title_en'):
|
||||||
|
conditions.append("title_en like '%%%%%s%%%%'" % ns.get('title_en'))
|
||||||
where_clause = " and ".join(conditions)
|
where_clause = " and ".join(conditions)
|
||||||
|
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
@ -25,10 +29,15 @@ async def front_news_search(ns={}):
|
|||||||
count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause
|
count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause
|
||||||
total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count']
|
total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count']
|
||||||
search_sql = """
|
search_sql = """
|
||||||
select id, title, article_type, summary, cover_img, publish_time, read_count
|
select id, title_zh, title_en, article_type_zh, article_type_en,
|
||||||
|
summary_zh, summary_en, cover_img_zh, cover_img_en,
|
||||||
|
publish_time_zh, publish_time_en, read_count
|
||||||
from enterprise_news_article
|
from enterprise_news_article
|
||||||
where %s
|
where %s
|
||||||
order by publish_time desc, update_time desc
|
order by greatest(
|
||||||
|
ifnull(publish_time_zh, '1000-01-01'),
|
||||||
|
ifnull(publish_time_en, '1000-01-01')
|
||||||
|
) desc, update_time desc
|
||||||
limit %s offset %s;
|
limit %s offset %s;
|
||||||
""" % (where_clause, page_size, offset)
|
""" % (where_clause, page_size, offset)
|
||||||
result = await sor.sqlExe(search_sql, {})
|
result = await sor.sqlExe(search_sql, {})
|
||||||
|
|||||||
@ -4,16 +4,11 @@ async def news_article_add(ns={}):
|
|||||||
'status': False,
|
'status': False,
|
||||||
'msg': '请传递url_link'
|
'msg': '请传递url_link'
|
||||||
}
|
}
|
||||||
if not ns.get('title'):
|
if not ns.get('title_zh'):
|
||||||
return {
|
return {
|
||||||
'status': False,
|
'status': False,
|
||||||
'msg': '请传递标题'
|
'msg': '请传递中文标题'
|
||||||
}
|
}
|
||||||
# if ns.get('article_type') not in ['企业动态', '产品动态', '行业洞察', '活动资讯']:
|
|
||||||
# return {
|
|
||||||
# 'status': False,
|
|
||||||
# 'msg': '文章类型错误'
|
|
||||||
# }
|
|
||||||
|
|
||||||
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
domain_name = ns.get('url_link').split("//")[1].split("/")[0]
|
||||||
if 'localhost' in domain_name:
|
if 'localhost' in domain_name:
|
||||||
@ -26,13 +21,19 @@ async def news_article_add(ns={}):
|
|||||||
ns_dic = {
|
ns_dic = {
|
||||||
'id': uuid(),
|
'id': uuid(),
|
||||||
'domain_name': domain_name,
|
'domain_name': domain_name,
|
||||||
'title': ns.get('title'),
|
'title_zh': ns.get('title_zh'),
|
||||||
'article_type': ns.get('article_type'),
|
'title_en': ns.get('title_en'),
|
||||||
'summary': ns.get('summary'),
|
'article_type_zh': ns.get('article_type_zh'),
|
||||||
'cover_img': ns.get('cover_img'),
|
'article_type_en': ns.get('article_type_en'),
|
||||||
'content': ns.get('content'),
|
'summary_zh': ns.get('summary_zh'),
|
||||||
|
'summary_en': ns.get('summary_en'),
|
||||||
|
'cover_img_zh': ns.get('cover_img_zh'),
|
||||||
|
'cover_img_en': ns.get('cover_img_en'),
|
||||||
|
'content_zh': ns.get('content_zh'),
|
||||||
|
'content_en': ns.get('content_en'),
|
||||||
'status': status,
|
'status': status,
|
||||||
'publish_time': ns.get('publish_time'),
|
'publish_time_zh': ns.get('publish_time_zh') or None,
|
||||||
|
'publish_time_en': ns.get('publish_time_en') or None,
|
||||||
'read_count': 0,
|
'read_count': 0,
|
||||||
'del_flg': '0'
|
'del_flg': '0'
|
||||||
}
|
}
|
||||||
@ -41,8 +42,13 @@ async def news_article_add(ns={}):
|
|||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
try:
|
try:
|
||||||
await sor.C('enterprise_news_article', ns_dic)
|
await sor.C('enterprise_news_article', ns_dic)
|
||||||
if status == '1' and not ns.get('publish_time'):
|
if status == '1':
|
||||||
publish_sql = """update enterprise_news_article set publish_time = current_timestamp() where id = '%s';""" % ns_dic.get('id')
|
publish_sql = """
|
||||||
|
update enterprise_news_article
|
||||||
|
set publish_time_zh = ifnull(publish_time_zh, current_date()),
|
||||||
|
publish_time_en = ifnull(publish_time_en, current_date())
|
||||||
|
where id = '%s';
|
||||||
|
""" % ns_dic.get('id')
|
||||||
await sor.sqlExe(publish_sql, {})
|
await sor.sqlExe(publish_sql, {})
|
||||||
return {
|
return {
|
||||||
'status': True,
|
'status': True,
|
||||||
|
|||||||
@ -8,11 +8,15 @@ async def news_article_publish(ns={}):
|
|||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
try:
|
try:
|
||||||
publish_time = ns.get('publish_time')
|
publish_time_zh = "'%s'" % ns.get('publish_time_zh') if ns.get('publish_time_zh') else "ifnull(publish_time_zh, current_date())"
|
||||||
if publish_time:
|
publish_time_en = "'%s'" % ns.get('publish_time_en') if ns.get('publish_time_en') else "ifnull(publish_time_en, current_date())"
|
||||||
publish_sql = """update enterprise_news_article set status = '1', publish_time = '%s' where id = '%s' and del_flg = '0';""" % (publish_time, ns.get('id'))
|
publish_sql = """
|
||||||
else:
|
update enterprise_news_article
|
||||||
publish_sql = """update enterprise_news_article set status = '1' where id = '%s' and del_flg = '0';""" % ns.get('id')
|
set status = '1',
|
||||||
|
publish_time_zh = %s,
|
||||||
|
publish_time_en = %s
|
||||||
|
where id = '%s' and del_flg = '0';
|
||||||
|
""" % (publish_time_zh, publish_time_en, ns.get('id'))
|
||||||
await sor.sqlExe(publish_sql, {})
|
await sor.sqlExe(publish_sql, {})
|
||||||
return {
|
return {
|
||||||
'status': True,
|
'status': True,
|
||||||
|
|||||||
@ -14,10 +14,14 @@ async def news_article_search(ns={}):
|
|||||||
|
|
||||||
conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"]
|
conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"]
|
||||||
summary_conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"]
|
summary_conditions = ["domain_name = '%s'" % domain_name, "del_flg = '0'"]
|
||||||
if ns.get('title'):
|
if ns.get('title_zh'):
|
||||||
conditions.append("title like '%%%%%s%%%%'" % ns.get('title'))
|
conditions.append("title_zh like '%%%%%s%%%%'" % ns.get('title_zh'))
|
||||||
if ns.get('article_type'):
|
if ns.get('title_en'):
|
||||||
conditions.append("article_type = '%s'" % ns.get('article_type'))
|
conditions.append("title_en like '%%%%%s%%%%'" % ns.get('title_en'))
|
||||||
|
if ns.get('article_type_zh'):
|
||||||
|
conditions.append("article_type_zh = '%s'" % ns.get('article_type_zh'))
|
||||||
|
if ns.get('article_type_en'):
|
||||||
|
conditions.append("article_type_en = '%s'" % ns.get('article_type_en'))
|
||||||
if ns.get('status'):
|
if ns.get('status'):
|
||||||
conditions.append("status = '%s'" % ns.get('status'))
|
conditions.append("status = '%s'" % ns.get('status'))
|
||||||
where_clause = " and ".join(conditions)
|
where_clause = " and ".join(conditions)
|
||||||
@ -27,30 +31,53 @@ async def news_article_search(ns={}):
|
|||||||
try:
|
try:
|
||||||
count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause
|
count_sql = """select count(*) as total_count from enterprise_news_article where %s;""" % where_clause
|
||||||
total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count']
|
total_count = (await sor.sqlExe(count_sql, {}))[0]['total_count']
|
||||||
summary_sql = """
|
summary_zh_sql = """
|
||||||
select article_type, count(*) as article_count, ifnull(sum(read_count), 0) as read_count
|
select article_type_zh as article_type, count(*) as article_count,
|
||||||
|
ifnull(sum(read_count), 0) as read_count
|
||||||
from enterprise_news_article
|
from enterprise_news_article
|
||||||
where %s
|
where %s
|
||||||
group by article_type;
|
and article_type_zh is not null and article_type_zh != ''
|
||||||
|
group by article_type_zh;
|
||||||
""" % " and ".join(summary_conditions)
|
""" % " and ".join(summary_conditions)
|
||||||
summary_result = await sor.sqlExe(summary_sql, {})
|
summary_en_sql = """
|
||||||
summary_mapping = {}
|
select article_type_en as article_type, count(*) as article_count,
|
||||||
for summary_dic in summary_result:
|
ifnull(sum(read_count), 0) as read_count
|
||||||
summary_mapping[summary_dic.get('article_type')] = summary_dic
|
from enterprise_news_article
|
||||||
article_type_summary = []
|
where %s
|
||||||
|
and article_type_en is not null and article_type_en != ''
|
||||||
|
group by article_type_en
|
||||||
|
order by article_type_en;
|
||||||
|
""" % " and ".join(summary_conditions)
|
||||||
|
summary_zh_result = await sor.sqlExe(summary_zh_sql, {})
|
||||||
|
summary_en_result = await sor.sqlExe(summary_en_sql, {})
|
||||||
|
summary_zh_mapping = {}
|
||||||
|
for summary_dic in summary_zh_result:
|
||||||
|
summary_zh_mapping[summary_dic.get('article_type')] = summary_dic
|
||||||
|
article_type_summary_zh = []
|
||||||
for article_type in ['企业动态', '产品动态', '行业洞察', '活动资讯']:
|
for article_type in ['企业动态', '产品动态', '行业洞察', '活动资讯']:
|
||||||
summary_dic = summary_mapping.get(article_type, {})
|
summary_dic = summary_zh_mapping.get(article_type, {})
|
||||||
article_type_summary.append({
|
article_type_summary_zh.append({
|
||||||
'article_type': article_type,
|
'article_type': article_type,
|
||||||
'article_count': summary_dic.get('article_count', 0),
|
'article_count': summary_dic.get('article_count', 0),
|
||||||
'read_count': summary_dic.get('read_count', 0)
|
'read_count': summary_dic.get('read_count', 0)
|
||||||
})
|
})
|
||||||
|
article_type_summary_en = []
|
||||||
|
for summary_dic in summary_en_result:
|
||||||
|
article_type_summary_en.append({
|
||||||
|
'article_type': summary_dic.get('article_type'),
|
||||||
|
'article_count': summary_dic.get('article_count', 0),
|
||||||
|
'read_count': summary_dic.get('read_count', 0)
|
||||||
|
})
|
||||||
search_sql = """
|
search_sql = """
|
||||||
select id, domain_name, title, article_type, summary, cover_img, status, content,
|
select id, domain_name, title_zh, title_en, article_type_zh, article_type_en,
|
||||||
publish_time, read_count, update_time, create_at
|
summary_zh, summary_en, cover_img_zh, cover_img_en, content_zh, content_en,
|
||||||
|
status, publish_time_zh, publish_time_en, read_count, update_time, create_at
|
||||||
from enterprise_news_article
|
from enterprise_news_article
|
||||||
where %s
|
where %s
|
||||||
order by publish_time desc, update_time desc
|
order by greatest(
|
||||||
|
ifnull(publish_time_zh, '1000-01-01'),
|
||||||
|
ifnull(publish_time_en, '1000-01-01')
|
||||||
|
) desc, update_time desc
|
||||||
limit %s offset %s;
|
limit %s offset %s;
|
||||||
""" % (where_clause, page_size, offset)
|
""" % (where_clause, page_size, offset)
|
||||||
result = await sor.sqlExe(search_sql, {})
|
result = await sor.sqlExe(search_sql, {})
|
||||||
@ -61,7 +88,8 @@ async def news_article_search(ns={}):
|
|||||||
'status': True,
|
'status': True,
|
||||||
'msg': 'search news article success',
|
'msg': 'search news article success',
|
||||||
'data': result,
|
'data': result,
|
||||||
'article_type_summary': article_type_summary,
|
'article_type_summary_zh': article_type_summary_zh,
|
||||||
|
'article_type_summary_en': article_type_summary_en,
|
||||||
'pagination': {
|
'pagination': {
|
||||||
'total': total_count,
|
'total': total_count,
|
||||||
'page_size': page_size,
|
'page_size': page_size,
|
||||||
|
|||||||
@ -8,21 +8,16 @@ async def news_article_update(ns={}):
|
|||||||
ns_dic = {
|
ns_dic = {
|
||||||
'id': ns.get('id')
|
'id': ns.get('id')
|
||||||
}
|
}
|
||||||
if 'title' in ns:
|
for field in [
|
||||||
ns_dic['title'] = ns.get('title')
|
'title_zh', 'title_en', 'article_type_zh', 'article_type_en',
|
||||||
if 'article_type' in ns:
|
'summary_zh', 'summary_en', 'content_zh', 'content_en',
|
||||||
# if ns.get('article_type') not in ['企业动态', '产品动态', '行业洞察', '活动资讯']:
|
'cover_img_zh', 'cover_img_en', 'publish_time_zh', 'publish_time_en'
|
||||||
# return {
|
]:
|
||||||
# 'status': False,
|
if field in ns:
|
||||||
# 'msg': '文章类型错误'
|
if field in ['publish_time_zh', 'publish_time_en']:
|
||||||
# }
|
ns_dic[field] = ns.get(field) or None
|
||||||
ns_dic['article_type'] = ns.get('article_type')
|
else:
|
||||||
if 'summary' in ns:
|
ns_dic[field] = ns.get(field)
|
||||||
ns_dic['summary'] = ns.get('summary')
|
|
||||||
if 'cover_img' in ns:
|
|
||||||
ns_dic['cover_img'] = ns.get('cover_img')
|
|
||||||
if 'content' in ns:
|
|
||||||
ns_dic['content'] = ns.get('content')
|
|
||||||
if 'status' in ns:
|
if 'status' in ns:
|
||||||
if ns.get('status') not in ['0', '1']:
|
if ns.get('status') not in ['0', '1']:
|
||||||
return {
|
return {
|
||||||
@ -30,15 +25,17 @@ async def news_article_update(ns={}):
|
|||||||
'msg': '状态错误'
|
'msg': '状态错误'
|
||||||
}
|
}
|
||||||
ns_dic['status'] = ns.get('status')
|
ns_dic['status'] = ns.get('status')
|
||||||
if 'publish_time' in ns:
|
|
||||||
ns_dic['publish_time'] = ns.get('publish_time')
|
|
||||||
|
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext('kboss') as sor:
|
async with db.sqlorContext('kboss') as sor:
|
||||||
try:
|
try:
|
||||||
await sor.U('enterprise_news_article', ns_dic)
|
await sor.U('enterprise_news_article', ns_dic)
|
||||||
if ns.get('status') == '1' and not ns.get('publish_time'):
|
if ns.get('status') == '1':
|
||||||
publish_sql = """update enterprise_news_article set publish_time = ifnull(publish_time, current_timestamp()) where id = '%s';""" % ns.get('id')
|
publish_sql = """
|
||||||
|
update enterprise_news_article
|
||||||
|
set publish_time_zh = ifnull(publish_time_zh, current_date()),
|
||||||
|
publish_time_en = ifnull(publish_time_en, current_date())
|
||||||
|
where id = '%s';
|
||||||
|
""" % ns.get('id')
|
||||||
await sor.sqlExe(publish_sql, {})
|
await sor.sqlExe(publish_sql, {})
|
||||||
return {
|
return {
|
||||||
'status': True,
|
'status': True,
|
||||||
|
|||||||
233
docs/superpowers/plans/2026-07-29-bilingual-enterprise-news.md
Normal file
233
docs/superpowers/plans/2026-07-29-bilingual-enterprise-news.md
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
# Bilingual Enterprise News Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Update enterprise news APIs to store, filter, and return Chinese and English article fields without maintaining legacy content fields.
|
||||||
|
|
||||||
|
**Architecture:** Keep the existing DSPY endpoint structure and database table. Add and update endpoints write the eight language-specific columns; search and detail endpoints return those columns directly. Shared publication, image, date, status, and read-count behavior remains unchanged.
|
||||||
|
|
||||||
|
**Tech Stack:** DSPY Python endpoints, async DBPools/sor database access, MySQL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add and update bilingual content
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `b/news/news_article_add.dspy`
|
||||||
|
- Modify: `b/news/news_article_update.dspy`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update add validation**
|
||||||
|
|
||||||
|
Replace the legacy `title` requirement with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if not ns.get('title_zh'):
|
||||||
|
return {
|
||||||
|
'status': False,
|
||||||
|
'msg': '请传递中文标题'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update add insert fields**
|
||||||
|
|
||||||
|
Write these fields in `ns_dic`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
'title_zh': ns.get('title_zh'),
|
||||||
|
'title_en': ns.get('title_en'),
|
||||||
|
'article_type_zh': ns.get('article_type_zh'),
|
||||||
|
'article_type_en': ns.get('article_type_en'),
|
||||||
|
'summary_zh': ns.get('summary_zh'),
|
||||||
|
'summary_en': ns.get('summary_en'),
|
||||||
|
'content_zh': ns.get('content_zh'),
|
||||||
|
'content_en': ns.get('content_en'),
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not write `title`, `article_type`, `summary`, or `content`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update editable fields**
|
||||||
|
|
||||||
|
In `news_article_update.dspy`, loop through the eight bilingual fields and copy only keys present in `ns`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for field in [
|
||||||
|
'title_zh', 'title_en', 'article_type_zh', 'article_type_en',
|
||||||
|
'summary_zh', 'summary_en', 'content_zh', 'content_en'
|
||||||
|
]:
|
||||||
|
if field in ns:
|
||||||
|
ns_dic[field] = ns.get(field)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify static diagnostics**
|
||||||
|
|
||||||
|
Run IDE lint diagnostics for both files. Expected: no new errors.
|
||||||
|
|
||||||
|
### Task 2: Update backend search and detail
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `b/news/news_article_search.dspy`
|
||||||
|
- Verify: `b/news/news_article_detail.dspy`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add bilingual filters**
|
||||||
|
|
||||||
|
Build optional conditions for exact type matching and fuzzy title matching:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if ns.get('title_zh'):
|
||||||
|
conditions.append("title_zh like '%%%%%s%%%%'" % ns.get('title_zh'))
|
||||||
|
if ns.get('title_en'):
|
||||||
|
conditions.append("title_en like '%%%%%s%%%%'" % ns.get('title_en'))
|
||||||
|
if ns.get('article_type_zh'):
|
||||||
|
conditions.append("article_type_zh = '%s'" % ns.get('article_type_zh'))
|
||||||
|
if ns.get('article_type_en'):
|
||||||
|
conditions.append("article_type_en = '%s'" % ns.get('article_type_en'))
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Return bilingual list fields**
|
||||||
|
|
||||||
|
Select:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
id, domain_name,
|
||||||
|
title_zh, title_en,
|
||||||
|
article_type_zh, article_type_en,
|
||||||
|
summary_zh, summary_en,
|
||||||
|
content_zh, content_en,
|
||||||
|
cover_img, status, publish_time, read_count, update_time, create_at
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Return bilingual type summaries**
|
||||||
|
|
||||||
|
Aggregate Chinese and English article types separately and return:
|
||||||
|
|
||||||
|
```python
|
||||||
|
'article_type_summary_zh': article_type_summary_zh,
|
||||||
|
'article_type_summary_en': article_type_summary_en,
|
||||||
|
```
|
||||||
|
|
||||||
|
Each item keeps `article_type`, `article_count`, and `read_count`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify backend detail**
|
||||||
|
|
||||||
|
`news_article_detail.dspy` already uses `select *`; confirm it returns the new columns without changing publication behavior.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify static diagnostics**
|
||||||
|
|
||||||
|
Run IDE lint diagnostics for backend search and detail. Expected: no new errors.
|
||||||
|
|
||||||
|
### Task 3: Update frontend search and detail
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `b/news/front_news_search.dspy`
|
||||||
|
- Modify: `b/news/front_news_detail.dspy`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add bilingual frontend filters**
|
||||||
|
|
||||||
|
Support `title_zh`, `title_en`, `article_type_zh`, and `article_type_en` using the same matching rules as backend search.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Return bilingual frontend list fields**
|
||||||
|
|
||||||
|
Select:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
id, title_zh, title_en, article_type_zh, article_type_en,
|
||||||
|
summary_zh, summary_en, cover_img, publish_time, read_count
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Return bilingual frontend detail fields**
|
||||||
|
|
||||||
|
After incrementing `read_count`, select:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
id, title_zh, title_en, article_type_zh, article_type_en,
|
||||||
|
summary_zh, summary_en, cover_img, content_zh, content_en,
|
||||||
|
publish_time, read_count
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify publication and read-count behavior**
|
||||||
|
|
||||||
|
Confirm both frontend queries retain `status = '1' AND del_flg = '0'`, and detail still increments `read_count` once.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify static diagnostics**
|
||||||
|
|
||||||
|
Run IDE lint diagnostics for both frontend files. Expected: no new errors.
|
||||||
|
|
||||||
|
### Task 4: Final verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify: `b/news/news_article_add.dspy`
|
||||||
|
- Verify: `b/news/news_article_update.dspy`
|
||||||
|
- Verify: `b/news/news_article_search.dspy`
|
||||||
|
- Verify: `b/news/news_article_detail.dspy`
|
||||||
|
- Verify: `b/news/front_news_search.dspy`
|
||||||
|
- Verify: `b/news/front_news_detail.dspy`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Check legacy-field removal**
|
||||||
|
|
||||||
|
Search the six endpoints for writes or selected output fields named exactly `title`, `article_type`, `summary`, or `content`. Expected: none except comments or compatibility-neutral code.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Check diff scope**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff -- b/news
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: only the requested bilingual endpoint changes plus the user's existing SQL schema update.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Check whitespace**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff --check -- b/news
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: no output and exit code 0.
|
||||||
|
|
||||||
|
### Task 5: Add bilingual cover images and publish times
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `b/news/news_article_add.dspy`
|
||||||
|
- Modify: `b/news/news_article_update.dspy`
|
||||||
|
- Modify: `b/news/news_article_publish.dspy`
|
||||||
|
- Modify: `b/news/news_article_search.dspy`
|
||||||
|
- Modify: `b/news/front_news_search.dspy`
|
||||||
|
- Modify: `b/news/front_news_detail.dspy`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace shared write fields**
|
||||||
|
|
||||||
|
Use `cover_img_zh`, `cover_img_en`, `publish_time_zh`, and
|
||||||
|
`publish_time_en` in add and update. Do not write `cover_img` or
|
||||||
|
`publish_time`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Apply publication defaults**
|
||||||
|
|
||||||
|
When status becomes `1`, set each missing publish-time column with:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
publish_time_zh = ifnull(publish_time_zh, current_date()),
|
||||||
|
publish_time_en = ifnull(publish_time_en, current_date())
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update publish endpoint**
|
||||||
|
|
||||||
|
Accept both publish-time parameters and use `current_date()` for either
|
||||||
|
missing value while setting `status = '1'`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Update list and detail output**
|
||||||
|
|
||||||
|
Return both cover-image and publish-time columns. Sort lists by:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
greatest(
|
||||||
|
ifnull(publish_time_zh, '1000-01-01'),
|
||||||
|
ifnull(publish_time_en, '1000-01-01')
|
||||||
|
) desc, update_time desc
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify**
|
||||||
|
|
||||||
|
Run static field-contract checks, IDE diagnostics, and
|
||||||
|
`git diff --check -- b/news`. Expected: all pass.
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
# 中英文企业动态接口设计
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
企业文章支持运营同时录入中文和英文内容。接口直接使用
|
||||||
|
`enterprise_news_article` 表中的中英文字段,不再读写旧字段
|
||||||
|
`title`、`article_type`、`summary`、`content`。
|
||||||
|
|
||||||
|
## 字段
|
||||||
|
|
||||||
|
- 中文:`title_zh`、`article_type_zh`、`summary_zh`、`content_zh`
|
||||||
|
- 英文:`title_en`、`article_type_en`、`summary_en`、`content_en`
|
||||||
|
- 中文扩展:`cover_img_zh`、`publish_time_zh`
|
||||||
|
- 英文扩展:`cover_img_en`、`publish_time_en`
|
||||||
|
- 共用:`status`、`read_count`
|
||||||
|
|
||||||
|
新增文章仅要求 `title_zh` 必填,其余中英文字段允许为空。
|
||||||
|
文章发布时,如果未传中英文发布时间,两个字段都默认当天日期。
|
||||||
|
|
||||||
|
## 接口调整
|
||||||
|
|
||||||
|
- `news_article_add.dspy`:接收并写入全部中英文字段。
|
||||||
|
- `news_article_update.dspy`:按传入字段更新中英文内容。
|
||||||
|
- `news_article_search.dspy`:支持中英文标题和类型筛选,返回全部中英文字段;类型汇总分别使用中英文类型。
|
||||||
|
- `news_article_detail.dspy`:返回全部中英文字段。
|
||||||
|
- `front_news_search.dspy`:支持中英文标题和类型筛选,返回全部中英文字段。
|
||||||
|
- `front_news_detail.dspy`:返回全部中英文字段,并保持阅读量加一。
|
||||||
|
- `news_article_publish.dspy`:支持分别设置中英文发布时间,未传时均默认当天。
|
||||||
|
|
||||||
|
下架、删除接口不涉及语言字段,不调整。
|
||||||
|
|
||||||
|
## 兼容边界
|
||||||
|
|
||||||
|
旧字段已由数据库移除非空约束,本次接口不再维护旧字段。调用方需要改为传入和读取中英文新字段。
|
||||||
|
旧字段 `cover_img`、`publish_time` 同样不再读写。列表按两个发布时间中的较晚日期倒序排列,再按更新时间倒序排列。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 新增时缺少 `title_zh` 返回校验错误。
|
||||||
|
- 新增、更新后详情能完整返回中英文字段。
|
||||||
|
- 后台和前端列表能按中英文标题、类型筛选。
|
||||||
|
- 新增、编辑、列表和详情能分别读写中英文封面及发布时间。
|
||||||
|
- 发布时未传中英文发布时间,两个字段均写入当天日期。
|
||||||
|
- 前端详情仍仅允许读取已发布文章,并正确增加阅读量。
|
||||||
@ -18,7 +18,7 @@ export default {
|
|||||||
switchEnSuccess: 'Switched to English'
|
switchEnSuccess: 'Switched to English'
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
heroTitle: 'One Platform · Intelligent Leap · Across All Industries.',
|
heroTitle: 'Make AI Everywhere, Make AI Easy',
|
||||||
heroSubtitle: '',
|
heroSubtitle: '',
|
||||||
heroSlogan: 'Make AI Everywhere, Make AI Easy',
|
heroSlogan: 'Make AI Everywhere, Make AI Easy',
|
||||||
solutionsBtn: 'Solutions',
|
solutionsBtn: 'Solutions',
|
||||||
|
|||||||
@ -57,6 +57,14 @@
|
|||||||
<button v-if="!isNcmatchDomain" type="button" class="nav-item" @click.stop="goHomeAnchor('news')">
|
<button v-if="!isNcmatchDomain" type="button" class="nav-item" @click.stop="goHomeAnchor('news')">
|
||||||
{{ $t('topbar.news') }}
|
{{ $t('topbar.news') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ active: $route.path.includes('/homePage/about') }"
|
||||||
|
@click.stop="navigateTo('/homePage/about')"
|
||||||
|
>
|
||||||
|
{{ aboutUsText }}
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="user-actions">
|
<div class="user-actions">
|
||||||
@ -327,6 +335,9 @@ export default Vue.extend({
|
|||||||
langToggleTitle() {
|
langToggleTitle() {
|
||||||
return this.activeLocale === 'en-US' ? 'Switch to Chinese' : '切换到英文'
|
return this.activeLocale === 'en-US' ? 'Switch to Chinese' : '切换到英文'
|
||||||
},
|
},
|
||||||
|
aboutUsText() {
|
||||||
|
return this.activeLocale === 'en-US' ? 'About Us' : '关于我们'
|
||||||
|
},
|
||||||
localizedProductMenuItems() {
|
localizedProductMenuItems() {
|
||||||
const isEn = this.activeLocale === 'en-US'
|
const isEn = this.activeLocale === 'en-US'
|
||||||
const labels = {
|
const labels = {
|
||||||
@ -488,7 +499,7 @@ export default Vue.extend({
|
|||||||
if (yuanJingWindow) yuanJingWindow.close()
|
if (yuanJingWindow) yuanJingWindow.close()
|
||||||
this.$message.error((res && res.msg) || '获取元境授权参数失败')
|
this.$message.error((res && res.msg) || '获取元境授权参数失败')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginUrl = `${yuanJingBaseUrl}/#/getCookie?deerer=${encodeURIComponent(deerer)}`
|
const loginUrl = `${yuanJingBaseUrl}/#/getCookie?deerer=${encodeURIComponent(deerer)}`
|
||||||
if (yuanJingWindow) yuanJingWindow.location.href = loginUrl
|
if (yuanJingWindow) yuanJingWindow.location.href = loginUrl
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
<h1>{{ $t('home.heroTitle') }}</h1>
|
<h1>{{ $t('home.heroTitle') }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<h2 v-if="$t('home.heroSubtitle')">{{ $t('home.heroSubtitle') }}</h2>
|
<h2 v-if="$t('home.heroSubtitle')">{{ $t('home.heroSubtitle') }}</h2>
|
||||||
<p>{{ $t('home.heroSlogan') }}</p>
|
<!-- <p>{{ $t('home.heroSlogan') }}</p> -->
|
||||||
<div class="hero-actions">
|
<div class="hero-actions">
|
||||||
<button type="button" class="use-btn" @click="goSolution">{{ $t('home.solutionsBtn') }}</button>
|
<button type="button" class="use-btn" @click="goSolution">{{ $t('home.solutionsBtn') }}</button>
|
||||||
<div class="outline-btn" @click="contactSales">{{ $t('home.contactSalesBtn') }}</div>
|
<div class="outline-btn" @click="contactSales">{{ $t('home.contactSalesBtn') }}</div>
|
||||||
@ -37,6 +37,14 @@
|
|||||||
@mouseleave="startSolutionCarousel"
|
@mouseleave="startSolutionCarousel"
|
||||||
@click="setSolutionIndex(item.index)"
|
@click="setSolutionIndex(item.index)"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
v-if="item.card.scenarioPath"
|
||||||
|
type="button"
|
||||||
|
class="solution-card__scenario-link"
|
||||||
|
@click.stop="goSolutionScenario(item.card.scenarioPath)"
|
||||||
|
>
|
||||||
|
{{ $i18n && $i18n.locale === 'en-US' ? 'Scenarios→' : item.card.scenarioLabel }}
|
||||||
|
</button>
|
||||||
<div class="solution-card__front">
|
<div class="solution-card__front">
|
||||||
<div class="solution-card__icon" v-html="item.card.icon"></div>
|
<div class="solution-card__icon" v-html="item.card.icon"></div>
|
||||||
<h3>{{ item.card.title }}</h3>
|
<h3>{{ item.card.title }}</h3>
|
||||||
@ -71,7 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- 成功案例 -->
|
<!-- 成功案例 -->
|
||||||
<section id="cases-section" class="case-section">
|
<section id="cases-section" class="case-section">
|
||||||
<div class="case-inner">
|
<div class="case-inner">
|
||||||
<div class="case-head">
|
<div class="case-head">
|
||||||
@ -101,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="case-cta" :style="{ backgroundImage: `url(${caseCtaBg})` }">
|
<div class="case-cta" :style="{ backgroundImage: `url(${caseCtaBg})` }">
|
||||||
@ -129,7 +137,7 @@
|
|||||||
<div class="news-list">
|
<div class="news-list">
|
||||||
<article
|
<article
|
||||||
v-for="(item, index) in localizedNewsList"
|
v-for="(item, index) in localizedNewsList"
|
||||||
:key="item.title"
|
:key="item.id"
|
||||||
class="news-card"
|
class="news-card"
|
||||||
:class="{ 'news-card-active': index === activeNewsIndex }"
|
:class="{ 'news-card-active': index === activeNewsIndex }"
|
||||||
@mouseenter="activeNewsIndex = index"
|
@mouseenter="activeNewsIndex = index"
|
||||||
@ -213,6 +221,8 @@ export default {
|
|||||||
],
|
],
|
||||||
bg: 'linear-gradient(145deg, rgba(208, 236, 249, 0.72) 0%, rgba(168, 216, 244, 0.64) 44%, rgba(124, 196, 238, 0.58) 100%)',
|
bg: 'linear-gradient(145deg, rgba(208, 236, 249, 0.72) 0%, rgba(168, 216, 244, 0.64) 44%, rgba(124, 196, 238, 0.58) 100%)',
|
||||||
shadow: '0 18px 54px rgba(80, 150, 220, 0.2), 0 4px 16px rgba(80, 150, 220, 0.08)',
|
shadow: '0 18px 54px rgba(80, 150, 220, 0.2), 0 4px 16px rgba(80, 150, 220, 0.08)',
|
||||||
|
scenarioLabel: '投策智能体→',
|
||||||
|
scenarioPath: '/homePage/agentStore/decisionCase',
|
||||||
icon: '<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="24" cy="18" r="6"/><circle cx="10" cy="34" r="5"/><circle cx="38" cy="34" r="5"/><line x1="20" y1="23" x2="13" y2="30"/><line x1="28" y1="23" x2="35" y2="30"/><circle cx="36" cy="14" r="3.5"/><circle cx="12" cy="14" r="3.5"/></svg>'
|
icon: '<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="24" cy="18" r="6"/><circle cx="10" cy="34" r="5"/><circle cx="38" cy="34" r="5"/><line x1="20" y1="23" x2="13" y2="30"/><line x1="28" y1="23" x2="35" y2="30"/><circle cx="36" cy="14" r="3.5"/><circle cx="12" cy="14" r="3.5"/></svg>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -303,6 +313,8 @@ export default {
|
|||||||
],
|
],
|
||||||
bg: 'linear-gradient(145deg, rgba(200, 214, 242, 0.72) 0%, rgba(158, 180, 230, 0.64) 44%, rgba(116, 146, 216, 0.58) 100%)',
|
bg: 'linear-gradient(145deg, rgba(200, 214, 242, 0.72) 0%, rgba(158, 180, 230, 0.64) 44%, rgba(116, 146, 216, 0.58) 100%)',
|
||||||
shadow: '0 18px 54px rgba(60, 80, 160, 0.18), 0 4px 16px rgba(60, 80, 160, 0.08)',
|
shadow: '0 18px 54px rgba(60, 80, 160, 0.18), 0 4px 16px rgba(60, 80, 160, 0.08)',
|
||||||
|
scenarioLabel: '合同智能审查→',
|
||||||
|
scenarioPath: '/homePage/agentStore/contractCase',
|
||||||
icon: '<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6h16l10 10v26a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4V10a4 4 0 0 1 4-4z"/><polyline points="28,6 28,16 38,16"/><line x1="14" y1="24" x2="34" y2="24"/><line x1="14" y1="31" x2="30" y2="31"/></svg>'
|
icon: '<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6h16l10 10v26a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4V10a4 4 0 0 1 4-4z"/><polyline points="28,6 28,16 38,16"/><line x1="14" y1="24" x2="34" y2="24"/><line x1="14" y1="31" x2="30" y2="31"/></svg>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -378,7 +390,29 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
localizedNewsList() {
|
localizedNewsList() {
|
||||||
return this.newsList
|
const isEnglish = this.$i18n && this.$i18n.locale === 'en-US'
|
||||||
|
return this.newsList.map((item) => {
|
||||||
|
const tag = isEnglish
|
||||||
|
? (item.tagEn || item.tagZh || this.$t('home.news.tag'))
|
||||||
|
: (item.tagZh || item.tagEn || this.$t('home.news.tag'))
|
||||||
|
const title = isEnglish
|
||||||
|
? (item.titleEn || item.titleZh || '')
|
||||||
|
: (item.titleZh || item.titleEn || '')
|
||||||
|
const desc = isEnglish
|
||||||
|
? (item.descEn || item.descZh || '')
|
||||||
|
: (item.descZh || item.descEn || '')
|
||||||
|
const date = isEnglish
|
||||||
|
? (item.dateEn || item.dateZh || '')
|
||||||
|
: (item.dateZh || item.dateEn || '')
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
tag,
|
||||||
|
title,
|
||||||
|
desc,
|
||||||
|
date
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
visibleSolutionCards() {
|
visibleSolutionCards() {
|
||||||
const slots = [-3, -2, -1, 0, 1, 2, 3]
|
const slots = [-3, -2, -1, 0, 1, 2, 3]
|
||||||
@ -430,6 +464,9 @@ export default {
|
|||||||
block: 'start'
|
block: 'start'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
goSolutionScenario(path) {
|
||||||
|
this.navigateTo(path)
|
||||||
|
},
|
||||||
contactSales() {
|
contactSales() {
|
||||||
this.$store.commit('setShowTalk', true)
|
this.$store.commit('setShowTalk', true)
|
||||||
},
|
},
|
||||||
@ -455,11 +492,15 @@ export default {
|
|||||||
normalizeHomeNewsItem(row, index) {
|
normalizeHomeNewsItem(row, index) {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
tag: row.article_type || this.$t('home.news.tag'),
|
|
||||||
tagClass: this.getNewsTagClass(index),
|
tagClass: this.getNewsTagClass(index),
|
||||||
date: row.publish_time || row.publishTime || '',
|
tagZh: row.article_type_zh || row.article_type || '',
|
||||||
title: row.title || '',
|
tagEn: row.article_type_en || '',
|
||||||
desc: row.summary || row.desc || row.description || ''
|
dateZh: row.publish_time_zh || row.publish_time || row.publishTime || '',
|
||||||
|
dateEn: row.publish_time_en || '',
|
||||||
|
titleZh: row.title_zh || row.title || '',
|
||||||
|
titleEn: row.title_en || '',
|
||||||
|
descZh: row.summary_zh || row.summary || row.desc || row.description || '',
|
||||||
|
descEn: row.summary_en || ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getResponseList(res) {
|
getResponseList(res) {
|
||||||
@ -481,8 +522,7 @@ export default {
|
|||||||
this.newsList = []
|
this.newsList = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
nextSolution() {
|
nextSolution() {
|
||||||
this.switchSolution(1)
|
this.switchSolution(1)
|
||||||
},
|
},
|
||||||
@ -690,7 +730,6 @@ body.dark-theme .bg-orb {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
@ -824,6 +863,39 @@ body.dark-theme .bg-orb {
|
|||||||
transition: all 0.28s ease;
|
transition: all 0.28s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.solution-card__scenario-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
z-index: 3;
|
||||||
|
padding: 5px 9px;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: rgba(255, 255, 255, 0.58);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||||
|
border-radius: 999px;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solution-card:hover .solution-card__scenario-link {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solution-card__scenario-link:hover {
|
||||||
|
color: #1e40af;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.solution-card__icon {
|
.solution-card__icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -865,15 +937,12 @@ body.dark-theme .bg-orb {
|
|||||||
transition: all 0.25s ease;
|
transition: all 0.25s ease;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 4px;
|
display: none;
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(37, 99, 235, 0.18);
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
@ -1546,8 +1615,6 @@ body.dark-theme .bg-orb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@keyframes heroFadeIn {
|
@keyframes heroFadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="article-editor" :class="{ 'is-editor-fullscreen': editorFullscreen }">
|
<div class="article-editor" :class="{ 'is-editor-fullscreen': editorFullscreen }">
|
||||||
<div class="article-editor__left">
|
<div class="article-editor__left" :class="{ 'has-language-form': showEnglishForm }">
|
||||||
<div class="article-editor__form">
|
<div class="article-editor__form">
|
||||||
<el-form :model="form" label-width="76px" size="small">
|
<el-form :model="form" label-width="76px" size="small">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@ -71,11 +71,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<div class="language-trigger">
|
||||||
|
<div class="language-trigger__title">
|
||||||
|
<span class="language-trigger__badge">EN</span>
|
||||||
|
<div>
|
||||||
|
<strong>多语言输入</strong>
|
||||||
|
<small>添加英文版文章内容</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="language-trigger__button"
|
||||||
|
:aria-expanded="showEnglishForm.toString()"
|
||||||
|
@click="showEnglishForm = !showEnglishForm"
|
||||||
|
>
|
||||||
|
{{ showEnglishForm ? '收起英文版' : '添加英文版' }}
|
||||||
|
<i :class="showEnglishForm ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div v-if="dialogVisible" class="wang-editor-wrap">
|
<div v-if="dialogVisible" class="wang-editor-wrap">
|
||||||
|
|
||||||
<Toolbar
|
<Toolbar
|
||||||
class="wang-toolbar"
|
class="wang-toolbar"
|
||||||
:editor="editor"
|
:editor="editor"
|
||||||
@ -90,6 +106,92 @@
|
|||||||
@onCreated="handleEditorCreated"
|
@onCreated="handleEditorCreated"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section v-if="showEnglishForm" class="article-editor__language-section">
|
||||||
|
<div class="language-section-head">
|
||||||
|
<div>
|
||||||
|
<span class="language-section-head__eyebrow">ENGLISH VERSION</span>
|
||||||
|
<h3>英文版文章</h3>
|
||||||
|
<p>英文内容会与中文内容一并保存或发布。</p>
|
||||||
|
</div>
|
||||||
|
<span class="language-section-head__tag">English</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="language-form-grid">
|
||||||
|
<div class="language-field language-field--wide">
|
||||||
|
<label>Article Title</label>
|
||||||
|
<el-input v-model.trim="english.title" placeholder="Enter article title" />
|
||||||
|
</div>
|
||||||
|
<div class="language-field language-field--wide">
|
||||||
|
<label>English Summary</label>
|
||||||
|
<el-input
|
||||||
|
v-model.trim="english.summary"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
maxlength="120"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="Enter article summary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="language-field">
|
||||||
|
<label>Category</label>
|
||||||
|
<el-select v-model="english.category" placeholder="Select category">
|
||||||
|
<el-option label="Corporate News" value="Corporate News" />
|
||||||
|
<el-option label="Product News" value="Product News" />
|
||||||
|
<el-option label="Industry Insights" value="Industry Insights" />
|
||||||
|
<el-option label="Event News" value="Event News" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="language-field">
|
||||||
|
<label>Date</label>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="english.publishTime"
|
||||||
|
type="date"
|
||||||
|
value-format="yyyy-MM-dd"
|
||||||
|
placeholder="Select date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="language-field language-field--wide">
|
||||||
|
<label>Cover</label>
|
||||||
|
<div class="language-cover">
|
||||||
|
<button type="button" class="language-cover__upload" @click="$refs.englishCoverInput.click()">
|
||||||
|
<i class="el-icon-upload2" />
|
||||||
|
Upload cover
|
||||||
|
</button>
|
||||||
|
<el-input
|
||||||
|
v-model.trim="english.coverUrl"
|
||||||
|
class="language-cover__input"
|
||||||
|
placeholder="Upload an image or paste its URL"
|
||||||
|
/>
|
||||||
|
<span v-if="english.coverName" class="language-cover__name">{{ english.coverName }}</span>
|
||||||
|
<input ref="englishCoverInput" type="file" accept="image/*" class="hidden-input" @change="handleEnglishCoverUpload">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="language-richtext">
|
||||||
|
<div class="language-richtext__head">
|
||||||
|
<span>English Content</span>
|
||||||
|
<small>Rich text editor</small>
|
||||||
|
</div>
|
||||||
|
<div v-if="dialogVisible" class="wang-editor-wrap wang-editor-wrap--english">
|
||||||
|
<Toolbar
|
||||||
|
class="wang-toolbar"
|
||||||
|
:editor="englishEditor"
|
||||||
|
:default-config="toolbarConfig"
|
||||||
|
:mode="editorMode"
|
||||||
|
/>
|
||||||
|
<Editor
|
||||||
|
v-model="english.content"
|
||||||
|
class="wang-editor"
|
||||||
|
:default-config="englishEditorConfig"
|
||||||
|
:mode="editorMode"
|
||||||
|
@onCreated="handleEnglishEditorCreated"
|
||||||
|
@onDestroyed="handleEnglishEditorDestroyed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -157,6 +259,7 @@ export default {
|
|||||||
dirty: false,
|
dirty: false,
|
||||||
// 保存 wangEditor 实例,组件销毁时需要手动释放。
|
// 保存 wangEditor 实例,组件销毁时需要手动释放。
|
||||||
editor: null,
|
editor: null,
|
||||||
|
englishEditor: null,
|
||||||
// wangEditor 模式配置。
|
// wangEditor 模式配置。
|
||||||
editorMode: 'default',
|
editorMode: 'default',
|
||||||
// wangEditor 工具栏配置。
|
// wangEditor 工具栏配置。
|
||||||
@ -166,7 +269,7 @@ export default {
|
|||||||
placeholder: '请输入文章内容...',
|
placeholder: '请输入文章内容...',
|
||||||
MENU_CONF: {
|
MENU_CONF: {
|
||||||
uploadImage: {
|
uploadImage: {
|
||||||
customUpload: async (file, insertFn) => {
|
customUpload: async(file, insertFn) => {
|
||||||
try {
|
try {
|
||||||
// 正文图片上传到文件接口,正文中保存可回显的图片地址。
|
// 正文图片上传到文件接口,正文中保存可回显的图片地址。
|
||||||
const url = await this.uploadNewsImage(file)
|
const url = await this.uploadNewsImage(file)
|
||||||
@ -178,13 +281,33 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
englishEditorConfig: {
|
||||||
|
placeholder: 'Start writing the English version of your article...',
|
||||||
|
MENU_CONF: {
|
||||||
|
uploadImage: {
|
||||||
|
customUpload: async(file, insertFn) => {
|
||||||
|
try {
|
||||||
|
const url = await this.uploadNewsImage(file)
|
||||||
|
insertFn(normalizeImageUrl(url), file.name, normalizeImageUrl(url))
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error('英文正文图片上传失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
// 上传封面的本地预览地址,只用于页面显示,不提交给接口。
|
// 上传封面的本地预览地址,只用于页面显示,不提交给接口。
|
||||||
coverPreview: '',
|
coverPreview: '',
|
||||||
imageUploading: false,
|
imageUploading: false,
|
||||||
|
englishImageUploading: false,
|
||||||
editorFullscreen: false,
|
editorFullscreen: false,
|
||||||
editorFullscreenObserver: null,
|
editorFullscreenObserver: null,
|
||||||
// 右侧预览区域宽度。
|
// 右侧预览区域宽度。
|
||||||
previewWidth: 400,
|
previewWidth: 400,
|
||||||
|
// 控制英文版区域展开状态。
|
||||||
|
showEnglishForm: false,
|
||||||
|
// 英文版内容,与中文字段一起提交到文章保存接口。
|
||||||
|
english: this.createEmptyEnglishForm(),
|
||||||
// 标记预览区域是否正在拖拽调整宽度。
|
// 标记预览区域是否正在拖拽调整宽度。
|
||||||
resizing: false,
|
resizing: false,
|
||||||
// 记录拖拽开始时的鼠标位置。
|
// 记录拖拽开始时的鼠标位置。
|
||||||
@ -230,6 +353,12 @@ export default {
|
|||||||
// 任意表单字段变化后标记为未保存。
|
// 任意表单字段变化后标记为未保存。
|
||||||
this.dirty = true
|
this.dirty = true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
english: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.dirty = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@ -238,12 +367,16 @@ export default {
|
|||||||
this.editor.destroy()
|
this.editor.destroy()
|
||||||
this.editor = null
|
this.editor = null
|
||||||
}
|
}
|
||||||
|
if (this.englishEditor) {
|
||||||
|
this.englishEditor.destroy()
|
||||||
|
this.englishEditor = null
|
||||||
|
}
|
||||||
// 清理拖拽监听事件。
|
// 清理拖拽监听事件。
|
||||||
this.removeResizeListeners()
|
this.removeResizeListeners()
|
||||||
this.stopObserveEditorFullscreen()
|
this.stopObserveEditorFullscreen()
|
||||||
this.editorFullscreen = false
|
this.editorFullscreen = false
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createEmptyForm() {
|
createEmptyForm() {
|
||||||
// 创建新增文章时使用的默认表单结构。
|
// 创建新增文章时使用的默认表单结构。
|
||||||
return {
|
return {
|
||||||
@ -259,17 +392,58 @@ export default {
|
|||||||
coverName: ''
|
coverName: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
createEmptyEnglishForm() {
|
||||||
|
return {
|
||||||
|
title: '',
|
||||||
|
summary: '',
|
||||||
|
category: '',
|
||||||
|
publishTime: '',
|
||||||
|
coverUrl: '',
|
||||||
|
coverName: '',
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
handleOpen() {
|
handleOpen() {
|
||||||
// 弹窗打开时合并默认值和待编辑文章数据。
|
// 弹窗打开时合并默认值和待编辑文章数据。
|
||||||
|
const article = this.article || {}
|
||||||
|
const english = article.english || {}
|
||||||
this.form = {
|
this.form = {
|
||||||
...this.createEmptyForm(),
|
...this.createEmptyForm(),
|
||||||
...(this.article || {})
|
...article,
|
||||||
|
title: article.title || article.title_zh || '',
|
||||||
|
summary: article.summary || article.summary_zh || '',
|
||||||
|
type: article.type || article.article_type_zh || '企业动态',
|
||||||
|
publishTime: article.publishTime || article.publish_time_zh || article.publish_time || '',
|
||||||
|
content: article.content || article.content_zh || '',
|
||||||
|
coverUrl: article.coverUrl || article.cover_img_zh || article.cover_img || '',
|
||||||
|
coverName: article.coverName || article.cover_name_zh || article.cover_name || ''
|
||||||
}
|
}
|
||||||
// 没有发布时间时默认使用当天日期。
|
// 没有发布时间时默认使用当天日期。
|
||||||
if (!this.form.publishTime) this.form.publishTime = this.today
|
if (!this.form.publishTime) this.form.publishTime = this.today
|
||||||
// 初始化封面预览。
|
// 初始化封面预览。
|
||||||
this.coverPreview = this.form.coverUrl || ''
|
this.coverPreview = this.form.coverUrl || ''
|
||||||
this.editorFullscreen = false
|
this.editorFullscreen = false
|
||||||
|
this.imageUploading = false
|
||||||
|
this.englishImageUploading = false
|
||||||
|
this.english = {
|
||||||
|
...this.createEmptyEnglishForm(),
|
||||||
|
...english,
|
||||||
|
title: english.title || article.title_en || '',
|
||||||
|
summary: english.summary || article.summary_en || '',
|
||||||
|
category: english.category || article.article_type_en || '',
|
||||||
|
publishTime: english.publishTime || article.publish_time_en || '',
|
||||||
|
content: english.content || article.content_en || '',
|
||||||
|
coverUrl: english.coverUrl || article.cover_img_en || '',
|
||||||
|
coverName: english.coverName || article.cover_name_en || ''
|
||||||
|
}
|
||||||
|
this.showEnglishForm = Boolean(
|
||||||
|
this.english.title ||
|
||||||
|
this.english.summary ||
|
||||||
|
this.english.category ||
|
||||||
|
this.english.publishTime ||
|
||||||
|
this.english.coverUrl ||
|
||||||
|
this.english.content
|
||||||
|
)
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
// 表单初始化完成后重置未保存状态。
|
// 表单初始化完成后重置未保存状态。
|
||||||
this.dirty = false
|
this.dirty = false
|
||||||
@ -280,6 +454,12 @@ export default {
|
|||||||
// 保存编辑器实例,后续销毁时使用。
|
// 保存编辑器实例,后续销毁时使用。
|
||||||
this.editor = Object.seal(editor)
|
this.editor = Object.seal(editor)
|
||||||
},
|
},
|
||||||
|
handleEnglishEditorCreated(editor) {
|
||||||
|
this.englishEditor = Object.seal(editor)
|
||||||
|
},
|
||||||
|
handleEnglishEditorDestroyed() {
|
||||||
|
this.englishEditor = null
|
||||||
|
},
|
||||||
handleDialogClose() {
|
handleDialogClose() {
|
||||||
this.stopObserveEditorFullscreen()
|
this.stopObserveEditorFullscreen()
|
||||||
this.editorFullscreen = false
|
this.editorFullscreen = false
|
||||||
@ -398,6 +578,23 @@ export default {
|
|||||||
event.target.value = ''
|
event.target.value = ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async handleEnglishCoverUpload(event) {
|
||||||
|
const file = event.target.files && event.target.files[0]
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
this.english.coverName = file.name
|
||||||
|
this.englishImageUploading = true
|
||||||
|
try {
|
||||||
|
this.english.coverUrl = await this.uploadNewsImage(file)
|
||||||
|
this.$message.success('英文封面图片上传成功')
|
||||||
|
} catch (error) {
|
||||||
|
this.english.coverName = ''
|
||||||
|
this.$message.error('英文封面图片上传失败')
|
||||||
|
} finally {
|
||||||
|
this.englishImageUploading = false
|
||||||
|
event.target.value = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
handleCoverUrlInput() {
|
handleCoverUrlInput() {
|
||||||
// 用户手动输入图片路径时,清空本地上传预览状态。
|
// 用户手动输入图片路径时,清空本地上传预览状态。
|
||||||
this.coverPreview = ''
|
this.coverPreview = ''
|
||||||
@ -469,13 +666,16 @@ export default {
|
|||||||
this.$message.warning('请输入文章正文')
|
this.$message.warning('请输入文章正文')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.imageUploading) {
|
if (this.imageUploading || this.englishImageUploading) {
|
||||||
this.$message.warning('图片上传中,请稍后保存')
|
this.$message.warning('图片上传中,请稍后保存')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 第六步:组装保存参数,status 由按钮决定是草稿还是发布。
|
// 第六步:组装保存参数,status 由按钮决定是草稿还是发布。
|
||||||
const payload = {
|
const payload = {
|
||||||
...this.form,
|
...this.form,
|
||||||
|
english: {
|
||||||
|
...this.english
|
||||||
|
},
|
||||||
publish_time: this.form.publishTime,
|
publish_time: this.form.publishTime,
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
@ -572,6 +772,21 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-editor__left.has-language-form {
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-color: #cbd5e1 transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor__left.has-language-form .article-editor__form {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-editor__left.has-language-form .wang-editor-wrap {
|
||||||
|
flex: 0 0 420px;
|
||||||
|
min-height: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
.resize-handle {
|
.resize-handle {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 0 0 10px;
|
flex: 0 0 10px;
|
||||||
@ -614,6 +829,70 @@ export default {
|
|||||||
border-bottom: 1px solid #edf0f5;
|
border-bottom: 1px solid #edf0f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.language-trigger {
|
||||||
|
min-height: 48px;
|
||||||
|
margin: 2px 0 12px 88px;
|
||||||
|
padding: 9px 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 14px;
|
||||||
|
background: linear-gradient(90deg, #f8fbff 0%, #f7f5ff 100%);
|
||||||
|
border: 1px dashed #bfdbfe;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-trigger__title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-trigger__badge {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #4f46e5;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
background: #ede9fe;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-trigger__title strong,
|
||||||
|
.language-trigger__title small {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-trigger__title strong {
|
||||||
|
color: #334155;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-trigger__title small {
|
||||||
|
margin-top: 2px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-trigger__button {
|
||||||
|
padding: 5px 8px;
|
||||||
|
color: #4f46e5;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-trigger__button i {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.article-editor__inline {
|
.article-editor__inline {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
@ -671,6 +950,219 @@ export default {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-editor__language-section {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 22px;
|
||||||
|
padding: 24px 18px 40px 0;
|
||||||
|
border-top: 1px solid #dbeafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-section-head {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
background: linear-gradient(135deg, #f5f9ff 0%, #f7f5ff 100%);
|
||||||
|
border: 1px solid #dbeafe;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-section-head__eyebrow {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #6366f1;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-section-head h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #1e293b;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-section-head p {
|
||||||
|
margin: 5px 0 0;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-section-head__tag {
|
||||||
|
padding: 4px 9px;
|
||||||
|
color: #4f46e5;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #ede9fe;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-field--wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-field label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-field .el-select,
|
||||||
|
.language-field .el-date-editor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-cover {
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-cover__upload {
|
||||||
|
padding: 5px 9px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #409eff;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-cover__upload i {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-cover__input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-cover__name {
|
||||||
|
max-width: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #16a34a;
|
||||||
|
font-size: 11px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext .wang-editor-wrap {
|
||||||
|
height: 420px;
|
||||||
|
min-height: 420px;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext .wang-toolbar {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__head {
|
||||||
|
padding: 12px 14px 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #edf0f5;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__head span {
|
||||||
|
color: #334155;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__head small {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__toolbar {
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
color: #64748b;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-top: 1px solid #edf2f7;
|
||||||
|
border-bottom: 1px solid #edf2f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__toolbar > span {
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 5px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__toolbar > span:not(.language-richtext__divider):hover {
|
||||||
|
color: #2563eb;
|
||||||
|
background: #eff6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__italic {
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__toolbar .language-richtext__divider {
|
||||||
|
min-width: 1px;
|
||||||
|
width: 1px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 4px;
|
||||||
|
background: #dbe2ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__canvas {
|
||||||
|
min-height: 230px;
|
||||||
|
padding: 16px;
|
||||||
|
color: #c0c7d1;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.8;
|
||||||
|
cursor: text;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-richtext__canvas:empty::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
color: #c0c7d1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.article-editor__preview {
|
.article-editor__preview {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -53,6 +53,16 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
isEnglish() {
|
||||||
|
return this.$i18n && this.$i18n.locale === 'en-US'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isEnglish() {
|
||||||
|
this.getNewsDetail()
|
||||||
|
}
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getNewsDetail()
|
this.getNewsDetail()
|
||||||
},
|
},
|
||||||
@ -84,6 +94,10 @@ export default {
|
|||||||
if (/^(https?:|blob:|data:|\/\/)/.test(url)) return url
|
if (/^(https?:|blob:|data:|\/\/)/.test(url)) return url
|
||||||
return `${window.location.origin}/idfile?path=${url}`
|
return `${window.location.origin}/idfile?path=${url}`
|
||||||
},
|
},
|
||||||
|
getLocalizedField(row, zhField, enField, legacyField) {
|
||||||
|
if (this.isEnglish && row[enField]) return row[enField]
|
||||||
|
return row[zhField] || row[legacyField] || row[enField] || ''
|
||||||
|
},
|
||||||
getResponseDetail(res) {
|
getResponseDetail(res) {
|
||||||
if (res.data && res.data.data && !Array.isArray(res.data.data)) return res.data.data
|
if (res.data && res.data.data && !Array.isArray(res.data.data)) return res.data.data
|
||||||
if (res.data && !Array.isArray(res.data)) return res.data
|
if (res.data && !Array.isArray(res.data)) return res.data
|
||||||
@ -94,12 +108,12 @@ export default {
|
|||||||
normalizeArticle(row) {
|
normalizeArticle(row) {
|
||||||
return {
|
return {
|
||||||
id: row.id || this.$route.params.id,
|
id: row.id || this.$route.params.id,
|
||||||
title: row.title || '未命名文章',
|
title: this.getLocalizedField(row, 'title_zh', 'title_en', 'title') || '未命名文章',
|
||||||
summary: row.summary || '',
|
summary: this.getLocalizedField(row, 'summary_zh', 'summary_en', 'summary'),
|
||||||
articleType: row.article_type || row.type || '企业动态',
|
articleType: this.getLocalizedField(row, 'article_type_zh', 'article_type_en', 'article_type') || row.type || '企业动态',
|
||||||
publishTime: row.publish_time || row.publishTime || '',
|
publishTime: this.getLocalizedField(row, 'publish_time_zh', 'publish_time_en', 'publish_time') || row.publishTime || '',
|
||||||
coverImg: this.normalizeImageUrl(row.cover_img || row.coverImg || ''),
|
coverImg: this.normalizeImageUrl(this.getLocalizedField(row, 'cover_img_zh', 'cover_img_en', 'cover_img') || row.coverImg || ''),
|
||||||
content: row.content || ''
|
content: this.getLocalizedField(row, 'content_zh', 'content_en', 'content')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getNewsDetail() {
|
async getNewsDetail() {
|
||||||
|
|||||||
@ -291,16 +291,25 @@ export default {
|
|||||||
// 将接口字段统一转换成页面表格和编辑弹窗使用的字段。
|
// 将接口字段统一转换成页面表格和编辑弹窗使用的字段。
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
title: row.title || '',
|
title: row.title_zh || row.title || '',
|
||||||
summary: row.summary || '',
|
summary: row.summary_zh || row.summary || '',
|
||||||
type: row.article_type || row.type || '企业动态',
|
type: row.article_type_zh || row.article_type || row.type || '企业动态',
|
||||||
status: this.getViewStatus(row.status),
|
status: this.getViewStatus(row.status),
|
||||||
publishTime: row.publish_time || row.publishTime || '',
|
publishTime: row.publish_time_zh || row.publish_time || row.publishTime || '',
|
||||||
updateTime: row.update_time || row.updateTime || '',
|
updateTime: row.update_time || row.updateTime || '',
|
||||||
views: row.read_count || row.views || 0,
|
views: row.read_count || row.views || 0,
|
||||||
content: row.content || '',
|
content: row.content_zh || row.content || '',
|
||||||
coverUrl: row.cover_img || row.coverUrl || '',
|
coverUrl: row.cover_img_zh || row.cover_img || row.coverUrl || '',
|
||||||
coverName: row.cover_name || row.coverName || ''
|
coverName: row.cover_name_zh || row.cover_name || row.coverName || '',
|
||||||
|
english: {
|
||||||
|
title: row.title_en || '',
|
||||||
|
summary: row.summary_en || '',
|
||||||
|
category: row.article_type_en || '',
|
||||||
|
publishTime: row.publish_time_en || '',
|
||||||
|
content: row.content_en || '',
|
||||||
|
coverUrl: row.cover_img_en || '',
|
||||||
|
coverName: row.cover_name_en || ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getResponseList(res) {
|
getResponseList(res) {
|
||||||
@ -325,17 +334,24 @@ export default {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
buildArticlePayload(article) {
|
buildArticlePayload(article) {
|
||||||
// 将编辑弹窗字段转换成新增/编辑接口需要的字段。
|
// 将中文、英文编辑字段转换成新增/编辑接口需要的双语字段。
|
||||||
|
const english = article.english || {}
|
||||||
return {
|
return {
|
||||||
...(article.id ? { id: article.id } : {}),
|
...(article.id ? { id: article.id } : {}),
|
||||||
url_link: window.location.href,
|
url_link: window.location.href,
|
||||||
title: article.title,
|
title_zh: article.title,
|
||||||
article_type: article.type,
|
title_en: english.title || '',
|
||||||
summary: article.summary,
|
article_type_zh: article.type,
|
||||||
|
article_type_en: english.category || '',
|
||||||
|
summary_zh: article.summary,
|
||||||
|
summary_en: english.summary || '',
|
||||||
update_time: article.update_time || article.updateTime,
|
update_time: article.update_time || article.updateTime,
|
||||||
publish_time: article.publish_time || article.publishTime,
|
publish_time_zh: article.publish_time_zh || article.publishTime,
|
||||||
cover_img: article.coverUrl,
|
publish_time_en: english.publishTime || '',
|
||||||
content: article.content,
|
cover_img_zh: article.coverUrl,
|
||||||
|
cover_img_en: english.coverUrl || '',
|
||||||
|
content_zh: article.content,
|
||||||
|
content_en: english.content || '',
|
||||||
status: this.getApiStatus(article.status)
|
status: this.getApiStatus(article.status)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -726,4 +742,4 @@ export default {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -11,10 +11,9 @@
|
|||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
<span class="hero-badge">COMPANY NEWS</span>
|
<span class="hero-badge">COMPANY NEWS</span>
|
||||||
<div class="hero-title-row">
|
<div class="hero-title-row">
|
||||||
<h1>企业动态</h1>
|
<h1>{{ isEnglish ? 'Company News' : '企业动态' }}</h1>
|
||||||
<button type="button" class="about-link" @click="goAbout">关于我们 →</button>
|
|
||||||
</div>
|
</div>
|
||||||
<p>了解开元云最新动态,把握AI行业前沿资讯,与我们一起见证智能跃迁</p>
|
<p>{{ isEnglish ? 'Stay up to date with Open Computing AI, explore the latest AI industry insights, and witness the intelligent leap with us.' : '了解开元云最新动态,把握AI行业前沿资讯,与我们一起见证智能跃迁' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -101,7 +100,7 @@
|
|||||||
<i class="el-icon-arrow-right"></i>
|
<i class="el-icon-arrow-right"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -109,13 +108,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import { reqNewsList } from '@/api/newsapi/newsapi'
|
import { reqNewsList } from '@/api/newsapi/newsapi'
|
||||||
export default {
|
export default {
|
||||||
name: 'NewsView',
|
name: 'NewsView',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fallbackNewsImage: require('@/assets/image/news.jpg'),
|
fallbackNewsImage: require('@/assets/image/news.jpg'),
|
||||||
activeCategory: 'all',
|
activeCategory: 'all',
|
||||||
page: 1,
|
page: 1,
|
||||||
|
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
filterTabs: [
|
filterTabs: [
|
||||||
@ -129,6 +127,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isEnglish() {
|
||||||
|
return this.$i18n && this.$i18n.locale === 'en-US'
|
||||||
|
},
|
||||||
featuredNews() {
|
featuredNews() {
|
||||||
return this.newsList[0]
|
return this.newsList[0]
|
||||||
},
|
},
|
||||||
@ -142,6 +143,11 @@ export default {
|
|||||||
return Array.from({ length: Math.min(this.totalPages, 3) }, (_, index) => index + 1)
|
return Array.from({ length: Math.min(this.totalPages, 3) }, (_, index) => index + 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
isEnglish() {
|
||||||
|
this.getNewsList()
|
||||||
|
}
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getNewsList()
|
this.getNewsList()
|
||||||
},
|
},
|
||||||
@ -187,26 +193,34 @@ export default {
|
|||||||
企业动态: 'rgba(16,185,129,0.85)',
|
企业动态: 'rgba(16,185,129,0.85)',
|
||||||
产品动态: 'rgba(99,102,241,0.85)',
|
产品动态: 'rgba(99,102,241,0.85)',
|
||||||
行业洞察: 'rgba(13,148,136,0.85)',
|
行业洞察: 'rgba(13,148,136,0.85)',
|
||||||
活动资讯: 'rgba(245,158,11,0.85)'
|
活动资讯: 'rgba(245,158,11,0.85)',
|
||||||
|
'Corporate News': 'rgba(16,185,129,0.85)',
|
||||||
|
'Product News': 'rgba(99,102,241,0.85)',
|
||||||
|
'Industry Insights': 'rgba(13,148,136,0.85)',
|
||||||
|
'Event News': 'rgba(245,158,11,0.85)'
|
||||||
}
|
}
|
||||||
return map[type] || 'rgba(99,102,241,0.85)'
|
return map[type] || 'rgba(99,102,241,0.85)'
|
||||||
},
|
},
|
||||||
|
getLocalizedField(row, zhField, enField, legacyField) {
|
||||||
|
if (this.isEnglish && row[enField]) return row[enField]
|
||||||
|
return row[zhField] || row[legacyField] || row[enField] || ''
|
||||||
|
},
|
||||||
normalizeImageUrl(url) {
|
normalizeImageUrl(url) {
|
||||||
if (!url) return ''
|
if (!url) return ''
|
||||||
if (/^(https?:|blob:|data:|\/\/)/.test(url)) return url
|
if (/^(https?:|blob:|data:|\/\/)/.test(url)) return url
|
||||||
return `${window.location.origin}/idfile?path=${url}`
|
return `${window.location.origin}/idfile?path=${url}`
|
||||||
},
|
},
|
||||||
normalizeNewsItem(row) {
|
normalizeNewsItem(row) {
|
||||||
const type = row.article_type || '企业动态'
|
const type = this.getLocalizedField(row, 'article_type_zh', 'article_type_en', 'article_type') || '企业动态'
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
category: type,
|
category: type,
|
||||||
tag: type,
|
tag: type,
|
||||||
tagColor: this.getTagColor(type),
|
tagColor: this.getTagColor(type),
|
||||||
date: row.publish_time || '',
|
date: this.getLocalizedField(row, 'publish_time_zh', 'publish_time_en', 'publish_time'),
|
||||||
title: row.title || '未命名文章',
|
title: this.getLocalizedField(row, 'title_zh', 'title_en', 'title') || '未命名文章',
|
||||||
desc: row.summary || '',
|
desc: this.getLocalizedField(row, 'summary_zh', 'summary_en', 'summary'),
|
||||||
img: this.normalizeImageUrl(row.cover_img || ''),
|
img: this.normalizeImageUrl(this.getLocalizedField(row, 'cover_img_zh', 'cover_img_en', 'cover_img')),
|
||||||
views: row.read_count || 0
|
views: row.read_count || 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,14 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog custom-class="forgot-password-dialog" :visible="visible" width="540px" :close-on-click-modal="false"
|
||||||
custom-class="forgot-password-dialog"
|
destroy-on-close append-to-body @open="handleOpen" @close="handleClose">
|
||||||
:visible="visible"
|
|
||||||
width="540px"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
destroy-on-close
|
|
||||||
append-to-body
|
|
||||||
@open="handleOpen"
|
|
||||||
@close="handleClose"
|
|
||||||
>
|
|
||||||
<div slot="title" class="forgot-dialog-title">
|
<div slot="title" class="forgot-dialog-title">
|
||||||
<div class="title-icon">
|
<div class="title-icon">
|
||||||
<i class="el-icon-lock"></i>
|
<i class="el-icon-lock"></i>
|
||||||
@ -25,19 +17,16 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="新密码" prop="password">
|
<el-form-item label="新密码" prop="password">
|
||||||
<el-input v-model="form.password" clearable show-password autocomplete="new-password" placeholder="请输入新密码"></el-input>
|
<el-input v-model="form.password" clearable show-password autocomplete="new-password"
|
||||||
|
placeholder="请输入新密码"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="验证码" prop="vcode">
|
<el-form-item label="验证码" prop="vcode">
|
||||||
<div class="code-row">
|
<div class="code-row">
|
||||||
<el-input v-model="form.vcode" clearable autocomplete="off" placeholder="请输入验证码"></el-input>
|
<el-input v-model="form.vcode" clearable autocomplete="off" placeholder="请输入验证码"></el-input>
|
||||||
<el-button
|
<el-button class="code-btn" :disabled="isDisabled || isGettingCode" :loading="isGettingCode"
|
||||||
class="code-btn"
|
@click="debouncedGetCode">
|
||||||
:disabled="isDisabled || isGettingCode"
|
{{ sendCodeText }}
|
||||||
:loading="isGettingCode"
|
|
||||||
@click="debouncedGetCode"
|
|
||||||
>
|
|
||||||
{{ sendCodeText }}
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -288,18 +277,21 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.code-btn {
|
.code-btn {
|
||||||
width: 118px;
|
|
||||||
height: 48px;
|
height: 48px;
|
||||||
color: #2f6bff;
|
padding: 0 18px;
|
||||||
background: #eef4ff;
|
color: #1d4ed8;
|
||||||
border-color: #cfe0ff;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid rgba(37, 99, 235, 0.35);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 255, 255, 0.94);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover,
|
&:disabled {
|
||||||
&:focus {
|
opacity: 0.6;
|
||||||
color: #ffffff;
|
cursor: not-allowed;
|
||||||
background: #2f6bff;
|
|
||||||
border-color: #2f6bff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user