241.Redux架构

打印 上一主题 下一主题

主题 1685|帖子 1685|积分 5055

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x

1. Redux 架构简介

Redux 最初起源于 JavaScript 世界,用于解决前端应用状态管理问题,其焦点头脑是将整个应用的状态存储在一个全局的、只读的“Store”中,通太过发(dispatch)动作(Action)来更新状态,最终通过纯函数(Reducer)计算得出新的状态。Redux 夸大“单一数据源”、“状态不可变”和“利用纯函数更新状态”这三个根本原则,因而具有可猜测、可测试、可调试等显著特点。
在 Android 开发中,尤其利用 Kotlin 语言,我们也可以应用 Redux 架构的头脑。由于 Android 应用通常涉及多组件复杂交互,状态管理和数据流问题不容忽视——此时引入 Redux 风格架构不失为一种有用的方案。常见的 Redux 实现库包罗 ReKotlin(雷同于 ReSwift)以及基于 Kotlin 协程和 Flow 的定制实现。本文将结合纯 Kotlin 的方式,讲解怎样构建一个基于 Redux 架构的 Android 应用。

2. Redux 焦点概念

在 Redux 架构中,重要包罗以下几个焦点组件:

  • Store(存储器)
    Store 是应用状态的容器,它维护了当前所有状态的快照。Store 内部只能通太过发一个 Action(动作)来改变状态,且这种改变必须是不可变的(immutable)。
  • Action(动作)
    Action 是描述状态变更的对象。它通常包罗一个类型(type)和可选的 payload,用来通报所需的数据。Action 必须是纯描述,不应包罗业务逻辑。
  • Reducer(纯函数)
    Reducer 是一个纯函数,吸收当前状态和分发进入的 Action,然后根据 Action 的类型,返回一个新的状态对象。每个 reducer 只负责处置惩罚应用状态的一个切片。
  • Middleware(中央件)
    Middleware 用于拦截和扩展 Action 的分发过程。通常用于处置惩罚异步操作、日记记录、错误捕获等。中央件答应你在 Action 到达 Reducer 前后插入自定义逻辑。
  • View(视图层)
    在 Android 中,View(通常是 Activity 或 Fragment)负责响应用户交互,并订阅 Store 厘革来调解界面。采用 Redux 架构时,界面层通常是无状态的,只依赖 Store 中数据。

3. Redux 在 Android 开发中的优势

将 Redux 头脑引入 Android 应用开发有以下优点:


  • 状态会合管理
    将全局状态同一存储在一个 Store 中,使得数据流向更加清晰,便于调试和测试。
  • 可猜测的状态改变
    由于状态改变只能通过纯函数(Reducer)举行计算,不会有副作用,所以状态变化具有可重复性和可猜测性。
  • 进步代码可测试性
    纯函数 Reducer 和明确的 Action 定义,便于对应用状态管理逻辑举行单位测试。
  • 易于维护与扩展
    除了分散在各个组件中的状态逻辑完全上移到 Store 和 Reducer 层中,代码变更时关注面较小。
  • 支持日记和调试
    借助 Redux DevTools 等工具,可以记录每一次 Action 和随后的 State 快照,使得调试和回滚更容易。
4. Kotlin 实现 Redux 的根本结构

在 Kotlin 中实现 Redux 架构,通常必要计划以下几个部分:
4.1 定义 State

State 将存储应用中所有必要管理的数据。一般会利用数据类来表示状态,而且要求状态不可变(利用 val 声明,大概借助 copy 方法举行局部更新)。
下面是一个简朴的示例,假设我们正在开发一个 Todo 应用,则全局状态可以包罗一个 Todo 列表和输入框内容:
  1. data class Todo(val id: Int, val text: String, val completed: Boolean)
  2. data class AppState(
  3.     val todos: List<Todo> = emptyList(),
  4.     val currentInput: String = ""
  5. )
复制代码
4.2 定义 Action

