fix: issues

This commit is contained in:
Rogee
2024-12-13 10:38:54 +08:00
parent 5a6b7bcdae
commit 19f445144d
13 changed files with 262 additions and 67 deletions

View File

@@ -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

View File

@@ -12,12 +12,13 @@ import (
) )
type Tenants struct { type Tenants struct {
ID int64 `sql:"primary_key" json:"id"` ID int64 `sql:"primary_key" json:"id"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
Description *string `json:"description"` Description *string `json:"description"`
ExpireAt time.Time `json:"expire_at"` ExpireAt time.Time `json:"expire_at"`
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"`
} }

View File

@@ -17,14 +17,15 @@ type tenantsTable struct {
postgres.Table postgres.Table
// Columns // Columns
ID postgres.ColumnInteger ID postgres.ColumnInteger
Name postgres.ColumnString Name postgres.ColumnString
Slug postgres.ColumnString Slug postgres.ColumnString
Description postgres.ColumnString Description postgres.ColumnString
ExpireAt postgres.ColumnTimestamp ExpireAt postgres.ColumnTimestamp
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
@@ -65,30 +66,32 @@ func newTenantsTable(schemaName, tableName, alias string) *TenantsTable {
func newTenantsTableImpl(schemaName, tableName, alias string) tenantsTable { func newTenantsTableImpl(schemaName, tableName, alias string) tenantsTable {
var ( var (
IDColumn = postgres.IntegerColumn("id") IDColumn = postgres.IntegerColumn("id")
NameColumn = postgres.StringColumn("name") NameColumn = postgres.StringColumn("name")
SlugColumn = postgres.StringColumn("slug") SlugColumn = postgres.StringColumn("slug")
DescriptionColumn = postgres.StringColumn("description") DescriptionColumn = postgres.StringColumn("description")
ExpireAtColumn = postgres.TimestampColumn("expire_at") ExpireAtColumn = postgres.TimestampColumn("expire_at")
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{
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...), Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
//Columns //Columns
ID: IDColumn, ID: IDColumn,
Name: NameColumn, Name: NameColumn,
Slug: SlugColumn, Slug: SlugColumn,
Description: DescriptionColumn, Description: DescriptionColumn,
ExpireAt: ExpireAtColumn, ExpireAt: ExpireAtColumn,
CreatedAt: CreatedAtColumn, CreatedAt: CreatedAtColumn,
UpdatedAt: UpdatedAtColumn, UpdatedAt: UpdatedAtColumn,
BindUserID: BindUserIDColumn, BindUserID: BindUserIDColumn,
BindUserContact: BindUserContactColumn,
AllColumns: allColumns, AllColumns: allColumns,
MutableColumns: mutableColumns, MutableColumns: mutableColumns,

View File

@@ -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)
}

View File

@@ -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"`
Balance int64 `json:"balance"` AdminContact string `json:"admin_contact"`
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"`
} }

View File

@@ -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)
} }

View File

@@ -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
}

View File

@@ -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 {

View 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>

View 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>

View 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>

View File

@@ -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>