fix list style

This commit is contained in:
Timi
2026-08-07 17:06:03 +08:00
parent 28b789eb86
commit 82e717e3c6
13 changed files with 130 additions and 120 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ async function detail(id: string): Promise<Store> {
});
}
async function loginStore(): Promise<Store> {
async function loginStore(): Promise<Store | undefined> {
return await axios.post(`${STORE_BASE_URI}/user`);
}
+6 -1
View File
@@ -2,6 +2,7 @@ import {createApp} from "vue";
import {createPinia} from "pinia";
import router from "@/router";
import Root from "@/Root.vue";
import {useCurrentStoreStore} from "@/store/currentStoreStore";
import "tdesign-mobile-vue/es/style/index.css";
import "timi-web/style.css";
@@ -47,7 +48,11 @@ async function bootstrap(): Promise<void> {
app.directive("role", VRole);
const userStore = useUserStore(pinia);
await userStore.loginByStorage();
const currentStoreStore = useCurrentStoreStore(pinia);
const loginUser = await userStore.loginByStorage();
if (loginUser) {
await currentStoreStore.loadAfterLogin();
}
Network.setGlobalErrorCallback((resp: ApiResponse<any>) => {
Toast({
+3
View File
@@ -44,6 +44,7 @@
<script setup lang="ts">
import {Toast} from "tdesign-mobile-vue";
import {Captcha, useUserStore} from "timi-web";
import {useCurrentStoreStore} from "@/store/currentStoreStore";
type CaptchaExpose = {
captchaId: string;
@@ -57,6 +58,7 @@ defineOptions({
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const formModel = reactive({
user: "",
password: "",
@@ -90,6 +92,7 @@ async function login(): Promise<void> {
password: formModel.password
}
});
await currentStoreStore.loadAfterLogin();
const redirect = typeof route.query.redirect === "string" ? route.query.redirect : "/management";
await router.replace(redirect);
} catch (error) {
+10 -2
View File
@@ -54,7 +54,7 @@
alt-text="客户照片"
:loading="customerPhotoUploading"
:preview="false"
@add="customerPhotoInputRef?.click"
@add="openCustomerPhotoPicker"
@clear="customerPhotoTempFileId = ''"
/>
</t-form-item>
@@ -78,7 +78,7 @@
title="登记事件照片"
v-model="recordPhotoList"
:loading="recordPhotoUploading"
@add="recordPhotoInputRef?.click"
@add="openRecordPhotoPicker"
/>
</div>
</t-form>
@@ -375,6 +375,10 @@ const customerPhotoTempFileId = ref("");
const customerPhotoInputRef = ref<HTMLInputElement>();
const customerPhotoUploading = ref(false);
function openCustomerPhotoPicker(): void {
customerPhotoInputRef.value?.click();
}
function syncCustomerPhoto(customer?: Customer): void {
customerPhotoTempFileId.value = "";
customerPhoto.value = getCustomerPhotoURL(customer);
@@ -406,6 +410,10 @@ async function handleCustomerPhotoSelected(event: Event): Promise<void> {
const recordPhotoInputRef = ref<HTMLInputElement>();
const recordPhotoUploading = ref(false);
const recordPhotoItemList = ref<QuickPhoto[]>([]);
function openRecordPhotoPicker(): void {
recordPhotoInputRef.value?.click();
}
const recordPhotoList = computed<string[]>({
get() {
return recordPhotoItemList.value.map(item => item.url);
+6 -1
View File
@@ -55,7 +55,7 @@
alt-text="客户照片"
:loading="isPhotoUploading"
:preview="false"
@add="photoInputRef?.click"
@add="openPhotoPicker"
@clear="photoTempFileId = ''"
/>
</t-form-item>
@@ -264,6 +264,10 @@ const selectedAreas = ref<BodyAreaValue>(createBodyAreaValue());
const formData = reactive<Customer>({
});
function openPhotoPicker(): void {
photoInputRef.value?.click();
}
const canSubmit = computed(() => formData.name?.trim());
async function loadDetail() {
@@ -334,6 +338,7 @@ async function doSubmit(): Promise<void> {
try {
formData.conditioningPart = selectedAreas.value;
formData.attachmentList = photoTempFileId.value ? [{
attachType: "PHOTO",
tempFileId: photoTempFileId.value
}] : undefined;
+43 -97
View File
@@ -9,53 +9,47 @@
borderless
/>
</div>
<section class="total">
<span class="gray">客户总数:</span>
<span class="text-bold" v-text="pageResult.total" />
</section>
<t-pull-down-refresh
class="list-wrapper"
v-model="isRefreshing"
class="list-refresh"
:refresh-timeout="30000"
@refresh="refresh"
@scrolltolower="fetch"
>
<section class="items">
<div
<t-cell-group class="items" theme="card">
<t-cell
class="item"
v-for="(item, index) in pageResult.list"
:key="item.id || item.code || index"
arrow
@click="router.push(`/management/customers/${item.id}`)"
>
<t-image v-if="getCustomerPhotoURL(item)" :src="getCustomerPhotoURL(item)" :alt="item.name" fit="cover" class="avatar" />
<div v-else class="avatar fallback" v-text="(item.name || item.code || '客').slice(0, 1)" />
<div class="content">
<div class="line">
<div class="name">
<h4 class="value" v-text="item.name" />
<template #left-icon>
<t-image
v-if="getCustomerPhotoURL(item)"
class="avatar"
:src="getCustomerPhotoURL(item)"
:alt="item.name"
fit="cover"
/>
<div v-else class="avatar fallback" v-text="(item.name || item.code || '客').slice(0, 1)" />
</template>
<template #title>
<div class="header">
<div class="left">
<div class="text-bold" v-text="item.name" />
<gender-icon :value="item.gender" size="1.125rem" />
</div>
<span class="meta gray" v-text="item.code || ''" />
<span class="gray" v-text="item.code || ''" />
</div>
<div class="line">
<div class="gray">
<template v-if="item.telephone">
<t-icon name="call" />
<telephone-link class="word-space" :value="item.telephone" />
</template>
</div>
<t-tag
size="small"
variant="outline"
theme="success"
:content="`${item.invitedCount} 位邀请`"
/>
</div>
</div>
</div>
<div v-if="!pageResult.list.length" class="empty-block" v-text="isLoading ? '正在加载客户' : '暂无客户数据'" />
<div v-if="pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载更多' : isNoMore ? '已经到底了' : '上拉加载更多'" />
</section>
</template>
<template #description>
<telephone-link :value="item.telephone" />
</template>
</t-cell>
</t-cell-group>
<div v-if="!pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载客户' : '暂无客户数据'" />
<div v-if="pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载更多' : isNoMore ? '已经到底了' : '上拉加载更多'" />
</t-pull-down-refresh>
<div class="bottom-actions">
<t-button
@@ -188,85 +182,37 @@ async function doExport(): Promise<void> {
}
}
.total {
display: flex;
margin-top: 1rem;
justify-content: center;
}
.list-wrapper {
padding: 1rem 0 4rem 0;
.items {
gap: .5rem;
padding: 1rem;
display: grid;
.items {
.item {
gap: .75rem;
padding: .5rem;
display: flex;
text-align: left;
background: #f7faff;
align-items: center;
border: 1px solid var(--td-border-level-1-color);
border-radius: .875rem;
justify-content: space-between;
.item {
padding: 10px;
.avatar {
flex: none;
width: 3.5rem;
height: 3.5rem;
border-radius: .5rem;
&.fallback {
color: #2563eb;
display: flex;
font-size: 1.25rem;
background: #dbeafe;
align-items: center;
font-weight: 600;
justify-content: center;
}
}
.content {
gap: .75rem;
flex: 1;
display: flex;
min-width: 0;
flex-direction: column;
justify-content: space-between;
.meta {
font-size: .875rem;
.avatar {
width: 3.5rem;
height: 3.5rem;
border-radius: .5rem;
}
.line {
.header {
gap: .5rem;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.name {
gap: .375rem;
display: flex;
align-items: center;
.value {
margin: 0;
.left {
gap: .5rem;
display: flex;
align-items: center;
}
}
}
}
}
.empty-block,
.load-state {
padding: 1rem;
color: #52607a;
text-align: center;
background: #f8fbff;
border-radius: 1rem;
}
.bottom-actions {
gap: .75rem;
left: 1rem;
+1 -7
View File
@@ -91,7 +91,7 @@
</template>
</t-swipe-cell>
</t-cell-group>
<div v-else class="empty-block" v-text="isLoading ? '正在加载登记事件记录' : '暂无登记事件记录'" />
<div v-else class="load-state" v-text="isLoading ? '正在加载登记事件记录' : '暂无登记事件记录'" />
<div v-if="pageResult.list.length" class="load-state" v-text="loadStateText" />
</t-pull-down-refresh>
</div>
@@ -595,12 +595,6 @@ onUnmounted(() => {
}
}
}
.empty-block,
.load-state {
color: #52607a;
text-align: center;
}
}
.export {
+1 -1
View File
@@ -111,7 +111,7 @@ async function prepareStoreScope(): Promise<void> {
}
const store = await currentStoreStore.loadLoginStore();
formModel.storeId = store.id || "";
formModel.storeId = store?.id || "";
}
async function fillForm(): Promise<void> {
+1 -1
View File
@@ -158,7 +158,7 @@ async function loadStoreScope(): Promise<void> {
}
const store = await currentStoreStore.loadLoginStore();
loginStoreId.value = store.id || "";
loginStoreId.value = store?.id || "";
}
async function loadData(): Promise<void> {
+1 -1
View File
@@ -154,7 +154,7 @@ async function loadStoreScope(): Promise<void> {
}
const store = await currentStoreStore.loadLoginStore();
loginStoreId.value = store.id || "";
loginStoreId.value = store?.id || "";
}
async function loadData(): Promise<void> {
+31 -6
View File
@@ -2,7 +2,8 @@ import { Toast } from "tdesign-mobile-vue";
import { useUserStore } from "timi-web";
import StoreAPI from "@/api/StoreAPI";
import type { Store } from "@/types/Store";
import { getCurrentStoreId, readStoredCurrentStoreId, setCurrentStoreId } from "@/utils/store";
import { gaoRoleCodes } from "@/utils/permission";
import { getCurrentStoreId, readStoredCurrentStoreId, setCurrentStoreId, setLoginStoreId } from "@/utils/store";
export const useCurrentStoreStore = defineStore("currentStore", () => {
const userStore = useUserStore();
@@ -11,7 +12,7 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
const loginStore = ref<Store>();
const loginStoreUserId = ref("");
const isLoading = ref(false);
let loginStoreRequest: Promise<Store> | undefined;
let loginStoreRequest: Promise<Store | undefined> | undefined;
let loginStoreRequestUserId = "";
const currentStore = computed(() => storeList.value.find(item => item.id === currentStoreId.value));
@@ -44,9 +45,9 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
}
}
async function loadLoginStore(): Promise<Store> {
async function loadLoginStore(): Promise<Store | undefined> {
const loginUserId = userStore.loginUser.user?.id || userStore.loginUser.id || "";
if (loginStore.value?.id && loginStoreUserId.value === loginUserId) {
if (loginStoreUserId.value === loginUserId && loginUserId) {
return loginStore.value;
}
@@ -64,16 +65,39 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
}
}
async function loadLoginStoreRequest(loginUserId: string): Promise<Store> {
async function loadLoginStoreRequest(loginUserId: string): Promise<Store | undefined> {
const result = await StoreAPI.loginStore();
loginStore.value = result;
loginStoreUserId.value = loginUserId;
if (!result?.id) {
loginStore.value = undefined;
return undefined;
}
loginStore.value = result;
if (result.id) {
setLoginStoreId(result.id);
selectStore(result.id);
}
return result;
}
async function loadAfterLogin(): Promise<void> {
const isGlobalManager = userStore.hasRoles(gaoRoleCodes.globalManager);
const hasStoreRole = userStore.hasRoles([gaoRoleCodes.storeManager, gaoRoleCodes.employee], "or");
if (!isGlobalManager && !hasStoreRole) {
return;
}
try {
await loadLoginStore();
} catch {
Toast.error("登录门店加载失败");
}
if (isGlobalManager) {
await loadStoreList();
}
}
function selectStore(storeId: string): void {
if (!storeId) {
return;
@@ -105,6 +129,7 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
isLoading,
loadStoreList,
loadLoginStore,
loadAfterLogin,
selectStore
};
});
+14
View File
@@ -44,3 +44,17 @@ html.theme-dark body {
text-align: center;
font-size: .875rem;
}
.avatar {
flex: none;
&.fallback {
color: #2563eb;
display: flex;
font-size: 1.25rem;
background: #dbeafe;
align-items: center;
font-weight: 600;
justify-content: center;
}
}
+12 -2
View File
@@ -3,6 +3,7 @@ import { permissionCodes } from "@/utils/permission";
import { type Page, useUserStore } from "timi-web";
export const currentStoreIdStorageKey = "gao-customer-h5:current-store-id";
export const loginStoreIdStorageKey = "gao-customer-h5:login-store-id";
type LoginUserWithStore = {
storeId?: string;
@@ -19,20 +20,29 @@ type LoginUserWithStore = {
export function getCurrentStoreId(): string {
const userStore = useUserStore();
const loginStoreId = readLoginStoreId();
if (userStore.hasPermissions(permissionCodes.store.read)) {
return readStoredCurrentStoreId() || getLoginUserStoreId();
return readStoredCurrentStoreId() || loginStoreId || getLoginUserStoreId();
}
return getLoginUserStoreId();
return loginStoreId || getLoginUserStoreId();
}
export function setCurrentStoreId(storeId: string): void {
localStorage.setItem(currentStoreIdStorageKey, storeId);
}
export function setLoginStoreId(storeId: string): void {
localStorage.setItem(loginStoreIdStorageKey, storeId);
}
export function readStoredCurrentStoreId(): string {
return localStorage.getItem(currentStoreIdStorageKey) || "";
}
export function readLoginStoreId(): string {
return localStorage.getItem(loginStoreIdStorageKey) || "";
}
export function withCurrentStoreId<T extends { storeId?: string }>(data: T): T {
return {
...data,