Action 用于描述用户交互大概体系事件,在 Redux 中一般定义为 sealed class 大概枚举。这里我们利用 sealed class 来列举所有行为,比方增加 Todo、删除 Todo、修改输入框内容以及切换 Todo 完成状态:
  1. sealed class Action {
  2.     data class AddTodo(val todo: Todo) : Action()
  3.     data class RemoveTodo(val todoId: Int) : Action()
  4.     data class UpdateInput(val input: String) : Action()
  5.     data class ToggleTodo(val todoId: Int) : Action()
  6. }
  7. /*
  8. sealed class 是一种特殊的类,它用于表示受限的类继承结构。 当你在 Redux 中使用 sealed class 时,通常是为了定义 Action 的类型。
  9. sealed class 的概念:
  10. 受限的继承: sealed class 允许你定义一个类的继承结构,但限制了子类的数量和位置
  11. 所有子类必须在 sealed class 的声明中定义,或者在同一个文件中定义。
  12. 类型安全: sealed class 提供了类型安全,因为编译器知道所有可能的子类
  13. 这使得在使用 when 表达式时,编译器可以检查是否处理了所有可能的子类,从而避免了遗漏情况。
  14. 枚举类的增强版: 可以把 sealed class 看作是枚举类 (enum class) 的增强版
  15. 枚举类只能定义有限个实例,而 sealed class 可以定义有限个类。
  16. */
复制代码
4.3 定义 Reducer

Reducer 是一个严酷按照 Action 修改 State 的纯函数。注意:Reducer 不应包罗异步操作和副作用,所有副作用要放到 Middleware 中处置惩罚。下面是一个简朴的 Reducer 示例:
  1. import java.util.UUID
  2. // 定义一个数据类 Todo,表示一个待办事项
  3. data class Todo(
  4.     val id: String = UUID.randomUUID().toString(), // 待办事项的唯一 ID,默认为 UUID
  5.     val text: String, // 待办事项的内容
  6.     val completed: Boolean = false // 待办事项是否已完成,默认为 false
  7. )
  8. // 定义一个数据类 AppState,表示应用程序的状态
  9. data class AppState(
  10.     val todos: List<Todo> = emptyList(), // 待办事项列表,默认为空列表
  11.     val currentInput: String = "" // 当前输入框中的内容,默认为空字符串
  12. )
  13. // 定义一个密封类 Action,表示所有可能的 Action 类型
  14. sealed class Action {
  15.     // 添加待办事项的 Action
  16.     data class AddTodo(val todo: Todo) : Action()
  17.     // 移除待办事项的 Action
  18.     data class RemoveTodo(val todoId: String) : Action()
  19.     // 更新输入框内容的 Action
  20.     data class UpdateInput(val input: String) : Action()
  21.     // 切换待办事项完成状态的 Action
  22.     data class ToggleTodo(val todoId: String) : Action()
  23. }
  24. /**
  25. * 应用的 Reducer 函数,用于根据 Action 更新应用的状态
  26. *
  27. * @param state 先前的应用状态
  28. * @param action 发生的 Action
  29. * @return 新的应用状态
  30. */
  31. fun appReducer(state: AppState, action: Action): AppState {
  32.     return when (action) {
  33.         // 添加待办事项
  34.         is Action.AddTodo -> {
  35.             // 创建一个新的待办事项列表,将新添加的待办事项添加到列表末尾
  36.             val newTodos = state.todos + action.todo
  37.             // 返回一个新的 AppState 对象,其中 todos 属性更新为新的待办事项列表,currentInput 属性清空
  38.             state.copy(todos = newTodos, currentInput = "")
  39.         }
  40.         // 移除待办事项
  41.         is Action.RemoveTodo -> {
  42.             // 创建一个新的待办事项列表,过滤掉要移除的待办事项
  43.             val newTodos = state.todos.filter { it.id != action.todoId }
  44.             // 返回一个新的 AppState 对象,其中 todos 属性更新为新的待办事项列表
  45.             state.copy(todos = newTodos)
  46.         }
  47.         // 更新输入框内容
  48.         is Action.UpdateInput -> {
  49.             // 返回一个新的 AppState 对象,其中 currentInput 属性更新为新的输入内容
  50.             state.copy(currentInput = action.input)
  51.         }
  52.         // 切换待办事项完成状态
  53.         /*
  54.         map 是一个集合转换方法,在 Kotlin (以及许多其他编程语言) 中
  55.         用于将一个集合(例如 List、Set 等)中的每个元素转换为另一个元素,并返回一个包含转换后元素的新集合。
  56.          */
  57.         is Action.ToggleTodo -> {
  58.             // 创建一个新的待办事项列表,遍历列表中的每个待办事项
  59.             val newTodos = state.todos.map {
  60.                 // 如果当前待办事项的 ID 与要切换的待办事项的 ID 相同,则创建一个新的待办事项对象,切换其完成状态
  61.                 if (it.id == action.todoId) it.copy(completed = !it.completed)
  62.                 // 否则,保持原样
  63.                 else it
  64.             }
  65.             // 返回一个新的 AppState 对象,其中 todos 属性更新为新的待办事项列表
  66.             state.copy(todos = newTodos)
  67.         }
  68.     }
  69. }
