diff --git a/resources/template/shop/main.css b/resources/template/shop/main.css new file mode 100644 index 00000000..73d2c08f --- /dev/null +++ b/resources/template/shop/main.css @@ -0,0 +1,297 @@ +@font-face { + font-family: fzrzFont; + /* 导入的字体文件 */ + src: url("../../font/fzrzExtraBold.ttf"); +} + +@font-face { + font-family: msyhFont; + /* 导入的字体文件 */ + src: url("../../font/msyh.ttf"); +} +@font-face { + font-family: hywhFont; + /* 导入的字体文件 */ + src: url("../../font/HYWenHei-85W.ttf"); +} +@font-face { + font-family: syhtFont; + /* 导入的字体文件 */ + src: url("../../font/syht.otf"); +} + +body { + position: absolute; + left: -8px; + top: -8px; +} + +.wrapper { + width: 800px; + font-family: "hywhFont"; + padding: 10px 0; + background-color: #fbe4e4; + box-sizing: border-box; +} + +.top-title { + color: #e87692; + font-size: 50px; + text-align: center; + font-family: "fzrzFont"; +} + +.split { + background-image: url("./res/img/split.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + height: 41px; +} + +.top-head { + background-image: url("./res/img/head.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + height: 400px; +} + +.shop-item { + padding-top: 30px; + box-sizing: border-box; +} + +.shop-item-border { + display: flex; + position: relative; +} + +.shop-item-title { + background-image: url("./res/img/title-bk.png"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + height: 100px; + margin-bottom: 20px; + display: flex; + justify-content: center; +} + +.shop-item-title-text { + color: white; + font-size: 30px; + margin-top: 13px; +} + +.shop-item-left { + min-height: 460px; + position: relative; + width: 140px; +} + +.shop-item-left-qq { + position: absolute; + height: 100px; +} + +.left-img { + left: 10px; +} + +.shop-item-right { + width: 130px; +} + +.shop-item-right-zx { + height: 460px; + position: absolute; + z-index: 3; + top: 10px; +} + +.right-img { + right: 10px; +} + +.shop-item-mid { + width: 480px; + box-sizing: border-box; + padding-top: 20px; + position: relative; +} + +.shop-item-mid-bk-inner { + width: 520px; + box-sizing: border-box; + padding-top: 30px; + background-color: #be5972; + border: 1px solid #b14b5f; + border-radius: 10px; + position: absolute; + right: -50px; + top: 10px; + z-index: 1; + height: calc(100% - 20px); +} + +.shop-item-mid-bk-out { + width: 520px; + box-sizing: border-box; + background-color: #f096a8; + border: 1px solid #812528; + border-radius: 10px; + height: 100%; + z-index: 2; + position: relative; + padding: 30px; +} + +.goods-item { + background-color: #f8cfd8; + width: 100%; + min-height: 130px; + border-radius: 10px; + padding: 10px; + display: flex; + position: relative; + border: 1px solid #994446; +} + +.goods-id { + position: absolute; + color: white; + font-size: 15px; + border-top-left-radius: 10px; + top: 0; + left: 0; + border-right: 60px solid transparent; + border-bottom: 60px solid transparent; + border-top: 60px solid #ea7492; +} + +.goods-id-text { + position: absolute; + top: -54px; + left: 9px; + color: white; + font-size: 16px; + font-family: "fzrzFont"; + transform: rotate(-45deg); +} + +.goods-item-left { + height: 100%; + min-height: 130px; + display: flex; + flex-direction: column; + justify-content: space-between; + padding-top: 30px; + box-sizing: border-box; +} + +.goods-item-left-o { + height: 24px; + width: 20px; + background-color: #e99eab; + border-radius: 40%; + border: 1px solid #994446; +} + +.goods-item-icon { + background-color: #fefefe; + border: 1px solid #994446; + margin-left: 20px; + width: 120px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; +} + +.goods-item-icon-img { + height: 104px; + width: 104px; +} + +.goods-item-right-des { + width: 272px; + min-height: 80px; + background-color: #fefefe; + border: 1px solid #994446; + margin-left: 10px; + border-radius: 10px; + padding: 5px; + font-family: "msyhFont"; +} + +.goods-item-right-price { + min-height: 30px; + background-color: #fefefe; + border-radius: 30px; + border: 1px solid #994446; + height: 20px; + margin-left: 10px; + margin-top: 5px; + font-size: 15px; + font-family: "msyhFont"; + display: flex; + align-items: center; + padding-left: 10px; + position: relative; +} + +.goods-item-right-btn { + min-height: 30px; + background-color: #bf9ac6; + color: white; + border-radius: 30px; + position: absolute; + right: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 0 10px; +} + +.goods-item-right-btn-line { + width: 1px; + height: 20px; + background-color: #994446; + margin: 0 10px; +} + +.shop-item-mid-bag1 { + position: absolute; + width: 70px; + height: 78px; + bottom: -35px; + left: -35px; + z-index: 4; +} + +.shop-item-mid-bag2 { + position: absolute; + width: 121px; + height: 89px; + right: -35px; + bottom: -35px; + z-index: 4; +} + +.bottom-s { + margin-top: 70px; +} + +.goods-item-name { + font-size: 18px; + font-family: "syhtFont"; +} + +.goods-item-name-line { + height: 2px; + width: 100%; + margin: 3px 0; + background-color: #731c1c; + border-radius: 10px; +} diff --git a/resources/template/shop/main.html b/resources/template/shop/main.html new file mode 100644 index 00000000..82c84a4d --- /dev/null +++ b/resources/template/shop/main.html @@ -0,0 +1,130 @@ + + + + + + + + test + + + + + +
+
+ {{name}}的神秘商店 +
+
+
+
+ {% for data in data_list %} +
+
+ {{data.partition}} +
+
+
+ +
+
+
+
+ {% for goods in data['goods_list'] %} +
+
+
{{goods.id}}
+
+
+
+
+
+
+
+ +
+
+
+
+ {{goods.name}} +
+
+ {{goods.description}} +
+
+ {{goods.price}}金币 + + 立即购买 + + 限购: {{goods.daily_limit}} + +
+
+
+ {% endfor %} +
+ + +
+
+ +
+
+ + +
+ {% endfor %} +
+
+
+ + + + \ No newline at end of file diff --git a/resources/template/shop/main.js b/resources/template/shop/main.js new file mode 100644 index 00000000..e69de29b diff --git a/resources/template/shop/res/img/1.png b/resources/template/shop/res/img/1.png new file mode 100644 index 00000000..331ee5ea Binary files /dev/null and b/resources/template/shop/res/img/1.png differ diff --git a/resources/template/shop/res/img/2.png b/resources/template/shop/res/img/2.png new file mode 100644 index 00000000..f8c22f5e Binary files /dev/null and b/resources/template/shop/res/img/2.png differ diff --git a/resources/template/shop/res/img/bag1.png b/resources/template/shop/res/img/bag1.png new file mode 100644 index 00000000..806b3316 Binary files /dev/null and b/resources/template/shop/res/img/bag1.png differ diff --git a/resources/template/shop/res/img/bag2.png b/resources/template/shop/res/img/bag2.png new file mode 100644 index 00000000..d02d1b20 Binary files /dev/null and b/resources/template/shop/res/img/bag2.png differ diff --git a/resources/template/shop/res/img/bag3.png b/resources/template/shop/res/img/bag3.png new file mode 100644 index 00000000..e8cff836 Binary files /dev/null and b/resources/template/shop/res/img/bag3.png differ diff --git a/resources/template/shop/res/img/goods-bk.png b/resources/template/shop/res/img/goods-bk.png new file mode 100644 index 00000000..66978ef4 Binary files /dev/null and b/resources/template/shop/res/img/goods-bk.png differ diff --git a/resources/template/shop/res/img/head.png b/resources/template/shop/res/img/head.png new file mode 100644 index 00000000..e240972c Binary files /dev/null and b/resources/template/shop/res/img/head.png differ diff --git a/resources/template/shop/res/img/item-bk1.png b/resources/template/shop/res/img/item-bk1.png new file mode 100644 index 00000000..2a72b00a Binary files /dev/null and b/resources/template/shop/res/img/item-bk1.png differ diff --git a/resources/template/shop/res/img/item-bk2.png b/resources/template/shop/res/img/item-bk2.png new file mode 100644 index 00000000..c6480ce2 Binary files /dev/null and b/resources/template/shop/res/img/item-bk2.png differ diff --git a/resources/template/shop/res/img/item-bk3.png b/resources/template/shop/res/img/item-bk3.png new file mode 100644 index 00000000..29543cb1 Binary files /dev/null and b/resources/template/shop/res/img/item-bk3.png differ diff --git a/resources/template/shop/res/img/qq.png b/resources/template/shop/res/img/qq.png new file mode 100644 index 00000000..2faf9e7c Binary files /dev/null and b/resources/template/shop/res/img/qq.png differ diff --git a/resources/template/shop/res/img/split.png b/resources/template/shop/res/img/split.png new file mode 100644 index 00000000..129e05d9 Binary files /dev/null and b/resources/template/shop/res/img/split.png differ diff --git a/resources/template/shop/res/img/title-bk.png b/resources/template/shop/res/img/title-bk.png new file mode 100644 index 00000000..6c49f190 Binary files /dev/null and b/resources/template/shop/res/img/title-bk.png differ diff --git a/zhenxun/builtin_plugins/shop/__init__.py b/zhenxun/builtin_plugins/shop/__init__.py index 104ae88e..71c7f713 100644 --- a/zhenxun/builtin_plugins/shop/__init__.py +++ b/zhenxun/builtin_plugins/shop/__init__.py @@ -16,7 +16,7 @@ from nonebot_plugin_alconna import ( ) from nonebot_plugin_uninfo import Uninfo -from zhenxun.configs.utils import BaseBlock, PluginExtraData +from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.depends import UserName from zhenxun.utils.enum import BlockType, PluginType @@ -45,6 +45,14 @@ __plugin_meta__ = PluginMetadata( plugin_type=PluginType.NORMAL, menu_type="商店", limits=[BaseBlock(check_type=BlockType.GROUP)], + configs=[ + RegisterConfig( + key="style", + value="zhenxun", + help="商店样式类型,[normal, zhenxun]", + default_value="zhenxun", + ) + ], ).dict(), ) @@ -108,7 +116,7 @@ _matcher.shortcut( @_matcher.assign("$main") async def _(session: Uninfo, arparma: Arparma): - image = await ShopManage.build_shop_image() + image = await ShopManage.get_shop_image() logger.info("查看商店", arparma.header_result, session=session) await MessageUtils.build_message(image).send() diff --git a/zhenxun/builtin_plugins/shop/_data_source.py b/zhenxun/builtin_plugins/shop/_data_source.py index ec8c3d97..56069b56 100644 --- a/zhenxun/builtin_plugins/shop/_data_source.py +++ b/zhenxun/builtin_plugins/shop/_data_source.py @@ -10,7 +10,6 @@ from nonebot_plugin_alconna import UniMessage, UniMsg from nonebot_plugin_uninfo import Uninfo from pydantic import BaseModel, create_model -from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.models.friend_user import FriendUser from zhenxun.models.goods_info import GoodsInfo from zhenxun.models.group_member_info import GroupInfoUser @@ -19,19 +18,12 @@ from zhenxun.models.user_gold_log import UserGoldLog from zhenxun.models.user_props_log import UserPropsLog from zhenxun.services.log import logger from zhenxun.utils.enum import GoldHandle, PropHandle -from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image +from zhenxun.utils.image_utils import BuildImage, ImageTemplate from zhenxun.utils.platform import PlatformUtils -ICON_PATH = IMAGE_PATH / "shop_icon" - -RANK_ICON_PATH = IMAGE_PATH / "_icon" - -PLATFORM_PATH = { - "dodo": RANK_ICON_PATH / "dodo.png", - "discord": RANK_ICON_PATH / "discord.png", - "kaiheila": RANK_ICON_PATH / "kook.png", - "qq": RANK_ICON_PATH / "qq.png", -} +from .config import ICON_PATH, PLATFORM_PATH, base_config +from .html_image import html_image +from .normal_image import normal_image class Goods(BaseModel): @@ -137,6 +129,12 @@ async def gold_rank( class ShopManage: uuid2goods: dict[str, Goods] = {} # noqa: RUF012 + @classmethod + async def get_shop_image(cls) -> bytes: + if base_config.get("style") == "zhenxun": + return await html_image() + return await normal_image() + @classmethod def __build_params( cls, @@ -484,200 +482,3 @@ class ShopManage: """ user = await UserConsole.get_user(user_id, platform) return user.gold - - @classmethod - async def build_shop_image(cls) -> BuildImage: - """制作商店图片 - - 返回: - BuildImage: 商店图片 - """ - goods_lst = await GoodsInfo.get_all_goods() - h = 10 - _list: list[GoodsInfo] = [ - goods - for goods in goods_lst - if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time - ] - # A = BuildImage(1100, h, color="#f9f6f2") - total_n = 0 - image_list = [] - for idx, goods in enumerate(_list): - name_image = BuildImage( - 580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf" - ) - await name_image.text( - (15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height" - ) - await name_image.line((380, -5, 280, 45), "#a29ad6", 5) - await name_image.text((390, 0), "售价:", center_type="height") - if goods.goods_discount != 1: - discount_price = int(goods.goods_discount * goods.goods_price) - old_price_image = await BuildImage.build_text_image( - str(goods.goods_price), font_color=(194, 194, 194), size=15 - ) - await old_price_image.line( - ( - 0, - int(old_price_image.height / 2), - old_price_image.width + 1, - int(old_price_image.height / 2), - ), - (0, 0, 0), - ) - await name_image.paste(old_price_image, (440, 0)) - await name_image.text((440, 15), str(discount_price), (255, 255, 255)) - else: - await name_image.text( - (440, 0), - str(goods.goods_price), - (255, 255, 255), - center_type="height", - ) - _tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25) - await name_image.text( - ( - 440 + _tmp.width, - 0, - ), - " 金币", - center_type="height", - ) - des_image = None - font_img = BuildImage(600, 80, font_size=20, color="#a29ad6") - p = font_img.getsize("简介:")[0] + 20 - if goods.goods_description: - des_list = goods.goods_description.split("\n") - desc = "" - for des in des_list: - if font_img.getsize(des)[0] > font_img.width - p - 20: - msg = "" - tmp = "" - for i in range(len(des)): - if font_img.getsize(tmp)[0] < font_img.width - p - 20: - tmp += des[i] - else: - msg += tmp + "\n" - tmp = des[i] - desc += msg - if tmp: - desc += tmp - else: - desc += des + "\n" - if desc[-1] == "\n": - desc = desc[:-1] - des_image = await text2image(desc, color="#a29ad6") - goods_image = BuildImage( - 600, - (50 + des_image.height) if des_image else 50, - font_size=20, - color="#a29ad6", - font="CJGaoDeGuo.otf", - ) - if des_image: - await goods_image.text((15, 50), "简介:") - await goods_image.paste(des_image, (p, 50)) - await name_image.circle_corner(5) - await goods_image.paste(name_image, (0, 5), center_type="width") - await goods_image.circle_corner(20) - bk = BuildImage( - 1180, - (50 + des_image.height) if des_image else 50, - font_size=15, - color="#f9f6f2", - font="CJGaoDeGuo.otf", - ) - if goods.icon and (ICON_PATH / goods.icon).exists(): - icon = BuildImage(70, 70, background=ICON_PATH / goods.icon) - await bk.paste(icon) - await bk.paste(goods_image, (70, 0)) - n = 0 - _w = 650 - # 添加限时图标和时间 - if goods.goods_limit_time > 0: - n += 140 - _limit_time_logo = BuildImage( - 40, 40, background=f"{IMAGE_PATH}/other/time.png" - ) - await bk.paste(_limit_time_logo, (_w + 50, 0)) - _time_img = await BuildImage.build_text_image("限时!", size=23) - await bk.paste( - _time_img, - (_w + 90, 10), - ) - limit_time = time.strftime( - "%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time) - ).split() - y_m_d = limit_time[0] - _h_m = limit_time[1].split(":") - h_m = f"{_h_m[0]}时 {_h_m[1]}分" - await bk.text((_w + 55, 38), str(y_m_d)) - await bk.text((_w + 65, 57), str(h_m)) - _w += 140 - if goods.goods_discount != 1: - n += 140 - _discount_logo = BuildImage( - 30, 30, background=f"{IMAGE_PATH}/other/discount.png" - ) - await bk.paste(_discount_logo, (_w + 50, 10)) - _tmp = await BuildImage.build_text_image("折扣!", size=23) - await bk.paste(_tmp, (_w + 90, 15)) - _tmp = await BuildImage.build_text_image( - f"{10 * goods.goods_discount:.1f} 折", - size=30, - font_color=(85, 156, 75), - ) - await bk.paste(_tmp, (_w + 50, 44)) - _w += 140 - if goods.daily_limit != 0: - n += 140 - _daily_limit_logo = BuildImage( - 35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png" - ) - await bk.paste(_daily_limit_logo, (_w + 50, 10)) - _tmp = await BuildImage.build_text_image( - "限购!", - size=23, - ) - await bk.paste(_tmp, (_w + 90, 20)) - _tmp = await BuildImage.build_text_image( - f"{goods.daily_limit}", size=30 - ) - await bk.paste(_tmp, (_w + 72, 45)) - total_n = max(total_n, n) - if n: - await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5) - # await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5) - - # 添加限时图标和时间 - image_list.append(bk) - # await A.apaste(bk, (0, current_h), True) - # current_h += 90 - current_h = 0 - h = sum(img.height + 10 for img in image_list) or 400 - A = BuildImage(1100, h, color="#f9f6f2") - for img in image_list: - await A.paste(img, (0, current_h)) - current_h += img.height + 10 - w = 950 - if total_n: - w += total_n - h = A.height + 230 + 100 - h = max(h, 1000) - shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png") - shop = BuildImage(w, h, font_size=20, color="#f9f6f2") - await shop.paste(A, (20, 230)) - await shop.paste(shop_logo, (450, 30)) - tip = "注【通过 购买道具 序号 或者 商品名称 购买】" - await shop.text( - ( - int((1000 - shop.getsize(tip)[0]) / 2), - 170, - ), - "注【通过 序号 或者 商品名称 购买】", - ) - await shop.text( - (20, h - 100), - "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~", - ) - return shop diff --git a/zhenxun/builtin_plugins/shop/config.py b/zhenxun/builtin_plugins/shop/config.py new file mode 100644 index 00000000..7fa13a5d --- /dev/null +++ b/zhenxun/builtin_plugins/shop/config.py @@ -0,0 +1,20 @@ +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH + +base_config = Config.get("shop") + +ICON_PATH = IMAGE_PATH / "shop_icon" + + +RANK_ICON_PATH = IMAGE_PATH / "_icon" + +PLATFORM_PATH = { + "dodo": RANK_ICON_PATH / "dodo.png", + "discord": RANK_ICON_PATH / "discord.png", + "kaiheila": RANK_ICON_PATH / "kook.png", + "qq": RANK_ICON_PATH / "qq.png", +} + +LEFT_RIGHT_IMAGE = ["1.png", "2.png", "qq.png"] + +LEFT_RIGHT_PATH = TEMPLATE_PATH / "shop" / "res" / "img" diff --git a/zhenxun/builtin_plugins/shop/html_image.py b/zhenxun/builtin_plugins/shop/html_image.py new file mode 100644 index 00000000..61801415 --- /dev/null +++ b/zhenxun/builtin_plugins/shop/html_image.py @@ -0,0 +1,91 @@ +import random +import time + +from nonebot_plugin_htmlrender import template_to_pic +from pydantic import BaseModel + +from zhenxun.configs.config import BotConfig +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.goods_info import GoodsInfo +from zhenxun.utils._build_image import BuildImage + +from .config import ICON_PATH, LEFT_RIGHT_IMAGE + + +class GoodsItem(BaseModel): + goods_list: list[dict] + """商品列表""" + partition: str + """分区名称""" + left_image: tuple[int, str, str] + """左图""" + right_image: tuple[int, str, str] + """右图""" + + +def get_left_right_image() -> tuple[tuple[int, str, str], tuple[int, str, str]]: + qq_top = random.randint(0, 280) + img_top = random.randint(10, 80) + left_image = random.choice(LEFT_RIGHT_IMAGE) + right_image = None + if left_image == "qq.png": + left_top = qq_top + right_top = img_top + left_css = "shop-item-left-qq" + right_css = "shop-item-right-zx" + right_image = random.choice(LEFT_RIGHT_IMAGE[:-1]) + else: + left_top = img_top + right_top = qq_top + right_image = "qq.png" + left_css = "shop-item-right-zx" + right_css = "shop-item-left-qq" + return (left_top, left_css, left_image), (right_top, right_css, right_image) + + +async def html_image() -> bytes: + """构建图片""" + goods_list: list[tuple[int, GoodsInfo]] = [ + (i + 1, goods) + for i, goods in enumerate(await GoodsInfo.get_all_goods()) + if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time + ] + partition_dict: dict[str, list[dict]] = {} + for goods in goods_list: + if not goods[1].partition: + goods[1].partition = "默认分区" + if goods[1].partition not in partition_dict: + partition_dict[goods[1].partition] = [] + partition_dict[goods[1].partition].append( + { + "id": goods[0], + "price": goods[1].goods_price, + "daily_limit": goods[1].daily_limit or "∞", + "name": goods[1].goods_name, + "icon": "data:image/png;base64," + + BuildImage.open(ICON_PATH / goods[1].icon).pic2bs4()[9:], + "description": goods[1].goods_description, + } + ) + data_list = [] + for partition in partition_dict: + left, right = get_left_right_image() + data_list.append( + GoodsItem( + goods_list=partition_dict[partition], + partition=partition, + left_image=left, + right_image=right, + ) + ) + + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "shop").absolute()), + template_name="main.html", + templates={"name": BotConfig.self_nickname, "data_list": data_list}, + pages={ + "viewport": {"width": 800, "height": 1024}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/builtin_plugins/shop/normal_image.py b/zhenxun/builtin_plugins/shop/normal_image.py new file mode 100644 index 00000000..76420a24 --- /dev/null +++ b/zhenxun/builtin_plugins/shop/normal_image.py @@ -0,0 +1,203 @@ +import time + +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.goods_info import GoodsInfo +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.image_utils import text2image + +from .config import ICON_PATH + + +async def normal_image() -> bytes: + """制作商店图片 + + 返回: + BuildImage: 商店图片 + """ + goods_lst = await GoodsInfo.get_all_goods() + h = 10 + _list: list[GoodsInfo] = [ + goods + for goods in goods_lst + if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time + ] + # A = BuildImage(1100, h, color="#f9f6f2") + total_n = 0 + image_list = [] + for idx, goods in enumerate(_list): + name_image = BuildImage( + 580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf" + ) + await name_image.text( + (15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height" + ) + await name_image.line((380, -5, 280, 45), "#a29ad6", 5) + await name_image.text((390, 0), "售价:", center_type="height") + if goods.goods_discount != 1: + discount_price = int(goods.goods_discount * goods.goods_price) + old_price_image = await BuildImage.build_text_image( + str(goods.goods_price), font_color=(194, 194, 194), size=15 + ) + await old_price_image.line( + ( + 0, + int(old_price_image.height / 2), + old_price_image.width + 1, + int(old_price_image.height / 2), + ), + (0, 0, 0), + ) + await name_image.paste(old_price_image, (440, 0)) + await name_image.text((440, 15), str(discount_price), (255, 255, 255)) + else: + await name_image.text( + (440, 0), + str(goods.goods_price), + (255, 255, 255), + center_type="height", + ) + _tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25) + await name_image.text( + ( + 440 + _tmp.width, + 0, + ), + " 金币", + center_type="height", + ) + des_image = None + font_img = BuildImage(600, 80, font_size=20, color="#a29ad6") + p = font_img.getsize("简介:")[0] + 20 + if goods.goods_description: + des_list = goods.goods_description.split("\n") + desc = "" + for des in des_list: + if font_img.getsize(des)[0] > font_img.width - p - 20: + msg = "" + tmp = "" + for i in range(len(des)): + if font_img.getsize(tmp)[0] < font_img.width - p - 20: + tmp += des[i] + else: + msg += tmp + "\n" + tmp = des[i] + desc += msg + if tmp: + desc += tmp + else: + desc += des + "\n" + if desc[-1] == "\n": + desc = desc[:-1] + des_image = await text2image(desc, color="#a29ad6") + goods_image = BuildImage( + 600, + (50 + des_image.height) if des_image else 50, + font_size=20, + color="#a29ad6", + font="CJGaoDeGuo.otf", + ) + if des_image: + await goods_image.text((15, 50), "简介:") + await goods_image.paste(des_image, (p, 50)) + await name_image.circle_corner(5) + await goods_image.paste(name_image, (0, 5), center_type="width") + await goods_image.circle_corner(20) + bk = BuildImage( + 1180, + (50 + des_image.height) if des_image else 50, + font_size=15, + color="#f9f6f2", + font="CJGaoDeGuo.otf", + ) + if goods.icon and (ICON_PATH / goods.icon).exists(): + icon = BuildImage(70, 70, background=ICON_PATH / goods.icon) + await bk.paste(icon) + await bk.paste(goods_image, (70, 0)) + n = 0 + _w = 650 + # 添加限时图标和时间 + if goods.goods_limit_time > 0: + n += 140 + _limit_time_logo = BuildImage( + 40, 40, background=f"{IMAGE_PATH}/other/time.png" + ) + await bk.paste(_limit_time_logo, (_w + 50, 0)) + _time_img = await BuildImage.build_text_image("限时!", size=23) + await bk.paste( + _time_img, + (_w + 90, 10), + ) + limit_time = time.strftime( + "%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time) + ).split() + y_m_d = limit_time[0] + _h_m = limit_time[1].split(":") + h_m = f"{_h_m[0]}时 {_h_m[1]}分" + await bk.text((_w + 55, 38), str(y_m_d)) + await bk.text((_w + 65, 57), str(h_m)) + _w += 140 + if goods.goods_discount != 1: + n += 140 + _discount_logo = BuildImage( + 30, 30, background=f"{IMAGE_PATH}/other/discount.png" + ) + await bk.paste(_discount_logo, (_w + 50, 10)) + _tmp = await BuildImage.build_text_image("折扣!", size=23) + await bk.paste(_tmp, (_w + 90, 15)) + _tmp = await BuildImage.build_text_image( + f"{10 * goods.goods_discount:.1f} 折", + size=30, + font_color=(85, 156, 75), + ) + await bk.paste(_tmp, (_w + 50, 44)) + _w += 140 + if goods.daily_limit != 0: + n += 140 + _daily_limit_logo = BuildImage( + 35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png" + ) + await bk.paste(_daily_limit_logo, (_w + 50, 10)) + _tmp = await BuildImage.build_text_image( + "限购!", + size=23, + ) + await bk.paste(_tmp, (_w + 90, 20)) + _tmp = await BuildImage.build_text_image(f"{goods.daily_limit}", size=30) + await bk.paste(_tmp, (_w + 72, 45)) + total_n = max(total_n, n) + if n: + await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5) + # await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5) + + # 添加限时图标和时间 + image_list.append(bk) + # await A.apaste(bk, (0, current_h), True) + # current_h += 90 + current_h = 0 + h = sum(img.height + 10 for img in image_list) or 400 + A = BuildImage(1100, h, color="#f9f6f2") + for img in image_list: + await A.paste(img, (0, current_h)) + current_h += img.height + 10 + w = 950 + if total_n: + w += total_n + h = A.height + 230 + 100 + h = max(h, 1000) + shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png") + shop = BuildImage(w, h, font_size=20, color="#f9f6f2") + await shop.paste(A, (20, 230)) + await shop.paste(shop_logo, (450, 30)) + tip = "注【通过 购买道具 序号 或者 商品名称 购买】" + await shop.text( + ( + int((1000 - shop.getsize(tip)[0]) / 2), + 170, + ), + "注【通过 序号 或者 商品名称 购买】", + ) + await shop.text( + (20, h - 100), + "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~", + ) + return shop.pic2bytes() diff --git a/zhenxun/builtin_plugins/web_ui/__init__.py b/zhenxun/builtin_plugins/web_ui/__init__.py index bbea8708..87fa0ded 100644 --- a/zhenxun/builtin_plugins/web_ui/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/__init__.py @@ -1,117 +1,117 @@ -import asyncio -import secrets +# import asyncio +# import secrets -from fastapi import APIRouter, FastAPI -import nonebot -from nonebot.log import default_filter, default_format -from nonebot.plugin import PluginMetadata +# from fastapi import APIRouter, FastAPI +# import nonebot +# from nonebot.log import default_filter, default_format +# from nonebot.plugin import PluginMetadata -from zhenxun.configs.config import Config as gConfig -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger, logger_ -from zhenxun.utils.enum import PluginType +# from zhenxun.configs.config import Config as gConfig +# from zhenxun.configs.utils import PluginExtraData, RegisterConfig +# from zhenxun.services.log import logger, logger_ +# from zhenxun.utils.enum import PluginType -from .api.logs import router as ws_log_routes -from .api.logs.log_manager import LOG_STORAGE -from .api.tabs.dashboard import router as dashboard_router -from .api.tabs.database import router as database_router -from .api.tabs.main import router as main_router -from .api.tabs.main import ws_router as status_routes -from .api.tabs.manage import router as manage_router -from .api.tabs.manage.chat import ws_router as chat_routes -from .api.tabs.plugin_manage import router as plugin_router -from .api.tabs.plugin_manage.store import router as store_router -from .api.tabs.system import router as system_router -from .auth import router as auth_router -from .public import init_public +# from .api.logs import router as ws_log_routes +# from .api.logs.log_manager import LOG_STORAGE +# from .api.tabs.dashboard import router as dashboard_router +# from .api.tabs.database import router as database_router +# from .api.tabs.main import router as main_router +# from .api.tabs.main import ws_router as status_routes +# from .api.tabs.manage import router as manage_router +# from .api.tabs.manage.chat import ws_router as chat_routes +# from .api.tabs.plugin_manage import router as plugin_router +# from .api.tabs.plugin_manage.store import router as store_router +# from .api.tabs.system import router as system_router +# from .auth import router as auth_router +# from .public import init_public -__plugin_meta__ = PluginMetadata( - name="WebUi", - description="WebUi API", - usage=""" - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.HIDDEN, - configs=[ - RegisterConfig( - module="web-ui", - key="username", - value="admin", - help="前端管理用户名", - type=str, - default_value="admin", - ), - RegisterConfig( - module="web-ui", - key="password", - value=None, - help="前端管理密码", - type=str, - default_value=None, - ), - RegisterConfig( - module="web-ui", - key="secret", - value=secrets.token_urlsafe(32), - help="JWT密钥", - type=str, - default_value=None, - ), - ], - ).dict(), -) +# __plugin_meta__ = PluginMetadata( +# name="WebUi", +# description="WebUi API", +# usage=""" +# """.strip(), +# extra=PluginExtraData( +# author="HibiKier", +# version="0.1", +# plugin_type=PluginType.HIDDEN, +# configs=[ +# RegisterConfig( +# module="web-ui", +# key="username", +# value="admin", +# help="前端管理用户名", +# type=str, +# default_value="admin", +# ), +# RegisterConfig( +# module="web-ui", +# key="password", +# value=None, +# help="前端管理密码", +# type=str, +# default_value=None, +# ), +# RegisterConfig( +# module="web-ui", +# key="secret", +# value=secrets.token_urlsafe(32), +# help="JWT密钥", +# type=str, +# default_value=None, +# ), +# ], +# ).dict(), +# ) -driver = nonebot.get_driver() +# driver = nonebot.get_driver() -gConfig.set_name("web-ui", "web-ui") +# gConfig.set_name("web-ui", "web-ui") -BaseApiRouter = APIRouter(prefix="/zhenxun/api") +# BaseApiRouter = APIRouter(prefix="/zhenxun/api") -BaseApiRouter.include_router(auth_router) -BaseApiRouter.include_router(store_router) -BaseApiRouter.include_router(dashboard_router) -BaseApiRouter.include_router(main_router) -BaseApiRouter.include_router(manage_router) -BaseApiRouter.include_router(database_router) -BaseApiRouter.include_router(plugin_router) -BaseApiRouter.include_router(system_router) +# BaseApiRouter.include_router(auth_router) +# BaseApiRouter.include_router(store_router) +# BaseApiRouter.include_router(dashboard_router) +# BaseApiRouter.include_router(main_router) +# BaseApiRouter.include_router(manage_router) +# BaseApiRouter.include_router(database_router) +# BaseApiRouter.include_router(plugin_router) +# BaseApiRouter.include_router(system_router) -WsApiRouter = APIRouter(prefix="/zhenxun/socket") +# WsApiRouter = APIRouter(prefix="/zhenxun/socket") -WsApiRouter.include_router(ws_log_routes) -WsApiRouter.include_router(status_routes) -WsApiRouter.include_router(chat_routes) +# WsApiRouter.include_router(ws_log_routes) +# WsApiRouter.include_router(status_routes) +# WsApiRouter.include_router(chat_routes) -@driver.on_startup -async def _(): - try: +# @driver.on_startup +# async def _(): +# try: - async def log_sink(message: str): - loop = None - if not loop: - try: - loop = asyncio.get_running_loop() - except Exception as e: - logger.warning("Web Ui log_sink", e=e) - if not loop: - loop = asyncio.new_event_loop() - loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) # noqa: RUF006 +# async def log_sink(message: str): +# loop = None +# if not loop: +# try: +# loop = asyncio.get_running_loop() +# except Exception as e: +# logger.warning("Web Ui log_sink", e=e) +# if not loop: +# loop = asyncio.new_event_loop() +# loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) - logger_.add( - log_sink, colorize=True, filter=default_filter, format=default_format - ) +# logger_.add( +# log_sink, colorize=True, filter=default_filter, format=default_format +# ) - app: FastAPI = nonebot.get_app() - app.include_router(BaseApiRouter) - app.include_router(WsApiRouter) - await init_public(app) - logger.info("API启动成功", "Web UI") - except Exception as e: - logger.error("API启动失败", "Web UI", e=e) +# app: FastAPI = nonebot.get_app() +# app.include_router(BaseApiRouter) +# app.include_router(WsApiRouter) +# await init_public(app) +# logger.info("API启动成功", "Web UI") +# except Exception as e: +# logger.error("API启动失败", "Web UI", e=e) diff --git a/zhenxun/models/goods_info.py b/zhenxun/models/goods_info.py index 4560d903..4a66c1cf 100644 --- a/zhenxun/models/goods_info.py +++ b/zhenxun/models/goods_info.py @@ -27,10 +27,12 @@ class GoodsInfo(Model): """每日限购""" is_passive = fields.BooleanField(default=False) """是否为被动道具""" + partition = fields.CharField(255, null=True) + """分区名称""" icon = fields.TextField(null=True) """图标路径""" - class Meta: + class Meta: # type: ignore table = "goods_info" table_description = "商品数据表" @@ -159,4 +161,5 @@ class GoodsInfo(Model): "ALTER TABLE goods_info ADD icon VARCHAR(255);", # 删除 daily_purchase_limit 字段 "ALTER TABLE goods_info DROP daily_purchase_limit;", + "ALTER TABLE goods_info ADD partition VARCHAR(255);", ]