fix list style
This commit is contained in:
+1
-1
@@ -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
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user