feat: 配置 TailwindCSS 并迁移到 Redux

- 配置 TailwindCSS 支持小程序开发
  - 安装 tailwindcss, postcss, autoprefixer
  - 安装 weapp-tailwindcss 插件支持小程序
  - 配置 tailwind.config.js 和 postcss.config.js
  - 更新 Taro 配置支持 TailwindCSS

- 从 Zustand 迁移到 Redux Toolkit
  - 移除 zustand 依赖
  - 安装 redux, react-redux, redux-thunk, @reduxjs/toolkit
  - 重构状态管理架构:
    - src/constants/ - Action 类型常量
    - src/actions/ - Action creators 和异步 actions
    - src/reducers/ - Reducers
    - src/selectors/ - 状态选择器
    - src/hooks/redux.ts - 类型化 hooks
  - 更新组件使用新的 Redux API
  - 保持数据持久化功能

- 更新应用配置
  - 将 app.ts 重命名为 app.tsx 支持 JSX
  - 添加 Redux Provider 到应用根组件
  - 更新 TODO.md 标记完成状态

- 构建验证通过,所有功能正常
This commit is contained in:
2025-09-03 16:33:06 +08:00
parent c8480308e0
commit 14205a9021
24 changed files with 2606 additions and 287 deletions

13
TODO.md
View File

