diff --git a/electron/index.js b/electron/index.js
index 6d0c282..7f2bbb8 100644
--- a/electron/index.js
+++ b/electron/index.js
@@ -1,9 +1,11 @@
 import { app, BrowserWindow, Menu } from 'electron';
 import CONFIG from './const';
 import { checkUpdate } from './utils';
-import initIPC from './ipc';
+import initIPC, { setWin } from './ipc';
 
 app.commandLine.appendSwitch('--no-proxy-server');
+process.on('uncaughtException', () => {});
+process.on('unhandledRejection', () => {});
 
 function createWindow() {
   Menu.setApplicationMenu(null);
@@ -24,6 +26,7 @@ function createWindow() {
     },
   });
 
+  setWin(mainWindow);
   mainWindow.loadURL(CONFIG.APP_START_URL);
   CONFIG.IS_DEV && mainWindow.webContents.openDevTools();
 }
diff --git a/electron/ipc.js b/electron/ipc.js
index 5312c51..99a7616 100644
--- a/electron/ipc.js
+++ b/electron/ipc.js
@@ -1,7 +1,11 @@
-import { ipcMain } from 'electron';
+import { ipcMain, dialog } from 'electron';
 import log from 'electron-log';
+import { throttle } from 'lodash';
 import { startServer } from './proxyServer';
 import { installCert, checkCertInstalled } from './cert';