复制代码
通过这个 Reducer,每个 Action 都能返回对应的全新 State。由于 State 是不可变对象,因此可以包管每次状态改变都是明确可追踪的。
4.4 实现 Store

Store 负责维护 AppState,同一时刻只存在一份状态。Store 必要提供以下能力:


  • 生存当前 state
  • 提供 dispatch 方法,吸收 Action、调用 Reducer 更新 state
  • 答应组件订阅 state 改变事件
  • 支持 Middleware 机制(可选)
下面给出一个简朴的 Store 实现示例,基于 Kotlin 的高阶函数和观察者模式:
  1. /**
  2. * Redux Store 类,用于管理应用的状态
  3. *
  4. * @param state 初始应用状态
  5. * @param reducer Reducer 函数,用于根据 Action 更新状态
  6. * @param middleware 中间件列表,用于拦截和处理 Action
  7. */
  8. class Store(
  9.     private var state: AppState, // 存储当前应用状态,使用 var 允许状态更新
  10.     private val reducer: (AppState, Action) -> AppState, // Reducer 函数,接收 state 和 action,返回新的 state
  11.     private val middleware: List<(Store, Action, (Action) -> Unit) -> Unit> = emptyList() // 中间件列表,用于处理 action,默认为空列表
  12. ) {
  13.     private val subscribers = mutableListOf<(AppState) -> Unit>() // 存储订阅者(监听器)的列表,当状态变化时通知他们
  14.     /**
  15.      * 获取当前状态
  16.      *
  17.      * @return 当前应用状态
  18.      */
  19.     fun getState(): AppState = state // 返回当前存储的应用状态
  20.     /**
  21.      * 订阅状态变化
  22.      *
  23.      * @param listener 状态变化监听器,接收新的 AppState 作为参数
  24.      */
  25.     fun subscribe(listener: (AppState) -> Unit) {
  26.         subscribers.add(listener) // 将监听器添加到订阅者列表中
  27.     }
  28.     /**
  29.      * 分发 Action,是修改状态的入口
  30.      *
  31.      * @param action 要分发的 Action
  32.      */
  33.     fun dispatch(action: Action) {
  34.         // 中间件链处理
  35.         if (middleware.isNotEmpty()) {
  36.             // 如果存在中间件,则执行中间件链
  37.             executeMiddleware(0, action) // 从第一个中间件开始执行
  38.         } else {
  39.             // 如果没有中间件,则直接调用内部 dispatch 方法更新状态
  40.             internalDispatch(action)
  41.         }
  42.     }
  43.     /**
  44.      * 内部的分发 Action 方法,调用 Reducer 更新状态
  45.      *
  46.      * @param action 要分发的 Action
  47.      */
  48.     private fun internalDispatch(action: Action) {
  49.         state = reducer(state, action) // 调用 Reducer 函数,根据 Action 更新状态
  50.         notifySubscribers() // 通知所有订阅者状态已更新
  51.     }
  52.     /**
  53.      * 递归执行中间件链
  54.      *
  55.      * @param index 当前中间件的索引
  56.      * @param action 要分发的 Action
  57.      */
  58.     private fun executeMiddleware(index: Int, action: Action) {
  59.         if (index < middleware.size) {
  60.             // 如果当前索引小于中间件列表的大小,则执行当前中间件
  61.             val currentMiddleware = middleware[index] // 获取当前中间件
  62.             currentMiddleware(this, action) { nextAction ->
  63.                 // 执行中间件,并将 Store、Action 和一个 next 函数传递给它
  64.                 // next 函数用于将 Action 传递给下一个中间件或最终的 internalDispatch 方法
  65.                 executeMiddleware(index + 1, nextAction) // 递归调用 executeMiddleware,执行下一个中间件
  66.             }
  67.         } else {
  68.             // 如果所有中间件都已执行完毕,则调用内部 dispatch 方法更新状态
  69.             internalDispatch(action)
  70.         }
  71.     }
  72.     /**
  73.      * 通知所有订阅者状态已更新
  74.      */
  75.     private fun notifySubscribers() {
  76.         subscribers.forEach { it(state) } // 遍历订阅者列表,并调用每个订阅者的监听器函数,将新的状态传递给它
  77.     }
  78. }
