iOS 开发环境配置备忘

按照一些教学视频做的简单备忘:

  • 斯坦福大学 CS193p
  • Udemy付费课程 iOS13 & Swift - The Complete iOS App Development Bootcamp

这篇文章主要记录一些环境配置的内容,涉及代码的地方以 objectc为主,interface以storyboard为主。

后续分成几篇分别记录 objectc , swift , swiftUI 的 reference。

个人认为,对于有代码能力的人来说,这些教程内容都太过冗长,实际操作中我一般快速扫过视频,让脑子有个印象即可,需要学习特定内容再使用 chatgpt + 返回教程回顾,可以省下大量时间,并且在学习的过程中给自己更强的信心。

iOS和其他后端语言相比,乍一看让人生畏的地方,可能就是可视化的那些内容。至少我一开始是这样看的。稍微掌握之后就可以直接实战了。我甚至想到了photoshop的一些操作。

零、环境准备

  1. 一台MacBook Air/Pro 或者 Mac mini/studio 或者 iMac

  2. 一台iPhone,iPad也可以。

  3. macOS,一个较新的版本。

  4. Xcode:,一个较新的版本。

    https://developer.apple.com/support/xcode,这里列了不同版本 xcode 的OS、sdk支持情况。

    Xcode也可以直接在 app store 中下载,但就不能选择特定版本了。

    另外不要下载Beta版本,以免有奇怪的bug。

    image1.20231011193518209

  5. 一个apple 开发者账号。

    只开发不发布的话,是免费的,不需要付费。 https://developer.apple.com

  6. 一个良好、稳定的网络环境。

一些文档

  1. Xcode的Help链接里有文档:

    image-20221222午後10106457

    image-20230904171337241

  2. 还有一个app,名字叫 Developer:

    image-20240110下午32607879

  3. https://developer.apple.com/documentation

  4. GitHub Awesome :https://github.com/dkhamsing/open-source-ios-apps

    image-20240123下午33138859

  5. udemy 教材相关链接:https://www.appbrewery.co/p/ios-course-resources/,我也保存了一份pdf备份

  6. 令人发指的函数式编程和相关语法糖

一、Project

image-20221208午前101417830

image-20221208午前112203800

Project interface分为 Storyboard 和 SwiftUI。这两者的区别为:

  • storyboardsXIB(Xml Interface Builder) 包含相当多的 XML 语句,不容易阅读,也不容易编辑。
  • storyboards 随着时间的推移越来越大。
  • XIBSwift 是非常独立的东西,XIBSwift 代码不了解,而 Swift 代码对 XIB 也不了解。
  • SwiftUI 是一个只适用于 Swift 的框架。
  • SwiftUI 代码比 storyboardsXML 更容易阅读和管理。

image-20240123下午120701255

最简单的创建:

  • storyboard
  • object c
  • 不包括Tests

1. 使用storyboard, objectc生成

image-20230904165451693

image-20230904170522997

  • Main.storyboard文件用来设计、拖拽。

2. 使用 storyboard, swift 生成

image-20240123下午21400900

image-20240123下午21641949

3. 使用 swiftUI, swift 生成

image-20240123下午43520274

image-20240123下午43350025

二、真机运行

如果是在真机上运行:

  1. 需要在xcode上配置证书
  2. 需要在手机上开启开发者模式:设置->隐私与安全性->开发者模式
  3. 需要在手机上信任开发者证书

以上都是免费的,不需要注册开发者账号。如果使用模拟器,则不用这么麻烦。

另外如果是从互联网上下载的代码,建议不要随意在真机上运行,有安全风险。

image-20240123下午25621027

image-20221207午前113451384

image-20221207午前114103301

image-20221207午前115613715

设置-> 通用->VPN与设备管理:

image-20221207午前115553351

