refactor timi-web
This commit is contained in:
+1
-1
@@ -14,13 +14,13 @@
|
||||
"dependencies": {
|
||||
"@zxing/browser": "^0.1.5",
|
||||
"@zxing/library": "^0.21.3",
|
||||
"axios": "1.15.0",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"echarts": "^6.0.0",
|
||||
"less": "^4.3.0",
|
||||
"pinia": "^3.0.2",
|
||||
"tdesign-mobile-vue": "^1.16.0",
|
||||
"timi-tdesign-mobile": "0.0.10",
|
||||
"timi-web-api": "link:E:/WebProject/timi-web-api",
|
||||
"timi-web": "link:E:/WebProject/timi-web",
|
||||
"ts-node": "^10.9.2",
|
||||
"vue": "^3.5.16",
|
||||
|
||||
Generated
+3
-95
@@ -14,9 +14,6 @@ importers:
|
||||
'@zxing/library':
|
||||
specifier: ^0.21.3
|
||||
version: 0.21.3
|
||||
axios:
|
||||
specifier: 1.15.0
|
||||
version: 1.15.0
|
||||
browser-image-compression:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
@@ -38,6 +35,9 @@ importers:
|
||||
timi-web:
|
||||
specifier: link:E:/WebProject/timi-web
|
||||
version: link:../timi-web
|
||||
timi-web-api:
|
||||
specifier: link:E:/WebProject/timi-web-api
|
||||
version: link:../timi-web-api
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@24.13.3)(typescript@5.8.3)
|
||||
@@ -789,12 +789,6 @@ packages:
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
axios@1.15.0:
|
||||
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
@@ -867,10 +861,6 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
@@ -964,10 +954,6 @@ packages:
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -1010,10 +996,6 @@ packages:
|
||||
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
esbuild@0.25.12:
|
||||
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1180,19 +1162,6 @@ packages:
|
||||
flatted@3.4.3:
|
||||
resolution: {integrity: sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==}
|
||||
|
||||
follow-redirects@1.16.0:
|
||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
form-data@4.0.6:
|
||||
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -1248,10 +1217,6 @@ packages:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1424,18 +1389,10 @@ packages:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-db@1.54.0:
|
||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.2:
|
||||
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1611,10 +1568,6 @@ packages:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
proxy-from-env@2.1.0:
|
||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
prr@1.0.1:
|
||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||
|
||||
@@ -2699,16 +2652,6 @@ snapshots:
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.15.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.6
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
@@ -2794,10 +2737,6 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@2.20.3:
|
||||
optional: true
|
||||
|
||||
@@ -2862,8 +2801,6 @@ snapshots:
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
diff@4.0.4: {}
|
||||
@@ -2898,13 +2835,6 @@ snapshots:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.4
|
||||
|
||||
esbuild@0.25.12:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.12
|
||||
@@ -3162,16 +3092,6 @@ snapshots:
|
||||
|
||||
flatted@3.4.3: {}
|
||||
|
||||
follow-redirects@1.16.0: {}
|
||||
|
||||
form-data@4.0.6:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.4
|
||||
mime-types: 2.1.35
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fresh@2.0.0: {}
|
||||
@@ -3222,10 +3142,6 @@ snapshots:
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
dependencies:
|
||||
has-symbols: 1.1.0
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
@@ -3375,14 +3291,8 @@ snapshots:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.2
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
mime-types@3.0.2:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
@@ -3550,8 +3460,6 @@ snapshots:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
prr@1.0.1:
|
||||
optional: true
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { axios, type Page, type PageResult } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { Page, PageResult } from "timi-web-api";
|
||||
import type { Customer, CustomerDailyStatView, CustomerGenderStatView, CustomerTrendStatReq } from "@/types/Customer";
|
||||
import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/utils/store";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { axios } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { CustomerChartReq } from "@/types/Customer";
|
||||
import type { StoreEventRecordCalendarView, StoreEventRecordDailyStatView } from "@/types/Store";
|
||||
import { withCurrentStoreId } from "@/utils/store";
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { axios, type Page, type PageResult } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { Page, PageResult } from "timi-web-api";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import { getCurrentStoreId, withCurrentStoreId, withCurrentStorePage } from "@/utils/store";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { axios, type PageResult } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { PageResult } from "timi-web-api";
|
||||
import type { CustomerEventRankView, EventRecord, EventRecordPage, EventRecordPageResult, EventRecordRankPage } from "@/types/EventRecord";
|
||||
import type { Customer } from "@/types/Customer.ts";
|
||||
import { withCurrentStoreId } from "@/utils/store";
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { axios } from "timi-web-api";
|
||||
import type { Notify, Page, PageResult } from "timi-web-api";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
|
||||
const BASE_URI = "/gao/notify";
|
||||
|
||||
type RecipientListOptions = {
|
||||
storeId?: string;
|
||||
};
|
||||
|
||||
async function listRecipients(
|
||||
page: Page<ManagedUser>,
|
||||
options?: RecipientListOptions
|
||||
): Promise<PageResult<ManagedUser>> {
|
||||
const equalsExample = {
|
||||
...(page.equalsExample || {})
|
||||
};
|
||||
if (options?.storeId) {
|
||||
equalsExample.storeId = options.storeId;
|
||||
} else {
|
||||
delete equalsExample.storeId;
|
||||
}
|
||||
return await axios.post(`${BASE_URI}/recipient/list`, {
|
||||
...page,
|
||||
equalsExample
|
||||
});
|
||||
}
|
||||
|
||||
async function create(notify: Notify): Promise<Notify> {
|
||||
return await axios.post(`${BASE_URI}/create`, notify);
|
||||
}
|
||||
|
||||
export default {
|
||||
listRecipients,
|
||||
create
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { axios, type LoginResponse, type Page, type PageResult, type Role } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { LoginResponse, Page, PageResult, Role } from "timi-web-api";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
import { withCurrentStoreId, withCurrentStorePage } from "@/utils/store";
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { axios, type Page, type PageResult } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { Page, PageResult } from "timi-web-api";
|
||||
import type {
|
||||
PointAccount,
|
||||
PointAccountPage,
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { axios, type Page, type PageResult } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { Page, PageResult } from "timi-web-api";
|
||||
import type { Store, StoreBusinessDay } from "@/types/Store";
|
||||
|
||||
const STORE_BASE_URI = "/gao/store";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { axios } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import StoreAPI from "@/api/StoreAPI";
|
||||
import type { CustomerDailyStatView, CustomerGenderStatView } from "@/types/Customer";
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { axios, type Page, type PageResult } from "timi-web";
|
||||
import { axios } from "timi-web-api";
|
||||
import type { Page, PageResult } from "timi-web-api";
|
||||
import type { Tag, TagApply, TagBizType } from "@/types/Tag";
|
||||
|
||||
const BASE_URI = "/gao/tag";
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import TagAPI from "@/api/TagAPI";
|
||||
import type { Tag } from "@/types/Tag";
|
||||
import { corePermissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -75,7 +75,7 @@ import PageTransition from "@/components/PageTransition.vue";
|
||||
import {useNavBarStore} from "@/store/navBarStore";
|
||||
import {useTabBarStore} from "@/store/tabBarStore";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
+4
-1
@@ -10,7 +10,9 @@ import "timi-tdesign-mobile/style.css";
|
||||
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
||||
import "@/styles/tdesign-custom.css";
|
||||
import "@/styles/global.less";
|
||||
import { type ApiResponse, configureUserStore, Network, useUserStore, VCan, VRole } from "timi-web";
|
||||
import TimiWebUI from "timi-web";
|
||||
import { configureUserStore, Network, useUserStore, VCan, VRole } from "timi-web-api";
|
||||
import type { ApiResponse } from "timi-web-api";
|
||||
import { Message } from "@/utils/message";
|
||||
import vEntranceTransition from "@/directives/entranceTransition";
|
||||
|
||||
@@ -45,6 +47,7 @@ Network.setGlobalRequestInterceptor((config: any) => {
|
||||
async function bootstrap(): Promise<void> {
|
||||
const app = createApp(Root);
|
||||
app.use(pinia);
|
||||
app.use(TimiWebUI);
|
||||
app.directive("can", VCan);
|
||||
app.directive("role", VRole);
|
||||
app.directive("entrance-transition", vEntranceTransition);
|
||||
|
||||
@@ -156,7 +156,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { PassTimeLabel, Text, useUserStore, type Attachment } from "timi-web";
|
||||
import { PassTimeLabel, Text } from "timi-web";
|
||||
import { useUserStore, type Attachment } from "timi-web-api";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import TagAPI from "@/api/TagAPI";
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type Attachment, AttachmentAPI, useUserStore } from "timi-web";
|
||||
import { type Attachment, AttachmentAPI, useUserStore } from "timi-web-api";
|
||||
import { Message } from "@/utils/message";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import TagAPI from "@/api/TagAPI";
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import CodeScanner from "@/components/CodeScanner.vue";
|
||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { type Page, type PageResult, Time, Toolkit, useUserStore } from "timi-web";
|
||||
import { Time, Toolkit } from "timi-web";
|
||||
import { type Page, type PageResult, useUserStore } from "timi-web-api";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import TagSelector from "@/components/TagSelector.vue";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ModuleCode, type Role, RoleAPI, Time, useUserStore, Text } from "timi-web";
|
||||
import { Time, Text } from "timi-web";
|
||||
import { type ModuleCode, type Role, RoleAPI, useUserStore } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
|
||||
@@ -208,7 +208,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ModuleCode, type Role, RoleAPI, Time, useUserStore } from "timi-web";
|
||||
import { Time } from "timi-web";
|
||||
import { type ModuleCode, type Role, RoleAPI, useUserStore } from "timi-web-api";
|
||||
import type { ColorObject } from "tdesign-mobile-vue";
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { type Page, type PageResult, type Role, useUserStore } from "timi-web";
|
||||
import { type Page, type PageResult, type Role, useUserStore } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Text, Time, useUserStore, type Attachment } from "timi-web";
|
||||
import { Text, Time } from "timi-web";
|
||||
import { useUserStore, type Attachment } from "timi-web-api";
|
||||
import EventRecordAPI from "@/api/EventRecordAPI";
|
||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
|
||||
@@ -190,7 +190,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AttachmentAPI, Time, type Attachment, useUserStore } from "timi-web";
|
||||
import { Time } from "timi-web";
|
||||
import { AttachmentAPI, type Attachment, useUserStore } from "timi-web-api";
|
||||
import { Message } from "@/utils/message";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
|
||||
@@ -127,7 +127,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DialogPlugin, type DropdownValue } from "tdesign-mobile-vue";
|
||||
import { Time, Toolkit, useUserStore } from "timi-web";
|
||||
import { Time, Toolkit } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import EventRecordAPI from "@/api/EventRecordAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
|
||||
@@ -126,7 +126,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type DropdownValue } from "tdesign-mobile-vue";
|
||||
import { type PageResult, Time } from "timi-web";
|
||||
import { Time } from "timi-web";
|
||||
import type { PageResult } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import EventRecordAPI from "@/api/EventRecordAPI";
|
||||
import type { CustomerEventRankView, EventRecordRankPage, EventRecordRankType } from "@/types/EventRecord";
|
||||
|
||||
@@ -141,7 +141,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { AttachmentAPI, Toolkit, useUserStore, type Attachment } from "timi-web";
|
||||
import { Toolkit } from "timi-web";
|
||||
import { AttachmentAPI, useUserStore, type Attachment } from "timi-web-api";
|
||||
import type { TdInputProps } from "tdesign-mobile-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
|
||||
@@ -103,7 +103,8 @@ import type { PointAction } from "@/types/Point";
|
||||
import { getEventNumberValuePlaceholder, resolveEventNumberValue } from "@/utils/eventValue";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { getEventRecordPhotoURL } from "@/utils/eventRecord";
|
||||
import { Storage, Time, useUserStore } from "timi-web";
|
||||
import { Storage, Time } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import { loadSourceValueConfig, type SourceValueConfig } from "@/utils/point";
|
||||
|
||||
defineOptions({
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
</div>
|
||||
<pass-time-label class="time" :timestamp="detail.sendAt" underline />
|
||||
</header>
|
||||
<section v-text="detail.content" />
|
||||
<section class="body">
|
||||
<notify-view :detail="detail" empty-text="暂无通知内容" />
|
||||
</section>
|
||||
</article>
|
||||
<t-empty
|
||||
v-else
|
||||
@@ -18,7 +20,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NotifyAPI, type NotifyDetail, PassTimeLabel } from "timi-web";
|
||||
import { NotifyView } from "timi-tdesign-mobile";
|
||||
import { PassTimeLabel } from "timi-web";
|
||||
import { NotifyAPI, type NotifyDetail } from "timi-web-api";
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NotifyAPI, Time, type NotifyDetail, type Page, type PageResult, useUserStore } from "timi-web";
|
||||
import { Time } from "timi-web";
|
||||
import { NotifyAPI, type NotifyDetail, type Page, type PageResult, useUserStore } from "timi-web-api";
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
|
||||
@@ -1,22 +1,372 @@
|
||||
<template>
|
||||
<div class="notify-management">
|
||||
<t-empty description="该功能暂不可用" />
|
||||
<div v-can="corePermissionCodes.notify.read" class="notify-management">
|
||||
<t-pull-down-refresh
|
||||
v-model="isRefreshing"
|
||||
:refresh-timeout="30000"
|
||||
@refresh="refresh"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<t-cell-group v-if="pageResult.list.length" class="items" theme="card">
|
||||
<t-swipe-cell
|
||||
v-for="item in pageResult.list"
|
||||
:key="item.id"
|
||||
class="item"
|
||||
>
|
||||
<t-cell
|
||||
:description="getPreview(item)"
|
||||
:note="getSendTime(item)"
|
||||
arrow
|
||||
hover
|
||||
@click="toDetail(item)"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<div class="icon" :class="{ scheduled: isScheduled(item) }">
|
||||
<t-icon :name="isScheduled(item) ? 'time' : 'notification'" size="1.25rem" />
|
||||
</div>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="title">
|
||||
<span v-text="getSubject(item)" />
|
||||
<t-tag size="small" :theme="getStatusTheme(item)" :content="getStatusText(item)" />
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="description">
|
||||
<span v-text="getRecipientSummary(item)" />
|
||||
<span v-if="item.readCount" v-text="`已读 ${item.readCount}`" />
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
<template #right>
|
||||
<t-button
|
||||
v-if="canCancel(item)"
|
||||
class="cancel"
|
||||
theme="primary"
|
||||
@click.stop="confirmCancel(item)"
|
||||
>取消</t-button>
|
||||
<t-button
|
||||
v-if="canDeleteNotify"
|
||||
class="delete"
|
||||
theme="danger"
|
||||
@click.stop="confirmDelete(item)"
|
||||
>删除</t-button>
|
||||
</template>
|
||||
</t-swipe-cell>
|
||||
</t-cell-group>
|
||||
<t-empty v-else-if="!isLoading" description="暂无已发送通知" />
|
||||
<div class="load-state" v-text="loadStateText" />
|
||||
</t-pull-down-refresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Time } from "timi-web";
|
||||
import { NotifyAPI, type Notify, type NotifyManagementItem, type Page, type PageResult, useUserStore } from "timi-web-api";
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { corePermissionCodes } from "@/utils/permission";
|
||||
|
||||
type NotifyTagTheme = "default" | "success" | "warning" | "primary" | "danger";
|
||||
|
||||
defineOptions({
|
||||
name: "NotifyManagement"
|
||||
});
|
||||
|
||||
const pageSize = 20;
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const canReadNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.read));
|
||||
const canCreateNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.create));
|
||||
const canDeleteNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.delete));
|
||||
const canCancelNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.cancel));
|
||||
|
||||
// ---------- 列表分页 ----------
|
||||
|
||||
const isRefreshing = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const isNoMore = ref(false);
|
||||
const page = reactive<Page<Notify>>({
|
||||
index: 0,
|
||||
size: pageSize,
|
||||
orderMap: { createdAt: "DESC" }
|
||||
});
|
||||
const pageResult = reactive<PageResult<NotifyManagementItem>>({
|
||||
total: 0,
|
||||
list: []
|
||||
});
|
||||
|
||||
const loadStateText = computed(() => {
|
||||
if (isLoading.value) {
|
||||
return "正在加载通知";
|
||||
}
|
||||
if (isNoMore.value) {
|
||||
return pageResult.list.length ? "没有更多通知" : "";
|
||||
}
|
||||
return "上拉加载更多";
|
||||
});
|
||||
|
||||
// ---------- 展示 ----------
|
||||
|
||||
function getSubject(item: NotifyManagementItem): string {
|
||||
return item.subject?.trim() || "站内通知";
|
||||
}
|
||||
|
||||
function getPreview(item: NotifyManagementItem): string {
|
||||
const text = item.content?.replace(/\s+/g, " ").trim() || "无通知内容";
|
||||
return 80 < text.length ? `${text.slice(0, 80)}...` : text;
|
||||
}
|
||||
|
||||
function isScheduled(item: NotifyManagementItem): boolean {
|
||||
return Boolean(item.sendAt && item.waitingCount && Time.now() < item.sendAt);
|
||||
}
|
||||
|
||||
function getStatusText(item: NotifyManagementItem): string {
|
||||
if (isScheduled(item)) {
|
||||
return "待发送";
|
||||
}
|
||||
const total = item.recipientCount || 0;
|
||||
if (0 < total && item.cancelCount === total) {
|
||||
return "已取消";
|
||||
}
|
||||
if (item.failCount || item.timeoutCount) {
|
||||
return "部分失败";
|
||||
}
|
||||
return "已发送";
|
||||
}
|
||||
|
||||
function getStatusTheme(item: NotifyManagementItem): NotifyTagTheme {
|
||||
if (isScheduled(item)) {
|
||||
return "warning";
|
||||
}
|
||||
if (getStatusText(item) === "已取消") {
|
||||
return "default";
|
||||
}
|
||||
if (getStatusText(item) === "部分失败") {
|
||||
return "danger";
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
|
||||
function getRecipientSummary(item: NotifyManagementItem): string {
|
||||
return `${item.recipientCount || 0} 位接收用户`;
|
||||
}
|
||||
|
||||
function getSendTime(item: NotifyManagementItem): string {
|
||||
if (isScheduled(item)) {
|
||||
return `定时 ${Time.toDateTime(item.sendAt)}`;
|
||||
}
|
||||
return Time.toPassedDateTime(item.sendAt || item.createdAt) || "-";
|
||||
}
|
||||
|
||||
function canCancel(item: NotifyManagementItem): boolean {
|
||||
return canCancelNotify.value && isScheduled(item);
|
||||
}
|
||||
|
||||
// ---------- 列表操作 ----------
|
||||
|
||||
async function loadMore(): Promise<void> {
|
||||
if (isLoading.value || isNoMore.value || !canReadNotify.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const result = await NotifyAPI.listManagement(page);
|
||||
pageResult.total = result.total || 0;
|
||||
pageResult.list.push(...(result.list || []));
|
||||
page.index += 1;
|
||||
isNoMore.value = pageResult.list.length >= pageResult.total || (result.list || []).length < page.size;
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "通知列表加载失败"));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadList(): Promise<void> {
|
||||
page.index = 0;
|
||||
pageResult.total = 0;
|
||||
pageResult.list = [];
|
||||
isNoMore.value = false;
|
||||
await loadMore();
|
||||
}
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
isRefreshing.value = true;
|
||||
try {
|
||||
await reloadList();
|
||||
} finally {
|
||||
isRefreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function toDetail(item: NotifyManagementItem): Promise<void> {
|
||||
if (!item.id) {
|
||||
return;
|
||||
}
|
||||
await router.push({
|
||||
name: "NotifyManagementDetail",
|
||||
params: { id: item.id }
|
||||
});
|
||||
}
|
||||
|
||||
async function toCreate(): Promise<void> {
|
||||
await router.push({ name: "NotifyManagementCreate" });
|
||||
}
|
||||
|
||||
async function confirmCancel(item: NotifyManagementItem): Promise<void> {
|
||||
if (!item.id || !canCancel(item)) {
|
||||
return;
|
||||
}
|
||||
const notifyId = item.id;
|
||||
const dialog = DialogPlugin.confirm({
|
||||
title: "取消定时通知",
|
||||
content: "确定取消这条尚未发送的通知吗?",
|
||||
cancelBtn: "返回",
|
||||
confirmBtn: "取消发送",
|
||||
onCancel() {
|
||||
dialog.destroy();
|
||||
},
|
||||
async onConfirm() {
|
||||
dialog.destroy();
|
||||
try {
|
||||
await NotifyAPI.managementCancel(notifyId);
|
||||
await reloadList();
|
||||
Message.success("通知已取消");
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "通知取消失败"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmDelete(item: NotifyManagementItem): Promise<void> {
|
||||
if (!item.id || !canDeleteNotify.value) {
|
||||
return;
|
||||
}
|
||||
const notifyId = item.id;
|
||||
const dialog = DialogPlugin.confirm({
|
||||
title: "删除通知",
|
||||
content: "确定删除这条通知吗?删除后收件人也将无法查看。",
|
||||
cancelBtn: "返回",
|
||||
confirmBtn: "删除",
|
||||
onCancel() {
|
||||
dialog.destroy();
|
||||
},
|
||||
async onConfirm() {
|
||||
dialog.destroy();
|
||||
try {
|
||||
await NotifyAPI.managementDelete(notifyId);
|
||||
await reloadList();
|
||||
Message.success("通知已删除");
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "通知删除失败"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 生命周期 ----------
|
||||
|
||||
onMounted(async () => {
|
||||
navBarStore.clearRight();
|
||||
if (!canReadNotify.value) {
|
||||
return;
|
||||
}
|
||||
if (canCreateNotify.value) {
|
||||
navBarStore.setRightText("新建", "var(--td-brand-color)", toCreate);
|
||||
}
|
||||
await reloadList();
|
||||
});
|
||||
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.notify-management {
|
||||
gap: .75rem;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 1rem 1rem 1rem;
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 1rem;
|
||||
|
||||
.items {
|
||||
overflow: hidden;
|
||||
|
||||
.item {
|
||||
.cancel,
|
||||
.delete {
|
||||
height: 100%;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:deep(.t-cell__title) {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.t-cell__description) {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.t-cell__note) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
color: #64748b;
|
||||
display: flex;
|
||||
background: #e2e8f0;
|
||||
align-items: center;
|
||||
border-radius: .875rem;
|
||||
justify-content: center;
|
||||
|
||||
&.scheduled {
|
||||
color: #b45309;
|
||||
background: #fef3c7;
|
||||
}
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
gap: .5rem;
|
||||
|
||||
> span:first-child {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
gap: .75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.notify-management {
|
||||
.icon {
|
||||
color: #94a3b8;
|
||||
background: #1e293b;
|
||||
|
||||
&.scheduled {
|
||||
color: #fbbf24;
|
||||
background: rgba(180, 83, 9, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
<template>
|
||||
<div v-can="corePermissionCodes.notify.create" class="notify-management-create">
|
||||
<t-empty v-if="!canCreate" description="暂无发送通知权限" />
|
||||
<t-form v-else class="form" label-align="left" label-width="5rem">
|
||||
<t-form-item label="通知标题" required-mark>
|
||||
<t-input
|
||||
v-model.trim="subject"
|
||||
borderless
|
||||
maxlength="100"
|
||||
placeholder="请输入通知标题"
|
||||
/>
|
||||
</t-form-item>
|
||||
<notify-recipient-scope-picker
|
||||
v-if="isGlobalScope"
|
||||
v-model="selectedStoreId"
|
||||
:options="storeScopeOptions"
|
||||
@update:model-value="handleStoreScopeChange"
|
||||
/>
|
||||
<notify-recipient-picker
|
||||
v-model="selectedUserIdList"
|
||||
:users="recipientList"
|
||||
:self-user="selfRecipient"
|
||||
:loading="isRecipientLoading"
|
||||
/>
|
||||
<t-form-item label="发送时间" arrow @click="openSchedulePicker">
|
||||
<div class="schedule-value">
|
||||
<span v-text="scheduleAtText || '立即发送'" />
|
||||
<t-button
|
||||
v-if="scheduleAtText"
|
||||
size="extra-small"
|
||||
content="立即发送"
|
||||
@click.stop="clearSchedule"
|
||||
/>
|
||||
</div>
|
||||
</t-form-item>
|
||||
<section class="editor-wrapper">
|
||||
<notify-editor
|
||||
ref="notifyEditorRef"
|
||||
class="editor"
|
||||
v-model="draftDetail"
|
||||
placeholder="请输入通知内容"
|
||||
/>
|
||||
</section>
|
||||
</t-form>
|
||||
</div>
|
||||
<t-popup v-model="schedulePickerVisible" placement="bottom">
|
||||
<t-date-time-picker
|
||||
v-model="scheduleDraftText"
|
||||
:start="minimumScheduleText"
|
||||
:mode="['date', 'minute']"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
title="选择发送时间"
|
||||
@cancel="schedulePickerVisible = false"
|
||||
@confirm="confirmSchedule"
|
||||
/>
|
||||
</t-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NotifyEditor, type NotifyEditorExpose } from "timi-tdesign-mobile";
|
||||
import { Time } from "timi-web";
|
||||
import { type Attachment as ApiAttachment, type NotifyContentType, type NotifyDetail, useUserStore } from "timi-web-api";
|
||||
import { Message } from "@/utils/message";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import GaoNotifyAPI from "@/api/GaoNotifyAPI";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
import { corePermissionCodes, coreRoleCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import NotifyRecipientScopePicker, { type NotifyRecipientScopeOption } from "@/pages/notify/component/NotifyRecipientScopePicker.vue";
|
||||
import NotifyRecipientPicker from "@/pages/notify/component/NotifyRecipientPicker.vue";
|
||||
|
||||
const allStoreValue = "__ALL_STORES__";
|
||||
|
||||
defineOptions({
|
||||
name: "NotifyManagementCreate"
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
const notifyEditorRef = ref<NotifyEditorExpose>();
|
||||
const isGlobalScope = computed(() => userStore.hasRoles([coreRoleCodes.admin, gaoRoleCodes.globalManager], "or"));
|
||||
const canReadRecipients = computed(() => userStore.hasPermissions([permissionCodes.user.read, permissionCodes.store.read], "or"));
|
||||
const canCreate = computed(() => userStore.hasPermissions(corePermissionCodes.notify.create) && canReadRecipients.value);
|
||||
|
||||
// ---------- 通知表单 ----------
|
||||
|
||||
const subject = ref("");
|
||||
const draftDetail = ref<NotifyDetail>({
|
||||
contentType: "TIP_TAP",
|
||||
content: "",
|
||||
attachmentList: []
|
||||
});
|
||||
const selectedUserIdList = ref<string[]>([]);
|
||||
const isSaving = ref(false);
|
||||
const selectedStoreId = ref(allStoreValue);
|
||||
|
||||
// ---------- 接收用户 ----------
|
||||
|
||||
const isRecipientLoading = ref(false);
|
||||
const recipientList = ref<ManagedUser[]>([]);
|
||||
const loginUserId = computed(() => userStore.loginUser.user?.id || userStore.loginUser.id || "");
|
||||
const selfRecipient = computed<ManagedUser | undefined>(() => {
|
||||
if (!loginUserId.value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const user = userStore.loginUser.user;
|
||||
return {
|
||||
userId: loginUserId.value,
|
||||
user: user ? {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
nick: user.nick,
|
||||
phoneNo: user.phoneNo
|
||||
} : undefined
|
||||
};
|
||||
});
|
||||
|
||||
const storeScopeOptions = computed<NotifyRecipientScopeOption[]>(() => [
|
||||
...(isGlobalScope.value ? [{ label: "所有门店", value: allStoreValue }] : []),
|
||||
...currentStoreStore.storeList
|
||||
.map(item => ({
|
||||
label: item.name || item.code || "未命名门店",
|
||||
value: item.id || ""
|
||||
}))
|
||||
.filter(item => item.value)
|
||||
]);
|
||||
|
||||
// ---------- 定时发送 ----------
|
||||
|
||||
const schedulePickerVisible = ref(false);
|
||||
const scheduleAtText = ref("");
|
||||
const scheduleDraftText = ref("");
|
||||
const minimumScheduleText = computed(() => Time.formatDateTime(new Date(Time.now() + Time.M)));
|
||||
|
||||
function getContentText(content: string): string {
|
||||
const parser = new DOMParser();
|
||||
return parser.parseFromString(content, "text/html").body.textContent?.trim() || "";
|
||||
}
|
||||
|
||||
function hasMediaContent(content: string): boolean {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(content, "text/html");
|
||||
return Boolean(doc.querySelector("img, video"));
|
||||
}
|
||||
|
||||
function getScheduleTimestamp(): number | undefined {
|
||||
if (!scheduleAtText.value) {
|
||||
return undefined;
|
||||
}
|
||||
const timestamp = new Date(scheduleAtText.value.replace(" ", "T")).getTime();
|
||||
return Number.isNaN(timestamp) ? undefined : timestamp;
|
||||
}
|
||||
|
||||
function validateForm(): boolean {
|
||||
if (!subject.value.trim()) {
|
||||
Message.warning("请输入通知标题");
|
||||
return false;
|
||||
}
|
||||
if (!selectedUserIdList.value.length) {
|
||||
Message.warning("请选择至少一位接收用户");
|
||||
return false;
|
||||
}
|
||||
const content = draftDetail.value.content || "";
|
||||
if (!getContentText(content) && !hasMediaContent(content)) {
|
||||
Message.warning("请输入通知内容");
|
||||
return false;
|
||||
}
|
||||
const scheduleAt = getScheduleTimestamp();
|
||||
if (scheduleAtText.value && (!scheduleAt || scheduleAt < Time.now())) {
|
||||
Message.warning("定时发送时间必须晚于当前时间");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function saveNotification(): Promise<void> {
|
||||
if (isSaving.value || !validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSaving.value = true;
|
||||
try {
|
||||
const editorValue = await notifyEditorRef.value?.submit();
|
||||
const detailValue = editorValue || draftDetail.value;
|
||||
const sendAt = getScheduleTimestamp();
|
||||
const contentType: NotifyContentType = detailValue.contentType === "TEXT" ? "TEXT" : "TIP_TAP";
|
||||
const detailList: NotifyDetail[] = selectedUserIdList.value.map(sendTo => ({
|
||||
msgType: "INTERNAL",
|
||||
subject: subject.value.trim(),
|
||||
content: detailValue.content || "",
|
||||
contentType,
|
||||
attachmentList: (detailValue.attachmentList || []) as unknown as ApiAttachment[],
|
||||
sendTo,
|
||||
...(sendAt ? { sendAt } : {})
|
||||
}));
|
||||
const result = await GaoNotifyAPI.create({ detailList });
|
||||
Message.success(sendAt ? "定时通知已创建" : "通知已发送");
|
||||
if (result.id) {
|
||||
await router.replace({
|
||||
name: "NotifyManagementDetail",
|
||||
params: { id: result.id }
|
||||
});
|
||||
return;
|
||||
}
|
||||
await router.replace({ name: "NotifyManagement" });
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "通知保存失败"));
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 定时发送 ----------
|
||||
|
||||
function openSchedulePicker(): void {
|
||||
scheduleDraftText.value = scheduleAtText.value || Time.formatDateTime(new Date(Time.now() + Time.M));
|
||||
schedulePickerVisible.value = true;
|
||||
}
|
||||
|
||||
function confirmSchedule(): void {
|
||||
const timestamp = new Date(scheduleDraftText.value.replace(" ", "T")).getTime();
|
||||
if (Number.isNaN(timestamp) || timestamp < Time.now()) {
|
||||
Message.warning("定时发送时间必须晚于当前时间");
|
||||
return;
|
||||
}
|
||||
scheduleAtText.value = scheduleDraftText.value;
|
||||
schedulePickerVisible.value = false;
|
||||
}
|
||||
|
||||
function clearSchedule(): void {
|
||||
scheduleAtText.value = "";
|
||||
scheduleDraftText.value = "";
|
||||
}
|
||||
|
||||
// ---------- 接收用户 ----------
|
||||
|
||||
async function loadRecipients(): Promise<void> {
|
||||
if (isRecipientLoading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
isRecipientLoading.value = true;
|
||||
try {
|
||||
const pageSize = 100;
|
||||
const list: ManagedUser[] = [];
|
||||
let pageIndex = 0;
|
||||
let total = 0;
|
||||
const storeId = selectedStoreId.value && selectedStoreId.value !== allStoreValue
|
||||
? selectedStoreId.value
|
||||
: undefined;
|
||||
while (true) {
|
||||
const result = await GaoNotifyAPI.listRecipients(
|
||||
{ index: pageIndex, size: pageSize },
|
||||
storeId ? { storeId } : undefined
|
||||
);
|
||||
const currentList = result.list || [];
|
||||
list.push(...currentList);
|
||||
total = result.total || 0;
|
||||
pageIndex += 1;
|
||||
if (!currentList.length || list.length >= total || currentList.length < pageSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const userMap = new Map<string, ManagedUser>();
|
||||
for (const item of list) {
|
||||
const userId = item.userId || item.user?.id;
|
||||
if (userId && userId !== loginUserId.value) {
|
||||
userMap.set(userId, { ...item, userId });
|
||||
}
|
||||
}
|
||||
recipientList.value = Array.from(userMap.values());
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "接收用户加载失败"));
|
||||
} finally {
|
||||
isRecipientLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStoreScopeChange(): Promise<void> {
|
||||
selectedUserIdList.value = [];
|
||||
await loadRecipients();
|
||||
}
|
||||
|
||||
// ---------- 生命周期 ----------
|
||||
|
||||
onMounted(async () => {
|
||||
navBarStore.clearRight();
|
||||
if (!canCreate.value) {
|
||||
return;
|
||||
}
|
||||
if (isGlobalScope.value) {
|
||||
await currentStoreStore.loadStoreList();
|
||||
} else {
|
||||
const loginStore = await currentStoreStore.loadLoginStore();
|
||||
selectedStoreId.value = loginStore?.id || currentStoreStore.currentStoreId || "";
|
||||
}
|
||||
navBarStore.setRightText("发送", "var(--td-brand-color)", saveNotification);
|
||||
await loadRecipients();
|
||||
});
|
||||
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.notify-management-create {
|
||||
min-height: 100%;
|
||||
padding-top: var(--app-nav-height);
|
||||
|
||||
.form {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
|
||||
.schedule-value {
|
||||
gap: .5rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
|
||||
.editor {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:deep(.tp-editor) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:deep(.content) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:deep(.toolbar) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
top: var(--app-nav-height);
|
||||
z-index: 10;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
:deep(.toolbar .scroll) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
color: #64748b;
|
||||
margin: 0 1rem;
|
||||
font-size: .8125rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.notify-management-create {
|
||||
.tip {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<div v-can="corePermissionCodes.notify.read" class="notify-management-detail">
|
||||
<t-empty
|
||||
v-if="!detail"
|
||||
:description="isLoading ? '正在加载通知' : '通知不存在'"
|
||||
/>
|
||||
<template v-else>
|
||||
<article class="article">
|
||||
<header class="header">
|
||||
<h1 v-text="getSubject(detail)" />
|
||||
<div class="time" v-text="getSendTime(detail.detailList?.[0])" />
|
||||
</header>
|
||||
<section class="body">
|
||||
<notify-view
|
||||
v-if="mainDetail"
|
||||
:detail="mainDetail"
|
||||
empty-text="暂无通知内容"
|
||||
/>
|
||||
<div v-else class="empty-content">暂无通知内容</div>
|
||||
</section>
|
||||
</article>
|
||||
<t-cell-group title="通知状态" theme="card">
|
||||
<t-cell title="接收用户" :note="`${detail.detailList?.length || 0} 人`" />
|
||||
<t-cell title="已读用户" :note="`${readCount} 人`" />
|
||||
<t-cell title="当前状态">
|
||||
<template #note>
|
||||
<t-tag size="small" :theme="getSummaryTheme" :content="getSummaryStatus" />
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<div v-if="canCancelNotify && canCancel" class="actions">
|
||||
<t-button theme="primary" variant="outline" block :loading="isCanceling" @click="confirmCancel">
|
||||
取消发送
|
||||
</t-button>
|
||||
</div>
|
||||
<div v-if="canDeleteNotify" class="actions">
|
||||
<t-button theme="danger" variant="outline" block :loading="isDeleting" @click="confirmDelete">
|
||||
删除通知
|
||||
</t-button>
|
||||
</div>
|
||||
<t-segmented
|
||||
v-model="recipientFilter"
|
||||
class="filter"
|
||||
block
|
||||
:options="recipientFilterOptions"
|
||||
/>
|
||||
<t-cell-group v-if="filteredDetailList.length" title="发送用户" theme="card" class="recipients">
|
||||
<t-cell v-for="item in filteredDetailList" :key="item.id">
|
||||
<template #title>
|
||||
<div class="recipient-title">
|
||||
<span v-text="getRecipientName(item)" />
|
||||
<t-tag size="small" :theme="getReadTheme(item)" :content="getReadText(item)" />
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="recipient-description">
|
||||
<span v-text="getRecipientContact(item)" />
|
||||
<span v-if="item.resultDesc" v-text="item.resultDesc" />
|
||||
</div>
|
||||
</template>
|
||||
<template #note>
|
||||
<span v-text="getReadTime(item)" />
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<t-empty v-else description="暂无匹配的接收用户" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NotifyView } from "timi-tdesign-mobile";
|
||||
import { Time } from "timi-web";
|
||||
import { NotifyAPI, type Notify, type NotifyDetail, useUserStore } from "timi-web-api";
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { corePermissionCodes } from "@/utils/permission";
|
||||
|
||||
type RecipientFilter = "all" | "unread" | "read";
|
||||
type NotifyTagTheme = "default" | "success" | "warning" | "primary" | "danger";
|
||||
|
||||
defineOptions({
|
||||
name: "NotifyManagementDetail"
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const canDeleteNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.delete));
|
||||
const canCancelNotify = computed(() => userStore.hasPermissions(corePermissionCodes.notify.cancel));
|
||||
|
||||
// ---------- 通知数据 ----------
|
||||
|
||||
const detail = ref<Notify>();
|
||||
const isLoading = ref(false);
|
||||
const isCanceling = ref(false);
|
||||
const isDeleting = ref(false);
|
||||
const recipientFilter = ref<RecipientFilter>("all");
|
||||
let loadToken = 0;
|
||||
|
||||
const recipientFilterOptions = [
|
||||
{ value: "all" as RecipientFilter, label: "全部" },
|
||||
{ value: "unread" as RecipientFilter, label: "未读" },
|
||||
{ value: "read" as RecipientFilter, label: "已读" }
|
||||
];
|
||||
const mainDetail = computed(() => detail.value?.detailList?.[0]);
|
||||
const readCount = computed(() => detail.value?.detailList?.filter(item => Boolean(item.readAt)).length || 0);
|
||||
const filteredDetailList = computed(() => {
|
||||
const detailList = detail.value?.detailList || [];
|
||||
if (recipientFilter.value === "unread") {
|
||||
return detailList.filter(item => !item.readAt);
|
||||
}
|
||||
if (recipientFilter.value === "read") {
|
||||
return detailList.filter(item => Boolean(item.readAt));
|
||||
}
|
||||
return detailList;
|
||||
});
|
||||
const canCancel = computed(() => detail.value?.detailList?.some(item => isCancelable(item)) || false);
|
||||
const getSummaryStatus = computed(() => {
|
||||
const detailList = detail.value?.detailList || [];
|
||||
if (canCancel.value) {
|
||||
return "待发送";
|
||||
}
|
||||
if (detailList.length && detailList.every(item => item.status === "CANCEL")) {
|
||||
return "已取消";
|
||||
}
|
||||
if (detailList.some(item => item.status === "FAIL" || item.status === "TIMEOUT")) {
|
||||
return "部分失败";
|
||||
}
|
||||
return "已发送";
|
||||
});
|
||||
const getSummaryTheme = computed<NotifyTagTheme>(() => {
|
||||
if (getSummaryStatus.value === "待发送") {
|
||||
return "warning";
|
||||
}
|
||||
if (getSummaryStatus.value === "已取消") {
|
||||
return "default";
|
||||
}
|
||||
if (getSummaryStatus.value === "部分失败") {
|
||||
return "danger";
|
||||
}
|
||||
return "success";
|
||||
});
|
||||
|
||||
async function loadDetail(): Promise<void> {
|
||||
const requestToken = loadToken + 1;
|
||||
loadToken = requestToken;
|
||||
detail.value = undefined;
|
||||
navBarStore.clearRight();
|
||||
const notifyId = String(route.params.id || "");
|
||||
if (!notifyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const result = await NotifyAPI.managementDetail(notifyId);
|
||||
if (requestToken !== loadToken) {
|
||||
return;
|
||||
}
|
||||
detail.value = result;
|
||||
setRightAction();
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "通知详情加载失败"));
|
||||
} finally {
|
||||
if (requestToken === loadToken) {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setRightAction(): void {
|
||||
if (detail.value?.id && canDeleteNotify.value) {
|
||||
navBarStore.setRightText("删除", "var(--td-error-color)", confirmDelete);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 通知操作 ----------
|
||||
|
||||
function isCancelable(item: NotifyDetail): boolean {
|
||||
return item.status === "WAITING" && Boolean(item.sendAt && Time.now() < item.sendAt);
|
||||
}
|
||||
|
||||
async function confirmCancel(): Promise<void> {
|
||||
const notifyId = detail.value?.id;
|
||||
if (!notifyId || !canCancelNotify.value || !canCancel.value || isCanceling.value) {
|
||||
return;
|
||||
}
|
||||
const dialog = DialogPlugin.confirm({
|
||||
title: "取消定时通知",
|
||||
content: "确定取消这条尚未发送的通知吗?",
|
||||
cancelBtn: "返回",
|
||||
confirmBtn: "取消发送",
|
||||
onCancel() {
|
||||
dialog.destroy();
|
||||
},
|
||||
async onConfirm() {
|
||||
dialog.destroy();
|
||||
isCanceling.value = true;
|
||||
try {
|
||||
await NotifyAPI.managementCancel(notifyId);
|
||||
await loadDetail();
|
||||
Message.success("通知已取消");
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "通知取消失败"));
|
||||
} finally {
|
||||
isCanceling.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmDelete(): Promise<void> {
|
||||
const notifyId = detail.value?.id;
|
||||
if (!notifyId || !canDeleteNotify.value || isDeleting.value) {
|
||||
return;
|
||||
}
|
||||
const dialog = DialogPlugin.confirm({
|
||||
title: "删除通知",
|
||||
content: "确定删除这条通知吗?删除后收件人也将无法查看。",
|
||||
cancelBtn: "返回",
|
||||
confirmBtn: "删除",
|
||||
onCancel() {
|
||||
dialog.destroy();
|
||||
},
|
||||
async onConfirm() {
|
||||
dialog.destroy();
|
||||
isDeleting.value = true;
|
||||
navBarStore.clearRight();
|
||||
try {
|
||||
await NotifyAPI.managementDelete(notifyId);
|
||||
Message.success("通知已删除");
|
||||
await router.replace({ name: "NotifyManagement" });
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "通知删除失败"));
|
||||
setRightAction();
|
||||
} finally {
|
||||
isDeleting.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 展示 ----------
|
||||
|
||||
function getSubject(item: Notify): string {
|
||||
return item.detailList?.[0]?.subject?.trim() || "站内通知";
|
||||
}
|
||||
|
||||
function getSendTime(item?: NotifyDetail): string {
|
||||
if (!item?.sendAt) {
|
||||
return "-";
|
||||
}
|
||||
if (isCancelable(item)) {
|
||||
return `定时 ${Time.toDateTime(item.sendAt)}`;
|
||||
}
|
||||
return Time.toDateTime(item.sendAt);
|
||||
}
|
||||
|
||||
function getRecipientName(item: NotifyDetail): string {
|
||||
return item.toUser?.nick?.trim() || item.toUser?.name?.trim() || item.sendTo || "未知用户";
|
||||
}
|
||||
|
||||
function getRecipientContact(item: NotifyDetail): string {
|
||||
return item.toUser?.phoneNo || item.sendTo || "-";
|
||||
}
|
||||
|
||||
function getReadText(item: NotifyDetail): string {
|
||||
if (isCancelable(item)) {
|
||||
return "待发送";
|
||||
}
|
||||
if (item.status === "CANCEL") {
|
||||
return "已取消";
|
||||
}
|
||||
if (item.status === "FAIL" || item.status === "TIMEOUT") {
|
||||
return item.status === "TIMEOUT" ? "已超时" : "发送失败";
|
||||
}
|
||||
return item.readAt ? "已读" : "未读";
|
||||
}
|
||||
|
||||
function getReadTheme(item: NotifyDetail): NotifyTagTheme {
|
||||
if (isCancelable(item)) {
|
||||
return "warning";
|
||||
}
|
||||
if (item.status === "CANCEL" || item.status === "FAIL" || item.status === "TIMEOUT") {
|
||||
return "danger";
|
||||
}
|
||||
return item.readAt ? "primary" : "default";
|
||||
}
|
||||
|
||||
function getReadTime(item: NotifyDetail): string {
|
||||
return item.readAt ? `已读 ${Time.toDateTime(item.readAt)}` : "未读";
|
||||
}
|
||||
|
||||
// ---------- 生命周期 ----------
|
||||
|
||||
watch(() => route.params.id, async () => {
|
||||
await loadDetail();
|
||||
}, { immediate: true });
|
||||
|
||||
onMounted(() => navBarStore.clearRight());
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.notify-management-detail {
|
||||
gap: 1rem;
|
||||
min-height: 100%;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.article {
|
||||
padding: 1.25rem 1rem;
|
||||
margin: 0 1rem;
|
||||
background: var(--td-bg-color-container);
|
||||
box-shadow: var(--td-shadow-1);
|
||||
border-radius: 1.4rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, .2);
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #64748b;
|
||||
margin-top: .5rem;
|
||||
font-size: .8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
color: #334155;
|
||||
padding-top: 1rem;
|
||||
font-size: .9375rem;
|
||||
line-height: 1.8;
|
||||
word-break: break-word;
|
||||
|
||||
.empty-content {
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.filter {
|
||||
width: calc(100% - 2rem);
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.recipients {
|
||||
:deep(.t-cell__title) {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.t-cell__description) {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.recipient-title,
|
||||
.recipient-description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.recipient-title {
|
||||
gap: .5rem;
|
||||
|
||||
> span:first-child {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.recipient-description {
|
||||
gap: .75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.notify-management-detail {
|
||||
.header {
|
||||
.time {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
color: #cbd5e1;
|
||||
|
||||
.empty-content {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<t-form-item
|
||||
label="接收用户"
|
||||
required-mark
|
||||
:arrow="true"
|
||||
@click="openPicker"
|
||||
>
|
||||
<div class="selected-text" v-text="selectedText" />
|
||||
</t-form-item>
|
||||
<t-popup v-model="visible" placement="bottom" destroy-on-close>
|
||||
<div class="recipient-picker">
|
||||
<header class="picker-header">
|
||||
<t-button variant="text" size="small" @click="cancelPicker">取消</t-button>
|
||||
<h3>选择接收用户</h3>
|
||||
<t-button theme="primary" variant="text" size="small" @click="confirmPicker">确定</t-button>
|
||||
</header>
|
||||
<div class="search-wrapper">
|
||||
<t-input
|
||||
v-model.trim="keyword"
|
||||
class="search"
|
||||
borderless
|
||||
clearable
|
||||
placeholder="搜索姓名、昵称或手机号"
|
||||
/>
|
||||
</div>
|
||||
<div class="selection-actions">
|
||||
<span class="selection-count" v-text="`已选择 ${selectedUserIdList.length} 人`" />
|
||||
<t-button
|
||||
variant="text"
|
||||
size="small"
|
||||
:disabled="!filteredIdList.length"
|
||||
@click="toggleSelectAll"
|
||||
>
|
||||
<span v-text="allFilteredSelected ? '取消全选' : '全选当前结果'" />
|
||||
</t-button>
|
||||
</div>
|
||||
<div class="recipient-list">
|
||||
<t-checkbox-group
|
||||
v-if="filteredList.length"
|
||||
v-model="selectedUserIdList"
|
||||
class="recipient-options"
|
||||
>
|
||||
<t-checkbox
|
||||
v-for="item in filteredList"
|
||||
:key="item.userId"
|
||||
:value="item.userId"
|
||||
class="recipient-option"
|
||||
borderless
|
||||
>
|
||||
<div class="recipient-info">
|
||||
<span class="recipient-name" v-text="getRecipientName(item)" />
|
||||
<span class="recipient-phone" v-text="item.user?.phoneNo || item.userId" />
|
||||
</div>
|
||||
</t-checkbox>
|
||||
</t-checkbox-group>
|
||||
<t-empty
|
||||
v-else
|
||||
:description="loading ? '正在加载用户' : '暂无匹配用户'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</t-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: string[];
|
||||
users: ManagedUser[];
|
||||
selfUser?: ManagedUser;
|
||||
loading?: boolean;
|
||||
}>(), {
|
||||
loading: false
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: string[]];
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const keyword = ref("");
|
||||
const draftSelectedUserIdList = ref<string[]>([]);
|
||||
|
||||
const selectedUserIdList = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit("update:modelValue", value)
|
||||
});
|
||||
|
||||
const availableList = computed(() => {
|
||||
const selfUserId = props.selfUser?.userId;
|
||||
const userList = selfUserId
|
||||
? props.users.filter(item => item.userId !== selfUserId)
|
||||
: props.users;
|
||||
return props.selfUser ? [props.selfUser, ...userList] : userList;
|
||||
});
|
||||
|
||||
const filteredList = computed(() => {
|
||||
const normalizedKeyword = keyword.value.toLowerCase();
|
||||
if (!normalizedKeyword) {
|
||||
return availableList.value;
|
||||
}
|
||||
|
||||
return availableList.value.filter(item => {
|
||||
const user = item.user;
|
||||
return [item.userId, user?.name, user?.nick, user?.phoneNo]
|
||||
.some(value => Boolean(value?.toLowerCase().includes(normalizedKeyword)));
|
||||
});
|
||||
});
|
||||
|
||||
const filteredIdList = computed(() => filteredList.value
|
||||
.map(item => item.userId)
|
||||
.filter((item): item is string => Boolean(item)));
|
||||
const allFilteredSelected = computed(() => 0 < filteredIdList.value.length
|
||||
&& filteredIdList.value.every(id => selectedUserIdList.value.includes(id)));
|
||||
const selectedText = computed(() => selectedUserIdList.value.length
|
||||
? `已选择 ${selectedUserIdList.value.length} 位用户`
|
||||
: "请选择接收用户");
|
||||
|
||||
function getRecipientName(item: ManagedUser): string {
|
||||
if (item.userId === props.selfUser?.userId) {
|
||||
return `自己(${item.user?.nick || item.user?.name || "当前账号"})`;
|
||||
}
|
||||
return item.user?.nick || item.user?.name || item.userId || "未命名用户";
|
||||
}
|
||||
|
||||
function openPicker(): void {
|
||||
draftSelectedUserIdList.value = [...selectedUserIdList.value];
|
||||
keyword.value = "";
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function cancelPicker(): void {
|
||||
selectedUserIdList.value = [...draftSelectedUserIdList.value];
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function confirmPicker(): void {
|
||||
draftSelectedUserIdList.value = [...selectedUserIdList.value];
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function toggleSelectAll(): void {
|
||||
const filteredIdSet = new Set(filteredIdList.value);
|
||||
if (allFilteredSelected.value) {
|
||||
selectedUserIdList.value = selectedUserIdList.value.filter(id => !filteredIdSet.has(id));
|
||||
return;
|
||||
}
|
||||
|
||||
selectedUserIdList.value = Array.from(new Set([
|
||||
...selectedUserIdList.value,
|
||||
...filteredIdList.value
|
||||
]));
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.selected-text {
|
||||
color: #52607a;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.recipient-picker {
|
||||
display: flex;
|
||||
height: 80vh;
|
||||
padding-top: 1rem;
|
||||
background: var(--td-bg-color-container);
|
||||
flex-direction: column;
|
||||
|
||||
.picker-header {
|
||||
padding: 0 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: 4rem 1fr 4rem;
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.search-wrapper {
|
||||
padding: 1rem 0 .5rem;
|
||||
box-shadow: 0 4px 4px rgba(0, 0, 0, .05);
|
||||
|
||||
.search {
|
||||
margin: 0 1rem;
|
||||
border: 1px solid var(--td-component-border);
|
||||
border-radius: .875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.selection-actions {
|
||||
padding: .25rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.selection-count {
|
||||
color: #64748b;
|
||||
font-size: .8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.recipient-list {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
padding: 0 1rem env(safe-area-inset-bottom, 0);
|
||||
|
||||
.recipient-options {
|
||||
display: grid;
|
||||
|
||||
.recipient-option {
|
||||
padding: .75rem 0;
|
||||
border-bottom: 1px solid var(--td-component-border);
|
||||
|
||||
.recipient-info {
|
||||
gap: .25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.recipient-name {
|
||||
font-size: .9375rem;
|
||||
}
|
||||
|
||||
.recipient-phone {
|
||||
color: #64748b;
|
||||
font-size: .75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.selected-text {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.selection-count,
|
||||
.recipient-phone {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<t-form-item label="用户范围" arrow @click="openPicker">
|
||||
<div class="scope-text" v-text="selectedLabel" />
|
||||
</t-form-item>
|
||||
<t-popup v-model="visible" placement="bottom" destroy-on-close>
|
||||
<div class="scope-picker">
|
||||
<header class="picker-header">
|
||||
<t-button variant="text" size="small" @click="cancelPicker">取消</t-button>
|
||||
<h3>选择用户范围</h3>
|
||||
<t-button theme="primary" variant="text" size="small" @click="confirmPicker">确定</t-button>
|
||||
</header>
|
||||
<t-radio-group v-model="draftValue" class="scope-options">
|
||||
<t-radio
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:block="false"
|
||||
borderless
|
||||
:value="item.value"
|
||||
>
|
||||
<span v-text="item.label" />
|
||||
</t-radio>
|
||||
</t-radio-group>
|
||||
</div>
|
||||
</t-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
export type NotifyRecipientScopeOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string;
|
||||
options: NotifyRecipientScopeOption[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: string];
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const draftValue = ref(props.modelValue);
|
||||
const selectedLabel = computed(() => props.options.find(item => item.value === props.modelValue)?.label || "请选择用户范围");
|
||||
|
||||
function openPicker(): void {
|
||||
draftValue.value = props.modelValue;
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function cancelPicker(): void {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function confirmPicker(): void {
|
||||
emit("update:modelValue", draftValue.value);
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.scope-text {
|
||||
color: #52607a;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scope-picker {
|
||||
padding: 1rem 1rem env(safe-area-inset-bottom, 0);
|
||||
background: var(--td-bg-color-container);
|
||||
|
||||
.picker-header {
|
||||
display: grid;
|
||||
grid-template-columns: 4rem 1fr 4rem;
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.scope-options {
|
||||
gap: .5rem;
|
||||
margin-top: 1rem;
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.scope-text {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -32,7 +32,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type Role as UserRole, RoleAPI, useUserStore } from "timi-web";
|
||||
import { type Role as UserRole, RoleAPI, useUserStore } from "timi-web-api";
|
||||
import type { Permission } from "@/store/permissionSettingsStore";
|
||||
import { moduleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { corePermissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import type { Role } from "@/store/permissionSettingsStore";
|
||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
import { type Permission, useUserStore } from "timi-web";
|
||||
import { type Permission, useUserStore } from "timi-web-api";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import type { Role } from "@/store/permissionSettingsStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
import { type Permission, useUserStore } from "timi-web";
|
||||
import { type Permission, useUserStore } from "timi-web-api";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type PageResult, useUserStore } from "timi-web";
|
||||
import { type PageResult, useUserStore } from "timi-web-api";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import type { PointAccount, PointAccountPage } from "@/types/Point";
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import type { PointAction } from "@/types/Point";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
|
||||
@@ -73,7 +73,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PassTimeLabel, Text, useUserStore } from "timi-web";
|
||||
import { PassTimeLabel, Text } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import EventRecordAPI from "@/api/EventRecordAPI";
|
||||
|
||||
@@ -156,7 +156,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DropdownValue } from "tdesign-mobile-vue";
|
||||
import { Time, type PageResult, useUserStore } from "timi-web";
|
||||
import { Time } from "timi-web";
|
||||
import { type PageResult, useUserStore } from "timi-web-api";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import CodeScanner from "@/components/CodeScanner.vue";
|
||||
|
||||
@@ -75,7 +75,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { Text, Time, useUserStore } from "timi-web";
|
||||
import { Text, Time } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
|
||||
@@ -177,7 +177,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Time, useUserStore } from "timi-web";
|
||||
import { Time } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import PointAPI from "@/api/PointAPI";
|
||||
import PointRuleTierEditor from "@/pages/point/component/PointRuleTierEditor.vue";
|
||||
|
||||
@@ -67,7 +67,7 @@ import type { EventItem } from "@/types/EventItem";
|
||||
import type { NavBarAction } from "@/types/NavBarAction";
|
||||
import type { PointRule, PointRuleStatus } from "@/types/Point";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
import { type Page, type PageResult, useUserStore } from "timi-web";
|
||||
import { type Page, type PageResult, useUserStore } from "timi-web-api";
|
||||
import { Message } from "@/utils/message.ts";
|
||||
|
||||
defineOptions({
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type CalendarProps, DialogPlugin, type TDate } from "tdesign-mobile-vue";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import StoreAPI from "@/api/StoreAPI";
|
||||
import type { Store, StoreBusinessDay, StoreBusinessDayStatus } from "@/types/Store";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import StoreAPI from "@/api/StoreAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { Time, Toolkit, type Page, type PageResult, useUserStore } from "timi-web";
|
||||
import { Time, Toolkit } from "timi-web";
|
||||
import { type Page, type PageResult, useUserStore } from "timi-web-api";
|
||||
import StoreAPI from "@/api/StoreAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { Store } from "@/types/Store";
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NotifyAPI, useUserStore } from "timi-web";
|
||||
import { NotifyAPI, useUserStore } from "timi-web-api";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { corePermissionCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ let settingTabScrollTop: number | undefined;
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
import { type Role as UserRole, RoleAPI, UserAPI, useUserStore } from "timi-web";
|
||||
import { type Role as UserRole, RoleAPI, UserAPI, useUserStore } from "timi-web-api";
|
||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||
import { moduleCode } from "@/store/permissionSettingsStore";
|
||||
import { corePermissionCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
@@ -221,7 +221,7 @@ const menuGroupList = computed<MenuGroup[]>(() => [
|
||||
icon: "notification",
|
||||
path: "/management/notifications",
|
||||
title: "通知管理",
|
||||
visible: true
|
||||
visible: userStore.hasPermissions(corePermissionCodes.notify.create)
|
||||
},
|
||||
{
|
||||
icon: "qrcode",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { gaoRoleCodes, gaoStoreScopePermissionCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { type Page, useUserStore } from "timi-web";
|
||||
import { type Page, useUserStore } from "timi-web-api";
|
||||
import TagAPI from "@/api/TagAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { Tag } from "@/types/Tag";
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { Captcha, NotifyAPI, useUserStore, type LoginResponse, Time } from "timi-web";
|
||||
import { Captcha, Time } from "timi-web";
|
||||
import { NotifyAPI, useUserStore, type LoginResponse } from "timi-web-api";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import Logo from "@/pages/user/component/Logo.vue";
|
||||
|
||||
@@ -56,14 +56,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { PassTimeLabel, type User, UserAPI } from "timi-web";
|
||||
import { PassTimeLabel } from "timi-web";
|
||||
import { type User, UserAPI, useUserStore } from "timi-web-api";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||
import { buildEffectiveRoleCodeSet, getManagedUserRoleList, getRoleDisplayName } from "@/utils/role";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { type Page, type PageResult, UserAPI, type User, useUserStore } from "timi-web";
|
||||
import { type Page, type PageResult, UserAPI, type User, useUserStore } from "timi-web-api";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { UserAPI, useUserStore } from "timi-web";
|
||||
import { UserAPI, useUserStore } from "timi-web-api";
|
||||
|
||||
defineOptions({
|
||||
name: "UserPassword"
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||
import { AttachmentAPI, type Gender, UserAPI, useUserStore } from "timi-web";
|
||||
import { AttachmentAPI, type Gender, UserAPI, useUserStore } from "timi-web-api";
|
||||
import PhotoField from "@/components/PhotoField.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
||||
import { Message } from "@/utils/message";
|
||||
import { RoleAPI, type ModuleCode, type Role as UserRole, useUserStore } from "timi-web";
|
||||
import { RoleAPI, type ModuleCode, type Role as UserRole, useUserStore } from "timi-web-api";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { Captcha, useUserStore } from "timi-web";
|
||||
import { Captcha } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
|
||||
type CaptchaExpose = {
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from "@/utils/message";
|
||||
import { Captcha, NotifyAPI, UserAPI, useUserStore } from "timi-web";
|
||||
import { Captcha } from "timi-web";
|
||||
import { NotifyAPI, UserAPI, useUserStore } from "timi-web-api";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
|
||||
type CaptchaExpose = {
|
||||
|
||||
@@ -48,7 +48,7 @@ import { Message } from "@/utils/message";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { coreRoleCodes, gaoRoleCodes } from "@/utils/permission";
|
||||
import { RoleAPI, type Role, useUserStore } from "timi-web";
|
||||
import { RoleAPI, type Role, useUserStore } from "timi-web-api";
|
||||
|
||||
defineOptions({ name: "UserInvite" });
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import notifyRoutes from "@/router/routes/notifyRoutes";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useGlobalUIStore } from "@/store/globalUI";
|
||||
import { gaoRoleCodes } from "@/utils/permission";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
|
||||
const detailRoutes = [
|
||||
...customerRoutes,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
import AppSettings from "@/pages/setting/AppSettings.vue";
|
||||
import NotifyManagement from "@/pages/notify/NotifyManagement.vue";
|
||||
import NotifyManagementCreate from "@/pages/notify/NotifyManagementCreate.vue";
|
||||
import NotifyManagementDetail from "@/pages/notify/NotifyManagementDetail.vue";
|
||||
import UserProfile from "@/pages/user/UserProfile.vue";
|
||||
import UserPassword from "@/pages/user/UserPassword.vue";
|
||||
import CustomerQrCode from "@/pages/customer/CustomerQrCode.vue";
|
||||
@@ -112,6 +114,34 @@ export const managementSettingRoutes: RouteRecordRaw[] = [
|
||||
darkBodyBackground: "#10161f"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/management/notifications/create",
|
||||
name: "NotifyManagementCreate",
|
||||
component: NotifyManagementCreate,
|
||||
meta: {
|
||||
depth: 4,
|
||||
navBarVisible: true,
|
||||
navBarCanBack: true,
|
||||
navBarTitle: "发送通知",
|
||||
tabBarVisible: false,
|
||||
bodyBackground: "#F5F5F8",
|
||||
darkBodyBackground: "#10161f"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/management/notifications/:id",
|
||||
name: "NotifyManagementDetail",
|
||||
component: NotifyManagementDetail,
|
||||
meta: {
|
||||
depth: 4,
|
||||
navBarVisible: true,
|
||||
navBarCanBack: true,
|
||||
navBarTitle: "通知详情",
|
||||
tabBarVisible: false,
|
||||
bodyBackground: "#F5F5F8",
|
||||
darkBodyBackground: "#10161f"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/management/tags",
|
||||
name: "TagList",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Message } from "@/utils/message";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import StoreAPI from "@/api/StoreAPI";
|
||||
import type { Store } from "@/types/Store";
|
||||
import { gaoRoleCodes, gaoStoreScopePermissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type ModuleCode,
|
||||
type PermissionPayload,
|
||||
type Role as UserRole
|
||||
} from "timi-web";
|
||||
} from "timi-web-api";
|
||||
import type {
|
||||
Permission,
|
||||
PermissionDraft,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Attachment, Model, User } from "timi-web";
|
||||
import type { Attachment, Model, User } from "timi-web-api";
|
||||
import type { EventRecord } from "@/types/EventRecord.ts";
|
||||
import type { TagApply } from "@/types/Tag";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Model } from "timi-web";
|
||||
import type { Model } from "timi-web-api";
|
||||
|
||||
export type EventStatus = "DRAFT" | "ACTIVE" | "INACTIVE" | "DELETED";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Attachment, BasePage, Model, PageResult, User } from "timi-web";
|
||||
import type { Attachment, BasePage, Model, PageResult, User } from "timi-web-api";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import type { PointLedgerChangeType } from "@/types/Point";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Model, Role, User } from "timi-web";
|
||||
import type { Model, Role, User } from "timi-web-api";
|
||||
|
||||
export type ManagedUser = {
|
||||
storeId?: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AuthOwnerType, ModuleCode, Permission as UserPermission, Role as UserRole } from "timi-web";
|
||||
import type { AuthOwnerType, ModuleCode, Permission as UserPermission, Role as UserRole } from "timi-web-api";
|
||||
|
||||
export type Permission = UserPermission & {
|
||||
builtin?: boolean;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { Attachment } from "timi-web";
|
||||
import type { Attachment } from "timi-web-api";
|
||||
|
||||
/** 照片滚动组件使用的附件展示项。 */
|
||||
export type PhotoItem = {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { BasePage, Model } from "timi-web";
|
||||
import type { BasePage, Model } from "timi-web-api";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
|
||||
export type PointRuleStatus = "DRAFT" | "ACTIVE" | "INACTIVE" | "DELETED";
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { BasePage, Model } from "timi-web";
|
||||
import type { BasePage, Model } from "timi-web-api";
|
||||
|
||||
export type StoreStatus = "ACTIVE" | "INACTIVE" | "DELETED";
|
||||
export type StoreBusinessDayStatus = "OPEN" | "CLOSED";
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { Model } from "timi-web";
|
||||
import type { Model } from "timi-web-api";
|
||||
|
||||
export type TagOwnerType = "GAO_STORE";
|
||||
export type TagBizType = "GAO_CUSTOMER";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AttachmentAPI } from "timi-web";
|
||||
import { AttachmentAPI } from "timi-web-api";
|
||||
|
||||
/**
|
||||
* 获取附件读取地址。
|
||||
|
||||
@@ -88,7 +88,10 @@ export const corePermissionCodes = {
|
||||
delete: "CORE:USER_ROLE:DELETE"
|
||||
},
|
||||
notify: {
|
||||
read: "CORE:NOTIFY:READ"
|
||||
create: "CORE:NOTIFY:CREATE",
|
||||
read: "CORE:NOTIFY:READ",
|
||||
delete: "CORE:NOTIFY:DELETE",
|
||||
cancel: "CORE:NOTIFY:CANCEL"
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { StoreBusinessDayStatus, StoreStatus } from "@/types/Store";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { type Page, useUserStore } from "timi-web";
|
||||
import { type Page, useUserStore } from "timi-web-api";
|
||||
|
||||
export const currentStoreIdStorageKey = "gao-customer-h5:current-store-id";
|
||||
export const loginStoreIdStorageKey = "gao-customer-h5:login-store-id";
|
||||
|
||||
@@ -12,6 +12,18 @@
|
||||
"timi-web/*": [
|
||||
"../timi-web/dist/src/*"
|
||||
],
|
||||
"timi-web-api": [
|
||||
"../timi-web-api/src/index.ts"
|
||||
],
|
||||
"timi-web-api/*": [
|
||||
"../timi-web-api/src/*"
|
||||
],
|
||||
"timi-tdesign-mobile": [
|
||||
"../timi-tdesign-mobile/dist/timi-tdesign-mobile/src/index.d.ts"
|
||||
],
|
||||
"timi-tdesign-mobile/*": [
|
||||
"../timi-tdesign-mobile/dist/timi-tdesign-mobile/src/*"
|
||||
],
|
||||
"vue": [
|
||||
"node_modules/vue"
|
||||
],
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
"timi-web/*": [
|
||||
"../timi-web/dist/src/*"
|
||||
],
|
||||
"timi-web-api": [
|
||||
"../timi-web-api/dist/index.d.ts"
|
||||
],
|
||||
"timi-web-api/*": [
|
||||
"../timi-web-api/dist/*"
|
||||
],
|
||||
"timi-tdesign-mobile": [
|
||||
"../timi-tdesign-mobile/dist/timi-tdesign-mobile/src/index.d.ts"
|
||||
],
|
||||
"timi-tdesign-mobile/*": [
|
||||
"../timi-tdesign-mobile/dist/timi-tdesign-mobile/src/*"
|
||||
],
|
||||
"vue": [
|
||||
"node_modules/vue"
|
||||
],
|
||||
|
||||
+34
-1
@@ -8,11 +8,18 @@ import { TDesignResolver } from "unplugin-vue-components/resolvers";
|
||||
const localTimiWebRoot = fileURLToPath(new URL("../timi-web", import.meta.url));
|
||||
const localTimiWebModule = fileURLToPath(new URL("../timi-web/dist/timi-web.mjs", import.meta.url));
|
||||
const localTimiWebStyle = fileURLToPath(new URL("../timi-web/dist/timi-web.css", import.meta.url));
|
||||
const localTimiWebEditor = fileURLToPath(new URL("../timi-web/src/editor/index.ts", import.meta.url));
|
||||
const localTimiWebApiRoot = fileURLToPath(new URL("../timi-web-api", import.meta.url));
|
||||
const localTimiWebApiModule = fileURLToPath(new URL("../timi-web-api/dist/index.js", import.meta.url));
|
||||
const localTimiTDesignMobileRoot = fileURLToPath(new URL("../timi-tdesign-mobile", import.meta.url));
|
||||
const localTimiTDesignMobileModule = fileURLToPath(new URL("../timi-tdesign-mobile/src/index.ts", import.meta.url));
|
||||
const localTimiTDesignMobileStyle = fileURLToPath(new URL("../timi-tdesign-mobile/dist/timi-tdesign-mobile.css", import.meta.url));
|
||||
const projectRoot = fileURLToPath(new URL(".", import.meta.url));
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
const useLocalTimiWeb = env.VITE_USE_LOCAL_TIMI_WEB !== "false";
|
||||
const useLocalTimiTDesignMobile = env.VITE_USE_LOCAL_TIMI_TDESIGN_MOBILE !== "false";
|
||||
return {
|
||||
resolve: {
|
||||
dedupe: ["vue", "pinia", "vue-router"],
|
||||
@@ -27,11 +34,35 @@ export default defineConfig(({ mode }) => {
|
||||
find: /^timi-web$/,
|
||||
replacement: localTimiWebModule
|
||||
},
|
||||
{
|
||||
find: /^timi-web\/editor$/,
|
||||
replacement: localTimiWebEditor
|
||||
},
|
||||
{
|
||||
find: /^timi-web\/style\.css$/,
|
||||
replacement: localTimiWebStyle
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
find: /^timi-web-api$/,
|
||||
replacement: localTimiWebApiModule
|
||||
},
|
||||
{
|
||||
find: /^timi-web-api\//,
|
||||
replacement: `${localTimiWebApiRoot}/dist/`
|
||||
},
|
||||
...(useLocalTimiTDesignMobile
|
||||
? [
|
||||
{
|
||||
find: /^timi-tdesign-mobile$/,
|
||||
replacement: localTimiTDesignMobileModule
|
||||
},
|
||||
{
|
||||
find: /^timi-tdesign-mobile\/style\.css$/,
|
||||
replacement: localTimiTDesignMobileStyle
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
@@ -76,7 +107,9 @@ export default defineConfig(({ mode }) => {
|
||||
fs: {
|
||||
allow: [
|
||||
projectRoot,
|
||||
localTimiWebRoot
|
||||
localTimiWebRoot,
|
||||
localTimiWebApiRoot,
|
||||
localTimiTDesignMobileRoot
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user