diff --git a/code/allapp/allapp-go-v3/internal/middleware/auth.go b/code/allapp/allapp-go-v3/internal/middleware/auth.go index d4bb557e..646f6d48 100644 --- a/code/allapp/allapp-go-v3/internal/middleware/auth.go +++ b/code/allapp/allapp-go-v3/internal/middleware/auth.go @@ -20,12 +20,15 @@ func Auth() fiber.Handler { return httpx.Unauthorized(c, "未登录") } - verifyToken, ok := jwtx.VerifyToken(c.Context(), token) + _, ok := jwtx.VerifyToken(c.Context(), token) if !ok { return httpx.Unauthorized(c, "登录已过期") } - ctx := context.WithValue(c.Context(), CtxUserIDKey, verifyToken.Data.UserID) + //ctx := context.WithValue(c.Context(), CtxUserIDKey, verifyToken.Data.UserID) + + ctx := context.WithValue(c.Context(), CtxUserIDKey, int64(796541901389893)) + c.SetContext(ctx) return c.Next() diff --git a/code/allapp/allapp-go-v3/pkg/db/delete.go b/code/allapp/allapp-go-v3/pkg/db/delete.go index 7b780e49..1b30073e 100644 --- a/code/allapp/allapp-go-v3/pkg/db/delete.go +++ b/code/allapp/allapp-go-v3/pkg/db/delete.go @@ -7,18 +7,28 @@ import ( ) func (c *Client) Delete(ctx context.Context, table, keyField string, data map[string]any) error { - keyVal, err := validateData(table, keyField, data) + _, err := validateData(table, keyField, data) if err != nil { return err } + where := make([]string, 0, len(data)) + args := make([]any, 0, len(data)) + i := 1 + + for k, v := range data { + where = append(where, fmt.Sprintf("%s = $%d", quoteCol(k), i)) + args = append(args, v) + i++ + } + sql := fmt.Sprintf( - "DELETE FROM %s WHERE %s=$1", + "DELETE FROM %s WHERE %s", quoteTable(table), - quoteCol(keyField), + strings.Join(where, " AND "), ) - _, err = c.exec().Exec(ctx, sql, keyVal) + _, err = c.exec().Exec(ctx, sql, args...) return err } diff --git a/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go b/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go index bdda844e..afba9669 100644 --- a/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go +++ b/code/allapp/allapp-go-v3/pkg/jwtx/jwtx.go @@ -84,6 +84,7 @@ func VerifyToken(ctx context.Context, tokenString string) (*CustomClaims, bool) } claims, ok := token.Claims.(*CustomClaims) + if !ok { return nil, false } diff --git a/code/app/app-rn4/package.json b/code/app/app-rn4/package.json index 56cc2c0f..b12a7680 100644 --- a/code/app/app-rn4/package.json +++ b/code/app/app-rn4/package.json @@ -22,9 +22,11 @@ "@react-navigation/native": "^7.1.33", "alova": "^3.5.1", "axios": "^1.15.0", + "dayjs": "^1.11.20", "expo": "~55.0.15", "expo-blur": "~55.0.14", "expo-build-properties": "~55.0.13", + "expo-clipboard": "~55.0.13", "expo-constants": "~55.0.14", "expo-device": "~55.0.15", "expo-document-picker": "~55.0.13", diff --git a/code/app/app-rn4/pnpm-lock.yaml b/code/app/app-rn4/pnpm-lock.yaml index ad040fde..2698767f 100644 --- a/code/app/app-rn4/pnpm-lock.yaml +++ b/code/app/app-rn4/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: axios: specifier: ^1.15.0 version: 1.15.0 + dayjs: + specifier: ^1.11.20 + version: 1.11.20 expo: specifier: ~55.0.15 version: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) @@ -44,6 +47,9 @@ importers: expo-build-properties: specifier: ~55.0.13 version: 55.0.13(expo@55.0.15) + expo-clipboard: + specifier: ~55.0.13 + version: 55.0.13(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) expo-constants: specifier: ~55.0.14 version: 55.0.14(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(typescript@5.9.3) @@ -1016,48 +1022,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.44.0': resolution: {integrity: sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.44.0': resolution: {integrity: sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.44.0': resolution: {integrity: sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.44.0': resolution: {integrity: sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.44.0': resolution: {integrity: sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.44.0': resolution: {integrity: sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.44.0': resolution: {integrity: sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxfmt/binding-openharmony-arm64@0.44.0': resolution: {integrity: sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==} @@ -1130,48 +1144,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.60.0': resolution: {integrity: sha512-eDYDXZGhQAXyn6GwtwiX/qcLS0HlOLPJ/+iiIY8RYr+3P8oKBmgKxADLlniL6FtWfE7pPk7IGN9/xvDEvDvFeg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.60.0': resolution: {integrity: sha512-nxehly5XYBHUWI9VJX1bqCf9j/B43DaK/aS/T1fcxCpX3PA4Rm9BB54nPD1CKayT8xg6REN1ao+01hSRNgy8OA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.60.0': resolution: {integrity: sha512-j1qf/NaUfOWQutjeoooNG1Q0zsK0XGmSu1uDLq3cctquRF3j7t9Hxqf/76ehCc5GEUAanth2W4Fa+XT1RFg/nw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.60.0': resolution: {integrity: sha512-YELKPRefQ/q/h3RUmeRfPCUhh2wBvgV1RyZ/F9M9u8cDyXsQW2ojv1DeWQTt466yczDITjZnIOg/s05pk7Ve2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.60.0': resolution: {integrity: sha512-JkO3C6Gki7Y6h/MiIkFKvHFOz98/YWvQ4WYbK9DLXACMP2rjULzkeGyAzorJE5S1dzLQGFgeqvN779kSFwoV1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.60.0': resolution: {integrity: sha512-XjKHdFVCpZZZSWBCKyyqCq65s2AKXykMXkjLoKYODrD+f5toLhlwsMESscu8FbgnJQ4Y/dpR/zdazsahmgBJIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-musl@1.60.0': resolution: {integrity: sha512-js29ZWIuPhNWzY8NC7KoffEMEeWG105vbmm+8EOJsC+T/jHBiKIJEUF78+F/IrgEWMMP9N0kRND4Pp75+xAhKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxlint/binding-openharmony-arm64@1.60.0': resolution: {integrity: sha512-H+PUITKHk04stFpWj3x3Kg08Afp/bcXSBi0EhasR5a0Vw7StXHTzdl655PUI0fB4qdh2Wsu6Dsi+3ACxPoyQnA==} @@ -1590,24 +1612,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -2049,6 +2075,9 @@ packages: resolution: {integrity: sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -2231,6 +2260,13 @@ packages: peerDependencies: expo: '*' + expo-clipboard@55.0.13: + resolution: {integrity: sha512-PrOmmuVsGW4bAkNQmGKtxMXj3invsfN+jfIKmQxHwE/dn7ODqwFWviUTa+PMUjP3XZmYCDLyu/i0GLeu7HF9Ew==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + expo-constants@55.0.14: resolution: {integrity: sha512-l23QVQCYBPKT5zbxxZdJeuhiunadvWdjcQ9+GC8h+02jCoLmWRk20064nCINnQTP3Hf+uLPteUiwYrJd0e446w==} peerDependencies: @@ -2540,7 +2576,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} @@ -2873,72 +2909,84 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.31.1: resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.1: resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.1: resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.1: resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.1: resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} @@ -6333,6 +6381,8 @@ snapshots: culori@4.0.2: {} + dayjs@1.11.20: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -6471,6 +6521,12 @@ snapshots: resolve-from: 5.0.0 semver: 7.7.4 + expo-clipboard@55.0.13(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + dependencies: + expo: 55.0.15(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.9)(expo-router@55.0.12)(react-dom@19.2.0(react@19.2.0))(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + react: 19.2.0 + react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) + expo-constants@55.0.14(expo@55.0.15)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(typescript@5.9.3): dependencies: '@expo/config': 55.0.15(typescript@5.9.3) diff --git a/code/app/app-rn4/src/app/(tabs)/home/index.tsx b/code/app/app-rn4/src/app/(tabs)/home/index.tsx index b8aca6e1..0fbc164d 100644 --- a/code/app/app-rn4/src/app/(tabs)/home/index.tsx +++ b/code/app/app-rn4/src/app/(tabs)/home/index.tsx @@ -1,12 +1,20 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import LayoutView from "@/components/layout/view"; import { loadDataApi } from "@/request/api"; -import { View, Text, TouchableOpacity } from "react-native"; +import { View, Text } from "react-native"; import { useFamilyStore } from "@/store"; +import { + HouseHeartIcon, + LayoutGridIcon, + LayersIcon, +} from "lucide-react-native"; +import { IconAppFinance, IconAppClothes } from "@/components/iconfont"; +import { PressableFeedback } from "heroui-native"; export default function Home() { const familyStore = useFamilyStore(); const [family, setFamily] = useState({}); + const [duration, setDuration] = useState({ days: 0, hours: 0, @@ -14,16 +22,10 @@ export default function Home() { seconds: 0, }); - const mockMembers = [ - { name: "张三", online: true }, - { name: "李四", online: false }, - { name: "王五", online: true }, - ]; - async function loadFamilyData() { const res = await loadDataApi({ - view_name: "b_family", - search_condition: "id = ?", + view_name: `b_family`, + search_condition: `id = ?`, search_columns: [], order_by: "", args: [familyStore.currentFamily?.family_id], @@ -68,97 +70,63 @@ export default function Home() { return ( - {/* 🌸 顶部情绪背景区 */} - - - {family.name || "我们的家庭"} - + + {family.name} - 在一起的第 + + + - {/* 💖 超大核心数字(恋爱记风格) */} - - {duration.days} - + 家庭已陪伴你 + - 天 - + + {duration.days} + 天 - {/* ⏱ 小时间补充 */} - - - {duration.hours}时 {duration.minutes}分 {duration.seconds}秒 - - + {duration.hours} + 时 - {/* 💕 情侣/家庭状态区 */} - - 我们的状态 + {duration.minutes} + 分 - - 家庭成员 - {mockMembers.length} 人 - - - - 在线人数 - - {mockMembers.filter((m) => m.online).length} 人 - + {duration.seconds} + 秒 + - {/* 💌 邀请区(恋爱记风格按钮) */} - - - 邀请家人加入 - - + + + + + + 快捷应用 + - {/* 🔑 邀请码(弱化但温柔) */} - - 邀请码 - AB12CD - + + + - {/* 🕊 底部情绪提示 */} - - 💖 一起走过的每一天都值得记录 + 记账 + + + + + + + + 衣服搭配 + + + + + + + 更多 + + + diff --git a/code/app/app-rn4/src/app/(tabs)/my/index.tsx b/code/app/app-rn4/src/app/(tabs)/my/index.tsx index 0b7b69cb..10a33dca 100644 --- a/code/app/app-rn4/src/app/(tabs)/my/index.tsx +++ b/code/app/app-rn4/src/app/(tabs)/my/index.tsx @@ -46,9 +46,13 @@ export default function My() { 账户 - - - {}}> + + { + router.navigate("/family/user"); + }} + > + @@ -63,6 +67,31 @@ export default function My() { + + 家庭 + + + { + router.navigate("/family/settings"); + }} + > + + + + + + + 家庭设置 + + + + + + + + + { @@ -82,6 +111,7 @@ export default function My() { + diff --git a/code/app/app-rn4/src/app/(tabs)/space/index.tsx b/code/app/app-rn4/src/app/(tabs)/space/index.tsx deleted file mode 100644 index 28a303ab..00000000 --- a/code/app/app-rn4/src/app/(tabs)/space/index.tsx +++ /dev/null @@ -1,606 +0,0 @@ -import React, { useEffect, useState, useRef } from "react"; -import LayoutView from "@/components/layout/view"; -import { View, Text } from "react-native"; -import { - Button, - Separator, - Spinner, - Chip, - BottomSheet, - Label, - TextField, - Input, - InputOTP, - type InputOTPRef, - PressableFeedback, - useBottomSheetAwareHandlers, -} from "heroui-native"; -import { PlusIcon, HousePlusIcon, GripIcon } from "lucide-react-native"; -import { useUserStore } from "@/store"; -import { getUniqueIdApi, loadDataApi, loadDataBySqlApi, saveDataApi } from "@/request/api"; -import EmptyState from "@/components/EmptyStatus"; -import Sortable from "react-native-sortables"; -import { useSpaceStore } from "@/store"; -import { useRouter } from "expo-router"; -import { UserRoundIcon, UsersRoundIcon } from "lucide-react-native"; -import { toast } from "sonner-native"; -import { FadeIn } from "react-native-reanimated"; -import { generateCode } from "@/utils/common"; -import { Keyboard } from "react-native"; -import { forwardRef } from "react"; - -const BottomSheetTextInput = forwardRef>((props, ref) => { - const { onFocus, onBlur } = useBottomSheetAwareHandlers(); - - return ( - { - onFocus(e); - props.onFocus?.(e); - }} - onBlur={(e) => { - onBlur(e); - props.onBlur?.(e); - }} - /> - ); -}); - -const BottomSheetTextInputOTP = forwardRef>( - (props, ref) => { - const { onFocus, onBlur } = useBottomSheetAwareHandlers(); - - return ( - { - onFocus(e); - props.onFocus?.(e); - }} - onBlur={(e) => { - onBlur(e); - props.onBlur?.(e); - }} - /> - ); - }, -); - -export default function Space() { - const router = useRouter(); - const userStore = useUserStore(); - const spaceStore = useSpaceStore(); - - const ITEM_HEIGHT = 64; - - const [spaces, setSpaces] = useState([]); - - const [loading, setLoading] = useState(false); - - // 加入 - const [showJoin, setShowJoin] = useState(false); - const [joinLoading, setJoinLoading] = useState(false); - const [joinForm, setJoinForm] = useState({ - invite_code: "", - }); - const joinInputRef = useRef(null); - - // 新建 - const [showCreate, setShowCreate] = useState(false); - const [createForm, setCreateForm] = useState({ - name: "", - }); - const [createLoading, setCreateLoading] = useState(false); - const createInputRef = useRef(null); - - useEffect(() => { - listUserSpaces(); - }, []); - - useEffect(() => { - if (showCreate) { - requestAnimationFrame(() => { - createInputRef.current?.focus(); - }); - } else { - setCreateForm({ - name: "", - }); - setCreateLoading(false); - Keyboard.dismiss(); - } - }, [showCreate]); - - useEffect(() => { - if (showJoin) { - requestAnimationFrame(() => { - joinInputRef.current?.focus(); - }); - } else { - setJoinForm({ - invite_code: "", - }); - setJoinLoading(false); - Keyboard.dismiss(); - } - }, [showJoin]); - - function listUserSpaces(loading = true) { - if (loading) { - setLoading(true); - } - - loadDataApi({ - view_name: "v_user_spaces", - search_condition: `v_user_id = ?`, - search_columns: [], - order_by: "", - args: [userStore.userInfo.id], - }).then((res) => { - if (res.isSuccess) { - setSpaces(res.data); - if (loading) { - setLoading(false); - } - } - }); - } - - async function handleCreate() { - if (createLoading) return; - - if (createForm.name === "") { - toast.warning("空间名称不能为空"); - return; - } - - setCreateLoading(true); - - try { - let inviteCode = ""; - let isExist = true; - - // 循环生成直到不存在 - while (isExist) { - inviteCode = generateCode(6); - - const res = await loadDataBySqlApi({ - sql: `SELECT 1 FROM space WHERE invite_code = ? LIMIT 1`, - args: [inviteCode], - }); - - if (!res.isSuccess) { - throw new Error("校验邀请码失败"); - } - - isExist = res.data.length > 0; - } - - // 设置是否是默认空间 - let isDefault = false; - - const defaultRes = await loadDataApi({ - view_name: "v_user_spaces", - search_condition: `v_user_id = ?`, - search_columns: ["v_is_default"], - order_by: "", - args: [userStore.userInfo.id], - }); - - if (!defaultRes.isSuccess) { - return; - } - - if (defaultRes.data.length == 0) { - isDefault = true; - } - - // 创建 - const autoRes = await getUniqueIdApi(2); - if (!autoRes.isSuccess) { - return; - } - - const spaceId = autoRes.data[0]; - const spaceMemberId = autoRes.data[1]; - - // 插入空间 - const requestData = []; - - requestData.push({ - table_name: "space", - key_field: "id", - inserts: [ - { - id: spaceId, - name: createForm.name, - invite_code: inviteCode, - owner_id: userStore.userInfo.id, - status: 0, - }, - ], - updates: [], - deletes: [], - }); - - // 插入空间用户 - requestData.push({ - table_name: "space_member", - key_field: "id", - inserts: [ - { - id: spaceMemberId, - space_id: spaceId, - user_id: userStore.userInfo.id, - role: 0, - is_default: isDefault ? 1 : 0, - sort_number: defaultRes.data.length, - member_name: userStore.userInfo.nickname, - }, - ], - updates: [], - deletes: [], - }); - - const saveRes = await saveDataApi(requestData); - - if (saveRes.isSuccess) { - toast.success("创建成功"); - setShowCreate(false); - - setCreateForm({ - name: "", - }); - - listUserSpaces(); - } - } catch (e) { - console.log(e); - } finally { - setCreateLoading(false); - } - } - - async function handleJoin() { - if (joinLoading) return; - - const code = joinForm.invite_code.trim(); - - if (!code) { - toast.warning("邀请码不能为空"); - return; - } - - if (!/^[A-Za-z0-9]{6}$/.test(code)) { - toast.warning("邀请码必须为6位字母或数字"); - return; - } - - setJoinLoading(true); - - try { - const res = await loadDataBySqlApi({ - sql: ` SELECT - s.id, - s.name, - EXISTS ( - SELECT 1 - FROM space_member sm - WHERE sm.space_id = s.id - AND sm.user_id = ? - ) AS is_joined - FROM space s - WHERE s.invite_code = ? - LIMIT 1; - `, - args: [userStore.userInfo.id, code], - }); - - if (!res.isSuccess) return; - - const row = res.data?.[0]; - - if (!row) { - toast.warning("邀请码无效"); - return; - } - - if (row.is_joined) { - toast.warning("你已经在该空间了"); - return; - } - - const autoRes = await getUniqueIdApi(1); - if (!autoRes.isSuccess) { - return; - } - - // 设置是否是默认空间 - let isDefault = false; - - const defaultRes = await loadDataApi({ - view_name: "v_user_spaces", - search_condition: `v_user_id = ?`, - search_columns: ["v_is_default"], - order_by: "", - args: [userStore.userInfo.id], - }); - - if (!defaultRes.isSuccess) { - return; - } - - if (defaultRes.data.length == 0) { - isDefault = true; - } - - // 加入 - const requestData = []; - - requestData.push({ - table_name: "space_member", - key_field: "id", - inserts: [ - { - id: autoRes.data[0], - space_id: row.id, - user_id: userStore.userInfo.id, - role: 1, - is_default: isDefault ? 1 : 0, - sort_number: defaultRes.data.length, - member_name: userStore.userInfo.nickname, - }, - ], - updates: [], - deletes: [], - }); - - const saveRes = await saveDataApi(requestData); - - if (saveRes.isSuccess) { - toast.success("加入成功"); - setShowJoin(false); - - setJoinForm({ - invite_code: "", - }); - - listUserSpaces(); - } - } catch (e) { - console.error(e); - toast.error("加入失败,请稍后重试"); - } finally { - setJoinLoading(false); - } - } - - const renderItem = ({ item, index }: { item: any; index: number }) => ( - { - router.navigate({ - pathname: "/tab-space" as any, - params: { - spaceId: item.space_id, - }, - }); - }} - style={{ - height: ITEM_HEIGHT, - }} - className={`rounded-xl flex-row items-center px-4 py-3 bg-white ${ - index === spaces.length - 1 ? "mb-4" : "" - }`} - > - - - - {item.name} - - {item.owner_id === userStore.userInfo.id && ( - - 创建者 - - )} - - {item.v_is_default === 1 && ( - - 默认 - - )} - - {item.space_id === spaceStore?.currentSpace?.space_id && ( - - 当前空间 - - )} - - - - - - {item.v_member_name} - - - - {item.v_member_count} - - - - - - - - - - - ); - - return ( - - - - - - - - - - 我的空间 - - - - {loading ? ( - - - - ) : spaces.length === 0 ? ( - - ) : ( - { - return String(item.space_id); - }} - activeItemScale={1} - columns={1} - data={spaces} - overDrag="vertical" - renderItem={renderItem} - rowGap={10} - highlight-next-line - customHandle - autoScrollActivationOffset={200} - onDragEnd={(dragData: any) => { - const list = dragData.data; - - const updates = list.map((item: any, index: number) => ({ - id: item.v_space_member_id, - sort_number: index, - })); - - saveDataApi([ - { - table_name: "space_member", - key_field: "id", - inserts: [], - updates, - deletes: [], - }, - ]).then((_res) => { - // console.log(res); - }); - }} - /> - )} - - { - setShowCreate(val); - }} - > - - - - - - - - setCreateForm({ - name: val, - }) - } - /> - - - - - - - - - { - setShowJoin(val); - }} - > - - - - - - - - { - setJoinForm({ - invite_code: val, - }); - }} - > - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/code/app/app-rn4/src/app/auth/login.tsx b/code/app/app-rn4/src/app/auth/login.tsx index d69ebe6b..46cb4441 100644 --- a/code/app/app-rn4/src/app/auth/login.tsx +++ b/code/app/app-rn4/src/app/auth/login.tsx @@ -1,17 +1,11 @@ -import { useEffect } from "react"; import { Stack } from "expo-router"; import { Image, Text, View } from "react-native"; import { Button, LinkButton, ControlField, Checkbox, Spinner } from "heroui-native"; -import { IconQq, IconWeixin } from "@/components/iconfont"; +import { IconQq } from "@/components/iconfont"; import { useState } from "react"; import { FadeIn } from "react-native-reanimated"; import { useUserStore, useFamilyStore } from "@/store"; import { useRouter } from "expo-router"; -import ExpoQQ from "expo-qq"; -import ExpoWeChat from "expo-wechat"; -import { useEvent } from "expo"; -import { toast } from "sonner-native"; -import { loginQqApi, loginWxApi } from "@/request/api"; export default function Login() { const router = useRouter(); @@ -20,189 +14,48 @@ export default function Login() { const [loading, setLoading] = useState(false); const [isAgreed, setIsAgreed] = useState(true); - const [form, setForm] = useState({ - type: 0, - openid: "", - code: "", - }); - // 登录 - const onLoginFinished = useEvent(ExpoQQ, "onLoginFinished"); - const onGetUserInfo = useEvent(ExpoQQ, "onGetUserInfo"); - const authResult = useEvent(ExpoWeChat, "onAuthResult"); + /** + * {"code": 1000, "data": {"avatar": "http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100", + * "nickname": "Zhang", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjo3OTY0NTY3MTI3ODU5ODl9LCJpYXQiOjE3NzY1ODU2MDR9.ZwmN5_0MGrP9dYpn3quK94HXMG3QYKmAq7wnP5X68dY", "userId": 796456712785989}, "isSuccess": true, "message": "success", "track_id": "djIRo23SZlA160zBTH2DX-kxeuLApKTNyZdxqqauSMk"} + */ - useEffect(() => { - ExpoQQ.init("102826474", ""); - ExpoWeChat.registerApp("wxdab3e21a1f7e392f", ""); - }, []); - - // 微信登录 - async function handleLoginWechat() { - if (loading) { - return; - } - setLoading(true); - - // 检查微信是否安装 - const isInstalled = await ExpoWeChat.isWXAppInstalled(); - if (!isInstalled) { - toast.warning(onLoginFinished?.errorMessage || "请先安装微信"); - setLoading(false); - return; - } - - setForm((val) => ({ - ...val, - type: 0, - })); - - ExpoWeChat.sendAuthRequest("snsapi_userinfo", "state123"); - } - - // QQ 登录 - async function handleLoginQQ() { + function handleTest() { if (loading) { return; } setLoading(true); - setForm((val) => ({ - ...val, - type: 1, - })); - await ExpoQQ.login(["get_user_info"]); - } + setTimeout(() => { + requestAnimationFrame(() => { + userStore.setUserInfo({ + id: 796541901389893, + token: + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjo3OTY0NTY3MTI3ODU5ODl9LCJpYXQiOjE3NzY1ODU2MDR9.ZwmN5_0MGrP9dYpn3quK94HXMG3QYKmAq7wnP5X68dY", + nickname: "Zhang", + avatar: + "http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100", + }); + userStore.setAuthExpired(false); + console.log(familyStore.inFamily()); - useEffect(() => { - if (!onLoginFinished) { - return; - } + // if (familyStore.inFamily()) { + // router.replace("/(tabs)/home"); + // } else { + // router.replace("/family/entry"); + // return; + // } - if (!onLoginFinished.success) { - toast.warning(onLoginFinished?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权"); - setLoading(false); - return; - } - - const fetchUserInfo = async () => { - try { - const tokenInfo = await ExpoQQ.getLoginTokenInfo(); - if (!tokenInfo.openId) { - toast.warning(tokenInfo?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权"); - setLoading(false); - return; - } - - setForm((val) => ({ - ...val, - openid: String(tokenInfo.openId), - })); - - await ExpoQQ.sendGetUserInfoReq(); - } catch (e) { - console.log(e); - - setLoading(false); - } - }; - - fetchUserInfo(); - }, [onLoginFinished]); - - // QQ登录 - useEffect(() => { - const handleLogin = async () => { - if (!onGetUserInfo) { - setLoading(false); - return; - } - - try { - const loginRes = await loginQqApi({ - openid: form.openid, - nickname: onGetUserInfo.nickname || "", - avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2, + familyStore.setCurrentFamily({ + family_id: 797139042881605, }); - if (loginRes.isSuccess) { - requestAnimationFrame(() => { - userStore.setUserInfo({ - token: loginRes.data.token, - id: loginRes.data.userId, - nickname: loginRes.data.nickname, - avatar: loginRes.data.avatar, - }); - - userStore.setAuthExpired(false); - - if (loginRes.data.family_id === 0) { - router.replace("/family/entry"); - } else { - familyStore.setCurrentFamily({ - family_id: loginRes.data.family_id, - }); - - router.replace("/(tabs)/home"); - } - }); - } - } catch (err) { - console.error("登录接口出错", err); - } finally { - setLoading(false); - } - }; - - handleLogin(); - }, [onGetUserInfo]); - - // 微信登录 - useEffect(() => { - if (!authResult) return; - - const handleLogin = async () => { - if (authResult.errorCode === 0) { - try { - const loginRes = await loginWxApi({ - code: authResult.code, - }); - - if (loginRes.isSuccess) { - requestAnimationFrame(() => { - userStore.setUserInfo({ - token: loginRes.data.token, - id: loginRes.data.userId, - nickname: loginRes.data.nickname, - avatar: loginRes.data.avatar, - }); - - userStore.setAuthExpired(false); - - if (loginRes.data.family_id === 0) { - router.replace("/family/entry"); - } else { - familyStore.setCurrentFamily({ - family_id: loginRes.data.family_id, - }); - - router.replace("/(tabs)/home"); - } - }); - } - } catch (err) { - console.error("登录接口出错", err); - } finally { - setLoading(false); - } - } else { - console.log("授权失败:", authResult.errorMessage); - setLoading(false); - } - }; - - handleLogin(); - }, [authResult]); + router.replace("/(tabs)/home"); + }); + setLoading(false); + }, 500); + } return ( <> @@ -222,7 +75,7 @@ export default function Login() { - */} + + diff --git a/code/app/app-rn4/src/app/auth/login2.tsx b/code/app/app-rn4/src/app/auth/login2.tsx deleted file mode 100644 index 20c38fd2..00000000 --- a/code/app/app-rn4/src/app/auth/login2.tsx +++ /dev/null @@ -1,280 +0,0 @@ -import { useEffect } from "react"; -import { Stack } from "expo-router"; -import { Image, Text, View } from "react-native"; -import { Button, LinkButton, ControlField, Checkbox, Spinner } from "heroui-native"; -import { IconQq, IconWeixin } from "@/components/iconfont"; -import { useState } from "react"; -import { FadeIn } from "react-native-reanimated"; -import { useUserStore } from "@/store"; -import { useRouter } from "expo-router"; -import ExpoQQ from "expo-qq"; -import ExpoWeChat from "expo-wechat"; -import { useEvent } from "expo"; -import { toast } from "sonner-native"; -import { loginQqApi, loginWxApi } from "@/request/api"; - -export default function Login() { - const router = useRouter(); - const userStore = useUserStore(); - - const [loading, setLoading] = useState(false); - const [isAgreed, setIsAgreed] = useState(true); - const [form, setForm] = useState({ - type: 0, - openid: "", - code: "", - }); - - // 登录 - // const onLoginFinished = useEvent(ExpoQQ, "onLoginFinished"); - // const onGetUserInfo = useEvent(ExpoQQ, "onGetUserInfo"); - // const authResult = useEvent(ExpoWeChat, "onAuthResult"); - - // useEffect(() => { - // ExpoQQ.init("102826474", ""); - // ExpoWeChat.registerApp("wxdab3e21a1f7e392f", ""); - // }, []); - - // // 微信登录 - // async function handleLoginWechat() { - // if (loading) { - // return; - // } - // setLoading(true); - - // // 检查微信是否安装 - // const isInstalled = await ExpoWeChat.isWXAppInstalled(); - // if (!isInstalled) { - // toast.warning(onLoginFinished?.errorMessage || "请先安装微信"); - // setLoading(false); - // return; - // } - - // setForm((val) => ({ - // ...val, - // type: 0, - // })); - - // ExpoWeChat.sendAuthRequest("snsapi_userinfo", "state123"); - // } - - // // QQ 登录 - // async function handleLoginQQ() { - // if (loading) { - // return; - // } - - // setLoading(true); - - // setForm((val) => ({ - // ...val, - // type: 1, - // })); - // await ExpoQQ.login(["get_user_info"]); - // } - - /** - * {"code": 1000, "data": {"avatar": "http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100", - * "nickname": "Zhang", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjo3OTY0NTY3MTI3ODU5ODl9LCJpYXQiOjE3NzY1ODU2MDR9.ZwmN5_0MGrP9dYpn3quK94HXMG3QYKmAq7wnP5X68dY", "userId": 796456712785989}, "isSuccess": true, "message": "success", "track_id": "djIRo23SZlA160zBTH2DX-kxeuLApKTNyZdxqqauSMk"} - */ - - function handleTest() { - if (loading) { - return; - } - - setLoading(true); - - setTimeout(() => { - requestAnimationFrame(() => { - userStore.setUserInfo({ - id: 779895419121733, - token: - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjo3OTY0NTY3MTI3ODU5ODl9LCJpYXQiOjE3NzY1ODU2MDR9.ZwmN5_0MGrP9dYpn3quK94HXMG3QYKmAq7wnP5X68dY", - nickname: "Zhang", - avatar: - "http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100", - }); - userStore.setAuthExpired(false); - // router.replace("/(tabs)/home"); - router.replace("/family/entry"); - }); - setLoading(false); - }, 2000); - } - - // useEffect(() => { - // if (!onLoginFinished) { - // return; - // } - - // if (!onLoginFinished.success) { - // toast.warning(onLoginFinished?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权"); - // setLoading(false); - // return; - // } - - // const fetchUserInfo = async () => { - // try { - // const tokenInfo = await ExpoQQ.getLoginTokenInfo(); - // if (!tokenInfo.openId) { - // toast.warning(tokenInfo?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权"); - // setLoading(false); - // return; - // } - - // setForm((val) => ({ - // ...val, - // openid: String(tokenInfo.openId), - // })); - - // await ExpoQQ.sendGetUserInfoReq(); - // } catch (e) { - // console.log(e); - - // setLoading(false); - // } - // }; - - // fetchUserInfo(); - // }, [onLoginFinished]); - - // // QQ登录 - // useEffect(() => { - // const handleLogin = async () => { - // if (!onGetUserInfo) { - // setLoading(false); - // return; - // } - - // try { - // const loginRes = await loginQqApi({ - // openid: form.openid, - // nickname: onGetUserInfo.nickname || "", - // avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2, - // }); - - // console.log(loginRes); - - // if (loginRes.isSuccess) { - // requestAnimationFrame(() => { - // userStore.setUserInfo({ - // token: loginRes.data.token, - // id: loginRes.data.userId, - // nickname: loginRes.data.nickname, - // avatar: loginRes.data.avatar, - // }); - - // userStore.setAuthExpired(false); - // router.replace("/(tabs)/home"); - // }); - // } - // } catch (err) { - // console.error("登录接口出错", err); - // } finally { - // setLoading(false); - // } - // }; - - // handleLogin(); - // }, [onGetUserInfo]); - - // // 微信登录 - // useEffect(() => { - // if (!authResult) return; - - // const handleLogin = async () => { - // if (authResult.errorCode === 0) { - // try { - // const loginRes = await loginWxApi({ - // code: authResult.code, - // }); - - // if (loginRes.isSuccess) { - // requestAnimationFrame(() => { - // userStore.setUserInfo({ - // token: loginRes.data.token, - // id: loginRes.data.userId, - // nickname: loginRes.data.nickname, - // avatar: loginRes.data.avatar, - // }); - - // userStore.setAuthExpired(false); - // router.replace("/(tabs)/home"); - // }); - // } - // } catch (err) { - // console.error("登录接口出错", err); - // } finally { - // setLoading(false); - // } - // } else { - // console.log("授权失败:", authResult.errorMessage); - // setLoading(false); - // } - // }; - - // handleLogin(); - // }, [authResult]); - - return ( - <> - - - - - Welcome to Allapp - - - - {/* - - */} - - - - - - - - - - 登录即表示你同意我们的 - - 用户协议 - - 和 - - 隐私政策 - - - - - - ); -} diff --git a/code/app/app-rn4/src/app/auth/login_2.txt b/code/app/app-rn4/src/app/auth/login_2.txt new file mode 100644 index 00000000..d69ebe6b --- /dev/null +++ b/code/app/app-rn4/src/app/auth/login_2.txt @@ -0,0 +1,262 @@ +import { useEffect } from "react"; +import { Stack } from "expo-router"; +import { Image, Text, View } from "react-native"; +import { Button, LinkButton, ControlField, Checkbox, Spinner } from "heroui-native"; +import { IconQq, IconWeixin } from "@/components/iconfont"; +import { useState } from "react"; +import { FadeIn } from "react-native-reanimated"; +import { useUserStore, useFamilyStore } from "@/store"; +import { useRouter } from "expo-router"; +import ExpoQQ from "expo-qq"; +import ExpoWeChat from "expo-wechat"; +import { useEvent } from "expo"; +import { toast } from "sonner-native"; +import { loginQqApi, loginWxApi } from "@/request/api"; + +export default function Login() { + const router = useRouter(); + const userStore = useUserStore(); + const familyStore = useFamilyStore(); + + const [loading, setLoading] = useState(false); + const [isAgreed, setIsAgreed] = useState(true); + const [form, setForm] = useState({ + type: 0, + openid: "", + code: "", + }); + + // 登录 + const onLoginFinished = useEvent(ExpoQQ, "onLoginFinished"); + const onGetUserInfo = useEvent(ExpoQQ, "onGetUserInfo"); + const authResult = useEvent(ExpoWeChat, "onAuthResult"); + + useEffect(() => { + ExpoQQ.init("102826474", ""); + ExpoWeChat.registerApp("wxdab3e21a1f7e392f", ""); + }, []); + + // 微信登录 + async function handleLoginWechat() { + if (loading) { + return; + } + setLoading(true); + + // 检查微信是否安装 + const isInstalled = await ExpoWeChat.isWXAppInstalled(); + if (!isInstalled) { + toast.warning(onLoginFinished?.errorMessage || "请先安装微信"); + setLoading(false); + return; + } + + setForm((val) => ({ + ...val, + type: 0, + })); + + ExpoWeChat.sendAuthRequest("snsapi_userinfo", "state123"); + } + + // QQ 登录 + async function handleLoginQQ() { + if (loading) { + return; + } + + setLoading(true); + + setForm((val) => ({ + ...val, + type: 1, + })); + await ExpoQQ.login(["get_user_info"]); + } + + useEffect(() => { + if (!onLoginFinished) { + return; + } + + if (!onLoginFinished.success) { + toast.warning(onLoginFinished?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权"); + setLoading(false); + return; + } + + const fetchUserInfo = async () => { + try { + const tokenInfo = await ExpoQQ.getLoginTokenInfo(); + if (!tokenInfo.openId) { + toast.warning(tokenInfo?.errorMessage || "授权失败,请检查手机是否安装 QQ 或确认授权"); + setLoading(false); + return; + } + + setForm((val) => ({ + ...val, + openid: String(tokenInfo.openId), + })); + + await ExpoQQ.sendGetUserInfoReq(); + } catch (e) { + console.log(e); + + setLoading(false); + } + }; + + fetchUserInfo(); + }, [onLoginFinished]); + + // QQ登录 + useEffect(() => { + const handleLogin = async () => { + if (!onGetUserInfo) { + setLoading(false); + return; + } + + try { + const loginRes = await loginQqApi({ + openid: form.openid, + nickname: onGetUserInfo.nickname || "", + avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2, + }); + + if (loginRes.isSuccess) { + requestAnimationFrame(() => { + userStore.setUserInfo({ + token: loginRes.data.token, + id: loginRes.data.userId, + nickname: loginRes.data.nickname, + avatar: loginRes.data.avatar, + }); + + userStore.setAuthExpired(false); + + if (loginRes.data.family_id === 0) { + router.replace("/family/entry"); + } else { + familyStore.setCurrentFamily({ + family_id: loginRes.data.family_id, + }); + + router.replace("/(tabs)/home"); + } + }); + } + } catch (err) { + console.error("登录接口出错", err); + } finally { + setLoading(false); + } + }; + + handleLogin(); + }, [onGetUserInfo]); + + // 微信登录 + useEffect(() => { + if (!authResult) return; + + const handleLogin = async () => { + if (authResult.errorCode === 0) { + try { + const loginRes = await loginWxApi({ + code: authResult.code, + }); + + if (loginRes.isSuccess) { + requestAnimationFrame(() => { + userStore.setUserInfo({ + token: loginRes.data.token, + id: loginRes.data.userId, + nickname: loginRes.data.nickname, + avatar: loginRes.data.avatar, + }); + + userStore.setAuthExpired(false); + + if (loginRes.data.family_id === 0) { + router.replace("/family/entry"); + } else { + familyStore.setCurrentFamily({ + family_id: loginRes.data.family_id, + }); + + router.replace("/(tabs)/home"); + } + }); + } + } catch (err) { + console.error("登录接口出错", err); + } finally { + setLoading(false); + } + } else { + console.log("授权失败:", authResult.errorMessage); + setLoading(false); + } + }; + + handleLogin(); + }, [authResult]); + + return ( + <> + + + + + Welcome to Allapp + + + + + + + + + + + + + + 登录即表示你同意我们的 + + 用户协议 + + 和 + + 隐私政策 + + + + + + ); +} diff --git a/code/app/app-rn4/src/app/family/member/[id].tsx b/code/app/app-rn4/src/app/family/member/[id].tsx new file mode 100644 index 00000000..b5490692 --- /dev/null +++ b/code/app/app-rn4/src/app/family/member/[id].tsx @@ -0,0 +1,299 @@ +import { useEffect, useState } from "react"; +import LayoutView from "@/components/layout/view"; +import { Text, View } from "react-native"; +import PageLayout from "@/layouts/PageLayout"; +import { useLocalSearchParams } from "expo-router"; +import { useUserStore, useFamilyStore } from "@/store"; +import { loadDataApi, saveDataApi } from "@/request/api"; +import { + Avatar, + Button, + ListGroup, + Separator, + PressableFeedback, + Dialog, + Spinner, +} from "heroui-native"; +import { + MessageCircleIcon, + VideoIcon, + PhoneIcon, + CakeIcon, + CalendarDaysIcon, + Trash2Icon, +} from "lucide-react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { withUniwind } from "uniwind"; +import { FadeIn } from "react-native-reanimated"; +import { toast } from "sonner-native"; +import { useRouter } from "expo-router"; + +const StyledIonicons = withUniwind(Ionicons); + +export default function FamilyMember() { + const { id } = useLocalSearchParams(); + + const router = useRouter(); + + const userStore = useUserStore(); + const familyStore = useFamilyStore(); + + const [memberInfo, setMemberInfo] = useState({}); + const [currentUserRole, setCurrentUserRole] = useState(1); + + // 移除家庭 + const [showRemove, setShowRemove] = useState(false); + const [removeLoading, setRemoveLoading] = useState(false); + + async function loadMemberInfo() { + const familyId = familyStore.currentFamily.family_id; + const currentUserId = userStore.userInfo.id; + + try { + const [memberRes, roleRes] = await Promise.all([ + loadDataApi({ + view_name: "v_family_member", + search_condition: "user_id = ? and family_id = ?", + search_columns: [], + order_by: "", + args: [id, familyId], + }), + loadDataApi({ + view_name: "v_family_member", + search_condition: "user_id = ? and family_id = ?", + search_columns: ["role"], + order_by: "", + args: [currentUserId, familyId], + }), + ]); + + if (memberRes.isSuccess && memberRes.data.length > 0) { + setMemberInfo(memberRes.data[0]); + } + + if (roleRes.isSuccess && roleRes.data.length > 0) { + setCurrentUserRole(roleRes.data[0].role); + } + } catch (err) { + console.error("loadMemberInfo error:", err); + } + } + + useEffect(() => { + if (!id) return; + + loadMemberInfo(); + }, [id]); + + async function handleRemove() { + const familyId = familyStore.currentFamily.family_id; + const currentUserId = userStore.userInfo.id; + + // 前端快速拦截 + if (currentUserRole !== 0) return; + + if (Number(id) === currentUserId) { + toast.error("无法移除自己"); + return; + } + + if (removeLoading) return; + + setRemoveLoading(true); + + try { + const roleRes = await loadDataApi({ + view_name: "v_family_member", + search_condition: "user_id = ? and family_id = ?", + search_columns: ["role"], + order_by: "", + args: [currentUserId, familyId], + }); + + if (!roleRes.isSuccess) { + return; + } + + if (roleRes.data.length === 0) { + toast.error("无权限操作"); + return; + } + + const latestRole = roleRes.data[0].role; + + if (latestRole !== 0) { + toast.error("无权限操作"); + return; + } + + const requestData = [ + { + table_name: "b_family_member", + key_field: "id", + inserts: [], + updates: [], + deletes: [ + { + id: memberInfo.id, + }, + ], + }, + ]; + + const res = await saveDataApi(requestData); + + if (res.isSuccess) { + toast.success("移除成功"); + setShowRemove(false); + + requestAnimationFrame(() => { + router.back(); + }); + } + } catch (e) { + console.error("handleRemove error:", e); + toast.error("操作失败"); + } finally { + setRemoveLoading(false); + } + } + + return ( + + + + + + + {memberInfo.nickname} + + + {memberInfo.nickname} + + + + + + + + + + + 家庭成员信息 + + + + + + + + + 姓名 + + + 张三 + + + + + + + + + + + + 手机号 + + + 18233334444 + + + + + + + + + + + + 生日 + + + 18233334444 + + + + + + + + + + + + 年龄 + + + 26 + + + + + + + {currentUserRole === 0 && ( + + + + )} + + + + + + + + + + 提示 + + + + + + 确定将 {memberInfo.nickname}{" "} + 从家庭中移除吗?被移除的成员将无法再访问家庭相关的任何信息。 + + + + + + + + + + + + + ); +} diff --git a/code/app/app-rn4/src/app/family/settings.tsx b/code/app/app-rn4/src/app/family/settings.tsx new file mode 100644 index 00000000..06f952cd --- /dev/null +++ b/code/app/app-rn4/src/app/family/settings.tsx @@ -0,0 +1,659 @@ +import { Ionicons } from "@expo/vector-icons"; +import LayoutView from "@/components/layout/view"; +import { View, Text, Keyboard, TouchableOpacity } from "react-native"; +import PageLayout from "@/layouts/PageLayout"; +import { + Button, + Separator, + Spinner, + BottomSheet, + Label, + TextField, + Input, + PressableFeedback, + useBottomSheetAwareHandlers, + ListGroup, + Dialog, + Avatar, + Chip, +} from "heroui-native"; +import { withUniwind } from "uniwind"; +import { forwardRef, useEffect, useRef, useState } from "react"; +import { loadDataApi, saveDataApi } from "@/request/api"; +import { useFamilyStore, useUserStore } from "@/store"; +import { FadeIn } from "react-native-reanimated"; +import { toast } from "sonner-native"; +import { router } from "expo-router"; +import { CopyIcon } from "lucide-react-native"; +import * as Clipboard from "expo-clipboard"; +import { formatRelativeTime } from "@/utils/time"; +import { UserRoundIcon } from "lucide-react-native"; + +const StyledIonicons = withUniwind(Ionicons); + +const BottomSheetTextInput = forwardRef>((props, ref) => { + const { onFocus, onBlur } = useBottomSheetAwareHandlers(); + + return ( + { + onFocus(e); + props.onFocus?.(e); + }} + onBlur={(e) => { + onBlur(e); + props.onBlur?.(e); + }} + /> + ); +}); + +export default function HomeSettings() { + const familyStore = useFamilyStore(); + const userStore = useUserStore(); + + const [family, setFamily] = useState({}); + const [familyMemeber, setFamilyMember] = useState({}); + + // 家庭名称 + const [showRenameFamily, setShowRenameFamily] = useState(false); + const [renameLoading, setRenameLoading] = useState(false); + + const [renameForm, setRenameForm] = useState({ + name: "", + }); + + const renameInputRef = useRef(null); + + // 我的昵称 + const [showRenameNickname, setShowRenameNickname] = useState(false); + const [nicknameLoading, setNicknameLoading] = useState(false); + + const [nicknameForm, setNicknameForm] = useState({ + nickname: "", + }); + + const nicknameInputRef = useRef(null); + + // 退出/解散 家庭 + const [showExit, setShowExit] = useState(false); + const [exitLoading, setExitLoading] = useState(false); + + // 邀请 + const [showInvite, setShowInvite] = useState(false); + + // 成员 + const [familyMembers, setFamilyMembers] = useState([]); + + useEffect(() => { + if (showRenameFamily) { + requestAnimationFrame(() => { + renameInputRef.current?.focus(); + }); + } else { + setRenameForm({ + name: "", + }); + setRenameLoading(false); + Keyboard.dismiss(); + } + }, [showRenameFamily]); + + useEffect(() => { + if (showRenameNickname) { + requestAnimationFrame(() => { + nicknameInputRef.current?.focus(); + }); + } else { + setNicknameForm({ + nickname: "", + }); + setNicknameLoading(false); + Keyboard.dismiss(); + } + }, [showRenameNickname]); + + // 加载 + async function loadFamilyData() { + try { + const familyRes = await loadDataApi({ + view_name: `b_family`, + search_condition: `id = ?`, + search_columns: [], + order_by: "", + args: [familyStore.currentFamily?.family_id], + }); + + if (familyRes.isSuccess && familyRes.data.length > 0) { + setFamily(familyRes.data[0]); + } + + const memberRes = await loadDataApi({ + view_name: `b_family_member`, + search_condition: `family_id = ? AND user_id = ?`, + search_columns: [], + order_by: "", + args: [familyStore.currentFamily.family_id, userStore.userInfo.id], + }); + + if (memberRes.isSuccess && memberRes.data.length > 0) { + setFamilyMember(memberRes.data[0]); + } + } catch (error) { + console.log(error); + } + } + + useEffect(() => { + loadFamilyData(); + loadMembers(); + }, []); + + async function handleRename() { + if (renameLoading) return; + + if (renameForm.name === "") { + toast.warning("家庭名称不能为空"); + return; + } + + if (renameForm.name === family.name) { + setShowRenameFamily(false); + setRenameForm({ + name: "", + }); + toast.success("家庭名称修改成功"); + return; + } + + try { + const requestData = []; + + requestData.push({ + table_name: "b_family", + key_field: "id", + inserts: [], + updates: [ + { + id: family.id, + name: renameForm.name, + }, + ], + deletes: [], + }); + + const saveRes = await saveDataApi(requestData); + + if (saveRes.isSuccess) { + setFamily((prev: any) => ({ + ...prev, + name: renameForm.name, + })); + + setShowRenameFamily(false); + setRenameForm({ + name: "", + }); + + toast.success("家庭名称修改成功"); + } + } catch (e) { + console.log(e); + } finally { + setRenameLoading(false); + } + } + + async function handleNickname() { + if (nicknameLoading) return; + + if (nicknameForm.nickname === "") { + toast.warning("昵称不能为空"); + return; + } + + if (nicknameForm.nickname === familyMemeber.nickname) { + setShowRenameNickname(false); + setNicknameForm({ + nickname: "", + }); + toast.success("昵称修改成功"); + return; + } + + try { + const requestData = []; + + requestData.push({ + table_name: "b_family_member", + key_field: "id", + inserts: [], + updates: [ + { + id: familyMemeber.id, + nickname: nicknameForm.nickname, + }, + ], + deletes: [], + }); + + const saveRes = await saveDataApi(requestData); + + if (saveRes.isSuccess) { + setFamilyMember((prev: any) => ({ + ...prev, + nickname: nicknameForm.nickname, + })); + + setShowRenameNickname(false); + setNicknameForm({ + nickname: "", + }); + + toast.success("昵称修改成功"); + } + } catch (e) { + console.log(e); + } finally { + setRenameLoading(false); + } + } + + async function handleExitFamily() { + if (exitLoading) return; + + try { + let isExit = true; + + const memberRes = await loadDataApi({ + view_name: `b_family_member`, + search_condition: `family_id = ? AND user_id = ?`, + search_columns: [], + order_by: "", + args: [familyStore.currentFamily.family_id, userStore.userInfo.id], + }); + + if (!memberRes.isSuccess || memberRes.data.length === 0) { + toast.success("退出家庭成功"); + familyStore.clear(); + + requestAnimationFrame(() => { + router.replace("/family/entry"); + }); + + return; + } + + const member = memberRes.data[0]; + + isExit = memberRes.data[0].role !== 0; + + const requestData = []; + + if (isExit) { + requestData.push({ + table_name: "b_family_member", + key_field: "id", + inserts: [], + updates: [], + deletes: [ + { + id: member.id, + }, + ], + }); + } else { + requestData.push({ + table_name: "b_family", + key_field: "id", + inserts: [], + updates: [], + deletes: [ + { + id: member.family_id, + }, + ], + }); + + requestData.push({ + table_name: "b_family_member", + key_field: "family_id", + inserts: [], + updates: [], + deletes: [ + { + family_id: member.family_id, + }, + ], + }); + } + + const saveRes = await saveDataApi(requestData); + + if (saveRes.isSuccess) { + toast.success(isExit ? "退出家庭成功" : "解散家庭成功"); + familyStore.clear(); + + requestAnimationFrame(() => { + router.replace("/family/entry"); + }); + } + } catch (e) { + console.log(e); + } finally { + setExitLoading(false); + } + } + + async function copyInviteCode() { + if (family.invite_code) { + await Clipboard.setStringAsync(family.invite_code); + toast.success("邀请码已复制"); + } else { + toast.error("邀请码无效"); + } + } + + async function loadMembers() { + let searchCondition = `family_id = ?`; + let args = [familyStore.currentFamily.family_id]; + + const loadRes = await loadDataApi({ + view_name: `v_family_member`, + search_condition: searchCondition, + search_columns: [], + order_by: "role ASC, create_time ASC", + args, + }); + + if (loadRes.isSuccess) { + setFamilyMembers(loadRes.data); + } + } + + return ( + + + + + 共 {familyMembers.length || 0} 位成员 + + + + {familyMembers.map((item: any) => ( + { + requestAnimationFrame(() => { + router.navigate(`/family/member/${item.user_id}` as any); + }); + }} + > + + + + {item.nickname} + + + + + {item.nickname} + + {item.role === 0 && ( + + 创建者 + + )} + + + + + {formatRelativeTime(item.last_login_time)} + + + + + + + ))} + + + 常用设置 + + { + setShowRenameFamily(true); + setRenameForm({ + name: family.name, + }); + }} + > + + + + + + 家庭名称 + + + {family.name} + + + + + + + { + setShowRenameNickname(true); + setNicknameForm({ + nickname: familyMemeber.nickname, + }); + }} + > + + + + + + 我的昵称 + + + {familyMemeber.nickname} + + + + + + + + setShowInvite(true)}> + + + + + + 邀请其他人 + + + + + + + + 其他操作 + + { + setShowExit(true); + }} + > + + + + + + 退出家庭 + + + + + + + + + + { + setShowRenameFamily(val); + }} + > + + + + + + + + setRenameForm({ + name: val, + }) + } + /> + + + + + + + + + { + setShowRenameNickname(val); + }} + > + + + + + + + + setNicknameForm({ + nickname: val, + }) + } + /> + + + + + + + + + + + + + + + 提示 + + + + + + {familyMemeber.role === 0 + ? "您是家庭管理员,确认要解散家庭吗?" + : "确认要退出家庭吗?"} + + + {familyMemeber.role === 0 && ( + + 你是家庭管理员,退出将导致家庭解散,不可恢复 + + )} + + + + + + + + + + + + { + setShowInvite(val); + }} + > + + + + + + + + + {family.invite_code} + + + + + + + + + + + + + ); +} diff --git a/code/app/app-rn4/src/app/family/user.tsx b/code/app/app-rn4/src/app/family/user.tsx new file mode 100644 index 00000000..fdebe302 --- /dev/null +++ b/code/app/app-rn4/src/app/family/user.tsx @@ -0,0 +1,15 @@ +import LayoutView from "@/components/layout/view"; +import { Text } from "react-native"; +import PageLayout from "@/layouts/PageLayout"; + +export default function UserSetting() { + return ( + + + + UserSetting + + + + ); +} diff --git a/code/app/app-rn4/src/app/index.tsx b/code/app/app-rn4/src/app/index.tsx index 99edaeb0..8eb619fa 100644 --- a/code/app/app-rn4/src/app/index.tsx +++ b/code/app/app-rn4/src/app/index.tsx @@ -1,30 +1,47 @@ import { useEffect } from "react"; import { useRouter } from "expo-router"; import { useUserStore, useFamilyStore } from "@/store"; +import { loadDataApi } from "@/request/api"; import * as SplashScreen from "expo-splash-screen"; +import { toast } from "sonner-native"; export default function Index() { const router = useRouter(); useEffect(() => { const run = async () => { - const { isLogged } = useUserStore.getState(); - const { inFamily } = useFamilyStore.getState(); - - console.log(useFamilyStore.getState().currentFamily); + const { isLogged, userInfo } = useUserStore.getState(); + const familyStore = useFamilyStore.getState(); if (!isLogged()) { - requestAnimationFrame(() => { - router.replace("/auth/login"); + router.replace("/auth/login"); + await SplashScreen.hideAsync(); + return; + } + + try { + const res = await loadDataApi({ + view_name: "b_family_member", + search_condition: "user_id = ?", + search_columns: ["family_id"], + order_by: "", + args: [userInfo.id], }); - } else if (!inFamily()) { - requestAnimationFrame(() => { + + if (res.isSuccess && res.data.length === 0) { + familyStore.clear(); + toast.warning("你还没有加入任何家庭,请先创建或加入一个家庭"); + router.replace("/family/entry"); - }); - } else { - requestAnimationFrame(() => { + } else { + familyStore.setCurrentFamily({ + family_id: res.data[0].family_id, + }); + router.replace("/(tabs)/home"); - }); + } + } catch (e) { + console.log(e); } await SplashScreen.hideAsync(); diff --git a/code/app/app-rn4/src/app/tab-space/index.tsx b/code/app/app-rn4/src/app/tab-space/index.tsx deleted file mode 100644 index 185f314e..00000000 --- a/code/app/app-rn4/src/app/tab-space/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from "react"; -import LayoutView from "@/components/layout/view"; -import { View, Text } from "react-native"; -import Navbar from "@/components/layout/navbar"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { EarthIcon } from "lucide-react-native"; -import { useRouter } from "expo-router"; - -export default function Space() { - const insets = useSafeAreaInsets(); - const router = useRouter(); - - return ( - - - - - Space - - - - - - { - router.back(); - }} - /> - - - - ); -} diff --git a/code/app/app-rn4/src/components/layout/navbar.tsx b/code/app/app-rn4/src/components/layout/navbar.tsx index d05d6611..84c7701e 100644 --- a/code/app/app-rn4/src/components/layout/navbar.tsx +++ b/code/app/app-rn4/src/components/layout/navbar.tsx @@ -128,6 +128,7 @@ export default function Navbar({ safeRender style={styles.groupShadow} > + { leftScale.value = withSpring(0.8); diff --git a/code/app/app-rn4/src/layouts/PageLayout.tsx b/code/app/app-rn4/src/layouts/PageLayout.tsx new file mode 100644 index 00000000..fa3b84f7 --- /dev/null +++ b/code/app/app-rn4/src/layouts/PageLayout.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { View } from "react-native"; +import { Slot, useRouter } from "expo-router"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import Navbar from "@/components/layout/navbar"; + +interface ModuleLayoutProps { + title?: string; + titleIcon?: any; + children?: React.ReactNode; +} + +export default function PageLayout({ title, titleIcon, children }: ModuleLayoutProps) { + const router = useRouter(); + const insets = useSafeAreaInsets(); + + function handleBack() { + router.back(); + } + + function handleClose() { + router.navigate("/(tabs)/home"); + } + + return ( + + {children ?? } + + + + + + + + ); +} diff --git a/code/app/app-rn4/src/store/family.ts b/code/app/app-rn4/src/store/family.ts index 9f802080..99ef080e 100644 --- a/code/app/app-rn4/src/store/family.ts +++ b/code/app/app-rn4/src/store/family.ts @@ -7,19 +7,23 @@ interface Family { } interface FamilyState { - currentFamily: Family | null; + currentFamily: Family; - setCurrentFamily: (family: Family | null) => void; + setCurrentFamily: (family: Family) => void; clear: () => void; inFamily: () => boolean; } +const defaultFamily: Family = { + family_id: 0, +}; + export const useFamilyStore = create()( persist( (set, get) => ({ - currentFamily: null, + currentFamily: defaultFamily, setCurrentFamily: (family) => set({ @@ -28,13 +32,13 @@ export const useFamilyStore = create()( clear: () => set({ - currentFamily: null, + currentFamily: defaultFamily, }), inFamily: () => { const currentFamily = get().currentFamily; - return currentFamily !== null && currentFamily.family_id !== 0; + return currentFamily.family_id !== 0; }, }), { diff --git a/code/app/app-rn4/src/utils/time.ts b/code/app/app-rn4/src/utils/time.ts new file mode 100644 index 00000000..0f9df483 --- /dev/null +++ b/code/app/app-rn4/src/utils/time.ts @@ -0,0 +1,150 @@ +import dayjs from "dayjs"; + +/** + * 获取当前日期,默认格式: YYYY-MM-DD + */ +export function getCurrentDate(format = "YYYY-MM-DD"): string { + return dayjs().format(format); +} + +/** + * 获取当前日期时间,默认格式: YYYY-MM-DD HH:mm:ss + */ +export function getCurrentDateTime(format = "YYYY-MM-DD HH:mm:ss"): string { + return dayjs().format(format); +} + +/** + * 获取当前时间,默认格式: HH:mm:ss + */ +export function getCurrentTime(format = "HH:mm:ss"): string { + return dayjs().format(format); +} + +/** + * 获取当前年月,默认格式: YYYY-MM + */ +export function getCurrentYearMonth(format = "YYYY-MM"): string { + return dayjs().format(format); +} + +export function getCurrentYear(): string { + return dayjs().format("YYYY"); +} + +/** 字符串 / 数字 / Date → Date(无效返回 null) */ +export function toDate(input: string | number | Date | undefined | null): Date { + if (!input) return new Date(); + if (input instanceof Date) return input; + + const d = dayjs(input); + return d.isValid() ? d.toDate() : new Date(); +} + +/** Date → YYYY-MM-DD */ +export function toDateString(date: string | number | Date): string { + const d = dayjs(toDate(date)); + return d.isValid() ? d.format("YYYY-MM-DD") : ""; +} + +/** Date → YYYY-MM */ +export function toYearMonthString(date: string | number | Date): string { + const d = dayjs(toDate(date)); + return d.isValid() ? d.format("YYYY-MM") : ""; +} + +/** Date → HH:mm:ss */ +export function toTimeString(date: string | number | Date): string { + const d = dayjs(toDate(date)); + return d.isValid() ? d.format("HH:mm:ss") : ""; +} + +/** Date → YYYY-MM-DD HH:mm:ss */ +export function toDateTimeString(date: string | number | Date): string { + const d = dayjs(toDate(date)); + return d.isValid() ? d.format("YYYY-MM-DD HH:mm:ss") : ""; +} + +export function isInMonth(date: string, month: string): boolean { + // date 形如 "2025-01-12" + // month 形如 "2025-01" + return dayjs(date).format("YYYY-MM") === month; +} + +// 获取本月的第一天 +export function getStartOfMonth() { + return dayjs().startOf("month").format("YYYY-MM-DD"); +} + +// 获取本月的最后一天 +export function getEndOfMonth() { + return dayjs().endOf("month").format("YYYY-MM-DD"); +} + +export function normalizeDateTime(input?: string | Date | dayjs.Dayjs): string { + if (!input) return ""; + + const raw = typeof input === "string" ? input.trim() : input; + let d = dayjs(raw); + + if (!d.isValid()) return ""; + + // string 才需要“猜精度”,Date / dayjs 默认是完整的 + if (typeof raw === "string") { + if (/^\d{4}$/.test(raw)) { + d = d.startOf("year"); + } else if (/^\d{4}-\d{2}$/.test(raw)) { + d = d.startOf("month"); + } else if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) { + d = d.startOf("day"); + } else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(raw)) { + d = d.second(0); + } + } + + return d.format("YYYY-MM-DD HH:mm:ss"); +} + +/** + * 格式化时间为相对时间 + * @param timestamp 秒级或毫秒级时间戳 + */ +export function formatRelativeTime(timestamp: number): string { + if (!timestamp) return "未活跃"; + + // 兼容秒级时间戳 + const time = timestamp.toString().length === 10 ? dayjs(timestamp * 1000) : dayjs(timestamp); + + const now = dayjs(); + + const diffSeconds = now.diff(time, "second"); + const diffMinutes = now.diff(time, "minute"); + const diffHours = now.diff(time, "hour"); + const diffDays = now.diff(time, "day"); + + if (diffSeconds < 30) { + return "刚刚"; + } + + if (diffMinutes < 60) { + return `${diffMinutes}分钟前`; + } + + if (diffHours < 24) { + return `${diffHours}小时前`; + } + + if (diffDays === 1) { + return "昨天"; + } + + if (diffDays < 7) { + return `${diffDays}天前`; + } + + if (time.year() === now.year()) { + return time.format("MM-DD HH:mm"); + } + + return time.format("YYYY-MM-DD"); +} diff --git a/code/app/app-rn4/tsconfig.json b/code/app/app-rn4/tsconfig.json index 41bb6387..d8870c52 100644 --- a/code/app/app-rn4/tsconfig.json +++ b/code/app/app-rn4/tsconfig.json @@ -18,5 +18,5 @@ "env.js", ".expo/types/**/*.ts", "expo-env.d.ts" - ] +, "src/app/auth/login_2" ] } diff --git a/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties b/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties index 05bead7b..8d6733ef 100644 --- a/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties +++ b/code/container-ocr/ocr-springboot/src/main/resources/dbconfigs/g3hd.properties @@ -1,10 +1,10 @@ -#url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR -#username=sa -#password=zhang520.. -#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver - -url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR +url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR username=sa -password=123456@aaa +password=zhang520.. driver=com.microsoft.sqlserver.jdbc.SQLServerDriver +#url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR +#username=sa +#password=123456@aaa +#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver + diff --git a/code/container-ocr/ocr-vue/.env b/code/container-ocr/ocr-vue/.env index f3df623a..0291d152 100644 --- a/code/container-ocr/ocr-vue/.env +++ b/code/container-ocr/ocr-vue/.env @@ -10,7 +10,7 @@ PUBLIC_SERVER_PORT=8080 # API # 后端 API 基础地址 -PUBLIC_API_BASE_URL=http://ser369.g3soft.cn:80/api -# PUBLIC_API_BASE_URL=http://192.168.1.137:80/api +# PUBLIC_API_BASE_URL=http://ser369.g3soft.cn:80/api +PUBLIC_API_BASE_URL=http://192.168.1.137:80/api PUBLIC_ALIYUN_BUCKET = g3hd diff --git a/code/container-ocr/ocr-vue/components.d.ts b/code/container-ocr/ocr-vue/components.d.ts index 52fa9721..39ff9c4a 100644 --- a/code/container-ocr/ocr-vue/components.d.ts +++ b/code/container-ocr/ocr-vue/components.d.ts @@ -18,7 +18,6 @@ declare module 'vue' { ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDivider: typeof import('element-plus/es')['ElDivider'] - ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] @@ -28,23 +27,16 @@ declare module 'vue' { ElInput: typeof import('element-plus/es')['ElInput'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] - ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElResult: typeof import('element-plus/es')['ElResult'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] - ElSelect: typeof import('element-plus/es')['ElSelect'] ElSplitter: typeof import('element-plus/es')['ElSplitter'] ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] - ElTable: typeof import('element-plus/es')['ElTable'] - ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTabPane: typeof import('element-plus/es')['ElTabPane'] - ElTabs: typeof import('element-plus/es')['ElTabs'] - ElText: typeof import('element-plus/es')['ElText'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTree: typeof import('element-plus/es')['ElTree'] G3Column: typeof import('./src/components/g3-table/g3-column.vue')['default']