|
|
|
import os
|
|
import zipfile
|
|
from datetime import datetime
|
|
from io import BytesIO
|
|
|
|
import pandas as pd
|
|
from flask import request, current_app, send_file
|
|
from flask_restful import Resource, reqparse
|
|
from flask_jwt_extended import jwt_required, get_jwt_identity
|
|
|
|
from app import db
|
|
from app.models import Customer
|
|
from app.models.comparison import Comparison, ComparisonFav
|
|
from app.utils.response import APIResponse
|
|
from sqlalchemy import func
|
|
from datetime import datetime
|
|
|
|
|
|
class MyComparisonListResource(Resource):
|
|
@jwt_required()
|
|
def get(self):
|
|
"""获取我的术语表列表[^1]"""
|
|
|
|
query = Comparison.query.filter_by(customer_id=get_jwt_identity())
|
|
comparisons = [self._format_comparison(comparison) for comparison in query.all()]
|
|
|
|
|
|
return APIResponse.success({
|
|
'data': comparisons,
|
|
'total': len(comparisons)
|
|
})
|
|
|
|
def _format_comparison(self, comparison):
|
|
"""格式化术语表数据"""
|
|
|
|
content_list = []
|
|
if comparison.content:
|
|
for item in comparison.content.split('; '):
|
|
if ':' in item:
|
|
origin, target = item.split(':', 1)
|
|
content_list.append({
|
|
'origin': origin.strip(),
|
|
'target': target.strip()
|
|
})
|
|
|
|
|
|
return {
|
|
'id': comparison.id,
|
|
'title': comparison.title,
|
|
'origin_lang': comparison.origin_lang,
|
|
'target_lang': comparison.target_lang,
|
|
'share_flag': comparison.share_flag,
|
|
'added_count': comparison.added_count,
|
|
'content': content_list,
|
|
'customer_id': comparison.customer_id,
|
|
'created_at': comparison.created_at.strftime('%Y-%m-%d %H:%M') if comparison.created_at else None,
|
|
'updated_at': comparison.updated_at.strftime('%Y-%m-%d %H:%M') if comparison.updated_at else None,
|
|
'deleted_flag': comparison.deleted_flag
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class SharedComparisonListResource(Resource):
|
|
@jwt_required()
|
|
def get(self):
|
|
"""获取共享术语表列表[^3]"""
|
|
|
|
parser = reqparse.RequestParser()
|
|
parser.add_argument('page', type=int, default=1, location='args')
|
|
parser.add_argument('limit', type=int, default=10, location='args')
|
|
parser.add_argument('order', type=str, default='latest', location='args')
|
|
args = parser.parse_args()
|
|
|
|
|
|
query = db.session.query(
|
|
Comparison,
|
|
func.count(ComparisonFav.id).label('fav_count'),
|
|
Customer.email.label('customer_email')
|
|
).outerjoin(
|
|
ComparisonFav, Comparison.id == ComparisonFav.comparison_id
|
|
).outerjoin(
|
|
Customer, Comparison.customer_id == Customer.id
|
|
).filter(
|
|
Comparison.share_flag == 'Y',
|
|
Comparison.deleted_flag == 'N'
|
|
).group_by(
|
|
Comparison.id
|
|
)
|
|
|
|
|
|
if args['order'] == 'latest':
|
|
query = query.order_by(Comparison.created_at.desc())
|
|
elif args['order'] == 'added':
|
|
query = query.order_by(Comparison.added_count.desc())
|
|
elif args['order'] == 'fav':
|
|
query = query.order_by(func.count(ComparisonFav.id).desc())
|
|
|
|
|
|
pagination = query.paginate(page=args['page'], per_page=args['limit'], error_out=False)
|
|
comparisons = [{
|
|
'id': comparison.id,
|
|
'title': comparison.title,
|
|
'origin_lang': comparison.origin_lang,
|
|
'target_lang': comparison.target_lang,
|
|
'content': self.parse_content(comparison.content),
|
|
'email': customer_email if customer_email else '匿名用户',
|
|
'added_count': comparison.added_count,
|
|
'created_at': comparison.created_at.strftime('%Y-%m-%d %H:%M'),
|
|
'faved': self.check_faved(comparison.id),
|
|
'fav_count': fav_count
|
|
} for comparison, fav_count, customer_email in pagination.items]
|
|
|
|
|
|
return APIResponse.success({
|
|
'data': comparisons,
|
|
'total': pagination.total,
|
|
'current_page': pagination.page,
|
|
'per_page': pagination.per_page
|
|
})
|
|
|
|
def parse_content(self, content_str):
|
|
"""将 content 字符串解析为数组格式"""
|
|
content_list = []
|
|
if content_str:
|
|
for item in content_str.split('; '):
|
|
if ':' in item:
|
|
origin, target = item.split(':', 1)
|
|
content_list.append({
|
|
'origin': origin.strip(),
|
|
'target': target.strip()
|
|
})
|
|
return content_list
|
|
|
|
def check_faved(self, comparison_id):
|
|
"""检查当前用户是否收藏了该术语表"""
|
|
|
|
user_id = get_jwt_identity()
|
|
if user_id:
|
|
fav = ComparisonFav.query.filter_by(
|
|
comparison_id=comparison_id,
|
|
customer_id=user_id
|
|
).first()
|
|
return 1 if fav else 0
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
class EditComparisonResource(Resource):
|
|
@jwt_required()
|
|
def post(self, id):
|
|
"""编辑术语表[^3]"""
|
|
comparison = Comparison.query.filter_by(
|
|
id=id,
|
|
customer_id=get_jwt_identity()
|
|
).first_or_404()
|
|
|
|
data = request.form
|
|
if 'title' in data:
|
|
comparison.title = data['title']
|
|
if 'content' in data:
|
|
comparison.content = data['content']
|
|
if 'origin_lang' in data:
|
|
comparison.origin_lang = data['origin_lang']
|
|
if 'target_lang' in data:
|
|
comparison.target_lang = data['target_lang']
|
|
|
|
db.session.commit()
|
|
return APIResponse.success(message='术语表更新成功')
|
|
|
|
|
|
class ShareComparisonResource(Resource):
|
|
@jwt_required()
|
|
def post(self, id):
|
|
"""修改共享状态[^4]"""
|
|
comparison = Comparison.query.filter_by(
|
|
id=id,
|
|
customer_id=get_jwt_identity()
|
|
).first_or_404()
|
|
|
|
data = request.form
|
|
if 'share_flag' not in data or data['share_flag'] not in ['Y', 'N']:
|
|
return APIResponse.error('share_flag 参数无效', 400)
|
|
|
|
comparison.share_flag = data['share_flag']
|
|
db.session.commit()
|
|
return APIResponse.success(message='共享状态已更新')
|
|
|
|
|
|
|
|
|
|
class CopyComparisonResource(Resource):
|
|
@jwt_required()
|
|
def post(self, id):
|
|
"""复制到我的术语库[^5]"""
|
|
comparison = Comparison.query.filter_by(
|
|
id=id,
|
|
share_flag='Y'
|
|
).first_or_404()
|
|
|
|
new_comparison = Comparison(
|
|
title=f"{comparison.title} (副本)",
|
|
content=comparison.content,
|
|
origin_lang=comparison.origin_lang,
|
|
target_lang=comparison.target_lang,
|
|
customer_id=get_jwt_identity(),
|
|
share_flag='N'
|
|
)
|
|
db.session.add(new_comparison)
|
|
db.session.commit()
|
|
return APIResponse.success({
|
|
'new_id': new_comparison.id
|
|
})
|
|
|
|
|
|
class FavoriteComparisonResource(Resource):
|
|
@jwt_required()
|
|
def post(self, id):
|
|
"""收藏/取消收藏[^6]"""
|
|
comparison = Comparison.query.filter_by(id=id).first_or_404()
|
|
customer_id = get_jwt_identity()
|
|
|
|
favorite = ComparisonFav.query.filter_by(
|
|
comparison_id=id,
|
|
customer_id=customer_id
|
|
).first()
|
|
|
|
if favorite:
|
|
db.session.delete(favorite)
|
|
message = '已取消收藏'
|
|
else:
|
|
new_favorite = ComparisonFav(
|
|
comparison_id=id,
|
|
customer_id=customer_id
|
|
)
|
|
db.session.add(new_favorite)
|
|
message = '已收藏'
|
|
|
|
db.session.commit()
|
|
return APIResponse.success(message=message)
|
|
|
|
|
|
class CreateComparisonResource(Resource):
|
|
@jwt_required()
|
|
def post(self):
|
|
"""创建新术语表[^1]"""
|
|
data = request.form
|
|
required_fields = ['title', 'share_flag', 'origin_lang', 'target_lang']
|
|
if not all(field in data for field in required_fields):
|
|
return APIResponse.error('缺少必要参数', 400)
|
|
|
|
|
|
content_list = []
|
|
for key, value in data.items():
|
|
if key.startswith('content[') and '][origin]' in key:
|
|
|
|
index = key.split('[')[1].split(']')[0]
|
|
origin = value
|
|
target = data.get(f'content[{index}][target]', '')
|
|
content_list.append(f"{origin}: {target}")
|
|
|
|
|
|
content_str = '; '.join(content_list)
|
|
|
|
|
|
current_time = datetime.utcnow()
|
|
|
|
|
|
comparison = Comparison(
|
|
title=data['title'],
|
|
origin_lang=data['origin_lang'],
|
|
target_lang=data['target_lang'],
|
|
content=content_str,
|
|
customer_id=get_jwt_identity(),
|
|
share_flag=data.get('share_flag', 'N'),
|
|
created_at=current_time,
|
|
updated_at=current_time
|
|
)
|
|
db.session.add(comparison)
|
|
db.session.commit()
|
|
return APIResponse.success({
|
|
'id': comparison.id
|
|
})
|
|
|
|
|
|
|
|
class DeleteComparisonResource(Resource):
|
|
@jwt_required()
|
|
def delete(self, id):
|
|
"""删除术语表[^2]"""
|
|
comparison = Comparison.query.filter_by(
|
|
id=id,
|
|
customer_id=get_jwt_identity()
|
|
).first_or_404()
|
|
|
|
db.session.delete(comparison)
|
|
db.session.commit()
|
|
return APIResponse.success(message='删除成功')
|
|
|
|
|
|
|
|
class DownloadTemplateResource(Resource):
|
|
def get(self):
|
|
"""下载模板文件[^3]"""
|
|
from flask import send_file
|
|
from io import BytesIO
|
|
import pandas as pd
|
|
|
|
|
|
df = pd.DataFrame(columns=['源术语', '目标术语'])
|
|
output = BytesIO()
|
|
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
|
|
df.to_excel(writer, index=False)
|
|
output.seek(0)
|
|
|
|
return send_file(
|
|
output,
|
|
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
as_attachment=True,
|
|
download_name='术语表模板.xlsx'
|
|
)
|
|
|
|
|
|
class ImportComparisonResource(Resource):
|
|
@jwt_required()
|
|
def post(self):
|
|
"""
|
|
导入 Excel 文件
|
|
"""
|
|
|
|
if 'file' not in request.files:
|
|
return APIResponse.error('未选择文件', 400)
|
|
file = request.files['file']
|
|
|
|
try:
|
|
|
|
import pandas as pd
|
|
df = pd.read_excel(file)
|
|
|
|
|
|
if not {'源术语', '目标术语'}.issubset(df.columns):
|
|
return APIResponse.error('文件格式不符合模板要求', 406)
|
|
|
|
content = ';'.join([f"{row['源术语']}: {row['目标术语']}" for _, row in df.iterrows()])
|
|
|
|
comparison = Comparison(
|
|
title='导入的术语表',
|
|
origin_lang='未知',
|
|
target_lang='未知',
|
|
content=content,
|
|
customer_id=get_jwt_identity(),
|
|
share_flag='N'
|
|
)
|
|
db.session.add(comparison)
|
|
db.session.commit()
|
|
|
|
|
|
return APIResponse.success({
|
|
'id': comparison.id
|
|
})
|
|
except Exception as e:
|
|
|
|
return APIResponse.error(f"文件导入失败:{str(e)}", 500)
|
|
|
|
|
|
|
|
|
|
class ExportComparisonResource(Resource):
|
|
@jwt_required()
|
|
def get(self, id):
|
|
"""
|
|
导出单个术语表
|
|
"""
|
|
|
|
current_user_id = get_jwt_identity()
|
|
|
|
|
|
comparison = Comparison.query.get_or_404(id)
|
|
|
|
|
|
if comparison.share_flag != 'Y' and comparison.user_id != current_user_id:
|
|
return {'message': '术语表未共享或无权限访问', 'code': 403}, 403
|
|
|
|
|
|
terms = [term.split(': ') for term in comparison.content.split(';')]
|
|
df = pd.DataFrame(terms, columns=['源术语', '目标术语'])
|
|
|
|
|
|
output = BytesIO()
|
|
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
|
|
df.to_excel(writer, index=False)
|
|
output.seek(0)
|
|
|
|
|
|
return send_file(
|
|
output,
|
|
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
as_attachment=True,
|
|
download_name=f'{comparison.title}.xlsx'
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class ExportAllComparisonsResource(Resource):
|
|
@jwt_required()
|
|
def get(self):
|
|
"""
|
|
批量导出所有术语表
|
|
"""
|
|
|
|
current_user_id = get_jwt_identity()
|
|
|
|
|
|
comparisons = Comparison.query.filter_by(customer_id=current_user_id).all()
|
|
|
|
|
|
memory_file = BytesIO()
|
|
with zipfile.ZipFile(memory_file, 'w') as zf:
|
|
for comparison in comparisons:
|
|
|
|
terms = [term.split(': ') for term in comparison.content.split(';')]
|
|
df = pd.DataFrame(terms, columns=['源术语', '目标术语'])
|
|
|
|
|
|
output = BytesIO()
|
|
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
|
|
df.to_excel(writer, index=False)
|
|
output.seek(0)
|
|
|
|
|
|
zf.writestr(f"{comparison.title}.xlsx", output.getvalue())
|
|
|
|
memory_file.seek(0)
|
|
|
|
|
|
return send_file(
|
|
memory_file,
|
|
mimetype='application/zip',
|
|
as_attachment=True,
|
|
download_name=f'术语表_{datetime.now().strftime("%Y%m%d")}.zip'
|
|
)
|
|
|
|
|
|
|