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