复制代码
在这个 Store 实现中,我们可以看到:


  • 通过 dispatch 方法,Action 先经过所有 middleware 处置惩罚后,再通报给 Reducer。
  • 状态厘革后,通过调用 notifySubscribers 告诉所有订阅组件更新 UI。
  • 实现中保持了状态不可变的特性。
4.5 实现 Middleware

Middleware 用于扩展 Redux 的功能,比如日记记录、异步处置惩罚等。下面举例阐明怎样编写一个简朴的日记记录中央件:
  1. val loggerMiddleware: (Store, Action, (Action) -> Unit) -> Unit = { store, action, next ->
  2.     println("Dispatching action: $action")
  3.     next(action)
  4.     println("New state: ${store.getState()}")
  5. }
复制代码
当在 Store 内部利用中央件时,可以将多个中央件依次注册,如下面的示例:
  1. // 创建 Redux Store 实例
  2. val store = Store(
  3.     state = AppState(), // 初始应用状态,使用 AppState() 创建一个默认的 AppState 对象
  4.     reducer = ::appReducer, // Reducer 函数,使用 ::appReducer 引用 appReducer 函数
  5.     middleware = listOf(loggerMiddleware) // 中间件列表,包含 loggerMiddleware 中间件
  6. )
复制代码

这样,每一次 dispatch 操作都会先经过 loggerMiddleware 输出日记,然后再计算新的状态。

5. 将 Redux 架构与 Android UI 结合

在 Android 应用中,Activity 或 Fragment 通常充当 View 层,它们负责展示状态和捕获用户交互。结合 Redux 架构的关键在于:


  • Activity/Fragment 订阅 Store 状态厘革,以便当状态更新时刷新 UI。
  • 用户在界面上举行操作后,调用 Store.dispatch() 发送 Action,触发状态更新。
下面提供一个简朴的示例,展示怎样在 Activity 中结合 Store 更新 UI:
  1. import android.os.Bundle
  2. import androidx.appcompat.app.AppCompatActivity
  3. import kotlinx.android.synthetic.main.activity_main.*
  4. class MainActivity : AppCompatActivity() {
  5.     // 创建全局 Store 对象
  6.     private val store = Store(
  7.         state = AppState(),
  8.         reducer = ::appReducer,
  9.         middleware = listOf(loggerMiddleware)
  10.     )
  11.     override fun onCreate(savedInstanceState: Bundle?) {
  12.         super.onCreate(savedInstanceState)
  13.         // 假设 activity_main.xml 包含一个 EditText 和一个 RecyclerView 用于展示 Todo 列表
  14.         setContentView(R.layout.activity_main)
  15.         // 订阅 Store 状态变化,更新 UI
  16.         store.subscribe { state ->
  17.             // 更新输入框文本(例如状态 currentInput)
  18.             editTextInput.setText(state.currentInput)
  19.             // 更新 RecyclerView,显示 todos 列表(这部分需配合 Adapter 实现)
  20.             // adapter.submitList(state.todos)
  21.         }
  22.         // 用户输入时发送更新输入内容的 Action
  23.         editTextInput.addTextChangedListener { text ->
  24.             store.dispatch(Action.UpdateInput(text.toString()))
  25.         }
  26.         // 示例:点击按钮添加 Todo 项
  27.         buttonAddTodo.setOnClickListener {
  28.             val currentInput = store.getState().currentInput
  29.             if (currentInput.isNotBlank()) {
  30.                 // 使用当前时间戳作为 id 简单示例
  31.                 val newTodo = Todo(id = currentInput.hashCode(), text = currentInput, completed = false)
  32.                 store.dispatch(Action.AddTodo(newTodo))
  33.             }
  34.         }
  35.     }
  36. }