如果你的账号是团队的成员,我这边遇到好几个问题顺便记录一下(2023.08.20):

  1. 在账号中不显示团队信息,

    大概率是在web配置页面,管理员没有配置你为开发者,并且开启了拉取配置信息的权限。

    4e9c20fbba9155bb

  2. Failed to register bundle identifier The app identifier “xxx” cannot be registered to your development team because it is not available. Change your bundle identifier to a unique string to try again.

    b6be7ea68b750d7f

    这里参考 Xcode Error: “The app ID cannot be registered to your development team.”

     cd ~/Library/MobileDevice/Provisioning\ Profiles
    rm *
    Xcode > Preferences... > Accounts > click your Account and Team name > click Download Manual Profiles
    

running,点击这个三角形就在真机运行。

image-20240123下午25829431

在Windows-> Devices and Simulators 可以看到设备列表:

image-20240123下午30748376

2024-01-18下午1.26.50

Preview 可以在这里设置:

image-20240123下午31727488

三、概念理解/设计模式

1. 系统库

IMG_7213

IMG_7212

IMG_7211

IMG_7210

框架:

  • Foundation
  • Core Data
  • Map Kit
  • UIKit
  • Core Motion

2. MVC/MVVM

一般后端常讲 mvc,在iOS等前端语言中一般说mvvm,个人感觉是一样的概念。MVC:

IMG_7214

image-20240123下午73930919

3. Protocol/Delegate

(视频在model里)声明protocol:

image-20240126下午10142266

controller 里实现:

image-20240126下午30629383

4. extension

image-20240126下午33926716

5. function

image-20240126下午31012325

image-20240126上午111840138

6. 闭包

关键字是 in

image-20240126上午112803299

image-20240126上午112938937

转成闭包:

image-20240126上午113012787

7. 闭包细节

将函数作为返回值,如下。

image-20240126上午112111008

最后精简为:

calculator(n1:2,n2:3){$0 * $1}

这种场景下,就可以用闭包代替。一步一步代替:

  1. 把multiply改成闭包:

    image-20240126上午113229823

  2. 挪到调用的地方:

    image-20240126上午113319006

  3. 让编译器自个判断闭包的返回值类型

    image-20240126上午113705983

  4. return可以省略:

    image-20240126上午113725863

  5. 把3行换成1行显示

    image-20240126上午113801798

  6. 用匿名参数$0 $1

    image-20240126上午113903014

  7. swift的规则,如果函数的最后一个参数是闭包,可以直接把这个参数省略掉,扔到括号外。(逆天!!!,可读性逆天!!!)

    image-20240126上午115235333

另一个逆天的缩写,这次用map函数:

image-20240126上午115640191

image-20240126上午115701813

一步一步来:

image-20240126上午115732206

image-20240126上午115802678

image-20240126上午115834940

最终结果:

image-20240126上午115904221

image-20240126上午115942776

8. 线程

UI的内容更新要在主线程里:DispatchQueue.main

image-20240126下午32815547

9.其他库

  • CoreLocation

以下开始是 xcode 的一些简单功能

四、添加资源、素材

右上角的+用得最多了,添加各种组件。

image-20221208午前104903090

1. 增加画布

  • 增加一个 viewcontroller.swift文件,自个copy 模版ViewController的内容,主要是import UIKit
  • 再在storyboard上增加一个view controller。
  • 右侧栏上设置对应的class。

2024-01-24上午9.23.01

image-20240124上午100701785

2. 导出各种格式尺寸的第三方工具

asset 目录里有很多不同尺寸的图(注意设置All Size):

image-20240123下午24034428

image-20221208午前105037207

3. 图标 SF Symbols

image-20221222午後54231470

4. 暗色模式

Image Set -> Appearance -> 设置明色与暗色

image-20240126上午100116656

5. 矢量图

Image Set-> Resizing -> 保留矢量信息

image-20221226午後85627331

五、页面、页面元素和代码关联

1. 代码关联

image-20221208午前113552569

选择 Assistant 可以将代码和界面放一起:

image-20221208午前113635472

声明组件和Action可以用拖拽的方式,ctrl+拖动

image-20221208午後20539567

image-20221208午後20653439

放在 class的前部分默认就是 outlet,放在后部分默认就是Action。

image-20221208午後20801275

关联关系可以在图形上右键查看:

image-20221208午後40128397

image-20221208午後34618912

2. 自适应布局

选集,061 Setting Constraints and working with the Safe Area

