#!/bin/bash # 遇到错误时退出 set -e # 显示帮助信息 show_help() { echo "使用方法: ./create-roles.sh [选项]" echo "选项:" echo " -h, --help 显示帮助信息" echo echo "此脚本在 src/views 目录下创建角色管理相关的 Vue3 TSX 路由视图结构" echo "将生成以下结构:" echo "src/views/<角色名称>" echo "├── index.tsx # 入口文件" echo "├── useController.ts # 控制器" echo "├── useStore.ts # 状态管理" echo "├── index.module.css # 样式文件" echo "├── types.d.ts # 类型定义" echo "├── children/ # 子路由" echo "│ └── permissions # 权限管理子路由" echo "│ ├── index.tsx # 视图" echo "│ ├── index.module.css # 样式" echo "│ ├── useController.ts # 控制器" echo "│ ├── useStore.ts # 状态管理" echo "│ └── types.d.ts # 类型定义" echo "└── components/ # 组件" echo " └── role-form # 角色表单组件" echo " ├── index.tsx # 视图" echo " ├── index.module.css # 样式" echo " ├── useController.ts # 控制器" echo " ├── useStore.ts # 状态管理" echo " └── types.d.ts # 类型定义" echo echo "同时会创建:" echo "src/api/<角色名称>.ts # API 文件" echo "src/types/<角色名称>.d.ts # 类型定义文件" } # 解析命令行参数 while [[ $# -gt 0 ]]; do case $1 in -h|--help) show_help exit 0 ;; *) echo "错误: 未知参数 $1" show_help exit 1 ;; esac done # 交互式选择函数 select_option() { local prompt="$1" local options=("是" "否") local selected echo "$prompt" select choice in "${options[@]}"; do case $REPLY in 1|2) selected=$choice break ;; *) echo "请选择有效的选项 [1-2]" ;; esac done [[ "$selected" == "是" ]] && return 0 || return 1 } # 交互式输入路由名称 read -p "请输入路由名称 (routerName): " ROUTER_NAME if [ -z "$ROUTER_NAME" ]; then echo "错误: 路由名称不能为空" exit 1 fi # 询问是否创建子路由 if select_option "是否创建子路由?"; then read -p "请输入子路由名称 [默认: list]: " CHILD_ROUTER_NAME CHILD_ROUTER_NAME=${CHILD_ROUTER_NAME:-"list"} echo "将创建子路由: $CHILD_ROUTER_NAME" else CHILD_ROUTER_NAME="" echo "不创建子路由" fi # 询问是否创建组件 if select_option "是否创建组件?"; then read -p "请输入组件名称 [默认: todo-form]: " COMPONENT_NAME COMPONENT_NAME=${COMPONENT_NAME:-"todo-form"} echo "将创建组件: $COMPONENT_NAME" else COMPONENT_NAME="" echo "不创建组件" fi # 获取脚本所在目录的绝对路径 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # 获取项目根目录 PROJECT_ROOT="$SCRIPT_DIR/../" # 创建目录函数 create_dir() { local dir="$1" mkdir -p "$PROJECT_ROOT/$dir" } # 确保必需的目录存在 create_dir "src/views" create_dir "src/api" create_dir "src/types" # 创建主目录结构 create_dir "src/views/$ROUTER_NAME/children/$CHILD_ROUTER_NAME" create_dir "src/views/$ROUTER_NAME/components/$COMPONENT_NAME" # 创建 API 文件 cat > "$PROJECT_ROOT/src/api/${ROUTER_NAME}.ts" << '' # 创建类型定义文件 cat > "$PROJECT_ROOT/src/types/${ROUTER_NAME}.d.ts" << '' # 创建主路由类型文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/types.d.ts" << EOL export interface Todo { id: string title: string completed: boolean createdAt: string } export interface TodoState { todos: Todo[] loading: boolean error: string | null } EOL # 创建主路由状态管理文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/useStore.ts" << EOL import { defineStore } from '@baota/pinia' import { ref } from 'vue' import type { Todo, TodoState } from './types' const store = defineStore('todo-store', () => { const todos = ref([]) const loading = ref(false) const error = ref(null) const addTodo = (title: string) => { const newTodo: Todo = { id: Date.now().toString(), title, completed: false, createdAt: new Date().toISOString() } todos.value.push(newTodo) } const toggleTodo = (id: string) => { const todo = todos.value.find(t => t.id === id) if (todo) { todo.completed = !todo.completed } } const removeTodo = (id: string) => { todos.value = todos.value.filter(t => t.id !== id) } return { todos, loading, error, addTodo, toggleTodo, removeTodo } }) export const useStore = () => store() EOL # 创建主路由控制器文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/useController.ts" << EOL import { onMounted } from 'vue' import { storeToRefs } from '@baota/pinia' import { useStore } from './useStore' export const useController = () => { const store = useStore() const { todos, loading, error } = storeToRefs(store) const handleAddTodo = (title: string) => { if (title.trim()) { store.addTodo(title.trim()) } } const handleToggleTodo = (id: string) => { store.toggleTodo(id) } const handleRemoveTodo = (id: string) => { store.removeTodo(id) } onMounted(() => { // 可以在这里加载初始数据 console.log('Todo List Component Mounted') }) return { todos, loading, error, handleAddTodo, handleToggleTodo, handleRemoveTodo } } EOL # 创建主路由样式文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/index.module.css" << EOL .container { max-width: 600px; margin: 0 auto; padding: 24px; } .header { margin-bottom: 24px; text-align: center; } .title { font-size: 32px; color: #2c3e50; } .form { display: flex; gap: 8px; margin-bottom: 24px; } .input { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; } .button { padding: 8px 16px; background: #42b883; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } .button:hover { background: #3aa876; } .todoList { list-style: none; padding: 0; } .todoItem { display: flex; align-items: center; padding: 12px; background: white; border-radius: 4px; margin-bottom: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .todoCheckbox { margin-right: 12px; } .todoTitle { flex: 1; } .todoTitle.completed { text-decoration: line-through; color: #999; } .deleteButton { padding: 4px 8px; background: #ff4757; color: white; border: none; border-radius: 4px; cursor: pointer; } .deleteButton:hover { background: #ff3748; } EOL # 创建主路由入口文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/index.tsx" << EOL import { defineComponent, ref } from 'vue' import { useController } from './useController' import styles from './index.module.css' export default defineComponent({ name: 'TodoList', setup() { const { todos, handleAddTodo, handleToggleTodo, handleRemoveTodo } = useController() const newTodo = ref('') const onSubmit = (e: Event) => { e.preventDefault() handleAddTodo(newTodo.value) newTodo.value = '' } return () => (