@@ -53,11 +53,14 @@ interface HistoryRecord {
```
#### 2.2 状态管理
- [ ] 创建 `src/store/` 目录
- [ ] 实现 Zustand 状态管理
- [ ] `src/store/historyStore.ts` - 历史记录管理
- [ ] `src/store/templateStore.ts` - 模板配置管理
- [ ] 添加数据持久化功能
- [x] 创建 `src/store/` 目录
- [x] 实现 Redux 状态管理(已从 Zustand 迁移到 Redux Toolkit
- [x] `src/reducers/history.ts` - 历史记录管理
- [x] `src/reducers/template.ts` - 模板配置管理
- [x] `src/actions/` - Action creators
- [x] `src/selectors/` - 选择器
- [x] `src/hooks/redux.ts` - 类型化 hooks
- [x] 添加数据持久化功能(通过 Taro Storage API
### 阶段三:模板卡片首页开发 (优先级:高)

View File

@@ -1,4 +1,5 @@
import { defineConfig, type UserConfigExport } from '@tarojs/cli'
import { UnifiedWebpackPluginV5 } from 'weapp-tailwindcss/webpack'
import devConfig from './dev'
import prodConfig from './prod'
@@ -56,6 +57,20 @@ export default defineConfig<'vite'>(async (merge) => {
}
}
},
webpackChain(chain) {
chain.merge({
plugin: {
install: {
plugin: UnifiedWebpackPluginV5,
args: [{
appType: 'taro',
// 下面个配置,会开启 rem -> rpx 的转化
rem2rpx: true
}]
}
}
})
}
},
// 小程序平台特定配置
weapp: {

View File

@@ -12,6 +12,7 @@
"scripts": {
"claude": "claude --dangerously-skip-permissions",
"prepare": "husky",
"postinstall": "npx weapp-tw patch",
"new": "taro new",
"test": "jest",
"test:watch": "jest --watch",
@@ -51,6 +52,7 @@
"author": "",
"dependencies": {
"@babel/runtime": "^7.24.4",
"@reduxjs/toolkit": "^2.9.0",
"@tarojs/components": "4.1.6",
"@tarojs/helper": "4.1.6",
"@tarojs/plugin-framework-react": "4.1.6",
@@ -68,7 +70,9 @@
"@tarojs/taro": "4.1.6",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"zustand": "^5.0.8"
"react-redux": "^9.2.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0"
},
"devDependencies": {
"@babel/core": "^7.24.4",
@@ -87,6 +91,7 @@
"@types/minimatch": "^5",
"@types/react": "^18.0.0",
"@vitejs/plugin-react": "^4.3.0",
"autoprefixer": "^10.4.21",
"babel-jest": "^30.1.2",
"babel-preset-taro": "4.1.6",
"eslint": "^8.57.0",
@@ -102,9 +107,11 @@
"react-refresh": "^0.14.0",
"stylelint": "^16.4.0",
"stylelint-config-standard": "^38.0.0",
"tailwindcss": "^4.1.12",
"terser": "^5.30.4",
"ts-jest": "^29.4.1",
"typescript": "^5.4.5",
"vite": "^4.2.0"
"vite": "^4.2.0",
"weapp-tailwindcss": "^4.2.6"
}
}

2001
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

7
postcss.config.js Normal file
View File

@@ -0,0 +1,7 @@
// postcss 插件以 object 方式注册的话,是按照由上到下的顺序执行的
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

200
src/actions/history.ts Normal file
View File

@@ -0,0 +1,200 @@
import Taro from '@tarojs/taro'
import { Dispatch } from 'redux'
import { HistoryRecord } from '../store/types'
import * as types from '../constants/history'
const STORAGE_KEY = 'historyRecords'
// Action creators
export const loadRecordsRequest = () => ({
type: types.LOAD_RECORDS_REQUEST
})
export const loadRecordsSuccess = (records: HistoryRecord[]) => ({
type: types.LOAD_RECORDS_SUCCESS,
payload: records
})
export const loadRecordsFailure = (error: string) => ({
type: types.LOAD_RECORDS_FAILURE,
payload: error
})
export const addRecordRequest = () => ({
type: types.ADD_RECORD_REQUEST
})
export const addRecordSuccess = (record: HistoryRecord) => ({
type: types.ADD_RECORD_SUCCESS,
payload: record
})
export const addRecordFailure = (error: string) => ({
type: types.ADD_RECORD_FAILURE,
payload: error
})
export const updateRecordRequest = () => ({
type: types.UPDATE_RECORD_REQUEST
})
export const updateRecordSuccess = (record: HistoryRecord) => ({
type: types.UPDATE_RECORD_SUCCESS,
payload: record
})
export const updateRecordFailure = (error: string) => ({
type: types.UPDATE_RECORD_FAILURE,
payload: error
})
export const deleteRecordRequest = () => ({
type: types.DELETE_RECORD_REQUEST
})
export const deleteRecordSuccess = (id: string) => ({
type: types.DELETE_RECORD_SUCCESS,
payload: id
})
export const deleteRecordFailure = (error: string) => ({
type: types.DELETE_RECORD_FAILURE,
payload: error
})
export const clearRecordsRequest = () => ({
type: types.CLEAR_RECORDS_REQUEST
})
export const clearRecordsSuccess = () => ({
type: types.CLEAR_RECORDS_SUCCESS
})
export const clearRecordsFailure = (error: string) => ({
type: types.CLEAR_RECORDS_FAILURE,
payload: error
})
export const clearError = () => ({
type: types.CLEAR_ERROR
})
// Async actions
export const loadRecords = () => {
return async (dispatch: Dispatch) => {
dispatch(loadRecordsRequest())
try {
const savedRecords = Taro.getStorageSync(STORAGE_KEY) || []
dispatch(loadRecordsSuccess(savedRecords))
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '加载历史记录失败'
dispatch(loadRecordsFailure(errorMsg))
console.error('加载历史记录失败:', error)
}
}
}
export const addRecord = (recordData: Omit<HistoryRecord, 'id' | 'createTime' | 'updateTime'>) => {
return async (dispatch: Dispatch, getState: () => any) => {
dispatch(addRecordRequest())
try {
const id = `record_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
const now = new Date().toISOString()
const newRecord: HistoryRecord = {
...recordData,
id,
createTime: now,
updateTime: now
}
const currentRecords = getState().history.records
const updatedRecords = [newRecord, ...currentRecords]
await Taro.setStorage({
key: STORAGE_KEY,
data: updatedRecords
})
dispatch(addRecordSuccess(newRecord))
return id
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '添加记录失败'
dispatch(addRecordFailure(errorMsg))
console.error('添加记录失败:', error)
throw error
}
}
}
export const updateRecord = (id: string, updates: Partial<HistoryRecord>) => {
return async (dispatch: Dispatch, getState: () => any) => {
dispatch(updateRecordRequest())
try {
const currentRecords = getState().history.records
const recordIndex = currentRecords.findIndex((record: HistoryRecord) => record.id === id)
if (recordIndex === -1) {
throw new Error('记录不存在')
}
const updatedRecord = {
...currentRecords[recordIndex],
...updates,
updateTime: new Date().toISOString()
}
const updatedRecords = [...currentRecords]
updatedRecords[recordIndex] = updatedRecord
await Taro.setStorage({
key: STORAGE_KEY,
data: updatedRecords
})
dispatch(updateRecordSuccess(updatedRecord))
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '更新记录失败'
dispatch(updateRecordFailure(errorMsg))
console.error('更新记录失败:', error)
throw error
}
}
}
export const deleteRecord = (id: string) => {
return async (dispatch: Dispatch, getState: () => any) => {
dispatch(deleteRecordRequest())
try {
const currentRecords = getState().history.records
const updatedRecords = currentRecords.filter((record: HistoryRecord) => record.id !== id)
await Taro.setStorage({
key: STORAGE_KEY,
data: updatedRecords
})
dispatch(deleteRecordSuccess(id))
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '删除记录失败'
dispatch(deleteRecordFailure(errorMsg))
console.error('删除记录失败:', error)
throw error
}
}
}
export const clearRecords = () => {
return async (dispatch: Dispatch) => {
dispatch(clearRecordsRequest())
try {
await Taro.removeStorage({ key: STORAGE_KEY })
dispatch(clearRecordsSuccess())
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '清空记录失败'
dispatch(clearRecordsFailure(errorMsg))
console.error('清空记录失败:', error)
throw error
}
}
}