2.1 constraints

指定item和边缘的距离。

未标题-1

2.2 alignment/pinning

image-20221213午後42724791

iOS里的stack view组件可以类比为html中的div组件。

view controller里的文件视图类似于photoshop里的图层反过来。

2.3 stack view

上图右下角的最右边那个收纳的图标。

4. UITextField

image-20240126上午105045443

六、view、controller和绑定

1. 纯代码

这样在图形化界面里我们就根本看不见这个view了,没办法用拖拽的方式给它添加东西。

  • 新建一个 swift 文件:

image-20221222午後13259831

  • 命名为ViewController secondVC,改一下 import 为 UIKit`

  • 定义一些基本的变量保存数据

  • override func viewDidLoad

  • 定义一个组件,例如 UILabel

  • 在上一个页面的ViewController里给 secondVC 的变量赋值

  • 在上一个页面的ViewControllerself.present(secondVC, animated:true, completion: nil)

2. 拖拽式生成view并绑定controller

在图形界面新建一个viewController

image-20221222午後12943449

image-20221222午後13017785

新建一个cocoa touch class:

image-20221222午後12312137

image-20221222午後12409400

然后在 storyboard 里点一下还没有绑定 vc 的图形:

image-20221222午後12626236

image-20221222午後12645768

选择之后就可以看到它绑定好了。

image-20221222午後12711338

3. 生成view间关联:segues

image-20221222午後30157878

点击中间那个关联的方块,设定segues的identifier名字:

image-20221222午後30336922

然后在代码中触发这个切换:

self.performSegue(withIdentifier: "testSeg", sender: self)

同时,在前一个 controller 里我们可以做这个view 在切换前的准备工作。

4. 设置顶部出现返回栏:navigation controller

先点击第一个viewcontroller(黄色的图标),再点击再菜单栏上选择 Editor->Embed In->Navigation Controller

image-20221228午後55346354

image-20221228午後55313868

navitationItem.hidesBackButton = true

4. 数据传输

给另一个 vc 创建变量:

image-20240125下午65634660

转场的时候,将数值传过去:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
          if segue.identifier == "testSeg" {
            let destinationVC = segue.destination as! ResultViewController
            destinationVC.bmiValue = calculatorBrain.getBMIValue()
            destinationVC.advice = calculatorBrain.getAdvice()
            destinationVC.color = calculatorBrain.getColor()
        }
    }

5. 返回上个view

在新的vc里返回上一个view: 用 dismiss 方法

    @IBAction func recalculatePressed(_ sender: UIButton) {
        dismiss(animated: true, completion: nil)
    }

6. 3D查看

确保应用在运行时,可以用3D的方式查看图层,点击中间那个三个图层叠加的图标Debug View Hierarchy

image-20221222午後34223358

image-20221222午後34307338

8. viewcontroller的生命周期

七、cocoapods

image-20240126下午45533244

sudo gem install cocoapods
pod setup --verbose

更详细内容参考这篇《cocoapods 初接触》

八、

1. mark

image-20240126下午33445977

2. 代码片段

image-20240126下午33600132

image-20240126下午33759155

image-20240126下午33720122

十二、教程课程备忘

Udemy付费课程 iOS13 & Swift - The Complete iOS App Development Bootcamp

app:Rich, 015

image-20240123下午44941549

app: Dicee, 035

image-20240123下午45132434

app:Xylophone, 071

image-20240123下午44757205

up 推荐面向Google 和stack overflow编程。

image-20240123下午64119116

app: EggTimer, 081

swift相关,没啥看头

image-20240123下午71633363

看一个时间控制器:

image-20240123下午73223779

app: Quizzler, 095

也没啥看头

image-20240123下午72302971

同样一个时间控制器:

image-20240123下午73429913

image-20240123下午74323982

app: BMI Calculator, 119

用 UISlider

image-20240123下午74638623

也教了一些使用reference文档的方法,124

image-20240123下午75841633

app:clima, 143

image-20240125下午71700611

查查api开发之类的。

app: Flash Chat, 173

https://github.com/appbrewery/Flash-Chat-iOS13

image-20240126下午40247147

布局相关 , 059

image-20240123下午45330384

网络相关,149

image-20240126上午111121994

image-20240126上午111244807

动画相关,175

Timer.scheduledTimer

image-20240126下午43630969

swift相关, 122

这些内容我觉得 斯坦福 那个视频更好。

image-20240123下午75453593

image-20240123下午75512471

firebase, 184-186

Firebase 是由 Google 提供的一套为开发者提供后端服务的平台,可以帮助开发者快速构建高质量的移动应用、Web 应用和桌面应用。Firebase 提供了多种功能,包括实时数据库、身份认证、云存储、云函数、分析、消息推送等。

对于 iOS 和 Android 开发者来说,Firebase 提供了一系列 SDK 和工具,使他们可以轻松地集成 Firebase 功能到他们的应用中。以下是 Firebase 提供的一些主要功能:

  1. 实时数据库 (Realtime Database):Firebase 的实时数据库是一个云托管的 NoSQL 数据库,它能够实时同步数据,使得多个客户端能够同时查看和修改数据,而不需要编写复杂的同步代码。

  2. 身份认证 (Authentication):Firebase 身份认证提供了简单易用的身份验证功能,支持电子邮件/密码、手机号码、Google、Facebook、Twitter、GitHub 等多种登录方式。

  3. 云存储 (Cloud Storage):Firebase 的云存储可以让开发者轻松地将用户生成的内容,如图像、视频或用户数据存储到云端,而无需自己搭建服务器。

  4. 云函数 (Cloud Functions):Firebase 云函数让开发者能够以事件驱动的方式在云端运行代码,例如在数据库写入时触发某些操作。

  5. 分析 (Analytics):Firebase 分析提供了应用的使用情况分析,包括用户行为、用户流、转化率等指标,帮助开发者了解用户并改进应用。

  6. 消息推送 (Cloud Messaging):Firebase 云消息推送可以向应用的用户发送通知消息,包括文字、图像和声音等。

  7. 性能监控 (Performance Monitoring):Firebase 提供了性能监控功能,帮助开发者监控应用的性能指标,并定位性能问题。

  8. A/B 测试 (A/B Testing):Firebase 提供了 A/B 测试功能,让开发者能够测试不同功能或设计对用户行为的影响,从而优化应用。

总的来说,Firebase 提供了一整套的后端解决方案,使得开发者可以专注于应用的前端开发,而无需花费大量精力在搭建和管理后端服务上。

其他内容

  • 闭包,150
  • json,151
  • compute 方法,152

参考资料


Mac 和 Win 下的微信双开/多开

Win

微信的路径根据各自需要改:

@ECHO OFF
start /d "C:\Program Files (x86)\Tencent\WeChat\" WeChat.exe
start /d "C:\Program Files (x86)\Tencent\WeChat\" WeChat.exe
exit

把文件保存成 xxx.bat,双击运行即可。原理就是趁着系统还没反应过来多开几个微信实例。

如果想多开几个,把 start 那行多写几遍就可以了。

Mac

echo "nohup /Applications/WeChat.app/Contents/MacOS/WeChat >/dev/null 2>&1 &" > ~/Desktop/WeChat.command
chmod +x ~/Desktop/WeChat.command

双击桌面的WeChat.command文件即可。

Mac下还有相关的插件可以使用,比如这个 A dynamic library tweak for WeChat macOS

image-20230922112248840

ps: 这个插件还能防撤回

image-20230922131611959

Pps: 如果注入出错,可以尝试换一个终端。系统弹出允许提示有时候没法显示,就会导致注入出错。

image-20231003上午01546274


laravel nova 的一些资源和几个常见问题

最近使用 nova 比较多,整理了一些常用的资源链接和几个我遇到的问题:

移动端自适应包

这个插件可以让 3.0 及 更早的 nova 适应手机上的显示。https://github.com/gregoriohc/laravel-nova-theme-responsive

当然自动4.0官方支持响应式后就不再更新了。我目前还没有升级的需要,就一直用着3.x了。

composer require gregoriohc/laravel-nova-theme-responsive

image-20221128午前113207562

升级

可把我难住了:

compose.json里添加:

    "repositories": [
        {
            "type": "path",
            "url": "./src-nova"
        }
    ],

解压官方压缩包到目录 ./src-nova

composer update "laravel/nova @3.32" --ignore-platform-reqs

laravel nova 不显示资源

好久没用了,发现怎么不会用了,不显示资源。reddit上一个答案解释了原因: 需要关闭认证:

https://nova.laravel.com/docs/3.0/resources/authorization.html#disabling-authorization

/**
 * Determine if the given resource is authorizable.
 *
 * @return bool
 */
public static function authorizable()
{
    return false;
}

参考资料


在 Mac M1 上安装 selenium

自从主力机换成 M1 后,就很少开docker了,很多容器都没有做arm架构的镜像。我常用的 selenium 也是,可叹。最后还是裸着安装了。以前在这篇文章里折腾过本地安装《selenium 裸安装备忘》,这次也是类似的操作,在本篇文章里记录一下。

一、安装java

brew install java

image-20221117午後13156478

image-20221117午後13232702

export PATH="/opt/homebrew/opt/openjdk/bin:$PATH"
alias java="/opt/homebrew/opt/openjdk/bin/java

image-20221117午後32601808

我发现 Mac 下的PATH有问题,在 zshrc 里改的不符合想法,系统的PATH还是在前面,我担心这样会有问题,遂直接改了这个文件 /etc/paths

二、安装chromedriver

先去官网下载 Chrome 版本对应的Chromedriver:https://chromedriver.chromium.org/downloads,扔到 path 对应的目录里。这里我扔到 ~/bin 目录下

image-20221117午後70447004

Mac 下允许执行权限,设置->安全 里:

Screenshot 8

三、安装selenium

官方地址:https://www.selenium.dev/downloads/

我下载了目前最新的 4.6.0版本:

image-20221125午後45446075

然后运行:

java -jar bin/selenium-server-4.6.0.jar standalone --host 100.100.100.9 --port 14444

IP 端口根据各自的需要配置了。

image-20221117午後72428922

看到日志显示 找到driver 就说明已经运行成功了。

尝试调用,看到输出了:

image-20221125午後45927768

16:57:45.003 INFO [LocalDistributor.newSession] - Session request received by the Distributor:
 [Capabilities {browserName: chrome, chromeOptions: {args: [--no-sandbox, window-size=1080x1920, --disable-gpu, --hide-scrollbars, --disable-extensions, start-maximized, --lang=zh-cn, --accept-lang=zh-cn, --arc-disable-locale-sync, --override-language-detection, --disable-dev-shm-usage]}, platform: ANY}, Capabilities {browserName: chrome, goog:chromeOptions: {args: [--no-sandbox, window-size=1080x1920, --disable-gpu, --hide-scrollbars, --disable-extensions, start-maximized, --lang=zh-cn, --accept-lang=zh-cn, --arc-disable-locale-sync, --override-language-detection, --disable-dev-shm-usage]}}]
Starting ChromeDriver 107.0.5304.62 (1eec40d3a5764881c92085aaee66d25075c159aa-refs/branch-heads/5304@{#942}) on port 43558
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.

16:57:46.590 WARN [ProtocolHandshake.createSession] - Support for Legacy Capabilities is deprecated; You are sending the following invalid capabilities: [chromeOptions, platform]; Please update to W3C Syntax: https://www.selenium.dev/blog/2022/legacy-protocol-support/

16:57:46.606 INFO [LocalNode.newSession] - Session created by the Node. Id: d0aff398d25ed2e6c16003eb81336a8b, Caps: Capabilities {acceptInsecureCerts: false, browserName: chrome, browserVersion: 107.0.5304.110, chrome: {chromedriverVersion: 107.0.5304.62 (1eec40d3a576..., userDataDir: /var/folders/k1/qfq95df561b...}, goog:chromeOptions: {debuggerAddress: localhost:49491}, networkConnectionEnabled: false, pageLoadStrategy: normal, platformName: ANY, proxy: Proxy(), se:cdp: http://localhost:49491, se:cdpVersion: 107.0.5304.110, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, webauthn:extension:credBlob: true, webauthn:extension:largeBlob: true, webauthn:virtualAuthenticators: true}

16:57:46.611 INFO [LocalDistributor.newSession] - Session created by the Distributor. Id: d0aff398d25ed2e6c16003eb81336a8b
 Caps: Capabilities {acceptInsecureCerts: false, browserName: chrome, browserVersion: 107.0.5304.110, chrome: {chromedriverVersion: 107.0.5304.62 (1eec40d3a576..., userDataDir: /var/folders/k1/qfq95df561b...}, chromeOptions: {args: [--no-sandbox, window-size=1080x1920, --disable-gpu, --hide-scrollbars, --disable-extensions, start-maximized, --lang=zh-cn, --accept-lang=zh-cn, --arc-disable-locale-sync, --override-language-detection, --disable-dev-shm-usage]}, goog:chromeOptions: {debuggerAddress: localhost:49491}, networkConnectionEnabled: false, pageLoadStrategy: normal, platform: ANY, platformName: ANY, proxy: Proxy(), se:bidiEnabled: false, se:cdp: ws://100.100.100.9:14444/se..., se:cdpVersion: 107.0.5304.110, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, webauthn:extension:credBlob: true, webauthn:extension:largeBlob: true, webauthn:virtualAuthenticators: true}

四、番外-容器化

这两个命令是可以跑起来了:

docker run -d -p 4444:4444 --name selenium-hub selenium/hub:latest 
docker run -d --link selenium-hub:hub -e SE_EVENT_BUS_HOST=hub -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 -e SE_EVENT_BUS_PUBLISH_PORT=4442 -p 5900:5900 --name selenium-node-chrome-vnc selenium/node-chrome:latest 

因为 arm下还没有相关的镜像,虽然容器可以运行

image-20231231142107145

image-20231231142135144

但是无法拉起chrome,用代码拉起chrome一定报错

image-20231231142327309

其实如果直接在vnc里启动chrome,也可以注意到,chrome是无法启动的:

image-20231231142419187

正常情况下应该是这个样子:

image-20231231184340328

所以如果有需要远程运行的话, 找一台x86的机器运行即可。我也是没想到,我x86的MacBook Air还能发挥余热。

docker run -d \
  --name selenium-chrome-1 \
  --restart always \
  --network bridge \
  -p 5901:5900 \
  -p 4445:4444 \
  -v /dev/shm:/dev/shm \
  selenium/standalone-chrome-debug

docker run -d \
  --name selenium-chrome-2 \
  --restart always \
  --network bridge \
  -p 5902:5900 \
  -p 4446:4444 \
  -v /dev/shm:/dev/shm \
  selenium/standalone-chrome-debug

docker run -d \
  --name selenium-chrome-3 \
  --restart always \
  --network bridge \
  -p 5903:5900 \
  -p 4447:4444 \
  -v /dev/shm:/dev/shm \
  selenium/standalone-chrome-debug

参考资料


pandas 操作 excel 备忘

以下链接是我边搜边写 pandas 代码的过程,记录一下。写完手头需求之后基本也懂了。

总结性内容:

细节操作:

我的test代码:

import pandas as pd
import yaml
import datetime

class Project():
    def __init__(self,name):
        self.name = name

    @staticmethod
    def columns():
        return {
            'name':'项目名称',
            'requestCount':'需求总数',
            "requestDoneCount":'需求完成数', 
            'percentRequestCount':'需求完成率',
            'requestKpiCount':"一个月前需求总数",
            "requestKpiDoneCount":'一个月前需求完成数',
            'percentKpiRequestCount':'一个月前需求完成率',
            'director':'主责人',
        }

    def doneFlag():
        return ("a","b","c","d","e")
    def directorOverwrite():
        return {
            "xxx平台":"无",
        }

def getProjectDict(df,now):
    ft = df[df.主责中心 == "kelu.org中心"]
    ftProjects = ft.groupby("blog")
    datetimeNow=datetime.datetime.strptime(now,"%Y%m%d")
    kpiTime=datetimeNow.replace(month=datetimeNow.month - 1).strftime("%Y-%m-%d")

    projectDict = {}

    for mainSys, sysReqs in ftProjects:
        project = Project(mainSys)
        project.requestCount = sysReqs.shape[0]
        sysReqsDone = sysReqs[sysReqs['状态'].isin(Project.doneFlag())]
        project.requestDoneCount = sysReqsDone.shape[0]
        if(project.requestCount == 0):
            project.percentRequestCount = 1
        else:
            project.percentRequestCount = round((float(project.requestDoneCount) / project.requestCount), 4)

        sysReqs['需求创建时间'] = pd.to_datetime(sysReqs['需求创建时间'])
        sysReqsKpi = sysReqs[sysReqs['需求创建时间'] < kpiTime]
        project.requestKpiCount = sysReqsKpi.shape[0]
        sysReqsKpiDone = sysReqsKpi[sysReqsKpi['状态'].isin(Project.doneFlag())]
        project.requestKpiDoneCount = sysReqsKpiDone.shape[0]
        if(project.requestKpiCount == 0):
            project.percentKpiRequestCount = 1
        else:
            project.percentKpiRequestCount = round((float(project.requestKpiDoneCount) / project.requestKpiCount), 4)

        if mainSys in Project.directorOverwrite():
            project.director = Project.directorOverwrite()[mainSys]
        else:
            for _, reqData in sysReqs.iterrows():
                project.director = reqData.主责人
                break

        project.reqs = sysReqs

        projectDict[mainSys] = vars(project)
        # print(type(projectDict[mainSys]))

    return projectDict

def exportMainSheet(projectDict):
    df=pd.DataFrame(projectDict).transpose()
    newDf=df[[
        'name',
        'requestCount',
        "requestDoneCount",
        'percentRequestCount',
        'requestKpiCount',
        'requestKpiDoneCount',
        'percentKpiRequestCount',
        'director'
        ]].sort_values(by=['percentKpiRequestCount','requestKpiCount'],ascending=False)

    # 处理输出内容
    newDf.reset_index(drop=True, inplace=True)
    newDf.index = newDf.index + 1
    newDf.loc['合计'] = newDf[['requestCount',"requestDoneCount",'requestKpiCount','requestKpiDoneCount']].sum()
    newDf.loc['合计','percentRequestCount'] = round((float(newDf.loc['合计','requestDoneCount']) / newDf.loc['合计','requestCount']), 4)
    newDf.loc['合计','percentKpiRequestCount'] = round((float(newDf.loc['合计','requestKpiDoneCount']) / newDf.loc['合计','requestKpiCount']), 4)

    newDf[u'percentRequestCount']= newDf['percentRequestCount'].apply(lambda x:format(x, '.2%'))
    newDf[u'percentKpiRequestCount']= newDf['percentKpiRequestCount'].apply(lambda x:format(x, '.2%'))

    newDf.rename(columns=Project.columns(),inplace=True)

    return newDf

def main():
    now='20221115'
    inputFilename='input'+now+'.xlsx'
    outputFilename='output'+now+'.xlsx'

    df = pd.read_excel(inputFilename,sheet_name='需求列表')
    projectDict = getProjectDict(df,now)
    # print(yaml.dump(projectDict,allow_unicode=True))

    with pd.ExcelWriter(outputFilename) as writer:
        exportMainSheet(projectDict).to_excel(writer, sheet_name="汇总")
        directorDict = {}
        for projectItem in projectDict.values():
            if(projectItem['percentRequestCount'] < 1):
                sysReqs = projectItem['reqs']
                reqsNotDone = sysReqs[~sysReqs['状态'].isin(Project.doneFlag())]
                if projectItem['director'] in directorDict:
                    reqsNotDone = pd.concat([reqsNotDone,directorDict[projectItem['director']]])
                directorDict[projectItem['director']] = reqsNotDone

        for director, reqsNotDone in directorDict.items():
            reqsNotDone.reset_index(drop=True, inplace=True)
            reqsNotDone.index = reqsNotDone.index + 1
            reqsNotDone.to_excel(writer, sheet_name=director)

if __name__=="__main__":
    main()