Files
李鹏宇 73d764938d feat: 前端 UI 精修 + 后端视图调整 + 资源预置
前端:
- world.html 游戏内聊天组件重构:表情选择器改固定方格+自适应列+去滚动条;
  输入框清空占位文字+隐藏滚动条;聊天面板整体高度缩短约 1/3;
  顶部用户区改为纯文字并点开个人信息弹窗(头像/头像框/性别/说明/称号/注册)
- chat.html 聊天输入框压扁(46→34px)
- codex.html 新增神话品质、天赋/技能书/书籍分类与预置数据
- rules.html 规则文档更新

后端(Odoo):
- game_base 附件改为看板视图(图片/名称/备注/大小)
- yt_world 玩家菜单直接指向 hr.employee
- game_base 员工表单隐藏原生联系人/组织架构页签
- chat 模型/接口/视图与规则数据扩充

资源:生成 4x4 资源精灵表(树木/石材/芦苇/宝箱)
2026-08-14 14:27:15 +08:00

129 lines
5.3 KiB
Python

# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request
from .game_api import _json, _post_body
from ..models.chat import GUIDE_NAME
class ChatApiController(http.Controller):
# ============ 世界聊天 API ============
# 实时采用前端轮询(1.5s 增量拉取),零额外依赖、稳;后续可平滑升级为 bus 推送。
@http.route('/game/api/chat/channels', type='http', auth='public',
methods=['GET'], csrf=False)
def chat_channels(self, **kw):
"""当前用户可见频道:世界频道 + AI向导频道(均公开) + 自己参与的私聊/公会。"""
uid = request.session.uid
Channel = request.env['game.chat.channel'].sudo()
public = [('channel_type', 'in', ['world', 'guide'])]
if uid:
domain = ['|'] + public + [('member_ids', 'in', [uid])]
else:
domain = public
recs = Channel.search_read(domain, ['id', 'name', 'channel_type'],
order='sequence, id')
return _json({'items': [{
'id': r['id'],
'name': r['name'],
'type': r['channel_type'],
} for r in recs]})
@http.route('/game/api/chat/messages', type='http', auth='public',
methods=['GET'], csrf=False)
def chat_messages(self, **kw):
"""拉消息:channel_id 必填;after_id 增量拉取;limit 默认 50。"""
channel_id = request.params.get('channel_id')
after_id = request.params.get('after_id')
try:
limit = int(request.params.get('limit') or 50)
except (TypeError, ValueError):
limit = 50
if not channel_id:
return _json({'error': '缺少 channel_id', 'items': []})
try:
channel_id = int(channel_id)
if after_id:
after_id = int(after_id)
except (TypeError, ValueError):
return _json({'error': '参数格式错误', 'items': []})
Msg = request.env['game.chat.message'].sudo()
domain = [('channel_id', '=', channel_id)]
if after_id:
domain.append(('id', '>', after_id))
recs = Msg.search_read(
domain, ['id', 'author_id', 'author_name', 'body', 'create_date'],
order='id asc', limit=limit)
uid = request.session.uid
items = [{
'id': r['id'],
'author_name': r['author_name'] or '匿名',
'body': r['body'] or '',
'create_date': str(r['create_date']),
'is_self': bool(uid and r.get('author_id')
and r['author_id'][0] == uid),
'is_guide': bool(r['author_name'] == GUIDE_NAME),
} for r in recs]
return _json({'items': items})
@http.route('/game/api/chat/send', type='http', auth='public',
methods=['POST', 'OPTIONS'], csrf=False)
def chat_send(self, **kw):
"""发消息:需登录;body 限 1000 字。昵称取游戏角色名(hr.employee)。"""
uid = request.session.uid
if not uid:
return _json({'ok': False, 'error': '请先登录'})
data = _post_body()
channel_id = data.get('channel_id')
body = (data.get('body') or '').strip()
if not channel_id:
return _json({'ok': False, 'error': '缺少 channel_id'})
if not body:
return _json({'ok': False, 'error': '消息不能为空'})
if len(body) > 1000:
return _json({'ok': False, 'error': '消息过长(最多 1000 字)'})
emp = request.env['hr.employee'].sudo().search(
[('user_id', '=', uid)], limit=1)
author_name = emp.name if emp else request.env['res.users'].sudo().browse(uid).name
try:
channel_id = int(channel_id)
except (TypeError, ValueError):
return _json({'ok': False, 'error': '频道参数错误'})
msg = request.env['game.chat.message'].sudo().create({
'channel_id': channel_id,
'author_id': uid,
'author_name': author_name,
'body': body,
})
# AI 向导自动回复:仅对 guide 类型频道生效,与世界频道隔离
guide_reply = None
ch = request.env['game.chat.channel'].sudo().browse(channel_id)
if ch.exists() and ch.channel_type == 'guide':
reply_text = ch.generate_guide_reply(body)
if reply_text:
reply = request.env['game.chat.message'].sudo().create({
'channel_id': channel_id,
'author_id': False,
'author_name': GUIDE_NAME,
'body': reply_text,
})
guide_reply = {
'id': reply.id,
'author_name': GUIDE_NAME,
'body': reply_text,
'create_date': str(reply.create_date),
'is_self': False,
'is_guide': True,
}
return _json({
'ok': True,
'message': {
'id': msg.id,
'author_name': author_name,
'body': body,
'create_date': str(msg.create_date),
'is_self': True,
},
'guide_reply': guide_reply,
})