复制代码
在上面的示例中,你可以看到 UI 层通过监听 store.subscribe() 得到状态厘革,然后更新输入框、列表等视图;同时,用户在界面操作(比方编辑输入框、点击添加按钮)时,会发送对应的 Action 到 Store,从而触发状态更新。
6. Redux 异步操作与 Side Effects

在实际应用中,大部分操作可能涉及网络请求、数据库访问等异步操作。如果每个异步操作都直接放在 UI 层实现,容易导致代码杂乱。Redux 通过 Middleware 拦截 Action,将异步操作作为副作用处置惩罚,从而保持 Reducer 的纯粹性。
6.1 异步 Middleware 示例

假如我们必要在添加 Todo 项时,从远程服务器获取一些数据(比方数据校验或唯一 ID),这时可以编写一个异步中央件。下面给出一个模拟异步操作的例子:
  1. import kotlinx.coroutines.*
  2. val asyncMiddleware: (Store, Action, (Action) -> Unit) -> Unit = { store, action, next ->
  3.     when (action) {
  4.         is Action.AddTodo -> {
  5.             // 模拟网络请求或异步操作
  6.             GlobalScope.launch {
  7.                 delay(1000) // 模拟网络延迟
  8.                 // 异步操作结束后,继续分发 AddTodo Action
  9.                 next(action)
  10.             }
  11.         }
  12.         else -> next(action)
  13.     }
  14. }
复制代码
在这个中央件中,当吸收到 AddTodo Action 后,先启动协程举行模拟异步操作(耽误 1 秒),然后再调用 next(action) 将 Action 交给下一个中央件大概直接通报给 Reducer。注意:实际项目中应避免直接利用 GlobalScope,而是利用专门的 CoroutineScope。
6.2 注册多个 Middleware

在实际应用中,我们可以同时注册日记中央件、异步中央件等多个中央件。Store 将按照设置的顺序递归处置惩罚这些中央件,比方:
  1. val store = Store(
  2.     state = AppState(),
  3.     reducer = ::appReducer,
  4.     middleware = listOf(loggerMiddleware, asyncMiddleware)
  5. )
复制代码
这样,用户界面上每一次对状态的修改请求都将依次流过 loggerMiddleware 记录日记,然后经过 asyncMiddleware 处置惩罚异步操作,末了由 Reducer 计算出新的状态。

7. 实战示例与扩展

7.1 架构扩展

随着应用业务逻辑越来越复杂,Redux 架构也必要做一些扩展,比如:


  • 拆分 State 与 Reducer:
    将全局状态分拆为多个子状态,每个子状态由单独的 reducer 处置惩罚,再通过 combineReducers 归并成全局 Reducer。这样使各个模块解耦,便于维护。
  • 增强 Middleware:
    可以编写中央件处置惩罚错误、重试机制等。比方,当某个网络请求失败时,可以捕获失败 Action,并触发错误报告或重试逻辑。
  • 状态持久化:
    由于 Redux 状态是内存中的一份快照,可以通过序列化状态到本地存储(如 SharedPreferences 或数据库),实现应用重启后的状态恢复。
7.2 利用 Kotlin 协程与 Flow

随着 Kotlin Coroutines 和 Flow 的遍及,很多开发者开始用其来实现响应式的数据流。在 Redux 架构中,可以结合 Flow 来定义状态厘革流,从而使 UI 组件以流式数据订阅方式主动更新。比方:
  1. import kotlinx.coroutines.flow.MutableStateFlow
  2. import kotlinx.coroutines.flow.asStateFlow
  3. class FlowStore(
  4.     initialState: AppState,
  5.     private val reducer: (AppState, Action) -> AppState
  6. ) {
  7.     private val _stateFlow = MutableStateFlow(initialState)
  8.     val stateFlow = _stateFlow.asStateFlow()
  9.     fun dispatch(action: Action) {
  10.         // 简单同步分发,没有中间件,直接生成新状态
  11.         val newState = reducer(_stateFlow.value, action)
  12.         _stateFlow.value = newState
  13.     }
  14. }
