2025-12-29 First Commit
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
// 引入九宮格主選單,登入成功後跳轉
|
||||
import 'main.dart';
|
||||
|
||||
// API 相關常數
|
||||
const String BASE_IP = "https://api.gex.com.tw:8033";
|
||||
const String API_ENDPOINT = "xapi/v1/eis_demo/checklogin/2/";
|
||||
const String API_URL = "$BASE_IP/$API_ENDPOINT";
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
// 用於獲取輸入框內容
|
||||
final TextEditingController _userController = TextEditingController(text: 'admin'); // 預設帳號
|
||||
final TextEditingController _pwdController = TextEditingController(text: 'gex123'); // 預設密碼
|
||||
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
|
||||
// 輔助函數:將字串轉換為 MD5 雜湊值
|
||||
String _generateMd5(String input) {
|
||||
return md5.convert(utf8.encode(input)).toString();
|
||||
}
|
||||
|
||||
// 登入邏輯,模擬您的 JS logon() 函數
|
||||
Future<void> _logon() async {
|
||||
if (_userController.text.isEmpty || _pwdController.text.isEmpty) {
|
||||
setState(() {
|
||||
_errorMessage = '請輸入帳號和密碼';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
final String userId = _userController.text;
|
||||
final String password = _pwdController.text;
|
||||
// 對密碼進行 MD5 加密
|
||||
final String md5Password = _generateMd5(password);
|
||||
|
||||
// 建立 API 參數,使用您的邏輯
|
||||
final Map<String, String> params = {
|
||||
'token': 'xxx',
|
||||
'para01': userId,
|
||||
'para02': md5Password,
|
||||
'para03': 'web',
|
||||
};
|
||||
|
||||
try {
|
||||
// 執行 POST 請求。使用 application/x-www-form-urlencoded 格式
|
||||
final response = await http.post(
|
||||
Uri.parse(API_URL),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: params,
|
||||
);
|
||||
|
||||
// 解析 API 回應
|
||||
final Map<String, dynamic> responseData = json.decode(response.body);
|
||||
|
||||
// 根據您的 API 邏輯:res.data.code === 0 為成功
|
||||
if (responseData['code'] == 0) {
|
||||
// 登入成功:導航至 MainMenu 並替換登入頁面(防止按返回鍵回到登入)
|
||||
if (mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MainMenu()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 登入失敗:顯示錯誤訊息
|
||||
setState(() {
|
||||
_errorMessage = '登入失敗,請重新輸入正確的帳號及密碼!';
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 網路或伺服器錯誤
|
||||
setState(() {
|
||||
_errorMessage = '網路錯誤或伺服器無法連線。';
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('用戶登入')),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(32.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
const Icon(Icons.lock_open, size: 80, color: Colors.blue),
|
||||
const SizedBox(height: 48.0),
|
||||
|
||||
// 帳號輸入框
|
||||
TextField(
|
||||
controller: _userController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '用戶帳號',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.person),
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
|
||||
// 密碼輸入框
|
||||
TextField(
|
||||
controller: _pwdController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '密碼',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.lock),
|
||||
),
|
||||
onSubmitted: (_) => _logon(), // 允許按 Enter 鍵登入
|
||||
),
|
||||
const SizedBox(height: 24.0),
|
||||
|
||||
// 錯誤訊息顯示
|
||||
if (_errorMessage != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Text(
|
||||
_errorMessage!,
|
||||
style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
|
||||
// 登入按鈕
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _logon,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
),
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 3.0),
|
||||
)
|
||||
: const Text(
|
||||
'登入',
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import './news_manager.dart';
|
||||
import './placeholder_screens.dart';
|
||||
import './login_page.dart'; // 確保引入登入頁面
|
||||
/*
|
||||
您的 Dart HTTP 客戶端可能無法完成與伺服器的 SSL/TLS 握手。這需要將您的整個 App 結構調整為使用 io.HttpClient
|
||||
警告: 這是一個臨時且不安全的解決方案。 它會強制 Dart 客戶端信任所有憑證。只建議在無法控制伺服器憑證或開發環境中臨時使用。
|
||||
還需要找時間查一下錯誤原因 20251214 modify by Gemini 3.0
|
||||
*/
|
||||
import 'dart:io';
|
||||
|
||||
class MyHttpOverrides extends HttpOverrides {
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true; // 總是回傳 true (忽略 SSL 錯誤)
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
// 在 main 函數中加入這行
|
||||
HttpOverrides.global = MyHttpOverrides();
|
||||
runApp(const ZenApp());
|
||||
}
|
||||
|
||||
class ZenApp extends StatelessWidget {
|
||||
const ZenApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: '企業應用主選單',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
// 使用 Material 3 風格
|
||||
useMaterial3: true,
|
||||
),
|
||||
// 設置主選單畫面為首頁
|
||||
// home: const MainMenu(),
|
||||
home: const LoginPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// 主選單的資料結構 (Data Model)
|
||||
// ------------------------------------
|
||||
|
||||
class MenuItem {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final Widget targetScreen;
|
||||
|
||||
const MenuItem({
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.targetScreen,
|
||||
});
|
||||
}
|
||||
|
||||
// 所有選單項目的列表
|
||||
const List<MenuItem> menuItems = [
|
||||
// 1. 新聞資訊 (使用您的 NewsManager)
|
||||
MenuItem(
|
||||
title: '公告事項',
|
||||
icon: Icons.article,
|
||||
targetScreen: NewsManager(),
|
||||
),
|
||||
// 2. 員工打卡
|
||||
MenuItem(
|
||||
title: '員工打卡',
|
||||
icon: Icons.access_time_filled,
|
||||
targetScreen: PlaceholderScreen(title: '員工打卡'),
|
||||
),
|
||||
// 2. 員工打卡
|
||||
MenuItem(
|
||||
title: '請假作業',
|
||||
icon: Icons.access_time_filled,
|
||||
targetScreen: PlaceholderScreen(title: '請假作業'),
|
||||
), // 3. 待簽核事項
|
||||
MenuItem(
|
||||
title: '待簽核事項',
|
||||
icon: Icons.pending_actions,
|
||||
targetScreen: PlaceholderScreen(title: '待簽核事項'),
|
||||
),
|
||||
// 4. 待辦事項
|
||||
MenuItem(
|
||||
title: '待辦事項',
|
||||
icon: Icons.checklist,
|
||||
targetScreen: PlaceholderScreen(title: '待辦事項'),
|
||||
),
|
||||
// 5. 行事曆
|
||||
MenuItem(
|
||||
title: '行事曆',
|
||||
icon: Icons.calendar_today,
|
||||
targetScreen: PlaceholderScreen(title: '行事曆'),
|
||||
),
|
||||
// 6. 其他功能 (佔位)
|
||||
MenuItem(
|
||||
title: '會議通知',
|
||||
icon: Icons.folder,
|
||||
targetScreen: PlaceholderScreen(title: '會議通知'),
|
||||
),
|
||||
// 6. 其他功能 (佔位)
|
||||
MenuItem(
|
||||
title: '產品型錄',
|
||||
icon: Icons.folder,
|
||||
targetScreen: PlaceholderScreen(title: '產品型錄'),
|
||||
),
|
||||
// 6. 其他功能 (佔位)
|
||||
MenuItem(
|
||||
title: '通訊錄',
|
||||
icon: Icons.folder,
|
||||
targetScreen: PlaceholderScreen(title: '通訊錄'),
|
||||
),
|
||||
MenuItem(
|
||||
title: '業績查詢',
|
||||
icon: Icons.bar_chart,
|
||||
targetScreen: PlaceholderScreen(title: '報告查詢'),
|
||||
),
|
||||
MenuItem(
|
||||
title: 'Issue List',
|
||||
icon: Icons.bar_chart,
|
||||
targetScreen: PlaceholderScreen(title: 'Issue List'),
|
||||
),
|
||||
MenuItem(
|
||||
title: '設定',
|
||||
icon: Icons.settings,
|
||||
targetScreen: PlaceholderScreen(title: '設定'),
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
// ------------------------------------
|
||||
// 主選單畫面 (MainMenu) - 九宮格實作
|
||||
// ------------------------------------
|
||||
|
||||
class MainMenu extends StatelessWidget {
|
||||
const MainMenu({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('企業應用主選單'),
|
||||
// 增加 App Bar 的高度,使其更符合 Material 3 風格 (可選)
|
||||
toolbarHeight: 70,
|
||||
// 可以在這裡加入登出按鈕
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout),
|
||||
onPressed: () {
|
||||
// 登出:導航回登入頁面並清除所有歷史堆棧
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(builder: (context) => const LoginPage()),
|
||||
(Route<dynamic> route) => false,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
// 使用 Padding 讓 GridView 內容與邊緣保持距離
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
// 核心:使用 GridView.count 建立九宮格佈局
|
||||
child: GridView.count(
|
||||
// 每行顯示 3 個項目(實現九宮格的第一條件)
|
||||
crossAxisCount: 3,
|
||||
// 項目間的間距
|
||||
crossAxisSpacing: 10.0,
|
||||
mainAxisSpacing: 10.0,
|
||||
// 遍歷 menuItems 列表來構建每個選單項目
|
||||
children: menuItems.map((item) {
|
||||
return MainMenuItemTile(item: item);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// 單個選單項目的小部件 (MainMenuItemTile)
|
||||
// ------------------------------------
|
||||
|
||||
class MainMenuItemTile extends StatelessWidget {
|
||||
final MenuItem item;
|
||||
|
||||
const MainMenuItemTile({required this.item, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 4.0, // 增加陰影,更有質感
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
|
||||
child: InkWell(
|
||||
// 使用 InkWell 獲得點擊時的漣漪效果
|
||||
onTap: () {
|
||||
// 點擊後導航到對應的目標畫面
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => item.targetScreen,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
// 選單圖標
|
||||
Icon(
|
||||
item.icon,
|
||||
size: 48.0,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
// 選單名稱
|
||||
Text(
|
||||
item.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
// 1. 確保引入 news_detail.dart
|
||||
import './news_detail.dart';
|
||||
|
||||
class News extends StatelessWidget {
|
||||
final List<String> news;
|
||||
|
||||
// 構造函數保持不變
|
||||
const News(this.news, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 使用 ListView.builder 替代 Column 和 map().toList()
|
||||
// 這樣可以確保內容可以捲動,並且只構建可見的項目,效率更高。
|
||||
return ListView.builder(
|
||||
// 設置列表的長度,即 news 列表的項目數
|
||||
itemCount: news.length,
|
||||
|
||||
// 根據索引來構建每個列表項目
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final String currentNewsTitle = news[index]; // 提取當前新聞標題
|
||||
|
||||
// 使用 news[index] 來取得對應的新聞文本
|
||||
return Card(
|
||||
elevation: 5, // 可選: 增加卡片陰影
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), // 增加邊距
|
||||
// 在這裡可以選擇添加 Key
|
||||
key: ValueKey(currentNewsTitle + index.toString()),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch, // 讓圖片和文字可以寬度延伸
|
||||
children: <Widget>[
|
||||
// --- 點擊功能的修改在這裡 ---
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// 點擊事件:導航到 NewsDetail 畫面
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NewsDetail(
|
||||
// 2. 將當前新聞的標題/內容傳遞給詳細頁面
|
||||
newsTitle: currentNewsTitle,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Image.asset(
|
||||
'assets/F16-02.jpg',
|
||||
fit: BoxFit.cover,
|
||||
height: 200,
|
||||
),
|
||||
),
|
||||
// --------------------------
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Text(
|
||||
currentNewsTitle,
|
||||
style: const TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// 這是一個 StatelessWidget,用於顯示單條新聞的詳細內容
|
||||
class NewsDetail extends StatelessWidget {
|
||||
final String newsTitle; // 接收從前一個畫面傳遞過來的新聞標題
|
||||
|
||||
// 為了演示,我們假設詳細內容是一個較長的文本
|
||||
final String newsDetailContent =
|
||||
"這是新聞的詳細內容。它包含了更深入的分析、更多的數據和完整的背景資訊。點擊圖片後,使用者可以在這個頁面仔細閱讀。由於這是一個範例,我們使用了一些重複的文本來模擬文章長度,以確保頁面可以滾動。在這個頁面,您可以加入更多的元素,例如不同的圖片、作者資訊、發布日期等。";
|
||||
|
||||
const NewsDetail({required this.newsTitle, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
// 設置 App Bar,標題可以是傳入的新聞標題
|
||||
appBar: AppBar(
|
||||
title: Text(newsTitle, overflow: TextOverflow.ellipsis), // 防止標題過長
|
||||
),
|
||||
// 使用 SingleChildScrollView 確保頁面內容超出螢幕時可以滾動
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// 再次顯示圖片 (可選)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: Image.asset(
|
||||
'assets/F16-02.jpg',
|
||||
fit: BoxFit.cover,
|
||||
height: 250,
|
||||
width: double.infinity, // 佔滿寬度
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
|
||||
// 顯示新聞標題
|
||||
Text(
|
||||
newsTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 24.0,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const Divider(height: 32.0), // 分隔線
|
||||
|
||||
// 顯示新聞詳細內容
|
||||
Text(
|
||||
newsDetailContent * 5, // 重複內容 5 次來模擬長文章
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
height: 1.5, // 增加行高,改善閱讀體驗
|
||||
),
|
||||
textAlign: TextAlign.justify, // 內容兩端對齊
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
// 確保你的 news.dart 文件和 News 類已經被正確引入
|
||||
import './news.dart';
|
||||
// 確保引入 MainMenu 所在的 main.dart 文件,以便導航
|
||||
import './main.dart';
|
||||
|
||||
class NewsManager extends StatefulWidget {
|
||||
const NewsManager({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _NewsManagerState();
|
||||
}
|
||||
}
|
||||
|
||||
class _NewsManagerState extends State<NewsManager> {
|
||||
List<String> news = ['第一筆資料: F-16 戰機升級計畫', '第二筆資料: 台積電研發新突破'];
|
||||
|
||||
void _addNews() {
|
||||
setState(() {
|
||||
news.add('新增資料:最新動態 ${DateTime.now().second}');
|
||||
});
|
||||
}
|
||||
|
||||
// 新增的導航函數:返回到主選單
|
||||
void _backToHome() {
|
||||
// Navigator.popUntil 會一直彈出 (Pop) 路由堆棧中的頁面,直到遇到一個
|
||||
// 滿足條件 (predicte) 的路由。在這裡,我們使用 (route) => route.isFirst
|
||||
// 來返回到路由堆棧中的第一個頁面,也就是 MainMenu (如果您的啟動順序是 Login -> MainMenu)。
|
||||
// 如果 MainMenu 不是第一個頁面,您可以考慮使用:
|
||||
// Navigator.popUntil(context, ModalRoute.withName('/main_menu'));
|
||||
// 但這需要您在 MaterialApp 中為 MainMenu 設定路由名稱。
|
||||
|
||||
// 最保險且簡單的方法是直接彈出所有,然後重新推入 MainMenu(確保 MainMenu 不會有歷史記錄)
|
||||
// 或者我們假設 MainMenu 是 Login 成功後的第一個頁面,使用 pushReplacement 清理過的堆棧。
|
||||
// 在本例中,我們直接導航回 MainMenu,並清除所有在它之上的頁面。
|
||||
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MainMenu()),
|
||||
(Route<dynamic> route) => false, // 移除所有前面的路由
|
||||
);
|
||||
|
||||
// 註:若 NewsManager 已經是 Scaffold 的子頁面,使用 Navigator.pop(context)
|
||||
// 即可返回上一個頁面(即 MainMenu)。但如果想確保是回主頁,用 pushAndRemoveUntil 更明確。
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// *** 關鍵修改:將整個頁面包裹在 Scaffold 中以包含 AppBar ***
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('新聞管理'),
|
||||
// ----------------------------------------------------
|
||||
// 新增的返回主頁按鈕
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.home), // 使用一個 Home 圖標
|
||||
tooltip: '返回主頁', // 長按時的提示
|
||||
onPressed: _backToHome, // 點擊時呼叫導航函數
|
||||
),
|
||||
],
|
||||
// ----------------------------------------------------
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
// 1. 固定高度的按鈕部分
|
||||
Container(
|
||||
margin: const EdgeInsets.all(10),
|
||||
child: ElevatedButton(
|
||||
onPressed: _addNews,
|
||||
child: const Text(
|
||||
'新增資料',
|
||||
style: TextStyle(fontSize: 20, color: Colors.redAccent),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 2. 佔據剩餘空間的列表部分
|
||||
Expanded(
|
||||
child: News(news),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// 通用的佔位畫面
|
||||
class PlaceholderScreen extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const PlaceholderScreen({required this.title, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
),
|
||||
body: Center(
|
||||
child: Text(
|
||||
'$title 功能正在開發中...',
|
||||
style: const TextStyle(fontSize: 24, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 各功能頁面 (繼承自 PlaceholderScreen,方便未來替換成真實頁面)
|
||||
class AttendanceScreen extends PlaceholderScreen {
|
||||
AttendanceScreen({super.key}) : super(title: '員工打卡');
|
||||
}
|
||||
|
||||
class ApprovalScreen extends PlaceholderScreen {
|
||||
ApprovalScreen({super.key}) : super(title: '待簽核事項');
|
||||
}
|
||||
|
||||
class TodoScreen extends PlaceholderScreen {
|
||||
TodoScreen({super.key}) : super(title: '待辦事項');
|
||||
}
|
||||
|
||||
class CalendarScreen extends PlaceholderScreen {
|
||||
CalendarScreen({super.key}) : super(title: '行事曆');
|
||||
}
|
||||
Reference in New Issue
Block a user