This commit is contained in:
oneao committed 2026-04-08 17:32:07 +08:00
1 parent 4274b26dd9
commit f28107399e
183 files changed
+32632 -20013

No files matched your search

+6 -27
View File
@@ -1,34 +1,13 @@
import { useEffect } from 'react'
import { Appearance, Platform, StyleSheet, Text, View } from 'react-native'
import { Button, ButtonIcon, ButtonSpinner, ButtonText } from '@/components/ui/button'
import { useAppStore } from '@/store/app'
import { View } from 'react-native'
import { Button, ButtonText } from '@/components/ui/button'
// import { useAppStore } from '@/store/app'
export default function HomeScreen() {
const appStore = useAppStore()
function handleSwitchMode() {
if (appStore.themeScheme === 'dark') {
appStore.setThemeScheme('light')
}
else if (appStore.themeScheme === 'light') {
appStore.setThemeScheme('dark')
}
}
// const themeScheme = useAppStore(s => s.themeScheme)
// const setThemeScheme = useAppStore(s => s.setThemeScheme)
return (
<View className="flex-1 items-center justify-center dark:bg-black">
<Button
action="primary"
size="sm"
isDisabled={false}
onPress={() => {
handleSwitchMode()
}}
>
<ButtonText>Hello World</ButtonText>
</Button>
<View className="flex-1 items-center justify-center gap-4 dark:bg-black">
</View>
)
}
+7 -12
View File
@@ -2,31 +2,26 @@ import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native
import { Stack } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
import { useEffect } from 'react'
import { Appearance } from 'react-native'
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
import { useIsDark } from '@/hooks/use-color-scheme'
import { useColorScheme } from '@/hooks/use-color-scheme'
import 'react-native-reanimated'
import '@/styles/global.css'
export default function RootLayout() {
const isDark = useIsDark()
const colorScheme = useColorScheme()
useEffect(() => {
const sub = Appearance.addChangeListener(({ colorScheme }) => {
console.log('system changed:', colorScheme)
})
return () => sub.remove()
}, [])
console.log(colorScheme)
}, [colorScheme])
return (
<GluestackUIProvider mode={isDark ? 'dark' : 'light'}>
<ThemeProvider value={isDark ? DarkTheme : DefaultTheme}>
<GluestackUIProvider mode={colorScheme === 'dark' ? 'dark' : 'light'}>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
</Stack>
<StatusBar style={isDark ? 'light' : 'dark'} />
<StatusBar style="auto" />
</ThemeProvider>
</GluestackUIProvider>
)
+1 -16
View File
@@ -1,16 +1 @@
import { useEffect, useState } from 'react'
import { Appearance, useColorScheme } from 'react-native'
import { useAppStore } from '@/store/app'
function useIsDark() {
const system = useColorScheme()
const themeScheme = useAppStore(s => s.themeScheme)
if (themeScheme === 'auto') {
return system === 'dark'
}
return themeScheme === 'dark'
}
export { useColorScheme, useIsDark }
export { useColorScheme } from 'react-native';
@@ -0,0 +1,21 @@
import { useEffect, useState } from 'react';
import { useColorScheme as useRNColorScheme } from 'react-native';
/**
* To support static rendering, this value needs to be re-calculated on the client side for web
*/
export function useColorScheme() {
const [hasHydrated, setHasHydrated] = useState(false);
useEffect(() => {
setHasHydrated(true);
}, []);
const colorScheme = useRNColorScheme();
if (hasHydrated) {
return colorScheme;
}
return 'light';
}
+12 -14
View File
@@ -2,22 +2,20 @@ import AsyncStorage from '@react-native-async-storage/async-storage'
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'
export type ThemeScheme = 'light' | 'dark' | 'auto'
interface AppState {
themeScheme: ThemeScheme
setThemeScheme: (theme: ThemeScheme) => void
}
export const useAppStore = create<AppState>()(
persist(
set => ({
themeScheme: 'auto',
setThemeScheme: theme => set({ themeScheme: theme }),
}),
{
name: 'app-storage',
storage: createJSONStorage(() => AsyncStorage),
},
),
)
// export const useAppStore = create<AppState>()(
// persist(
// set => ({
// themeScheme: 'system',
// setThemeScheme: theme => set({ themeScheme: theme }),
// }),
// {
// name: 'app-storage',
// storage: createJSONStorage(() => AsyncStorage),
// },
// ),
// )
+1 -1
View File
@@ -1,5 +1,5 @@
export {}
declare global {
type ThemeScheme = 'light' | 'dark' | 'auto'
type ThemeScheme = 'light' | 'dark' | 'system'
}
+43
View File
@@ -0,0 +1,43 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
app-example
# generated native folders
/ios
/android
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": []
}
+1
View File
@@ -0,0 +1 @@
{ "recommendations": ["expo.vscode-expo-tools"] }
+17
View File
@@ -0,0 +1,17 @@
{
"oxc.fmt.configPath": ".oxfmtrc.json",
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
}
}
+50
View File
@@ -0,0 +1,50 @@
# Welcome to your Expo app 👋
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
## Get started
1. Install dependencies
```bash
npm install
```
2. Start the app
```bash
npx expo start
```
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
## Get a fresh project
When you're ready, run:
```bash
npm run reset-project
```
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
## Learn more
To learn more about developing your project with Expo, look at the following resources:
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
## Join the community
Join our community of developers creating universal apps.
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
+49
View File
@@ -0,0 +1,49 @@
{
"expo": {
"name": "app-rn2",
"slug": "app-rn2",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "apprn2",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"backgroundImage": "./assets/images/android-icon-background.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/images/splash-icon.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#000000"
}
}
]
],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import { useColorScheme } from "@/hooks/use-color-scheme";
import { Stack } from "expo-router";
import { useEffect } from "react";
import "@/styles/global.css";
export default function RootLayout() {
const colorScheme = useColorScheme();
useEffect(() => {
console.log(colorScheme);
}, [colorScheme]);
return <Stack />;
}
+9
View File
@@ -0,0 +1,9 @@
import { Text, View } from "react-native";
export default function App() {
return (
<View className="flex-1 items-center justify-center bg-white dark:bg-black">
<Text className="text-xl font-bold text-blue-500 dark:text-white">Welcome to Nativewind!</Text>
</View>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+6
View File
@@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [["babel-preset-expo", { jsxImportSource: "nativewind" }], "nativewind/babel"],
};
};
@@ -0,0 +1 @@
export { useColorScheme } from "react-native";
@@ -0,0 +1,21 @@
import { useEffect, useState } from "react";
import { useColorScheme as useRNColorScheme } from "react-native";
/**
* To support static rendering, this value needs to be re-calculated on the client side for web
*/
export function useColorScheme() {
const [hasHydrated, setHasHydrated] = useState(false);
useEffect(() => {
setHasHydrated(true);
}, []);
const colorScheme = useRNColorScheme();
if (hasHydrated) {
return colorScheme;
}
return "light";
}
+21
View File
@@ -0,0 +1,21 @@
/**
* Learn more about light and dark modes:
* https://docs.expo.dev/guides/color-schemes/
*/
import { Colors } from "@/constants/theme";
import { useColorScheme } from "@/hooks/use-color-scheme";
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
) {
const theme = useColorScheme() ?? "light";
const colorFromProps = props[theme];
if (colorFromProps) {
return colorFromProps;
} else {
return Colors[theme][colorName];
}
}
+6
View File
@@ -0,0 +1,6 @@
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./styles/global.css" });
+3
View File
@@ -0,0 +1,3 @@
/// <reference types="nativewind/types" />
// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
+12829
View File
File diff suppressed because it is too large. Load diff
+54
View File
@@ -0,0 +1,54 @@
{
"name": "app-rn2",
"version": "1.0.0",
"private": true,
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"lint": "expo lint",
"fmt": "oxfmt",
"fmt:check": "oxfmt --check"
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"expo": "~54.0.33",
"expo-constants": "~18.0.13",
"expo-font": "~14.0.11",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
"expo-linking": "~8.0.11",
"expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
"expo-symbols": "~1.0.8",
"expo-system-ui": "~6.0.9",
"expo-web-browser": "~15.0.10",
"nativewind": "^4.2.3",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-css-interop": "^0.2.3",
"react-native-gesture-handler": "~2.28.0",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@types/react": "~19.1.0",
"babel-preset-expo": "^55.0.16",
"oxfmt": "^0.44.0",
"prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.19",
"typescript": "~5.9.2"
}
}
+7897
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+207
View File
@@ -0,0 +1,207 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: process.env.DARK_MODE ? process.env.DARK_MODE : "class",
content: [
"./app/**/*.{html,js,jsx,ts,tsx,mdx}",
"./components/**/*.{html,js,jsx,ts,tsx,mdx}",
"./utils/**/*.{html,js,jsx,ts,tsx,mdx}",
"./*.{html,js,jsx,ts,tsx,mdx}",
"./src/**/*.{html,js,jsx,ts,tsx,mdx}",
],
presets: [require("nativewind/preset")],
important: "html",
safelist: [
{
pattern:
// eslint-disable-next-line regexp/no-dupe-disjunctions
/(bg|border|text|stroke|fill)-(primary|secondary|tertiary|error|success|warning|info|typography|outline|background|indicator)-(0|50|100|200|300|400|500|600|700|800|900|950|white|gray|black|error|warning|muted|success|info|light|dark|primary)/,
},
],
theme: {
extend: {
colors: {
primary: {
0: "rgb(var(--color-primary-0)/<alpha-value>)",
50: "rgb(var(--color-primary-50)/<alpha-value>)",
100: "rgb(var(--color-primary-100)/<alpha-value>)",
200: "rgb(var(--color-primary-200)/<alpha-value>)",
300: "rgb(var(--color-primary-300)/<alpha-value>)",
400: "rgb(var(--color-primary-400)/<alpha-value>)",
500: "rgb(var(--color-primary-500)/<alpha-value>)",
600: "rgb(var(--color-primary-600)/<alpha-value>)",
700: "rgb(var(--color-primary-700)/<alpha-value>)",
800: "rgb(var(--color-primary-800)/<alpha-value>)",
900: "rgb(var(--color-primary-900)/<alpha-value>)",
950: "rgb(var(--color-primary-950)/<alpha-value>)",
},
secondary: {
0: "rgb(var(--color-secondary-0)/<alpha-value>)",
50: "rgb(var(--color-secondary-50)/<alpha-value>)",
100: "rgb(var(--color-secondary-100)/<alpha-value>)",
200: "rgb(var(--color-secondary-200)/<alpha-value>)",
300: "rgb(var(--color-secondary-300)/<alpha-value>)",
400: "rgb(var(--color-secondary-400)/<alpha-value>)",
500: "rgb(var(--color-secondary-500)/<alpha-value>)",
600: "rgb(var(--color-secondary-600)/<alpha-value>)",
700: "rgb(var(--color-secondary-700)/<alpha-value>)",
800: "rgb(var(--color-secondary-800)/<alpha-value>)",
900: "rgb(var(--color-secondary-900)/<alpha-value>)",
950: "rgb(var(--color-secondary-950)/<alpha-value>)",
},
tertiary: {
50: "rgb(var(--color-tertiary-50)/<alpha-value>)",
100: "rgb(var(--color-tertiary-100)/<alpha-value>)",
200: "rgb(var(--color-tertiary-200)/<alpha-value>)",
300: "rgb(var(--color-tertiary-300)/<alpha-value>)",
400: "rgb(var(--color-tertiary-400)/<alpha-value>)",
500: "rgb(var(--color-tertiary-500)/<alpha-value>)",
600: "rgb(var(--color-tertiary-600)/<alpha-value>)",
700: "rgb(var(--color-tertiary-700)/<alpha-value>)",
800: "rgb(var(--color-tertiary-800)/<alpha-value>)",
900: "rgb(var(--color-tertiary-900)/<alpha-value>)",
950: "rgb(var(--color-tertiary-950)/<alpha-value>)",
},
error: {
0: "rgb(var(--color-error-0)/<alpha-value>)",
50: "rgb(var(--color-error-50)/<alpha-value>)",
100: "rgb(var(--color-error-100)/<alpha-value>)",
200: "rgb(var(--color-error-200)/<alpha-value>)",
300: "rgb(var(--color-error-300)/<alpha-value>)",
400: "rgb(var(--color-error-400)/<alpha-value>)",
500: "rgb(var(--color-error-500)/<alpha-value>)",
600: "rgb(var(--color-error-600)/<alpha-value>)",
700: "rgb(var(--color-error-700)/<alpha-value>)",
800: "rgb(var(--color-error-800)/<alpha-value>)",
900: "rgb(var(--color-error-900)/<alpha-value>)",
950: "rgb(var(--color-error-950)/<alpha-value>)",
},
success: {
0: "rgb(var(--color-success-0)/<alpha-value>)",
50: "rgb(var(--color-success-50)/<alpha-value>)",
100: "rgb(var(--color-success-100)/<alpha-value>)",
200: "rgb(var(--color-success-200)/<alpha-value>)",
300: "rgb(var(--color-success-300)/<alpha-value>)",
400: "rgb(var(--color-success-400)/<alpha-value>)",
500: "rgb(var(--color-success-500)/<alpha-value>)",
600: "rgb(var(--color-success-600)/<alpha-value>)",
700: "rgb(var(--color-success-700)/<alpha-value>)",
800: "rgb(var(--color-success-800)/<alpha-value>)",
900: "rgb(var(--color-success-900)/<alpha-value>)",
950: "rgb(var(--color-success-950)/<alpha-value>)",
},
warning: {
0: "rgb(var(--color-warning-0)/<alpha-value>)",
50: "rgb(var(--color-warning-50)/<alpha-value>)",
100: "rgb(var(--color-warning-100)/<alpha-value>)",
200: "rgb(var(--color-warning-200)/<alpha-value>)",
300: "rgb(var(--color-warning-300)/<alpha-value>)",
400: "rgb(var(--color-warning-400)/<alpha-value>)",
500: "rgb(var(--color-warning-500)/<alpha-value>)",
600: "rgb(var(--color-warning-600)/<alpha-value>)",
700: "rgb(var(--color-warning-700)/<alpha-value>)",
800: "rgb(var(--color-warning-800)/<alpha-value>)",
900: "rgb(var(--color-warning-900)/<alpha-value>)",
950: "rgb(var(--color-warning-950)/<alpha-value>)",
},
info: {
0: "rgb(var(--color-info-0)/<alpha-value>)",
50: "rgb(var(--color-info-50)/<alpha-value>)",
100: "rgb(var(--color-info-100)/<alpha-value>)",
200: "rgb(var(--color-info-200)/<alpha-value>)",
300: "rgb(var(--color-info-300)/<alpha-value>)",
400: "rgb(var(--color-info-400)/<alpha-value>)",
500: "rgb(var(--color-info-500)/<alpha-value>)",
600: "rgb(var(--color-info-600)/<alpha-value>)",
700: "rgb(var(--color-info-700)/<alpha-value>)",
800: "rgb(var(--color-info-800)/<alpha-value>)",
900: "rgb(var(--color-info-900)/<alpha-value>)",
950: "rgb(var(--color-info-950)/<alpha-value>)",
},
typography: {
0: "rgb(var(--color-typography-0)/<alpha-value>)",
50: "rgb(var(--color-typography-50)/<alpha-value>)",
100: "rgb(var(--color-typography-100)/<alpha-value>)",
200: "rgb(var(--color-typography-200)/<alpha-value>)",
300: "rgb(var(--color-typography-300)/<alpha-value>)",
400: "rgb(var(--color-typography-400)/<alpha-value>)",
500: "rgb(var(--color-typography-500)/<alpha-value>)",
600: "rgb(var(--color-typography-600)/<alpha-value>)",
700: "rgb(var(--color-typography-700)/<alpha-value>)",
800: "rgb(var(--color-typography-800)/<alpha-value>)",
900: "rgb(var(--color-typography-900)/<alpha-value>)",
950: "rgb(var(--color-typography-950)/<alpha-value>)",
white: "#FFFFFF",
gray: "#D4D4D4",
black: "#181718",
},
outline: {
0: "rgb(var(--color-outline-0)/<alpha-value>)",
50: "rgb(var(--color-outline-50)/<alpha-value>)",
100: "rgb(var(--color-outline-100)/<alpha-value>)",
200: "rgb(var(--color-outline-200)/<alpha-value>)",
300: "rgb(var(--color-outline-300)/<alpha-value>)",
400: "rgb(var(--color-outline-400)/<alpha-value>)",
500: "rgb(var(--color-outline-500)/<alpha-value>)",
600: "rgb(var(--color-outline-600)/<alpha-value>)",
700: "rgb(var(--color-outline-700)/<alpha-value>)",
800: "rgb(var(--color-outline-800)/<alpha-value>)",
900: "rgb(var(--color-outline-900)/<alpha-value>)",
950: "rgb(var(--color-outline-950)/<alpha-value>)",
},
background: {
0: "rgb(var(--color-background-0)/<alpha-value>)",
50: "rgb(var(--color-background-50)/<alpha-value>)",
100: "rgb(var(--color-background-100)/<alpha-value>)",
200: "rgb(var(--color-background-200)/<alpha-value>)",
300: "rgb(var(--color-background-300)/<alpha-value>)",
400: "rgb(var(--color-background-400)/<alpha-value>)",
500: "rgb(var(--color-background-500)/<alpha-value>)",
600: "rgb(var(--color-background-600)/<alpha-value>)",
700: "rgb(var(--color-background-700)/<alpha-value>)",
800: "rgb(var(--color-background-800)/<alpha-value>)",
900: "rgb(var(--color-background-900)/<alpha-value>)",
950: "rgb(var(--color-background-950)/<alpha-value>)",
error: "rgb(var(--color-background-error)/<alpha-value>)",
warning: "rgb(var(--color-background-warning)/<alpha-value>)",
muted: "rgb(var(--color-background-muted)/<alpha-value>)",
success: "rgb(var(--color-background-success)/<alpha-value>)",
info: "rgb(var(--color-background-info)/<alpha-value>)",
light: "#FBFBFB",
dark: "#181719",
},
indicator: {
primary: "rgb(var(--color-indicator-primary)/<alpha-value>)",
info: "rgb(var(--color-indicator-info)/<alpha-value>)",
error: "rgb(var(--color-indicator-error)/<alpha-value>)",
},
},
fontFamily: {
heading: undefined,
body: undefined,
mono: undefined,
jakarta: ["var(--font-plus-jakarta-sans)"],
roboto: ["var(--font-roboto)"],
code: ["var(--font-source-code-pro)"],
inter: ["var(--font-inter)"],
"space-mono": ["var(--font-space-mono)"],
},
fontWeight: {
extrablack: "950",
},
fontSize: {
"2xs": "10px",
},
boxShadow: {
"hard-1": "-2px 2px 8px 0px rgba(38, 38, 38, 0.20)",
"hard-2": "0px 3px 10px 0px rgba(38, 38, 38, 0.20)",
"hard-3": "2px 2px 8px 0px rgba(38, 38, 38, 0.20)",
"hard-4": "0px -3px 10px 0px rgba(38, 38, 38, 0.20)",
"hard-5": "0px 2px 10px 0px rgba(38, 38, 38, 0.10)",
"soft-1": "0px 0px 10px rgba(38, 38, 38, 0.1)",
"soft-2": "0px 0px 20px rgba(38, 38, 38, 0.2)",
"soft-3": "0px 0px 30px rgba(38, 38, 38, 0.1)",
"soft-4": "0px 0px 40px rgba(38, 38, 38, 0.1)",
},
},
},
};
+18
View File
@@ -0,0 +1,18 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts",
"nativewind-env.d.ts"
]
}
+43
View File
@@ -0,0 +1,43 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
app-example
# generated native folders
/ios
/android
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": []
}
+1
View File
@@ -0,0 +1 @@
{ "recommendations": ["expo.vscode-expo-tools"] }
+17
View File
@@ -0,0 +1,17 @@
{
"oxc.fmt.configPath": ".oxfmtrc.json",
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
}
}
+50
View File
@@ -0,0 +1,50 @@
# Welcome to your Expo app 👋
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
## Get started
1. Install dependencies
```bash
npm install
```
2. Start the app
```bash
npx expo start
```
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
## Get a fresh project
When you're ready, run:
```bash
npm run reset-project
```
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
## Learn more
To learn more about developing your project with Expo, look at the following resources:
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
## Join the community
Join our community of developers creating universal apps.
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
+49
View File
@@ -0,0 +1,49 @@
{
"expo": {
"name": "app-rn3",
"slug": "app-rn3",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "apprn3",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"backgroundImage": "./assets/images/android-icon-background.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/images/splash-icon.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#000000"
}
}
]
],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import { useColorScheme } from "@/hooks/use-color-scheme";
import { Stack } from "expo-router";
import { useEffect } from "react";
import "@/styles/global.css";
export default function RootLayout() {
const colorScheme = useColorScheme();
useEffect(() => {
console.log(colorScheme);
}, [colorScheme]);
return <Stack />;
}
+16
View File
@@ -0,0 +1,16 @@
import React from "react";
import { Button, ButtonText, ButtonSpinner, ButtonIcon } from "@/components/ui/button";
import { Text, View } from "react-native";
export default function App() {
return (
<View className="flex-1 items-center justify-center bg-white dark:bg-black">
<Text className="text-xl font-bold text-blue-500 dark:text-white">
Welcome to Nativewind!
</Text>
<Button action={"primary"} variant={"solid"} size={"lg"} isDisabled={false}>
<ButtonText>Hello World</ButtonText>
</Button>
</View>
);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+6
View File
@@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [["babel-preset-expo", { jsxImportSource: "nativewind" }], "nativewind/babel"],
};
};
@@ -0,0 +1,434 @@
'use client';
import React from 'react';
import { createButton } from '@gluestack-ui/core/button/creator';
import {
tva,
withStyleContext,
useStyleContext,
type VariantProps,
} from '@gluestack-ui/utils/nativewind-utils';
import { cssInterop } from 'nativewind';
import { ActivityIndicator, Pressable, Text, View } from 'react-native';
import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator';
const SCOPE = 'BUTTON';
const Root = withStyleContext(Pressable, SCOPE);
const UIButton = createButton({
Root: Root,
Text,
Group: View,
Spinner: ActivityIndicator,
Icon: UIIcon,
});
cssInterop(PrimitiveIcon, {
className: {
target: 'style',
nativeStyleToProp: {
height: true,
width: true,
fill: true,
color: 'classNameColor',
stroke: true,
},
},
});
const buttonStyle = tva({
base: 'group/button rounded bg-primary-500 flex-row items-center justify-center data-[focus-visible=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[disabled=true]:opacity-40 gap-2',
variants: {
action: {
primary:
'bg-primary-500 data-[hover=true]:bg-primary-600 data-[active=true]:bg-primary-700 border-primary-300 data-[hover=true]:border-primary-400 data-[active=true]:border-primary-500 data-[focus-visible=true]:web:ring-indicator-info',
secondary:
'bg-secondary-500 border-secondary-300 data-[hover=true]:bg-secondary-600 data-[hover=true]:border-secondary-400 data-[active=true]:bg-secondary-700 data-[active=true]:border-secondary-700 data-[focus-visible=true]:web:ring-indicator-info',
positive:
'bg-success-500 border-success-300 data-[hover=true]:bg-success-600 data-[hover=true]:border-success-400 data-[active=true]:bg-success-700 data-[active=true]:border-success-500 data-[focus-visible=true]:web:ring-indicator-info',
negative:
'bg-error-500 border-error-300 data-[hover=true]:bg-error-600 data-[hover=true]:border-error-400 data-[active=true]:bg-error-700 data-[active=true]:border-error-500 data-[focus-visible=true]:web:ring-indicator-info',
default:
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
},
variant: {
link: 'px-0',
outline:
'bg-transparent border data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
solid: '',
},
size: {
xs: 'px-3.5 h-8',
sm: 'px-4 h-9',
md: 'px-5 h-10',
lg: 'px-6 h-11',
xl: 'px-7 h-12',
},
},
compoundVariants: [
{
action: 'primary',
variant: 'link',
class:
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
},
{
action: 'secondary',
variant: 'link',
class:
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
},
{
action: 'positive',
variant: 'link',
class:
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
},
{
action: 'negative',
variant: 'link',
class:
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
},
{
action: 'primary',
variant: 'outline',
class:
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
},
{
action: 'secondary',
variant: 'outline',
class:
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
},
{
action: 'positive',
variant: 'outline',
class:
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
},
{
action: 'negative',
variant: 'outline',
class:
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
},
],
});
const buttonTextStyle = tva({
base: 'text-typography-0 font-semibold web:select-none',
parentVariants: {
action: {
primary:
'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
secondary:
'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
positive:
'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
negative:
'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
},
variant: {
link: 'data-[hover=true]:underline data-[active=true]:underline',
outline: '',
solid:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
size: {
xs: 'text-xs',
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
xl: 'text-xl',
},
},
parentCompoundVariants: [
{
variant: 'solid',
action: 'primary',
class:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
{
variant: 'solid',
action: 'secondary',
class:
'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
},
{
variant: 'solid',
action: 'positive',
class:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
{
variant: 'solid',
action: 'negative',
class:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
{
variant: 'outline',
action: 'primary',
class:
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
},
{
variant: 'outline',
action: 'secondary',
class:
'text-typography-500 data-[hover=true]:text-primary-600 data-[active=true]:text-typography-700',
},
{
variant: 'outline',
action: 'positive',
class:
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
},
{
variant: 'outline',
action: 'negative',
class:
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
},
],
});
const buttonIconStyle = tva({
base: 'fill-none',
parentVariants: {
variant: {
link: 'data-[hover=true]:underline data-[active=true]:underline',
outline: '',
solid:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
size: {
xs: 'h-3.5 w-3.5',
sm: 'h-4 w-4',
md: 'h-[18px] w-[18px]',
lg: 'h-[18px] w-[18px]',
xl: 'h-5 w-5',
},
action: {
primary:
'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
secondary:
'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
positive:
'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
negative:
'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
},
},
parentCompoundVariants: [
{
variant: 'solid',
action: 'primary',
class:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
{
variant: 'solid',
action: 'secondary',
class:
'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
},
{
variant: 'solid',
action: 'positive',
class:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
{
variant: 'solid',
action: 'negative',
class:
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
},
],
});
const buttonGroupStyle = tva({
base: '',
variants: {
space: {
'xs': 'gap-1',
'sm': 'gap-2',
'md': 'gap-3',
'lg': 'gap-4',
'xl': 'gap-5',
'2xl': 'gap-6',
'3xl': 'gap-7',
'4xl': 'gap-8',
},
isAttached: {
true: 'gap-0',
},
flexDirection: {
'row': 'flex-row',
'column': 'flex-col',
'row-reverse': 'flex-row-reverse',
'column-reverse': 'flex-col-reverse',
},
},
});
type IButtonProps = Omit<
React.ComponentPropsWithoutRef<typeof UIButton>,
'context'
> &
VariantProps<typeof buttonStyle> & { className?: string };
const Button = React.forwardRef<
React.ElementRef<typeof UIButton>,
IButtonProps
>(
(
{ className, variant = 'solid', size = 'md', action = 'primary', ...props },
ref
) => {
return (
<UIButton
ref={ref}
{...props}
className={buttonStyle({ variant, size, action, class: className })}
context={{ variant, size, action }}
/>
);
}
);
type IButtonTextProps = React.ComponentPropsWithoutRef<typeof UIButton.Text> &
VariantProps<typeof buttonTextStyle> & { className?: string };
const ButtonText = React.forwardRef<
React.ElementRef<typeof UIButton.Text>,
IButtonTextProps
>(({ className, variant, size, action, ...props }, ref) => {
const {
variant: parentVariant,
size: parentSize,
action: parentAction,
} = useStyleContext(SCOPE);
return (
<UIButton.Text
ref={ref}
{...props}
className={buttonTextStyle({
parentVariants: {
variant: parentVariant,
size: parentSize,
action: parentAction,
},
variant,
size,
action,
class: className,
})}
/>
);
});
const ButtonSpinner = UIButton.Spinner;
type IButtonIcon = React.ComponentPropsWithoutRef<typeof UIButton.Icon> &
VariantProps<typeof buttonIconStyle> & {
className?: string | undefined;
as?: React.ElementType;
height?: number;
width?: number;
};
const ButtonIcon = React.forwardRef<
React.ElementRef<typeof UIButton.Icon>,
IButtonIcon
>(({ className, size, ...props }, ref) => {
const {
variant: parentVariant,
size: parentSize,
action: parentAction,
} = useStyleContext(SCOPE);
if (typeof size === 'number') {
return (
<UIButton.Icon
ref={ref}
{...props}
className={buttonIconStyle({ class: className })}
size={size}
/>
);
} else if (
(props.height !== undefined || props.width !== undefined) &&
size === undefined
) {
return (
<UIButton.Icon
ref={ref}
{...props}
className={buttonIconStyle({ class: className })}
/>
);
}
return (
<UIButton.Icon
{...props}
className={buttonIconStyle({
parentVariants: {
size: parentSize,
variant: parentVariant,
action: parentAction,
},
size,
class: className,
})}
ref={ref}
/>
);
});
type IButtonGroupProps = React.ComponentPropsWithoutRef<typeof UIButton.Group> &
VariantProps<typeof buttonGroupStyle>;
const ButtonGroup = React.forwardRef<
React.ElementRef<typeof UIButton.Group>,
IButtonGroupProps
>(
(
{
className,
space = 'md',
isAttached = false,
flexDirection = 'column',
...props
},
ref
) => {
return (
<UIButton.Group
className={buttonGroupStyle({
class: className,
space,
isAttached,
flexDirection,
})}
{...props}
ref={ref}
/>
);
}
);
Button.displayName = 'Button';
ButtonText.displayName = 'ButtonText';
ButtonSpinner.displayName = 'ButtonSpinner';
ButtonIcon.displayName = 'ButtonIcon';
ButtonGroup.displayName = 'ButtonGroup';
export { Button, ButtonText, ButtonSpinner, ButtonIcon, ButtonGroup };
@@ -0,0 +1,309 @@
'use client'
import { vars } from 'nativewind'
export const config = {
light: vars({
'--color-primary-0': '240 249 255',
'--color-primary-50': '224 242 254',
'--color-primary-100': '186 230 253',
'--color-primary-200': '125 211 252',
'--color-primary-300': '56 189 248',
'--color-primary-400': '14 165 233',
'--color-primary-500': '2 132 199',
'--color-primary-600': '3 105 161',
'--color-primary-700': '7 89 133',
'--color-primary-800': '12 74 110',
'--color-primary-900': '8 47 73',
'--color-primary-950': '2 22 36',
/* Secondary */
'--color-secondary-0': '253 253 253',
'--color-secondary-50': '251 251 251',
'--color-secondary-100': '246 246 246',
'--color-secondary-200': '242 242 242',
'--color-secondary-300': '237 237 237',
'--color-secondary-400': '230 230 231',
'--color-secondary-500': '217 217 219',
'--color-secondary-600': '198 199 199',
'--color-secondary-700': '189 189 189',
'--color-secondary-800': '177 177 177',
'--color-secondary-900': '165 164 164',
'--color-secondary-950': '157 157 157',
/* Tertiary */
'--color-tertiary-0': '255 250 245',
'--color-tertiary-50': '255 242 229',
'--color-tertiary-100': '255 233 213',
'--color-tertiary-200': '254 209 170',
'--color-tertiary-300': '253 180 116',
'--color-tertiary-400': '251 157 75',
'--color-tertiary-500': '231 129 40',
'--color-tertiary-600': '215 117 31',
'--color-tertiary-700': '180 98 26',
'--color-tertiary-800': '130 73 23',
'--color-tertiary-900': '108 61 19',
'--color-tertiary-950': '84 49 18',
/* Error */
'--color-error-0': '254 233 233',
'--color-error-50': '254 226 226',
'--color-error-100': '254 202 202',
'--color-error-200': '252 165 165',
'--color-error-300': '248 113 113',
'--color-error-400': '239 68 68',
'--color-error-500': '230 53 53',
'--color-error-600': '220 38 38',
'--color-error-700': '185 28 28',
'--color-error-800': '153 27 27',
'--color-error-900': '127 29 29',
'--color-error-950': '83 19 19',
/* Success */
'--color-success-0': '228 255 244',
'--color-success-50': '202 255 232',
'--color-success-100': '162 241 192',
'--color-success-200': '132 211 162',
'--color-success-300': '102 181 132',
'--color-success-400': '72 151 102',
'--color-success-500': '52 131 82',
'--color-success-600': '42 121 72',
'--color-success-700': '32 111 62',
'--color-success-800': '22 101 52',
'--color-success-900': '20 83 45',
'--color-success-950': '27 50 36',
/* Warning */
'--color-warning-0': '255 249 245',
'--color-warning-50': '255 244 236',
'--color-warning-100': '255 231 213',
'--color-warning-200': '254 205 170',
'--color-warning-300': '253 173 116',
'--color-warning-400': '251 149 75',
'--color-warning-500': '231 120 40',
'--color-warning-600': '215 108 31',
'--color-warning-700': '180 90 26',
'--color-warning-800': '130 68 23',
'--color-warning-900': '108 56 19',
'--color-warning-950': '84 45 18',
/* Info */
'--color-info-0': '236 248 254',
'--color-info-50': '199 235 252',
'--color-info-100': '162 221 250',
'--color-info-200': '124 207 248',
'--color-info-300': '87 194 246',
'--color-info-400': '50 180 244',
'--color-info-500': '13 166 242',
'--color-info-600': '11 141 205',
'--color-info-700': '9 115 168',
'--color-info-800': '7 90 131',
'--color-info-900': '5 64 93',
'--color-info-950': '3 38 56',
/* Typography */
'--color-typography-0': '254 254 255',
'--color-typography-50': '245 245 245',
'--color-typography-100': '229 229 229',
'--color-typography-200': '219 219 220',
'--color-typography-300': '212 212 212',
'--color-typography-400': '163 163 163',
'--color-typography-500': '140 140 140',
'--color-typography-600': '115 115 115',
'--color-typography-700': '82 82 82',
'--color-typography-800': '64 64 64',
'--color-typography-900': '38 38 39',
'--color-typography-950': '23 23 23',
/* Outline */
'--color-outline-0': '253 254 254',
'--color-outline-50': '243 243 243',
'--color-outline-100': '230 230 230',
'--color-outline-200': '221 220 219',
'--color-outline-300': '211 211 211',
'--color-outline-400': '165 163 163',
'--color-outline-500': '140 141 141',
'--color-outline-600': '115 116 116',
'--color-outline-700': '83 82 82',
'--color-outline-800': '65 65 65',
'--color-outline-900': '39 38 36',
'--color-outline-950': '26 23 23',
/* Background */
'--color-background-0': '255 255 255',
'--color-background-50': '246 246 246',
'--color-background-100': '242 241 241',
'--color-background-200': '220 219 219',
'--color-background-300': '213 212 212',
'--color-background-400': '162 163 163',
'--color-background-500': '142 142 142',
'--color-background-600': '116 116 116',
'--color-background-700': '83 82 82',
'--color-background-800': '65 64 64',
'--color-background-900': '39 38 37',
'--color-background-950': '18 18 18',
/* Background Special */
'--color-background-error': '254 241 241',
'--color-background-warning': '255 243 234',
'--color-background-success': '237 252 242',
'--color-background-muted': '247 248 247',
'--color-background-info': '235 248 254',
/* Focus Ring Indicator */
'--color-indicator-primary': '55 55 55',
'--color-indicator-info': '83 153 236',
'--color-indicator-error': '185 28 28',
}),
dark: vars({
'--color-primary-0': '2 22 36',
'--color-primary-50': '8 47 73',
'--color-primary-100': '12 74 110',
'--color-primary-200': '7 89 133',
'--color-primary-300': '3 105 161',
'--color-primary-400': '2 132 199',
'--color-primary-500': '56 189 248',
'--color-primary-600': '125 211 252',
'--color-primary-700': '186 230 253',
'--color-primary-800': '224 242 254',
'--color-primary-900': '240 249 255',
'--color-primary-950': '248 252 255',
/* Secondary */
'--color-secondary-0': '20 20 20',
'--color-secondary-50': '23 23 23',
'--color-secondary-100': '31 31 31',
'--color-secondary-200': '39 39 39',
'--color-secondary-300': '44 44 44',
'--color-secondary-400': '56 57 57',
'--color-secondary-500': '63 64 64',
'--color-secondary-600': '86 86 86',
'--color-secondary-700': '110 110 110',
'--color-secondary-800': '135 135 135',
'--color-secondary-900': '150 150 150',
'--color-secondary-950': '164 164 164',
/* Tertiary */
'--color-tertiary-0': '84 49 18',
'--color-tertiary-50': '108 61 19',
'--color-tertiary-100': '130 73 23',
'--color-tertiary-200': '180 98 26',
'--color-tertiary-300': '215 117 31',
'--color-tertiary-400': '231 129 40',
'--color-tertiary-500': '251 157 75',
'--color-tertiary-600': '253 180 116',
'--color-tertiary-700': '254 209 170',
'--color-tertiary-800': '255 233 213',
'--color-tertiary-900': '255 242 229',
'--color-tertiary-950': '255 250 245',
/* Error */
'--color-error-0': '83 19 19',
'--color-error-50': '127 29 29',
'--color-error-100': '153 27 27',
'--color-error-200': '185 28 28',
'--color-error-300': '220 38 38',
'--color-error-400': '230 53 53',
'--color-error-500': '239 68 68',
'--color-error-600': '249 97 96',
'--color-error-700': '229 91 90',
'--color-error-800': '254 202 202',
'--color-error-900': '254 226 226',
'--color-error-950': '254 233 233',
/* Success */
'--color-success-0': '27 50 36',
'--color-success-50': '20 83 45',
'--color-success-100': '22 101 52',
'--color-success-200': '32 111 62',
'--color-success-300': '42 121 72',
'--color-success-400': '52 131 82',
'--color-success-500': '72 151 102',
'--color-success-600': '102 181 132',
'--color-success-700': '132 211 162',
'--color-success-800': '162 241 192',
'--color-success-900': '202 255 232',
'--color-success-950': '228 255 244',
/* Warning */
'--color-warning-0': '84 45 18',
'--color-warning-50': '108 56 19',
'--color-warning-100': '130 68 23',
'--color-warning-200': '180 90 26',
'--color-warning-300': '215 108 31',
'--color-warning-400': '231 120 40',
'--color-warning-500': '251 149 75',
'--color-warning-600': '253 173 116',
'--color-warning-700': '254 205 170',
'--color-warning-800': '255 231 213',
'--color-warning-900': '255 244 237',
'--color-warning-950': '255 249 245',
/* Info */
'--color-info-0': '3 38 56',
'--color-info-50': '5 64 93',
'--color-info-100': '7 90 131',
'--color-info-200': '9 115 168',
'--color-info-300': '11 141 205',
'--color-info-400': '13 166 242',
'--color-info-500': '50 180 244',
'--color-info-600': '87 194 246',
'--color-info-700': '124 207 248',
'--color-info-800': '162 221 250',
'--color-info-900': '199 235 252',
'--color-info-950': '236 248 254',
/* Typography */
'--color-typography-0': '23 23 23',
'--color-typography-50': '38 38 39',
'--color-typography-100': '64 64 64',
'--color-typography-200': '82 82 82',
'--color-typography-300': '115 115 115',
'--color-typography-400': '140 140 140',
'--color-typography-500': '163 163 163',
'--color-typography-600': '212 212 212',
'--color-typography-700': '219 219 220',
'--color-typography-800': '229 229 229',
'--color-typography-900': '245 245 245',
'--color-typography-950': '254 254 255',
/* Outline */
'--color-outline-0': '26 23 23',
'--color-outline-50': '39 38 36',
'--color-outline-100': '65 65 65',
'--color-outline-200': '83 82 82',
'--color-outline-300': '115 116 116',
'--color-outline-400': '140 141 141',
'--color-outline-500': '165 163 163',
'--color-outline-600': '211 211 211',
'--color-outline-700': '221 220 219',
'--color-outline-800': '230 230 230',
'--color-outline-900': '243 243 243',
'--color-outline-950': '253 254 254',
/* Background */
'--color-background-0': '18 18 18',
'--color-background-50': '39 38 37',
'--color-background-100': '65 64 64',
'--color-background-200': '83 82 82',
'--color-background-300': '116 116 116',
'--color-background-400': '142 142 142',
'--color-background-500': '162 163 163',
'--color-background-600': '213 212 212',
'--color-background-700': '229 228 228',
'--color-background-800': '242 241 241',
'--color-background-900': '246 246 246',
'--color-background-950': '255 255 255',
/* Background Special */
'--color-background-error': '66 43 43',
'--color-background-warning': '65 47 35',
'--color-background-success': '28 43 33',
'--color-background-muted': '51 51 51',
'--color-background-info': '26 40 46',
/* Focus Ring Indicator */
'--color-indicator-primary': '247 247 247',
'--color-indicator-info': '161 199 245',
'--color-indicator-error': '232 70 69',
}),
}
@@ -0,0 +1,5 @@
{
"dependencies": {},
"devDependencies": {},
"components": []
}
@@ -0,0 +1,87 @@
// This is a Next.js 15 compatible version of the GluestackUIProvider
'use client';
import React, { useEffect, useLayoutEffect } from 'react';
import { config } from './config';
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator';
import { ToastProvider } from '@gluestack-ui/core/toast/creator';
import { setFlushStyles } from '@gluestack-ui/utils/nativewind-utils';
import { script } from './script';
const variableStyleTagId = 'nativewind-style';
const createStyle = (styleTagId: string) => {
const style = document.createElement('style');
style.id = styleTagId;
style.appendChild(document.createTextNode(''));
return style;
};
export const useSafeLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
export function GluestackUIProvider({
mode = 'light',
...props
}: {
mode?: 'light' | 'dark' | 'system';
children?: React.ReactNode;
}) {
let cssVariablesWithMode = ``;
Object.keys(config).forEach((configKey) => {
cssVariablesWithMode +=
configKey === 'dark' ? `\n .dark {\n ` : `\n:root {\n`;
const cssVariables = Object.keys(
config[configKey as keyof typeof config]
).reduce((acc: string, curr: string) => {
acc += `${curr}:${config[configKey as keyof typeof config][curr]}; `;
return acc;
}, '');
cssVariablesWithMode += `${cssVariables} \n}`;
});
setFlushStyles(cssVariablesWithMode);
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
script(e.matches ? 'dark' : 'light');
}, []);
useSafeLayoutEffect(() => {
if (mode !== 'system') {
const documentElement = document.documentElement;
if (documentElement) {
documentElement.classList.add(mode);
documentElement.classList.remove(mode === 'light' ? 'dark' : 'light');
documentElement.style.colorScheme = mode;
}
}
}, [mode]);
useSafeLayoutEffect(() => {
if (mode !== 'system') return;
const media = window.matchMedia('(prefers-color-scheme: dark)');
media.addListener(handleMediaQuery);
return () => media.removeListener(handleMediaQuery);
}, [handleMediaQuery]);
useSafeLayoutEffect(() => {
if (typeof window !== 'undefined') {
const documentElement = document.documentElement;
if (documentElement) {
const head = documentElement.querySelector('head');
let style = head?.querySelector(`[id='${variableStyleTagId}']`);
if (!style) {
style = createStyle(variableStyleTagId);
style.innerHTML = cssVariablesWithMode;
if (head) head.appendChild(style);
}
}
}
}, []);
return (
<OverlayProvider>
<ToastProvider>{props.children}</ToastProvider>
</OverlayProvider>
);
}
@@ -0,0 +1,40 @@
import type { ViewProps } from 'react-native'
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator'
import { ToastProvider } from '@gluestack-ui/core/toast/creator'
import { useColorScheme } from 'nativewind'
import * as React from 'react'
import { useEffect } from 'react'
import { View } from 'react-native'
import { config } from './config'
export type ModeType = 'light' | 'dark' | 'system'
export function GluestackUIProvider({
mode = 'light',
...props
}: {
mode?: ModeType
children?: React.ReactNode
style?: ViewProps['style']
}) {
const { colorScheme, setColorScheme } = useColorScheme()
useEffect(() => {
setColorScheme(mode)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mode])
return (
<View
style={[
config[colorScheme!],
{ flex: 1, height: '100%', width: '100%' },
props.style,
]}
>
<OverlayProvider>
<ToastProvider>{props.children}</ToastProvider>
</OverlayProvider>
</View>
)
}
@@ -0,0 +1,96 @@
'use client';
import React, { useEffect, useLayoutEffect } from 'react';
import { config } from './config';
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator';
import { ToastProvider } from '@gluestack-ui/core/toast/creator';
import { setFlushStyles } from '@gluestack-ui/utils/nativewind-utils';
import { script } from './script';
export type ModeType = 'light' | 'dark' | 'system';
const variableStyleTagId = 'nativewind-style';
const createStyle = (styleTagId: string) => {
const style = document.createElement('style');
style.id = styleTagId;
style.appendChild(document.createTextNode(''));
return style;
};
export const useSafeLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
export function GluestackUIProvider({
mode = 'light',
...props
}: {
mode?: ModeType;
children?: React.ReactNode;
}) {
let cssVariablesWithMode = ``;
Object.keys(config).forEach((configKey) => {
cssVariablesWithMode +=
configKey === 'dark' ? `\n .dark {\n ` : `\n:root {\n`;
const cssVariables = Object.keys(
config[configKey as keyof typeof config]
).reduce((acc: string, curr: string) => {
acc += `${curr}:${config[configKey as keyof typeof config][curr]}; `;
return acc;
}, '');
cssVariablesWithMode += `${cssVariables} \n}`;
});
setFlushStyles(cssVariablesWithMode);
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
script(e.matches ? 'dark' : 'light');
}, []);
useSafeLayoutEffect(() => {
if (mode !== 'system') {
const documentElement = document.documentElement;
if (documentElement) {
documentElement.classList.add(mode);
documentElement.classList.remove(mode === 'light' ? 'dark' : 'light');
documentElement.style.colorScheme = mode;
}
}
}, [mode]);
useSafeLayoutEffect(() => {
if (mode !== 'system') return;
const media = window.matchMedia('(prefers-color-scheme: dark)');
media.addListener(handleMediaQuery);
return () => media.removeListener(handleMediaQuery);
}, [handleMediaQuery]);
useSafeLayoutEffect(() => {
if (typeof window !== 'undefined') {
const documentElement = document.documentElement;
if (documentElement) {
const head = documentElement.querySelector('head');
let style = head?.querySelector(`[id='${variableStyleTagId}']`);
if (!style) {
style = createStyle(variableStyleTagId);
style.innerHTML = cssVariablesWithMode;
if (head) head.appendChild(style);
}
}
}
}, []);
return (
<>
<script
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: `(${script.toString()})('${mode}')`,
}}
/>
<OverlayProvider>
<ToastProvider>{props.children}</ToastProvider>
</OverlayProvider>
</>
);
}
@@ -0,0 +1,19 @@
export const script = (mode: string) => {
const documentElement = document.documentElement;
function getSystemColorMode() {
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
try {
const isSystem = mode === 'system';
const theme = isSystem ? getSystemColorMode() : mode;
documentElement.classList.remove(theme === 'light' ? 'dark' : 'light');
documentElement.classList.add(theme);
documentElement.style.colorScheme = theme;
} catch (e) {
console.error(e);
}
};
@@ -0,0 +1 @@
export { useColorScheme } from 'react-native';
@@ -0,0 +1,21 @@
import { useEffect, useState } from 'react';
import { useColorScheme as useRNColorScheme } from 'react-native';
/**
* To support static rendering, this value needs to be re-calculated on the client side for web
*/
export function useColorScheme() {
const [hasHydrated, setHasHydrated] = useState(false);
useEffect(() => {
setHasHydrated(true);
}, []);
const colorScheme = useRNColorScheme();
if (hasHydrated) {
return colorScheme;
}
return 'light';
}
+21
View File
@@ -0,0 +1,21 @@
/**
* Learn more about light and dark modes:
* https://docs.expo.dev/guides/color-schemes/
*/
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark
) {
const theme = useColorScheme() ?? 'light';
const colorFromProps = props[theme];
if (colorFromProps) {
return colorFromProps;
} else {
return Colors[theme][colorName];
}
}
+6
View File
@@ -0,0 +1,6 @@
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, { input: "./styles/global.css" });
+3
View File
@@ -0,0 +1,3 @@
/// <reference types="nativewind/types" />
// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
+55
View File
@@ -0,0 +1,55 @@
{
"name": "app-rn3",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"lint": "expo lint"
},
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@gluestack-ui/core": "^3.0.16",
"@gluestack-ui/utils": "^3.0.19",
"@gluestack/ui-next-adapter": "^3.0.3",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"expo": "~54.0.33",
"expo-constants": "~18.0.13",
"expo-font": "~14.0.11",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
"expo-linking": "~8.0.11",
"expo-router": "~6.0.23",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
"expo-symbols": "~1.0.8",
"expo-system-ui": "~6.0.9",
"expo-web-browser": "~15.0.10",
"nativewind": "^4.2.3",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5",
"react-native-css-interop": "^0.2.3",
"react-native-gesture-handler": "~2.28.0",
"react-native-reanimated": "~4.1.7",
"react-native-safe-area-context": "~5.6.2",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@types/react": "~19.1.0",
"babel-preset-expo": "^55.0.16",
"oxfmt": "^0.44.0",
"prettier-plugin-tailwindcss": "^0.5.14",
"tailwindcss": "^3.4.19",
"typescript": "~5.9.2"
},
"private": true
}
+9805
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
nodeLinker: hoisted
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+207
View File
@@ -0,0 +1,207 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: process.env.DARK_MODE ? process.env.DARK_MODE : "class",
content: [
"./app/**/*.{html,js,jsx,ts,tsx,mdx}",
"./components/**/*.{html,js,jsx,ts,tsx,mdx}",
"./utils/**/*.{html,js,jsx,ts,tsx,mdx}",
"./*.{html,js,jsx,ts,tsx,mdx}",
"./src/**/*.{html,js,jsx,ts,tsx,mdx}",
],
presets: [require("nativewind/preset")],
important: "html",
safelist: [
{
pattern:
// eslint-disable-next-line regexp/no-dupe-disjunctions
/(bg|border|text|stroke|fill)-(primary|secondary|tertiary|error|success|warning|info|typography|outline|background|indicator)-(0|50|100|200|300|400|500|600|700|800|900|950|white|gray|black|error|warning|muted|success|info|light|dark|primary)/,
},
],
theme: {
extend: {
colors: {
primary: {
0: "rgb(var(--color-primary-0)/<alpha-value>)",
50: "rgb(var(--color-primary-50)/<alpha-value>)",
100: "rgb(var(--color-primary-100)/<alpha-value>)",
200: "rgb(var(--color-primary-200)/<alpha-value>)",
300: "rgb(var(--color-primary-300)/<alpha-value>)",
400: "rgb(var(--color-primary-400)/<alpha-value>)",
500: "rgb(var(--color-primary-500)/<alpha-value>)",
600: "rgb(var(--color-primary-600)/<alpha-value>)",
700: "rgb(var(--color-primary-700)/<alpha-value>)",
800: "rgb(var(--color-primary-800)/<alpha-value>)",
900: "rgb(var(--color-primary-900)/<alpha-value>)",
950: "rgb(var(--color-primary-950)/<alpha-value>)",
},
secondary: {
0: "rgb(var(--color-secondary-0)/<alpha-value>)",
50: "rgb(var(--color-secondary-50)/<alpha-value>)",
100: "rgb(var(--color-secondary-100)/<alpha-value>)",
200: "rgb(var(--color-secondary-200)/<alpha-value>)",
300: "rgb(var(--color-secondary-300)/<alpha-value>)",
400: "rgb(var(--color-secondary-400)/<alpha-value>)",
500: "rgb(var(--color-secondary-500)/<alpha-value>)",
600: "rgb(var(--color-secondary-600)/<alpha-value>)",
700: "rgb(var(--color-secondary-700)/<alpha-value>)",
800: "rgb(var(--color-secondary-800)/<alpha-value>)",
900: "rgb(var(--color-secondary-900)/<alpha-value>)",
950: "rgb(var(--color-secondary-950)/<alpha-value>)",
},
tertiary: {
50: "rgb(var(--color-tertiary-50)/<alpha-value>)",
100: "rgb(var(--color-tertiary-100)/<alpha-value>)",
200: "rgb(var(--color-tertiary-200)/<alpha-value>)",
300: "rgb(var(--color-tertiary-300)/<alpha-value>)",
400: "rgb(var(--color-tertiary-400)/<alpha-value>)",
500: "rgb(var(--color-tertiary-500)/<alpha-value>)",
600: "rgb(var(--color-tertiary-600)/<alpha-value>)",
700: "rgb(var(--color-tertiary-700)/<alpha-value>)",
800: "rgb(var(--color-tertiary-800)/<alpha-value>)",
900: "rgb(var(--color-tertiary-900)/<alpha-value>)",
950: "rgb(var(--color-tertiary-950)/<alpha-value>)",
},
error: {
0: "rgb(var(--color-error-0)/<alpha-value>)",
50: "rgb(var(--color-error-50)/<alpha-value>)",
100: "rgb(var(--color-error-100)/<alpha-value>)",
200: "rgb(var(--color-error-200)/<alpha-value>)",
300: "rgb(var(--color-error-300)/<alpha-value>)",
400: "rgb(var(--color-error-400)/<alpha-value>)",
500: "rgb(var(--color-error-500)/<alpha-value>)",
600: "rgb(var(--color-error-600)/<alpha-value>)",
700: "rgb(var(--color-error-700)/<alpha-value>)",
800: "rgb(var(--color-error-800)/<alpha-value>)",
900: "rgb(var(--color-error-900)/<alpha-value>)",
950: "rgb(var(--color-error-950)/<alpha-value>)",
},
success: {
0: "rgb(var(--color-success-0)/<alpha-value>)",
50: "rgb(var(--color-success-50)/<alpha-value>)",
100: "rgb(var(--color-success-100)/<alpha-value>)",
200: "rgb(var(--color-success-200)/<alpha-value>)",
300: "rgb(var(--color-success-300)/<alpha-value>)",
400: "rgb(var(--color-success-400)/<alpha-value>)",
500: "rgb(var(--color-success-500)/<alpha-value>)",
600: "rgb(var(--color-success-600)/<alpha-value>)",
700: "rgb(var(--color-success-700)/<alpha-value>)",
800: "rgb(var(--color-success-800)/<alpha-value>)",
900: "rgb(var(--color-success-900)/<alpha-value>)",
950: "rgb(var(--color-success-950)/<alpha-value>)",
},
warning: {
0: "rgb(var(--color-warning-0)/<alpha-value>)",
50: "rgb(var(--color-warning-50)/<alpha-value>)",
100: "rgb(var(--color-warning-100)/<alpha-value>)",
200: "rgb(var(--color-warning-200)/<alpha-value>)",
300: "rgb(var(--color-warning-300)/<alpha-value>)",
400: "rgb(var(--color-warning-400)/<alpha-value>)",
500: "rgb(var(--color-warning-500)/<alpha-value>)",
600: "rgb(var(--color-warning-600)/<alpha-value>)",
700: "rgb(var(--color-warning-700)/<alpha-value>)",
800: "rgb(var(--color-warning-800)/<alpha-value>)",
900: "rgb(var(--color-warning-900)/<alpha-value>)",
950: "rgb(var(--color-warning-950)/<alpha-value>)",
},
info: {
0: "rgb(var(--color-info-0)/<alpha-value>)",
50: "rgb(var(--color-info-50)/<alpha-value>)",
100: "rgb(var(--color-info-100)/<alpha-value>)",
200: "rgb(var(--color-info-200)/<alpha-value>)",
300: "rgb(var(--color-info-300)/<alpha-value>)",
400: "rgb(var(--color-info-400)/<alpha-value>)",
500: "rgb(var(--color-info-500)/<alpha-value>)",
600: "rgb(var(--color-info-600)/<alpha-value>)",
700: "rgb(var(--color-info-700)/<alpha-value>)",
800: "rgb(var(--color-info-800)/<alpha-value>)",
900: "rgb(var(--color-info-900)/<alpha-value>)",
950: "rgb(var(--color-info-950)/<alpha-value>)",
},
typography: {
0: "rgb(var(--color-typography-0)/<alpha-value>)",
50: "rgb(var(--color-typography-50)/<alpha-value>)",
100: "rgb(var(--color-typography-100)/<alpha-value>)",
200: "rgb(var(--color-typography-200)/<alpha-value>)",
300: "rgb(var(--color-typography-300)/<alpha-value>)",
400: "rgb(var(--color-typography-400)/<alpha-value>)",
500: "rgb(var(--color-typography-500)/<alpha-value>)",
600: "rgb(var(--color-typography-600)/<alpha-value>)",
700: "rgb(var(--color-typography-700)/<alpha-value>)",
800: "rgb(var(--color-typography-800)/<alpha-value>)",
900: "rgb(var(--color-typography-900)/<alpha-value>)",
950: "rgb(var(--color-typography-950)/<alpha-value>)",
white: "#FFFFFF",
gray: "#D4D4D4",
black: "#181718",
},
outline: {
0: "rgb(var(--color-outline-0)/<alpha-value>)",
50: "rgb(var(--color-outline-50)/<alpha-value>)",
100: "rgb(var(--color-outline-100)/<alpha-value>)",
200: "rgb(var(--color-outline-200)/<alpha-value>)",
300: "rgb(var(--color-outline-300)/<alpha-value>)",
400: "rgb(var(--color-outline-400)/<alpha-value>)",
500: "rgb(var(--color-outline-500)/<alpha-value>)",
600: "rgb(var(--color-outline-600)/<alpha-value>)",
700: "rgb(var(--color-outline-700)/<alpha-value>)",
800: "rgb(var(--color-outline-800)/<alpha-value>)",
900: "rgb(var(--color-outline-900)/<alpha-value>)",
950: "rgb(var(--color-outline-950)/<alpha-value>)",
},
background: {
0: "rgb(var(--color-background-0)/<alpha-value>)",
50: "rgb(var(--color-background-50)/<alpha-value>)",
100: "rgb(var(--color-background-100)/<alpha-value>)",
200: "rgb(var(--color-background-200)/<alpha-value>)",
300: "rgb(var(--color-background-300)/<alpha-value>)",
400: "rgb(var(--color-background-400)/<alpha-value>)",
500: "rgb(var(--color-background-500)/<alpha-value>)",
600: "rgb(var(--color-background-600)/<alpha-value>)",
700: "rgb(var(--color-background-700)/<alpha-value>)",
800: "rgb(var(--color-background-800)/<alpha-value>)",
900: "rgb(var(--color-background-900)/<alpha-value>)",
950: "rgb(var(--color-background-950)/<alpha-value>)",
error: "rgb(var(--color-background-error)/<alpha-value>)",
warning: "rgb(var(--color-background-warning)/<alpha-value>)",
muted: "rgb(var(--color-background-muted)/<alpha-value>)",
success: "rgb(var(--color-background-success)/<alpha-value>)",
info: "rgb(var(--color-background-info)/<alpha-value>)",
light: "#FBFBFB",
dark: "#181719",
},
indicator: {
primary: "rgb(var(--color-indicator-primary)/<alpha-value>)",
info: "rgb(var(--color-indicator-info)/<alpha-value>)",
error: "rgb(var(--color-indicator-error)/<alpha-value>)",
},
},
fontFamily: {
heading: undefined,
body: undefined,
mono: undefined,
jakarta: ["var(--font-plus-jakarta-sans)"],
roboto: ["var(--font-roboto)"],
code: ["var(--font-source-code-pro)"],
inter: ["var(--font-inter)"],
"space-mono": ["var(--font-space-mono)"],
},
fontWeight: {
extrablack: "950",
},
fontSize: {
"2xs": "10px",
},
boxShadow: {
"hard-1": "-2px 2px 8px 0px rgba(38, 38, 38, 0.20)",
"hard-2": "0px 3px 10px 0px rgba(38, 38, 38, 0.20)",
"hard-3": "2px 2px 8px 0px rgba(38, 38, 38, 0.20)",
"hard-4": "0px -3px 10px 0px rgba(38, 38, 38, 0.20)",
"hard-5": "0px 2px 10px 0px rgba(38, 38, 38, 0.10)",
"soft-1": "0px 0px 10px rgba(38, 38, 38, 0.1)",
"soft-2": "0px 0px 20px rgba(38, 38, 38, 0.2)",
"soft-3": "0px 0px 30px rgba(38, 38, 38, 0.1)",
"soft-4": "0px 0px 40px rgba(38, 38, 38, 0.1)",
},
},
},
};
+18
View File
@@ -0,0 +1,18 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts",
"nativewind-env.d.ts"
]
}
-22
View File
@@ -1,22 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-1
View File
@@ -1 +0,0 @@
shamefully-hoist=true
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar"]
}
-50
View File
@@ -1,50 +0,0 @@
{
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
]
}
-17
View File
@@ -1,17 +0,0 @@
{
"Print Vue3 SFC": {
"scope": "vue",
"prefix": "v3",
"body": [
"<script lang=\"ts\" setup>",
"",
"</script>\n",
"<template>",
" <view class=\"\">$1</view>",
"</template>\n",
"<style lang=\"scss\" scoped>",
"",
"</style>\n"
]
}
}
-3
View File
@@ -1,3 +0,0 @@
VITE_APP_PORT = 3000
VITE_ALIYUN_BUCKET = 'g3cdcont'
-26
View File
@@ -1,26 +0,0 @@
// eslint.config.mjs
import antfu from '@antfu/eslint-config'
export default antfu({
vue: true,
stylistic: {
indent: 2,
quotes: 'single',
},
formatters: {
css: true,
html: true,
markdown: 'prettier',
},
rules: {
'no-console': 'off',
'prefer-promise-reject-errors': 'off',
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': 'warn',
'prefer-const': 'off',
'no-eval': 'off',
},
})
-26
View File
@@ -1,26 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" href="/public/logo.png" type="image/x-icon" />
<script>
const coverSupport =
'CSS' in window &&
typeof CSS.supports === 'function' &&
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
document.write(
`<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0${
coverSupport ? ', viewport-fit=cover' : ''
}" />`,
)
</script>
<title>App</title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
-129
View File
@@ -1,129 +0,0 @@
{
"name": "base-uniapp3",
"type": "module",
"version": "0.0.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev:app": "uni -p app",
"dev:app:test": "uni -p app --mode test",
"dev:app:prod": "uni -p app --mode production",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev": "node --experimental-loader ./scripts/window-path-loader.js node_modules/@dcloudio/vite-plugin-uni/bin/uni.js",
"dev:test": "uni --mode test",
"dev:prod": "uni --mode production",
"dev:h5": "uni",
"dev:h5:test": "uni --mode test",
"dev:h5:prod": "uni --mode production",
"dev:h5:ssr": "uni --ssr",
"dev:mp": "uni -p mp-weixin",
"dev:mp:test": "uni -p mp-weixin --mode test",
"dev:mp:prod": "uni -p mp-weixin --mode production",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-xhs": "uni -p mp-xhs",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app:test": "uni build -p app --mode test",
"build:app:prod": "uni build -p app --mode production",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:test": "uni build --mode test",
"build:h5:prod": "uni build --mode production",
"build": "uni build",
"build:test": "uni build --mode test",
"build:prod": "uni build --mode production",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp": "uni build -p mp-weixin",
"build:mp:test": "uni build -p mp-weixin --mode test",
"build:mp:prod": "uni build -p mp-weixin --mode production",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-xhs": "uni build -p mp-xhs",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4070620250821001",
"@dcloudio/uni-app-harmony": "3.0.0-4070620250821001",
"@dcloudio/uni-app-plus": "3.0.0-4070620250821001",
"@dcloudio/uni-components": "3.0.0-4070620250821001",
"@dcloudio/uni-h5": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-alipay": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-baidu": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-harmony": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-jd": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-lark": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-qq": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-xhs": "3.0.0-4070620250821001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001",
"@vitejs/plugin-basic-ssl": "^2.1.4",
"abortcontroller-polyfill": "^1.7.8",
"dayjs": "^1.11.18",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"sard-uniapp": "^1.28.0",
"vue": "^3.5.21",
"wot-design-uni": "^1.13.0",
"z-paging": "^2.8.8"
},
"devDependencies": {
"@antfu/eslint-config": "^5.2.1",
"@dcloudio/types": "^3.4.21",
"@dcloudio/uni-automator": "3.0.0-4070620250821001",
"@dcloudio/uni-cli-shared": "3.0.0-4070620250821001",
"@dcloudio/uni-stacktracey": "3.0.0-4070620250821001",
"@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@rollup/rollup-darwin-x64": "^4.28.0",
"@types/node": "^20.17.9",
"@uni-helper/plugin-uni": "0.1.0",
"@uni-helper/uni-env": "^0.1.8",
"@uni-helper/uni-types": "1.0.0-alpha.6",
"@uni-helper/vite-plugin-uni-components": "0.2.0",
"@uni-helper/vite-plugin-uni-layouts": "0.1.11",
"@uni-helper/vite-plugin-uni-manifest": "^0.2.8",
"@uni-helper/vite-plugin-uni-platform": "^0.0.5",
"@uni-ku/bundle-optimizer": "^1.3.3",
"@vitejs/plugin-legacy": "^7.2.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.21",
"eslint": "^9.34.0",
"eslint-plugin-format": "^1.0.1",
"miniprogram-api-typings": "^4.1.0",
"postcss": "^8.4.49",
"postcss-html": "^1.8.0",
"postcss-scss": "^4.0.9",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "1.77.8",
"terser": "^5.44.0",
"typescript": "~5.8.3",
"unplugin-auto-import": "^20.0.0",
"vite": "^7.1.7",
"vue-tsc": "^3.0.7"
}
}
-11927
View File
File diff suppressed because it is too large. Load diff
-25
View File
@@ -1,25 +0,0 @@
{
"setting": {
"es6": true,
"postcss": true,
"minified": true,
"uglifyFileName": false,
"enhance": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useCompilerPlugins": false,
"minifyWXML": true
},
"compileType": "miniprogram",
"simulatorPluginLibVersion": {},
"packOptions": {
"ignore": [],
"include": []
},
"appid": "wxff43f54d4267a569",
"editorSetting": {}
}
@@ -1,14 +0,0 @@
{
"libVersion": "3.10.2",
"projectname": "base-uniapp-main",
"setting": {
"urlCheck": true,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"compileHotReLoad": true
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-35
View File
@@ -1,35 +0,0 @@
<script setup lang="ts">
import { onLaunch, onShow } from '@dcloudio/uni-app'
import { navigateToInterceptor } from '@/router/interceptor'
onLaunch(() => {
})
onShow((options) => {
if (options?.path) {
navigateToInterceptor.invoke({ url: `/${options.path}`, query: options.query })
}
else {
navigateToInterceptor.invoke({ url: '/' })
}
})
</script>
<style lang="scss">
page {
background-color: #f1f5f9;
}
swiper,
scroll-view {
flex: 1;
height: 100%;
overflow: hidden;
}
image {
width: 100%;
height: 100%;
vertical-align: middle;
}
</style>
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

@@ -1,32 +0,0 @@
<script lang="ts" setup>
defineOptions({
name: 'OverlayLoading',
})
defineProps<{
visible: boolean
}>()
</script>
<template>
<sar-overlay :visible="visible">
<view class="overlay">
<sar-loading
vertical
color="#fff"
text-color="#fff"
>
加载中...
</sar-loading>
</view>
</sar-overlay>
</template>
<style scoped>
.overlay {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>
Whitespace-only changes.
@@ -1,104 +0,0 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { dateSearchProps } from './types'
// 定义 props 和 emits
const props = defineProps(dateSearchProps)
const emit = defineEmits<{
(e: 'update:modelValue', value: string | number): void
}>()
// 初始化 dateValue,确保它从 props.modelValue 获取初始值
const dateValue = ref<string | number>(props.modelValue || 0) // 如果没有 modelValue 默认 ''
// 监听 props.modelValue 的变化,并更新 dateValue
watch(
() => props.modelValue,
(newValue: string | number) => {
if (props.type === 'date') {
// 如果是字符串类型
if (typeof newValue === 'string') {
// 尝试将字符串转为时间戳
const timestamp = Date.parse(newValue)
// 如果是有效的时间戳,则使用它;否则使用 0
dateValue.value = Number.isNaN(timestamp) ? 0 : timestamp
}
else {
// 如果是数字类型,直接赋值
dateValue.value = newValue
}
}
},
{ immediate: true },
)
const showValue = computed(() => {
// 如果 dateValue 有值且不为 0
if (props.type === 'date') {
if (dateValue.value && dateValue.value !== 0) {
// 转换时间戳为 YYYY-MM-DD 格式
const date = new Date(dateValue.value)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0') // 补零
const day = date.getDate().toString().padStart(2, '0') // 补零
return `${year}-${month}-${day}`
}
else {
// 如果 dateValue 为 0 或空,返回 placeholder
return props.placeholder || '请输入'
}
}
else {
return dateValue.value || props.placeholder
}
})
const showValueClass = computed(() => {
return dateValue.value ? 'text_black' : 'text_gray'
})
// 监听 dateValue 变化并通知父组件
watch(dateValue, (newValue) => {
emit('update:modelValue', newValue) // 将变化同步回父组件
})
</script>
<template>
<wd-datetime-picker v-bind="props" v-model="dateValue" use-default-slot>
<view class="datetime-picker-container">
<view class="datetime-display" :class="showValueClass">
{{ showValue }}
</view>
</view>
</wd-datetime-picker>
</template>
<style lang="scss" scoped>
/* 包裹容器,确保内容水平居中 */
.datetime-picker-container {
display: flex;
align-items: center;
justify-content: center;
}
/* 输入框样式 */
.datetime-display {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 4px;
font-size: 14px;
color: #333; /* 默认文本颜色 */
text-align: center;
background-color: #f5f5f5; /* 背景色 */
border-radius: 4px; /* 圆角 */
}
/* 黑色文本样式 */
.text_black {
color: black;
}
/* 灰色文本样式 */
.text_gray {
color: gray;
}
</style>
@@ -1,10 +0,0 @@
import { datetimePickerProps } from 'wot-design-uni/components/wd-datetime-picker/types'
export const dateSearchProps = {
...datetimePickerProps,
modelValue: {
type: [String, Number] as PropType<string | number>,
default: '',
required: true,
},
}
@@ -1,236 +0,0 @@
<script setup lang="ts">
</script>
<template>
<view class="overlay-center" style="pointer-events: none;">
<view class="boxes">
<view class="box">
<view />
<view />
<view />
<view />
</view>
<view class="box">
<view />
<view />
<view />
<view />
</view>
<view class="box">
<view />
<view />
<view />
<view />
</view>
<view class="box">
<view />
<view />
<view />
<view />
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.boxes {
--size: 32px;
--duration: 800ms;
position: relative;
width: calc(var(--size) * 3);
height: calc(var(--size) * 2);
margin-top: calc(var(--size) * 1.5 * -1);
transform: rotateX(60deg) rotateZ(45deg) rotateY(0deg) translateZ(0px);
transform-origin: 50% 50%;
transform-style: preserve-3d;
}
.overlay-center {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.boxes .box {
position: absolute;
top: 0;
left: 0;
width: var(--size);
height: var(--size);
transform-style: preserve-3d;
}
.boxes .box:nth-child(1) {
transform: translate(100%, 0);
-webkit-animation: box1 var(--duration) linear infinite;
animation: box1 var(--duration) linear infinite;
}
.boxes .box:nth-child(2) {
transform: translate(0, 100%);
-webkit-animation: box2 var(--duration) linear infinite;
animation: box2 var(--duration) linear infinite;
}
.boxes .box:nth-child(3) {
transform: translate(100%, 100%);
-webkit-animation: box3 var(--duration) linear infinite;
animation: box3 var(--duration) linear infinite;
}
.boxes .box:nth-child(4) {
transform: translate(200%, 0);
-webkit-animation: box4 var(--duration) linear infinite;
animation: box4 var(--duration) linear infinite;
}
.boxes .box > view {
--background: #5c8df6;
--top: auto;
--right: auto;
--bottom: auto;
--left: auto;
--translateZ: calc(var(--size) / 2);
--rotateY: 0deg;
--rotateX: 0deg;
position: absolute;
top: var(--top);
right: var(--right);
bottom: var(--bottom);
left: var(--left);
width: 100%;
height: 100%;
background: var(--background);
transform: rotateY(var(--rotateY)) rotateX(var(--rotateX)) translateZ(var(--translateZ));
}
.boxes .box > view:nth-child(1) {
--top: 0;
--left: 0;
}
.boxes .box > view:nth-child(2) {
--background: #145af2;
--right: 0;
--rotateY: 90deg;
}
.boxes .box > view:nth-child(3) {
--background: #447cf5;
--rotateX: -90deg;
}
.boxes .box > view:nth-child(4) {
--background: #dbe3f4;
--top: 0;
--left: 0;
--translateZ: calc(var(--size) * 3 * -1);
}
@-webkit-keyframes box1 {
0%,
50% {
transform: translate(100%, 0);
}
100% {
transform: translate(200%, 0);
}
}
@keyframes box1 {
0%,
50% {
transform: translate(100%, 0);
}
100% {
transform: translate(200%, 0);
}
}
@-webkit-keyframes box2 {
0% {
transform: translate(0, 100%);
}
50% {
transform: translate(0, 0);
}
100% {
transform: translate(100%, 0);
}
}
@keyframes box2 {
0% {
transform: translate(0, 100%);
}
50% {
transform: translate(0, 0);
}
100% {
transform: translate(100%, 0);
}
}
@-webkit-keyframes box3 {
0%,
50% {
transform: translate(100%, 100%);
}
100% {
transform: translate(0, 100%);
}
}
@keyframes box3 {
0%,
50% {
transform: translate(100%, 100%);
}
100% {
transform: translate(0, 100%);
}
}
@-webkit-keyframes box4 {
0% {
transform: translate(200%, 0);
}
50% {
transform: translate(200%, 100%);
}
100% {
transform: translate(100%, 100%);
}
}
@keyframes box4 {
0% {
transform: translate(200%, 0);
}
50% {
transform: translate(200%, 100%);
}
100% {
transform: translate(100%, 100%);
}
}
</style>
@@ -1,71 +0,0 @@
<script setup lang="ts">
import PickerSelect from '../picker-select/index.vue'
const props = defineProps({
label: { type: String, default: '' },
value: { type: null as unknown as PropType<any>, default: undefined },
labelField: { type: String, required: true },
labelField2: { type: String, required: false },
valueField: { type: String, required: true },
viewname: { type: String, required: true },
field1: { type: String, required: true },
field2: { type: String, required: true },
placeholder: { type: String, default: '请选择' },
searchcondition: {
type: String,
required: false,
},
searchType: {
type: String as PropType<'default' | 'ex' | 'single'>,
default: 'default',
required: false,
},
})
const emits = defineEmits<{
(e: 'update:label', val: string): void
(e: 'update:value', val: any): void
(e: 'change', value: any): void
}>()
const _visible = ref(false)
const _label = ref(props.label)
// 同步 props.label
watch(() => props.label, (val) => {
_label.value = val
})
// 选中时只处理 label
function handleSelectChange(selectValue: any) {
if (!selectValue)
return
_label.value = selectValue[props.labelField]
emits('update:label', _label.value)
emits('change', selectValue)
}
</script>
<template>
<sar-popout-input
v-model="_label"
:placeholder="props.placeholder"
@click="_visible = true"
>
<PickerSelect
v-model:visible="_visible"
:value="props.value"
:viewname="props.viewname"
:field1="props.field1"
:field2="props.field2"
:label-field="props.labelField"
:label-field2="props.labelField2"
:value-field="props.valueField"
:searchcondition="searchcondition"
:search-type="searchType"
@update:value="emits('update:value', $event)"
@change="handleSelectChange"
/>
</sar-popout-input>
</template>
@@ -1,403 +0,0 @@
<script setup lang="ts">
import type { PropType } from 'vue'
import { useIndeterminate } from 'sard-uniapp'
import { computed, ref } from 'vue'
import { getAutocompleteDataApi, getAutocompleteDataExApi } from '@/request/api'
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
type: {
type: String as PropType<'radio' | 'multiple'>,
default: 'radio',
},
value: {
type: null as unknown as PropType<any | any[]>,
default: undefined,
},
labelField: {
type: String,
required: true,
},
labelField2: {
type: String,
required: false,
},
valueField: {
type: String,
required: true,
},
viewname: {
type: String,
},
searchcondition: {
type: String,
required: false,
},
userid: {
type: String,
required: false,
},
field1: {
type: String,
required: true,
},
field2: {
type: String,
required: true,
},
searchType: {
type: String as PropType<'default' | 'ex' | 'single'>,
default: 'default',
required: false,
},
})
const emit = defineEmits(['update:visible', 'update:value', 'change'])
const _visible = ref(props.visible)
watch(() => props.visible, val => _visible.value = val)
const loading = ref(true)
const searchValue = ref('')
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
})
const isPrevDisabled = computed(() => pagination.value.current <= 1)
const isNextDisabled = computed(() =>
pagination.value.current >= Math.ceil(pagination.value.total / pagination.value.pageSize),
)
// 内部 value,双向绑定 props.value
const _value = computed({
get: () => {
if (props.type === 'multiple') {
return Array.isArray(props.value) ? props.value : []
}
return props.value ?? null
},
set: (val) => {
emit('update:value', val)
},
})
const options = ref<any[]>([])
const { allChecked, isIndeterminate, onAllChange } = useIndeterminate(
_value,
options,
)
function select(option, toggle) {
if (!option.disabled) {
toggle(option.value)
if (props.type === 'radio') {
_visible.value = false
const selectedOption = options.value.find(
o => o.value === option.value,
)
_visible.value = false
emit('update:visible', false)
emit('change', selectedOption)
}
}
}
// 显示数量
const count = computed(() => {
if (props.type === 'multiple') {
return `${(_value.value).length || 0} / ${options.value.length}`
}
else {
return _value.value != null ? `1 / ${options.value.length}` : `0 / ${options.value.length}`
}
})
function handleAfterEnter() {
getData()
}
function handlePagationChange(page: number) {
pagination.value.current = page
getData()
}
function handleSearch() {
pagination.value.current = 1
pagination.value.total = 0
pagination.value.pageSize = 10
getData()
}
function getData() {
if (props.searchType === 'ex') {
getAutocompleteDataEx()
}
else if (props.searchType === 'single') {
getAutocompleteDataSingle()
}
else {
getAutocompleteData()
}
}
function getAutocompleteData() {
loading.value = true
getAutocompleteDataApi({
q: searchValue.value,
page: pagination.value.current,
limit: pagination.value.pageSize,
viewname: props.viewname,
field1: props.field1,
field2: props.field2,
}).then((res) => {
pagination.value.total = res.data.total
options.value = res.data.rows.map((item) => {
const label1 = item[props.labelField] ?? ''
const label2 = props.labelField2
? item[props.labelField2] ?? ''
: ''
return {
label: label2 ? `${label1} | ${label2}` : label1,
value: item[props.valueField] ?? undefined,
disabled: !!item.disabled,
...item,
}
})
console.log(res.data.rows)
}).finally(() => {
loading.value = false
})
}
function getAutocompleteDataEx() {
loading.value = true
getAutocompleteDataExApi({
q: searchValue.value,
page: pagination.value.current,
limit: pagination.value.pageSize,
viewname: props.viewname,
field1: props.field1,
field2: props.field2,
userid: props.userid,
searchcondition: props.searchcondition,
}).then((res) => {
pagination.value.total = res.data.total
options.value = res.data.rows.map((item) => {
const label1 = item[props.labelField] ?? ''
const label2 = props.labelField2
? item[props.labelField2] ?? ''
: ''
return {
label: label2 ? `${label1} | ${label2}` : label1,
value: item[props.valueField] ?? undefined,
disabled: !!item.disabled,
...item,
}
})
}).finally(() => {
loading.value = false
})
}
function getAutocompleteDataSingle() {
}
function handleCancel() {
_visible.value = false
emit('update:visible', false)
}
</script>
<template>
<sar-popout
:visible="_visible"
:show-footer="false"
title="请选择"
@after-enter="handleAfterEnter"
@close="handleCancel"
>
<template v-if="props.type === 'multiple'" #title-prepend>
<view class="sar-checkbox-popout__check-all">
<sar-checkbox
v-model:checked="allChecked"
:indeterminate="isIndeterminate"
@change="onAllChange"
>
{{ count }}
</sar-checkbox>
</view>
</template>
<template #visible="{ already }">
<view v-if="already">
<sar-list-item>
<sar-input
v-model="searchValue"
placeholder="请输入搜索的值"
clearable
@confirm="handleSearch"
>
<template #prepend>
<sar-icon family="sari" name="search" />
</template>
</sar-input>
</sar-list-item>
<view class="sar-checkbox-popout__container">
<scroll-view
class="sar-checkbox-popout__scroll_searchable sar-checkbox-popout__scroll"
scroll-y
trap-scroll
:upper-threshold="0"
:lower-threshold="0"
:throttle="false"
>
<sar-empty
v-if="options.length === 0"
:root-style="{ height: '100%' }"
/>
<sar-checkbox-group
v-else-if="props.type === 'multiple'"
v-model="_value"
:validate-event="false"
>
<template #custom="{ toggle }">
<sar-list inlaid>
<sar-list-item
v-for="option in options"
:key="option.value"
:title="option.label"
@click="select(option, toggle)"
>
<template #icon>
<sar-checkbox
readonly
:value="option.value"
:validate-event="false"
/>
</template>
</sar-list-item>
</sar-list>
</template>
</sar-checkbox-group>
<sar-radio-group
v-else
v-model="_value"
:validate-event="false"
>
<template #custom="{ toggle }">
<sar-list inlaid>
<sar-list-item
v-for="option in options"
:key="option.value"
:title="option.label"
:hover="!option.disabled"
@click="select(option, toggle)"
>
<template #icon>
<sar-radio
:key="option.value"
readonly
:disabled="option.disabled"
:value="option.value"
/>
</template>
</sar-list-item>
</sar-list>
</template>
</sar-radio-group>
</scroll-view>
</view>
<view class="sar-popout__footer">
<sar-pagination
:current="pagination.current"
:total="pagination.total"
:page-size="pagination.pageSize"
type="simple"
root-class="picker-select__pagination-root"
@change="handlePagationChange"
>
<template #prev>
<sar-button type="default" :disabled="isPrevDisabled" size="mini" :loading="loading">
上一页
</sar-button>
</template>
<template #next>
<sar-button type="default" :disabled="isNextDisabled" size="mini" :loading="loading">
下一页
</sar-button>
</template>
</sar-pagination>
<sar-button
v-if="props.type === 'multiple'"
:root-style="{ width: '100%' }"
>
确认
</sar-button>
</view>
</view>
</template>
</sar-popout>
</template>
<style lang="scss" scoped>
.sar-checkbox-popout__check-all {
position: relative;
box-sizing: border-box;
flex-direction: column;
position: absolute;
left: 32rpx;
z-index: 2;
display: flex;
flex-direction: row;
align-items: center;
color: var(--sar-tertiary-color);
font-size: var(--sar-text-sm);
}
.sar-checkbox-popout__container {
position: relative;
}
.sar-checkbox-popout__scroll_searchable {
height: var(--sar-checkbox-popout-max-height);
}
.sar-checkbox-popout__scroll {
max-height: var(--sar-checkbox-popout-max-height);
}
.sar-popout__footer {
width: 100%;
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16rpx;
align-items: center;
padding: var(--sar-popout-footer-gap-y) var(--sar-popout-footer-gap-x);
}
</style>
@@ -1,647 +0,0 @@
<script lang="ts" setup>
import { toast } from 'sard-uniapp'
import { loaddataApi, loaddatabysqlApi } from '@/request/api'
import { useUserStore } from '@/store'
import { jsonToString } from '@/utils/common'
import { saveObjectHelper } from '@/utils/save'
import { getCurrentDateTimeWithSeconds } from '@/utils/time'
const props = defineProps({
componentData: {
type: null,
},
})
const emits = defineEmits(['success'])
const visible = defineModel({
type: Boolean,
default: false,
})
const userStore = useUserStore()
const loading = ref(true)
const formRef = ref()
const auditData = ref({
showSelect: true,
selectSearchCondition: '',
toShUsername: '',
toShUserid: '',
shMemo: '',
})
const shsetdatas = ref<any[]>([])
const midAuditShData = ref<{
state: string
shlevel: number
rowdata: any
}>({
state: '',
shlevel: 0,
rowdata: {},
})
const midAuditBhData = ref<{
now_level: number
ywownerid: string
nowstate: string
}>({
ywownerid: '',
nowstate: '',
now_level: 0,
})
async function handleAfterEnter() {
console.log('===after enter===')
loading.value = true
if (props.componentData.row === null || props.componentData.moduleId === '' || props.componentData.saveTable === '') {
toast.fail('传递参数有误,请联系管理人员!')
return
}
const req1 = await loaddataApi({
viewname: 'sh_b_set',
searchcondition: `b_module_id = '${props.componentData.moduleId}'`,
orderfield: 'b_level',
soption: '',
})
shsetdatas.value = req1.data
if (req1.data.length === 0) {
handleNoLevelSh()
}
else {
if (props.componentData.state === '审核通过') {
handleLevelSh()
}
else if (props.componentData.state === '审核驳回') {
handleLevelBh()
}
}
}
async function handleNoLevelSh() {
const { state, saveTable, moduleId, row } = props.componentData
let canuse_state = []
if (state === '送审') {
canuse_state = ['草拟', '审核驳回', '']
}
if (state === '归档') {
canuse_state = ['审核通过', '完成']
}
let subtable = []
let subinsertvalue = []
let subupdatevalue = []
let subdelvalue = []
let keyid = []
let logvalues = []
let LogArray = []
let updateRows = []
let updateFeeData = []
subtable.push(saveTable)
keyid.push('b_id')
subdelvalue.push('[]')
subinsertvalue.push('[]')
if (canuse_state.length === 0 || canuse_state.includes(row.b_state)) {
if (['cw_zdorder', 'cw_zdorder_f'].includes(moduleId)) {
if (state === '审核通过') {
// 暂时不做处理
}
if (state === '审核驳回') {
let sql = `select top 1 * from bs_business_cw where mx_zdorderid ='${row.b_id}' and (isnull(mx_invoiceid,'')<>'' or isnull(mx_sumamount,0)<>0)`
await loaddatabysqlApi({
sql,
orderfield: 'subid',
soption: '',
}).then((res) => {
if (res.data.length > 0) {
toast.fail('驳回失败!费用项已销账或开票!')
}
})
}
}
const updateObj = {
b_id: row.b_id,
b_state: state,
}
const oldState = row.b_state
let OpContent = `审核状态:${oldState}->${state}`
let logoldData = `审核状态:${oldState}`
let lognewData = `审核状态:${state}`
updateRows.push(updateObj)
LogArray.push({
OpType: state,
ModuleID: moduleId,
RecordID: row.b_id,
viewsubtable: saveTable,
OpContent,
modify_b: logoldData,
modify_h: lognewData,
})
}
else {
toast.fail(`只有${canuse_state.join(',')}的记录才能${state}!`)
}
if (updateRows.length > 0) {
subupdatevalue.push(jsonToString(updateRows))
logvalues.push(jsonToString(LogArray))
if (updateFeeData.length > 0) {
subtable.push('bs_business_cw')
keyid.push('subid')
subdelvalue.push('[]')
subinsertvalue.push('[]')
subupdatevalue.push(jsonToString(updateFeeData))
}
await saveObjectHelper({
tables: subtable,
insertValues: subinsertvalue,
delValues: subdelvalue,
updateValues: subupdatevalue,
keys: keyid,
logValues: logvalues,
}).then((data) => {
if (data === 'OK') {
handleCloseDialog()
toast.success(`${state}成功!`)
}
})
}
}
async function handleLevelSh() {
const { userid } = userStore.userInfo
const { saveTable, moduleId, row } = props.componentData
const ywid = row.b_id
let rowdata: any = {}
await loaddataApi({
viewname: saveTable,
searchcondition: `b_id='${ywid}'`,
orderfield: 'b_id',
soption: '',
}).then((res) => {
if (res.data.length === 0) {
toast.fail('当前业务单不存在')
handleCloseDialog()
return
}
rowdata = res.data[0]
})
let ywownerid = rowdata.b_owner_id
let shrecords = []
await loaddataApi({
viewname: 'sh_record',
searchcondition: `b_dj_id='${ywid}' and b_module_id='${moduleId}'`,
orderfield: 'b_id desc',
soption: '',
}).then((res) => {
if (res.data.length === 0) {
toast.fail('没有待审记录')
handleCloseDialog()
return
}
shrecords = res.data
})
let now_level = shrecords[0].b_level
let shlevel: any = Number.parseInt(now_level) + 1
let dsh_useid = shrecords[0].b_tosh_useid
let can_sh = false
let can_nextsh = false
let level_userid = []
if (dsh_useid !== userid) {
toast.fail('你没有该票业务的审批权限')
handleCloseDialog()
return
}
for (const item of shsetdatas.value) {
if (Number(item.b_level) === shlevel && item.b_user_id === dsh_useid) {
can_sh = true
}
if (Number(item.b_level) === Number.parseInt(shlevel) + 1) {
can_nextsh = true
level_userid.push(item.b_user_id)
}
}
if (!can_sh) {
toast.fail('你没有该票业务的审批权限')
handleCloseDialog()
return
}
midAuditShData.value.shlevel = shlevel
let sh_state = '审核通过'
let xxx = ['一', '二', '三', '四', '五', '六', '七', '八']
if (can_nextsh) {
sh_state = `${xxx[now_level]}审通过`
}
midAuditShData.value.state = sh_state
midAuditShData.value.rowdata = rowdata
let str_leveluserid = level_userid.map(item => `'${item}'`).join(',')
let searchcondition = `b_id in (${str_leveluserid})`
auditData.value.selectSearchCondition = searchcondition
if (level_userid.length === 1) {
auditData.value.toShUserid = level_userid[0]
auditData.value.showSelect = false
await loadShUserName(level_userid[0])
}
if (level_userid.length === 0) {
auditData.value.toShUserid = ywownerid
auditData.value.showSelect = false
await loadShUserName(ywownerid)
}
loading.value = false
}
async function loadShUserName(userid) {
if (!userid)
return
try {
const res = await loaddatabysqlApi({
sql: `select b_name from b_user where b_id = '${userid}'`,
soption: '',
orderfield: '',
})
const name = res?.data?.[0]?.b_name
auditData.value.toShUsername = name || userid
}
catch (err) {
console.error('获取审核人名称失败:', err)
auditData.value.toShUsername = userid
}
}
// 驳回
async function handleLevelBh() {
const { userid } = userStore.userInfo
const { saveTable, moduleId, row } = props.componentData
const ywid = row.b_id
let rowdata: any = {}
await loaddataApi({
viewname: saveTable,
searchcondition: `b_id='${ywid}'`,
orderfield: 'b_id',
soption: '',
}).then((res) => {
if (res.data.length === 0) {
toast.fail('当前业务单不存在')
handleCloseDialog()
return
}
rowdata = res.data[0]
})
let ywownerid = rowdata.b_owner_id
let nowstate = rowdata.b_state
if (nowstate === '审核驳回') {
toast.fail('已驳回!请检查!')
return false
}
let shrecords = []
let now_level = 0
await loaddataApi({
viewname: 'sh_record',
searchcondition: `b_dj_id='${ywid}' and b_module_id='${moduleId}'`,
orderfield: 'b_id',
soption: '',
}).then((res) => {
shrecords = res.data
})
if (shrecords.length === 0) {
await loaddataApi({
viewname: 'sh_b_set',
searchcondition: `b_module_id='${moduleId}' and b_user_id='${userid}'`,
orderfield: 'b_level',
soption: '',
}).then((res) => {
if (res.data.length === 0) {
toast.fail('你没有该票业务的驳回权限')
handleCloseDialog()
}
})
}
else {
now_level = shrecords[0].b_level
let dsh_useid = shrecords[0].b_tosh_useid
let sh_userid = shrecords[0].b_user_id
if (dsh_useid !== userid && sh_userid !== userid && now_level > 0) {
toast.fail('你没有该票业务的驳回权限')
handleCloseDialog()
return false
}
if (nowstate === '送审' && dsh_useid !== userid) {
toast.fail('你没有该票业务的驳回权限')
handleCloseDialog()
return false
}
}
auditData.value.toShUserid = ywownerid
auditData.value.showSelect = false
await loadShUserName(ywownerid)
midAuditBhData.value = {
ywownerid,
nowstate,
now_level,
}
loading.value = false
}
async function handleBeforeClose(type: string) {
if (type !== 'confirm')
return true
if (props.componentData.state === '审核通过') {
return await handleConfirmSh()
}
else if (props.componentData.state === '审核驳回') {
return await handleConfirmBh()
}
return true
}
async function handleConfirmSh() {
if (auditData.value.toShUserid === '') {
toast.fail('请指定审批人')
return false
}
if (midAuditShData.value.rowdata === undefined) {
return false
}
try {
loading.value = true
const { userid } = userStore.userInfo
const { rowdata, shlevel, state } = midAuditShData.value
const { saveTable, moduleId } = props.componentData
let subtable = []
let subinsertvalue = []
let subupdatevalue = []
let subdelvalue = []
let keyid = []
let logvalues = []
let updateRows = []
let LogArray = []
let insert_sh_records = []
let inputdatetime = getCurrentDateTimeWithSeconds()
let oldstate = rowdata.b_state
let obj: any = { b_id: rowdata.b_id, b_state: state }
if (state === '审核通过') {
obj.mx_shdate = inputdatetime
obj.mx_shman_id = userid
}
updateRows.push(obj)
let OpContent = `审核状态:${oldstate}->${state}`
let logoldData = `审核状态:${oldstate}`
let lognewData = `审核状态:${state}`
LogArray.push({ OpType: '修改', ModuleID: moduleId, RecordID: rowdata.b_id, viewsubtable: saveTable, OpContent, modify_b: logoldData, modify_h: lognewData })
insert_sh_records.push({
b_module_id: moduleId,
b_user_id: userid,
b_level: shlevel,
b_name: state,
b_dj_id: rowdata.b_id,
b_sh: '1',
b_sh_date: inputdatetime,
b_sh_memo: auditData.value.shMemo,
b_tosh_useid: auditData.value.toShUserid,
})
if (updateRows.length > 0) {
subtable.length = 0
subtable.push(saveTable)
keyid.push('b_id')
subdelvalue.push('[]')
subinsertvalue.push('[]')
subupdatevalue.push(jsonToString(updateRows))
logvalues.push(jsonToString(LogArray))
subtable.push('sh_record')
keyid.push('b_id')
subdelvalue.push('[]')
subinsertvalue.push(jsonToString(insert_sh_records))
subupdatevalue.push('[]')
logvalues.push('[]')
const sResult = await saveObjectHelper({ tables: subtable, insertValues: subinsertvalue, delValues: subdelvalue, updateValues: subupdatevalue, keys: keyid, logValues: logvalues })
if (sResult === 'OK') {
toast.success(`${state}成功!`)
emits('success')
return true
}
}
}
catch (e) {
toast.fail('操作失败')
return false
}
return false
}
async function handleConfirmBh() {
const { now_level, ywownerid, nowstate } = midAuditBhData.value
const { state, saveTable, moduleId, row } = props.componentData
const { userid } = userStore.userInfo
const ywid = row.b_id
let subtable = []
let subinsertvalue = []
let subupdatevalue = []
let subdelvalue = []
let keyid = []
let logvalues = []
let updateRows = []
let LogArray = []
let insert_sh_records = []
let inputdatetime = getCurrentDateTimeWithSeconds()
let obj: any = { b_id: ywid, b_state: state }
if (state === '审核驳回') {
obj.mx_shdate = ''
obj.mx_shman_id = ''
}
updateRows.push(obj)
let OpContent = `审核状态:${nowstate}->${state}`
let logoldData = `审核状态:${nowstate}`
let lognewData = `审核状态:${state}`
LogArray.push({ OpType: '修改', ModuleID: moduleId, RecordID: ywid, viewsubtable: saveTable, OpContent, modify_b: logoldData, modify_h: lognewData })
insert_sh_records.push({
b_module_id: moduleId,
b_user_id: userid,
b_level: now_level,
b_name: state,
b_dj_id: ywid,
b_sh: '0',
b_sh_date: inputdatetime,
b_sh_memo: auditData.value.shMemo,
b_tosh_useid: ywownerid,
})
if (updateRows.length > 0) {
subtable.length = 0
subtable.push(saveTable)
keyid.push('b_id')
subdelvalue.push('[]')
subinsertvalue.push('[]')
subupdatevalue.push(jsonToString(updateRows))
logvalues.push(jsonToString(LogArray))
subtable.push('sh_record')
keyid.push('b_id')
subdelvalue.push('[]')
subinsertvalue.push(jsonToString(insert_sh_records))
subupdatevalue.push('[]')
logvalues.push('[]')
const sResult = await saveObjectHelper({
tables: subtable,
insertValues: subinsertvalue,
delValues: subdelvalue,
updateValues: subupdatevalue,
keys: keyid,
logValues: logvalues,
})
if (sResult === 'OK') {
toast.success(`${state}成功!`)
emits('success')
return true
}
}
}
function handleAfterClose() {
loading.value = true
auditData.value = {
showSelect: true,
selectSearchCondition: '',
toShUserid: '',
toShUsername: '',
shMemo: '',
}
shsetdatas.value = []
midAuditShData.value = {
state: '',
shlevel: 0,
rowdata: {},
}
midAuditBhData.value = {
ywownerid: '',
nowstate: '',
now_level: 0,
}
}
function handleCloseDialog() {
visible.value = false
}
</script>
<template>
<sar-dialog
v-model:visible="visible"
popup-style="top: 50%"
:title="props.componentData.state"
button-type="text"
:before-close="handleBeforeClose"
@after-enter="handleAfterEnter"
@after-leave="handleAfterClose"
>
<scroll-view
style="max-height: 60vh; margin-top: 1px; margin-bottom: 16rpx"
scroll-y
>
<view v-if="loading" class="dialog-loading">
<sar-loading vertical>
加载中...
</sar-loading>
</view>
<sar-form v-else ref="formRef" :model="auditData" label-width="150rpx" label-align="start" star-position="right">
<sar-form-item
v-if="auditData.showSelect"
name="toShUserid"
label="送报审批人"
>
<picker-select-input
v-model:label="auditData.toShUsername"
v-model:value="auditData.toShUserid"
:searchcondition="auditData.selectSearchCondition"
search-type="ex"
viewname="b_user"
label-field="b_name"
value-field="b_id"
field1="b_id"
field2="b_name"
/>
</sar-form-item>
<sar-form-item
name="shMemo"
label="审核意见"
>
<sar-input v-model="auditData.shMemo" type="textarea" inlaid placeholder="请输入审核意见" />
</sar-form-item>
</sar-form>
</scroll-view>
</sar-dialog>
</template>
<style lang="scss" scoped>
.dialog-loading {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
}
</style>
-24
View File
@@ -1,24 +0,0 @@
/// <reference types="vite/client" />
/// <reference types="vite-svg-loader" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<object, object, any>
export default component
}
interface ImportMetaEnv {
/** 服务端口号 */
readonly VITE_SERVER_PORT: string
/** 是否清除console */
readonly VITE_DELETE_CONSOLE: string
readonly VITE_ALIYUN_BUCKET: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
declare const __UNI_PLATFORM__: 'app' | 'h5' | 'mp-alipay' | 'mp-baidu' | 'mp-kuaishou' | 'mp-lark' | 'mp-qq' | 'mp-tiktok' | 'mp-weixin' | 'mp-xiaochengxu'
@@ -1,38 +0,0 @@
import { onMounted, ref } from 'vue'
export function useDeviceInfo() {
const isMobile = ref(false)
const screenWidth = ref(0)
// 判断是否为手机
function checkIsMobile() {
const ua = navigator.userAgent || ''
const isPhoneUA = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)
const isMobileViewport = window.matchMedia('(max-width: 767px)').matches
// 平板 / iPad / 电脑都算 PC
isMobile.value = isPhoneUA && isMobileViewport
}
onMounted(() => {
// 首先用 UA + 媒体查询判断
checkIsMobile()
// 兜底:uni.getSystemInfo 获取屏幕宽度
if (typeof uni !== 'undefined' && uni.getSystemInfo) {
uni.getSystemInfo({
success: (res) => {
screenWidth.value = res.screenWidth
},
})
}
else {
screenWidth.value = window.innerWidth
}
// 可选:监听窗口大小变化
window.addEventListener('resize', checkIsMobile)
})
return { isMobile, screenWidth }
}
@@ -1,11 +0,0 @@
<script lang="ts" setup>
</script>
<template>
<view>
<slot />
<sar-toast-agent />
<sar-dialog-agent />
<sar-notify-agent />
</view>
</template>
Loaded 100 of 183 files, more files were not shown because too many files have changed in this diff. Show more