复制代码
在 UI 层,你可以利用 Flow 订阅状态更新,利用 Jetpack 的 lifecycleScope 或 LiveData 转换实现数据绑定:
  1. import androidx.appcompat.app.AppCompatActivity
  2. import androidx.lifecycle.lifecycleScope
  3. import kotlinx.coroutines.flow.collect
  4. import kotlinx.coroutines.launch
  5. class MainActivityFlow : AppCompatActivity() {
  6.     private val store = FlowStore(initialState = AppState(), reducer = ::appReducer)
  7.     override fun onCreate(savedInstanceState: Bundle?) {
  8.         super.onCreate(savedInstanceState)
  9.         setContentView(R.layout.activity_main)
  10.         lifecycleScope.launch {
  11.             store.stateFlow.collect { state ->
  12.                 // 根据新状态更新 UI,比如刷新列表、显示当前输入框内容等
  13.                 println("New state received: $state")
  14.             }
  15.         }
  16.         // 例如,点击按钮时分发 Action
  17.         buttonAddTodo.setOnClickListener {
  18.             val currentInput = store.stateFlow.value.currentInput
  19.             if (currentInput.isNotBlank()) {
  20.                 val newTodo = Todo(id = currentInput.hashCode(), text = currentInput, completed = false)
  21.                 store.dispatch(Action.AddTodo(newTodo))
  22.             }
  23.         }
  24.     }
  25. }
复制代码
利用 Flow 的优势在于:


  • 代码更加简洁,符合响应式编程头脑;
  • 与 Android 生命周期友爱,主动在合适时机制止网络数据;
  • 极大降低手动管理订阅和通知机制的复杂度。

8. Redux 架构的优缺点

优点



  • 单一状态源: 更容易调试和实现时间旅行调试。
  • 状态不可变: 避免多线程状态写辩论,便于并发处置惩罚与测试。
  • 逻辑清晰: 将业务逻辑放在 Reducer 中,通过 Action 描述所有厘革,便于维护。
  • 中央件扩展性强: 方便添加日记、错误处置惩罚、异步处置惩罚等。
缺点



  • 模板代码较多: 对于简朴应用来说,Redux 的架构可能显得臃肿,增加了学习曲线。
  • 不善于局部状态: 对于只涉及局部 UI 状态(如控件焦点、动画状态等)的管理,Redux 可能显得过于笨重。
  • 调试异步逻辑需额外计划: 异步操作与副作用虽然可通过 Middleware 管理,但复杂场景下仍需谨慎计划和调试。

9. 实际项目中的应用与最佳实践

在实际 Android 项目中,通常会有这样一些需求:


  • 多个界面共享用户信息、购物车数据、设置设定等全局状态。
  • 步伐必要处置惩罚网络请求、用户输入、定时使命等各种异步事件。
  • 必要方便对应用状态举行测试、回溯和调试。
针对这些需求,应用 Redux 架构可以带来以下好处:


  • 状态会合而同一: 所有全局状态存储在 Store 中,而且只能通太过发 Action 修改,便于跟踪问题。
  • 进步单位测试覆盖率: Reducer 是纯函数,可以用单位测试验证各种 Action 的处置惩罚结果。
  • 易于开发调试工具: 比方可以将所有 Action 和状态变更记录下来,做时间旅行调试。
最佳实践建议:

  • 拆分 State 和 Roles: 计划得当的数据结构,将全局状态拆分为多个模块,每个模块拥有独立的 Reducer,末了在 Store 中整合
  • 明确 Action 定义: 定义清晰、可辨识的 Action 类型,避免多种行为混杂在一个 Action 中
  • 利用协程管理异步: 利用 Kotlin 协程和 Flow 结合中央件,实现异步操作和 API 请求,有用隔离副作用
  • 编写单位测试: 对 Reducer、Middleware 举行充实测试,保障业务逻辑的稳固性
  • 关注性能: 尽量淘汰不必要的状态更新和 UI 重绘,必要时利用 Diff 算法来优化 RecyclerView 或其他列表组件

10. 小结

通过本文的详细讲解,我们相识了 Redux 架构的根本概念及其在 Android 中的实现方法。简而言之,Redux 架构包罗 Store、Action、Reducer、Middleware 和 View 五大组件,借助这种架构,可以将应用的全局状态会合管理,并通过纯函数描述状态厘革,使应用更易于调试、测试与维护。同时,结合 Kotlin 协程与 Flow,可进一步进步开发效率与代码简洁度。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万万哇

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表