fix: issues
This commit is contained in:
Binary file not shown.
@@ -2,9 +2,11 @@
|
|||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
-- add column bind_user_id to tenants
|
-- add column bind_user_id to tenants
|
||||||
ALTER TABLE tenants ADD COLUMN bind_user_id INT8 NOT NULL DEFAULT 0;
|
ALTER TABLE tenants ADD COLUMN bind_user_id INT8 NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE tenants ADD COLUMN bind_user_contact varchar(128) NOT NULL DEFAULT '';
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|
||||||
-- +goose Down
|
-- +goose Down
|
||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
DROP COLUMN bind_user_id FROM tenants;
|
ALTER TABLE tenants DROP COLUMN bind_user_id ;
|
||||||
|
ALTER TABLE tenants DROP COLUMN bind_user_contact ;
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ type Tenants struct {
|
|||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
BindUserID int64 `json:"bind_user_id"`
|
BindUserID int64 `json:"bind_user_id"`
|
||||||
|
BindUserContact string `json:"bind_user_contact"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type tenantsTable struct {
|
|||||||
CreatedAt postgres.ColumnTimestamp
|
CreatedAt postgres.ColumnTimestamp
|
||||||
UpdatedAt postgres.ColumnTimestamp
|
UpdatedAt postgres.ColumnTimestamp
|
||||||
BindUserID postgres.ColumnInteger
|
BindUserID postgres.ColumnInteger
|
||||||
|
BindUserContact postgres.ColumnString
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
@@ -73,8 +74,9 @@ func newTenantsTableImpl(schemaName, tableName, alias string) tenantsTable {
|
|||||||
CreatedAtColumn = postgres.TimestampColumn("created_at")
|
CreatedAtColumn = postgres.TimestampColumn("created_at")
|
||||||
UpdatedAtColumn = postgres.TimestampColumn("updated_at")
|
UpdatedAtColumn = postgres.TimestampColumn("updated_at")
|
||||||
BindUserIDColumn = postgres.IntegerColumn("bind_user_id")
|
BindUserIDColumn = postgres.IntegerColumn("bind_user_id")
|
||||||
allColumns = postgres.ColumnList{IDColumn, NameColumn, SlugColumn, DescriptionColumn, ExpireAtColumn, CreatedAtColumn, UpdatedAtColumn, BindUserIDColumn}
|
BindUserContactColumn = postgres.StringColumn("bind_user_contact")
|
||||||
mutableColumns = postgres.ColumnList{NameColumn, SlugColumn, DescriptionColumn, ExpireAtColumn, CreatedAtColumn, UpdatedAtColumn, BindUserIDColumn}
|
allColumns = postgres.ColumnList{IDColumn, NameColumn, SlugColumn, DescriptionColumn, ExpireAtColumn, CreatedAtColumn, UpdatedAtColumn, BindUserIDColumn, BindUserContactColumn}
|
||||||
|
mutableColumns = postgres.ColumnList{NameColumn, SlugColumn, DescriptionColumn, ExpireAtColumn, CreatedAtColumn, UpdatedAtColumn, BindUserIDColumn, BindUserContactColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return tenantsTable{
|
return tenantsTable{
|
||||||
@@ -89,6 +91,7 @@ func newTenantsTableImpl(schemaName, tableName, alias string) tenantsTable {
|
|||||||
CreatedAt: CreatedAtColumn,
|
CreatedAt: CreatedAtColumn,
|
||||||
UpdatedAt: UpdatedAtColumn,
|
UpdatedAt: UpdatedAtColumn,
|
||||||
BindUserID: BindUserIDColumn,
|
BindUserID: BindUserIDColumn,
|
||||||
|
BindUserContact: BindUserContactColumn,
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ func (c *Controller) Info(ctx fiber.Ctx) error {
|
|||||||
return errors.Wrapf(err, "get tenant: %d", claim.TenantID)
|
return errors.Wrapf(err, "get tenant: %d", claim.TenantID)
|
||||||
}
|
}
|
||||||
info.IsAdmin = tenant.BindUserID == claim.UserID
|
info.IsAdmin = tenant.BindUserID == claim.UserID
|
||||||
|
info.AdminContact = tenant.BindUserContact
|
||||||
|
|
||||||
return ctx.JSON(info)
|
return ctx.JSON(info)
|
||||||
}
|
}
|
||||||
@@ -92,3 +93,26 @@ func (c *Controller) GetChargeCodes(ctx fiber.Ctx) error {
|
|||||||
|
|
||||||
return ctx.JSON(codes)
|
return ctx.JSON(codes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BalanceHistory
|
||||||
|
func (c *Controller) BalanceHistory(ctx fiber.Ctx) error {
|
||||||
|
claim := fiber.Locals[*jwt.Claims](ctx, consts.CtxKeyClaim)
|
||||||
|
log.Debug(claim)
|
||||||
|
|
||||||
|
histories, err := c.svc.GetBalanceHistory(ctx.Context(), claim.TenantID, claim.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return errorx.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []BalanceHistory{}
|
||||||
|
for _, h := range histories {
|
||||||
|
items = append(items, BalanceHistory{
|
||||||
|
Balance: h.Balance,
|
||||||
|
Type: h.Type,
|
||||||
|
Target: h.Target,
|
||||||
|
CreatedAt: h.CreatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(items)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,20 @@
|
|||||||
package users
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"backend/pkg/pg"
|
||||||
|
)
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
AdminContact string `json:"admin_contact"`
|
||||||
Balance int64 `json:"balance"`
|
Balance int64 `json:"balance"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BalanceHistory struct {
|
||||||
|
Type pg.BalanceType `json:"type"`
|
||||||
|
Target pg.BalanceTarget `json:"target"`
|
||||||
|
Balance int64 `json:"balance"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ func (r *Router) Register(router fiber.Router) {
|
|||||||
group.Get("info", r.controller.Info)
|
group.Get("info", r.controller.Info)
|
||||||
group.Patch("charge/:code", r.controller.Charge)
|
group.Patch("charge/:code", r.controller.Charge)
|
||||||
group.Get("codes", r.controller.GetChargeCodes)
|
group.Get("codes", r.controller.GetChargeCodes)
|
||||||
|
group.Get("balance-histories", r.controller.BalanceHistory)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,3 +413,28 @@ func (svc *Service) SetTenantBindUserID(ctx context.Context, tenantID, adminUser
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBalanceHistory
|
||||||
|
func (svc *Service) GetBalanceHistory(ctx context.Context, tenantID, userID int64) ([]model.UserBalanceHistories, error) {
|
||||||
|
log := svc.log.WithField("method", "GetBalanceHistory")
|
||||||
|
|
||||||
|
tbl := table.UserBalanceHistories
|
||||||
|
stmt := tbl.
|
||||||
|
SELECT(
|
||||||
|
tbl.AllColumns,
|
||||||
|
).
|
||||||
|
WHERE(
|
||||||
|
tbl.TenantID.EQ(Int64(tenantID)).AND(tbl.UserID.EQ(Int64(userID))),
|
||||||
|
).
|
||||||
|
ORDER_BY(
|
||||||
|
tbl.CreatedAt.DESC(),
|
||||||
|
).
|
||||||
|
LIMIT(20)
|
||||||
|
log.Debug(stmt.DebugSql())
|
||||||
|
|
||||||
|
var items []model.UserBalanceHistories
|
||||||
|
if err := stmt.QueryContext(ctx, db.FromContext(ctx, svc.db), &items); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to query balance history")
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func Command() atom.Option {
|
|||||||
return atom.Command(
|
return atom.Command(
|
||||||
atom.Name("tenants"),
|
atom.Name("tenants"),
|
||||||
atom.Short("租户相关操作"),
|
atom.Short("租户相关操作"),
|
||||||
|
atom.Example("create Name --slug [slug] --contact [wechat_contact]"),
|
||||||
atom.Command(
|
atom.Command(
|
||||||
atom.Name("create"),
|
atom.Name("create"),
|
||||||
atom.Providers(defaultProviders().With(
|
atom.Providers(defaultProviders().With(
|
||||||
@@ -40,6 +41,7 @@ func Command() atom.Option {
|
|||||||
)),
|
)),
|
||||||
atom.Arguments(func(cmd *cobra.Command) {
|
atom.Arguments(func(cmd *cobra.Command) {
|
||||||
cmd.Flags().String("slug", "", "slug")
|
cmd.Flags().String("slug", "", "slug")
|
||||||
|
cmd.Flags().String("contact", "", "contact")
|
||||||
}),
|
}),
|
||||||
atom.RunE(func(cmd *cobra.Command, args []string) error {
|
atom.RunE(func(cmd *cobra.Command, args []string) error {
|
||||||
return container.Container.Invoke(func(t *tenant.Create) error {
|
return container.Container.Invoke(func(t *tenant.Create) error {
|
||||||
|
|||||||
65
frontend/src/components/BalanceHistory.vue
Normal file
65
frontend/src/components/BalanceHistory.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<van-cell-group inset title="消费历史">
|
||||||
|
<van-cell v-for="h in histories" size="large" :title="processTitle(h)" :label="parseDate(h.created_at)"
|
||||||
|
:value="h.balance + '点'" />
|
||||||
|
</van-cell-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import request from "@/utils/request";
|
||||||
|
import { defineComponent, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const histories = ref([]);
|
||||||
|
|
||||||
|
const processTitle = (h) => {
|
||||||
|
if (h.type === "charge") {
|
||||||
|
return "充值";
|
||||||
|
} else if (h.type === "consume") {
|
||||||
|
return "消费";
|
||||||
|
} else if (h.type === "refund") {
|
||||||
|
return "退款";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadHistories = () => {
|
||||||
|
request.get("/users/balance-histories").then((res) => {
|
||||||
|
histories.value = res.data;
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseDate = (timeStr) => {
|
||||||
|
const date = new Date(timeStr);
|
||||||
|
|
||||||
|
// 获取年、月、日、时、分 using UTC
|
||||||
|
const year = date.getUTCFullYear();
|
||||||
|
const month = date.getUTCMonth() + 1; // 月份是从0开始计数的,所以要加1
|
||||||
|
const day = date.getUTCDate();
|
||||||
|
const hour = date.getUTCHours();
|
||||||
|
const minute = date.getUTCMinutes();
|
||||||
|
|
||||||
|
// 按照要求格式输出
|
||||||
|
const formattedTime = `${year}年${month}月${day}日 ${hour}时${minute}分`;
|
||||||
|
return formattedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadHistories();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
histories,
|
||||||
|
processTitle,
|
||||||
|
loadHistories,
|
||||||
|
parseDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
52
frontend/src/components/ChargeCode.vue
Normal file
52
frontend/src/components/ChargeCode.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<van-cell-group inset title="充值码">
|
||||||
|
<van-cell v-for="c in codes" size="large" :title="getCodeAmountTitle(c)" :value="c.code" @click="copyCode(c)" />
|
||||||
|
</van-cell-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import request from "@/utils/request";
|
||||||
|
import { defineComponent, onMounted } from 'vue';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
contact: String,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const codes = ref([]);
|
||||||
|
|
||||||
|
const loadChargeCodes = () => {
|
||||||
|
request.get("/users/codes").then((res) => {
|
||||||
|
codes.value = res.data;
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCodeAmountTitle = (code) => {
|
||||||
|
return code.amount / 100 + " 元/ " + code.amount + " 点";
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyCode = (code) => {
|
||||||
|
navigator.clipboard.writeText(code.code).then(() => {
|
||||||
|
showSuccessToast('充值码已复制');
|
||||||
|
}).catch((err) => {
|
||||||
|
showFailToast('复制失败');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadChargeCodes();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
codes,
|
||||||
|
getCodeAmountTitle,
|
||||||
|
copyCode,
|
||||||
|
loadChargeCodes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
29
frontend/src/components/ChargeNoticeBar.vue
Normal file
29
frontend/src/components/ChargeNoticeBar.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<van-notice-bar left-icon="volume-o" :text="'购买充值码请添加客服微信:' + contact + '(点击复制微信号)'" @click="copyCode(contact)" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
contact: String,
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const copyCode = (code) => {
|
||||||
|
navigator.clipboard.writeText(code).then(() => {
|
||||||
|
showSuccessToast('客服微信已复制');
|
||||||
|
}).catch((err) => {
|
||||||
|
showFailToast('复制失败');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
copyCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<van-image width="100%" height="100" src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg" />
|
<div style="width:100%; height: 50px;background-color: #3700B3;"></div>
|
||||||
|
|
||||||
|
<charge-notice-bar v-if="user.admin_contact" :contact="user.admin_contact" />
|
||||||
<!-- 可以使用 CellGroup 作为容器 -->
|
<!-- 可以使用 CellGroup 作为容器 -->
|
||||||
<van-cell-group>
|
<van-cell-group>
|
||||||
<van-cell size="large" title="账户余额" :value="user.balance ?? '--'" />
|
<van-cell size="large" title="账户余额" :value="user.balance ?? '--'" />
|
||||||
@@ -11,28 +12,25 @@
|
|||||||
</van-field>
|
</van-field>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<van-cell-group title="充值码" v-if="user.is_admin">
|
<charge-code v-if="user.is_admin === true" />
|
||||||
<van-cell v-for="c in codes" size="large" :title="getCodeAmountTitle(c)" :value="c.code" @click="copyCode(c)" />
|
|
||||||
</van-cell-group>
|
|
||||||
|
|
||||||
|
<balance-history />
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import BalanceHistory from "@/components/BalanceHistory.vue";
|
||||||
|
import ChargeCode from "@/components/ChargeCode.vue";
|
||||||
|
import ChargeNoticeBar from "@/components/ChargeNoticeBar.vue";
|
||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
|
|
||||||
const codes = ref({});
|
|
||||||
const user = ref({});
|
const user = ref({});
|
||||||
const chargeCode = ref("")
|
const chargeCode = ref("")
|
||||||
|
|
||||||
const loadUserInfo = () => {
|
const loadUserInfo = () => {
|
||||||
request.get("/users/info").then((res) => {
|
request.get("/users/info").then((res) => {
|
||||||
user.value = res.data;
|
user.value = res.data;
|
||||||
|
|
||||||
if (user.value.is_admin) {
|
|
||||||
loadChargeCodes();
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
@@ -42,17 +40,6 @@ onMounted(() => {
|
|||||||
loadUserInfo();
|
loadUserInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadChargeCodes = () => {
|
|
||||||
if (!user.value.is_admin) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.get("/users/codes").then((res) => {
|
|
||||||
codes.value = res.data;
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmCharge = () => {
|
const confirmCharge = () => {
|
||||||
if (!chargeCode.value) {
|
if (!chargeCode.value) {
|
||||||
@@ -80,16 +67,6 @@ const confirmCharge = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCodeAmountTitle = (code) => {
|
|
||||||
return code.amount / 100 + " 元/ " + code.amount + " 点";
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyCode = (c) => {
|
|
||||||
// h5 copy c.code to clipboard
|
|
||||||
navigator.clipboard.writeText(c.code);
|
|
||||||
|
|
||||||
showSuccessToast(getCodeAmountTitle(c) + " 已复制");
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user