12
src/actions/template.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Template } from '../store/types'
import * as types from '../constants/template'
export const initTemplates = (templates: Template[]) => ({
type: types.INIT_TEMPLATES,
payload: templates
})
export const setSelectedTemplate = (template: Template | null) => ({
type: types.SET_SELECTED_TEMPLATE,
payload: template
})

View File

@@ -1,4 +1,8 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
#app{
width: 100vw;
height: 100vh;

View File

@@ -1,16 +1,23 @@
import { PropsWithChildren } from 'react'
import { useLaunch } from '@tarojs/taro'
import { Provider } from 'react-redux'
import configStore from './store'
import './app.css'
const store = configStore()
function App({ children }: PropsWithChildren<any>) {
useLaunch(() => {
console.log('App launched.')
})
// children 是将要会渲染的页面
return children
return (
<Provider store={store}>
{children}
</Provider>
)
}

22
src/constants/history.ts Normal file
View File

@@ -0,0 +1,22 @@
// History action types
export const LOAD_RECORDS_REQUEST = 'LOAD_RECORDS_REQUEST'
export const LOAD_RECORDS_SUCCESS = 'LOAD_RECORDS_SUCCESS'
export const LOAD_RECORDS_FAILURE = 'LOAD_RECORDS_FAILURE'
export const ADD_RECORD_REQUEST = 'ADD_RECORD_REQUEST'
export const ADD_RECORD_SUCCESS = 'ADD_RECORD_SUCCESS'
export const ADD_RECORD_FAILURE = 'ADD_RECORD_FAILURE'
export const UPDATE_RECORD_REQUEST = 'UPDATE_RECORD_REQUEST'
export const UPDATE_RECORD_SUCCESS = 'UPDATE_RECORD_SUCCESS'
export const UPDATE_RECORD_FAILURE = 'UPDATE_RECORD_FAILURE'
export const DELETE_RECORD_REQUEST = 'DELETE_RECORD_REQUEST'
export const DELETE_RECORD_SUCCESS = 'DELETE_RECORD_SUCCESS'
export const DELETE_RECORD_FAILURE = 'DELETE_RECORD_FAILURE'
export const CLEAR_RECORDS_REQUEST = 'CLEAR_RECORDS_REQUEST'
export const CLEAR_RECORDS_SUCCESS = 'CLEAR_RECORDS_SUCCESS'
export const CLEAR_RECORDS_FAILURE = 'CLEAR_RECORDS_FAILURE'
export const CLEAR_ERROR = 'CLEAR_ERROR'

