@ddj-v2/shop 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmignore +21 -0
- package/README.md +266 -0
- package/frontend/foo.page.ts +230 -0
- package/index.ts +681 -0
- package/model.ts +165 -0
- package/package.json +19 -0
- package/templates/coin_base.html +101 -0
- package/templates/coin_bill.html +59 -0
- package/templates/coin_exchange.html +40 -0
- package/templates/coin_gift.html +43 -0
- package/templates/coin_import.html +25 -0
- package/templates/coin_inc.html +39 -0
- package/templates/coin_mall.html +90 -0
- package/templates/coin_myrecord.html +74 -0
- package/templates/coin_record.html +54 -0
- package/templates/coin_show.html +62 -0
- package/templates/domain_coin_setting.html +31 -0
- package/templates/goods_add.html +50 -0
- package/templates/goods_edit.html +57 -0
- package/templates/goods_manage.html +79 -0
- package/templates/shop_manage_entries.html +22 -0
- package/templates/uname_change.html +42 -0
package/model.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { db, UserModel } from 'hydrooj';
|
|
2
|
+
|
|
3
|
+
const collcoin = db.collection('coin');
|
|
4
|
+
const collgoods = db.collection('goods');
|
|
5
|
+
|
|
6
|
+
interface Bill {
|
|
7
|
+
_id: ObjectId;
|
|
8
|
+
userId: number;
|
|
9
|
+
rootId: number;
|
|
10
|
+
amount: number;
|
|
11
|
+
text: string;
|
|
12
|
+
status: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Goods {
|
|
16
|
+
_id: number;
|
|
17
|
+
objectId?: string;
|
|
18
|
+
purchaseModelId?: string;
|
|
19
|
+
data?: Record<string, unknown>;
|
|
20
|
+
name: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
price: number;
|
|
23
|
+
num: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GoodsPurchaseModel {
|
|
27
|
+
purchase(uid: number, goods: Goods, amount: number):
|
|
28
|
+
| Promise<boolean | { success: boolean; message?: string }>
|
|
29
|
+
| (boolean | { success: boolean; message?: string });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare module 'hydrooj' {
|
|
33
|
+
interface Model {
|
|
34
|
+
coin: typeof CoinModel;
|
|
35
|
+
goods: typeof GoodsModel;
|
|
36
|
+
}
|
|
37
|
+
interface Collections {
|
|
38
|
+
bill: Bill;
|
|
39
|
+
goods: Goods;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class CoinModel {
|
|
44
|
+
static coll = collcoin;
|
|
45
|
+
|
|
46
|
+
static async inc(userId: number, rootId: number, amount: number, text: string, asset: number, status?: number) {
|
|
47
|
+
await CoinModel.coll.insertOne({ userId, rootId, amount, text, ...(status !== undefined && { status }) });
|
|
48
|
+
await UserModel.inc(userId, 'coin_now', amount);
|
|
49
|
+
if (amount > 0 && asset === 1) { //如果asset==1则计入总资产
|
|
50
|
+
await UserModel.inc(userId, 'coin_all', amount);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static async getUserBill(userId: number) {
|
|
55
|
+
const query = userId === 0 ? {} : { userId };
|
|
56
|
+
return CoinModel.coll.find(query).sort({ _id: -1 });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static async getUserRecord(userId: number) {
|
|
60
|
+
const query = userId === 0 ? { status: { $gte: 0 } } : { userId, status: { $gte: 0 } };
|
|
61
|
+
return CoinModel.coll.find(query).sort({ status: -1, _id: -1 });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async getBill(billId: string): Promise<Bill> {
|
|
65
|
+
return CoinModel.coll.findOne({ _id: billId });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static async deleteBill(billId: string): Promise<number> {
|
|
69
|
+
const result = await CoinModel.coll.deleteOne({ _id: billId });
|
|
70
|
+
return result.deletedCount;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static async updateBill(id: string, update: Partial<Bill>): Promise<number> {
|
|
74
|
+
const result = await CoinModel.coll.updateOne({ _id: id }, { $set: update });
|
|
75
|
+
return result.modifiedCount;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class GoodsModel {
|
|
80
|
+
static coll = collgoods;
|
|
81
|
+
|
|
82
|
+
static async add(
|
|
83
|
+
name: string,
|
|
84
|
+
price: number,
|
|
85
|
+
num: number,
|
|
86
|
+
objectId = '',
|
|
87
|
+
goodsId?: number,
|
|
88
|
+
purchaseModelId = '',
|
|
89
|
+
data?: Record<string, unknown>,
|
|
90
|
+
description = '',
|
|
91
|
+
) {
|
|
92
|
+
if (typeof goodsId !== 'number') {
|
|
93
|
+
const [goods] = await GoodsModel.coll.find({}).sort({ _id: -1 }).limit(1).toArray();
|
|
94
|
+
goodsId = Math.max((goods?._id || 0) + 1, 1);
|
|
95
|
+
}
|
|
96
|
+
const result = await GoodsModel.coll.insertOne({
|
|
97
|
+
_id: goodsId,
|
|
98
|
+
objectId,
|
|
99
|
+
purchaseModelId,
|
|
100
|
+
data,
|
|
101
|
+
name,
|
|
102
|
+
description,
|
|
103
|
+
price,
|
|
104
|
+
num,
|
|
105
|
+
});
|
|
106
|
+
return result.insertedId;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static async getMulti(): Promise<Goods[]> {
|
|
110
|
+
return GoodsModel.coll.find({});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static async get(goodsId: number): Promise<Goods> {
|
|
114
|
+
return GoodsModel.coll.findOne({ _id: goodsId });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static async getByObjectId(objectId: string): Promise<Goods | null> {
|
|
118
|
+
return GoodsModel.coll.findOne({ objectId });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static async getMultiByObjectId(objectId: string) {
|
|
122
|
+
return GoodsModel.coll.find({ objectId });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static async edit(
|
|
126
|
+
goodsId: number,
|
|
127
|
+
name: string,
|
|
128
|
+
price: number,
|
|
129
|
+
num: number,
|
|
130
|
+
objectId = '',
|
|
131
|
+
purchaseModelId?: string,
|
|
132
|
+
data?: Record<string, unknown>,
|
|
133
|
+
description?: string,
|
|
134
|
+
): Promise<number> {
|
|
135
|
+
const $set: Record<string, unknown> = { name, price, num, objectId };
|
|
136
|
+
if (typeof purchaseModelId === 'string') $set.purchaseModelId = purchaseModelId;
|
|
137
|
+
if (data !== undefined) $set.data = data;
|
|
138
|
+
if (description !== undefined) $set.description = description;
|
|
139
|
+
const result = await GoodsModel.coll.updateOne(
|
|
140
|
+
{ _id: goodsId },
|
|
141
|
+
{ $set }
|
|
142
|
+
);
|
|
143
|
+
return result.modifiedCount;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static async delete(goodsId: number): Promise<number> {
|
|
147
|
+
const result = await GoodsModel.coll.deleteOne({ _id: goodsId });
|
|
148
|
+
return result.deletedCount;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static async updateStock(goodsId: number, delta: number): Promise<number> {
|
|
152
|
+
const filter: Record<string, unknown> = { _id: goodsId, num: { $gte: 0 } };
|
|
153
|
+
if (delta < 0) filter.num = { $gte: -delta };
|
|
154
|
+
const result = await GoodsModel.coll.updateOne(
|
|
155
|
+
filter,
|
|
156
|
+
{ $inc: { num: delta } }
|
|
157
|
+
);
|
|
158
|
+
return result.modifiedCount;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
global.Hydro.model.coin = CoinModel;
|
|
163
|
+
global.Hydro.model.goods = GoodsModel;
|
|
164
|
+
|
|
165
|
+
export { CoinModel, GoodsModel };
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ddj-v2/shop",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "HydroOJ shop module",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"author": "fdhscpp",
|
|
7
|
+
"license": "AGPL-3.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/ddj-v2/shop.git"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
14
|
+
"@semantic-release/github": "^12.0.6",
|
|
15
|
+
"@semantic-release/npm": "^13.1.5",
|
|
16
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
17
|
+
"semantic-release": "^25.0.3"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{% extends "layout/basic.html" %}
|
|
2
|
+
{% block content %}
|
|
3
|
+
<div class="row" data-sticky-parent>
|
|
4
|
+
<div class="medium-9 columns">
|
|
5
|
+
{% block coin_content %}{% endblock %}
|
|
6
|
+
</div>
|
|
7
|
+
<div class="medium-3 columns">
|
|
8
|
+
<div data-sticky="large">
|
|
9
|
+
<div class="section side">
|
|
10
|
+
<ol class="menu">
|
|
11
|
+
<li class="menu__item">
|
|
12
|
+
<div class="menu__link expandable">
|
|
13
|
+
<span class="icon icon-bold"></span> {{ _('硬幣管理') }}
|
|
14
|
+
</div>
|
|
15
|
+
<ol class="menu collapsed">
|
|
16
|
+
<li class="menu__item">
|
|
17
|
+
<div class="menu__link expandable">
|
|
18
|
+
<span class="icon icon-shopping-cart"></span> {{ _('使用者功能') }}
|
|
19
|
+
</div>
|
|
20
|
+
<ol class="menu collapsed">
|
|
21
|
+
<li class="menu__item" id="menu-item-coin_mall">
|
|
22
|
+
<a class="menu__link{% if page_name == 'coin_mall' %} active{% endif %}" href="{{ url('coin_mall') }}">
|
|
23
|
+
{{ _('coin_mall') }}
|
|
24
|
+
</a>
|
|
25
|
+
</li>
|
|
26
|
+
<li class="menu__item" id="menu-item-coin_myrecord">
|
|
27
|
+
<a class="menu__link{% if page_name == 'coin_myrecord' %} active{% endif %}" href="{{ url('coin_myrecord') }}">
|
|
28
|
+
{{ _('coin_myrecord') }}
|
|
29
|
+
</a>
|
|
30
|
+
</li>
|
|
31
|
+
<!-- <li class="menu__item" id="menu-item-coin_gift">
|
|
32
|
+
<a class="menu__link{% if page_name == 'coin_gift' %} active{% endif %}" href="{{ url('coin_gift') }}">
|
|
33
|
+
{{ _('coin_gift') }}
|
|
34
|
+
</a>
|
|
35
|
+
</li> -->
|
|
36
|
+
</ol>
|
|
37
|
+
</li>
|
|
38
|
+
<li class="menu__item">
|
|
39
|
+
<div class="menu__link expandable">
|
|
40
|
+
<span class="icon icon-search"></span> {{ _('查詢功能') }}
|
|
41
|
+
</div>
|
|
42
|
+
<ol class="menu collapsed">
|
|
43
|
+
<li class="menu__item" id="menu-item-coin_show">
|
|
44
|
+
<a class="menu__link{% if page_name == 'coin_show' %} active{% endif %}" href="{{ url('coin_show') }}">
|
|
45
|
+
{{ _('所有人的硬幣餘額') }}
|
|
46
|
+
</a>
|
|
47
|
+
</li>
|
|
48
|
+
<li class="menu__item" id="menu-item-coin_bill">
|
|
49
|
+
<a class="menu__link{% if page_name == 'coin_bill' and udoc._id==handler.user._id %} active{% endif %}" href="{{ url('coin_bill') }}">
|
|
50
|
+
{{ _('我的硬幣發放紀錄') }}
|
|
51
|
+
</a>
|
|
52
|
+
</li>
|
|
53
|
+
<li class="menu__item" id="menu-item-coin_all">
|
|
54
|
+
<a class="menu__link{% if page_name == 'coin_bill' and udoc._id==0 %} active{% endif %}" href="{{ url('coin_bill',query={uid:0}) }}">
|
|
55
|
+
{{ _('所有人的硬幣發放紀錄') }}
|
|
56
|
+
</a>
|
|
57
|
+
</li>
|
|
58
|
+
</ol>
|
|
59
|
+
</li>
|
|
60
|
+
{% if handler.user.hasPriv(PRIV.PRIV_SET_PERM) %}
|
|
61
|
+
<li class="menu__item">
|
|
62
|
+
<div class="menu__link expandable">
|
|
63
|
+
<span class="icon icon-setting"></span> {{ _('管理功能') }}
|
|
64
|
+
</div>
|
|
65
|
+
<ol class="menu collapsed">
|
|
66
|
+
<li class="menu__item" id="menu-item-coin_inc">
|
|
67
|
+
<a class="menu__link{% if page_name == 'coin_inc' %} active{% endif %}" href="{{ url('coin_inc') }}">
|
|
68
|
+
{{ _('coin_inc') }}
|
|
69
|
+
</a>
|
|
70
|
+
</li>
|
|
71
|
+
<li class="menu__item" id="menu-item-coin_import">
|
|
72
|
+
<a class="menu__link{% if page_name == 'coin_import' %} active{% endif %}" href="{{ url('coin_import') }}">
|
|
73
|
+
{{ _('coin_import') }}
|
|
74
|
+
</a>
|
|
75
|
+
</li>
|
|
76
|
+
<li class="menu__item" id="menu-item-coin_record">
|
|
77
|
+
<a class="menu__link{% if page_name == 'coin_record' %} active{% endif %}" href="{{ url('coin_record') }}">
|
|
78
|
+
{{ _('coin_record') }}
|
|
79
|
+
</a>
|
|
80
|
+
</li>
|
|
81
|
+
<li class="menu__item" id="menu-item-goods_manage">
|
|
82
|
+
<a class="menu__link{% if page_name == 'goods_manage' %} active{% endif %}" href="{{ url('goods_manage') }}">
|
|
83
|
+
{{ _('goods_manage') }}
|
|
84
|
+
</a>
|
|
85
|
+
</li>
|
|
86
|
+
<li class="menu__item" id="menu-item-shop_manage_entries">
|
|
87
|
+
<a class="menu__link{% if page_name == 'shop_manage_entries' %} active{% endif %}" href="{{ url('shop_manage_entries') }}">
|
|
88
|
+
{{ _('shop_manage_entries') }}
|
|
89
|
+
</a>
|
|
90
|
+
</li>
|
|
91
|
+
</ol>
|
|
92
|
+
</li>
|
|
93
|
+
{% endif %}
|
|
94
|
+
</ol>
|
|
95
|
+
</li>
|
|
96
|
+
</ol>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
{% endblock %}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{% import "components/paginator.html" as paginator with context %}
|
|
2
|
+
{% import "components/nothing.html" as nothing with context %}
|
|
3
|
+
{% extends "coin_base.html" %}
|
|
4
|
+
{% block coin_content %}
|
|
5
|
+
<div class="section">
|
|
6
|
+
<div class="section__header">
|
|
7
|
+
<h1 class="section__title">{% if udoc._id %}{{user.render_inline(udoc,badge=false) }}{% else %}{{ _('所有人') }}{% endif %}{{ _('的發放紀錄') }}</h1>
|
|
8
|
+
<div class="section__tools">
|
|
9
|
+
{% if udoc._id %}
|
|
10
|
+
目前硬幣數量: {{ udoc['coin_now']|default('0') }} (累計獲得硬幣數量:{{ udoc['coin_all']|default('0') }})
|
|
11
|
+
{% endif %}
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="section__body no-padding">
|
|
15
|
+
{% if not bills.length %}
|
|
16
|
+
{{ nothing.render('沒有硬幣紀錄!') }}
|
|
17
|
+
{% else %}
|
|
18
|
+
<table class="data-table">
|
|
19
|
+
<colgroup>
|
|
20
|
+
<col class="col--time" style="width:6rem">
|
|
21
|
+
<col class="col--uid" style="width:12rem">
|
|
22
|
+
<col class="col--uid" style="width:12rem">
|
|
23
|
+
<col class="col--amount" style="width:4rem">
|
|
24
|
+
<col class="col--text">
|
|
25
|
+
</colgroup>
|
|
26
|
+
<thead>
|
|
27
|
+
<tr>
|
|
28
|
+
<th>{{ _('Time') }}</th>
|
|
29
|
+
<th>{{ _('發放者') }}</th>
|
|
30
|
+
<th>{{ _('接收者') }}</th>
|
|
31
|
+
<th>{{ _('數額') }}</th>
|
|
32
|
+
<th>{{ _('原因') }}</th>
|
|
33
|
+
</tr>
|
|
34
|
+
</thead>
|
|
35
|
+
<tbody>
|
|
36
|
+
{%- for bill in bills -%}
|
|
37
|
+
<tr>
|
|
38
|
+
<td>{{ datetimeSpan(bill._id)|safe }}</td>
|
|
39
|
+
<td>
|
|
40
|
+
<a href="{{ url('coin_bill',query={uid:bill.rootId}) }}">
|
|
41
|
+
{{ user.render_inline(udict[bill.rootId],avatar=false,badge=false,inline=false) }}
|
|
42
|
+
</a>
|
|
43
|
+
</td>
|
|
44
|
+
<td>
|
|
45
|
+
<a href="{{ url('coin_bill',query={uid:bill.userId}) }}">
|
|
46
|
+
{{ user.render_inline(udict[bill.userId],avatar=false,badge=false,inline=false) }}
|
|
47
|
+
</a>
|
|
48
|
+
</td>
|
|
49
|
+
<td>{{ bill.amount }}</td>
|
|
50
|
+
<td>{{ bill.text }}</td>
|
|
51
|
+
</tr>
|
|
52
|
+
{%- endfor -%}
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
{{ paginator.render(page, upcount,add_qs=('uid=' + udoc._id)) }}
|
|
56
|
+
{% endif %}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
{% endblock %}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{% extends "coin_base.html" %}
|
|
2
|
+
{% block coin_content %}
|
|
3
|
+
<div class="section">
|
|
4
|
+
<div class="section__header">
|
|
5
|
+
<h1 class="section__title">{{ _('coin_exchange') }}</h1>
|
|
6
|
+
<div class="section__tools">
|
|
7
|
+
目前硬幣數量: {{ udoc['coin_now']|default('0') }}
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="section__body typo richmedia">
|
|
11
|
+
<p>{{ _('物件ID:') }}{{ goods.objectId|default('-') }}</p>
|
|
12
|
+
<p>{{ _('商品名稱:') }}{{ _(goods.name) }}</p>
|
|
13
|
+
<div>
|
|
14
|
+
<p>{{ _('商品描述:') }}</p>
|
|
15
|
+
<div class="typo richmedia">{{ goods.description|default('')|markdown|safe }}</div>
|
|
16
|
+
</div>
|
|
17
|
+
<p>{{ _('商品價格:') }}{{ goods.price}}</p>
|
|
18
|
+
<p>{{ _('商品庫存:') }}{% if goods.num < 0 %}無限{% else %}{{ goods.num }}{% endif %}</p>
|
|
19
|
+
<form method="post">
|
|
20
|
+
{{ form.form_text({
|
|
21
|
+
label:'商品數量',
|
|
22
|
+
name:'num',
|
|
23
|
+
value:'1',
|
|
24
|
+
autofocus:true,
|
|
25
|
+
required:true
|
|
26
|
+
}) }}
|
|
27
|
+
<div class="row">
|
|
28
|
+
<div class="columns">
|
|
29
|
+
<button type="submit" class="rounded primary button">
|
|
30
|
+
{{ _('兌換') }}
|
|
31
|
+
</button>
|
|
32
|
+
<button type="button" class="rounded button" onclick="window.history.go(-1)">
|
|
33
|
+
{{ _('Cancel') }}
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</form>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{% endblock %}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{% extends "coin_base.html" %}
|
|
2
|
+
{% block coin_content %}
|
|
3
|
+
<div class="section">
|
|
4
|
+
<div class="section__header">
|
|
5
|
+
<h1 class="section__title">{{ _('coin_gift') }}</h1>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="section__body">
|
|
8
|
+
<blockquote class="note typo">
|
|
9
|
+
<p>{{ _('⚠️警告:硬幣一旦送出將無法取消,請謹慎操作!') }}</p>
|
|
10
|
+
</blockquote>
|
|
11
|
+
<form method="post">
|
|
12
|
+
{{ form.form_text({
|
|
13
|
+
label:'Current Password',
|
|
14
|
+
name:'password',
|
|
15
|
+
autofocus:true,
|
|
16
|
+
required:true,
|
|
17
|
+
type:'password'
|
|
18
|
+
}) }}
|
|
19
|
+
{{ form.form_text({
|
|
20
|
+
label:'Username / UID',
|
|
21
|
+
name:'uidOrName',
|
|
22
|
+
required:true,
|
|
23
|
+
value:uidOrName
|
|
24
|
+
}) }}
|
|
25
|
+
{{ form.form_text({
|
|
26
|
+
label:'贈送數量',
|
|
27
|
+
name:'amount',
|
|
28
|
+
required:true
|
|
29
|
+
}) }}
|
|
30
|
+
<div class="row">
|
|
31
|
+
<div class="columns">
|
|
32
|
+
<button type="submit" class="rounded primary button">
|
|
33
|
+
{{ _('贈送') }}
|
|
34
|
+
</button>
|
|
35
|
+
<button type="button" class="rounded button" onclick="window.history.go(-1)">
|
|
36
|
+
{{ _('Cancel') }}
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</form>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
{% endblock %}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{% extends "coin_base.html" %}
|
|
2
|
+
{% block coin_content %}
|
|
3
|
+
<div class="section">
|
|
4
|
+
<div class="section__header">
|
|
5
|
+
<h1 class="section__title">{{ _('coin_import') }}</h1>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="section__body">
|
|
8
|
+
<blockquote class="note">
|
|
9
|
+
<p>{{ _('從 Excel 或 CSV 複製 [使用者名稱],[發放數量],[發放原因] 以批量發放硬幣。如:student1,10,reward') }}</p>
|
|
10
|
+
</blockquote>
|
|
11
|
+
{{ noscript_note.render() }}
|
|
12
|
+
<p name="messages"></p>
|
|
13
|
+
{{ form.form_textarea({
|
|
14
|
+
columns:null,
|
|
15
|
+
label:'發放硬幣',
|
|
16
|
+
name:'coins',
|
|
17
|
+
nospellcheck: true
|
|
18
|
+
}) }}
|
|
19
|
+
<div class="row"><div class="columns">
|
|
20
|
+
<button name="preview" class="rounded primary button">{{ _('Preview') }}</button>
|
|
21
|
+
<button name="submit" class="rounded button">{{ _('Submit') }}</button>
|
|
22
|
+
</div></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
{% endblock %}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{% extends "coin_base.html" %}
|
|
2
|
+
{% block coin_content %}
|
|
3
|
+
<div class="section">
|
|
4
|
+
<div class="section__header">
|
|
5
|
+
<h1 class="section__title">{{ _('coin_inc') }}</h1>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="section__body">
|
|
8
|
+
<form method="post">
|
|
9
|
+
{{ form.form_text({
|
|
10
|
+
label:'Username / UID',
|
|
11
|
+
name:'uidOrName',
|
|
12
|
+
autofocus:true,
|
|
13
|
+
required:true,
|
|
14
|
+
value:uidOrName
|
|
15
|
+
}) }}
|
|
16
|
+
{{ form.form_text({
|
|
17
|
+
label:'發放數量(負數即為扣除)',
|
|
18
|
+
name:'amount',
|
|
19
|
+
required:true
|
|
20
|
+
}) }}
|
|
21
|
+
{{ form.form_text({
|
|
22
|
+
label:'發放原因',
|
|
23
|
+
name:'text',
|
|
24
|
+
required:true
|
|
25
|
+
}) }}
|
|
26
|
+
<div class="row">
|
|
27
|
+
<div class="columns">
|
|
28
|
+
<button type="submit" class="rounded primary button">
|
|
29
|
+
{{ _('發放') }}
|
|
30
|
+
</button>
|
|
31
|
+
<button type="button" class="rounded button" onclick="window.history.go(-1)">
|
|
32
|
+
{{ _('Cancel') }}
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</form>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
{% endblock %}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{% extends "coin_base.html" %}
|
|
2
|
+
{% import "components/paginator.html" as paginator with context %}
|
|
3
|
+
{% import "components/nothing.html" as nothing with context %}
|
|
4
|
+
{% block coin_content %}
|
|
5
|
+
<div class="section">
|
|
6
|
+
<div class="section__header">
|
|
7
|
+
<h1 class="section__title">{{ _('coin_mall')}}</h1>
|
|
8
|
+
<div class="section__tools">
|
|
9
|
+
目前硬幣數量: {{ udoc['coin_now']|default('0') }}
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="section__body">
|
|
13
|
+
<form method="get" class="row">
|
|
14
|
+
<div class="medium-5 columns">
|
|
15
|
+
{{ form.form_text({
|
|
16
|
+
label:'搜尋商品',
|
|
17
|
+
name:'keyword',
|
|
18
|
+
value:keyword|default(''),
|
|
19
|
+
placeholder:'商品名 / 物件ID / 描述'
|
|
20
|
+
}) }}
|
|
21
|
+
</div>
|
|
22
|
+
<div class="medium-3 columns">
|
|
23
|
+
<label>{{ _('庫存篩選') }}</label>
|
|
24
|
+
<select name="stock">
|
|
25
|
+
<option value="all" {% if stock == 'all' %}selected{% endif %}>全部</option>
|
|
26
|
+
<option value="in" {% if stock == 'in' %}selected{% endif %}>有庫存</option>
|
|
27
|
+
<option value="out" {% if stock == 'out' %}selected{% endif %}>已售罄</option>
|
|
28
|
+
<option value="infinite" {% if stock == 'infinite' %}selected{% endif %}>無限供應</option>
|
|
29
|
+
</select>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="medium-4 columns">
|
|
32
|
+
<label>
|
|
33
|
+
<input type="checkbox" name="purchasable" value="1" {% if purchasable == '1' %}checked{% endif %}>
|
|
34
|
+
只看可購買商品(排除售罄且價格可負擔)
|
|
35
|
+
</label>
|
|
36
|
+
<button type="submit" class="rounded primary button">{{ _('搜尋') }}</button>
|
|
37
|
+
<a href="{{ url('coin_mall') }}" class="rounded button">{{ _('清除') }}</a>
|
|
38
|
+
</div>
|
|
39
|
+
</form>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="section__body no-padding">
|
|
42
|
+
{% if not ddocs.length %}
|
|
43
|
+
{{ nothing.render('抱歉,目前沒有商品。') }}
|
|
44
|
+
{% else %}
|
|
45
|
+
<div class="section__table-container">
|
|
46
|
+
<table class="data-table section__table-heade">
|
|
47
|
+
<colgroup>
|
|
48
|
+
<!-- <col class="col--id" style="width:5rem"> -->
|
|
49
|
+
<col class="col--id" style="width: 5rem">
|
|
50
|
+
<col class="col--name" style="width:12rem">
|
|
51
|
+
<col class="col--description">
|
|
52
|
+
<col class="col--price">
|
|
53
|
+
<col class="col--num">
|
|
54
|
+
<col class="col--detail">
|
|
55
|
+
</colgroup>
|
|
56
|
+
<thead>
|
|
57
|
+
<tr>
|
|
58
|
+
<!-- <th>{{ _('商品 ID') }}</th> -->
|
|
59
|
+
<th>{{ _('物件ID') }}</th>
|
|
60
|
+
<th>{{ _('商品名稱') }}</th>
|
|
61
|
+
<th>{{ _('商品描述') }}</th>
|
|
62
|
+
<th>{{ _('商品價格') }}</th>
|
|
63
|
+
<th>{{ _('商品數量') }}</th>
|
|
64
|
+
<th>{{ _('查看詳細') }}</th>
|
|
65
|
+
</tr>
|
|
66
|
+
</thead>
|
|
67
|
+
<tbody>
|
|
68
|
+
{%- for ddoc in ddocs -%}
|
|
69
|
+
<tr>
|
|
70
|
+
<!-- <td>{{ ddoc._id }}</td> -->
|
|
71
|
+
<td>{{ ddoc.objectId|default('-') }}</td>
|
|
72
|
+
<td>{{ _(ddoc.name) }}</td>
|
|
73
|
+
<td>
|
|
74
|
+
<div class="typo richmedia">{{ ddoc.description|default('')|markdown|safe }}</div>
|
|
75
|
+
</td>
|
|
76
|
+
<td>{{ ddoc.price }}</td>
|
|
77
|
+
<td>{% if ddoc.num < 0 %}無限{% else %}{{ ddoc.num }}{% endif %}</td>
|
|
78
|
+
<td>
|
|
79
|
+
<a href="{{ url('coin_exchange', id=ddoc._id) }}" class="typo-a contest-tag">{{ _('詳細') }}</a>
|
|
80
|
+
</td>
|
|
81
|
+
</tr>
|
|
82
|
+
{%- endfor -%}
|
|
83
|
+
</tbody>
|
|
84
|
+
</table>
|
|
85
|
+
{{ paginator.render(page, dpcount) }}
|
|
86
|
+
</div>
|
|
87
|
+
{% endif %}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{% endblock%}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{% import "components/paginator.html" as paginator with context %}
|
|
2
|
+
{% import "components/nothing.html" as nothing with context %}
|
|
3
|
+
{% extends "coin_base.html" %}
|
|
4
|
+
{% block coin_content %}
|
|
5
|
+
<div class="section">
|
|
6
|
+
<div class="section__header">
|
|
7
|
+
<h1 class="section__title">{{ _('coin_myrecord') }}</h1>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="section__body">
|
|
10
|
+
<form method="get" class="row">
|
|
11
|
+
<div class="medium-5 columns">
|
|
12
|
+
{{ form.form_text({
|
|
13
|
+
label:'關鍵字',
|
|
14
|
+
name:'keyword',
|
|
15
|
+
value:keyword|default(''),
|
|
16
|
+
placeholder:'依原因文字搜尋'
|
|
17
|
+
}) }}
|
|
18
|
+
</div>
|
|
19
|
+
<div class="medium-4 columns">
|
|
20
|
+
{{ form.form_text({
|
|
21
|
+
label:'物件ID',
|
|
22
|
+
name:'objectId',
|
|
23
|
+
value:objectId|default(''),
|
|
24
|
+
placeholder:'例如:badge:3'
|
|
25
|
+
}) }}
|
|
26
|
+
</div>
|
|
27
|
+
<div class="medium-3 columns">
|
|
28
|
+
<button type="submit" class="rounded primary button">{{ _('搜尋') }}</button>
|
|
29
|
+
<a href="{{ url('coin_myrecord') }}" class="rounded button">{{ _('清除') }}</a>
|
|
30
|
+
</div>
|
|
31
|
+
</form>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="section__body no-padding">
|
|
34
|
+
{% if not bills.length %}
|
|
35
|
+
{{ nothing.render('沒有兌換紀錄!') }}
|
|
36
|
+
{% else %}
|
|
37
|
+
<table class="data-table">
|
|
38
|
+
<colgroup>
|
|
39
|
+
<col class="col--time" style="width:6rem">
|
|
40
|
+
<col class="col--uid" style="width:12rem">
|
|
41
|
+
<col class="col--uid" style="width:12rem">
|
|
42
|
+
<col class="col--amount" style="width:4rem">
|
|
43
|
+
<col class="col--text">
|
|
44
|
+
</colgroup>
|
|
45
|
+
<thead>
|
|
46
|
+
<tr>
|
|
47
|
+
<th>{{ _('Time') }}</th>
|
|
48
|
+
<th>{{ _('發放者') }}</th>
|
|
49
|
+
<th>{{ _('接收者') }}</th>
|
|
50
|
+
<th>{{ _('數額') }}</th>
|
|
51
|
+
<th>{{ _('原因') }}</th>
|
|
52
|
+
</tr>
|
|
53
|
+
</thead>
|
|
54
|
+
<tbody>
|
|
55
|
+
{%- for bill in bills -%}
|
|
56
|
+
<tr data-uid="{{ udoc.uid }}">
|
|
57
|
+
<td>{{ datetimeSpan(bill._id)|safe }}</td>
|
|
58
|
+
<td>{{ user.render_inline(udict[bill.rootId],avatar=false,badge=false,inline=false) }}</td>
|
|
59
|
+
<td>
|
|
60
|
+
<a href="{{ url('coin_bill',query={uid:bill.userId}) }}">
|
|
61
|
+
{{ user.render_inline(udict[bill.userId],avatar=false,badge=false,inline=false) }}
|
|
62
|
+
</a>
|
|
63
|
+
</td>
|
|
64
|
+
<td>{{ bill.amount }}</td>
|
|
65
|
+
<td>{{ bill.text }}</td>
|
|
66
|
+
</tr>
|
|
67
|
+
{%- endfor -%}
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
{{ paginator.render(page, upcount) }}
|
|
71
|
+
{% endif %}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
{% endblock %}
|