diff --git a/addons/game_base/controllers/game_api.py b/addons/game_base/controllers/game_api.py
index 9d6ed42..debd622 100644
--- a/addons/game_base/controllers/game_api.py
+++ b/addons/game_base/controllers/game_api.py
@@ -203,8 +203,10 @@ class GameApiController(http.Controller):
password = data.get('password') or ''
if not login or not password:
return _json({'ok': False, 'error': '账号和密码必填'})
- if len(password) < 6:
- return _json({'ok': False, 'error': '密码至少 6 位'})
+ if len(password) < 8:
+ return _json({'ok': False, 'error': '密码至少 8 位'})
+ if not any(c.isalpha() for c in password) or not any(c.isdigit() for c in password):
+ return _json({'ok': False, 'error': '密码必须同时包含字母和数字'})
User = request.env['res.users'].sudo()
if User.search([('login', '=', login)]):
return _json({'ok': False, 'error': '该账号已存在'})
diff --git a/addons/game_base/models/page.py b/addons/game_base/models/page.py
new file mode 100644
index 0000000..6b59a2c
--- /dev/null
+++ b/addons/game_base/models/page.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from odoo import models, fields
+
+
+class GamePage(models.Model):
+ """页面板块:官网单页(特色 / 背景 / 致玩家等)的容器。"""
+ _name = 'game.page'
+ _description = '页面板块'
+ _order = 'sequence asc'
+ _rec_name = 'title'
+
+ key = fields.Char(string='标识', required=True, index=True,
+ help='前端按 key 取板块,如 features / background / letter')
+ title = fields.Char(string='标题', required=True)
+ subtitle = fields.Char(string='副标题')
+ sequence = fields.Integer(string='排序', default=10)
+ body_html = fields.Html(string='正文', sanitize=True)
+ active = fields.Boolean(string='生效', default=True)
+ item_ids = fields.One2many('game.page.item', 'page_id', string='条目')
+
+
+class GamePageItem(models.Model):
+ """页面板块下的条目(卡片)。"""
+ _name = 'game.page.item'
+ _description = '页面条目'
+ _order = 'sequence asc'
+ _rec_name = 'title'
+
+ page_id = fields.Many2one('game.page', string='所属板块',
+ required=True, ondelete='cascade', index=True)
+ sequence = fields.Integer(string='排序', default=10)
+ icon = fields.Char(string='图标', help='emoji 或符号,如 🌐 / ✦')
+ title = fields.Char(string='标题', required=True)
+ desc = fields.Text(string='描述')
diff --git a/addons/yt_world/models/yt_world_tile.py b/addons/yt_world/models/yt_world_tile.py
index 1937f30..04ade8f 100644
--- a/addons/yt_world/models/yt_world_tile.py
+++ b/addons/yt_world/models/yt_world_tile.py
@@ -31,11 +31,11 @@ class YtWorldTile(models.Model):
for t in self:
t.coord = "%s,%s" % (t.q, t.r)
- @api.model
- def create(self, vals):
- rec = super(YtWorldTile, self).create(vals)
- rec._ensure_micro()
- return rec
+ @api.model_create_multi
+ def create(self, vals_list):
+ records = super(YtWorldTile, self).create(vals_list)
+ records._ensure_micro()
+ return records
def _ensure_micro(self):
"""新建地块时同步建 1:1 微观世界占位。"""
@@ -44,7 +44,6 @@ class YtWorldTile(models.Model):
m = self.env['yt.world.tile.micro'].create({'tile_id': t.id})
t.micro_world_id = m
- @api.model
def write(self, vals):
# 任何编辑都刷新 updated_at
if 'updated_at' not in vals:
diff --git a/addons/yt_world/security/ir.model.access.csv b/addons/yt_world/security/ir.model.access.csv
index 3cbaddd..bd1c97f 100644
--- a/addons/yt_world/security/ir.model.access.csv
+++ b/addons/yt_world/security/ir.model.access.csv
@@ -1,5 +1,13 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_yt_world,yt.world,model_yt_world,base.group_user,1,1,1,1
-access_yt_world_tile,yt.world.tile,model_yt_world_tile,base.group_user,1,1,1,1
-access_yt_world_tile_micro,yt.world.tile.micro,model_yt_world_tile_micro,base.group_user,1,1,1,1
-access_yt_resource,yt.resource,model_yt_resource,base.group_user,1,1,1,1
+access_yt_world_user,yt.world user,model_yt_world,base.group_user,1,1,1,1
+access_yt_world_public,yt.world public,model_yt_world,base.group_public,1,1,1,1
+access_yt_world_portal,yt.world portal,model_yt_world,base.group_portal,1,1,1,1
+access_yt_world_tile_user,yt.world.tile user,model_yt_world_tile,base.group_user,1,1,1,1
+access_yt_world_tile_public,yt.world.tile public,model_yt_world_tile,base.group_public,1,1,1,1
+access_yt_world_tile_portal,yt.world.tile portal,model_yt_world_tile,base.group_portal,1,1,1,1
+access_yt_world_tile_micro_user,yt.world.tile.micro user,model_yt_world_tile_micro,base.group_user,1,1,1,1
+access_yt_world_tile_micro_public,yt.world.tile.micro public,model_yt_world_tile_micro,base.group_public,1,1,1,1
+access_yt_world_tile_micro_portal,yt.world.tile.micro portal,model_yt_world_tile_micro,base.group_portal,1,1,1,1
+access_yt_resource_user,yt.resource user,model_yt_resource,base.group_user,1,1,1,1
+access_yt_resource_public,yt.resource public,model_yt_resource,base.group_public,1,1,1,1
+access_yt_resource_portal,yt.resource portal,model_yt_resource,base.group_portal,1,1,1,1
diff --git a/addons/yt_world/views/yt_world_views.xml b/addons/yt_world/views/yt_world_views.xml
index 92aa1b9..1890677 100644
--- a/addons/yt_world/views/yt_world_views.xml
+++ b/addons/yt_world/views/yt_world_views.xml
@@ -1,13 +1,6 @@
+
+
+
+