This commit is contained in:
oneao committed 2026-04-16 21:29:38 +08:00
1 parent 6300778150
commit e893ef2e48
27 files changed
+870 -33

No files matched your search

+24 -3
View File
@@ -5,8 +5,12 @@
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "apprn",
"userInterfaceStyle": "automatic",
"scheme": [
"oneao",
"tencent102826474",
"wxdab3e21a1f7e392f"
],
"ios": {
"icon": "./assets/expo.icon"
},
@@ -18,7 +22,7 @@
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"predictiveBackGestureEnabled": false,
"package": "com.anonymous.apprn"
"package": "cn.oneao.app"
},
"web": {
"output": "static",
@@ -26,6 +30,7 @@
"bundler": "metro"
},
"plugins": [
"./plugins/withAndroidSignature",
"expo-router",
[
"expo-splash-screen",
@@ -37,7 +42,23 @@
}
}
],
"expo-web-browser"
"expo-web-browser",
[
"expo-qq",
{
"appId": "102826474"
}
],
[
"expo-build-properties",
{
"android": {
"usesCleartextTraffic": true,
"extraProguardRules": "-keep class com.tencent.mm.opensdk.** { *; }\n-keep class com.tencent.wxop.** { *; }\n-keep class com.tencent.mm.sdk.** { *; }"
}
}
],
"expo-wechat"
],
"experiments": {
"typedRoutes": true,
@@ -0,0 +1,17 @@
{
"android": {
"keystore": {
"keystorePath": "credentials/oneao-android.keystore",
"keystorePassword": "zhang520..",
"keyAlias": "oneao-android",
"keyPassword": "zhang520.."
}
},
"ios": {
"provisioningProfilePath": "ios/certs/profile.mobileprovision",
"distributionCertificate": {
"path": "ios/certs/dist-cert.p12",
"password": "password"
}
}
}
Binary file not shown.
+3
View File
@@ -24,6 +24,7 @@
"axios": "^1.15.0",
"expo": "~55.0.15",
"expo-blur": "~55.0.14",
"expo-build-properties": "~55.0.13",
"expo-constants": "~55.0.14",
"expo-device": "~55.0.15",
"expo-font": "~55.0.6",
@@ -31,12 +32,14 @@
"expo-image": "~55.0.8",
"expo-linear-gradient": "~55.0.13",
"expo-linking": "~55.0.13",
"expo-qq": "^0.1.8",
"expo-router": "~55.0.12",
"expo-splash-screen": "~55.0.18",
"expo-status-bar": "~55.0.5",
"expo-symbols": "~55.0.7",
"expo-system-ui": "~55.0.15",
"expo-web-browser": "~55.0.14",
"expo-wechat": "^0.2.6",
"heroui-native": "^1.0.1",
"lucide-react-native": "^1.8.0",
"react": "19.2.0",
@@ -0,0 +1,95 @@
const fs = require("node:fs");
const path = require("node:path");
const { withAppBuildGradle } = require("expo/config-plugins");
const CREDENTIAL_PATH = path.resolve(
__dirname,
"../credentials/credentials.json"
);
const KEYSTORE_PATH = path.resolve(
__dirname,
"../credentials/oneao-android.keystore"
);
module.exports = function withAndroidSignature(config) {
return withAppBuildGradle(config, (config) => {
if (config.modResults.language !== "groovy") {
throw new Error("only groovy build.gradle is supported");
}
config.modResults.contents = setAndroidSignature(
config.modResults.contents
);
return config;
});
};
// ======================== 主入口 ========================
function setAndroidSignature(appBuildGradle) {
if (!fs.existsSync(CREDENTIAL_PATH)) {
console.warn("⚠️ credentials.json 不存在,跳过签名配置");
return appBuildGradle;
}
const info = JSON.parse(
fs.readFileSync(CREDENTIAL_PATH, "utf8")
);
let output = appBuildGradle;
// ⭐ 1. 重写 signingConfigs(关键)
output = replaceSigningConfigs(output, info);
// ⭐ 2. 确保所有 debug/release 使用统一 keystore
output = unifySigningUsage(output);
return output;
}
// ======================== ⭐核心:重写 signingConfigs ========================
function replaceSigningConfigs(appBuildGradle, info) {
const block = `
signingConfigs {
release {
storeFile file(${JSON.stringify(KEYSTORE_PATH)})
storePassword ${JSON.stringify(info.android.keystore.keystorePassword)}
keyAlias ${JSON.stringify(info.android.keystore.keyAlias)}
keyPassword ${JSON.stringify(info.android.keystore.keyPassword)}
}
debug {
storeFile file(${JSON.stringify(KEYSTORE_PATH)})
storePassword ${JSON.stringify(info.android.keystore.keystorePassword)}
keyAlias ${JSON.stringify(info.android.keystore.keyAlias)}
keyPassword ${JSON.stringify(info.android.keystore.keyPassword)}
}
}
`;
const regex = /signingConfigs\s*\{[\s\S]*?\n\s*\}/m;
if (regex.test(appBuildGradle)) {
return appBuildGradle.replace(regex, block.trim());
}
// 没有就插入
return appBuildGradle.replace(
/(android\s*\{)/,
`$1\n${block.trim()}`
);
}
// ======================== ⭐统一使用 signingConfig ========================
function unifySigningUsage(appBuildGradle) {
let output = appBuildGradle;
// debug -> release(你如果想 debug 也用 release,可以改这里)
output = output.replace(
/signingConfig\s+signingConfigs\.debug/g,
"signingConfig signingConfigs.release"
);
return output;
}
+47
View File
@@ -41,6 +41,9 @@ importers:
expo-blur:
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))(react@19.2.0)
expo-build-properties:
specifier: ~55.0.13
version: 55.0.13(expo@55.0.15)
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)
@@ -62,6 +65,9 @@ importers:
expo-linking:
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)(typescript@5.9.3)
expo-qq:
specifier: ^0.1.8
version: 0.1.8(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-router:
specifier: ~55.0.12
version: 55.0.12(@expo/log-box@55.0.10)(@expo/metro-runtime@55.0.9)(@types/react@19.2.14)(expo-constants@55.0.14)(expo-font@55.0.6)(expo-linking@55.0.13)(expo@55.0.15)(react-dom@19.2.0(react@19.2.0))(react-native-gesture-handler@2.30.1(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-reanimated@4.2.1(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))(react-native-safe-area-context@5.6.2(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-screens@4.23.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-web@0.21.2(react-dom@19.2.0(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)
@@ -80,6 +86,9 @@ importers:
expo-web-browser:
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))
expo-wechat:
specifier: ^0.2.6
version: 0.2.6(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)
heroui-native:
specifier: ^1.0.1
version: 1.0.2(f8f492cc9ab1e25a4f7a8dde00438ce1)
@@ -2205,6 +2214,11 @@ packages:
react: '*'
react-native: '*'
expo-build-properties@55.0.13:
resolution: {integrity: sha512-UYZhUKyh7YQhbJdkBvo68WUQ7fOtZeSV7F8kfYkjEiN/ADRHG0WfEIiddvGfi9cH/5iwpptv/+Lu5cx6uvfegA==}
peerDependencies:
expo: '*'
expo-constants@55.0.14:
resolution: {integrity: sha512-l23QVQCYBPKT5zbxxZdJeuhiunadvWdjcQ9+GC8h+02jCoLmWRk20064nCINnQTP3Hf+uLPteUiwYrJd0e446w==}
peerDependencies:
@@ -2280,6 +2294,13 @@ packages:
react-native-worklets:
optional: true
expo-qq@0.1.8:
resolution: {integrity: sha512-Khuu4LWOjpYGGBamtUFBxaA5q8oIcFiVgXkyRVqfNdSEP6Z0Fy3eBj3HuXP8mxonT/hJjG3GCX7ypJD7M9nU/g==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
expo-router@55.0.12:
resolution: {integrity: sha512-Bm6IhI0Kl5/tDlCHPms8jDqy1O6HLHIOrMsEmmAQ5Lgg5UBtDfRThEyHPVOLNTOs8e7/bG/Ftz6a4UgQVA+NhQ==}
peerDependencies:
@@ -2354,6 +2375,13 @@ packages:
expo: '*'
react-native: '*'
expo-wechat@0.2.6:
resolution: {integrity: sha512-hLWFPDqSKwEgG8Jm0cTthyQftD/ip9xjJLQUEd+fJJVq8YEiEt0gJCCgCEMbr1SrEjoK4TUlkG/HETZobNReuQ==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
expo@55.0.15:
resolution: {integrity: sha512-sHIvqG477UU1jZHhaexXbUgsU7y+xnYZqDW1HrUkEBYiuEb5lobvWLmwea76EBVkityQx46UDtepFtarpUJQqQ==}
hasBin: true
@@ -6385,6 +6413,13 @@ snapshots:
react: 19.2.0
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
expo-build-properties@55.0.13(expo@55.0.15):
dependencies:
'@expo/schema-utils': 55.0.3
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)
resolve-from: 5.0.0
semver: 7.7.4
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)
@@ -6467,6 +6502,12 @@ snapshots:
optionalDependencies:
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)
expo-qq@0.1.8(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-router@55.0.12(@expo/log-box@55.0.10)(@expo/metro-runtime@55.0.9)(@types/react@19.2.14)(expo-constants@55.0.14)(expo-font@55.0.6)(expo-linking@55.0.13)(expo@55.0.15)(react-dom@19.2.0(react@19.2.0))(react-native-gesture-handler@2.30.1(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-reanimated@4.2.1(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))(react-native-safe-area-context@5.6.2(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-screens@4.23.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-web@0.21.2(react-dom@19.2.0(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):
dependencies:
'@expo/log-box': 55.0.10(@expo/dom-webview@55.0.5)(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)
@@ -6556,6 +6597,12 @@ snapshots:
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-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
expo-wechat@0.2.6(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@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):
dependencies:
'@babel/runtime': 7.29.2
-12
View File
@@ -1,12 +0,0 @@
import type { Method } from "alova";
import type { AxiosError } from "axios";
import { axiosRequestAdapter } from "@alova/adapter-axios";
import { createAlova } from "alova";
import { toast } from "sonner-native";
const alovaInstance = createAlova({
baseURL: "",
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
});
+3
View File
@@ -7,6 +7,7 @@ import * as SplashScreen from "expo-splash-screen";
import "@/global.css";
import { useEffect, useState } from "react";
import { Stack } from "expo-router";
import { Toaster } from "sonner-native";
SplashScreen.preventAutoHideAsync();
@@ -52,6 +53,8 @@ export default function RootLayout() {
{!isDev && <StatusBar style={isDark ? "light" : "dark"} />}
</HeroUINativeProvider>
</ThemeProvider>
<Toaster duration={1000} />
</GestureHandlerRootView>
);
}
@@ -1,11 +1,24 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import LayoutView from "@/components/layout/view";
import { Text } from "react-native";
import EmptyState from "@/components/EmptyStatus";
import { loadDataApi } from "@/request/api";
export default function Home() {
const [dataList, setDataList] = useState<any>([]);
useEffect(() => {
loadDataApi({
view_name: "v_finance_record",
search_condition: "",
search_columns: [],
order_by: "",
args: [],
}).then((res) => {
console.log(res);
});
}, []);
return (
<LayoutView>
<LayoutView.SectionView<any>
+32
View File
@@ -0,0 +1,32 @@
import type { LoginQqParams, LoginResult, LoginWxParams } from "@/types/auth";
import alovaInstance from "./index";
export function loginQqApi(params: LoginQqParams) {
return alovaInstance.Post<ResponseData<LoginResult>>(`/auth/login/qq`, params);
}
export function loginWxApi(params: LoginWxParams) {
return alovaInstance.Post<ResponseData<LoginResult>>(`/auth/login/wechat`, params);
}
interface LoadDataBySqlParams {
sql: string;
args: any[];
}
export function loadDataBySqlApi(params: LoadDataBySqlParams) {
return alovaInstance.Post<ResponseData<any[]>>(`/data/loadDataBySql`, params);
}
interface LoadDataParams {
view_name: string;
search_condition: string;
order_by: string;
search_columns: string[];
args: string[];
}
export function loadDataApi(params: LoadDataParams) {
return alovaInstance.Post<ResponseData<any[]>>(`/data/loadData`, params);
}
+81
View File
@@ -0,0 +1,81 @@
import type { Method } from "alova";
import type { AxiosError } from "axios";
import { axiosRequestAdapter } from "@alova/adapter-axios";
import { createAlova } from "alova";
import { toast } from "sonner-native";
import { useUserStore } from "@/store/user";
enum HttpCode {
Success = 1000,
Unauthorized = 1001,
Fail = 500,
BusinessError = 2000,
SystemError = 3000,
}
// 仅保留「无需登录」接口
const WhiteList = ["/auth/login/**"];
function isWhiteListed(url: string) {
return WhiteList.some((pattern) => {
if (pattern.endsWith("/**")) {
const base = pattern.slice(0, -3);
return url.startsWith(base);
}
return url === pattern;
});
}
const alovaInstance = createAlova({
baseURL: process.env.EXPO_PUBLIC_API_URL,
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
responded: {
onSuccess: async (response, _method: Method) => {
const resData = response.data as ResponseData<any>;
const result = {
...resData,
isSuccess: resData.code === HttpCode.Success,
};
if (resData.code === HttpCode.Unauthorized) {
toast.error("登录状态已过期,请重新登录");
useUserStore.getState().setAuthExpired(true);
return result;
} else if (resData.code === HttpCode.BusinessError) {
toast.error(resData.message || "业务异常,请稍后再试");
return result;
} else if (resData.code === HttpCode.SystemError) {
toast.error(resData.message || "系统异常,请稍后再试");
return result;
}
return result;
},
onError: (error: AxiosError) => {
return Promise.reject(error);
},
},
beforeRequest(method) {
// 白名单接口直接放行
if (isWhiteListed(method.url)) return;
const userStore = useUserStore.getState();
// 未登录直接拦截
if (!userStore.isLogged()) {
userStore.setAuthExpired(true);
return Promise.reject(new Error("未登录"));
}
// 注入 JWT
method.config.headers.Token = userStore.userInfo!.token;
},
});
export default alovaInstance;
+17
View File
@@ -4,9 +4,15 @@ import { createJSONStorage, persist } from "zustand/middleware";
interface UserState {
userInfo: UserInfo;
isLogged: () => boolean;
setUserInfo: (userInfo: UserInfo) => void;
clear: () => void;
authExpired: boolean;
setAuthExpired: (v: boolean) => void;
}
const defaultUserInfo: UserInfo = {
@@ -21,6 +27,8 @@ export const useUserStore = create<UserState>()(
(set, get) => ({
userInfo: defaultUserInfo,
authExpired: false,
isLogged: () => !!get().userInfo?.token,
setUserInfo: (userInfo) =>
@@ -33,10 +41,19 @@ export const useUserStore = create<UserState>()(
userInfo: defaultUserInfo,
});
},
setAuthExpired: (v: boolean) =>
set({
authExpired: v,
}),
}),
{
name: "user-storage",
storage: createJSONStorage(() => AsyncStorage),
partialize: (state) => ({
userInfo: state.userInfo,
}),
},
),
);
+16
View File
@@ -0,0 +1,16 @@
export interface LoginQqParams {
openid: string
nickname: string
avatar: string
}
export interface LoginWxParams {
code: string
}
export interface LoginResult {
token: string
userId: number
nickname: string
avatar: string
}
+8
View File
@@ -10,6 +10,14 @@ declare global {
SystemError = 3000,
}
interface ResponseData<T> {
track_id: string;
code: number;
message: string;
data: T;
isSuccess: boolean;
}
interface UserInfo {
id: number;
token: string;