+import { downloadFile } from './utils';
+
+let win;
 
 export default function initIPC() {
   ipcMain.handle('invoke_初始化信息', async (event, arg) => {
@@ -13,14 +17,42 @@ export default function initIPC() {
   });
 
   ipcMain.handle('invoke_启动服务', async (event, arg) => {
-    console.log('invoke_启动服务');
     return startServer({
-      interceptCallback: async req => {
-        console.log('=========> intercept', req.url);
+      interceptCallback: phase => async (req, res) => {
+        if (phase === 'response' && res?._data?.headers?.['content-type'] == 'video/mp4') {
+          win?.webContents?.send?.('VIDEO_CAPTURE', {
+            url: req.fullUrl(),
+            size: res?._data?.headers?.['content-length'] ?? 0,
+          });
+        }
       },
       setProxyErrorCallback: err => {
-        console.log({ err });
+        console.log('开启代理失败', err);
       },
     });
   });
+
+  ipcMain.handle('invoke_选择下载位置', async (event, arg) => {
+    const result = dialog.showOpenDialogSync({ title: '保存', properties: ['openDirectory'] });
+
+    if (!result?.[0]) {
+      throw '取消';
+    }
+
+    return result?.[0];
+  });
+
+  ipcMain.handle('invoke_下载视频', async (event, { url, savePath }) => {
+    return downloadFile(
+      url,
+      `${savePath}/${Date.now()}.mp4`,
+      throttle(value => win?.webContents?.send?.('e_进度变化', value), 1000),
+    ).catch(err => {
+      console;
+    });
+  });
+}
+
+export function setWin(w) {
+  win = w;
 }
diff --git a/electron/proxyServer.js b/electron/proxyServer.js
index 24ab503..4ff39dd 100644
--- a/electron/proxyServer.js
+++ b/electron/proxyServer.js
@@ -5,34 +5,50 @@ import { app } from 'electron';
 import CONFIG from './const';
 import { setProxy, closeProxy } from './setProxy';
 
-export async function startServer({ interceptCallback = f => f, setProxyErrorCallback = f => f }) {
+export async function startServer({
+  interceptCallback = f => f => f,
+  setProxyErrorCallback = f => f,
+}) {
   const port = await getPort();
-  const proxy = hoxy
-    .createServer({
-      certAuthority: {
-        key: fs.readFileSync(CONFIG.CERT_PRIVATE_PATH),
-        cert: fs.readFileSync(CONFIG.CERT_PUBLIC_PATH),
-      },
-    })
-    .listen(port, () => {
-      setProxy('127.0.0.1', port).catch(setProxyErrorCallback);
-    })
-    .on('error', e => {
-      console.log('proxy lib error', e);
-    });
 
-  proxy.intercept(
-    {
-      phase: 'request',
-    },
-    interceptCallback,
-  );
+  return new Promise(async (resolve, reject) => {
+    const proxy = hoxy
+      .createServer({
+        certAuthority: {
+          key: fs.readFileSync(CONFIG.CERT_PRIVATE_PATH),
+          cert: fs.readFileSync(CONFIG.CERT_PUBLIC_PATH),
+        },
+      })
+      .listen(port, () => {
+        setProxy('127.0.0.1', port)
+          .then(() => resolve())
+          .catch(() => {
+            setProxyErrorCallback(data);
+            reject('设置代理失败');
+          });
+      });
+
+    proxy.intercept(
+      {
+        phase: 'request',
+      },
+      interceptCallback('request'),
+    );
+
+    proxy.intercept(
+      {
+        phase: 'response',
+      },
+      interceptCallback('response'),
+    );
+  });
 }
 
 app.on('before-quit', async e => {
   e.preventDefault();
   try {
     await closeProxy();
+    console.log('close proxy success');
   } catch (error) {}
 
   app.exit();
diff --git a/electron/utils.js b/electron/utils.js
index 61e9648..d900c77 100644
--- a/electron/utils.js
+++ b/electron/utils.js
@@ -1,6 +1,7 @@
 import { get } from 'axios';
-const { app, dialog, shell } = require('electron');
+import { app, dialog, shell } from 'electron';
 import semver from 'semver';
+import fs from 'fs';
 
 // packageUrl 需要包含 { "version": "1.0.0" } 结构
 function checkUpdate(
@@ -27,4 +28,35 @@ function checkUpdate(
     .catch(err => {});
 }
 
-export { checkUpdate };
+function downloadFile(url, fullFileName, progressCallback) {
+  return get(url, {
+    responseType: 'stream',
+    headers: {
+      'User-Agent':
+        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
+    },
+  }).then(({ data, headers }) => {
+    let currentLen = 0;
+    const totalLen = headers['content-length'];
+
+    return new Promise((resolve, reject) => {
+      data.on('data', ({ length }) => {
+        currentLen += length;
+        progressCallback?.(currentLen / totalLen);
+      });
+
+      data.on('error', err => reject(err));
+
+      data.pipe(
+        fs.createWriteStream(fullFileName).on('finish', () => {
+          resolve({
+            fullFileName,
+            totalLen,
+          });
+        }),
+      );
+    });
+  });
+}
+
+export { checkUpdate, downloadFile };
diff --git a/package.json b/package.json
index 18de558..e832325 100644
--- a/package.json
+++ b/package.json
@@ -48,13 +48,17 @@
     "webpack-cli": "^4.9.2"
   },
   "dependencies": {
+    "@ant-design/icons": "^4.7.0",
     "@xstate/react": "^3.0.0",
+    "antd": "^4.20.5",
     "axios": "^0.27.2",
     "electron-is-dev": "^2.0.0",
     "electron-log": "^4.4.7",
     "get-port": "^6.1.2",
     "hoxy": "^3.3.1",
+    "lodash": "^4.17.21",
     "mkdirp": "^1.0.4",
+    "pretty-bytes": "^6.0.0",
     "react": "^18.1.0",
     "react-dom": "^18.1.0",
     "regedit": "5.0.0",
diff --git a/src/App.jsx b/src/App.jsx
index 3c1ece6..2e77ffc 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,20 +1,142 @@
 import { useMachine } from '@xstate/react';
+import { Table, Button, Progress, Alert } from 'antd';
+import { shell } from 'electron';
+import {
+  DownloadOutlined,
+  PlaySquareOutlined,
+  ClearOutlined,
+  GithubOutlined,
+  EyeOutlined,
+  FormatPainterOutlined,
+  RedoOutlined,
+} from '@ant-design/icons';
 import fsm from './fsm';
 
 import './App.less';
 function App() {
   const [state, send] = useMachine(fsm);
-  const {} = state.context;
+  const { captureList, currentUrl, downloadProgress } = state.context;
 
   return (
     <div className="App">
       {state.matches('检测初始化') ? <div>检测中……</div> : null}
-      {state.matches('初始化完成') ? <div>初始化完成</div> : null}
+      {state.matches('初始化完成') ? (
+        <div className="App-inited">
+          <Button
+            className="App-inited-clear"
+            icon={<ClearOutlined />}
+            onClick={() => send('e_清空捕获记录')}
+          >
+            清空
+          </Button>
+          <Button
+            className="App-inited-github"
+            icon={<GithubOutlined />}
+            onClick={() => shell.openExternal('https://github.com/lecepin/WeChatVideoDownloader')}
+            type="primary"
+            ghost
+          >
+            Star
+          </Button>
+          <Table
+            sticky
+            dataSource={captureList}
+            columns={[
+              {
+                title: '视频地址(捕获中……)',
+                dataIndex: 'url',
+                key: 'url',
+                render: value => value,
+                ellipsis: true,
+              },
+              {
+                title: '大小',
+                dataIndex: 'prettySize',
+                key: 'prettySize',
+                width: '100px',
+                render: value => value,
+              },
+              {
+                title: '操作',
+                dataIndex: 'action',
+                key: 'action',
+                width: '200px',
+                render: (_, { url, fullFileName }) => (
+                  <div>
+                    {fullFileName ? (
+                      <Button
+                        icon={<EyeOutlined />}
+                        type="primary"
+                        onClick={() => {
+                          shell.openPath(fullFileName);
+                        }}
+                        size="small"
+                        ghost
+                      >
+                        查看
+                      </Button>
+                    ) : (
+                      <Button
+                        icon={<DownloadOutlined />}
+                        type="primary"
+                        onClick={() => {
+                          send({ type: 'e_下载', url });
+                        }}
+                        size="small"
+                      >
+                        下载
+                      </Button>
+                    )}
+                    &nbsp; &nbsp;
+                    <Button
+                      icon={<PlaySquareOutlined />}
+                      onClick={() => {
+                        send({ type: 'e_预览', url });
+                      }}
+                      size="small"
+                    >
+                      预览
+                    </Button>
+                  </div>
+                ),
+              },
+            ]}
+            pagination={{ position: ['none', 'none'] }}
+          ></Table>
+
+          {state.matches('初始化完成.预览') ? (
+            <div
+              className="App-inited-preview"
+              onClick={e => {
+                e.target == e.currentTarget && send('e_关闭');
+              }}
+            >
+              <video src={currentUrl} controls autoPlay></video>
+            </div>
+          ) : null}
+
+          {state.matches('初始化完成.下载.下载中') ? (
+            <div className="App-inited-download">
+              <Progress type="circle" percent={downloadProgress} />
+            </div>
+          ) : null}
+        </div>
+      ) : null}
       {state.matches('未初始化') ? (
-        <div>
-          <p>未初始化</p>
-          <button onClick={() => send('e_开始初始化')}>初始化</button>
-          <button onClick={() => send('e_重新检测')}>重新检测</button>
+        <div className="App-uninit">
+          <Alert message="首次进入,请先初始化~" type="warning" showIcon closable={false} />
+          <Button
+            size="large"
+            onClick={() => send('e_开始初始化')}
+            type="primary"
+            icon={<FormatPainterOutlined />}
+          >
+            初始化
+          </Button>
+          &nbsp;&nbsp;
+          <Button size="large" onClick={() => send('e_重新检测')} icon={<RedoOutlined />}>
+            重新检测
+          </Button>
         </div>
       ) : null}
     </div>
diff --git a/src/App.less b/src/App.less
index 748abd5..cfc8434 100644
--- a/src/App.less
+++ b/src/App.less
@@ -1,2 +1,52 @@
 .App {
+  padding: 10px;
+
+  &-uninit{
+      text-align: center;
+      padding: 10px;
+
+      & button{
+          margin-top: 50px;
+      }
+  }
+
+  &-inited {
+    &-clear {
+      margin-bottom: 10px;
+    }
+
+    &-github {
+      float: right;
+    }
+
+    &-preview {
+      position: fixed;
+      left: 0;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      background: rgba(0, 0, 0, 0.2);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      z-index: 9999999999;
+
+      & > video {
+        max-width: 90%;
+        max-height: 90%;
+      }
+    }
+    &-download {
+      position: fixed;
+      left: 0;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      background: rgba(0, 0, 0, 0.2);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      z-index: 9999999999;
+    }
+  }
 }
diff --git a/src/fsm.js b/src/fsm.js
index 6afb906..5a59f95 100644
--- a/src/fsm.js
+++ b/src/fsm.js
@@ -1,10 +1,18 @@
-import { createMachine } from 'xstate';
+import { createMachine, actions } from 'xstate';
 import { ipcRenderer } from 'electron';
+import prettyBytes from 'pretty-bytes';
+import { uniqBy } from 'lodash';
+import { message } from 'antd';
 
 export default createMachine(
   {
     id: '微信视频号下载工具',
-    context: {},
+    context: {
+      captureList: [],
+      currentUrl: '',
+      savePath: '',
+      downloadProgress: 0,
+    },
     initial: '检测初始化',
     states: {
       检测初始化: {
@@ -54,14 +62,22 @@ export default createMachine(
           e_视频捕获: {
             actions: 'action_视频捕获',
           },
+          e_开启服务失败: {
+            target: '开启服务失败',
+          },
+          e_清空捕获记录: {
+            actions: 'action_清空捕获记录',
+          },
         },
         states: {
           空闲: {
             on: {
               e_下载: {
+                actions: 'action_设置当前地址',
                 target: '下载',
               },
               e_预览: {
+                actions: 'action_设置当前地址',
                 target: '预览',
               },
               e_改变规则: {
@@ -74,9 +90,12 @@ export default createMachine(
             states: {
               选择位置: {
                 on: {
-                  e_确认位置: { target: '下载中' },
+                  e_确认位置: { actions: 'action_存储下载位置', target: '下载中' },
                   e_取消: { target: '#初始化完成.空闲' },
                 },
+                invoke: {
+                  src: 'invoke_选择下载位置',
+                },
               },
               下载中: {
                 on: {
@@ -84,13 +103,17 @@ export default createMachine(
                     actions: 'action_进度变化',
                   },
                   e_下载完成: {
-                    target: '下载完成',
+                    target: '#初始化完成.空闲',
+                    actions: 'action_下载完成',
                   },
                   e_下载失败: {
                     target: '#初始化完成.空闲',
                     actions: 'action_下载失败',
                   },
                 },
+                invoke: {
+                  src: 'invoke_下载视频',
+                },
               },
               下载完成: {
                 on: {
@@ -111,6 +134,13 @@ export default createMachine(
           },
         },
       },
+      开启服务失败: {
+        on: {
+          e_重试: {
+            target: '初始化完成',
+          },
+        },
+      },
     },
   },
   {
@@ -131,17 +161,102 @@ export default createMachine(
           .finally(() => send('e_重新检测'));
       },
       invoke_启动服务: (context, event) => send => {
-        ipcRenderer.invoke('invoke_启动服务');
-        // .then(data => {})
-        // .catch(data => {});
+        const fnDealVideoCapture = (eName, { url, size }) => {
+          send({ type: 'e_视频捕获', url, size });
+        };
+
+        ipcRenderer
+          .invoke('invoke_启动服务')
+          .then(() => {
+            ipcRenderer.on('VIDEO_CAPTURE', fnDealVideoCapture);
+          })
+          .catch(() => {
+            send('e_开启服务失败');
+          });
+
+        return () => {
+          ipcRenderer.removeListener('VIDEO_CAPTURE', fnDealVideoCapture);
+        };
       },
+      invoke_选择下载位置: (context, event) => send => {
+        ipcRenderer
+          .invoke('invoke_选择下载位置')
+          .then(data => {
+            send({
+              type: 'e_确认位置',
+              data,
+            });
+          })
+          .catch(() => send('e_取消'));
+      },
+      invoke_下载视频:
+        ({ currentUrl, savePath }) =>
+        send => {
+          ipcRenderer
+            .invoke('invoke_下载视频', {
+              url: currentUrl,
+              savePath,
+            })
+            .then(({ fullFileName }) => {
+              send({ type: 'e_下载完成', fullFileName, currentUrl });
+            })
+            .catch(() => {
+              send('e_下载失败');
+            });
+
+          ipcRenderer.on('e_进度变化', (event, arg) => {
+            send({
+              type: 'e_进度变化',
+              data: arg,
+            });
+          });
+
+          return () => {
+            ipcRenderer.removeAllListeners('e_进度变化');
+          };
+        },
     },
     actions: {
-      action_视频捕获: (context, event) => {},
-      action_改变规则: (context, event) => {},
-      action_进度变化: (context, event) => {},
-      action_下载失败: (context, event) => {},
-      action_打开文件位置: (context, event) => {},
+      action_视频捕获: actions.assign(({ captureList }, { size, url }) => {
+        captureList.push({ size, url, prettySize: prettyBytes(+size) });
+
+        return {
+          captureList: uniqBy(captureList, 'url'),
+        };
+      }),
+      action_清空捕获记录: actions.assign(() => {
+        return {
+          captureList: [],
+        };
+      }),
+      action_设置当前地址: actions.assign((_, { url }) => {
+        return {
+          currentUrl: url,
+        };
+      }),
+      action_存储下载位置: actions.assign((_, { data }) => {
+        return {
+          savePath: data,
+        };
+      }),
+      action_进度变化: actions.assign((_, { data }) => {
+        return {
+          downloadProgress: ~~(data * 100),
+        };
+      }),
+      action_下载完成: actions.assign(({ captureList }, { fullFileName, currentUrl }) => {
+        return {
+          captureList: captureList.map(item => {
+            if (item.url === currentUrl) {
+              item.fullFileName = fullFileName;
+            }
+            return item;
+          }),
+        };
+      }),
+      action_下载失败: actions.log(() => {
+        message.error('网络错误,请重试');
+      }),
     },
   },
 );
diff --git a/src/index.js b/src/index.js
index 65a8dbf..5a1ffb7 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,6 +2,8 @@ import React from 'react';
 import ReactDOM from 'react-dom/client';
 import App from './App';
 
+import 'antd/dist/antd.css';
+
 const root = ReactDOM.createRoot(document.getElementById('root'));
 root.render(
   <React.StrictMode>