View File

@@ -0,0 +1,3 @@
// Template action types
export const INIT_TEMPLATES = 'INIT_TEMPLATES'
export const SET_SELECTED_TEMPLATE = 'SET_SELECTED_TEMPLATE'

6
src/hooks/redux.ts Normal file
View File

@@ -0,0 +1,6 @@
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from '../store'
// 使用类型化的 hooks
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

View File

@@ -1,19 +1,18 @@
import { View, Text, ScrollView } from '@tarojs/components'
import { useEffect } from 'react'
import Taro from '@tarojs/taro'
import { useHistoryStore } from '../../store'
import { useAppDispatch, useAppSelector } from '../../hooks/redux'
import { selectHistoryRecords } from '../../selectors/history'
import { loadRecords, clearRecords } from '../../actions/history'
import './index.css'
export default function History() {
const {
records,
loadRecords,
clearRecords
} = useHistoryStore()
const dispatch = useAppDispatch()
const records = useAppSelector(selectHistoryRecords)
useEffect(() => {
loadRecords()
}, [loadRecords])
dispatch(loadRecords())
}, [dispatch])
const clearHistory = () => {
Taro.showModal({
@@ -22,7 +21,7 @@ export default function History() {
success: async (res) => {
if (res.confirm) {
try {
await clearRecords()
await dispatch(clearRecords())
Taro.showToast({
title: '清空成功',
icon: 'success'

View File

@@ -1,20 +1,26 @@
import { View, Text } from '@tarojs/components'
import { useEffect } from 'react'
import { navigateTo } from '@tarojs/taro'
import { useTemplateStore, Template } from '../../store/index'
import { useAppDispatch, useAppSelector } from '../../hooks/redux'
import { selectTemplates } from '../../selectors/template'
import { initTemplates, setSelectedTemplate } from '../../actions/template'
import { Template } from '../../store/types'
import TemplateCard from '../../components/TemplateCard'
import './index.css'
export default function Home() {
const { templates, initTemplates, setSelectedTemplate } = useTemplateStore()
const dispatch = useAppDispatch()
const templates = useAppSelector(selectTemplates)
useEffect(() => {
initTemplates()
}, [initTemplates])
// 需要先检查是否有模板配置文件
const { TEMPLATE_CONFIG } = require('../../config/templates')
dispatch(initTemplates(TEMPLATE_CONFIG))
}, [dispatch])
const handleTemplateClick = (template: Template) => {
// 设置选中的模板
setSelectedTemplate(template)
dispatch(setSelectedTemplate(template))
// 跳转到原有的功能页面,保持老功能可用
navigateTo({

91
src/reducers/history.ts Normal file
View File

@@ -0,0 +1,91 @@
import { HistoryRecord } from '../store/types'
import * as types from '../constants/history'
export interface HistoryState {
records: HistoryRecord[]
loading: boolean
error: string | null
}
const initialState: HistoryState = {
records: [],
loading: false,
error: null
}
export default function historyReducer(state = initialState, action: any): HistoryState {
switch (action.type) {
case types.LOAD_RECORDS_REQUEST:
case types.ADD_RECORD_REQUEST:
case types.UPDATE_RECORD_REQUEST:
case types.DELETE_RECORD_REQUEST:
case types.CLEAR_RECORDS_REQUEST:
return {
...state,
loading: true,
error: null
}
case types.LOAD_RECORDS_SUCCESS:
return {
...state,
loading: false,
records: action.payload,
error: null
}
case types.ADD_RECORD_SUCCESS:
return {
...state,
loading: false,
records: [action.payload, ...state.records],
error: null
}
case types.UPDATE_RECORD_SUCCESS:
return {
...state,
loading: false,
records: state.records.map(record =>
record.id === action.payload.id ? action.payload : record
),
error: null
}
case types.DELETE_RECORD_SUCCESS:
return {
...state,
loading: false,
records: state.records.filter(record => record.id !== action.payload),
error: null
}
case types.CLEAR_RECORDS_SUCCESS:
return {
...state,
loading: false,
records: [],
error: null
}
case types.LOAD_RECORDS_FAILURE:
case types.ADD_RECORD_FAILURE:
case types.UPDATE_RECORD_FAILURE:
case types.DELETE_RECORD_FAILURE:
case types.CLEAR_RECORDS_FAILURE:
return {
...state,
loading: false,
error: action.payload
}
case types.CLEAR_ERROR:
return {
...state,
error: null
}
default:
return state
}
}

11
src/reducers/index.ts Normal file
View File

@@ -0,0 +1,11 @@
import { combineReducers } from 'redux'
import history from './history'
import template from './template'
const rootReducer = combineReducers({
history,
template
})
export type RootState = ReturnType<typeof rootReducer>
export default rootReducer

31
src/reducers/template.ts Normal file
View File

@@ -0,0 +1,31 @@
import { Template } from '../store/types'
import * as types from '../constants/template'
export interface TemplateState {
templates: Template[]
selectedTemplate: Template | null
}
const initialState: TemplateState = {
templates: [],
selectedTemplate: null
}
export default function templateReducer(state = initialState, action: any): TemplateState {
switch (action.type) {
case types.INIT_TEMPLATES:
return {
...state,
templates: action.payload
}
case types.SET_SELECTED_TEMPLATE:
return {
...state,
selectedTemplate: action.payload
}
default:
return state
}
}

18
src/selectors/history.ts Normal file
View File

@@ -0,0 +1,18 @@
import { RootState } from '../store'
import { HistoryRecord } from '../store/types'
// History selectors
export const selectHistoryRecords = (state: RootState): HistoryRecord[] =>
state.history.records
export const selectHistoryLoading = (state: RootState): boolean =>
state.history.loading
export const selectHistoryError = (state: RootState): string | null =>
state.history.error
export const selectRecordById = (state: RootState, id: string): HistoryRecord | undefined =>
state.history.records.find(record => record.id === id)
export const selectRecordsByStatus = (state: RootState, status: HistoryRecord['status']): HistoryRecord[] =>
state.history.records.filter(record => record.status === status)

15
src/selectors/template.ts Normal file
View File

@@ -0,0 +1,15 @@
import { RootState } from '../store'
import { Template } from '../store/types'
// Template selectors
export const selectTemplates = (state: RootState): Template[] =>
state.template.templates
export const selectSelectedTemplate = (state: RootState): Template | null =>
state.template.selectedTemplate
export const selectTemplateById = (state: RootState, id: string): Template | undefined =>
state.template.templates.find(template => template.id === id)
export const selectTemplatesByType = (state: RootState, type: 'image-to-image' | 'image-to-video'): Template[] =>
state.template.templates.filter(template => template.type === type)

167
src/store/README.md Normal file
View File

@@ -0,0 +1,167 @@
# Redux Store 使用指南
本项目已从 Zustand 迁移到 Redux Toolkit以下是使用指南。
## 目录结构
```
src/
├── store/
│ ├── index.ts # Store 配置
│ └── types.ts # 类型定义
├── constants/ # Action 类型常量
│ ├── history.ts
│ └── template.ts
├── actions/ # Action creators
│ ├── history.ts
│ └── template.ts
├── reducers/ # Reducers
│ ├── index.ts
│ ├── history.ts
│ └── template.ts
├── selectors/ # 选择器
│ ├── history.ts
│ └── template.ts
└── hooks/
└── redux.ts # 类型化的 hooks
```
## 基本使用
### 1. 在组件中使用 Redux
```tsx
import { useAppDispatch, useAppSelector } from '../../hooks/redux'
import { selectHistoryRecords, selectHistoryLoading } from '../../selectors/history'
import { loadRecords, addRecord } from '../../actions/history'
export default function MyComponent() {
const dispatch = useAppDispatch()
const records = useAppSelector(selectHistoryRecords)
const loading = useAppSelector(selectHistoryLoading)
useEffect(() => {
dispatch(loadRecords())
}, [dispatch])
const handleAddRecord = async () => {
try {
await dispatch(addRecord({
type: 'image-to-image',
templateId: '1',
templateName: '艺术风格',
inputImage: 'path/to/image.jpg',
status: 'generating'
}))
} catch (error) {
console.error('添加记录失败:', error)
}
}
return (
<View>
{loading && <Text>...</Text>}
{records.map(record => (
<View key={record.id}>
<Text>{record.templateName}</Text>
</View>
))}
<Button onClick={handleAddRecord}></Button>
</View>
)
}
```
### 2. 历史记录管理
```tsx
// 加载历史记录
dispatch(loadRecords())
// 添加新记录
dispatch(addRecord({
type: 'image-to-video',
templateId: '2',
templateName: '视频生成',
inputImage: 'path/to/image.jpg',
status: 'generating'
}))
// 更新记录
dispatch(updateRecord('record_id', {
status: 'completed',
outputResult: 'path/to/result.mp4'
}))
// 删除记录
dispatch(deleteRecord('record_id'))
// 清空所有记录
dispatch(clearRecords())
```
### 3. 模板管理
```tsx
// 初始化模板
import { TEMPLATE_CONFIG } from '../../config/templates'
dispatch(initTemplates(TEMPLATE_CONFIG))
// 设置选中的模板
dispatch(setSelectedTemplate(template))
// 获取模板列表
const templates = useAppSelector(selectTemplates)
// 获取选中的模板
const selectedTemplate = useAppSelector(selectSelectedTemplate)
// 根据类型筛选模板
const videoTemplates = useAppSelector(state =>
selectTemplatesByType(state, 'image-to-video')
)
```
### 4. 选择器使用
```tsx
// 基本选择器
const records = useAppSelector(selectHistoryRecords)
const loading = useAppSelector(selectHistoryLoading)
const error = useAppSelector(selectHistoryError)
// 带参数的选择器
const record = useAppSelector(state => selectRecordById(state, 'record_id'))
const generatingRecords = useAppSelector(state =>
selectRecordsByStatus(state, 'generating')
)
```
## 迁移说明
### 从 Zustand 迁移到 Redux
原来的 Zustand 用法:
```tsx
// 旧的 Zustand 用法
const { records, loadRecords, addRecord } = useHistoryStore()
```
新的 Redux 用法:
```tsx
// 新的 Redux 用法
const dispatch = useAppDispatch()
const records = useAppSelector(selectHistoryRecords)
// 调用 actions
dispatch(loadRecords())
dispatch(addRecord(recordData))
```
## 注意事项
1. 所有异步操作都通过 Redux Thunk 处理
2. 使用类型化的 hooks (`useAppDispatch`, `useAppSelector`)
3. 选择器函数提供了类型安全的状态访问
4. 所有状态更新都是不可变的
5. 错误处理通过 Redux 状态管理

View File

@@ -1,137 +0,0 @@
import { create } from 'zustand'
import Taro from '@tarojs/taro'
import { HistoryRecord } from './types'
interface HistoryStore {
records: HistoryRecord[]
loading: boolean
error: string | null
// Actions
loadRecords: () => Promise<void>
addRecord: (record: Omit<HistoryRecord, 'id' | 'createTime' | 'updateTime'>) => Promise<string>
updateRecord: (id: string, updates: Partial<HistoryRecord>) => Promise<void>
deleteRecord: (id: string) => Promise<void>
clearRecords: () => Promise<void>
getRecordById: (id: string) => HistoryRecord | undefined
getRecordsByStatus: (status: HistoryRecord['status']) => HistoryRecord[]
}
const STORAGE_KEY = 'historyRecords'
export const useHistoryStore = create<HistoryStore>((set, get) => ({
records: [],
loading: false,
error: null,
// 加载历史记录
loadRecords: async () => {
set({ loading: true, error: null })
try {
const savedRecords = Taro.getStorageSync(STORAGE_KEY) || []
set({ records: savedRecords, loading: false })
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '加载历史记录失败'
set({ error: errorMsg, loading: false })
console.error('加载历史记录失败:', error)
}
},
// 添加新记录
addRecord: async (recordData) => {
const id = `record_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
const now = new Date().toISOString()
const newRecord: HistoryRecord = {
...recordData,
id,
createTime: now,
updateTime: now
}
try {
const currentRecords = get().records
const updatedRecords = [newRecord, ...currentRecords]
await Taro.setStorage({
key: STORAGE_KEY,
data: updatedRecords
})
set({ records: updatedRecords })
return id
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '添加记录失败'
set({ error: errorMsg })
console.error('添加记录失败:', error)
throw error
}
},
// 更新记录
updateRecord: async (id, updates) => {
try {
const currentRecords = get().records
const updatedRecords = currentRecords.map(record =>
record.id === id
? { ...record, ...updates, updateTime: new Date().toISOString() }
: record
)
await Taro.setStorage({
key: STORAGE_KEY,
data: updatedRecords
})
set({ records: updatedRecords })
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '更新记录失败'
set({ error: errorMsg })
console.error('更新记录失败:', error)
throw error
}
},
// 删除记录
deleteRecord: async (id) => {
try {
const currentRecords = get().records
const updatedRecords = currentRecords.filter(record => record.id !== id)
await Taro.setStorage({
key: STORAGE_KEY,
data: updatedRecords
})
set({ records: updatedRecords })
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '删除记录失败'
set({ error: errorMsg })
console.error('删除记录失败:', error)
throw error
}
},
// 清空所有记录
clearRecords: async () => {
try {
await Taro.removeStorage({ key: STORAGE_KEY })
set({ records: [] })
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '清空记录失败'
set({ error: errorMsg })
console.error('清空记录失败:', error)
throw error
}
},
// 获取单个记录
getRecordById: (id) => {
return get().records.find(record => record.id === id)
},
// 按状态筛选记录
getRecordsByStatus: (status) => {
return get().records.filter(record => record.status === status)
}
}))

View File

@@ -1,4 +1,25 @@
// 统一导出所有store
import { configureStore } from '@reduxjs/toolkit'
import historyReducer from '../reducers/history'
import templateReducer from '../reducers/template'
export default function configStore() {
const store = configureStore({
reducer: {
history: historyReducer,
template: templateReducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST']
}
})
})
return store
}
export type RootState = ReturnType<ReturnType<typeof configStore>['getState']>
export type AppDispatch = ReturnType<typeof configStore>['dispatch']
// 导出类型
export * from './types'
export * from './historyStore'
export * from './templateStore'

View File

@@ -1,39 +0,0 @@
import { create } from 'zustand'
import { Template } from './types'
import { TEMPLATE_CONFIG } from '../config/templates'
interface TemplateStore {
templates: Template[]
selectedTemplate: Template | null
// Actions
initTemplates: () => void
getTemplateById: (id: string) => Template | undefined
setSelectedTemplate: (template: Template | null) => void
getTemplatesByType: (type: 'image-to-image' | 'image-to-video') => Template[]
}
export const useTemplateStore = create<TemplateStore>((set, get) => ({
templates: [],
selectedTemplate: null,
// 初始化模板数据
initTemplates: () => {
set({ templates: TEMPLATE_CONFIG })
},
// 根据ID获取模板
getTemplateById: (id) => {
return get().templates.find(template => template.id === id)
},
// 设置选中的模板
setSelectedTemplate: (template) => {
set({ selectedTemplate: template })
},
// 根据类型筛选模板
getTemplatesByType: (type) => {
return get().templates.filter(template => template.type === type)
}
}))

15
tailwind.config.js Normal file
View File

@@ -0,0 +1,15 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// 这里给出了一份 taro 通用示例,具体要根据你自己项目的目录结构进行配置
// 比如你使用 vue3 项目,你就需要把 vue 这个格式也包括进来
// 不在 content glob 表达式中包括的文件,在里面编写 tailwindcss class是不会生成对应的 css 工具类的
content: ['./public/index.html', './src/**/*.{html,js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
corePlugins: {
// 小程序不需要 preflight因为这主要是给 h5 的,如果你要同时开发多端,你应该使用 process.env.TARO_ENV 环境变量来控制它
preflight: false,
},
}