Todo List

    {todos.value.map(todo => (
  • handleToggleTodo(todo.id)} /> {todo.title}
  • ))}
) } }) EOL # 在脚本开头添加大写转换函数 to_upper_first() { local str="$1" local first_char=$(echo "${str:0:1}" | tr '[:lower:]' '[:upper:]') echo "$first_char${str:1}" } # 存储转换后的变量 ROUTER_NAME_PASCAL=$(to_upper_first "$ROUTER_NAME") # 创建表单组件类型文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/components/$COMPONENT_NAME/types.d.ts" << EOL import type { ${ROUTER_NAME_PASCAL}Data } from '@/types/${ROUTER_NAME}' export interface FormProps { data?: ${ROUTER_NAME_PASCAL}Data | null } export interface FormEmits { (e: 'submit', data: ${ROUTER_NAME_PASCAL}Data): void (e: 'cancel'): void } EOL # 创建表单组件状态管理文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/components/$COMPONENT_NAME/useStore.ts" << EOL import { defineStore } from '@baota/pinia' import { ref } from 'vue' import type { ${ROUTER_NAME_PASCAL}Data } from '@/types/${ROUTER_NAME}' // 定义 store const store = defineStore('${ROUTER_NAME}-form-store', () => { const loading = ref(false) const formData = ref<${ROUTER_NAME_PASCAL}Data>({ id: '', name: '', code: '', description: '', permissions: [], createdAt: '', updatedAt: '' }) return { loading, formData } }) export const useStore = () => store() EOL # 创建表单组件控制器文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/components/$COMPONENT_NAME/useController.ts" << EOL import { onMounted } from 'vue' import { storeToRefs } from '@baota/pinia' import { useStore } from './useStore' import type { ${ROUTER_NAME_PASCAL}Data } from '@/types/${ROUTER_NAME}' export const useController = (initialData?: ${ROUTER_NAME_PASCAL}Data | null) => { const store = useStore() const storeRef = storeToRefs(store) onMounted(() => { if (initialData) { store.formData = { ...initialData } } }) return { ...storeRef } } EOL # 创建表单组件样式文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/components/$COMPONENT_NAME/index.module.css" << EOL .form { max-width: 600px; } .formTitle { font-size: 18px; font-weight: bold; margin-bottom: 24px; } .formItem { margin-bottom: 16px; } .label { display: block; margin-bottom: 8px; font-weight: 500; } .input { width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 4px; transition: all 0.3s; } .input:hover { border-color: #40a9ff; } .input:focus { border-color: #1890ff; outline: none; box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); } .textarea { composes: input; min-height: 100px; resize: vertical; } .actions { margin-top: 24px; display: flex; gap: 8px; justify-content: flex-end; } .button { padding: 8px 16px; border: 1px solid #d9d9d9; border-radius: 4px; cursor: pointer; background: #fff; transition: all 0.3s; } .button:hover { background: #f5f5f5; } .primaryButton { composes: button; background: #1890ff; color: #fff; border-color: #1890ff; } .primaryButton:hover { background: #40a9ff; } EOL # 创建表单组件入口文件 cat > "$PROJECT_ROOT/src/views/$ROUTER_NAME/components/$COMPONENT_NAME/index.tsx" << EOL import { defineComponent } from 'vue' import { useController } from './useController' import type { FormProps, FormEmits } from './types' import styles from './index.module.css' export default defineComponent({ name: 'RoleForm', props: { data: { type: Object as PropType, default: null } }, emits: ['submit', 'cancel'], setup(props, { emit }) { const { formData } = useController(props.data) const handleSubmit = (e: Event) => { e.preventDefault() emit('submit', formData.value) } return () => (

{props.data ? '编辑角色' : '创建角色'}