|
|
| 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'
|
| )
|
|
|
|
|
|
|